Merge lp:~lifeless/testtools/bug-1090582 into lp:~testtools-committers/testtools/trunk
- bug-1090582
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 298 |
Proposed branch: | lp:~lifeless/testtools/bug-1090582 |
Merge into: | lp:~testtools-committers/testtools/trunk |
Diff against target: |
617 lines (+231/-50) 8 files modified
NEWS (+11/-0) testtools/run.py (+13/-6) testtools/testresult/doubles.py (+18/-0) testtools/testresult/real.py (+101/-32) testtools/tests/helpers.py (+4/-0) testtools/tests/test_distutilscmd.py (+8/-8) testtools/tests/test_run.py (+21/-4) testtools/tests/test_testresult.py (+55/-0) |
To merge this branch: | bzr merge lp:~lifeless/testtools/bug-1090582 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jonathan Lange | Needs Fixing | ||
Review via email: mp+140067@code.launchpad.net |
Commit message
Description of the change
Our CLI help says we support -f / --failfast. We don't. This makes us.
Jonathan Lange (jml) wrote : | # |
Oops. Tried to merge this, ran the tests and got errors.
=======
ERROR: testtools.
-------
Traceback (most recent call last):
File "testtools/
runner_stdout = self.useFixture
AttributeError: 'module' object has no attribute 'DetailStream'
=======
ERROR: testtools.
-------
Traceback (most recent call last):
File "testtools/
runner_stdout = self.useFixture
AttributeError: 'module' object has no attribute 'DetailStream'
=======
ERROR: testtools.
-------
Traceback (most recent call last):
File "testtools/
runner_stdout = self.useFixture
AttributeError: 'module' object has no attribute 'DetailStream'
Appears this branch refers to ``fixtures.
Robert Collins (lifeless) wrote : | # |
Its in fixtures on pypi.
- 296. By Robert Collins
-
Adjust trunk for TestToolsTestRunner change.
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2012-12-15 14:11:14 +0000 |
3 | +++ NEWS 2012-12-16 12:29:26 +0000 |
4 | @@ -6,9 +6,20 @@ |
5 | NEXT |
6 | ~~~~ |
7 | |
8 | +Changes |
9 | +------- |
10 | + |
11 | +* ``run.TestToolsTestRunner`` now accepts the verbosity, buffer and failfast |
12 | + arguments the upstream python TestProgram code wants to give it, making it |
13 | + possible to support them in a compatible fashion. (Robert Collins) |
14 | + |
15 | Improvements |
16 | ------------ |
17 | |
18 | +* ``testtools.run`` now supports the ``-f`` or ``--failfast`` parameter. |
19 | + Previously it was advertised in the help but ignored. |
20 | + (Robert Collins, #1090582) |
21 | + |
22 | * ``AnyMatch`` added, a new matcher that matches when any item in a collection |
23 | matches the given matcher. (Jonathan Lange) |
24 | |
25 | |
26 | === modified file 'testtools/run.py' |
27 | --- testtools/run.py 2012-11-29 11:40:17 +0000 |
28 | +++ testtools/run.py 2012-12-16 12:29:26 +0000 |
29 | @@ -35,12 +35,19 @@ |
30 | class TestToolsTestRunner(object): |
31 | """ A thunk object to support unittest.TestProgram.""" |
32 | |
33 | - def __init__(self, stdout): |
34 | - self.stdout = stdout |
35 | + def __init__(self, verbosity=None, failfast=None, buffer=None): |
36 | + """Create a TestToolsTestRunner. |
37 | + |
38 | + :param verbosity: Ignored. |
39 | + :param failfast: Stop running tests at the first failure. |
40 | + :param buffer: Ignored. |
41 | + """ |
42 | + self.failfast = failfast |
43 | |
44 | def run(self, test): |
45 | "Run the given test case or test suite." |
46 | - result = TextTestResult(unicode_output_stream(self.stdout)) |
47 | + result = TextTestResult( |
48 | + unicode_output_stream(sys.stdout), failfast=self.failfast) |
49 | result.startTestRun() |
50 | try: |
51 | return test.run(result) |
52 | @@ -307,7 +314,7 @@ |
53 | and getattr(unittest, 'installHandler', None) is not None): |
54 | unittest.installHandler() |
55 | if self.testRunner is None: |
56 | - self.testRunner = TestToolsTestRunner(sys.stdout) |
57 | + self.testRunner = TestToolsTestRunner |
58 | if isinstance(self.testRunner, classtypes()): |
59 | try: |
60 | testRunner = self.testRunner(verbosity=self.verbosity, |
61 | @@ -325,8 +332,8 @@ |
62 | ################ |
63 | |
64 | def main(argv, stdout): |
65 | - runner = TestToolsTestRunner(stdout) |
66 | - program = TestProgram(argv=argv, testRunner=runner, stdout=stdout) |
67 | + program = TestProgram(argv=argv, testRunner=TestToolsTestRunner, |
68 | + stdout=stdout) |
69 | |
70 | if __name__ == '__main__': |
71 | main(sys.argv, sys.stdout) |
72 | |
73 | === modified file 'testtools/testresult/doubles.py' |
74 | --- testtools/testresult/doubles.py 2012-04-12 16:26:02 +0000 |
75 | +++ testtools/testresult/doubles.py 2012-12-16 12:29:26 +0000 |
76 | @@ -19,6 +19,7 @@ |
77 | self._events = [] |
78 | self.shouldStop = False |
79 | self._was_successful = True |
80 | + self.testsRun = 0 |
81 | |
82 | |
83 | class Python26TestResult(LoggingBase): |
84 | @@ -37,6 +38,7 @@ |
85 | |
86 | def startTest(self, test): |
87 | self._events.append(('startTest', test)) |
88 | + self.testsRun += 1 |
89 | |
90 | def stop(self): |
91 | self.shouldStop = True |
92 | @@ -51,6 +53,20 @@ |
93 | class Python27TestResult(Python26TestResult): |
94 | """A precisely python 2.7 like test result, that logs.""" |
95 | |
96 | + def __init__(self): |
97 | + super(Python27TestResult, self).__init__() |
98 | + self.failfast = False |
99 | + |
100 | + def addError(self, test, err): |
101 | + super(Python27TestResult, self).addError(test, err) |
102 | + if self.failfast: |
103 | + self.stop() |
104 | + |
105 | + def addFailure(self, test, err): |
106 | + super(Python27TestResult, self).addFailure(test, err) |
107 | + if self.failfast: |
108 | + self.stop() |
109 | + |
110 | def addExpectedFailure(self, test, err): |
111 | self._events.append(('addExpectedFailure', test, err)) |
112 | |
113 | @@ -59,6 +75,8 @@ |
114 | |
115 | def addUnexpectedSuccess(self, test): |
116 | self._events.append(('addUnexpectedSuccess', test)) |
117 | + if self.failfast: |
118 | + self.stop() |
119 | |
120 | def startTestRun(self): |
121 | self._events.append(('startTestRun',)) |
122 | |
123 | === modified file 'testtools/testresult/real.py' |
124 | --- testtools/testresult/real.py 2012-09-21 15:15:24 +0000 |
125 | +++ testtools/testresult/real.py 2012-12-16 12:29:26 +0000 |
126 | @@ -21,6 +21,7 @@ |
127 | text_content, |
128 | TracebackContent, |
129 | ) |
130 | +from testtools.helpers import safe_hasattr |
131 | from testtools.tags import TagContext |
132 | |
133 | # From http://docs.python.org/library/datetime.html |
134 | @@ -60,11 +61,12 @@ |
135 | :ivar skip_reasons: A dict of skip-reasons -> list of tests. See addSkip. |
136 | """ |
137 | |
138 | - def __init__(self): |
139 | + def __init__(self, failfast=False): |
140 | # startTestRun resets all attributes, and older clients don't know to |
141 | # call startTestRun, so it is called once here. |
142 | # Because subclasses may reasonably not expect this, we call the |
143 | # specific version we want to run. |
144 | + self.failfast = failfast |
145 | TestResult.startTestRun(self) |
146 | |
147 | def addExpectedFailure(self, test, err=None, details=None): |
148 | @@ -89,6 +91,8 @@ |
149 | """ |
150 | self.errors.append((test, |
151 | self._err_details_to_string(test, err, details))) |
152 | + if self.failfast: |
153 | + self.stop() |
154 | |
155 | def addFailure(self, test, err=None, details=None): |
156 | """Called when an error has occurred. 'err' is a tuple of values as |
157 | @@ -99,6 +103,8 @@ |
158 | """ |
159 | self.failures.append((test, |
160 | self._err_details_to_string(test, err, details))) |
161 | + if self.failfast: |
162 | + self.stop() |
163 | |
164 | def addSkip(self, test, reason=None, details=None): |
165 | """Called when a test has been skipped rather than running. |
166 | @@ -131,6 +137,8 @@ |
167 | def addUnexpectedSuccess(self, test, details=None): |
168 | """Called when a test was expected to fail, but succeed.""" |
169 | self.unexpectedSuccesses.append(test) |
170 | + if self.failfast: |
171 | + self.stop() |
172 | |
173 | def wasSuccessful(self): |
174 | """Has this result been successful so far? |
175 | @@ -174,6 +182,8 @@ |
176 | pristine condition ready for use in another test run. Note that this |
177 | is different from Python 2.7's startTestRun, which does nothing. |
178 | """ |
179 | + # failfast is reset by the super __init__, so stash it. |
180 | + failfast = self.failfast |
181 | super(TestResult, self).__init__() |
182 | self.skip_reasons = {} |
183 | self.__now = None |
184 | @@ -181,6 +191,7 @@ |
185 | # -- Start: As per python 2.7 -- |
186 | self.expectedFailures = [] |
187 | self.unexpectedSuccesses = [] |
188 | + self.failfast = failfast |
189 | # -- End: As per python 2.7 -- |
190 | |
191 | def stopTestRun(self): |
192 | @@ -236,8 +247,9 @@ |
193 | """A test result that dispatches to many test results.""" |
194 | |
195 | def __init__(self, *results): |
196 | + # Setup _results first, as the base class __init__ assigns to failfast. |
197 | + self._results = list(map(ExtendedToOriginalDecorator, results)) |
198 | super(MultiTestResult, self).__init__() |
199 | - self._results = list(map(ExtendedToOriginalDecorator, results)) |
200 | |
201 | def __repr__(self): |
202 | return '<%s (%s)>' % ( |
203 | @@ -248,10 +260,26 @@ |
204 | getattr(result, message)(*args, **kwargs) |
205 | for result in self._results) |
206 | |
207 | + def _get_failfast(self): |
208 | + return getattr(self._results[0], 'failfast', False) |
209 | + def _set_failfast(self, value): |
210 | + self._dispatch('__setattr__', 'failfast', value) |
211 | + failfast = property(_get_failfast, _set_failfast) |
212 | + |
213 | + def _get_shouldStop(self): |
214 | + return any(self._dispatch('__getattr__', 'shouldStop')) |
215 | + def _set_shouldStop(self, value): |
216 | + # Called because we subclass TestResult. Probably should not do that. |
217 | + pass |
218 | + shouldStop = property(_get_shouldStop, _set_shouldStop) |
219 | + |
220 | def startTest(self, test): |
221 | super(MultiTestResult, self).startTest(test) |
222 | return self._dispatch('startTest', test) |
223 | |
224 | + def stop(self): |
225 | + return self._dispatch('stop') |
226 | + |
227 | def stopTest(self, test): |
228 | super(MultiTestResult, self).stopTest(test) |
229 | return self._dispatch('stopTest', test) |
230 | @@ -303,9 +331,9 @@ |
231 | class TextTestResult(TestResult): |
232 | """A TestResult which outputs activity to a text stream.""" |
233 | |
234 | - def __init__(self, stream): |
235 | + def __init__(self, stream, failfast=False): |
236 | """Construct a TextTestResult writing to stream.""" |
237 | - super(TextTestResult, self).__init__() |
238 | + super(TextTestResult, self).__init__(failfast=failfast) |
239 | self.stream = stream |
240 | self.sep1 = '=' * 70 + '\n' |
241 | self.sep2 = '-' * 70 + '\n' |
242 | @@ -451,6 +479,24 @@ |
243 | finally: |
244 | self.semaphore.release() |
245 | |
246 | + def _get_shouldStop(self): |
247 | + self.semaphore.acquire() |
248 | + try: |
249 | + return self.result.shouldStop |
250 | + finally: |
251 | + self.semaphore.release() |
252 | + def _set_shouldStop(self, value): |
253 | + # Another case where we should not subclass TestResult |
254 | + pass |
255 | + shouldStop = property(_get_shouldStop, _set_shouldStop) |
256 | + |
257 | + def stop(self): |
258 | + self.semaphore.acquire() |
259 | + try: |
260 | + self.result.stop() |
261 | + finally: |
262 | + self.semaphore.release() |
263 | + |
264 | def stopTestRun(self): |
265 | self.semaphore.acquire() |
266 | try: |
267 | @@ -507,6 +553,8 @@ |
268 | def __init__(self, decorated): |
269 | self.decorated = decorated |
270 | self._tags = TagContext() |
271 | + # Only used for old TestResults that do not have failfast. |
272 | + self._failfast = False |
273 | |
274 | def __repr__(self): |
275 | return '<%s %r>' % (self.__class__.__name__, self.decorated) |
276 | @@ -515,14 +563,18 @@ |
277 | return getattr(self.decorated, name) |
278 | |
279 | def addError(self, test, err=None, details=None): |
280 | - self._check_args(err, details) |
281 | - if details is not None: |
282 | - try: |
283 | - return self.decorated.addError(test, details=details) |
284 | - except TypeError: |
285 | - # have to convert |
286 | - err = self._details_to_exc_info(details) |
287 | - return self.decorated.addError(test, err) |
288 | + try: |
289 | + self._check_args(err, details) |
290 | + if details is not None: |
291 | + try: |
292 | + return self.decorated.addError(test, details=details) |
293 | + except TypeError: |
294 | + # have to convert |
295 | + err = self._details_to_exc_info(details) |
296 | + return self.decorated.addError(test, err) |
297 | + finally: |
298 | + if self.failfast: |
299 | + self.stop() |
300 | |
301 | def addExpectedFailure(self, test, err=None, details=None): |
302 | self._check_args(err, details) |
303 | @@ -539,14 +591,18 @@ |
304 | return addExpectedFailure(test, err) |
305 | |
306 | def addFailure(self, test, err=None, details=None): |
307 | - self._check_args(err, details) |
308 | - if details is not None: |
309 | - try: |
310 | - return self.decorated.addFailure(test, details=details) |
311 | - except TypeError: |
312 | - # have to convert |
313 | - err = self._details_to_exc_info(details) |
314 | - return self.decorated.addFailure(test, err) |
315 | + try: |
316 | + self._check_args(err, details) |
317 | + if details is not None: |
318 | + try: |
319 | + return self.decorated.addFailure(test, details=details) |
320 | + except TypeError: |
321 | + # have to convert |
322 | + err = self._details_to_exc_info(details) |
323 | + return self.decorated.addFailure(test, err) |
324 | + finally: |
325 | + if self.failfast: |
326 | + self.stop() |
327 | |
328 | def addSkip(self, test, reason=None, details=None): |
329 | self._check_args(reason, details) |
330 | @@ -565,18 +621,22 @@ |
331 | return addSkip(test, reason) |
332 | |
333 | def addUnexpectedSuccess(self, test, details=None): |
334 | - outcome = getattr(self.decorated, 'addUnexpectedSuccess', None) |
335 | - if outcome is None: |
336 | - try: |
337 | - test.fail("") |
338 | - except test.failureException: |
339 | - return self.addFailure(test, sys.exc_info()) |
340 | - if details is not None: |
341 | - try: |
342 | - return outcome(test, details=details) |
343 | - except TypeError: |
344 | - pass |
345 | - return outcome(test) |
346 | + try: |
347 | + outcome = getattr(self.decorated, 'addUnexpectedSuccess', None) |
348 | + if outcome is None: |
349 | + try: |
350 | + test.fail("") |
351 | + except test.failureException: |
352 | + return self.addFailure(test, sys.exc_info()) |
353 | + if details is not None: |
354 | + try: |
355 | + return outcome(test, details=details) |
356 | + except TypeError: |
357 | + pass |
358 | + return outcome(test) |
359 | + finally: |
360 | + if self.failfast: |
361 | + self.stop() |
362 | |
363 | def addSuccess(self, test, details=None): |
364 | if details is not None: |
365 | @@ -614,6 +674,15 @@ |
366 | except AttributeError: |
367 | return |
368 | |
369 | + def _get_failfast(self): |
370 | + return getattr(self.decorated, 'failfast', self._failfast) |
371 | + def _set_failfast(self, value): |
372 | + if safe_hasattr(self.decorated, 'failfast'): |
373 | + self.decorated.failfast = value |
374 | + else: |
375 | + self._failfast = value |
376 | + failfast = property(_get_failfast, _set_failfast) |
377 | + |
378 | def progress(self, offset, whence): |
379 | method = getattr(self.decorated, 'progress', None) |
380 | if method is None: |
381 | |
382 | === modified file 'testtools/tests/helpers.py' |
383 | --- testtools/tests/helpers.py 2012-02-04 16:47:09 +0000 |
384 | +++ testtools/tests/helpers.py 2012-12-16 12:29:26 +0000 |
385 | @@ -38,6 +38,10 @@ |
386 | self._events.append(('startTest', test)) |
387 | super(LoggingResult, self).startTest(test) |
388 | |
389 | + def stop(self): |
390 | + self._events.append('stop') |
391 | + super(LoggingResult, self).stop() |
392 | + |
393 | def stopTest(self, test): |
394 | self._events.append(('stopTest', test)) |
395 | super(LoggingResult, self).stopTest(test) |
396 | |
397 | === modified file 'testtools/tests/test_distutilscmd.py' |
398 | --- testtools/tests/test_distutilscmd.py 2011-07-26 23:56:06 +0000 |
399 | +++ testtools/tests/test_distutilscmd.py 2012-12-16 12:29:26 +0000 |
400 | @@ -52,7 +52,7 @@ |
401 | |
402 | def test_test_module(self): |
403 | self.useFixture(SampleTestFixture()) |
404 | - stream = BytesIO() |
405 | + runner_stdout = self.useFixture(fixtures.DetailStream('stdout')).stream |
406 | dist = Distribution() |
407 | dist.script_name = 'setup.py' |
408 | dist.script_args = ['test'] |
409 | @@ -60,10 +60,10 @@ |
410 | dist.command_options = { |
411 | 'test': {'test_module': ('command line', 'testtools.runexample')}} |
412 | cmd = dist.reinitialize_command('test') |
413 | - cmd.runner.stdout = stream |
414 | - dist.run_command('test') |
415 | + with fixtures.MonkeyPatch('sys.stdout', runner_stdout): |
416 | + dist.run_command('test') |
417 | self.assertThat( |
418 | - stream.getvalue(), |
419 | + runner_stdout.getvalue(), |
420 | MatchesRegex(_b("""Tests running... |
421 | |
422 | Ran 2 tests in \\d.\\d\\d\\ds |
423 | @@ -72,7 +72,7 @@ |
424 | |
425 | def test_test_suite(self): |
426 | self.useFixture(SampleTestFixture()) |
427 | - stream = BytesIO() |
428 | + runner_stdout = self.useFixture(fixtures.DetailStream('stdout')).stream |
429 | dist = Distribution() |
430 | dist.script_name = 'setup.py' |
431 | dist.script_args = ['test'] |
432 | @@ -82,10 +82,10 @@ |
433 | 'test_suite': ( |
434 | 'command line', 'testtools.runexample.test_suite')}} |
435 | cmd = dist.reinitialize_command('test') |
436 | - cmd.runner.stdout = stream |
437 | - dist.run_command('test') |
438 | + with fixtures.MonkeyPatch('sys.stdout', runner_stdout): |
439 | + dist.run_command('test') |
440 | self.assertThat( |
441 | - stream.getvalue(), |
442 | + runner_stdout.getvalue(), |
443 | MatchesRegex(_b("""Tests running... |
444 | |
445 | Ran 2 tests in \\d.\\d\\d\\ds |
446 | |
447 | === modified file 'testtools/tests/test_run.py' |
448 | --- testtools/tests/test_run.py 2011-07-26 23:27:18 +0000 |
449 | +++ testtools/tests/test_run.py 2012-12-16 12:29:26 +0000 |
450 | @@ -2,6 +2,8 @@ |
451 | |
452 | """Tests for the test runner logic.""" |
453 | |
454 | +from unittest import TestSuite |
455 | + |
456 | from testtools.compat import ( |
457 | _b, |
458 | StringIO, |
459 | @@ -11,6 +13,7 @@ |
460 | |
461 | import testtools |
462 | from testtools import TestCase, run |
463 | +from testtools.matchers import Contains |
464 | |
465 | |
466 | if fixtures: |
467 | @@ -41,9 +44,12 @@ |
468 | |
469 | class TestRun(TestCase): |
470 | |
471 | + def setUp(self): |
472 | + super(TestRun, self).setUp() |
473 | + if fixtures is None: |
474 | + self.skipTest("Need fixtures") |
475 | + |
476 | def test_run_list(self): |
477 | - if fixtures is None: |
478 | - self.skipTest("Need fixtures") |
479 | self.useFixture(SampleTestFixture()) |
480 | out = StringIO() |
481 | run.main(['prog', '-l', 'testtools.runexample.test_suite'], out) |
482 | @@ -52,8 +58,6 @@ |
483 | """, out.getvalue()) |
484 | |
485 | def test_run_load_list(self): |
486 | - if fixtures is None: |
487 | - self.skipTest("Need fixtures") |
488 | self.useFixture(SampleTestFixture()) |
489 | out = StringIO() |
490 | # We load two tests - one that exists and one that doesn't, and we |
491 | @@ -74,6 +78,19 @@ |
492 | self.assertEqual("""testtools.runexample.TestFoo.test_bar |
493 | """, out.getvalue()) |
494 | |
495 | + def test_run_failfast(self): |
496 | + runner_stdout = self.useFixture(fixtures.DetailStream('stdout')).stream |
497 | + class Failing(TestCase): |
498 | + def test_a(self): |
499 | + self.fail('a') |
500 | + def test_b(self): |
501 | + self.fail('b') |
502 | + runner = run.TestToolsTestRunner(failfast=True) |
503 | + with fixtures.MonkeyPatch('sys.stdout', runner_stdout): |
504 | + runner.run(TestSuite([Failing('test_a'), Failing('test_b')])) |
505 | + self.assertThat(runner_stdout.getvalue(), Contains('Ran 1 test')) |
506 | + |
507 | + |
508 | |
509 | def test_suite(): |
510 | from unittest import TestLoader |
511 | |
512 | === modified file 'testtools/tests/test_testresult.py' |
513 | --- testtools/tests/test_testresult.py 2012-10-19 14:29:59 +0000 |
514 | +++ testtools/tests/test_testresult.py 2012-12-16 12:29:26 +0000 |
515 | @@ -12,6 +12,7 @@ |
516 | import sys |
517 | import tempfile |
518 | import threading |
519 | +from unittest import TestSuite |
520 | import warnings |
521 | |
522 | from testtools import ( |
523 | @@ -43,6 +44,7 @@ |
524 | TracebackContent, |
525 | ) |
526 | from testtools.content_type import ContentType, UTF8_TEXT |
527 | +from testtools.helpers import safe_hasattr |
528 | from testtools.matchers import ( |
529 | Contains, |
530 | DocTestMatches, |
531 | @@ -142,6 +144,11 @@ |
532 | result.stopTest(self) |
533 | self.assertTrue(result.wasSuccessful()) |
534 | |
535 | + def test_stop_sets_shouldStop(self): |
536 | + result = self.makeResult() |
537 | + result.stop() |
538 | + self.assertTrue(result.shouldStop) |
539 | + |
540 | |
541 | class Python27Contract(Python26Contract): |
542 | |
543 | @@ -193,6 +200,17 @@ |
544 | result.startTestRun() |
545 | result.stopTestRun() |
546 | |
547 | + def test_failfast(self): |
548 | + result = self.makeResult() |
549 | + result.failfast = True |
550 | + class Failing(TestCase): |
551 | + def test_a(self): |
552 | + self.fail('a') |
553 | + def test_b(self): |
554 | + self.fail('b') |
555 | + TestSuite([Failing('test_a'), Failing('test_b')]).run(result) |
556 | + self.assertEqual(1, result.testsRun) |
557 | + |
558 | |
559 | class TagsContract(Python27Contract): |
560 | """Tests to ensure correct tagging behaviour. |
561 | @@ -566,12 +584,36 @@ |
562 | # `TestResult`s. |
563 | self.assertResultLogsEqual([]) |
564 | |
565 | + def test_failfast_get(self): |
566 | + # Reading reads from the first one - arbitrary choice. |
567 | + self.assertEqual(False, self.multiResult.failfast) |
568 | + self.result1.failfast = True |
569 | + self.assertEqual(True, self.multiResult.failfast) |
570 | + |
571 | + def test_failfast_set(self): |
572 | + # Writing writes to all. |
573 | + self.multiResult.failfast = True |
574 | + self.assertEqual(True, self.result1.failfast) |
575 | + self.assertEqual(True, self.result2.failfast) |
576 | + |
577 | + def test_shouldStop(self): |
578 | + self.assertFalse(self.multiResult.shouldStop) |
579 | + self.result2.stop() |
580 | + # NB: result1 is not stopped: MultiTestResult has to combine the |
581 | + # values. |
582 | + self.assertTrue(self.multiResult.shouldStop) |
583 | + |
584 | def test_startTest(self): |
585 | # Calling `startTest` on a `MultiTestResult` calls `startTest` on all |
586 | # its `TestResult`s. |
587 | self.multiResult.startTest(self) |
588 | self.assertResultLogsEqual([('startTest', self)]) |
589 | |
590 | + def test_stop(self): |
591 | + self.assertFalse(self.multiResult.shouldStop) |
592 | + self.multiResult.stop() |
593 | + self.assertResultLogsEqual(['stop']) |
594 | + |
595 | def test_stopTest(self): |
596 | # Calling `stopTest` on a `MultiTestResult` calls `stopTest` on all |
597 | # its `TestResult`s. |
598 | @@ -1176,6 +1218,19 @@ |
599 | class TestExtendedToOriginalResultDecorator( |
600 | TestExtendedToOriginalResultDecoratorBase): |
601 | |
602 | + def test_failfast_py26(self): |
603 | + self.make_26_result() |
604 | + self.assertEqual(False, self.converter.failfast) |
605 | + self.converter.failfast = True |
606 | + self.assertFalse(safe_hasattr(self.converter.decorated, 'failfast')) |
607 | + |
608 | + def test_failfast_py27(self): |
609 | + self.make_27_result() |
610 | + self.assertEqual(False, self.converter.failfast) |
611 | + # setting it should write it to the backing result |
612 | + self.converter.failfast = True |
613 | + self.assertEqual(True, self.converter.decorated.failfast) |
614 | + |
615 | def test_progress_py26(self): |
616 | self.make_26_result() |
617 | self.converter.progress(1, 2) |
Thanks for the patch. All looks pretty straight-forward & well-tested.