Merge lp:~jml/testtools/unexpected-success-2 into lp:~testtools-committers/testtools/trunk

Proposed by Jonathan Lange
Status: Merged
Merged at revision: 142
Proposed branch: lp:~jml/testtools/unexpected-success-2
Merge into: lp:~testtools-committers/testtools/trunk
Diff against target: 570 lines (+233/-38)
5 files modified
NEWS (+6/-1)
testtools/testresult/doubles.py (+17/-1)
testtools/testresult/real.py (+38/-3)
testtools/tests/test_compat.py (+8/-7)
testtools/tests/test_testresult.py (+164/-26)
To merge this branch: bzr merge lp:~jml/testtools/unexpected-success-2
Reviewer Review Type Date Requested Status
Martin Packman Needs Fixing
Robert Collins Approve
Review via email: mp+42050@code.launchpad.net

Description of the change

This branch finished off the rest of bug 654474. It changes the contract of TestResult such that wasSuccessful returns False if there has ever been an unexpected success.

In addition, it updates the contract tests to test for the behaviour of wasSuccessful in many more cases, and adds contract tests for Python 2.6 and 2.7 which we run against our fake implementations.

To post a comment you must log in.
145. By Jonathan Lange

Update NEWS

Revision history for this message
Robert Collins (lifeless) wrote :

Seems to me that wasSuccessful() should be reset by startTestRun - what do you think?

Rather than a mixin, this looks like a job for testscenarios. Its fine without but clearly not as nice.

review: Approve
Revision history for this message
Jonathan Lange (jml) wrote :

Agreed on both points. Will only update startTestRun for testtools
TestResult, since I'm not controlling Python.

Would be better with testscenarios, but not in this branch. Also, we need
to have a chat about dependencies.

Thanks,
jml

Revision history for this message
Martin Packman (gz) wrote :

On Python 3 (with lp:~gz/testtools/raises_regressions_675327 merged) I get three failures. All in TestMultiTestresultContract (nit, should that be TestMultiTest*R*esultContract?) the methods test_addError_is_failure, test_addFailure_is_failure, and test_addUnexpectedSuccess_was_successful all return True from `result.wasSuccessful()`.

There are actually four failures, but one is silent...

As noted by Martin Pool in bug 654474 comment 5 changing the outcome without also providing feedback is bad, so I think TextTestResult.stopTestRun also needs changing. Something along the lines of but prettier than:

=== modified file 'testtools/testresult/real.py'
--- testtools/testresult/real.py 2010-10-24 09:21:01 +0000
+++ testtools/testresult/real.py 2010-11-28 19:18:08 +0000
@@ -258,6 +261,8 @@
         stop = self._now()
         self._show_list('ERROR', self.errors)
         self._show_list('FAIL', self.failures)
+ for test in self.unexpectedSuccesses:
+ self.stream.write("%sUNEXPECTEDSUCCESS: %s\n%s" % (self.sep1, test.id(), self.sep2))
         self.stream.write("Ran %d test%s in %.3fs\n\n" %
             (self.testsRun, plural,
              self._delta_to_float(stop - self.__start)))
@@ -267,7 +272,7 @@
             self.stream.write("FAILED (")
             details = []
             details.append("failures=%d" % (
- len(self.failures) + len(self.errors)))
+ len(self.failures) + len(self.errors) + len(self.unexpectedSuccesses)))
             self.stream.write(", ".join(details))
             self.stream.write(")\n")
         super(TextTestResult, self).stopTestRun()

The silent failure that should then be revealed on Python 3 also needs fixing:

=== modified file 'testtools/tests/test_compat.py'
--- testtools/tests/test_compat.py 2010-11-11 09:46:18 +0000
+++ testtools/tests/test_compat.py 2010-11-28 19:18:08 +0000
@@ -19,6 +19,7 @@
     )
 from testtools.matchers import (
     MatchesException,
+ Not,
     Raises,
     )

@@ -246,7 +247,7 @@
         if newio:
             self.expectFailure("Python 3 StringIO expects text not bytes",
                 self.assertThat, lambda: soutwrapper.write(self.uni),
- Raises(MatchesException(TypeError)))
+ Not(Raises(MatchesException(TypeError))))
         soutwrapper.write(self.uni)
         self.assertEqual("pa???n", sout.getvalue())

