summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorRobert Collins <robertc@robertcollins.net>2013-01-21 01:02:15 +1300
committerRobert Collins <robertc@robertcollins.net>2013-01-21 01:02:15 +1300
commit932199bcca7cd222d752f014a04b3bbfddaa1de9 (patch)
tree05901a9dbea188194808f2a95a4e57d9dcde690e /lib
parent4c2dc417782bdda3f4bf5e064339c0714ff93d8a (diff)
downloadtestresources-932199bcca7cd222d752f014a04b3bbfddaa1de9.tar.gz
Make FixtureResource.reset actually call fixture.reset().
Diffstat (limited to 'lib')
-rw-r--r--lib/testresources/__init__.py76
-rw-r--r--lib/testresources/tests/__init__.py6
-rw-r--r--lib/testresources/tests/test_test_resource.py59
3 files changed, 116 insertions, 25 deletions
diff --git a/lib/testresources/__init__.py b/lib/testresources/__init__.py
index 217280f..bda974a 100644
--- a/lib/testresources/__init__.py
+++ b/lib/testresources/__init__.py
@@ -546,6 +546,7 @@ class TestResourceManager(object):
:param dependency_resources: A dict mapping name -> resource instance
for the resources specified as dependencies.
+ :return: The made resource.
"""
raise NotImplementedError(
"Override make to construct resources.")
@@ -568,24 +569,71 @@ class TestResourceManager(object):
return result
def reset(self, old_resource, result=None):
- """Overridable method to return a clean version of old_resource.
+ """Return a clean version of old_resource.
By default, the resource will be cleaned then remade if it had
- previously been `dirtied`.
-
- This function needs to take the dependent resource stack into
- consideration as _make_all and _clean_all do.
-
- :return: The new resource.
+ previously been `dirtied` by the helper self._reset() - which is the
+ extension point folk should override to customise reset behaviour.
+
+ This function takes the dependent resource stack into consideration as
+ _make_all and _clean_all do. The inconsistent naming is because reset
+ is part of the public interface, but _make_all and _clean_all is not.
+
+ Note that if a resource A holds a lock or other blocking thing on
+ a dependency D, reset will result in this call sequence over a
+ getResource(), dirty(), getResource(), finishedWith(), finishedWith()
+ sequence:
+ B.make(), A.make(), B.reset(), A.reset(), A.clean(), B.clean()
+ Thus it is important that B.reset not assume that A has been cleaned or
+ reset before B is reset: it should arrange to reference count, lazy
+ cleanup or forcibly reset resource in some fashion.
+
+ As an example, consider that B is a database with sample data, and
+ A is an application server serving content from it. B._reset() should
+ disconnect all database clients, reset the state of the database, and
+ A._reset() should tell the application server to dump any internal
+ caches it might have.
+
+ In principle we might make a richer API to allow before-and-after
+ reset actions, but so far that hasn't been needed.
+
+ :return: The possibly new resource.
:param result: An optional TestResult to report resource changes to.
"""
- if self.isDirty():
- self._clean_all(old_resource, result)
- resource = self._make_all(result)
- else:
- resource = old_resource
+ # Core logic:
+ # - if neither we nor any parent is dirty, do nothing.
+ # otherwise
+ # - emit a signal to the test result
+ # - reset all dependencies all, getting new attributes.
+ # - call self._reset(old_resource, dependency_attributes)
+ # [the default implementation does a clean + make]
+ if not self.isDirty():
+ return old_resource
+ self._call_result_method_if_exists(result, "startResetResource", self)
+ dependency_resources = {}
+ for name, mgr in self.resources:
+ dependency_resources[name] = mgr.reset(
+ getattr(old_resource, name), result)
+ resource = self._reset(old_resource, dependency_resources)
+ for name, value in dependency_resources.items():
+ setattr(resource, name, value)
+ self._call_result_method_if_exists(result, "stopResetResource", self)
return resource
+ def _reset(self, resource, dependency_resources):
+ """Override this to reset resources other than via clean+make.
+
+ This method should reset the self._dirty flag (assuming the manager can
+ ever be clean) and return either the old resource cleaned or a fresh
+ one.
+
+ :param resource: The resource to reset.
+ :param dependency_resources: A dict mapping name -> resource instance
+ for the resources specified as dependencies.
+ """
+ self.clean(resource)
+ return self.make(dependency_resources)
+
def _setResource(self, new_resource):
"""Set the current resource to a new value."""
self._currentResource = new_resource
@@ -671,6 +719,10 @@ class FixtureResource(TestResourceManager):
self.fixture.setUp()
return self.fixture
+ def _reset(self, resource, dependency_resources):
+ self.fixture.reset()
+ return self.fixture
+
def isDirty(self):
return True
diff --git a/lib/testresources/tests/__init__.py b/lib/testresources/tests/__init__.py
index 06b6930..c20e4df 100644
--- a/lib/testresources/tests/__init__.py
+++ b/lib/testresources/tests/__init__.py
@@ -58,3 +58,9 @@ class ResultWithResourceExtensions(TestResult):
def stopMakeResource(self, resource):
self._calls.append(("make", "stop", resource))
+
+ def startResetResource(self, resource):
+ self._calls.append(("reset", "start", resource))
+
+ def stopResetResource(self, resource):
+ self._calls.append(("reset", "stop", resource))
diff --git a/lib/testresources/tests/test_test_resource.py b/lib/testresources/tests/test_test_resource.py
index 02524e7..5ef7fc7 100644
--- a/lib/testresources/tests/test_test_resource.py
+++ b/lib/testresources/tests/test_test_resource.py
@@ -46,11 +46,11 @@ class MockResourceInstance(object):
return self._name
-class MockResource(testresources.TestResource):
+class MockResource(testresources.TestResourceManager):
"""Mock resource that logs the number of make and clean calls."""
def __init__(self):
- testresources.TestResource.__init__(self)
+ super(MockResource, self).__init__()
self.makes = 0
self.cleans = 0
@@ -66,12 +66,13 @@ class MockResettableResource(MockResource):
"""Mock resource that logs the number of reset calls too."""
def __init__(self):
- MockResource.__init__(self)
+ super(MockResettableResource, self).__init__()
self.resets = 0
- def reset(self, resource, result):
+ def _reset(self, resource, dependency_resources):
self.resets += 1
resource._name += "!"
+ self._dirty = False
return resource
@@ -214,14 +215,16 @@ class TestTestResource(testtools.TestCase):
def testIsResetIfDependenciesAreDirty(self):
resource_manager = MockResource()
- dep1 = MockResource()
+ dep1 = MockResettableResource()
resource_manager.resources.append(("dep1", dep1))
r = resource_manager.getResource()
dep1.dirtied(r.dep1)
- # if we get the resource again, it should be clean
+ # if we get the resource again, it should be cleaned.
r = resource_manager.getResource()
self.assertFalse(resource_manager.isDirty())
self.assertFalse(dep1.isDirty())
+ resource_manager.finishedWith(r)
+ resource_manager.finishedWith(r)
def testUsedResourceResetBetweenUses(self):
resource_manager = MockResettableResource()
@@ -294,8 +297,6 @@ class TestTestResource(testtools.TestCase):
self.assertIs(resource, resource_manager._currentResource)
resource_manager.finishedWith(resource)
- # The default implementation of reset() performs a make/clean if
- # the dirty flag is set.
def testDirtiedSetsDirty(self):
resource_manager = MockResource()
resource = resource_manager.getResource()
@@ -334,6 +335,39 @@ class TestTestResource(testtools.TestCase):
self.assertEqual(2, resource_manager.makes)
self.assertEqual(1, resource_manager.cleans)
+ def testDefaultResetResetsDependencies(self):
+ resource_manager = MockResettableResource()
+ dep1 = MockResettableResource()
+ dep2 = MockResettableResource()
+ resource_manager.resources.append(("dep1", dep1))
+ resource_manager.resources.append(("dep2", dep2))
+ # A typical OptimisingTestSuite workflow
+ r_outer = resource_manager.getResource()
+ # test 1
+ r_inner = resource_manager.getResource()
+ dep2.dirtied(r_inner.dep2)
+ resource_manager.finishedWith(r_inner)
+ # test 2
+ r_inner = resource_manager.getResource()
+ dep2.dirtied(r_inner.dep2)
+ resource_manager.finishedWith(r_inner)
+ resource_manager.finishedWith(r_outer)
+ # Dep 1 was clean, doesn't do a reset, and should only have one
+ # make+clean.
+ self.assertEqual(1, dep1.makes)
+ self.assertEqual(1, dep1.cleans)
+ self.assertEqual(0, dep1.resets)
+ # Dep 2 was dirty, so _reset happens, and likewise only one make and
+ # clean.
+ self.assertEqual(1, dep2.makes)
+ self.assertEqual(1, dep2.cleans)
+ self.assertEqual(1, dep2.resets)
+ # The top layer should have had a reset happen, and only one make and
+ # clean.
+ self.assertEqual(1, resource_manager.makes)
+ self.assertEqual(1, resource_manager.cleans)
+ self.assertEqual(1, resource_manager.resets)
+
def testDirtyingWhenUnused(self):
resource_manager = MockResource()
resource = resource_manager.getResource()
@@ -388,10 +422,9 @@ class TestTestResource(testtools.TestCase):
def testResetActivityForResourceWithExtensions(self):
result = ResultWithResourceExtensions()
resource_manager = MockResource()
- expected = [("clean", "start", resource_manager),
- ("clean", "stop", resource_manager),
- ("make", "start", resource_manager),
- ("make", "stop", resource_manager)]
+ expected = [("reset", "start", resource_manager),
+ ("reset", "stop", resource_manager),
+ ]
resource_manager.getResource()
r = resource_manager.getResource()
resource_manager.dirtied(r)
@@ -488,4 +521,4 @@ class TestFixtureResource(testtools.TestCase):
mgr.reset(resource)
mgr.finishedWith(resource)
self.assertEqual(
- ['setUp', 'cleanUp', 'setUp', 'cleanUp'], fixture.calls)
+ ['setUp', 'reset', 'cleanUp'], fixture.calls)