Merge lp:~lifeless/testresources/bug-284125 into lp:~testresources-developers/testresources/trunk
- bug-284125
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
testresources developers | Pending | ||
Review via email: mp+8674@code.launchpad.net |
Commit message
Description of the change
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) |