From 3ef11fbf43c4e25d8cd63300c508759e8b466194 Mon Sep 17 00:00:00 2001 From: ramoj Date: Mon, 6 Jan 2025 02:33:02 +0000 Subject: [PATCH] More progress --- dftimewolf/lib/containers/manager.py | 109 ++++---- tests/lib/containers/manager.py | 376 ++++++++++++++++++++++++--- 2 files changed, 401 insertions(+), 84 deletions(-) diff --git a/dftimewolf/lib/containers/manager.py b/dftimewolf/lib/containers/manager.py index a7f45fa4..2548b240 100644 --- a/dftimewolf/lib/containers/manager.py +++ b/dftimewolf/lib/containers/manager.py @@ -1,6 +1,7 @@ """A ContainerManager class.""" +import dataclasses import threading import typing from typing import cast, Sequence, Type @@ -8,6 +9,16 @@ from dftimewolf.lib.containers import interface + +@dataclasses.dataclass +class _MODULE(): + name: str + dependencies: list[str] = dataclasses.field(default_factory=list) + storage: list[interface.AttributeContainer] = dataclasses.field( + default_factory=list) + completed: bool = False + + class ContainerManager(): """A ConatinerManager. @@ -25,54 +36,39 @@ class ContainerManager(): def __init__(self): """Initialise a ContainerManager.""" self._mutex = threading.Lock() - self._dependencies: dict[str, # Module name - list[str] # Module names the module depends on - ] = None - self._storage: dict[str, # Module name - list[interface.AttributeContainer] # A list of containers generated by the module # pylint: disable=line-too-long - ] = {} -# self._storage: dict[str, # Module name -# dict[str, # Container type name -# list[interface.AttributeContainer]] # A list of containers generated by the module of the type # pylint: disable=line-too-long -# ] = {} - - def ParseRecipe(self, recipe: dict[str, typing.Any]): + self._modules: dict[str, _MODULE] = None + + def ParseRecipe(self, recipe: dict[str, typing.Any]) -> None: """Parses a recipe to build the dependency graph.""" - self._dependencies = {} + self._modules = {} for module in recipe.get('preflights', []) + recipe.get('modules', []): name = module.get('runtime_name', module.get('name', None)) if not name: raise RuntimeError("Name not set for module in recipe") - self._dependencies[name] = module.get('wants', []) + [name] + self._modules[name] = _MODULE( + name=name, dependencies=module.get('wants', []) + [name]) def StoreContainer(self, source_module: str, - container: interface.AttributeContainer): + container: interface.AttributeContainer) -> None: """Adds a container to storage for later retrieval. Args: source_module: The module that generated the container. container: The container to store. """ - if not self._dependencies: + if not self._modules: raise RuntimeError("Container manager has not parsed a recipe yet") with self._mutex: - if source_module not in self._storage: -# self._storage[source_module] = {} -# if container.CONTAINER_TYPE not in self._storage[source_module]: -# self._storage[source_module][container.CONTAINER_TYPE] = [] -# self._storage[source_module][container.CONTAINER_TYPE].append(container) - self._storage[source_module] = [] - self._storage[source_module].append(container) + self._modules[source_module].storage.append(container) def GetContainers(self, requesting_module: str, container_class: Type[interface.AttributeContainer], - pop: bool = False, metadata_filter_key: str | None = None, metadata_filter_value: typing.Any = None ) -> Sequence[interface.AttributeContainer]: @@ -84,14 +80,13 @@ def GetContainers(self, Args: requesting_module: The module requesting the containers. container_class: The type of container to retrieve. - pop: True to remove requested containers from storage. metadata_filter_key: An optional metadata key to use to filter. metadata_filter_value: An optional metadata value to use to filter. Returns: A sequence of containers that match the various filters. """ - if not self._dependencies: + if not self._modules: raise RuntimeError("Container manager has not parsed a recipe yet") if bool(metadata_filter_key) ^ bool(metadata_filter_value): raise RuntimeError('Must specify both key and value for attribute filter') @@ -99,39 +94,45 @@ def GetContainers(self, with self._mutex: ret_val = [] - for dependency in self._dependencies[requesting_module]: - containers = self._storage.get(dependency, []) # All the containers generated by the dependency - self._storage[dependency] = [] # Remove all containers + for dependency in self._modules[requesting_module].dependencies: + containers = self._modules[dependency].storage for c in containers: - if c.CONTAINER_TYPE != container_class.CONTAINER_TYPE: - # Not the type we want - put it back - self._storage[dependency].append(c) + if (c.CONTAINER_TYPE != container_class.CONTAINER_TYPE or + (metadata_filter_key and + c.metadata.get(metadata_filter_key) != metadata_filter_value)): continue - if (metadata_filter_key and - c.metadata.get(metadata_filter_key) != metadata_filter_value): - # Doesn't match metadat filter - put it back - self._storage[dependency].append(c) - continue - - # if we get this far, we want to deliver the container to the caller. ret_val.append(c) - if not pop: - # The caller wants it left in the state anyway - put it back. - self._storage[dependency].append(c) - return cast(Sequence[interface.AttributeContainer], ret_val) -# def DedupeContainers(self, -# container_class: Type[interface.AttributeContainer]): -# """Dedupe containers. -# -# Unsure if this is needed at the time of writing, but the functionality -# exists in what this manager will replace. -# """ -# if not self._dependencies: -# raise RuntimeError("Container manager has not parsed a recipe yet") -# -# raise NotImplementedError() + + def CompleteModule(self, module_name: str) -> None: + """Mark a module as completed in storage. + + Containers can consume large amounts of memory. Marking a module as + completed tells the container manager that containers no longer needed can + be removed from storage to free up that memory. + + Args: + module_name: The module that has completed running. + """ + with self._mutex: + self._modules[module_name].completed = True + + # If all modules `module_name` is a dependency for are marked completed, + # then containers it generated are no longer needed. + for key in self._modules: + if self._CheckDependenciesCompletion(key): + for c in self._modules[key].storage: + del c + self._modules[key].storage = [] + + def _CheckDependenciesCompletion(self, module_name: str) -> bool: + """For a module, checks if other modules that depend on are complete.""" + for key in self._modules: + if module_name in self._modules[key].dependencies: + if not self._modules[key].completed: + return False + return True diff --git a/tests/lib/containers/manager.py b/tests/lib/containers/manager.py index 49dd05a3..787e13c7 100644 --- a/tests/lib/containers/manager.py +++ b/tests/lib/containers/manager.py @@ -79,6 +79,18 @@ def __eq__(self, other: "_TestContainer1"): return self.param == other.param +class _TestContainer2(interface.AttributeContainer): + """A Test container.""" + CONTAINER_TYPE = 'test2' + + def __init__(self, param: str): + super().__init__() + self.param = param + + def __eq__(self, other: "_TestContainer2"): + return self.param == other.param + + class ContainerManagerTest(unittest.TestCase): """Tests for the ContainerManager.""" @@ -87,10 +99,6 @@ def setUp(self): super().setUp() self._container_manager = manager.ContainerManager() -# -# def tearDown(self): -# """Tear down.""" -# super().tearDown() def test_GetWithoutRecipeParsed(self): """Tests an error is thrown getting containers before a recipe is parsed.""" @@ -110,7 +118,8 @@ def test_ContainerVisibility(self): self._container_manager.ParseRecipe(_TEST_RECIPE) # Have every module store a unique container - for c in _TEST_RECIPE.get('preflights') + _TEST_RECIPE.get('modules'): + for c in (_TEST_RECIPE.get('preflights', []) + + _TEST_RECIPE.get('modules', [])): name = c.get('runtime_name', c['name']) self._container_manager.StoreContainer( source_module=name, container=_TestContainer1(f'Stored by {name}')) @@ -118,8 +127,7 @@ def test_ContainerVisibility(self): with self.subTest('Preflight1'): # Only the container generated by itself actual = self._container_manager.GetContainers( - requesting_module='Preflight1', - container_class=_TestContainer1) + requesting_module='Preflight1', container_class=_TestContainer1) self.assertEqual(len(actual), 1) self.assertIn(_TestContainer1('Stored by Preflight1'), actual) self.assertNotIn(_TestContainer1('Stored by Preflight2_1'), actual) @@ -133,8 +141,7 @@ def test_ContainerVisibility(self): with self.subTest('Preflight2_1'): # The container from itself, and Preflight1 actual = self._container_manager.GetContainers( - requesting_module='Preflight2_1', - container_class=_TestContainer1) + requesting_module='Preflight2_1', container_class=_TestContainer1) self.assertEqual(len(actual), 2) self.assertIn(_TestContainer1('Stored by Preflight1'), actual) self.assertIn(_TestContainer1('Stored by Preflight2_1'), actual) @@ -148,8 +155,7 @@ def test_ContainerVisibility(self): with self.subTest('Preflight2_2'): # The container from itself, and Preflight1 actual = self._container_manager.GetContainers( - requesting_module='Preflight2_2', - container_class=_TestContainer1) + requesting_module='Preflight2_2', container_class=_TestContainer1) self.assertEqual(len(actual), 2) self.assertIn(_TestContainer1('Stored by Preflight1'), actual) self.assertNotIn(_TestContainer1('Stored by Preflight2_1'), actual) @@ -163,8 +169,7 @@ def test_ContainerVisibility(self): with self.subTest('ModuleA'): # The container from itself, and Preflight1 actual = self._container_manager.GetContainers( - requesting_module='ModuleA', - container_class=_TestContainer1) + requesting_module='ModuleA', container_class=_TestContainer1) self.assertEqual(len(actual), 2) self.assertIn(_TestContainer1('Stored by Preflight1'), actual) self.assertNotIn(_TestContainer1('Stored by Preflight2_1'), actual) @@ -178,8 +183,7 @@ def test_ContainerVisibility(self): with self.subTest('ModuleB'): # The container from itself only actual = self._container_manager.GetContainers( - requesting_module='ModuleB', - container_class=_TestContainer1) + requesting_module='ModuleB', container_class=_TestContainer1) self.assertEqual(len(actual), 1) self.assertNotIn(_TestContainer1('Stored by Preflight1'), actual) self.assertNotIn(_TestContainer1('Stored by Preflight2_1'), actual) @@ -193,8 +197,7 @@ def test_ContainerVisibility(self): with self.subTest('ModuleC'): # The container from itself only actual = self._container_manager.GetContainers( - requesting_module='ModuleC', - container_class=_TestContainer1) + requesting_module='ModuleC', container_class=_TestContainer1) self.assertEqual(len(actual), 1) self.assertNotIn(_TestContainer1('Stored by Preflight1'), actual) self.assertNotIn(_TestContainer1('Stored by Preflight2_1'), actual) @@ -208,8 +211,7 @@ def test_ContainerVisibility(self): with self.subTest('ModuleD'): # The container from itself, Modules B and C actual = self._container_manager.GetContainers( - requesting_module='ModuleD', - container_class=_TestContainer1) + requesting_module='ModuleD', container_class=_TestContainer1) self.assertEqual(len(actual), 3) self.assertNotIn(_TestContainer1('Stored by Preflight1'), actual) self.assertNotIn(_TestContainer1('Stored by Preflight2_1'), actual) @@ -223,8 +225,7 @@ def test_ContainerVisibility(self): with self.subTest('ModuleE'): # The container from itself, Modules A, B and D actual = self._container_manager.GetContainers( - requesting_module='ModuleE', - container_class=_TestContainer1) + requesting_module='ModuleE', container_class=_TestContainer1) self.assertEqual(len(actual), 4) self.assertNotIn(_TestContainer1('Stored by Preflight1'), actual) self.assertNotIn(_TestContainer1('Stored by Preflight2_1'), actual) @@ -236,23 +237,338 @@ def test_ContainerVisibility(self): self.assertIn(_TestContainer1('Stored by ModuleE'), actual) def test_MultipleContainerTypes(self): - """Tests that when a generator stores multiple types, only the requested are returned.""" - self.fail("Not implemented yet.") + """Tests that when a generator stores multiple container types, only the + requested are returned. + """ + self._container_manager.ParseRecipe(_TEST_RECIPE) + + self._container_manager.StoreContainer( + source_module='Preflight1', container=_TestContainer1('param1')) + self._container_manager.StoreContainer( + source_module='Preflight1', container=_TestContainer2('param2')) + + actual_tc1 = self._container_manager.GetContainers( + requesting_module='ModuleA', container_class=_TestContainer1) + actual_tc2 = self._container_manager.GetContainers( + requesting_module='ModuleA', container_class=_TestContainer2) + + self.assertEqual(len(actual_tc1), 1) + self.assertEqual(len(actual_tc2), 1) + + self.assertIn(_TestContainer1('param1'), actual_tc1) + self.assertIn(_TestContainer2('param2'), actual_tc2) def test_MetadataFilter(self): """Tests that metdata filters correctly filter containers returned.""" - self.fail("Not implemented yet.") + self._container_manager.ParseRecipe(_TEST_RECIPE) + c1 = _TestContainer1('param1') + c2 = _TestContainer1('param2') + c3 = _TestContainer1('param3') + c4 = _TestContainer1('param4') + + c1.SetMetadata('Key_1', 'Value_1') + c2.SetMetadata('Key_1', 'Value_2') + c3.SetMetadata('Key_2', 'Value_1') + c4.SetMetadata('Key_2', 'Value_1') + + self._container_manager.StoreContainer( + source_module='Preflight1', container=c1) + self._container_manager.StoreContainer( + source_module='Preflight1', container=c2) + self._container_manager.StoreContainer( + source_module='Preflight1', container=c3) + self._container_manager.StoreContainer( + source_module='Preflight1', container=c4) + + with self.subTest('No metadata filter'): + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', container_class=_TestContainer1) + self.assertEqual(len(actual), 4) + self.assertIn(_TestContainer1('param1'), actual) + self.assertIn(_TestContainer1('param2'), actual) + self.assertIn(_TestContainer1('param3'), actual) + self.assertIn(_TestContainer1('param4'), actual) + + with self.subTest('No matching key'): + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', + container_class=_TestContainer1, + metadata_filter_key='no_matching_key', + metadata_filter_value='Value_1') + self.assertEqual(len(actual), 0) + + with self.subTest('No matching value'): + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', + container_class=_TestContainer1, + metadata_filter_key='Key_1', + metadata_filter_value='no_matching_value') + self.assertEqual(len(actual), 0) + + with self.subTest('key_1_value_1'): + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', + container_class=_TestContainer1, + metadata_filter_key='Key_1', + metadata_filter_value='Value_1') + self.assertEqual(len(actual), 1) + self.assertIn(_TestContainer1('param1'), actual) + + with self.subTest('key_2_value_1'): + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', + container_class=_TestContainer1, + metadata_filter_key='Key_2', + metadata_filter_value='Value_1') + self.assertEqual(len(actual), 2) + self.assertIn(_TestContainer1('param3'), actual) + self.assertIn(_TestContainer1('param4'), actual) + + def test_MetadataFilterErrors(self): + """Tests error modes using metadata filters on retrieval.""" + self._container_manager.ParseRecipe(_TEST_RECIPE) + c = _TestContainer1('param1') + c.SetMetadata('key', 'value') + + self._container_manager.StoreContainer( + source_module='Preflight1', container=c) + + with self.subTest('No key'): + with self.assertRaisesRegex( + RuntimeError, 'Must specify both key and value for attribute filter'): + self._container_manager.GetContainers( + requesting_module='ModuleA', + container_class=_TestContainer1, + metadata_filter_key=None, + metadata_filter_value='value') + + with self.subTest('No value'): + with self.assertRaisesRegex( + RuntimeError, 'Must specify both key and value for attribute filter'): + self._container_manager.GetContainers( + requesting_module='ModuleA', + container_class=_TestContainer1, + metadata_filter_key='key') + + def test_MarkCompletion(self): + """Tests container cleanup when marking modules as completed.""" + self._container_manager.ParseRecipe(_TEST_RECIPE) + + # Have every module store a unique container + for c in (_TEST_RECIPE.get('preflights', []) + + _TEST_RECIPE.get('modules', [])): + name = c.get('runtime_name', c['name']) + self._container_manager.StoreContainer( + source_module=name, container=_TestContainer1(f'Stored by {name}')) + + with self.subTest('Preflight1'): + self._container_manager.CompleteModule('Preflight1') + + actual = self._container_manager.GetContainers( + requesting_module='Preflight1', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by Preflight1'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='Preflight2_1', container_class=_TestContainer1) + self.assertEqual(2, len(actual)) + self.assertIn(_TestContainer1('Stored by Preflight1'), actual) + self.assertIn(_TestContainer1('Stored by Preflight2_1'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='Preflight2_2', container_class=_TestContainer1) + self.assertEqual(2, len(actual)) + self.assertIn(_TestContainer1('Stored by Preflight1'), actual) + self.assertIn(_TestContainer1('Stored by Preflight2_2'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', container_class=_TestContainer1) + self.assertEqual(2, len(actual)) + self.assertIn(_TestContainer1('Stored by Preflight1'), actual) + self.assertIn(_TestContainer1('Stored by ModuleA'), actual) + + with self.subTest('Preflight2_1'): + self._container_manager.CompleteModule('Preflight2_1') + + actual = self._container_manager.GetContainers( + requesting_module='Preflight2_1', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by Preflight1'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='Preflight2_2', container_class=_TestContainer1) + self.assertEqual(2, len(actual)) + self.assertIn(_TestContainer1('Stored by Preflight1'), actual) + self.assertIn(_TestContainer1('Stored by Preflight2_2'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', container_class=_TestContainer1) + self.assertEqual(2, len(actual)) + self.assertIn(_TestContainer1('Stored by Preflight1'), actual) + self.assertIn(_TestContainer1('Stored by ModuleA'), actual) + + with self.subTest('Preflight2_2'): + self._container_manager.CompleteModule('Preflight2_2') + + actual = self._container_manager.GetContainers( + requesting_module='Preflight2_1', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by Preflight1'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='Preflight2_2', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by Preflight1'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', container_class=_TestContainer1) + self.assertEqual(2, len(actual)) + self.assertIn(_TestContainer1('Stored by Preflight1'), actual) + self.assertIn(_TestContainer1('Stored by ModuleA'), actual) + + with self.subTest('ModuleA'): + self._container_manager.CompleteModule('ModuleA') + + actual = self._container_manager.GetContainers( + requesting_module='Preflight2_1', container_class=_TestContainer1) + self.assertEqual(0, len(actual)) + + actual = self._container_manager.GetContainers( + requesting_module='Preflight2_2', container_class=_TestContainer1) + self.assertEqual(0, len(actual)) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleA'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleE', container_class=_TestContainer1) + self.assertEqual(4, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleA'), actual) + self.assertIn(_TestContainer1('Stored by ModuleB'), actual) + self.assertIn(_TestContainer1('Stored by ModuleD'), actual) + self.assertIn(_TestContainer1('Stored by ModuleE'), actual) + + with self.subTest('ModuleB'): + self._container_manager.CompleteModule('ModuleB') + + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleA'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleB', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleB'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleC', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleC'), actual) - def test_PoppingContainers(self): - """Tests container popping.""" - self.fail("Not implemented yet.") + actual = self._container_manager.GetContainers( + requesting_module='ModuleD', container_class=_TestContainer1) + self.assertEqual(3, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleB'), actual) + self.assertIn(_TestContainer1('Stored by ModuleC'), actual) + self.assertIn(_TestContainer1('Stored by ModuleD'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleE', container_class=_TestContainer1) + self.assertEqual(4, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleA'), actual) + self.assertIn(_TestContainer1('Stored by ModuleB'), actual) + self.assertIn(_TestContainer1('Stored by ModuleD'), actual) + self.assertIn(_TestContainer1('Stored by ModuleE'), actual) - # Get container, non-popped + with self.subTest('ModuleC'): + self._container_manager.CompleteModule('ModuleC') - # Then, get continaer, popped + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleA'), actual) - # then again, none returned + actual = self._container_manager.GetContainers( + requesting_module='ModuleB', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleB'), actual) + actual = self._container_manager.GetContainers( + requesting_module='ModuleC', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleC'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleD', container_class=_TestContainer1) + self.assertEqual(3, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleB'), actual) + self.assertIn(_TestContainer1('Stored by ModuleC'), actual) + self.assertIn(_TestContainer1('Stored by ModuleD'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleE', container_class=_TestContainer1) + self.assertEqual(4, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleA'), actual) + self.assertIn(_TestContainer1('Stored by ModuleB'), actual) + self.assertIn(_TestContainer1('Stored by ModuleD'), actual) + self.assertIn(_TestContainer1('Stored by ModuleE'), actual) + + with self.subTest('ModuleD'): + self._container_manager.CompleteModule('ModuleD') + + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleA'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleB', container_class=_TestContainer1) + self.assertEqual(1, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleB'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleC', container_class=_TestContainer1) + self.assertEqual(0, len(actual)) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleD', container_class=_TestContainer1) + self.assertEqual(2, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleB'), actual) + self.assertIn(_TestContainer1('Stored by ModuleD'), actual) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleE', container_class=_TestContainer1) + self.assertEqual(4, len(actual)) + self.assertIn(_TestContainer1('Stored by ModuleA'), actual) + self.assertIn(_TestContainer1('Stored by ModuleB'), actual) + self.assertIn(_TestContainer1('Stored by ModuleD'), actual) + self.assertIn(_TestContainer1('Stored by ModuleE'), actual) + + with self.subTest('ModuleE'): + self._container_manager.CompleteModule('ModuleE') + + actual = self._container_manager.GetContainers( + requesting_module='ModuleA', container_class=_TestContainer1) + self.assertEqual(0, len(actual)) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleB', container_class=_TestContainer1) + self.assertEqual(0, len(actual)) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleC', container_class=_TestContainer1) + self.assertEqual(0, len(actual)) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleD', container_class=_TestContainer1) + self.assertEqual(0, len(actual)) + + actual = self._container_manager.GetContainers( + requesting_module='ModuleE', container_class=_TestContainer1) + self.assertEqual(0, len(actual)) if __name__ == '__main__':