#!/usr/bin/env python # # Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) # # SPDX-License-Identifier: BSD-2-Clause # from __future__ import absolute_import, division, print_function, \ unicode_literals import unittest import hypothesis.strategies as st from hypothesis import given from capdl import Spec, Untyped, Endpoint, IRQControl, Frame from capdl.Allocator import BestFitAllocator, AllocatorException from capdl.util import round_up from tests import CapdlTestCase class TestAllocator(CapdlTestCase): def assertValidSpec(self, allocator, spec, ut_size, child_size, children, uts): if ut_size >= child_size: allocator.allocate(spec) # now check if all the children got allocated for c in children: if not any(c in ut.children for ut in uts): self.fail("Child {0} not allocated!".format(c)) else: with self.assertRaises(AllocatorException): allocator.allocate(spec) @given(st.integers(min_value=4, max_value=64), st.integers(min_value=4, max_value=64)) def test_alloc_single_fun_obj(self, child_size_bits, ut_size_bits): """ Test allocating a single object from a single untyped. Vary the untyped size and object size, and make sure it either succeeds (should always succeed if the untyped size is big enough) or fails by throwing an AllocatorException """ allocator = BestFitAllocator() spec = Spec() ut = Untyped(name="test_ut", size_bits=ut_size_bits, paddr=1 << ut_size_bits) allocator.add_untyped(ut) child = Untyped(name="child_ut", size_bits=child_size_bits) spec.add_object(child) self.assertValidSpec(allocator, spec, ut_size_bits, child_size_bits, [child], [ut]) @staticmethod def alloc_children(spec, object_sizes): sum = 0 children = [] for i, size_bits in enumerate(object_sizes): sum += (1 << size_bits) child = Untyped(name="child ut {0}".format(i), size_bits=size_bits) spec.add_object(child) children.append(child) return sum, children @given(st.lists(st.integers(min_value=4, max_value=64), max_size=100, min_size=10), st.integers(min_value=4, max_value=64)) def test_alloc_multiple_fun_obj(self, object_sizes, ut_size_bits): """Test allocating multiple child objects from a single untyped""" allocator = BestFitAllocator() spec = Spec() (total_child_size, children) = TestAllocator.alloc_children(spec, object_sizes) ut = Untyped(name="test_ut", size_bits=ut_size_bits, paddr=1 << ut_size_bits) allocator.add_untyped(ut) self.assertValidSpec(allocator, spec, 1 << ut_size_bits, total_child_size, children, [ut]) @given(st.lists(st.integers(min_value=4, max_value=32), max_size=100, min_size=10), st.lists(st.integers(min_value=32, max_value=64), max_size=20, min_size=2)) def test_alloc_multiple_fun_multiple_untyped(self, object_sizes, ut_sizes): """Test allocating multiple children from multiple untyped""" allocator = BestFitAllocator() spec = Spec() (total_child_size, children) = TestAllocator.alloc_children(spec, object_sizes) untyped = [] paddr = 0x1 ut_size = 0 for i in range(0, len(ut_sizes)): size_bits = ut_sizes[i] paddr = round_up(paddr, 1 << size_bits) ut = Untyped("untyped_{0}".format(i), size_bits=size_bits, paddr=paddr) untyped.append(ut) allocator.add_untyped(ut) paddr += 1 << size_bits ut_size += 1 << size_bits self.assertValidSpec(allocator, spec, ut_size, total_child_size, children, untyped) def test_alloc_no_spec_no_untyped(self): """ Test allocating nothing from nothing works. """ BestFitAllocator().allocate(Spec()) def test_alloc_no_spec(self): """ Test allocating nothing from something works """ allocator = BestFitAllocator() allocator.add_untyped(Untyped(name="test_ut", size_bits=16, paddr=0)) allocator.allocate(Spec()) def test_alloc_no_untyped(self): """ Test allocating something from nothing fails elegantly """ ep = Endpoint(name="test_ep") spec = Spec() spec.add_object(ep) with self.assertRaises(AllocatorException): BestFitAllocator().allocate(spec) def test_alloc_unsized(self): """Test allocating an object with no size""" irq_ctrl = IRQControl("irq_control") spec = Spec() spec.add_object(irq_ctrl) allocator = BestFitAllocator() allocator.add_untyped(Untyped(name="test_ut", size_bits=16, paddr=0x10000)) allocator.allocate(spec) self.assertTrue(irq_ctrl in spec.objs) @given(st.integers(min_value=0xA0, max_value=0xD0), st.integers(min_value=0xB0, max_value=0xC0)) def test_alloc_paddr(self, unfun_paddr, ut_paddr): """ Test allocating a single unfun untyped in and out of bounds of an untyped """ allocator = BestFitAllocator() size_bits = 12 unfun_paddr = unfun_paddr << size_bits ut_paddr = ut_paddr << size_bits unfun_end = unfun_paddr + (1 << size_bits) ut_end = ut_paddr + (1 << size_bits) parent = Untyped("parent_ut", size_bits=size_bits, paddr=ut_paddr) allocator.add_untyped(parent) spec = Spec() child = Untyped("child_ut", size_bits=size_bits, paddr=unfun_paddr) spec.add_object(child) if unfun_paddr >= ut_paddr and unfun_end <= ut_end: self.assertValidSpec(allocator, spec, size_bits, size_bits, [child], [parent]) else: with self.assertRaises(AllocatorException): allocator.allocate(spec) @given(st.lists(st.integers(min_value=4, max_value=64), min_size=1), st.lists(st.integers(min_value=4, max_value=64), min_size=1)) def test_device_ut_only(self, ut_sizes, obj_sizes): """ Test allocating fun objects from only device untypeds """ allocator = BestFitAllocator() paddr = 0x1 for i in range(0, len(ut_sizes)): paddr = round_up(paddr, 1 << ut_sizes[i]) allocator.add_device_untyped( Untyped("device_untyped_{0}".format(i), size_bits=ut_sizes[i], paddr=paddr)) paddr += 1 << ut_sizes[i] spec = Spec() for i in range(0, len(obj_sizes)): spec.add_object(Untyped("obj_untyped_{0}".format(i), size_bits=obj_sizes[i])) with self.assertRaises(AllocatorException): allocator.allocate(spec) @given(st.integers(min_value=0, max_value=3)) def test_overlapping_paddr_smaller(self, offset): """Test allocating unfun objects with overlapping paddrs, where the overlapping paddr is from a smaller object """ paddr = 0xAAAA0000 size_bits = 16 overlap_paddr = paddr + offset * (1 << (size_bits-2)) allocator = BestFitAllocator() allocator.add_untyped(Untyped("parent", paddr=paddr, size_bits=size_bits)) spec = Spec() spec.add_object(Untyped("child", paddr=paddr, size_bits=size_bits)) spec.add_object(Untyped("overlap_child", paddr=overlap_paddr, size_bits=size_bits-2)) with self.assertRaises(AllocatorException): allocator.allocate(spec) def test_overlapping_paddr_larger(self): """Test allocating unfun objects with overlapping paddrs, where the overlapping paddr is from a larger object""" allocator = BestFitAllocator() paddr = 0xAAAA0000 allocator.add_untyped(Untyped("deadbeef", size_bits=16, paddr=paddr)) spec = Spec() spec.add_object(Untyped("obj_untyped_1", size_bits=14, paddr=paddr + 2 * (1 << 15))) spec.add_object(Untyped("obj_untyped_2", size_bits=15, paddr=paddr)) with self.assertRaises(AllocatorException): allocator.allocate(spec) @given(st.lists(st.integers(min_value=4, max_value=16), min_size=1, max_size=1000)) def test_placeholder_uts(self, sizes): """ Test allocating a collection of unfun objects that do not align and have placeholder uts between them """ allocator = BestFitAllocator() start_paddr = 1 << (max(sizes) + len(sizes).bit_length()) paddr = start_paddr ut_size = 0 children = [] spec = Spec() for i in range(0, len(sizes)): paddr = round_up(paddr, 1 << sizes[i]) ut = Untyped("ut_{0}".format(i), size_bits=sizes[i], paddr=paddr) spec.add_object(ut) paddr += 1 << sizes[i] ut_size += 1 << sizes[i] children.append(ut) ut_size_bits = (paddr - start_paddr).bit_length() ut = Untyped("ut_parent", size_bits=ut_size_bits, paddr=start_paddr) allocator.add_untyped(ut) self.assertValidSpec(allocator, spec, 1 << ut_size_bits, ut_size, children, [ut]) def test_regression_unfun_at_end(self): """ Ensure that if an unfun object is the last to get allocated, its root ut is included in the spec. """ allocator = BestFitAllocator() root_ut_A = Untyped("root_ut_A", size_bits=16, paddr=0x10000) root_ut_B = Untyped("root_ut_B", size_bits=16, paddr=0x20000) allocator.add_untyped(root_ut_A) allocator.add_untyped(root_ut_B) spec = Spec() my_frame_A0 = Frame("my_frame_A0") my_frame_A1 = Frame("my_frame_A1") my_pinned_frame_B = Frame("my_pinned_frame_B", paddr=0x20000) spec.add_object(my_frame_A0) spec.add_object(my_frame_A1) spec.add_object(my_pinned_frame_B) allocator.allocate(spec) self.assertIn(root_ut_B, spec.objs) # main test # other tests: for obj in (root_ut_A, root_ut_B, my_frame_A0, my_frame_A1, my_pinned_frame_B): self.assertIn(obj, spec.objs) for obj in (my_frame_A0, my_frame_A1): self.assertIn(obj, root_ut_A.children) for obj in (my_pinned_frame_B,): self.assertIn(obj, root_ut_B.children) if __name__ == '__main__': unittest.main()