| #!/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() |