diff options
Diffstat (limited to 'lib/testtools/testtools/testresult/real.py')
-rw-r--r-- | lib/testtools/testtools/testresult/real.py | 129 |
1 files changed, 78 insertions, 51 deletions
diff --git a/lib/testtools/testtools/testresult/real.py b/lib/testtools/testtools/testresult/real.py index 95f6e8f04c..d1a1023645 100644 --- a/lib/testtools/testtools/testresult/real.py +++ b/lib/testtools/testtools/testresult/real.py @@ -11,6 +11,7 @@ __all__ = [ ] import datetime +import sys import unittest from testtools.compat import _format_exc_info, str_is_unicode, _u @@ -34,13 +35,11 @@ class TestResult(unittest.TestResult): """ def __init__(self): - super(TestResult, self).__init__() - self.skip_reasons = {} - self.__now = None - # -- Start: As per python 2.7 -- - self.expectedFailures = [] - self.unexpectedSuccesses = [] - # -- End: As per python 2.7 -- + # startTestRun resets all attributes, and older clients don't know to + # call startTestRun, so it is called once here. + # Because subclasses may reasonably not expect this, we call the + # specific version we want to run. + TestResult.startTestRun(self) def addExpectedFailure(self, test, err=None, details=None): """Called when a test has failed in an expected manner. @@ -107,6 +106,18 @@ class TestResult(unittest.TestResult): """Called when a test was expected to fail, but succeed.""" self.unexpectedSuccesses.append(test) + def wasSuccessful(self): + """Has this result been successful so far? + + If there have been any errors, failures or unexpected successes, + return False. Otherwise, return True. + + Note: This differs from standard unittest in that we consider + unexpected successes to be equivalent to failures, rather than + successes. + """ + return not (self.errors or self.failures or self.unexpectedSuccesses) + if str_is_unicode: # Python 3 and IronPython strings are unicode, use parent class method _exc_info_to_unicode = unittest.TestResult._exc_info_to_string @@ -145,8 +156,16 @@ class TestResult(unittest.TestResult): def startTestRun(self): """Called before a test run starts. - New in python 2.7 + New in python 2.7. The testtools version resets the result to a + pristine condition ready for use in another test run. """ + super(TestResult, self).__init__() + self.skip_reasons = {} + self.__now = None + # -- Start: As per python 2.7 -- + self.expectedFailures = [] + self.unexpectedSuccesses = [] + # -- End: As per python 2.7 -- def stopTestRun(self): """Called after a test run completes @@ -181,7 +200,7 @@ class MultiTestResult(TestResult): def __init__(self, *results): TestResult.__init__(self) - self._results = map(ExtendedToOriginalDecorator, results) + self._results = list(map(ExtendedToOriginalDecorator, results)) def _dispatch(self, message, *args, **kwargs): return tuple( @@ -222,6 +241,13 @@ class MultiTestResult(TestResult): def done(self): return self._dispatch('done') + def wasSuccessful(self): + """Was this result successful? + + Only returns True if every constituent result was successful. + """ + return all(self._dispatch('wasSuccessful')) + class TextTestResult(TestResult): """A TestResult which outputs activity to a text stream.""" @@ -257,6 +283,10 @@ class TextTestResult(TestResult): stop = self._now() self._show_list('ERROR', self.errors) self._show_list('FAIL', self.failures) + for test in self.unexpectedSuccesses: + self.stream.write( + "%sUNEXPECTED SUCCESS: %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))) @@ -266,7 +296,8 @@ class TextTestResult(TestResult): self.stream.write("FAILED (") details = [] details.append("failures=%d" % ( - len(self.failures) + len(self.errors))) + sum(map(len, ( + self.failures, self.errors, self.unexpectedSuccesses))))) self.stream.write(", ".join(details)) self.stream.write(")\n") super(TextTestResult, self).stopTestRun() @@ -300,59 +331,42 @@ class ThreadsafeForwardingResult(TestResult): self.result = ExtendedToOriginalDecorator(target) self.semaphore = semaphore - def addError(self, test, err=None, details=None): + def _add_result_with_semaphore(self, method, test, *args, **kwargs): self.semaphore.acquire() try: + self.result.time(self._test_start) self.result.startTest(test) - self.result.addError(test, err, details=details) - self.result.stopTest(test) + self.result.time(self._now()) + try: + method(test, *args, **kwargs) + finally: + self.result.stopTest(test) finally: self.semaphore.release() + def addError(self, test, err=None, details=None): + self._add_result_with_semaphore(self.result.addError, + test, err, details=details) + def addExpectedFailure(self, test, err=None, details=None): - self.semaphore.acquire() - try: - self.result.startTest(test) - self.result.addExpectedFailure(test, err, details=details) - self.result.stopTest(test) - finally: - self.semaphore.release() + self._add_result_with_semaphore(self.result.addExpectedFailure, + test, err, details=details) def addFailure(self, test, err=None, details=None): - self.semaphore.acquire() - try: - self.result.startTest(test) - self.result.addFailure(test, err, details=details) - self.result.stopTest(test) - finally: - self.semaphore.release() + self._add_result_with_semaphore(self.result.addFailure, + test, err, details=details) def addSkip(self, test, reason=None, details=None): - self.semaphore.acquire() - try: - self.result.startTest(test) - self.result.addSkip(test, reason, details=details) - self.result.stopTest(test) - finally: - self.semaphore.release() + self._add_result_with_semaphore(self.result.addSkip, + test, reason, details=details) def addSuccess(self, test, details=None): - self.semaphore.acquire() - try: - self.result.startTest(test) - self.result.addSuccess(test, details=details) - self.result.stopTest(test) - finally: - self.semaphore.release() + self._add_result_with_semaphore(self.result.addSuccess, + test, details=details) def addUnexpectedSuccess(self, test, details=None): - self.semaphore.acquire() - try: - self.result.startTest(test) - self.result.addUnexpectedSuccess(test, details=details) - self.result.stopTest(test) - finally: - self.semaphore.release() + self._add_result_with_semaphore(self.result.addUnexpectedSuccess, + test, details=details) def startTestRun(self): self.semaphore.acquire() @@ -375,6 +389,13 @@ class ThreadsafeForwardingResult(TestResult): finally: self.semaphore.release() + def startTest(self, test): + self._test_start = self._now() + super(ThreadsafeForwardingResult, self).startTest(test) + + def wasSuccessful(self): + return self.result.wasSuccessful() + class ExtendedToOriginalDecorator(object): """Permit new TestResult API code to degrade gracefully with old results. @@ -435,14 +456,20 @@ class ExtendedToOriginalDecorator(object): try: return addSkip(test, details=details) except TypeError: - # have to convert - reason = _details_to_str(details) + # extract the reason if it's available + try: + reason = ''.join(details['reason'].iter_text()) + except KeyError: + reason = _details_to_str(details) return addSkip(test, reason) def addUnexpectedSuccess(self, test, details=None): outcome = getattr(self.decorated, 'addUnexpectedSuccess', None) if outcome is None: - return self.decorated.addSuccess(test) + try: + test.fail("") + except test.failureException: + return self.addFailure(test, sys.exc_info()) if details is not None: try: return outcome(test, details=details) |