Merge lp:~lifeless/testresources/bug-284125 into lp:~testresources-developers/testresources/trunk

Proposed by Robert Collins
Status: Merged
Approved by: Jonathan Lange
Approved revision: 39
Merged at revision: not available
Proposed branch: lp:~lifeless/testresources/bug-284125
Merge into: lp:~testresources-developers/testresources/trunk
Diff against target: None lines
To merge this branch: bzr merge lp:~lifeless/testresources/bug-284125
Reviewer Review Type Date Requested Status
testresources developers Pending
Review via email: mp+8674@code.launchpad.net
To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2009-07-07 11:11:21 +0000
3+++ NEWS 2009-07-13 08:22:13 +0000
4@@ -26,9 +26,9 @@
5
6 * Started keeping a NEWS file! (Jonathan Lange)
7
8- * A trace_function can be supplied when constructing TestResource objects,
9- to allow debugging of when resources are made/cleaned. (Robert Collins,
10- #284125)
11+ * Resource creation and destruction are traced by calling methods on the
12+ TestResult object that tests are being run with.
13+ (Robert Collins, #284125)
14
15 BUG FIXES:
16
17
18=== modified file 'README'
19--- README 2009-06-17 09:11:03 +0000
20+++ README 2009-07-13 08:22:13 +0000
21@@ -36,7 +36,7 @@
22 How testresources works:
23 ========================
24
25-There are three main components to make testresources work:
26+These are the main components to make testresources work:
27
28 1) testresources.TestResource
29
30@@ -106,3 +106,11 @@
31 4) testresources.TestLoader
32
33 This is a trivial TestLoader that creates OptimisingTestSuites by default.
34+
35+5) unittest.TestResult
36+
37+testresources will log activity about resource creation and destruction to the
38+result object tests are run with. 4 extension methods are looked for:
39+``startCleanResource``, ``stopCleanResource``, ``startMakeResource``,
40+``stopMakeResource``. ``testresources.tests.ResultWithResourceExtensions`` is
41+an example of a ``TestResult`` with these methods present.
42
43=== modified file 'lib/testresources/__init__.py'
44--- lib/testresources/__init__.py 2009-07-07 11:11:21 +0000
45+++ lib/testresources/__init__.py 2009-07-13 08:22:13 +0000
46@@ -17,6 +17,7 @@
47 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
48 #
49
50+import inspect
51 import unittest
52
53
54@@ -88,19 +89,21 @@
55 return (sum(resource.setUpCost for resource in new_resources) +
56 sum(resource.tearDownCost for resource in gone_resources))
57
58- def switch(self, old_resource_set, new_resource_set):
59+ def switch(self, old_resource_set, new_resource_set, result):
60 """Switch from 'old_resource_set' to 'new_resource_set'.
61
62 Tear down resources in old_resource_set that aren't in
63 new_resource_set and set up resources that are in new_resource_set but
64 not in old_resource_set.
65+
66+ :param result: TestResult object to report activity on.
67 """
68 new_resources = new_resource_set - old_resource_set
69 old_resources = old_resource_set - new_resource_set
70 for resource in old_resources:
71- resource.finishedWith(resource._currentResource)
72+ resource.finishedWith(resource._currentResource, result)
73 for resource in new_resources:
74- resource.getResource()
75+ resource.getResource(result)
76
77 def run(self, result):
78 self.sortTests()
79@@ -112,10 +115,10 @@
80 new_resources = set()
81 for name, resource in resources:
82 new_resources.update(resource.neededResources())
83- self.switch(current_resources, new_resources)
84+ self.switch(current_resources, new_resources, result)
85 current_resources = new_resources
86 test(result)
87- self.switch(current_resources, set())
88+ self.switch(current_resources, set(), result)
89 return result
90
91 def sortTests(self):
92@@ -178,6 +181,14 @@
93 class TestResource(object):
94 """A resource that can be shared across tests.
95
96+ Resources can report activity to a TestResult. The methods
97+ - startCleanResource(resource)
98+ - stopCleanResource(resource)
99+ - startMakeResource(resource)
100+ - stopMakeResource(resource)
101+ will be looked for and if present invoked before and after cleaning or
102+ creation of resource objects takes place.
103+
104 :cvar resources: The same as the resources list on an instance, the default
105 constructor will look for the class instance and copy it. This is a
106 convenience to avoid needing to define __init__ solely to alter the
107@@ -197,26 +208,26 @@
108 setUpCost = 1
109 tearDownCost = 1
110
111- def __init__(self, trace_function=None):
112- """Create a TestResource object.
113-
114- :param trace_function: A callable that takes (event_label,
115- "start"|"stop", resource). This will be called with to tracec
116- events when the resource is made and cleaned.
117- """
118+ def __init__(self):
119+ """Create a TestResource object."""
120 self._dirty = False
121 self._uses = 0
122 self._currentResource = None
123 self.resources = list(getattr(self.__class__, "resources", []))
124- self._trace = trace_function or (lambda x,y,z:"")
125-
126- def _clean_all(self, resource):
127+
128+ def _call_result_method_if_exists(self, result, methodname, *args):
129+ """Call a method on a TestResult that may exist."""
130+ method = getattr(result, methodname, None)
131+ if callable(method):
132+ method(*args)
133+
134+ def _clean_all(self, resource, result):
135 """Clean the dependencies from resource, and then resource itself."""
136- self._trace("clean", "start", self)
137+ self._call_result_method_if_exists(result, "startCleanResource", self)
138 self.clean(resource)
139 for name, manager in self.resources:
140 manager.finishedWith(getattr(resource, name))
141- self._trace("clean", "stop", self)
142+ self._call_result_method_if_exists(result, "stopCleanResource", self)
143
144 def clean(self, resource):
145 """Override this to class method to hook into resource removal."""
146@@ -231,7 +242,7 @@
147 """
148 self._dirty = True
149
150- def finishedWith(self, resource):
151+ def finishedWith(self, resource, result=None):
152 """Indicate that 'resource' has one less user.
153
154 If there are no more registered users of 'resource' then we trigger
155@@ -239,24 +250,26 @@
156 cleanup.
157
158 :param resource: A resource returned by `TestResource.getResource`.
159+ :param result: An optional TestResult to report resource changes to.
160 """
161 self._uses -= 1
162 if self._uses == 0:
163- self._clean_all(resource)
164+ self._clean_all(resource, result)
165 self._setResource(None)
166
167- def getResource(self):
168+ def getResource(self, result=None):
169 """Get the resource for this class and record that it's being used.
170
171 The resource is constructed using the `make` hook.
172
173 Once done with the resource, pass it to `finishedWith` to indicated
174 that it is no longer needed.
175+ :param result: An optional TestResult to report resource changes to.
176 """
177 if self._uses == 0:
178- self._setResource(self._make_all())
179+ self._setResource(self._make_all(result))
180 elif self.isDirty():
181- self._setResource(self.reset(self._currentResource))
182+ self._setResource(self.reset(self._currentResource, result))
183 self._uses += 1
184 return self._currentResource
185
186@@ -278,17 +291,17 @@
187 finally:
188 mgr.finishedWith(res)
189
190- def _make_all(self):
191+ def _make_all(self, result):
192 """Make the dependencies of this resource and this resource."""
193- self._trace("make", "start", self)
194+ self._call_result_method_if_exists(result, "startMakeResource", self)
195 dependency_resources = {}
196 for name, resource in self.resources:
197 dependency_resources[name] = resource.getResource()
198- result = self.make(dependency_resources)
199+ resource = self.make(dependency_resources)
200 for name, value in dependency_resources.items():
201- setattr(result, name, value)
202- self._trace("make", "stop", self)
203- return result
204+ setattr(resource, name, value)
205+ self._call_result_method_if_exists(result, "stopMakeResource", self)
206+ return resource
207
208 def make(self, dependency_resources):
209 """Override this to construct resources.
210@@ -316,7 +329,7 @@
211 result.append(self)
212 return result
213
214- def reset(self, old_resource):
215+ def reset(self, old_resource, result=None):
216 """Overridable method to return a clean version of old_resource.
217
218 By default, the resource will be cleaned then remade if it had
219@@ -326,10 +339,11 @@
220 consideration as _make_all and _clean_all do.
221
222 :return: The new resource.
223+ :param result: An optional TestResult to report resource changes to.
224 """
225 if self._dirty:
226- self._clean_all(old_resource)
227- resource = self._make_all()
228+ self._clean_all(old_resource, result)
229+ resource = self._make_all(result)
230 else:
231 resource = old_resource
232 return resource
233@@ -350,14 +364,27 @@
234
235 resources = []
236
237+ def __get_result(self):
238+ # unittest hides the result. This forces us to look up the stack.
239+ # The result is passed to a run() or a __call__ method 4 or more frames
240+ # up: that method is what calls setUp and tearDown, and they call their
241+ # parent setUp etc. Its not guaranteed that the parameter to run will
242+ # be calls result as its not required to be a keyword parameter in
243+ # TestCase. However, in practice, this works.
244+ stack = inspect.stack()
245+ for frame in stack[3:]:
246+ if frame[3] in ('run', '__call__'):
247+ return frame[0].f_locals['result']
248+
249 def setUp(self):
250 unittest.TestCase.setUp(self)
251 self.setUpResources()
252
253 def setUpResources(self):
254 """Set up any resources that this test needs."""
255+ result = self.__get_result()
256 for resource in self.resources:
257- setattr(self, resource[0], resource[1].getResource())
258+ setattr(self, resource[0], resource[1].getResource(result))
259
260 def tearDown(self):
261 self.tearDownResources()
262@@ -365,6 +392,7 @@
263
264 def tearDownResources(self):
265 """Tear down any resources that this test declares."""
266+ result = self.__get_result()
267 for resource in self.resources:
268- resource[1].finishedWith(getattr(self, resource[0]))
269+ resource[1].finishedWith(getattr(self, resource[0]), result)
270 delattr(self, resource[0])
271
272=== modified file 'lib/testresources/tests/__init__.py'
273--- lib/testresources/tests/__init__.py 2008-09-07 04:16:25 +0000
274+++ lib/testresources/tests/__init__.py 2009-07-13 08:22:13 +0000
275@@ -18,6 +18,8 @@
276 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
277 #
278
279+from unittest import TestResult
280+
281 import testresources
282 from testresources.tests import TestUtil
283
284@@ -33,3 +35,27 @@
285 result.addTest(
286 testresources.tests.test_optimising_test_suite.test_suite())
287 return result
288+
289+
290+class ResultWithoutResourceExtensions(object):
291+ """A test fake which does not have resource extensions."""
292+
293+
294+class ResultWithResourceExtensions(TestResult):
295+ """A test fake which has resource extensions."""
296+
297+ def __init__(self):
298+ TestResult.__init__(self)
299+ self._calls = []
300+
301+ def startCleanResource(self, resource):
302+ self._calls.append(("clean", "start", resource))
303+
304+ def stopCleanResource(self, resource):
305+ self._calls.append(("clean", "stop", resource))
306+
307+ def startMakeResource(self, resource):
308+ self._calls.append(("make", "start", resource))
309+
310+ def stopMakeResource(self, resource):
311+ self._calls.append(("make", "stop", resource))
312
313=== modified file 'lib/testresources/tests/test_optimising_test_suite.py'
314--- lib/testresources/tests/test_optimising_test_suite.py 2009-06-17 09:32:04 +0000
315+++ lib/testresources/tests/test_optimising_test_suite.py 2009-07-13 08:22:13 +0000
316@@ -22,6 +22,7 @@
317 import random
318 import testresources
319 from testresources import split_by_resources
320+from testresources.tests import ResultWithResourceExtensions
321 import unittest
322
323
324@@ -165,6 +166,17 @@
325 self.assertEqual(make_counter.makes, 1)
326 self.assertEqual(make_counter.cleans, 1)
327
328+ def testResultPassedToResources(self):
329+ resource_manager = MakeCounter()
330+ test_case = self.makeTestCase(lambda x:None)
331+ test_case.resources = [('_default', resource_manager)]
332+ self.optimising_suite.addTest(test_case)
333+ result = ResultWithResourceExtensions()
334+ self.optimising_suite.run(result)
335+ # We should see the resource made and cleaned once. As its not a
336+ # resource aware test, it won't make any calls itself.
337+ self.assertEqual(4, len(result._calls))
338+
339 def testOptimisedRunNonResourcedTestCase(self):
340 case = self.makeTestCase()
341 self.optimising_suite.addTest(case)
342
343=== modified file 'lib/testresources/tests/test_resourced_test_case.py'
344--- lib/testresources/tests/test_resourced_test_case.py 2009-06-18 10:36:00 +0000
345+++ lib/testresources/tests/test_resourced_test_case.py 2009-07-13 08:22:13 +0000
346@@ -20,6 +20,7 @@
347
348 import testtools
349 import testresources
350+from testresources.tests import ResultWithResourceExtensions
351
352
353 def test_suite():
354@@ -47,13 +48,22 @@
355
356 def setUp(self):
357 testtools.TestCase.setUp(self)
358- self.resourced_case = testresources.ResourcedTestCase('run')
359+ class Example(testresources.ResourcedTestCase):
360+ def test_example(self):
361+ pass
362+ self.resourced_case = Example('test_example')
363 self.resource = self.getUniqueString()
364 self.resource_manager = MockResource(self.resource)
365
366 def testDefaults(self):
367 self.assertEqual(self.resourced_case.resources, [])
368
369+ def testResultPassedToResources(self):
370+ result = ResultWithResourceExtensions()
371+ self.resourced_case.resources = [("foo", self.resource_manager)]
372+ self.resourced_case.run(result)
373+ self.assertEqual(4, len(result._calls))
374+
375 def testSetUpResourcesSingle(self):
376 # setUpResources installs the resources listed in ResourcedTestCase.
377 self.resourced_case.resources = [("foo", self.resource_manager)]
378
379=== modified file 'lib/testresources/tests/test_test_resource.py'
380--- lib/testresources/tests/test_test_resource.py 2009-07-07 11:11:21 +0000
381+++ lib/testresources/tests/test_test_resource.py 2009-07-13 08:22:13 +0000
382@@ -21,6 +21,10 @@
383 import testtools
384
385 import testresources
386+from testresources.tests import (
387+ ResultWithResourceExtensions,
388+ ResultWithoutResourceExtensions,
389+ )
390
391
392 def test_suite():
393@@ -44,8 +48,8 @@
394 class MockResource(testresources.TestResource):
395 """Mock resource that logs the number of make and clean calls."""
396
397- def __init__(self, trace_function=None):
398- testresources.TestResource.__init__(self, trace_function=trace_function)
399+ def __init__(self):
400+ testresources.TestResource.__init__(self)
401 self.makes = 0
402 self.cleans = 0
403
404@@ -64,7 +68,7 @@
405 MockResource.__init__(self)
406 self.resets = 0
407
408- def reset(self, resource):
409+ def reset(self, resource, result):
410 self.resets += 1
411 resource._name += "!"
412 return resource
413@@ -276,6 +280,7 @@
414 resource = resource_manager.getResource()
415 resource_manager.finishedWith(resource)
416 self.assertIs(resource, resource_manager._currentResource)
417+ resource_manager.finishedWith(resource)
418
419 # The default implementation of reset() performs a make/clean if
420 # the dirty flag is set.
421@@ -326,16 +331,61 @@
422 resource = resource_manager.getResource()
423 self.assertEqual(2, resource_manager.makes)
424
425- def testTraceFunction(self):
426- output = []
427- def trace(operation, phase, mgr):
428- output.append((operation, phase, mgr))
429- resource_manager = MockResource(trace_function=trace)
430+ def testFinishedActivityForResourceWithoutExtensions(self):
431+ result = ResultWithoutResourceExtensions()
432+ resource_manager = MockResource()
433+ r = resource_manager.getResource()
434+ resource_manager.finishedWith(r, result)
435+
436+ def testFinishedActivityForResourceWithExtensions(self):
437+ result = ResultWithResourceExtensions()
438+ resource_manager = MockResource()
439+ r = resource_manager.getResource()
440+ expected = [("clean", "start", resource_manager),
441+ ("clean", "stop", resource_manager)]
442+ resource_manager.finishedWith(r, result)
443+ self.assertEqual(expected, result._calls)
444+
445+ def testGetActivityForResourceWithoutExtensions(self):
446+ result = ResultWithoutResourceExtensions()
447+ resource_manager = MockResource()
448+ r = resource_manager.getResource(result)
449+ resource_manager.finishedWith(r)
450+
451+ def testGetActivityForResourceWithExtensions(self):
452+ result = ResultWithResourceExtensions()
453+ resource_manager = MockResource()
454+ r = resource_manager.getResource(result)
455 expected = [("make", "start", resource_manager),
456 ("make", "stop", resource_manager)]
457- r = resource_manager.getResource()
458- self.assertEqual(expected, output)
459- expected.extend([("clean", "start", resource_manager),
460- ("clean", "stop", resource_manager)])
461- resource_manager.finishedWith(r)
462- self.assertEqual(expected, output)
463+ resource_manager.finishedWith(r)
464+ self.assertEqual(expected, result._calls)
465+
466+ def testResetActivityForResourceWithoutExtensions(self):
467+ result = ResultWithoutResourceExtensions()
468+ resource_manager = MockResource()
469+ resource_manager.getResource()
470+ r = resource_manager.getResource()
471+ resource_manager.dirtied(r)
472+ resource_manager.finishedWith(r)
473+ r = resource_manager.getResource(result)
474+ resource_manager.dirtied(r)
475+ resource_manager.finishedWith(r)
476+ resource_manager.finishedWith(resource_manager._currentResource)
477+
478+ def testResetActivityForResourceWithExtensions(self):
479+ result = ResultWithResourceExtensions()
480+ resource_manager = MockResource()
481+ expected = [("clean", "start", resource_manager),
482+ ("clean", "stop", resource_manager),
483+ ("make", "start", resource_manager),
484+ ("make", "stop", resource_manager)]
485+ resource_manager.getResource()
486+ r = resource_manager.getResource()
487+ resource_manager.dirtied(r)
488+ resource_manager.finishedWith(r)
489+ r = resource_manager.getResource(result)
490+ resource_manager.dirtied(r)
491+ resource_manager.finishedWith(r)
492+ resource_manager.finishedWith(resource_manager._currentResource)
493+ self.assertEqual(expected, result._calls)

Subscribers

People subscribed via source and target branches