Final nit:

+ def test_addUnexpectedSuccess_was_successful(self):
+ # addUnexpectedSuccess does not the test run in Python 2.7.

Verb?

review: Needs Fixing
Revision history for this message
Jonathan Lange (jml) wrote :

mgz, thanks very much for the review. I've fixed the nits, TextTestResult, the MultiTestResult failure (map(f, xs) returns an iterator in Python 3) and the hidden failure in test_compat. I also added tests for the TextTestResult changes.

lifeless, I've made the startTestRun change.

I'm going to merge it now, but I'd welcome any comments you have on the incremental diff.

146. By Jonathan Lange

minor nits

147. By Jonathan Lange

Make MultiTestResult at all usable in Python 3

148. By Jonathan Lange

startTestRun resets unexpected successes and other errors

149. By Jonathan Lange

NEWS update

150. By Jonathan Lange

Actually report the error in TextTestResult (thanks mgz)

151. By Jonathan Lange

Fix a inaccurate unexpected success, hidden by our broken Python 3 support

Revision history for this message
Robert Collins (lifeless) wrote :

+ len(self.failures) + len(self.errors)))
+ len(self.failures) + len(self.errors)
+ + len(self.unexpectedSuccesses)))
sum(map(len, (self, self.failures, self.errors,
    self.unexpectedSuccesses)))

