Support TestResults without stop/shouldStop

Twisted's IReporter contract doesn't specify `stop` or `shouldStop`.
With this change, we can still meaningfully call `stop()` after adapting
with `ExtendedToOriginalDecorator`.
This commit is contained in:
Jonathan Lange 2016-02-04 14:12:50 +00:00
parent b3394b2a5c
commit ce009d3e86
4 changed files with 106 additions and 29 deletions

9
NEWS
View File

@ -3,6 +3,15 @@ testtools NEWS
Changes and improvements to testtools_, grouped by release.
Next
~~~~
Improvements
------------
* ``TestResult`` objects that don't implement ``stop``/``shouldStop`` are now
handled sanely. (Jonathan Lange)
2.0.0
~~~~~

View File

@ -1,4 +1,4 @@
# Copyright (c) 2009-2015 testtools developers. See LICENSE for details.
# Copyright (c) 2009-2016 testtools developers. See LICENSE for details.
"""Doubles of test result objects, useful for testing unittest code."""
@ -6,6 +6,7 @@ __all__ = [
'Python26TestResult',
'Python27TestResult',
'ExtendedTestResult',
'TwistedTestResult',
'StreamResult',
]
@ -20,14 +21,17 @@ class LoggingBase(object):
if event_log is None:
event_log = []
self._events = event_log
self.shouldStop = False
self._was_successful = True
self.testsRun = 0
class Python26TestResult(LoggingBase):
"""A precisely python 2.6 like test result, that logs."""
def __init__(self, event_log=None):
super(Python26TestResult, self).__init__(event_log=event_log)
self.shouldStop = False
self._was_successful = True
self.testsRun = 0
def addError(self, test, err):
self._was_successful = False
self._events.append(('addError', test, err))
@ -153,17 +157,58 @@ class ExtendedTestResult(Python27TestResult):
return self._was_successful
class StreamResult(object):
class TwistedTestResult(LoggingBase):
"""
Emulate the relevant bits of :py:class:`twisted.trial.itrial.IReporter`.
Used to ensure that we can use ``trial`` as a test runner.
"""
def __init__(self, event_log=None):
super(TwistedTestResult, self).__init__(event_log=event_log)
self._was_successful = True
self.testsRun = 0
def startTest(self, test):
self.testsRun += 1
self._events.append(('startTest', test))
def stopTest(self, test):
self._events.append(('stopTest', test))
def addSuccess(self, test):
self._events.append(('addSuccess', test))
def addError(self, test, error):
self._was_successful = False
self._events.append(('addError', test, error))
def addFailure(self, test, error):
self._was_successful = False
self._events.append(('addFailure', test, error))
def addExpectedFailure(self, test, failure, todo=None):
self._events.append(('addExpectedFailure', test, failure))
def addUnexpectedSuccess(self, test, todo=None):
self._events.append(('addUnexpectedSuccess', test))
def addSkip(self, test, reason):
self._events.append(('addSkip', test, reason))
def wasSuccessful(self):
return self._was_successful
def done(self):
pass
class StreamResult(LoggingBase):
"""A StreamResult implementation for testing.
All events are logged to _events.
"""
def __init__(self, event_log=None):
if event_log is None:
event_log = []
self._events = event_log
def startTestRun(self):
self._events.append(('startTestRun',))

View File

@ -1349,6 +1349,8 @@ class ExtendedToOriginalDecorator(object):
self._tags = TagContext()
# Only used for old TestResults that do not have failfast.
self._failfast = False
# Used for old TestResults that do not have stop.
self._shouldStop = False
def __repr__(self):
return '<%s %r>' % (self.__class__.__name__, self.decorated)
@ -1485,9 +1487,15 @@ class ExtendedToOriginalDecorator(object):
return
return method(offset, whence)
@property
def shouldStop(self):
return self.decorated.shouldStop
def _get_shouldStop(self):
return getattr(self.decorated, 'shouldStop', self._shouldStop)
def _set_shouldStop(self, value):
if safe_hasattr(self.decorated, 'shouldStop'):
self.decorated.shouldStop = value
else:
self._shouldStop = value
shouldStop = property(_get_shouldStop, _set_shouldStop)
def startTest(self, test):
self._tags = TagContext(self._tags)
@ -1501,7 +1509,10 @@ class ExtendedToOriginalDecorator(object):
return
def stop(self):
return self.decorated.stop()
method = getattr(self.decorated, 'stop', None)
if method:
return method()
self.shouldStop = True
def stopTest(self, test):
self._tags = self._tags.parent

View File

@ -1,4 +1,4 @@
# Copyright (c) 2008-2015 testtools developers. See LICENSE for details.
# Copyright (c) 2008-2016 testtools developers. See LICENSE for details.
"""Test TestResults and related things."""
@ -83,6 +83,7 @@ from testtools.testresult.doubles import (
Python27TestResult,
ExtendedTestResult,
StreamResult as LoggingStreamResult,
TwistedTestResult,
)
from testtools.testresult.real import (
_details_to_str,
@ -134,7 +135,22 @@ def make_exception_info(exceptionFactory, *args, **kwargs):
return sys.exc_info()
class Python26Contract(object):
class TestControlContract(object):
"""Stopping test runs."""
def test_initially_not_shouldStop(self):
# A result is not set to stop initially.
result = self.makeResult()
self.assertFalse(result.shouldStop)
def test_stop_sets_shouldStop(self):
# Calling 'stop()' sets 'shouldStop' to True.
result = self.makeResult()
result.stop()
self.assertTrue(result.shouldStop)
class Python26Contract(TestControlContract):
def test_fresh_result_is_successful(self):
# A result is considered successful before any tests are run.
@ -165,11 +181,6 @@ class Python26Contract(object):
result.stopTest(self)
self.assertTrue(result.wasSuccessful())
def test_stop_sets_shouldStop(self):
result = self.makeResult()
result.stop()
self.assertTrue(result.shouldStop)
class Python27Contract(Python26Contract):
@ -462,6 +473,12 @@ class TestAdaptedPython27TestResultContract(TestCase, DetailsContract):
return ExtendedToOriginalDecorator(Python27TestResult())
class TestAdaptedTwistedTestResultContract(TestCase, DetailsContract):
def makeResult(self):
return ExtendedToOriginalDecorator(TwistedTestResult())
class TestAdaptedStreamResult(TestCase, DetailsContract):
def makeResult(self):
@ -1095,15 +1112,10 @@ testtools.matchers._impl.MismatchError: Differences: [
self.assertEqual("foo.bar", result.unexpectedSuccesses[0].id())
class TestTestControl(TestCase):
class TestTestControl(TestCase, TestControlContract):
def test_default(self):
self.assertEqual(False, TestControl().shouldStop)
def test_stop(self):
control = TestControl()
control.stop()
self.assertEqual(True, control.shouldStop)
def makeResult(self):
return TestControl()
class TestTestResult(TestCase):