summaryrefslogtreecommitdiff
path: root/lib/testtools/testtools/testresult/real.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/testtools/testtools/testresult/real.py')
-rw-r--r--lib/testtools/testtools/testresult/real.py129
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)