Would be cleaner, I think?

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2010-11-29 00:11:19 +0000
+++ NEWS 2010-11-29 00:28:14 +0000
@@ -8,7 +8,12 @@
8-------8-------
99
10* addUnexpectedSuccess is translated to addFailure for test results that don't10* addUnexpectedSuccess is translated to addFailure for test results that don't
11 know about addUnexpectedSuccess. (Jonathan Lange, #654474)11 know about addUnexpectedSuccess. Further, it fails the entire result for
12 all testtools TestResults (i.e. wasSuccessful() returns False after
13 addUnexpectedSuccess has been called). (Jonathan Lange, #654474)
14
15* startTestRun will reset any errors on the result. That is, wasSuccessful()
16 will always return True immediately after startTestRun() is called.
1217
13* Responsibility for running test cleanups has been moved to ``RunTest``.18* Responsibility for running test cleanups has been moved to ``RunTest``.
14 This change does not affect public APIs and can be safely ignored by test19 This change does not affect public APIs and can be safely ignored by test
1520
=== modified file 'testtools/testresult/doubles.py'
--- testtools/testresult/doubles.py 2009-12-31 03:15:19 +0000
+++ testtools/testresult/doubles.py 2010-11-29 00:28:14 +0000
@@ -1,4 +1,4 @@
1# Copyright (c) 2009 Jonathan M. Lange. See LICENSE for details.1# Copyright (c) 2009-2010 Jonathan M. Lange. See LICENSE for details.
22
3"""Doubles of test result objects, useful for testing unittest code."""3"""Doubles of test result objects, useful for testing unittest code."""
44
@@ -15,15 +15,18 @@
15 def __init__(self):15 def __init__(self):
16 self._events = []16 self._events = []
17 self.shouldStop = False17 self.shouldStop = False
18 self._was_successful = True
1819
1920
20class Python26TestResult(LoggingBase):21class Python26TestResult(LoggingBase):
21 """A precisely python 2.6 like test result, that logs."""22 """A precisely python 2.6 like test result, that logs."""
2223
23 def addError(self, test, err):24 def addError(self, test, err):
25 self._was_successful = False
24 self._events.append(('addError', test, err))26 self._events.append(('addError', test, err))
2527
26 def addFailure(self, test, err):28 def addFailure(self, test, err):
29 self._was_successful = False
27 self._events.append(('addFailure', test, err))30 self._events.append(('addFailure', test, err))
2831
29 def addSuccess(self, test):32 def addSuccess(self, test):
@@ -38,6 +41,9 @@
38 def stopTest(self, test):41 def stopTest(self, test):
39 self._events.append(('stopTest', test))42 self._events.append(('stopTest', test))
4043
44 def wasSuccessful(self):
45 return self._was_successful
46
4147
42class Python27TestResult(Python26TestResult):48class Python27TestResult(Python26TestResult):
43 """A precisely python 2.7 like test result, that logs."""49 """A precisely python 2.7 like test result, that logs."""
@@ -62,9 +68,11 @@
62 """A test result like the proposed extended unittest result API."""68 """A test result like the proposed extended unittest result API."""
6369
64 def addError(self, test, err=None, details=None):70 def addError(self, test, err=None, details=None):
71 self._was_successful = False
65 self._events.append(('addError', test, err or details))72 self._events.append(('addError', test, err or details))
6673
67 def addFailure(self, test, err=None, details=None):74 def addFailure(self, test, err=None, details=None):
75 self._was_successful = False
68 self._events.append(('addFailure', test, err or details))76 self._events.append(('addFailure', test, err or details))
6977
70 def addExpectedFailure(self, test, err=None, details=None):78 def addExpectedFailure(self, test, err=None, details=None):
@@ -80,6 +88,7 @@
80 self._events.append(('addSuccess', test))88 self._events.append(('addSuccess', test))
8189
82 def addUnexpectedSuccess(self, test, details=None):90 def addUnexpectedSuccess(self, test, details=None):
91 self._was_successful = False
83 if details is not None:92 if details is not None:
84 self._events.append(('addUnexpectedSuccess', test, details))93 self._events.append(('addUnexpectedSuccess', test, details))
85 else:94 else:
@@ -88,8 +97,15 @@
88 def progress(self, offset, whence):97 def progress(self, offset, whence):
89 self._events.append(('progress', offset, whence))98 self._events.append(('progress', offset, whence))
9099
100 def startTestRun(self):
101 super(ExtendedTestResult, self).startTestRun()
102 self._was_successful = True
103
91 def tags(self, new_tags, gone_tags):104 def tags(self, new_tags, gone_tags):
92 self._events.append(('tags', new_tags, gone_tags))105 self._events.append(('tags', new_tags, gone_tags))
93106
94 def time(self, time):107 def time(self, time):
95 self._events.append(('time', time))108 self._events.append(('time', time))
109
110 def wasSuccessful(self):
111 return self._was_successful
96112
=== modified file 'testtools/testresult/real.py'
--- testtools/testresult/real.py 2010-10-24 09:21:01 +0000
+++ testtools/testresult/real.py 2010-11-29 00:28:14 +0000
@@ -108,6 +108,18 @@
108 """Called when a test was expected to fail, but succeed."""108 """Called when a test was expected to fail, but succeed."""
109 self.unexpectedSuccesses.append(test)109 self.unexpectedSuccesses.append(test)
110110
111 def wasSuccessful(self):
112 """Has this result been successful so far?
113
114 If there have been any errors, failures or unexpected successes,
115 return False. Otherwise, return True.
116
117 Note: This differs from standard unittest in that we consider
118 unexpected successes to be equivalent to failures, rather than
119 successes.
120 """
121 return not (self.errors or self.failures or self.unexpectedSuccesses)
122
111 if str_is_unicode:123 if str_is_unicode:
112 # Python 3 and IronPython strings are unicode, use parent class method124 # Python 3 and IronPython strings are unicode, use parent class method
113 _exc_info_to_unicode = unittest.TestResult._exc_info_to_string125 _exc_info_to_unicode = unittest.TestResult._exc_info_to_string
@@ -148,6 +160,9 @@
148160
149 New in python 2.7161 New in python 2.7
150 """162 """
163 self.unexpectedSuccesses = []
164 self.errors = []
165 self.failures = []
151166
152 def stopTestRun(self):167 def stopTestRun(self):
153 """Called after a test run completes168 """Called after a test run completes
@@ -182,7 +197,7 @@
182197
183 def __init__(self, *results):198 def __init__(self, *results):
184 TestResult.__init__(self)199 TestResult.__init__(self)
185 self._results = map(ExtendedToOriginalDecorator, results)200 self._results = list(map(ExtendedToOriginalDecorator, results))
186201
187 def _dispatch(self, message, *args, **kwargs):202 def _dispatch(self, message, *args, **kwargs):
188 return tuple(203 return tuple(
@@ -223,6 +238,13 @@
223 def done(self):238 def done(self):
224 return self._dispatch('done')239 return self._dispatch('done')
225240
241 def wasSuccessful(self):
242 """Was this result successful?
243
244 Only returns True if every constituent result was successful.
245 """
246 return all(self._dispatch('wasSuccessful'))
247
226248
227class TextTestResult(TestResult):249class TextTestResult(TestResult):
228 """A TestResult which outputs activity to a text stream."""250 """A TestResult which outputs activity to a text stream."""
@@ -258,6 +280,10 @@
258 stop = self._now()280 stop = self._now()
259 self._show_list('ERROR', self.errors)281 self._show_list('ERROR', self.errors)
260 self._show_list('FAIL', self.failures)282 self._show_list('FAIL', self.failures)
283 for test in self.unexpectedSuccesses:
284 self.stream.write(
285 "%sUNEXPECTED SUCCESS: %s\n%s" % (
286 self.sep1, test.id(), self.sep2))
261 self.stream.write("Ran %d test%s in %.3fs\n\n" %287 self.stream.write("Ran %d test%s in %.3fs\n\n" %
262 (self.testsRun, plural,288 (self.testsRun, plural,
263 self._delta_to_float(stop - self.__start)))289 self._delta_to_float(stop - self.__start)))
@@ -267,7 +293,8 @@
267 self.stream.write("FAILED (")293 self.stream.write("FAILED (")
268 details = []294 details = []
269 details.append("failures=%d" % (295 details.append("failures=%d" % (
270 len(self.failures) + len(self.errors)))296 len(self.failures) + len(self.errors)
297 + len(self.unexpectedSuccesses)))
271 self.stream.write(", ".join(details))298 self.stream.write(", ".join(details))
272 self.stream.write(")\n")299 self.stream.write(")\n")
273 super(TextTestResult, self).stopTestRun()300 super(TextTestResult, self).stopTestRun()
@@ -363,6 +390,9 @@
363 self._test_start = self._now()390 self._test_start = self._now()
364 super(ThreadsafeForwardingResult, self).startTest(test)391 super(ThreadsafeForwardingResult, self).startTest(test)
365392
393 def wasSuccessful(self):
394 return self.result.wasSuccessful()
395
366396
367class ExtendedToOriginalDecorator(object):397class ExtendedToOriginalDecorator(object):
368 """Permit new TestResult API code to degrade gracefully with old results.398 """Permit new TestResult API code to degrade gracefully with old results.
@@ -376,11 +406,13 @@
376406
377 def __init__(self, decorated):407 def __init__(self, decorated):
378 self.decorated = decorated408 self.decorated = decorated
409 self._was_successful = True
379410
380 def __getattr__(self, name):411 def __getattr__(self, name):
381 return getattr(self.decorated, name)412 return getattr(self.decorated, name)
382413
383 def addError(self, test, err=None, details=None):414 def addError(self, test, err=None, details=None):
415 self._was_successful = False
384 self._check_args(err, details)416 self._check_args(err, details)
385 if details is not None:417 if details is not None:
386 try:418 try:
@@ -405,6 +437,7 @@
405 return addExpectedFailure(test, err)437 return addExpectedFailure(test, err)
406438
407 def addFailure(self, test, err=None, details=None):439 def addFailure(self, test, err=None, details=None):
440 self._was_successful = False
408 self._check_args(err, details)441 self._check_args(err, details)
409 if details is not None:442 if details is not None:
410 try:443 try:
@@ -431,6 +464,7 @@
431 return addSkip(test, reason)464 return addSkip(test, reason)
432465
433 def addUnexpectedSuccess(self, test, details=None):466 def addUnexpectedSuccess(self, test, details=None):
467 self._was_successful = False
434 outcome = getattr(self.decorated, 'addUnexpectedSuccess', None)468 outcome = getattr(self.decorated, 'addUnexpectedSuccess', None)
435 if outcome is None:469 if outcome is None:
436 try:470 try:
@@ -487,6 +521,7 @@
487 return self.decorated.startTest(test)521 return self.decorated.startTest(test)
488522
489 def startTestRun(self):523 def startTestRun(self):
524 self._was_successful = True
490 try:525 try:
491 return self.decorated.startTestRun()526 return self.decorated.startTestRun()
492 except AttributeError:527 except AttributeError:
@@ -517,7 +552,7 @@
517 return method(a_datetime)552 return method(a_datetime)
518553
519 def wasSuccessful(self):554 def wasSuccessful(self):
520 return self.decorated.wasSuccessful()555 return self._was_successful
521556
522557
523class _StringException(Exception):558class _StringException(Exception):
524559
=== modified file 'testtools/tests/test_compat.py'
--- testtools/tests/test_compat.py 2010-11-11 09:46:18 +0000
+++ testtools/tests/test_compat.py 2010-11-29 00:28:14 +0000
@@ -19,6 +19,7 @@
19 )19 )
20from testtools.matchers import (20from testtools.matchers import (
21 MatchesException,21 MatchesException,
22 Not,
22 Raises,23 Raises,
23 )24 )
2425
@@ -196,34 +197,34 @@
196 super(TestUnicodeOutputStream, self).setUp()197 super(TestUnicodeOutputStream, self).setUp()
197 if sys.platform == "cli":198 if sys.platform == "cli":
198 self.skip("IronPython shouldn't wrap streams to do encoding")199 self.skip("IronPython shouldn't wrap streams to do encoding")
199 200
200 def test_no_encoding_becomes_ascii(self):201 def test_no_encoding_becomes_ascii(self):
201 """A stream with no encoding attribute gets ascii/replace strings"""202 """A stream with no encoding attribute gets ascii/replace strings"""
202 sout = _FakeOutputStream()203 sout = _FakeOutputStream()
203 unicode_output_stream(sout).write(self.uni)204 unicode_output_stream(sout).write(self.uni)
204 self.assertEqual([_b("pa???n")], sout.writelog)205 self.assertEqual([_b("pa???n")], sout.writelog)
205 206
206 def test_encoding_as_none_becomes_ascii(self):207 def test_encoding_as_none_becomes_ascii(self):
207 """A stream with encoding value of None gets ascii/replace strings"""208 """A stream with encoding value of None gets ascii/replace strings"""
208 sout = _FakeOutputStream()209 sout = _FakeOutputStream()
209 sout.encoding = None210 sout.encoding = None
210 unicode_output_stream(sout).write(self.uni)211 unicode_output_stream(sout).write(self.uni)
211 self.assertEqual([_b("pa???n")], sout.writelog)212 self.assertEqual([_b("pa???n")], sout.writelog)
212 213
213 def test_bogus_encoding_becomes_ascii(self):214 def test_bogus_encoding_becomes_ascii(self):
214 """A stream with a bogus encoding gets ascii/replace strings"""215 """A stream with a bogus encoding gets ascii/replace strings"""
215 sout = _FakeOutputStream()216 sout = _FakeOutputStream()
216 sout.encoding = "bogus"217 sout.encoding = "bogus"
217 unicode_output_stream(sout).write(self.uni)218 unicode_output_stream(sout).write(self.uni)
218 self.assertEqual([_b("pa???n")], sout.writelog)219 self.assertEqual([_b("pa???n")], sout.writelog)
219 220
220 def test_partial_encoding_replace(self):221 def test_partial_encoding_replace(self):
221 """A string which can be partly encoded correctly should be"""222 """A string which can be partly encoded correctly should be"""
222 sout = _FakeOutputStream()223 sout = _FakeOutputStream()
223 sout.encoding = "iso-8859-7"224 sout.encoding = "iso-8859-7"
224 unicode_output_stream(sout).write(self.uni)225 unicode_output_stream(sout).write(self.uni)
225 self.assertEqual([_b("pa?\xe8?n")], sout.writelog)226 self.assertEqual([_b("pa?\xe8?n")], sout.writelog)
226 227
227 def test_unicode_encodings_not_wrapped(self):228 def test_unicode_encodings_not_wrapped(self):
228 """A unicode encoding is left unwrapped as needs no error handler"""229 """A unicode encoding is left unwrapped as needs no error handler"""
229 sout = _FakeOutputStream()230 sout = _FakeOutputStream()
@@ -232,7 +233,7 @@
232 sout = _FakeOutputStream()233 sout = _FakeOutputStream()
233 sout.encoding = "utf-16-be"234 sout.encoding = "utf-16-be"
234 self.assertIs(unicode_output_stream(sout), sout)235 self.assertIs(unicode_output_stream(sout), sout)
235 236
236 def test_stringio(self):237 def test_stringio(self):
237 """A StringIO object should maybe get an ascii native str type"""238 """A StringIO object should maybe get an ascii native str type"""
238 try:239 try:
@@ -246,7 +247,7 @@
246 if newio:247 if newio:
247 self.expectFailure("Python 3 StringIO expects text not bytes",248 self.expectFailure("Python 3 StringIO expects text not bytes",
248 self.assertThat, lambda: soutwrapper.write(self.uni),249 self.assertThat, lambda: soutwrapper.write(self.uni),
249 Raises(MatchesException(TypeError)))250 Not(Raises(MatchesException(TypeError))))
250 soutwrapper.write(self.uni)251 soutwrapper.write(self.uni)
251 self.assertEqual("pa???n", sout.getvalue())252 self.assertEqual("pa???n", sout.getvalue())
252253
253254
=== modified file 'testtools/tests/test_testresult.py'
--- testtools/tests/test_testresult.py 2010-11-28 12:15:49 +0000
+++ testtools/tests/test_testresult.py 2010-11-29 00:28:14 +0000
@@ -49,8 +49,39 @@
49StringIO = try_imports(['StringIO.StringIO', 'io.StringIO'])49StringIO = try_imports(['StringIO.StringIO', 'io.StringIO'])
5050
5151
52class TestTestResultContract(TestCase):52class Python26Contract(object):
53 """Tests for the contract of TestResults."""53
54 def test_fresh_result_is_successful(self):
55 # A result is considered successful before any tests are run.
56 result = self.makeResult()
57 self.assertTrue(result.wasSuccessful())
58
59 def test_addError_is_failure(self):
60 # addError fails the test run.
61 result = self.makeResult()
62 result.startTest(self)
63 result.addError(self, an_exc_info)
64 result.stopTest(self)
65 self.assertFalse(result.wasSuccessful())
66
67 def test_addFailure_is_failure(self):
68 # addFailure fails the test run.
69 result = self.makeResult()
70 result.startTest(self)
71 result.addFailure(self, an_exc_info)
72 result.stopTest(self)
73 self.assertFalse(result.wasSuccessful())
74
75 def test_addSuccess_is_success(self):
76 # addSuccess does not fail the test run.
77 result = self.makeResult()
78 result.startTest(self)
79 result.addSuccess(self)
80 result.stopTest(self)
81 self.assertTrue(result.wasSuccessful())
82
83
84class Python27Contract(Python26Contract):
5485
55 def test_addExpectedFailure(self):86 def test_addExpectedFailure(self):
56 # Calling addExpectedFailure(test, exc_info) completes ok.87 # Calling addExpectedFailure(test, exc_info) completes ok.
@@ -58,6 +89,52 @@
58 result.startTest(self)89 result.startTest(self)
59 result.addExpectedFailure(self, an_exc_info)90 result.addExpectedFailure(self, an_exc_info)
6091
92 def test_addExpectedFailure_is_success(self):
93 # addExpectedFailure does not fail the test run.
94 result = self.makeResult()
95 result.startTest(self)
96 result.addExpectedFailure(self, an_exc_info)
97 result.stopTest(self)
98 self.assertTrue(result.wasSuccessful())
99
100 def test_addSkipped(self):
101 # Calling addSkip(test, reason) completes ok.
102 result = self.makeResult()
103 result.startTest(self)
104 result.addSkip(self, _u("Skipped for some reason"))
105
106 def test_addSkip_is_success(self):
107 # addSkip does not fail the test run.
108 result = self.makeResult()
109 result.startTest(self)
110 result.addSkip(self, _u("Skipped for some reason"))
111 result.stopTest(self)
112 self.assertTrue(result.wasSuccessful())
113
114 def test_addUnexpectedSuccess(self):
115 # Calling addUnexpectedSuccess(test) completes ok.
116 result = self.makeResult()
117 result.startTest(self)
118 result.addUnexpectedSuccess(self)
119
120 def test_addUnexpectedSuccess_was_successful(self):
121 # addUnexpectedSuccess does not fail the test run in Python 2.7.
122 result = self.makeResult()
123 result.startTest(self)
124 result.addUnexpectedSuccess(self)
125 result.stopTest(self)
126 self.assertTrue(result.wasSuccessful())
127
128 def test_startStopTestRun(self):
129 # Calling startTestRun completes ok.
130 result = self.makeResult()
131 result.startTestRun()
132 result.stopTestRun()
133
134
135class TestResultContract(Python27Contract):
136 """Tests for the contract of TestResults."""
137
61 def test_addExpectedFailure_details(self):138 def test_addExpectedFailure_details(self):
62 # Calling addExpectedFailure(test, details=xxx) completes ok.139 # Calling addExpectedFailure(test, details=xxx) completes ok.
63 result = self.makeResult()140 result = self.makeResult()
@@ -76,24 +153,12 @@
76 result.startTest(self)153 result.startTest(self)
77 result.addFailure(self, details={})154 result.addFailure(self, details={})
78155
79 def test_addSkipped(self):
80 # Calling addSkip(test, reason) completes ok.
81 result = self.makeResult()
82 result.startTest(self)
83 result.addSkip(self, _u("Skipped for some reason"))
84
85 def test_addSkipped_details(self):156 def test_addSkipped_details(self):
86 # Calling addSkip(test, reason) completes ok.157 # Calling addSkip(test, reason) completes ok.
87 result = self.makeResult()158 result = self.makeResult()
88 result.startTest(self)159 result.startTest(self)
89 result.addSkip(self, details={})160 result.addSkip(self, details={})
90161
91 def test_addUnexpectedSuccess(self):
92 # Calling addUnexpectedSuccess(test) completes ok.
93 result = self.makeResult()
94 result.startTest(self)
95 result.addUnexpectedSuccess(self)
96
97 def test_addUnexpectedSuccess_details(self):162 def test_addUnexpectedSuccess_details(self):
98 # Calling addUnexpectedSuccess(test) completes ok.163 # Calling addUnexpectedSuccess(test) completes ok.
99 result = self.makeResult()164 result = self.makeResult()
@@ -106,32 +171,57 @@
106 result.startTest(self)171 result.startTest(self)
107 result.addSuccess(self, details={})172 result.addSuccess(self, details={})
108173
109 def test_startStopTestRun(self):174 def test_addUnexpectedSuccess_was_successful(self):
110 # Calling startTestRun completes ok.175 # addUnexpectedSuccess fails test run in testtools.
111 result = self.makeResult()176 result = self.makeResult()
112 result.startTestRun()177 result.startTest(self)
113 result.stopTestRun()178 result.addUnexpectedSuccess(self)
114179 result.stopTest(self)
115180 self.assertFalse(result.wasSuccessful())
116class TestTestResultContract(TestTestResultContract):181
182 def test_startTestRun_resets_unexpected_success(self):
183 result = self.makeResult()
184 result.startTest(self)
185 result.addUnexpectedSuccess(self)
186 result.stopTest(self)
187 result.startTestRun()
188 self.assertTrue(result.wasSuccessful())
189
190 def test_startTestRun_resets_failure(self):
191 result = self.makeResult()
192 result.startTest(self)
193 result.addFailure(self, an_exc_info)
194 result.stopTest(self)
195 result.startTestRun()
196 self.assertTrue(result.wasSuccessful())
197
198 def test_startTestRun_resets_errors(self):
199 result = self.makeResult()
200 result.startTest(self)
201 result.addError(self, an_exc_info)
202 result.stopTest(self)
203 result.startTestRun()
204 self.assertTrue(result.wasSuccessful())
205
206class TestTestResultContract(TestCase, TestResultContract):
117207
118 def makeResult(self):208 def makeResult(self):
119 return TestResult()209 return TestResult()
120210
121211
122class TestMultiTestresultContract(TestTestResultContract):212class TestMultiTestResultContract(TestCase, TestResultContract):
123213
124 def makeResult(self):214 def makeResult(self):
125 return MultiTestResult(TestResult(), TestResult())215 return MultiTestResult(TestResult(), TestResult())
126216
127217
128class TestTextTestResultContract(TestTestResultContract):218class TestTextTestResultContract(TestCase, TestResultContract):
129219
130 def makeResult(self):220 def makeResult(self):
131 return TextTestResult(StringIO())221 return TextTestResult(StringIO())
132222
133223
134class TestThreadSafeForwardingResultContract(TestTestResultContract):224class TestThreadSafeForwardingResultContract(TestCase, TestResultContract):
135225
136 def makeResult(self):226 def makeResult(self):
137 result_semaphore = threading.Semaphore(1)227 result_semaphore = threading.Semaphore(1)
@@ -139,6 +229,36 @@
139 return ThreadsafeForwardingResult(target, result_semaphore)229 return ThreadsafeForwardingResult(target, result_semaphore)
140230
141231
232class TestExtendedTestResultContract(TestCase, TestResultContract):
233
234 def makeResult(self):
235 return ExtendedTestResult()
236
237
238class TestPython26TestResultContract(TestCase, Python26Contract):
239
240 def makeResult(self):
241 return Python26TestResult()
242
243
244class TestAdaptedPython26TestResultContract(TestCase, TestResultContract):
245
246 def makeResult(self):
247 return ExtendedToOriginalDecorator(Python26TestResult())
248
249
250class TestPython27TestResultContract(TestCase, Python27Contract):
251
252 def makeResult(self):
253 return Python27TestResult()
254
255
256class TestAdaptedPython27TestResultContract(TestCase, TestResultContract):
257
258 def makeResult(self):
259 return ExtendedToOriginalDecorator(Python27TestResult())
260
261
142class TestTestResult(TestCase):262class TestTestResult(TestCase):
143 """Tests for `TestResult`."""263 """Tests for `TestResult`."""
144264
@@ -308,6 +428,12 @@
308 self.fail("yo!")428 self.fail("yo!")
309 return Test("failed")429 return Test("failed")
310430
431 def make_unexpectedly_successful_test(self):
432 class Test(TestCase):
433 def succeeded(self):
434 self.expectFailure("yo!", lambda: None)
435 return Test("succeeded")
436
311 def make_test(self):437 def make_test(self):
312 class Test(TestCase):438 class Test(TestCase):
313 def test(self):439 def test(self):
@@ -393,9 +519,18 @@
393 self.assertThat(self.getvalue(),519 self.assertThat(self.getvalue(),
394 DocTestMatches("...\n\nFAILED (failures=1)\n", doctest.ELLIPSIS))520 DocTestMatches("...\n\nFAILED (failures=1)\n", doctest.ELLIPSIS))
395521
522 def test_stopTestRun_not_successful_unexpected_success(self):
523 test = self.make_unexpectedly_successful_test()
524 self.result.startTestRun()
525 test.run(self.result)
526 self.result.stopTestRun()
527 self.assertThat(self.getvalue(),
528 DocTestMatches("...\n\nFAILED (failures=1)\n", doctest.ELLIPSIS))
529
396 def test_stopTestRun_shows_details(self):530 def test_stopTestRun_shows_details(self):
397 self.result.startTestRun()531 self.result.startTestRun()
398 self.make_erroring_test().run(self.result)532 self.make_erroring_test().run(self.result)
533 self.make_unexpectedly_successful_test().run(self.result)
399 self.make_failing_test().run(self.result)534 self.make_failing_test().run(self.result)
400 self.reset_output()535 self.reset_output()
401 self.result.stopTestRun()536 self.result.stopTestRun()
@@ -428,7 +563,10 @@
428 self.fail("yo!")563 self.fail("yo!")
429AssertionError: yo!564AssertionError: yo!
430------------565------------
431...""", doctest.ELLIPSIS))566======================================================================
567UNEXPECTED SUCCESS: testtools.tests.test_testresult.Test.succeeded
568----------------------------------------------------------------------
569...""", doctest.ELLIPSIS | doctest.REPORT_NDIFF))
432570
433571
434class TestThreadSafeForwardingResult(TestWithFakeExceptions):572class TestThreadSafeForwardingResult(TestWithFakeExceptions):

Subscribers

People subscribed via source and target branches