diff options
author | Robert Collins <robertc@robertcollins.net> | 2008-12-05 10:00:46 +1100 |
---|---|---|
committer | Robert Collins <robertc@robertcollins.net> | 2008-12-05 10:00:46 +1100 |
commit | d644778b747f3dbed177530d431da8a830cc070a (patch) | |
tree | 12bb69dc422f9e122fe52a0026e3bdc1b7cdace4 /lib | |
parent | 60505d1740cdf408c48c2b22af5de6e22ff17cdf (diff) | |
download | testresources-d644778b747f3dbed177530d431da8a830cc070a.tar.gz |
Implement non-optimising resource dependencies/cascading by extending the TestResource interface.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/testresources/__init__.py | 58 | ||||
-rw-r--r-- | lib/testresources/tests/test_optimising_test_suite.py | 2 | ||||
-rw-r--r-- | lib/testresources/tests/test_resourced_test_case.py | 29 | ||||
-rw-r--r-- | lib/testresources/tests/test_test_resource.py | 33 |
4 files changed, 111 insertions, 11 deletions
diff --git a/lib/testresources/__init__.py b/lib/testresources/__init__.py index 0228421..3db40bd 100644 --- a/lib/testresources/__init__.py +++ b/lib/testresources/__init__.py @@ -171,6 +171,14 @@ class TestLoader(unittest.TestLoader): class TestResource(object): """A resource that can be shared across tests. + :cvar resources: The same as the resources list on an instance, the default + constructor will look for the class instance and copy it. This is a + convenience to avoid needing to define __init__ solely to alter the + dependencies list. + :ivar resources: The resources that this resource needs. Calling + neededResources will return the closure of this resource and its needed + resources. The resources list is in the same format as resources on a + test case - a list of tuples (attribute_name, resource). :ivar setUpCost: The relative cost to construct a resource of this type. One good approach is to set this to the number of seconds it normally takes to set up the resource. @@ -186,6 +194,13 @@ class TestResource(object): self._dirty = False self._uses = 0 self._currentResource = None + self.resources = list(getattr(self.__class__, resources, [])) + + def clean_all(self, resource): + """Clean the dependencies from resource, and then resource itself.""" + for name, manager in self.resources: + manager.finishedWith(getattr(resource, name)) + self.clean(resource) def clean(self, resource): """Override this to class method to hook into resource removal.""" @@ -211,7 +226,7 @@ class TestResource(object): """ self._uses -= 1 if self._uses == 0: - self.clean(resource) + self.clean_all(resource) self._setResource(None) elif self._dirty: self._resetResource(resource) @@ -225,20 +240,51 @@ class TestResource(object): that it is no longer needed. """ if self._uses == 0: - self._setResource(self.make()) + self._setResource(self.make_all()) elif self._dirty: self._resetResource(self._currentResource) self._uses += 1 return self._currentResource - def make(self): - """Override this to construct resources.""" + def make_all(self): + """Make the dependencies of this resource and this resource.""" + dependency_resources = {} + for name, resource in self.resources: + dependency_resources[name] = resource.getResource() + result = self.make(dependency_resources) + for name, value in dependency_resources.items(): + setattr(result, name, value) + return result + + def make(self, dependency_resources): + """Override this to construct resources. + + :param dependency_resources: A dict mapping name -> resource instance + for the resources specified as dependencies. + """ raise NotImplementedError( "Override make to construct resources.") + def neededResources(self): + """Return the resources needed for this resource, including self. + + :return: A list of needed resources, in topological deepest-first + order. + """ + seen = set([self]) + result = [] + for name, resource in self.resources: + for resource in resource.neededResources(): + if resource in seen: + continue + seen.add(resource) + result.append(resource) + result.append(self) + return result + def _resetResource(self, old_resource): - self.clean(old_resource) - self._setResource(self.make()) + self.clean_all(old_resource) + self._setResource(self.make_all()) def _setResource(self, new_resource): """Set the current resource to a new value.""" diff --git a/lib/testresources/tests/test_optimising_test_suite.py b/lib/testresources/tests/test_optimising_test_suite.py index a707212..2b5ff2c 100644 --- a/lib/testresources/tests/test_optimising_test_suite.py +++ b/lib/testresources/tests/test_optimising_test_suite.py @@ -43,7 +43,7 @@ class MakeCounter(testresources.TestResource): def clean(self, resource): self.cleans += 1 - def make(self): + def make(self, dependency_resources): self.makes += 1 return "boo" diff --git a/lib/testresources/tests/test_resourced_test_case.py b/lib/testresources/tests/test_resourced_test_case.py index 31b29fa..73fffac 100644 --- a/lib/testresources/tests/test_resourced_test_case.py +++ b/lib/testresources/tests/test_resourced_test_case.py @@ -35,10 +35,14 @@ class MockResource(testresources.TestResource): testresources.TestResource.__init__(self) self._resource = resource - def make(self): + def make(self, dependency_resources): return self._resource +class MockResourceInstance(object): + """A resource instance.""" + + class TestResourcedTestCase(testtools.TestCase): def setUp(self): @@ -65,6 +69,17 @@ class TestResourcedTestCase(testtools.TestCase): self.assertEqual(self.resource, self.resourced_case.foo) self.assertEqual('bar_resource', self.resourced_case.bar) + def testSetUpResourcesSetsUpDependences(self): + resource = MockResourceInstance() + self.resource_manager = MockResource(resource) + self.resourced_case.resources = [('foo', self.resource_manager)] + # Give the 'foo' resource access to a 'bar' resource + self.resource_manager.resources.append( + ('bar', MockResource('bar_resource'))) + self.resourced_case.setUpResources() + self.assertEqual(resource, self.resourced_case.foo) + self.assertEqual('bar_resource', self.resourced_case.foo.bar) + def testSetUpUsesResource(self): # setUpResources records a use of each declared resource. self.resourced_case.resources = [("foo", self.resource_manager)] @@ -85,6 +100,18 @@ class TestResourcedTestCase(testtools.TestCase): self.resourced_case.tearDownResources() self.assertEqual(self.resource_manager._uses, 0) + def testTearDownResourcesStopsUsingDependencies(self): + resource = MockResourceInstance() + dep1 = MockResource('bar_resource') + self.resource_manager = MockResource(resource) + self.resourced_case.resources = [('foo', self.resource_manager)] + # Give the 'foo' resource access to a 'bar' resource + self.resource_manager.resources.append( + ('bar', dep1)) + self.resourced_case.setUpResources() + self.resourced_case.tearDownResources() + self.assertEqual(dep1._uses, 0) + def testSingleWithSetup(self): # setUp and tearDown invoke setUpResources and tearDownResources. self.resourced_case.resources = [("foo", self.resource_manager)] diff --git a/lib/testresources/tests/test_test_resource.py b/lib/testresources/tests/test_test_resource.py index 59f8729..754ecd3 100644 --- a/lib/testresources/tests/test_test_resource.py +++ b/lib/testresources/tests/test_test_resource.py @@ -40,7 +40,7 @@ class MockResource(testresources.TestResource): def clean(self, resource): self.cleans += 1 - def make(self): + def make(self, dependency_resources): self.makes += 1 return "Boo!" @@ -48,7 +48,8 @@ class MockResource(testresources.TestResource): class TestTestResource(testtools.TestCase): def testUnimplementedGetResource(self): - # By default, TestResource raises NotImplementedError on getResource. + # By default, TestResource raises NotImplementedError on getResource + # because make is not defined initially. resource_manager = testresources.TestResource() self.assertRaises(NotImplementedError, resource_manager.getResource) @@ -64,6 +65,32 @@ class TestTestResource(testtools.TestCase): resource_manager = testresources.TestResource() self.assertEqual(None, resource_manager._currentResource) + def testneededResourcesDefault(self): + # Calling neededResources on a default TestResource returns the + # resource. + resource = testresources.TestResource() + self.assertEqual([resource], resource.neededResources()) + + def testneededResourcesDependenciesFirst(self): + # Calling neededResources on a TestResource with dependencies puts the + # dependencies first. + resource = testresources.TestResource() + dep1 = testresources.TestResource() + dep2 = testresources.TestResource() + resource.resources.append(("dep1", dep1)) + resource.resources.append(("dep2", dep2)) + self.assertEqual([dep1, dep2, resource], resource.neededResources()) + + def testneededResourcesClosure(self): + # Calling neededResources on a TestResource with dependencies includes + # the needed resources of the needed resources. + resource = testresources.TestResource() + dep1 = testresources.TestResource() + dep2 = testresources.TestResource() + resource.resources.append(("dep1", dep1)) + dep1.resources.append(("dep2", dep2)) + self.assertEqual([dep2, dep1, resource], resource.neededResources()) + def testDefaultCosts(self): # The base TestResource costs 1 to set up and to tear down. resource_manager = testresources.TestResource() @@ -73,7 +100,7 @@ class TestTestResource(testtools.TestCase): def testGetResourceReturnsMakeResource(self): resource_manager = MockResource() resource = resource_manager.getResource() - self.assertEqual(resource_manager.make(), resource) + self.assertEqual(resource_manager.make({}), resource) def testGetResourceIncrementsUses(self): resource_manager = MockResource() |