Merge trunk.
This commit is contained in:
@@ -7,3 +7,4 @@ TAGS
|
|||||||
apidocs
|
apidocs
|
||||||
_trial_temp
|
_trial_temp
|
||||||
doc/_build
|
doc/_build
|
||||||
|
./.testrepository
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
test_command=PYTHONPATH=. python -m subunit.run testtools.tests.test_suite
|
test_command=PYTHONPATH=. python -m subunit.run $LISTOPT $IDOPTION testtools.tests.test_suite
|
||||||
|
test_id_option=--load-list $IDFILE
|
||||||
|
test_list_option=--list
|
||||||
|
|||||||
81
NEWS
81
NEWS
@@ -7,12 +7,58 @@ NEXT
|
|||||||
Changes
|
Changes
|
||||||
-------
|
-------
|
||||||
|
|
||||||
* addUnexpectedSuccess is translated to addFailure for test results that don't
|
* The timestamps generated by ``TestResult`` objects when no timing data has
|
||||||
know about addUnexpectedSuccess. (Jonathan Lange, #654474)
|
been received are now datetime-with-timezone, which allows them to be
|
||||||
|
sensibly serialised and transported. (Robert Collins, #692297)
|
||||||
|
|
||||||
Improvements
|
Improvements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
* ``MultiTestResult`` now forwards the ``time`` API. (Robert Collins, #692294)
|
||||||
|
|
||||||
|
0.9.8
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
In this release we bring some very interesting improvements:
|
||||||
|
|
||||||
|
* new matchers for exceptions, sets, lists, dicts and more.
|
||||||
|
|
||||||
|
* experimental (works but the contract isn't supported) twisted reactor
|
||||||
|
support.
|
||||||
|
|
||||||
|
* The built in runner can now list tests and filter tests (the -l and
|
||||||
|
--load-list options).
|
||||||
|
|
||||||
|
Changes
|
||||||
|
-------
|
||||||
|
|
||||||
|
* addUnexpectedSuccess is translated to addFailure for test results that don't
|
||||||
|
know about addUnexpectedSuccess. Further, it fails the entire result for
|
||||||
|
all testtools TestResults (i.e. wasSuccessful() returns False after
|
||||||
|
addUnexpectedSuccess has been called). Note that when using a delegating
|
||||||
|
result such as ThreadsafeForwardingResult, MultiTestResult or
|
||||||
|
ExtendedToOriginalDecorator then the behaviour of addUnexpectedSuccess is
|
||||||
|
determined by the delegated to result(s).
|
||||||
|
(Jonathan Lange, Robert Collins, #654474, #683332)
|
||||||
|
|
||||||
|
* startTestRun will reset any errors on the result. That is, wasSuccessful()
|
||||||
|
will always return True immediately after startTestRun() is called. This
|
||||||
|
only applies to delegated test results (ThreadsafeForwardingResult,
|
||||||
|
MultiTestResult and ExtendedToOriginalDecorator) if the delegated to result
|
||||||
|
is a testtools test result - we cannot reliably reset the state of unknown
|
||||||
|
test result class instances. (Jonathan Lange, Robert Collins, #683332)
|
||||||
|
|
||||||
|
* Responsibility for running test cleanups has been moved to ``RunTest``.
|
||||||
|
This change does not affect public APIs and can be safely ignored by test
|
||||||
|
authors. (Jonathan Lange, #662647)
|
||||||
|
|
||||||
|
Improvements
|
||||||
|
------------
|
||||||
|
|
||||||
|
* ``assertIsInstance`` supports a custom error message to be supplied, which
|
||||||
|
is necessary when using ``assertDictEqual`` on Python 2.7 with a
|
||||||
|
``testtools.TestCase`` base class. (Jelmer Vernooij)
|
||||||
|
|
||||||
* Experimental support for running tests that return Deferreds.
|
* Experimental support for running tests that return Deferreds.
|
||||||
(Jonathan Lange, Martin [gz])
|
(Jonathan Lange, Martin [gz])
|
||||||
|
|
||||||
@@ -22,6 +68,9 @@ Improvements
|
|||||||
* Fix the runTest parameter of TestCase to actually work, rather than raising
|
* Fix the runTest parameter of TestCase to actually work, rather than raising
|
||||||
a TypeError. (Jonathan Lange, #657760)
|
a TypeError. (Jonathan Lange, #657760)
|
||||||
|
|
||||||
|
* New matcher ``EndsWith`` added to complement the existing ``StartsWith``
|
||||||
|
matcher. (Jonathan Lange, #669165)
|
||||||
|
|
||||||
* Non-release snapshots of testtools will now work with buildout.
|
* Non-release snapshots of testtools will now work with buildout.
|
||||||
(Jonathan Lange, #613734)
|
(Jonathan Lange, #613734)
|
||||||
|
|
||||||
@@ -30,6 +79,14 @@ Improvements
|
|||||||
* ``MatchesException`` added to the ``testtools.matchers`` module - matches
|
* ``MatchesException`` added to the ``testtools.matchers`` module - matches
|
||||||
an exception class and parameters. (Robert Collins)
|
an exception class and parameters. (Robert Collins)
|
||||||
|
|
||||||
|
* ``MismatchesAll.describe`` no longer appends a trailing newline.
|
||||||
|
(Michael Hudson-Doyle, #686790)
|
||||||
|
|
||||||
|
* New ``KeysEqual`` matcher. (Jonathan Lange)
|
||||||
|
|
||||||
|
* New helpers for conditionally importing modules, ``try_import`` and
|
||||||
|
``try_imports``. (Jonathan Lange)
|
||||||
|
|
||||||
* ``Raises`` added to the ``testtools.matchers`` module - matches if the
|
* ``Raises`` added to the ``testtools.matchers`` module - matches if the
|
||||||
supplied callable raises, and delegates to an optional matcher for validation
|
supplied callable raises, and delegates to an optional matcher for validation
|
||||||
of the exception. (Robert Collins)
|
of the exception. (Robert Collins)
|
||||||
@@ -38,19 +95,29 @@ Improvements
|
|||||||
supplied callable raises and delegates to ``MatchesException`` to validate
|
supplied callable raises and delegates to ``MatchesException`` to validate
|
||||||
the exception. (Jonathan Lange)
|
the exception. (Jonathan Lange)
|
||||||
|
|
||||||
* ``testools.TestCase.useFixture`` has been added to glue with fixtures nicely.
|
* Tests will now pass on Python 2.6.4 : an ``Exception`` change made only in
|
||||||
|
2.6.4 and reverted in Python 2.6.5 was causing test failures on that version.
|
||||||
|
(Martin [gz], #689858).
|
||||||
|
|
||||||
|
* ``testtools.TestCase.useFixture`` has been added to glue with fixtures nicely.
|
||||||
(Robert Collins)
|
(Robert Collins)
|
||||||
|
|
||||||
|
* ``testtools.run`` now supports ``-l`` to list tests rather than executing
|
||||||
|
them. This is useful for integration with external test analysis/processing
|
||||||
|
tools like subunit and testrepository. (Robert Collins)
|
||||||
|
|
||||||
|
* ``testtools.run`` now supports ``--load-list``, which takes a file containing
|
||||||
|
test ids, one per line, and intersects those ids with the tests found. This
|
||||||
|
allows fine grained control of what tests are run even when the tests cannot
|
||||||
|
be named as objects to import (e.g. due to test parameterisation via
|
||||||
|
testscenarios). (Robert Collins)
|
||||||
|
|
||||||
* Update documentation to say how to use testtools.run() on Python 2.4.
|
* Update documentation to say how to use testtools.run() on Python 2.4.
|
||||||
(Jonathan Lange, #501174)
|
(Jonathan Lange, #501174)
|
||||||
|
|
||||||
* ``text_content`` conveniently converts a Python string to a Content object.
|
* ``text_content`` conveniently converts a Python string to a Content object.
|
||||||
(Jonathan Lange, James Westby)
|
(Jonathan Lange, James Westby)
|
||||||
|
|
||||||
* New ``KeysEqual`` matcher. (Jonathan Lange)
|
|
||||||
|
|
||||||
* New helpers for conditionally importing modules, ``try_import`` and
|
|
||||||
``try_imports``. (Jonathan Lange)
|
|
||||||
|
|
||||||
|
|
||||||
0.9.7
|
0.9.7
|
||||||
|
|||||||
@@ -119,28 +119,28 @@ permanently present at the top of the list.
|
|||||||
Release tasks
|
Release tasks
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
#. Choose a version number, say X.Y.Z
|
1. Choose a version number, say X.Y.Z
|
||||||
#. Branch from trunk to testtools-X.Y.Z
|
1. Branch from trunk to testtools-X.Y.Z
|
||||||
#. In testtools-X.Y.Z, ensure __init__ has version X.Y.Z.
|
1. In testtools-X.Y.Z, ensure __init__ has version X.Y.Z.
|
||||||
#. Replace NEXT in NEWS with the version number X.Y.Z, adjusting the reST.
|
1. Replace NEXT in NEWS with the version number X.Y.Z, adjusting the reST.
|
||||||
#. Possibly write a blurb into NEWS.
|
1. Possibly write a blurb into NEWS.
|
||||||
#. Replace any additional references to NEXT with the version being released.
|
1. Replace any additional references to NEXT with the version being
|
||||||
(should be none).
|
released. (should be none).
|
||||||
#. Commit the changes.
|
1. Commit the changes.
|
||||||
#. Tag the release, bzr tag testtools-X.Y.Z
|
1. Tag the release, bzr tag testtools-X.Y.Z
|
||||||
#. Create a source distribution and upload to pypi ('make release').
|
1. Create a source distribution and upload to pypi ('make release').
|
||||||
#. Make sure all "Fix committed" bugs are in the 'next' milestone on Launchpad
|
1. Make sure all "Fix committed" bugs are in the 'next' milestone on
|
||||||
#. Rename the 'next' milestone on Launchpad to 'X.Y.Z'
|
Launchpad
|
||||||
#. Create a release on the newly-renamed 'X.Y.Z' milestone
|
1. Rename the 'next' milestone on Launchpad to 'X.Y.Z'
|
||||||
#. Upload the tarball and asc file to Launchpad
|
1. Create a release on the newly-renamed 'X.Y.Z' milestone
|
||||||
#. Merge the release branch testtools-X.Y.Z into trunk. Before the commit,
|
1. Upload the tarball and asc file to Launchpad
|
||||||
add a NEXT heading to the top of NEWS. Push trunk to Launchpad.
|
1. Merge the release branch testtools-X.Y.Z into trunk. Before the commit,
|
||||||
#. If a new series has been created (e.g. 0.10.0), make the series on Launchpad.
|
add a NEXT heading to the top of NEWS and bump the version in __init__.py.
|
||||||
#. Make a new milestone for the *next release*.
|
Push trunk to Launchpad
|
||||||
|
1. If a new series has been created (e.g. 0.10.0), make the series on Launchpad.
|
||||||
#. During release we rename NEXT to $version.
|
1. Make a new milestone for the *next release*.
|
||||||
#. We call new milestones NEXT.
|
1. During release we rename NEXT to $version.
|
||||||
|
1. We call new milestones NEXT.
|
||||||
|
|
||||||
.. _PEP 8: http://www.python.org/dev/peps/pep-0008/
|
.. _PEP 8: http://www.python.org/dev/peps/pep-0008/
|
||||||
.. _unittest: http://docs.python.org/library/unittest.html
|
.. _unittest: http://docs.python.org/library/unittest.html
|
||||||
@@ -148,3 +148,4 @@ Release tasks
|
|||||||
.. _MIT license: http://www.opensource.org/licenses/mit-license.php
|
.. _MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||||
.. _Sphinx: http://sphinx.pocoo.org/
|
.. _Sphinx: http://sphinx.pocoo.org/
|
||||||
.. _restructuredtext: http://docutils.sourceforge.net/rst.html
|
.. _restructuredtext: http://docutils.sourceforge.net/rst.html
|
||||||
|
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ from testtools.matchers import (
|
|||||||
Matcher,
|
Matcher,
|
||||||
)
|
)
|
||||||
from testtools.runtest import (
|
from testtools.runtest import (
|
||||||
|
MultipleExceptions,
|
||||||
RunTest,
|
RunTest,
|
||||||
)
|
)
|
||||||
from testtools.testcase import (
|
from testtools.testcase import (
|
||||||
ErrorHolder,
|
ErrorHolder,
|
||||||
MultipleExceptions,
|
|
||||||
PlaceHolder,
|
PlaceHolder,
|
||||||
TestCase,
|
TestCase,
|
||||||
clone_test_with_new_id,
|
clone_test_with_new_id,
|
||||||
@@ -69,4 +69,4 @@ from testtools.testsuite import (
|
|||||||
# If the releaselevel is 'final', then the tarball will be major.minor.micro.
|
# If the releaselevel is 'final', then the tarball will be major.minor.micro.
|
||||||
# Otherwise it is major.minor.micro~$(revno).
|
# Otherwise it is major.minor.micro~$(revno).
|
||||||
|
|
||||||
__version__ = (0, 9, 8, 'dev', 0)
|
__version__ = (0, 9, 9, 'dev', 0)
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ __all__ = [
|
|||||||
|
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
|
from testtools.monkey import MonkeyPatcher
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
from twisted.internet.base import DelayedCall
|
||||||
from twisted.internet.interfaces import IReactorThreads
|
from twisted.internet.interfaces import IReactorThreads
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
from twisted.python.util import mergeFunctionMetadata
|
from twisted.python.util import mergeFunctionMetadata
|
||||||
@@ -165,13 +168,20 @@ class Spinner(object):
|
|||||||
# the ideal, and it actually works for many cases.
|
# the ideal, and it actually works for many cases.
|
||||||
_OBLIGATORY_REACTOR_ITERATIONS = 0
|
_OBLIGATORY_REACTOR_ITERATIONS = 0
|
||||||
|
|
||||||
def __init__(self, reactor):
|
def __init__(self, reactor, debug=False):
|
||||||
|
"""Construct a Spinner.
|
||||||
|
|
||||||
|
:param reactor: A Twisted reactor.
|
||||||
|
:param debug: Whether or not to enable Twisted's debugging. Defaults
|
||||||
|
to False.
|
||||||
|
"""
|
||||||
self._reactor = reactor
|
self._reactor = reactor
|
||||||
self._timeout_call = None
|
self._timeout_call = None
|
||||||
self._success = self._UNSET
|
self._success = self._UNSET
|
||||||
self._failure = self._UNSET
|
self._failure = self._UNSET
|
||||||
self._saved_signals = []
|
self._saved_signals = []
|
||||||
self._junk = []
|
self._junk = []
|
||||||
|
self._debug = debug
|
||||||
|
|
||||||
def _cancel_timeout(self):
|
def _cancel_timeout(self):
|
||||||
if self._timeout_call:
|
if self._timeout_call:
|
||||||
@@ -222,7 +232,6 @@ class Spinner(object):
|
|||||||
# we aren't going to bother.
|
# we aren't going to bother.
|
||||||
junk.append(selectable)
|
junk.append(selectable)
|
||||||
if IReactorThreads.providedBy(self._reactor):
|
if IReactorThreads.providedBy(self._reactor):
|
||||||
self._reactor.suggestThreadPoolSize(0)
|
|
||||||
if self._reactor.threadpool is not None:
|
if self._reactor.threadpool is not None:
|
||||||
self._reactor._stopThreadPool()
|
self._reactor._stopThreadPool()
|
||||||
self._junk.extend(junk)
|
self._junk.extend(junk)
|
||||||
@@ -270,18 +279,24 @@ class Spinner(object):
|
|||||||
:return: Whatever is at the end of the function's callback chain. If
|
:return: Whatever is at the end of the function's callback chain. If
|
||||||
it's an error, then raise that.
|
it's an error, then raise that.
|
||||||
"""
|
"""
|
||||||
|
debug = MonkeyPatcher()
|
||||||
|
if self._debug:
|
||||||
|
debug.add_patch(defer.Deferred, 'debug', True)
|
||||||
|
debug.add_patch(DelayedCall, 'debug', True)
|
||||||
|
debug.patch()
|
||||||
|
try:
|
||||||
junk = self.get_junk()
|
junk = self.get_junk()
|
||||||
if junk:
|
if junk:
|
||||||
raise StaleJunkError(junk)
|
raise StaleJunkError(junk)
|
||||||
self._save_signals()
|
self._save_signals()
|
||||||
self._timeout_call = self._reactor.callLater(
|
self._timeout_call = self._reactor.callLater(
|
||||||
timeout, self._timed_out, function, timeout)
|
timeout, self._timed_out, function, timeout)
|
||||||
# Calling 'stop' on the reactor will make it impossible to re-start
|
# Calling 'stop' on the reactor will make it impossible to
|
||||||
# the reactor. Since the default signal handlers for TERM, BREAK and
|
# re-start the reactor. Since the default signal handlers for
|
||||||
# INT all call reactor.stop(), we'll patch it over with crash.
|
# TERM, BREAK and INT all call reactor.stop(), we'll patch it over
|
||||||
# XXX: It might be a better idea to either install custom signal
|
# with crash. XXX: It might be a better idea to either install
|
||||||
# handlers or to override the methods that are Twisted's signal
|
# custom signal handlers or to override the methods that are
|
||||||
# handlers.
|
# Twisted's signal handlers.
|
||||||
stop, self._reactor.stop = self._reactor.stop, self._reactor.crash
|
stop, self._reactor.stop = self._reactor.stop, self._reactor.crash
|
||||||
def run_function():
|
def run_function():
|
||||||
d = defer.maybeDeferred(function, *args, **kwargs)
|
d = defer.maybeDeferred(function, *args, **kwargs)
|
||||||
@@ -297,3 +312,5 @@ class Spinner(object):
|
|||||||
return self._get_result()
|
return self._get_result()
|
||||||
finally:
|
finally:
|
||||||
self._clean()
|
self._clean()
|
||||||
|
finally:
|
||||||
|
debug.restore()
|
||||||
|
|||||||
@@ -65,6 +65,34 @@ else:
|
|||||||
_u.__doc__ = __u_doc
|
_u.__doc__ = __u_doc
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info > (2, 5):
|
||||||
|
all = all
|
||||||
|
_error_repr = BaseException.__repr__
|
||||||
|
def isbaseexception(exception):
|
||||||
|
"""Return whether exception inherits from BaseException only"""
|
||||||
|
return (isinstance(exception, BaseException)
|
||||||
|
and not isinstance(exception, Exception))
|
||||||
|
else:
|
||||||
|
def all(iterable):
|
||||||
|
"""If contents of iterable all evaluate as boolean True"""
|
||||||
|
for obj in iterable:
|
||||||
|
if not obj:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
def _error_repr(exception):
|
||||||
|
"""Format an exception instance as Python 2.5 and later do"""
|
||||||
|
return exception.__class__.__name__ + repr(exception.args)
|
||||||
|
def isbaseexception(exception):
|
||||||
|
"""Return whether exception would inherit from BaseException only
|
||||||
|
|
||||||
|
This approximates the hierarchy in Python 2.5 and later, compare the
|
||||||
|
difference between the diagrams at the bottom of the pages:
|
||||||
|
<http://docs.python.org/release/2.4.4/lib/module-exceptions.html>
|
||||||
|
<http://docs.python.org/release/2.5.4/lib/module-exceptions.html>
|
||||||
|
"""
|
||||||
|
return isinstance(exception, (KeyboardInterrupt, SystemExit))
|
||||||
|
|
||||||
|
|
||||||
def unicode_output_stream(stream):
|
def unicode_output_stream(stream):
|
||||||
"""Get wrapper for given stream that writes any unicode without exception
|
"""Get wrapper for given stream that writes any unicode without exception
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ class AsynchronousDeferredRunTest(_DeferredRunTest):
|
|||||||
This is highly experimental code. Use at your own risk.
|
This is highly experimental code. Use at your own risk.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, case, handlers=None, reactor=None, timeout=0.005):
|
def __init__(self, case, handlers=None, reactor=None, timeout=0.005,
|
||||||
|
debug=False):
|
||||||
"""Construct an `AsynchronousDeferredRunTest`.
|
"""Construct an `AsynchronousDeferredRunTest`.
|
||||||
|
|
||||||
:param case: The `testtools.TestCase` to run.
|
:param case: The `testtools.TestCase` to run.
|
||||||
@@ -102,22 +103,26 @@ class AsynchronousDeferredRunTest(_DeferredRunTest):
|
|||||||
default reactor.
|
default reactor.
|
||||||
:param timeout: The maximum time allowed for running a test. The
|
:param timeout: The maximum time allowed for running a test. The
|
||||||
default is 0.005s.
|
default is 0.005s.
|
||||||
|
:param debug: Whether or not to enable Twisted's debugging. Use this
|
||||||
|
to get information about unhandled Deferreds and left-over
|
||||||
|
DelayedCalls. Defaults to False.
|
||||||
"""
|
"""
|
||||||
super(AsynchronousDeferredRunTest, self).__init__(case, handlers)
|
super(AsynchronousDeferredRunTest, self).__init__(case, handlers)
|
||||||
if reactor is None:
|
if reactor is None:
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
self._reactor = reactor
|
self._reactor = reactor
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
|
self._debug = debug
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def make_factory(cls, reactor=None, timeout=0.005):
|
def make_factory(cls, reactor=None, timeout=0.005, debug=False):
|
||||||
"""Make a factory that conforms to the RunTest factory interface."""
|
"""Make a factory that conforms to the RunTest factory interface."""
|
||||||
# This is horrible, but it means that the return value of the method
|
# This is horrible, but it means that the return value of the method
|
||||||
# will be able to be assigned to a class variable *and* also be
|
# will be able to be assigned to a class variable *and* also be
|
||||||
# invoked directly.
|
# invoked directly.
|
||||||
class AsynchronousDeferredRunTestFactory:
|
class AsynchronousDeferredRunTestFactory:
|
||||||
def __call__(self, case, handlers=None):
|
def __call__(self, case, handlers=None):
|
||||||
return cls(case, handlers, reactor, timeout)
|
return cls(case, handlers, reactor, timeout, debug)
|
||||||
return AsynchronousDeferredRunTestFactory()
|
return AsynchronousDeferredRunTestFactory()
|
||||||
|
|
||||||
@defer.deferredGenerator
|
@defer.deferredGenerator
|
||||||
@@ -143,7 +148,7 @@ class AsynchronousDeferredRunTest(_DeferredRunTest):
|
|||||||
|
|
||||||
def _make_spinner(self):
|
def _make_spinner(self):
|
||||||
"""Make the `Spinner` to be used to run the tests."""
|
"""Make the `Spinner` to be used to run the tests."""
|
||||||
return Spinner(self._reactor)
|
return Spinner(self._reactor, debug=self._debug)
|
||||||
|
|
||||||
def _run_deferred(self):
|
def _run_deferred(self):
|
||||||
"""Run the test, assuming everything in it is Deferred-returning.
|
"""Run the test, assuming everything in it is Deferred-returning.
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ __metaclass__ = type
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
'Annotate',
|
'Annotate',
|
||||||
'DocTestMatches',
|
'DocTestMatches',
|
||||||
'DoesNotStartWith',
|
|
||||||
'Equals',
|
'Equals',
|
||||||
'Is',
|
'Is',
|
||||||
'LessThan',
|
'LessThan',
|
||||||
@@ -33,6 +32,8 @@ import operator
|
|||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from testtools.compat import classtypes, _error_repr, isbaseexception
|
||||||
|
|
||||||
|
|
||||||
class Matcher(object):
|
class Matcher(object):
|
||||||
"""A pattern matcher.
|
"""A pattern matcher.
|
||||||
@@ -179,6 +180,22 @@ class DoesNotStartWith(Mismatch):
|
|||||||
self.matchee, self.expected)
|
self.matchee, self.expected)
|
||||||
|
|
||||||
|
|
||||||
|
class DoesNotEndWith(Mismatch):
|
||||||
|
|
||||||
|
def __init__(self, matchee, expected):
|
||||||
|
"""Create a DoesNotEndWith Mismatch.
|
||||||
|
|
||||||
|
:param matchee: the string that did not match.
|
||||||
|
:param expected: the string that `matchee` was expected to end with.
|
||||||
|
"""
|
||||||
|
self.matchee = matchee
|
||||||
|
self.expected = expected
|
||||||
|
|
||||||
|
def describe(self):
|
||||||
|
return "'%s' does not end with '%s'." % (
|
||||||
|
self.matchee, self.expected)
|
||||||
|
|
||||||
|
|
||||||
class _BinaryComparison(object):
|
class _BinaryComparison(object):
|
||||||
"""Matcher that compares an object to another object."""
|
"""Matcher that compares an object to another object."""
|
||||||
|
|
||||||
@@ -299,7 +316,7 @@ class MismatchesAll(Mismatch):
|
|||||||
descriptions = ["Differences: ["]
|
descriptions = ["Differences: ["]
|
||||||
for mismatch in self.mismatches:
|
for mismatch in self.mismatches:
|
||||||
descriptions.append(mismatch.describe())
|
descriptions.append(mismatch.describe())
|
||||||
descriptions.append("]\n")
|
descriptions.append("]")
|
||||||
return '\n'.join(descriptions)
|
return '\n'.join(descriptions)
|
||||||
|
|
||||||
|
|
||||||
@@ -344,25 +361,24 @@ class MatchesException(Matcher):
|
|||||||
"""
|
"""
|
||||||
Matcher.__init__(self)
|
Matcher.__init__(self)
|
||||||
self.expected = exception
|
self.expected = exception
|
||||||
|
self._is_instance = type(self.expected) not in classtypes()
|
||||||
def _expected_type(self):
|
|
||||||
if type(self.expected) is type:
|
|
||||||
return self.expected
|
|
||||||
return type(self.expected)
|
|
||||||
|
|
||||||
def match(self, other):
|
def match(self, other):
|
||||||
if type(other) != tuple:
|
if type(other) != tuple:
|
||||||
return Mismatch('%r is not an exc_info tuple' % other)
|
return Mismatch('%r is not an exc_info tuple' % other)
|
||||||
if not issubclass(other[0], self._expected_type()):
|
expected_class = self.expected
|
||||||
return Mismatch('%r is not a %r' % (
|
if self._is_instance:
|
||||||
other[0], self._expected_type()))
|
expected_class = expected_class.__class__
|
||||||
if (type(self.expected) is not type and
|
if not issubclass(other[0], expected_class):
|
||||||
other[1].args != self.expected.args):
|
return Mismatch('%r is not a %r' % (other[0], expected_class))
|
||||||
return Mismatch('%r has different arguments to %r.' % (
|
if self._is_instance and other[1].args != self.expected.args:
|
||||||
other[1], self.expected))
|
return Mismatch('%s has different arguments to %s.' % (
|
||||||
|
_error_repr(other[1]), _error_repr(self.expected)))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "MatchesException(%r)" % self.expected
|
if self._is_instance:
|
||||||
|
return "MatchesException(%s)" % _error_repr(self.expected)
|
||||||
|
return "MatchesException(%s)" % repr(self.expected)
|
||||||
|
|
||||||
|
|
||||||
class StartsWith(Matcher):
|
class StartsWith(Matcher):
|
||||||
@@ -384,6 +400,25 @@ class StartsWith(Matcher):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class EndsWith(Matcher):
|
||||||
|
"""Checks whether one string starts with another."""
|
||||||
|
|
||||||
|
def __init__(self, expected):
|
||||||
|
"""Create a EndsWith Matcher.
|
||||||
|
|
||||||
|
:param expected: the string that matchees should end with.
|
||||||
|
"""
|
||||||
|
self.expected = expected
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Ends with '%s'." % self.expected
|
||||||
|
|
||||||
|
def match(self, matchee):
|
||||||
|
if not matchee.endswith(self.expected):
|
||||||
|
return DoesNotEndWith(matchee, self.expected)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class KeysEqual(Matcher):
|
class KeysEqual(Matcher):
|
||||||
"""Checks whether a dict has particular keys."""
|
"""Checks whether a dict has particular keys."""
|
||||||
|
|
||||||
@@ -467,7 +502,6 @@ class Raises(Matcher):
|
|||||||
# Catch all exceptions: Raises() should be able to match a
|
# Catch all exceptions: Raises() should be able to match a
|
||||||
# KeyboardInterrupt or SystemExit.
|
# KeyboardInterrupt or SystemExit.
|
||||||
except:
|
except:
|
||||||
exc_info = sys.exc_info()
|
|
||||||
if self.exception_matcher:
|
if self.exception_matcher:
|
||||||
mismatch = self.exception_matcher.match(sys.exc_info())
|
mismatch = self.exception_matcher.match(sys.exc_info())
|
||||||
if not mismatch:
|
if not mismatch:
|
||||||
@@ -476,9 +510,9 @@ class Raises(Matcher):
|
|||||||
mismatch = None
|
mismatch = None
|
||||||
# The exception did not match, or no explicit matching logic was
|
# The exception did not match, or no explicit matching logic was
|
||||||
# performed. If the exception is a non-user exception (that is, not
|
# performed. If the exception is a non-user exception (that is, not
|
||||||
# a subclass of Exception) then propogate it.
|
# a subclass of Exception on Python 2.5+) then propogate it.
|
||||||
if not issubclass(exc_info[0], Exception):
|
if isbaseexception(sys.exc_info()[1]):
|
||||||
raise exc_info[0], exc_info[1], exc_info[2]
|
raise
|
||||||
return mismatch
|
return mismatch
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import sys
|
|||||||
|
|
||||||
from testtools import TextTestResult
|
from testtools import TextTestResult
|
||||||
from testtools.compat import classtypes, istext, unicode_output_stream
|
from testtools.compat import classtypes, istext, unicode_output_stream
|
||||||
|
from testtools.testsuite import iterate_tests
|
||||||
|
|
||||||
|
|
||||||
defaultTestLoader = unittest.defaultTestLoader
|
defaultTestLoader = unittest.defaultTestLoader
|
||||||
@@ -34,9 +35,12 @@ else:
|
|||||||
class TestToolsTestRunner(object):
|
class TestToolsTestRunner(object):
|
||||||
""" A thunk object to support unittest.TestProgram."""
|
""" A thunk object to support unittest.TestProgram."""
|
||||||
|
|
||||||
|
def __init__(self, stdout):
|
||||||
|
self.stdout = stdout
|
||||||
|
|
||||||
def run(self, test):
|
def run(self, test):
|
||||||
"Run the given test case or test suite."
|
"Run the given test case or test suite."
|
||||||
result = TextTestResult(unicode_output_stream(sys.stdout))
|
result = TextTestResult(unicode_output_stream(self.stdout))
|
||||||
result.startTestRun()
|
result.startTestRun()
|
||||||
try:
|
try:
|
||||||
return test.run(result)
|
return test.run(result)
|
||||||
@@ -58,6 +62,12 @@ class TestToolsTestRunner(object):
|
|||||||
# removed.
|
# removed.
|
||||||
# - A tweak has been added to detect 'python -m *.run' and use a
|
# - A tweak has been added to detect 'python -m *.run' and use a
|
||||||
# better progName in that case.
|
# better progName in that case.
|
||||||
|
# - self.module is more comprehensively set to None when being invoked from
|
||||||
|
# the commandline - __name__ is used as a sentinel value.
|
||||||
|
# - --list has been added which can list tests (should be upstreamed).
|
||||||
|
# - --load-list has been added which can reduce the tests used (should be
|
||||||
|
# upstreamed).
|
||||||
|
# - The limitation of using getopt is declared to the user.
|
||||||
|
|
||||||
FAILFAST = " -f, --failfast Stop on first failure\n"
|
FAILFAST = " -f, --failfast Stop on first failure\n"
|
||||||
CATCHBREAK = " -c, --catch Catch control-C and display results\n"
|
CATCHBREAK = " -c, --catch Catch control-C and display results\n"
|
||||||
@@ -70,14 +80,17 @@ Options:
|
|||||||
-h, --help Show this message
|
-h, --help Show this message
|
||||||
-v, --verbose Verbose output
|
-v, --verbose Verbose output
|
||||||
-q, --quiet Minimal output
|
-q, --quiet Minimal output
|
||||||
|
-l, --list List tests rather than executing them.
|
||||||
|
--load-list Specifies a file containing test ids, only tests matching
|
||||||
|
those ids are executed.
|
||||||
%(failfast)s%(catchbreak)s%(buffer)s
|
%(failfast)s%(catchbreak)s%(buffer)s
|
||||||
Examples:
|
Examples:
|
||||||
%(progName)s test_module - run tests from test_module
|
%(progName)s test_module - run tests from test_module
|
||||||
%(progName)s module.TestClass - run tests from module.TestClass
|
%(progName)s module.TestClass - run tests from module.TestClass
|
||||||
%(progName)s module.Class.test_method - run specified test method
|
%(progName)s module.Class.test_method - run specified test method
|
||||||
|
|
||||||
[tests] can be a list of any number of test modules, classes and test
|
All options must come before [tests]. [tests] can be a list of any number of
|
||||||
methods.
|
test modules, classes and test methods.
|
||||||
|
|
||||||
Alternative Usage: %(progName)s discover [options]
|
Alternative Usage: %(progName)s discover [options]
|
||||||
|
|
||||||
@@ -87,6 +100,9 @@ Options:
|
|||||||
-p pattern Pattern to match test files ('test*.py' default)
|
-p pattern Pattern to match test files ('test*.py' default)
|
||||||
-t directory Top level directory of project (default to
|
-t directory Top level directory of project (default to
|
||||||
start directory)
|
start directory)
|
||||||
|
-l, --list List tests rather than executing them.
|
||||||
|
--load-list Specifies a file containing test ids, only tests matching
|
||||||
|
those ids are executed.
|
||||||
|
|
||||||
For test discovery all test modules must be importable from the top
|
For test discovery all test modules must be importable from the top
|
||||||
level directory of the project.
|
level directory of the project.
|
||||||
@@ -102,11 +118,13 @@ class TestProgram(object):
|
|||||||
# defaults for testing
|
# defaults for testing
|
||||||
failfast = catchbreak = buffer = progName = None
|
failfast = catchbreak = buffer = progName = None
|
||||||
|
|
||||||
def __init__(self, module='__main__', defaultTest=None, argv=None,
|
def __init__(self, module=__name__, defaultTest=None, argv=None,
|
||||||
testRunner=None, testLoader=defaultTestLoader,
|
testRunner=None, testLoader=defaultTestLoader,
|
||||||
exit=True, verbosity=1, failfast=None, catchbreak=None,
|
exit=True, verbosity=1, failfast=None, catchbreak=None,
|
||||||
buffer=None):
|
buffer=None, stdout=None):
|
||||||
if istext(module):
|
if module == __name__:
|
||||||
|
self.module = None
|
||||||
|
elif istext(module):
|
||||||
self.module = __import__(module)
|
self.module = __import__(module)
|
||||||
for part in module.split('.')[1:]:
|
for part in module.split('.')[1:]:
|
||||||
self.module = getattr(self.module, part)
|
self.module = getattr(self.module, part)
|
||||||
@@ -114,6 +132,8 @@ class TestProgram(object):
|
|||||||
self.module = module
|
self.module = module
|
||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv
|
argv = sys.argv
|
||||||
|
if stdout is None:
|
||||||
|
stdout = sys.stdout
|
||||||
|
|
||||||
self.exit = exit
|
self.exit = exit
|
||||||
self.failfast = failfast
|
self.failfast = failfast
|
||||||
@@ -121,6 +141,8 @@ class TestProgram(object):
|
|||||||
self.verbosity = verbosity
|
self.verbosity = verbosity
|
||||||
self.buffer = buffer
|
self.buffer = buffer
|
||||||
self.defaultTest = defaultTest
|
self.defaultTest = defaultTest
|
||||||
|
self.listtests = False
|
||||||
|
self.load_list = None
|
||||||
self.testRunner = testRunner
|
self.testRunner = testRunner
|
||||||
self.testLoader = testLoader
|
self.testLoader = testLoader
|
||||||
progName = argv[0]
|
progName = argv[0]
|
||||||
@@ -131,7 +153,27 @@ class TestProgram(object):
|
|||||||
progName = os.path.basename(argv[0])
|
progName = os.path.basename(argv[0])
|
||||||
self.progName = progName
|
self.progName = progName
|
||||||
self.parseArgs(argv)
|
self.parseArgs(argv)
|
||||||
|
if self.load_list:
|
||||||
|
# TODO: preserve existing suites (like testresources does in
|
||||||
|
# OptimisingTestSuite.add, but with a standard protocol).
|
||||||
|
# This is needed because the load_tests hook allows arbitrary
|
||||||
|
# suites, even if that is rarely used.
|
||||||
|
source = file(self.load_list, 'rb')
|
||||||
|
try:
|
||||||
|
lines = source.readlines()
|
||||||
|
finally:
|
||||||
|
source.close()
|
||||||
|
test_ids = set(line.strip() for line in lines)
|
||||||
|
filtered = unittest.TestSuite()
|
||||||
|
for test in iterate_tests(self.test):
|
||||||
|
if test.id() in test_ids:
|
||||||
|
filtered.addTest(test)
|
||||||
|
self.test = filtered
|
||||||
|
if not self.listtests:
|
||||||
self.runTests()
|
self.runTests()
|
||||||
|
else:
|
||||||
|
for test in iterate_tests(self.test):
|
||||||
|
stdout.write('%s\n' % test.id())
|
||||||
|
|
||||||
def usageExit(self, msg=None):
|
def usageExit(self, msg=None):
|
||||||
if msg:
|
if msg:
|
||||||
@@ -153,9 +195,10 @@ class TestProgram(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
import getopt
|
import getopt
|
||||||
long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer']
|
long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer',
|
||||||
|
'list', 'load-list=']
|
||||||
try:
|
try:
|
||||||
options, args = getopt.getopt(argv[1:], 'hHvqfcb', long_opts)
|
options, args = getopt.getopt(argv[1:], 'hHvqfcbl', long_opts)
|
||||||
for opt, value in options:
|
for opt, value in options:
|
||||||
if opt in ('-h','-H','--help'):
|
if opt in ('-h','-H','--help'):
|
||||||
self.usageExit()
|
self.usageExit()
|
||||||
@@ -175,14 +218,15 @@ class TestProgram(object):
|
|||||||
if self.buffer is None:
|
if self.buffer is None:
|
||||||
self.buffer = True
|
self.buffer = True
|
||||||
# Should this raise an exception if -b is not valid?
|
# Should this raise an exception if -b is not valid?
|
||||||
|
if opt in ('-l', '--list'):
|
||||||
|
self.listtests = True
|
||||||
|
if opt == '--load-list':
|
||||||
|
self.load_list = value
|
||||||
if len(args) == 0 and self.defaultTest is None:
|
if len(args) == 0 and self.defaultTest is None:
|
||||||
# createTests will load tests from self.module
|
# createTests will load tests from self.module
|
||||||
self.testNames = None
|
self.testNames = None
|
||||||
elif len(args) > 0:
|
elif len(args) > 0:
|
||||||
self.testNames = args
|
self.testNames = args
|
||||||
if __name__ == '__main__':
|
|
||||||
# to support python -m unittest ...
|
|
||||||
self.module = None
|
|
||||||
else:
|
else:
|
||||||
self.testNames = (self.defaultTest,)
|
self.testNames = (self.defaultTest,)
|
||||||
self.createTests()
|
self.createTests()
|
||||||
@@ -225,6 +269,10 @@ class TestProgram(object):
|
|||||||
help="Pattern to match tests ('test*.py' default)")
|
help="Pattern to match tests ('test*.py' default)")
|
||||||
parser.add_option('-t', '--top-level-directory', dest='top', default=None,
|
parser.add_option('-t', '--top-level-directory', dest='top', default=None,
|
||||||
help='Top level directory of project (defaults to start directory)')
|
help='Top level directory of project (defaults to start directory)')
|
||||||
|
parser.add_option('-l', '--list', dest='listtests', default=False,
|
||||||
|
help='List tests rather than running them.')
|
||||||
|
parser.add_option('--load-list', dest='load_list', default=None,
|
||||||
|
help='Specify a filename containing the test ids to use.')
|
||||||
|
|
||||||
options, args = parser.parse_args(argv)
|
options, args = parser.parse_args(argv)
|
||||||
if len(args) > 3:
|
if len(args) > 3:
|
||||||
@@ -241,6 +289,8 @@ class TestProgram(object):
|
|||||||
self.catchbreak = options.catchbreak
|
self.catchbreak = options.catchbreak
|
||||||
if self.buffer is None:
|
if self.buffer is None:
|
||||||
self.buffer = options.buffer
|
self.buffer = options.buffer
|
||||||
|
self.listtests = options.listtests
|
||||||
|
self.load_list = options.load_list
|
||||||
|
|
||||||
if options.verbose:
|
if options.verbose:
|
||||||
self.verbosity = 2
|
self.verbosity = 2
|
||||||
@@ -274,7 +324,9 @@ class TestProgram(object):
|
|||||||
sys.exit(not self.result.wasSuccessful())
|
sys.exit(not self.result.wasSuccessful())
|
||||||
################
|
################
|
||||||
|
|
||||||
|
def main(argv, stdout):
|
||||||
|
runner = TestToolsTestRunner(stdout)
|
||||||
|
program = TestProgram(argv=argv, testRunner=runner, stdout=stdout)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
runner = TestToolsTestRunner()
|
main(sys.argv, sys.stdout)
|
||||||
program = TestProgram(argv=sys.argv, testRunner=runner)
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Copyright (c) 2009 Jonathan M. Lange. See LICENSE for details.
|
# Copyright (c) 2009-2010 Jonathan M. Lange. See LICENSE for details.
|
||||||
|
|
||||||
"""Individual test case execution."""
|
"""Individual test case execution."""
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
'MultipleExceptions',
|
||||||
'RunTest',
|
'RunTest',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -11,6 +12,13 @@ import sys
|
|||||||
from testtools.testresult import ExtendedToOriginalDecorator
|
from testtools.testresult import ExtendedToOriginalDecorator
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleExceptions(Exception):
|
||||||
|
"""Represents many exceptions raised from some operation.
|
||||||
|
|
||||||
|
:ivar args: The sys.exc_info() tuples for each exception.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class RunTest(object):
|
class RunTest(object):
|
||||||
"""An object to run a test.
|
"""An object to run a test.
|
||||||
|
|
||||||
@@ -24,15 +32,15 @@ class RunTest(object):
|
|||||||
|
|
||||||
:ivar case: The test case that is to be run.
|
:ivar case: The test case that is to be run.
|
||||||
:ivar result: The result object a case is reporting to.
|
:ivar result: The result object a case is reporting to.
|
||||||
:ivar handlers: A list of (ExceptionClass->handler code) for exceptions
|
:ivar handlers: A list of (ExceptionClass, handler_function) for
|
||||||
that should be caught if raised from the user code. Exceptions that
|
exceptions that should be caught if raised from the user
|
||||||
are caught are checked against this list in first to last order.
|
code. Exceptions that are caught are checked against this list in
|
||||||
There is a catchall of Exception at the end of the list, so to add
|
first to last order. There is a catch-all of `Exception` at the end
|
||||||
a new exception to the list, insert it at the front (which ensures that
|
of the list, so to add a new exception to the list, insert it at the
|
||||||
it will be checked before any existing base classes in the list. If you
|
front (which ensures that it will be checked before any existing base
|
||||||
add multiple exceptions some of which are subclasses of each other, add
|
classes in the list. If you add multiple exceptions some of which are
|
||||||
the most specific exceptions last (so they come before their parent
|
subclasses of each other, add the most specific exceptions last (so
|
||||||
classes in the list).
|
they come before their parent classes in the list).
|
||||||
:ivar exception_caught: An object returned when _run_user catches an
|
:ivar exception_caught: An object returned when _run_user catches an
|
||||||
exception.
|
exception.
|
||||||
:ivar _exceptions: A list of caught exceptions, used to do the single
|
:ivar _exceptions: A list of caught exceptions, used to do the single
|
||||||
@@ -107,9 +115,7 @@ class RunTest(object):
|
|||||||
if self.exception_caught == self._run_user(self.case._run_setup,
|
if self.exception_caught == self._run_user(self.case._run_setup,
|
||||||
self.result):
|
self.result):
|
||||||
# Don't run the test method if we failed getting here.
|
# Don't run the test method if we failed getting here.
|
||||||
e = self.case._runCleanups(self.result)
|
self._run_cleanups(self.result)
|
||||||
if e is not None:
|
|
||||||
self._exceptions.append(e)
|
|
||||||
return
|
return
|
||||||
# Run everything from here on in. If any of the methods raise an
|
# Run everything from here on in. If any of the methods raise an
|
||||||
# exception we'll have failed.
|
# exception we'll have failed.
|
||||||
@@ -125,22 +131,42 @@ class RunTest(object):
|
|||||||
failed = True
|
failed = True
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
e = self._run_user(self.case._runCleanups, self.result)
|
if self.exception_caught == self._run_user(
|
||||||
if e is not None:
|
self._run_cleanups, self.result):
|
||||||
self._exceptions.append(e)
|
|
||||||
failed = True
|
failed = True
|
||||||
finally:
|
finally:
|
||||||
if not failed:
|
if not failed:
|
||||||
self.result.addSuccess(self.case,
|
self.result.addSuccess(self.case,
|
||||||
details=self.case.getDetails())
|
details=self.case.getDetails())
|
||||||
|
|
||||||
def _run_user(self, fn, *args):
|
def _run_cleanups(self, result):
|
||||||
|
"""Run the cleanups that have been added with addCleanup.
|
||||||
|
|
||||||
|
See the docstring for addCleanup for more information.
|
||||||
|
|
||||||
|
:return: None if all cleanups ran without error,
|
||||||
|
`self.exception_caught` if there was an error.
|
||||||
|
"""
|
||||||
|
failing = False
|
||||||
|
while self.case._cleanups:
|
||||||
|
function, arguments, keywordArguments = self.case._cleanups.pop()
|
||||||
|
got_exception = self._run_user(
|
||||||
|
function, *arguments, **keywordArguments)
|
||||||
|
if got_exception == self.exception_caught:
|
||||||
|
failing = True
|
||||||
|
if failing:
|
||||||
|
return self.exception_caught
|
||||||
|
|
||||||
|
def _run_user(self, fn, *args, **kwargs):
|
||||||
"""Run a user supplied function.
|
"""Run a user supplied function.
|
||||||
|
|
||||||
Exceptions are processed by self.handlers.
|
Exceptions are processed by `_got_user_exception`.
|
||||||
|
|
||||||
|
:return: Either whatever 'fn' returns or `self.exception_caught` if
|
||||||
|
'fn' raised an exception.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return fn(*args)
|
return fn(*args, **kwargs)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
@@ -149,10 +175,19 @@ class RunTest(object):
|
|||||||
def _got_user_exception(self, exc_info, tb_label='traceback'):
|
def _got_user_exception(self, exc_info, tb_label='traceback'):
|
||||||
"""Called when user code raises an exception.
|
"""Called when user code raises an exception.
|
||||||
|
|
||||||
|
If 'exc_info' is a `MultipleExceptions`, then we recurse into it
|
||||||
|
unpacking the errors that it's made up from.
|
||||||
|
|
||||||
:param exc_info: A sys.exc_info() tuple for the user error.
|
:param exc_info: A sys.exc_info() tuple for the user error.
|
||||||
:param tb_label: An optional string label for the error. If
|
:param tb_label: An optional string label for the error. If
|
||||||
not specified, will default to 'traceback'.
|
not specified, will default to 'traceback'.
|
||||||
|
:return: `exception_caught` if we catch one of the exceptions that
|
||||||
|
have handlers in `self.handlers`, otherwise raise the error.
|
||||||
"""
|
"""
|
||||||
|
if exc_info[0] is MultipleExceptions:
|
||||||
|
for sub_exc_info in exc_info[1].args:
|
||||||
|
self._got_user_exception(sub_exc_info, tb_label)
|
||||||
|
return self.exception_caught
|
||||||
try:
|
try:
|
||||||
e = exc_info[1]
|
e = exc_info[1]
|
||||||
self.case.onException(exc_info, tb_label=tb_label)
|
self.case.onException(exc_info, tb_label=tb_label)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'clone_test_with_new_id',
|
'clone_test_with_new_id',
|
||||||
'MultipleExceptions',
|
|
||||||
'run_test_with',
|
'run_test_with',
|
||||||
'skip',
|
'skip',
|
||||||
'skipIf',
|
'skipIf',
|
||||||
@@ -92,13 +91,6 @@ def run_test_with(test_runner, **kwargs):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
class MultipleExceptions(Exception):
|
|
||||||
"""Represents many exceptions raised from some operation.
|
|
||||||
|
|
||||||
:ivar args: The sys.exc_info() tuples for each exception.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class TestCase(unittest.TestCase):
|
class TestCase(unittest.TestCase):
|
||||||
"""Extensions to the basic TestCase.
|
"""Extensions to the basic TestCase.
|
||||||
|
|
||||||
@@ -224,35 +216,6 @@ class TestCase(unittest.TestCase):
|
|||||||
className = ', '.join(klass.__name__ for klass in classOrIterable)
|
className = ', '.join(klass.__name__ for klass in classOrIterable)
|
||||||
return className
|
return className
|
||||||
|
|
||||||
def _runCleanups(self, result):
|
|
||||||
"""Run the cleanups that have been added with addCleanup.
|
|
||||||
|
|
||||||
See the docstring for addCleanup for more information.
|
|
||||||
|
|
||||||
:return: None if all cleanups ran without error, the most recently
|
|
||||||
raised exception from the cleanups otherwise.
|
|
||||||
"""
|
|
||||||
last_exception = None
|
|
||||||
while self._cleanups:
|
|
||||||
function, arguments, keywordArguments = self._cleanups.pop()
|
|
||||||
try:
|
|
||||||
function(*arguments, **keywordArguments)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
exceptions = [sys.exc_info()]
|
|
||||||
while exceptions:
|
|
||||||
try:
|
|
||||||
exc_info = exceptions.pop()
|
|
||||||
if exc_info[0] is MultipleExceptions:
|
|
||||||
exceptions.extend(exc_info[1].args)
|
|
||||||
continue
|
|
||||||
self._report_traceback(exc_info)
|
|
||||||
last_exception = exc_info[1]
|
|
||||||
finally:
|
|
||||||
del exc_info
|
|
||||||
return last_exception
|
|
||||||
|
|
||||||
def addCleanup(self, function, *arguments, **keywordArguments):
|
def addCleanup(self, function, *arguments, **keywordArguments):
|
||||||
"""Add a cleanup function to be called after tearDown.
|
"""Add a cleanup function to be called after tearDown.
|
||||||
|
|
||||||
@@ -337,10 +300,11 @@ class TestCase(unittest.TestCase):
|
|||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
needle not in haystack, '%r in %r' % (needle, haystack))
|
needle not in haystack, '%r in %r' % (needle, haystack))
|
||||||
|
|
||||||
def assertIsInstance(self, obj, klass):
|
def assertIsInstance(self, obj, klass, msg=None):
|
||||||
self.assertTrue(
|
if msg is None:
|
||||||
isinstance(obj, klass),
|
msg = '%r is not an instance of %s' % (
|
||||||
'%r is not an instance of %s' % (obj, self._formatTypes(klass)))
|
obj, self._formatTypes(klass))
|
||||||
|
self.assertTrue(isinstance(obj, klass), msg)
|
||||||
|
|
||||||
def assertRaises(self, excClass, callableObj, *args, **kwargs):
|
def assertRaises(self, excClass, callableObj, *args, **kwargs):
|
||||||
"""Fail unless an exception of class excClass is thrown
|
"""Fail unless an exception of class excClass is thrown
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2009 Jonathan M. Lange. See LICENSE for details.
|
# Copyright (c) 2009-2010 Jonathan M. Lange. See LICENSE for details.
|
||||||
|
|
||||||
"""Doubles of test result objects, useful for testing unittest code."""
|
"""Doubles of test result objects, useful for testing unittest code."""
|
||||||
|
|
||||||
@@ -15,15 +15,18 @@ class LoggingBase(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._events = []
|
self._events = []
|
||||||
self.shouldStop = False
|
self.shouldStop = False
|
||||||
|
self._was_successful = True
|
||||||
|
|
||||||
|
|
||||||
class Python26TestResult(LoggingBase):
|
class Python26TestResult(LoggingBase):
|
||||||
"""A precisely python 2.6 like test result, that logs."""
|
"""A precisely python 2.6 like test result, that logs."""
|
||||||
|
|
||||||
def addError(self, test, err):
|
def addError(self, test, err):
|
||||||
|
self._was_successful = False
|
||||||
self._events.append(('addError', test, err))
|
self._events.append(('addError', test, err))
|
||||||
|
|
||||||
def addFailure(self, test, err):
|
def addFailure(self, test, err):
|
||||||
|
self._was_successful = False
|
||||||
self._events.append(('addFailure', test, err))
|
self._events.append(('addFailure', test, err))
|
||||||
|
|
||||||
def addSuccess(self, test):
|
def addSuccess(self, test):
|
||||||
@@ -38,6 +41,9 @@ class Python26TestResult(LoggingBase):
|
|||||||
def stopTest(self, test):
|
def stopTest(self, test):
|
||||||
self._events.append(('stopTest', test))
|
self._events.append(('stopTest', test))
|
||||||
|
|
||||||
|
def wasSuccessful(self):
|
||||||
|
return self._was_successful
|
||||||
|
|
||||||
|
|
||||||
class Python27TestResult(Python26TestResult):
|
class Python27TestResult(Python26TestResult):
|
||||||
"""A precisely python 2.7 like test result, that logs."""
|
"""A precisely python 2.7 like test result, that logs."""
|
||||||
@@ -62,9 +68,11 @@ class ExtendedTestResult(Python27TestResult):
|
|||||||
"""A test result like the proposed extended unittest result API."""
|
"""A test result like the proposed extended unittest result API."""
|
||||||
|
|
||||||
def addError(self, test, err=None, details=None):
|
def addError(self, test, err=None, details=None):
|
||||||
|
self._was_successful = False
|
||||||
self._events.append(('addError', test, err or details))
|
self._events.append(('addError', test, err or details))
|
||||||
|
|
||||||
def addFailure(self, test, err=None, details=None):
|
def addFailure(self, test, err=None, details=None):
|
||||||
|
self._was_successful = False
|
||||||
self._events.append(('addFailure', test, err or details))
|
self._events.append(('addFailure', test, err or details))
|
||||||
|
|
||||||
def addExpectedFailure(self, test, err=None, details=None):
|
def addExpectedFailure(self, test, err=None, details=None):
|
||||||
@@ -80,6 +88,7 @@ class ExtendedTestResult(Python27TestResult):
|
|||||||
self._events.append(('addSuccess', test))
|
self._events.append(('addSuccess', test))
|
||||||
|
|
||||||
def addUnexpectedSuccess(self, test, details=None):
|
def addUnexpectedSuccess(self, test, details=None):
|
||||||
|
self._was_successful = False
|
||||||
if details is not None:
|
if details is not None:
|
||||||
self._events.append(('addUnexpectedSuccess', test, details))
|
self._events.append(('addUnexpectedSuccess', test, details))
|
||||||
else:
|
else:
|
||||||
@@ -88,8 +97,15 @@ class ExtendedTestResult(Python27TestResult):
|
|||||||
def progress(self, offset, whence):
|
def progress(self, offset, whence):
|
||||||
self._events.append(('progress', offset, whence))
|
self._events.append(('progress', offset, whence))
|
||||||
|
|
||||||
|
def startTestRun(self):
|
||||||
|
super(ExtendedTestResult, self).startTestRun()
|
||||||
|
self._was_successful = True
|
||||||
|
|
||||||
def tags(self, new_tags, gone_tags):
|
def tags(self, new_tags, gone_tags):
|
||||||
self._events.append(('tags', new_tags, gone_tags))
|
self._events.append(('tags', new_tags, gone_tags))
|
||||||
|
|
||||||
def time(self, time):
|
def time(self, time):
|
||||||
self._events.append(('time', time))
|
self._events.append(('time', time))
|
||||||
|
|
||||||
|
def wasSuccessful(self):
|
||||||
|
return self._was_successful
|
||||||
|
|||||||
@@ -14,7 +14,26 @@ import datetime
|
|||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from testtools.compat import _format_exc_info, str_is_unicode, _u
|
from testtools.compat import all, _format_exc_info, str_is_unicode, _u
|
||||||
|
|
||||||
|
# From http://docs.python.org/library/datetime.html
|
||||||
|
_ZERO = datetime.timedelta(0)
|
||||||
|
|
||||||
|
# A UTC class.
|
||||||
|
|
||||||
|
class UTC(datetime.tzinfo):
|
||||||
|
"""UTC"""
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return _ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
return "UTC"
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
return _ZERO
|
||||||
|
|
||||||
|
utc = UTC()
|
||||||
|
|
||||||
|
|
||||||
class TestResult(unittest.TestResult):
|
class TestResult(unittest.TestResult):
|
||||||
@@ -35,13 +54,11 @@ class TestResult(unittest.TestResult):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(TestResult, self).__init__()
|
# startTestRun resets all attributes, and older clients don't know to
|
||||||
self.skip_reasons = {}
|
# call startTestRun, so it is called once here.
|
||||||
self.__now = None
|
# Because subclasses may reasonably not expect this, we call the
|
||||||
# -- Start: As per python 2.7 --
|
# specific version we want to run.
|
||||||
self.expectedFailures = []
|
TestResult.startTestRun(self)
|
||||||
self.unexpectedSuccesses = []
|
|
||||||
# -- End: As per python 2.7 --
|
|
||||||
|
|
||||||
def addExpectedFailure(self, test, err=None, details=None):
|
def addExpectedFailure(self, test, err=None, details=None):
|
||||||
"""Called when a test has failed in an expected manner.
|
"""Called when a test has failed in an expected manner.
|
||||||
@@ -108,6 +125,18 @@ class TestResult(unittest.TestResult):
|
|||||||
"""Called when a test was expected to fail, but succeed."""
|
"""Called when a test was expected to fail, but succeed."""
|
||||||
self.unexpectedSuccesses.append(test)
|
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:
|
if str_is_unicode:
|
||||||
# Python 3 and IronPython strings are unicode, use parent class method
|
# Python 3 and IronPython strings are unicode, use parent class method
|
||||||
_exc_info_to_unicode = unittest.TestResult._exc_info_to_string
|
_exc_info_to_unicode = unittest.TestResult._exc_info_to_string
|
||||||
@@ -139,15 +168,23 @@ class TestResult(unittest.TestResult):
|
|||||||
time() method.
|
time() method.
|
||||||
"""
|
"""
|
||||||
if self.__now is None:
|
if self.__now is None:
|
||||||
return datetime.datetime.now()
|
return datetime.datetime.now(utc)
|
||||||
else:
|
else:
|
||||||
return self.__now
|
return self.__now
|
||||||
|
|
||||||
def startTestRun(self):
|
def startTestRun(self):
|
||||||
"""Called before a test run starts.
|
"""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):
|
def stopTestRun(self):
|
||||||
"""Called after a test run completes
|
"""Called after a test run completes
|
||||||
@@ -182,7 +219,7 @@ class MultiTestResult(TestResult):
|
|||||||
|
|
||||||
def __init__(self, *results):
|
def __init__(self, *results):
|
||||||
TestResult.__init__(self)
|
TestResult.__init__(self)
|
||||||
self._results = map(ExtendedToOriginalDecorator, results)
|
self._results = list(map(ExtendedToOriginalDecorator, results))
|
||||||
|
|
||||||
def _dispatch(self, message, *args, **kwargs):
|
def _dispatch(self, message, *args, **kwargs):
|
||||||
return tuple(
|
return tuple(
|
||||||
@@ -220,9 +257,19 @@ class MultiTestResult(TestResult):
|
|||||||
def stopTestRun(self):
|
def stopTestRun(self):
|
||||||
return self._dispatch('stopTestRun')
|
return self._dispatch('stopTestRun')
|
||||||
|
|
||||||
|
def time(self, a_datetime):
|
||||||
|
return self._dispatch('time', a_datetime)
|
||||||
|
|
||||||
def done(self):
|
def done(self):
|
||||||
return self._dispatch('done')
|
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):
|
class TextTestResult(TestResult):
|
||||||
"""A TestResult which outputs activity to a text stream."""
|
"""A TestResult which outputs activity to a text stream."""
|
||||||
@@ -258,6 +305,10 @@ class TextTestResult(TestResult):
|
|||||||
stop = self._now()
|
stop = self._now()
|
||||||
self._show_list('ERROR', self.errors)
|
self._show_list('ERROR', self.errors)
|
||||||
self._show_list('FAIL', self.failures)
|
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.stream.write("Ran %d test%s in %.3fs\n\n" %
|
||||||
(self.testsRun, plural,
|
(self.testsRun, plural,
|
||||||
self._delta_to_float(stop - self.__start)))
|
self._delta_to_float(stop - self.__start)))
|
||||||
@@ -267,7 +318,8 @@ class TextTestResult(TestResult):
|
|||||||
self.stream.write("FAILED (")
|
self.stream.write("FAILED (")
|
||||||
details = []
|
details = []
|
||||||
details.append("failures=%d" % (
|
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(", ".join(details))
|
||||||
self.stream.write(")\n")
|
self.stream.write(")\n")
|
||||||
super(TextTestResult, self).stopTestRun()
|
super(TextTestResult, self).stopTestRun()
|
||||||
@@ -363,6 +415,9 @@ class ThreadsafeForwardingResult(TestResult):
|
|||||||
self._test_start = self._now()
|
self._test_start = self._now()
|
||||||
super(ThreadsafeForwardingResult, self).startTest(test)
|
super(ThreadsafeForwardingResult, self).startTest(test)
|
||||||
|
|
||||||
|
def wasSuccessful(self):
|
||||||
|
return self.result.wasSuccessful()
|
||||||
|
|
||||||
|
|
||||||
class ExtendedToOriginalDecorator(object):
|
class ExtendedToOriginalDecorator(object):
|
||||||
"""Permit new TestResult API code to degrade gracefully with old results.
|
"""Permit new TestResult API code to degrade gracefully with old results.
|
||||||
|
|||||||
@@ -10,47 +10,32 @@ def test_suite():
|
|||||||
test_compat,
|
test_compat,
|
||||||
test_content,
|
test_content,
|
||||||
test_content_type,
|
test_content_type,
|
||||||
|
test_deferredruntest,
|
||||||
|
test_fixturesupport,
|
||||||
test_helpers,
|
test_helpers,
|
||||||
test_matchers,
|
test_matchers,
|
||||||
test_monkey,
|
test_monkey,
|
||||||
|
test_run,
|
||||||
test_runtest,
|
test_runtest,
|
||||||
|
test_spinner,
|
||||||
test_testtools,
|
test_testtools,
|
||||||
test_testresult,
|
test_testresult,
|
||||||
test_testsuite,
|
test_testsuite,
|
||||||
)
|
)
|
||||||
suites = []
|
|
||||||
modules = [
|
modules = [
|
||||||
test_compat,
|
test_compat,
|
||||||
test_content,
|
test_content,
|
||||||
test_content_type,
|
test_content_type,
|
||||||
|
test_deferredruntest,
|
||||||
|
test_fixturesupport,
|
||||||
test_helpers,
|
test_helpers,
|
||||||
test_matchers,
|
test_matchers,
|
||||||
test_monkey,
|
test_monkey,
|
||||||
test_runtest,
|
test_run,
|
||||||
|
test_spinner,
|
||||||
test_testresult,
|
test_testresult,
|
||||||
test_testsuite,
|
test_testsuite,
|
||||||
test_testtools,
|
test_testtools,
|
||||||
]
|
]
|
||||||
try:
|
suites = map(lambda x:x.test_suite(), modules)
|
||||||
# Tests that rely on Twisted.
|
|
||||||
from testtools.tests import (
|
|
||||||
test_deferredruntest,
|
|
||||||
test_spinner,
|
|
||||||
)
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
modules.extend([test_deferredruntest, test_spinner])
|
|
||||||
try:
|
|
||||||
# Tests that rely on 'fixtures'.
|
|
||||||
from testtools.tests import (
|
|
||||||
test_fixturesupport,
|
|
||||||
)
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
modules.extend([test_fixturesupport])
|
|
||||||
|
|
||||||
for module in modules:
|
|
||||||
suites.append(getattr(module, 'test_suite')())
|
|
||||||
return unittest.TestSuite(suites)
|
return unittest.TestSuite(suites)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from testtools.compat import (
|
|||||||
)
|
)
|
||||||
from testtools.matchers import (
|
from testtools.matchers import (
|
||||||
MatchesException,
|
MatchesException,
|
||||||
|
Not,
|
||||||
Raises,
|
Raises,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -246,7 +247,7 @@ class TestUnicodeOutputStream(testtools.TestCase):
|
|||||||
if newio:
|
if newio:
|
||||||
self.expectFailure("Python 3 StringIO expects text not bytes",
|
self.expectFailure("Python 3 StringIO expects text not bytes",
|
||||||
self.assertThat, lambda: soutwrapper.write(self.uni),
|
self.assertThat, lambda: soutwrapper.write(self.uni),
|
||||||
Raises(MatchesException(TypeError)))
|
Not(Raises(MatchesException(TypeError))))
|
||||||
soutwrapper.write(self.uni)
|
soutwrapper.write(self.uni)
|
||||||
self.assertEqual("pa???n", sout.getvalue())
|
self.assertEqual("pa???n", sout.getvalue())
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,7 @@ from testtools import (
|
|||||||
from testtools.content import (
|
from testtools.content import (
|
||||||
text_content,
|
text_content,
|
||||||
)
|
)
|
||||||
from testtools.deferredruntest import (
|
from testtools.helpers import try_import
|
||||||
assert_fails_with,
|
|
||||||
AsynchronousDeferredRunTest,
|
|
||||||
flush_logged_errors,
|
|
||||||
SynchronousDeferredRunTest,
|
|
||||||
)
|
|
||||||
from testtools.tests.helpers import ExtendedTestResult
|
from testtools.tests.helpers import ExtendedTestResult
|
||||||
from testtools.matchers import (
|
from testtools.matchers import (
|
||||||
Equals,
|
Equals,
|
||||||
@@ -26,9 +21,20 @@ from testtools.matchers import (
|
|||||||
Raises,
|
Raises,
|
||||||
)
|
)
|
||||||
from testtools.runtest import RunTest
|
from testtools.runtest import RunTest
|
||||||
|
from testtools.tests.test_spinner import NeedsTwistedTestCase
|
||||||
|
|
||||||
from twisted.internet import defer
|
assert_fails_with = try_import('testtools.deferredruntest.assert_fails_with')
|
||||||
from twisted.python import failure, log
|
AsynchronousDeferredRunTest = try_import(
|
||||||
|
'testtools.deferredruntest.AsynchronousDeferredRunTest')
|
||||||
|
flush_logged_errors = try_import(
|
||||||
|
'testtools.deferredruntest.flush_logged_errors')
|
||||||
|
SynchronousDeferredRunTest = try_import(
|
||||||
|
'testtools.deferredruntest.SynchronousDeferredRunTest')
|
||||||
|
|
||||||
|
defer = try_import('twisted.internet.defer')
|
||||||
|
failure = try_import('twisted.python.failure')
|
||||||
|
log = try_import('twisted.python.log')
|
||||||
|
DelayedCall = try_import('twisted.internet.base.DelayedCall')
|
||||||
|
|
||||||
|
|
||||||
class X(object):
|
class X(object):
|
||||||
@@ -77,7 +83,7 @@ class X(object):
|
|||||||
self.calls.append('test')
|
self.calls.append('test')
|
||||||
self.addCleanup(lambda: 1/0)
|
self.addCleanup(lambda: 1/0)
|
||||||
|
|
||||||
class TestIntegration(TestCase):
|
class TestIntegration(NeedsTwistedTestCase):
|
||||||
|
|
||||||
def assertResultsMatch(self, test, result):
|
def assertResultsMatch(self, test, result):
|
||||||
events = list(result._events)
|
events = list(result._events)
|
||||||
@@ -104,9 +110,9 @@ def make_integration_tests():
|
|||||||
from unittest import TestSuite
|
from unittest import TestSuite
|
||||||
from testtools import clone_test_with_new_id
|
from testtools import clone_test_with_new_id
|
||||||
runners = [
|
runners = [
|
||||||
RunTest,
|
('RunTest', RunTest),
|
||||||
SynchronousDeferredRunTest,
|
('SynchronousDeferredRunTest', SynchronousDeferredRunTest),
|
||||||
AsynchronousDeferredRunTest,
|
('AsynchronousDeferredRunTest', AsynchronousDeferredRunTest),
|
||||||
]
|
]
|
||||||
|
|
||||||
tests = [
|
tests = [
|
||||||
@@ -118,12 +124,12 @@ def make_integration_tests():
|
|||||||
]
|
]
|
||||||
base_test = X.TestIntegration('test_runner')
|
base_test = X.TestIntegration('test_runner')
|
||||||
integration_tests = []
|
integration_tests = []
|
||||||
for runner in runners:
|
for runner_name, runner in runners:
|
||||||
for test in tests:
|
for test in tests:
|
||||||
new_test = clone_test_with_new_id(
|
new_test = clone_test_with_new_id(
|
||||||
base_test, '%s(%s, %s)' % (
|
base_test, '%s(%s, %s)' % (
|
||||||
base_test.id(),
|
base_test.id(),
|
||||||
runner.__name__,
|
runner_name,
|
||||||
test.__name__))
|
test.__name__))
|
||||||
new_test.test_factory = test
|
new_test.test_factory = test
|
||||||
new_test.runner = runner
|
new_test.runner = runner
|
||||||
@@ -131,7 +137,7 @@ def make_integration_tests():
|
|||||||
return TestSuite(integration_tests)
|
return TestSuite(integration_tests)
|
||||||
|
|
||||||
|
|
||||||
class TestSynchronousDeferredRunTest(TestCase):
|
class TestSynchronousDeferredRunTest(NeedsTwistedTestCase):
|
||||||
|
|
||||||
def make_result(self):
|
def make_result(self):
|
||||||
return ExtendedTestResult()
|
return ExtendedTestResult()
|
||||||
@@ -185,7 +191,7 @@ class TestSynchronousDeferredRunTest(TestCase):
|
|||||||
('stopTest', test)]))
|
('stopTest', test)]))
|
||||||
|
|
||||||
|
|
||||||
class TestAsynchronousDeferredRunTest(TestCase):
|
class TestAsynchronousDeferredRunTest(NeedsTwistedTestCase):
|
||||||
|
|
||||||
def make_reactor(self):
|
def make_reactor(self):
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
@@ -486,6 +492,17 @@ class TestAsynchronousDeferredRunTest(TestCase):
|
|||||||
self.assertIs(self, runner.case)
|
self.assertIs(self, runner.case)
|
||||||
self.assertEqual([handler], runner.handlers)
|
self.assertEqual([handler], runner.handlers)
|
||||||
|
|
||||||
|
def test_convenient_construction_default_debugging(self):
|
||||||
|
# As a convenience method, AsynchronousDeferredRunTest has a
|
||||||
|
# classmethod that returns an AsynchronousDeferredRunTest
|
||||||
|
# factory. This factory has the same API as the RunTest constructor.
|
||||||
|
handler = object()
|
||||||
|
factory = AsynchronousDeferredRunTest.make_factory(debug=True)
|
||||||
|
runner = factory(self, [handler])
|
||||||
|
self.assertIs(self, runner.case)
|
||||||
|
self.assertEqual([handler], runner.handlers)
|
||||||
|
self.assertEqual(True, runner._debug)
|
||||||
|
|
||||||
def test_deferred_error(self):
|
def test_deferred_error(self):
|
||||||
class SomeTest(TestCase):
|
class SomeTest(TestCase):
|
||||||
def test_something(self):
|
def test_something(self):
|
||||||
@@ -601,10 +618,40 @@ class TestAsynchronousDeferredRunTest(TestCase):
|
|||||||
error = result._events[1][2]
|
error = result._events[1][2]
|
||||||
self.assertThat(error, KeysEqual('traceback', 'twisted-log'))
|
self.assertThat(error, KeysEqual('traceback', 'twisted-log'))
|
||||||
|
|
||||||
|
def test_debugging_unchanged_during_test_by_default(self):
|
||||||
|
debugging = [(defer.Deferred.debug, DelayedCall.debug)]
|
||||||
|
class SomeCase(TestCase):
|
||||||
|
def test_debugging_enabled(self):
|
||||||
|
debugging.append((defer.Deferred.debug, DelayedCall.debug))
|
||||||
|
test = SomeCase('test_debugging_enabled')
|
||||||
|
runner = AsynchronousDeferredRunTest(
|
||||||
|
test, handlers=test.exception_handlers,
|
||||||
|
reactor=self.make_reactor(), timeout=self.make_timeout())
|
||||||
|
runner.run(self.make_result())
|
||||||
|
self.assertEqual(debugging[0], debugging[1])
|
||||||
|
|
||||||
class TestAssertFailsWith(TestCase):
|
def test_debugging_enabled_during_test_with_debug_flag(self):
|
||||||
|
self.patch(defer.Deferred, 'debug', False)
|
||||||
|
self.patch(DelayedCall, 'debug', False)
|
||||||
|
debugging = []
|
||||||
|
class SomeCase(TestCase):
|
||||||
|
def test_debugging_enabled(self):
|
||||||
|
debugging.append((defer.Deferred.debug, DelayedCall.debug))
|
||||||
|
test = SomeCase('test_debugging_enabled')
|
||||||
|
runner = AsynchronousDeferredRunTest(
|
||||||
|
test, handlers=test.exception_handlers,
|
||||||
|
reactor=self.make_reactor(), timeout=self.make_timeout(),
|
||||||
|
debug=True)
|
||||||
|
runner.run(self.make_result())
|
||||||
|
self.assertEqual([(True, True)], debugging)
|
||||||
|
self.assertEqual(False, defer.Deferred.debug)
|
||||||
|
self.assertEqual(False, defer.Deferred.debug)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAssertFailsWith(NeedsTwistedTestCase):
|
||||||
"""Tests for `assert_fails_with`."""
|
"""Tests for `assert_fails_with`."""
|
||||||
|
|
||||||
|
if SynchronousDeferredRunTest is not None:
|
||||||
run_tests_with = SynchronousDeferredRunTest
|
run_tests_with = SynchronousDeferredRunTest
|
||||||
|
|
||||||
def test_assert_fails_with_success(self):
|
def test_assert_fails_with_success(self):
|
||||||
|
|||||||
@@ -1,20 +1,26 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import fixtures
|
|
||||||
from fixtures.tests.helpers import LoggingFixture
|
|
||||||
|
|
||||||
from testtools import (
|
from testtools import (
|
||||||
TestCase,
|
TestCase,
|
||||||
content,
|
content,
|
||||||
content_type,
|
content_type,
|
||||||
)
|
)
|
||||||
|
from testtools.helpers import try_import
|
||||||
from testtools.tests.helpers import (
|
from testtools.tests.helpers import (
|
||||||
ExtendedTestResult,
|
ExtendedTestResult,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fixtures = try_import('fixtures')
|
||||||
|
LoggingFixture = try_import('fixtures.tests.helpers.LoggingFixture')
|
||||||
|
|
||||||
|
|
||||||
class TestFixtureSupport(TestCase):
|
class TestFixtureSupport(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestFixtureSupport, self).setUp()
|
||||||
|
if fixtures is None or LoggingFixture is None:
|
||||||
|
self.skipTest("Need fixtures")
|
||||||
|
|
||||||
def test_useFixture(self):
|
def test_useFixture(self):
|
||||||
fixture = LoggingFixture()
|
fixture = LoggingFixture()
|
||||||
class SimpleTest(TestCase):
|
class SimpleTest(TestCase):
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ from testtools.matchers import (
|
|||||||
Annotate,
|
Annotate,
|
||||||
Equals,
|
Equals,
|
||||||
DocTestMatches,
|
DocTestMatches,
|
||||||
|
DoesNotEndWith,
|
||||||
DoesNotStartWith,
|
DoesNotStartWith,
|
||||||
|
EndsWith,
|
||||||
KeysEqual,
|
KeysEqual,
|
||||||
Is,
|
Is,
|
||||||
LessThan,
|
LessThan,
|
||||||
@@ -181,8 +183,7 @@ class TestMatchesExceptionInstanceInterface(TestCase, TestMatchersInterface):
|
|||||||
MatchesException(Exception('foo')))
|
MatchesException(Exception('foo')))
|
||||||
]
|
]
|
||||||
describe_examples = [
|
describe_examples = [
|
||||||
("<type 'exceptions.Exception'> is not a "
|
("%r is not a %r" % (Exception, ValueError),
|
||||||
"<type 'exceptions.ValueError'>",
|
|
||||||
error_base_foo,
|
error_base_foo,
|
||||||
MatchesException(ValueError("foo"))),
|
MatchesException(ValueError("foo"))),
|
||||||
("ValueError('bar',) has different arguments to ValueError('foo',).",
|
("ValueError('bar',) has different arguments to ValueError('foo',).",
|
||||||
@@ -201,12 +202,11 @@ class TestMatchesExceptionTypeInterface(TestCase, TestMatchersInterface):
|
|||||||
matches_mismatches = [error_base_foo]
|
matches_mismatches = [error_base_foo]
|
||||||
|
|
||||||
str_examples = [
|
str_examples = [
|
||||||
("MatchesException(<type 'exceptions.Exception'>)",
|
("MatchesException(%r)" % Exception,
|
||||||
MatchesException(Exception))
|
MatchesException(Exception))
|
||||||
]
|
]
|
||||||
describe_examples = [
|
describe_examples = [
|
||||||
("<type 'exceptions.Exception'> is not a "
|
("%r is not a %r" % (Exception, ValueError),
|
||||||
"<type 'exceptions.ValueError'>",
|
|
||||||
error_base_foo,
|
error_base_foo,
|
||||||
MatchesException(ValueError)),
|
MatchesException(ValueError)),
|
||||||
]
|
]
|
||||||
@@ -247,8 +247,7 @@ Expected:
|
|||||||
Got:
|
Got:
|
||||||
3
|
3
|
||||||
|
|
||||||
]
|
]""",
|
||||||
""",
|
|
||||||
"3", MatchesAny(DocTestMatches("1"), DocTestMatches("2")))]
|
"3", MatchesAny(DocTestMatches("1"), DocTestMatches("2")))]
|
||||||
|
|
||||||
|
|
||||||
@@ -264,8 +263,7 @@ class TestMatchesAllInterface(TestCase, TestMatchersInterface):
|
|||||||
|
|
||||||
describe_examples = [("""Differences: [
|
describe_examples = [("""Differences: [
|
||||||
1 == 1
|
1 == 1
|
||||||
]
|
]""",
|
||||||
""",
|
|
||||||
1, MatchesAll(NotEquals(1), NotEquals(2)))]
|
1, MatchesAll(NotEquals(1), NotEquals(2)))]
|
||||||
|
|
||||||
|
|
||||||
@@ -362,7 +360,12 @@ class TestRaisesBaseTypes(TestCase):
|
|||||||
# Exception, it is propogated.
|
# Exception, it is propogated.
|
||||||
match_keyb = Raises(MatchesException(KeyboardInterrupt))
|
match_keyb = Raises(MatchesException(KeyboardInterrupt))
|
||||||
def raise_keyb_from_match():
|
def raise_keyb_from_match():
|
||||||
|
if sys.version_info > (2, 5):
|
||||||
matcher = Raises(MatchesException(Exception))
|
matcher = Raises(MatchesException(Exception))
|
||||||
|
else:
|
||||||
|
# On Python 2.4 KeyboardInterrupt is a StandardError subclass
|
||||||
|
# but should propogate from less generic exception matchers
|
||||||
|
matcher = Raises(MatchesException(EnvironmentError))
|
||||||
matcher.match(self.raiser)
|
matcher.match(self.raiser)
|
||||||
self.assertThat(raise_keyb_from_match, match_keyb)
|
self.assertThat(raise_keyb_from_match, match_keyb)
|
||||||
|
|
||||||
@@ -411,6 +414,38 @@ class StartsWithTests(TestCase):
|
|||||||
self.assertEqual("bar", mismatch.expected)
|
self.assertEqual("bar", mismatch.expected)
|
||||||
|
|
||||||
|
|
||||||
|
class DoesNotEndWithTests(TestCase):
|
||||||
|
|
||||||
|
def test_describe(self):
|
||||||
|
mismatch = DoesNotEndWith("fo", "bo")
|
||||||
|
self.assertEqual("'fo' does not end with 'bo'.", mismatch.describe())
|
||||||
|
|
||||||
|
|
||||||
|
class EndsWithTests(TestCase):
|
||||||
|
|
||||||
|
def test_str(self):
|
||||||
|
matcher = EndsWith("bar")
|
||||||
|
self.assertEqual("Ends with 'bar'.", str(matcher))
|
||||||
|
|
||||||
|
def test_match(self):
|
||||||
|
matcher = EndsWith("arf")
|
||||||
|
self.assertIs(None, matcher.match("barf"))
|
||||||
|
|
||||||
|
def test_mismatch_returns_does_not_end_with(self):
|
||||||
|
matcher = EndsWith("bar")
|
||||||
|
self.assertIsInstance(matcher.match("foo"), DoesNotEndWith)
|
||||||
|
|
||||||
|
def test_mismatch_sets_matchee(self):
|
||||||
|
matcher = EndsWith("bar")
|
||||||
|
mismatch = matcher.match("foo")
|
||||||
|
self.assertEqual("foo", mismatch.matchee)
|
||||||
|
|
||||||
|
def test_mismatch_sets_expected(self):
|
||||||
|
matcher = EndsWith("bar")
|
||||||
|
mismatch = matcher.match("foo")
|
||||||
|
self.assertEqual("bar", mismatch.expected)
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
from unittest import TestLoader
|
from unittest import TestLoader
|
||||||
return TestLoader().loadTestsFromName(__name__)
|
return TestLoader().loadTestsFromName(__name__)
|
||||||
|
|||||||
76
testtools/tests/test_run.py
Normal file
76
testtools/tests/test_run.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Copyright (c) 2010 Testtools authors. See LICENSE for details.
|
||||||
|
|
||||||
|
"""Tests for the test runner logic."""
|
||||||
|
|
||||||
|
from testtools.helpers import try_import, try_imports
|
||||||
|
fixtures = try_import('fixtures')
|
||||||
|
StringIO = try_imports(['StringIO.StringIO', 'io.StringIO'])
|
||||||
|
|
||||||
|
import testtools
|
||||||
|
from testtools import TestCase, run
|
||||||
|
|
||||||
|
|
||||||
|
if fixtures:
|
||||||
|
class SampleTestFixture(fixtures.Fixture):
|
||||||
|
"""Creates testtools.runexample temporarily."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.package = fixtures.PythonPackage(
|
||||||
|
'runexample', [('__init__.py', """
|
||||||
|
from testtools import TestCase
|
||||||
|
|
||||||
|
class TestFoo(TestCase):
|
||||||
|
def test_bar(self):
|
||||||
|
pass
|
||||||
|
def test_quux(self):
|
||||||
|
pass
|
||||||
|
def test_suite():
|
||||||
|
from unittest import TestLoader
|
||||||
|
return TestLoader().loadTestsFromName(__name__)
|
||||||
|
""")])
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(SampleTestFixture, self).setUp()
|
||||||
|
self.useFixture(self.package)
|
||||||
|
testtools.__path__.append(self.package.base)
|
||||||
|
self.addCleanup(testtools.__path__.remove, self.package.base)
|
||||||
|
|
||||||
|
|
||||||
|
class TestRun(TestCase):
|
||||||
|
|
||||||
|
def test_run_list(self):
|
||||||
|
if fixtures is None:
|
||||||
|
self.skipTest("Need fixtures")
|
||||||
|
package = self.useFixture(SampleTestFixture())
|
||||||
|
out = StringIO()
|
||||||
|
run.main(['prog', '-l', 'testtools.runexample.test_suite'], out)
|
||||||
|
self.assertEqual("""testtools.runexample.TestFoo.test_bar
|
||||||
|
testtools.runexample.TestFoo.test_quux
|
||||||
|
""", out.getvalue())
|
||||||
|
|
||||||
|
def test_run_load_list(self):
|
||||||
|
if fixtures is None:
|
||||||
|
self.skipTest("Need fixtures")
|
||||||
|
package = self.useFixture(SampleTestFixture())
|
||||||
|
out = StringIO()
|
||||||
|
# We load two tests - one that exists and one that doesn't, and we
|
||||||
|
# should get the one that exists and neither the one that doesn't nor
|
||||||
|
# the unmentioned one that does.
|
||||||
|
tempdir = self.useFixture(fixtures.TempDir())
|
||||||
|
tempname = tempdir.path + '/tests.list'
|
||||||
|
f = open(tempname, 'wb')
|
||||||
|
try:
|
||||||
|
f.write("""
|
||||||
|
testtools.runexample.TestFoo.test_bar
|
||||||
|
testtools.runexample.missingtest
|
||||||
|
""")
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
run.main(['prog', '-l', '--load-list', tempname,
|
||||||
|
'testtools.runexample.test_suite'], out)
|
||||||
|
self.assertEqual("""testtools.runexample.TestFoo.test_bar
|
||||||
|
""", out.getvalue())
|
||||||
|
|
||||||
|
def test_suite():
|
||||||
|
from unittest import TestLoader
|
||||||
|
return TestLoader().loadTestsFromName(__name__)
|
||||||
@@ -9,90 +9,91 @@ from testtools import (
|
|||||||
skipIf,
|
skipIf,
|
||||||
TestCase,
|
TestCase,
|
||||||
)
|
)
|
||||||
|
from testtools.helpers import try_import
|
||||||
from testtools.matchers import (
|
from testtools.matchers import (
|
||||||
Equals,
|
Equals,
|
||||||
Is,
|
Is,
|
||||||
MatchesException,
|
MatchesException,
|
||||||
Raises,
|
Raises,
|
||||||
)
|
)
|
||||||
from testtools._spinner import (
|
|
||||||
DeferredNotFired,
|
|
||||||
extract_result,
|
|
||||||
NoResultError,
|
|
||||||
not_reentrant,
|
|
||||||
ReentryError,
|
|
||||||
Spinner,
|
|
||||||
StaleJunkError,
|
|
||||||
TimeoutError,
|
|
||||||
trap_unhandled_errors,
|
|
||||||
)
|
|
||||||
|
|
||||||
from twisted.internet import defer
|
_spinner = try_import('testtools._spinner')
|
||||||
from twisted.python.failure import Failure
|
|
||||||
|
defer = try_import('twisted.internet.defer')
|
||||||
|
Failure = try_import('twisted.python.failure.Failure')
|
||||||
|
|
||||||
|
|
||||||
class TestNotReentrant(TestCase):
|
class NeedsTwistedTestCase(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(NeedsTwistedTestCase, self).setUp()
|
||||||
|
if defer is None or Failure is None:
|
||||||
|
self.skipTest("Need Twisted to run")
|
||||||
|
|
||||||
|
|
||||||
|
class TestNotReentrant(NeedsTwistedTestCase):
|
||||||
|
|
||||||
def test_not_reentrant(self):
|
def test_not_reentrant(self):
|
||||||
# A function decorated as not being re-entrant will raise a
|
# A function decorated as not being re-entrant will raise a
|
||||||
# ReentryError if it is called while it is running.
|
# _spinner.ReentryError if it is called while it is running.
|
||||||
calls = []
|
calls = []
|
||||||
@not_reentrant
|
@_spinner.not_reentrant
|
||||||
def log_something():
|
def log_something():
|
||||||
calls.append(None)
|
calls.append(None)
|
||||||
if len(calls) < 5:
|
if len(calls) < 5:
|
||||||
log_something()
|
log_something()
|
||||||
self.assertThat(log_something, Raises(MatchesException(ReentryError)))
|
self.assertThat(
|
||||||
|
log_something, Raises(MatchesException(_spinner.ReentryError)))
|
||||||
self.assertEqual(1, len(calls))
|
self.assertEqual(1, len(calls))
|
||||||
|
|
||||||
def test_deeper_stack(self):
|
def test_deeper_stack(self):
|
||||||
calls = []
|
calls = []
|
||||||
@not_reentrant
|
@_spinner.not_reentrant
|
||||||
def g():
|
def g():
|
||||||
calls.append(None)
|
calls.append(None)
|
||||||
if len(calls) < 5:
|
if len(calls) < 5:
|
||||||
f()
|
f()
|
||||||
@not_reentrant
|
@_spinner.not_reentrant
|
||||||
def f():
|
def f():
|
||||||
calls.append(None)
|
calls.append(None)
|
||||||
if len(calls) < 5:
|
if len(calls) < 5:
|
||||||
g()
|
g()
|
||||||
self.assertThat(f, Raises(MatchesException(ReentryError)))
|
self.assertThat(f, Raises(MatchesException(_spinner.ReentryError)))
|
||||||
self.assertEqual(2, len(calls))
|
self.assertEqual(2, len(calls))
|
||||||
|
|
||||||
|
|
||||||
class TestExtractResult(TestCase):
|
class TestExtractResult(NeedsTwistedTestCase):
|
||||||
|
|
||||||
def test_not_fired(self):
|
def test_not_fired(self):
|
||||||
# extract_result raises DeferredNotFired if it's given a Deferred that
|
# _spinner.extract_result raises _spinner.DeferredNotFired if it's
|
||||||
# has not fired.
|
# given a Deferred that has not fired.
|
||||||
self.assertThat(lambda:extract_result(defer.Deferred()),
|
self.assertThat(lambda:_spinner.extract_result(defer.Deferred()),
|
||||||
Raises(MatchesException(DeferredNotFired)))
|
Raises(MatchesException(_spinner.DeferredNotFired)))
|
||||||
|
|
||||||
def test_success(self):
|
def test_success(self):
|
||||||
# extract_result returns the value of the Deferred if it has fired
|
# _spinner.extract_result returns the value of the Deferred if it has
|
||||||
# successfully.
|
# fired successfully.
|
||||||
marker = object()
|
marker = object()
|
||||||
d = defer.succeed(marker)
|
d = defer.succeed(marker)
|
||||||
self.assertThat(extract_result(d), Equals(marker))
|
self.assertThat(_spinner.extract_result(d), Equals(marker))
|
||||||
|
|
||||||
def test_failure(self):
|
def test_failure(self):
|
||||||
# extract_result raises the failure's exception if it's given a
|
# _spinner.extract_result raises the failure's exception if it's given
|
||||||
# Deferred that is failing.
|
# a Deferred that is failing.
|
||||||
try:
|
try:
|
||||||
1/0
|
1/0
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
f = Failure()
|
f = Failure()
|
||||||
d = defer.fail(f)
|
d = defer.fail(f)
|
||||||
self.assertThat(lambda:extract_result(d),
|
self.assertThat(lambda:_spinner.extract_result(d),
|
||||||
Raises(MatchesException(ZeroDivisionError)))
|
Raises(MatchesException(ZeroDivisionError)))
|
||||||
|
|
||||||
|
|
||||||
class TestTrapUnhandledErrors(TestCase):
|
class TestTrapUnhandledErrors(NeedsTwistedTestCase):
|
||||||
|
|
||||||
def test_no_deferreds(self):
|
def test_no_deferreds(self):
|
||||||
marker = object()
|
marker = object()
|
||||||
result, errors = trap_unhandled_errors(lambda: marker)
|
result, errors = _spinner.trap_unhandled_errors(lambda: marker)
|
||||||
self.assertEqual([], errors)
|
self.assertEqual([], errors)
|
||||||
self.assertIs(marker, result)
|
self.assertIs(marker, result)
|
||||||
|
|
||||||
@@ -105,12 +106,13 @@ class TestTrapUnhandledErrors(TestCase):
|
|||||||
f = Failure()
|
f = Failure()
|
||||||
failures.append(f)
|
failures.append(f)
|
||||||
defer.fail(f)
|
defer.fail(f)
|
||||||
result, errors = trap_unhandled_errors(make_deferred_but_dont_handle)
|
result, errors = _spinner.trap_unhandled_errors(
|
||||||
|
make_deferred_but_dont_handle)
|
||||||
self.assertIs(None, result)
|
self.assertIs(None, result)
|
||||||
self.assertEqual(failures, [error.failResult for error in errors])
|
self.assertEqual(failures, [error.failResult for error in errors])
|
||||||
|
|
||||||
|
|
||||||
class TestRunInReactor(TestCase):
|
class TestRunInReactor(NeedsTwistedTestCase):
|
||||||
|
|
||||||
def make_reactor(self):
|
def make_reactor(self):
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
@@ -119,7 +121,7 @@ class TestRunInReactor(TestCase):
|
|||||||
def make_spinner(self, reactor=None):
|
def make_spinner(self, reactor=None):
|
||||||
if reactor is None:
|
if reactor is None:
|
||||||
reactor = self.make_reactor()
|
reactor = self.make_reactor()
|
||||||
return Spinner(reactor)
|
return _spinner.Spinner(reactor)
|
||||||
|
|
||||||
def make_timeout(self):
|
def make_timeout(self):
|
||||||
return 0.01
|
return 0.01
|
||||||
@@ -157,8 +159,8 @@ class TestRunInReactor(TestCase):
|
|||||||
# to run_in_reactor.
|
# to run_in_reactor.
|
||||||
spinner = self.make_spinner()
|
spinner = self.make_spinner()
|
||||||
self.assertThat(lambda: spinner.run(
|
self.assertThat(lambda: spinner.run(
|
||||||
self.make_timeout(), spinner.run, self.make_timeout(), lambda: None),
|
self.make_timeout(), spinner.run, self.make_timeout(),
|
||||||
Raises(MatchesException(ReentryError)))
|
lambda: None), Raises(MatchesException(_spinner.ReentryError)))
|
||||||
|
|
||||||
def test_deferred_value_returned(self):
|
def test_deferred_value_returned(self):
|
||||||
# If the given function returns a Deferred, run_in_reactor returns the
|
# If the given function returns a Deferred, run_in_reactor returns the
|
||||||
@@ -182,11 +184,12 @@ class TestRunInReactor(TestCase):
|
|||||||
self.assertEqual(new_hdlrs, map(signal.getsignal, signals))
|
self.assertEqual(new_hdlrs, map(signal.getsignal, signals))
|
||||||
|
|
||||||
def test_timeout(self):
|
def test_timeout(self):
|
||||||
# If the function takes too long to run, we raise a TimeoutError.
|
# If the function takes too long to run, we raise a
|
||||||
|
# _spinner.TimeoutError.
|
||||||
timeout = self.make_timeout()
|
timeout = self.make_timeout()
|
||||||
self.assertThat(
|
self.assertThat(
|
||||||
lambda:self.make_spinner().run(timeout, lambda: defer.Deferred()),
|
lambda:self.make_spinner().run(timeout, lambda: defer.Deferred()),
|
||||||
Raises(MatchesException(TimeoutError)))
|
Raises(MatchesException(_spinner.TimeoutError)))
|
||||||
|
|
||||||
def test_no_junk_by_default(self):
|
def test_no_junk_by_default(self):
|
||||||
# If the reactor hasn't spun yet, then there cannot be any junk.
|
# If the reactor hasn't spun yet, then there cannot be any junk.
|
||||||
@@ -241,7 +244,14 @@ class TestRunInReactor(TestCase):
|
|||||||
timeout = self.make_timeout()
|
timeout = self.make_timeout()
|
||||||
spinner = self.make_spinner(reactor)
|
spinner = self.make_spinner(reactor)
|
||||||
spinner.run(timeout, reactor.callInThread, time.sleep, timeout / 2.0)
|
spinner.run(timeout, reactor.callInThread, time.sleep, timeout / 2.0)
|
||||||
self.assertThat(list(threading.enumerate()), Equals(current_threads))
|
# Python before 2.5 has a race condition with thread handling where
|
||||||
|
# join() does not remove threads from enumerate before returning - the
|
||||||
|
# thread being joined does the removal. This was fixed in Python 2.5
|
||||||
|
# but we still support 2.4, so we have to workaround the issue.
|
||||||
|
# http://bugs.python.org/issue1703448.
|
||||||
|
self.assertThat(
|
||||||
|
[thread for thread in threading.enumerate() if thread.isAlive()],
|
||||||
|
Equals(current_threads))
|
||||||
|
|
||||||
def test_leftover_junk_available(self):
|
def test_leftover_junk_available(self):
|
||||||
# If 'run' is given a function that leaves the reactor dirty in some
|
# If 'run' is given a function that leaves the reactor dirty in some
|
||||||
@@ -263,7 +273,7 @@ class TestRunInReactor(TestCase):
|
|||||||
timeout = self.make_timeout()
|
timeout = self.make_timeout()
|
||||||
spinner.run(timeout, reactor.listenTCP, 0, ServerFactory())
|
spinner.run(timeout, reactor.listenTCP, 0, ServerFactory())
|
||||||
self.assertThat(lambda: spinner.run(timeout, lambda: None),
|
self.assertThat(lambda: spinner.run(timeout, lambda: None),
|
||||||
Raises(MatchesException(StaleJunkError)))
|
Raises(MatchesException(_spinner.StaleJunkError)))
|
||||||
|
|
||||||
def test_clear_junk_clears_previous_junk(self):
|
def test_clear_junk_clears_previous_junk(self):
|
||||||
# If 'run' is called and there's still junk in the spinner's junk
|
# If 'run' is called and there's still junk in the spinner's junk
|
||||||
@@ -279,7 +289,7 @@ class TestRunInReactor(TestCase):
|
|||||||
|
|
||||||
@skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only")
|
@skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only")
|
||||||
def test_sigint_raises_no_result_error(self):
|
def test_sigint_raises_no_result_error(self):
|
||||||
# If we get a SIGINT during a run, we raise NoResultError.
|
# If we get a SIGINT during a run, we raise _spinner.NoResultError.
|
||||||
SIGINT = getattr(signal, 'SIGINT', None)
|
SIGINT = getattr(signal, 'SIGINT', None)
|
||||||
if not SIGINT:
|
if not SIGINT:
|
||||||
self.skipTest("SIGINT not available")
|
self.skipTest("SIGINT not available")
|
||||||
@@ -288,19 +298,19 @@ class TestRunInReactor(TestCase):
|
|||||||
timeout = self.make_timeout()
|
timeout = self.make_timeout()
|
||||||
reactor.callLater(timeout, os.kill, os.getpid(), SIGINT)
|
reactor.callLater(timeout, os.kill, os.getpid(), SIGINT)
|
||||||
self.assertThat(lambda:spinner.run(timeout * 5, defer.Deferred),
|
self.assertThat(lambda:spinner.run(timeout * 5, defer.Deferred),
|
||||||
Raises(MatchesException(NoResultError)))
|
Raises(MatchesException(_spinner.NoResultError)))
|
||||||
self.assertEqual([], spinner._clean())
|
self.assertEqual([], spinner._clean())
|
||||||
|
|
||||||
@skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only")
|
@skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only")
|
||||||
def test_sigint_raises_no_result_error_second_time(self):
|
def test_sigint_raises_no_result_error_second_time(self):
|
||||||
# If we get a SIGINT during a run, we raise NoResultError. This test
|
# If we get a SIGINT during a run, we raise _spinner.NoResultError.
|
||||||
# is exactly the same as test_sigint_raises_no_result_error, and
|
# This test is exactly the same as test_sigint_raises_no_result_error,
|
||||||
# exists to make sure we haven't futzed with state.
|
# and exists to make sure we haven't futzed with state.
|
||||||
self.test_sigint_raises_no_result_error()
|
self.test_sigint_raises_no_result_error()
|
||||||
|
|
||||||
@skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only")
|
@skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only")
|
||||||
def test_fast_sigint_raises_no_result_error(self):
|
def test_fast_sigint_raises_no_result_error(self):
|
||||||
# If we get a SIGINT during a run, we raise NoResultError.
|
# If we get a SIGINT during a run, we raise _spinner.NoResultError.
|
||||||
SIGINT = getattr(signal, 'SIGINT', None)
|
SIGINT = getattr(signal, 'SIGINT', None)
|
||||||
if not SIGINT:
|
if not SIGINT:
|
||||||
self.skipTest("SIGINT not available")
|
self.skipTest("SIGINT not available")
|
||||||
@@ -309,7 +319,7 @@ class TestRunInReactor(TestCase):
|
|||||||
timeout = self.make_timeout()
|
timeout = self.make_timeout()
|
||||||
reactor.callWhenRunning(os.kill, os.getpid(), SIGINT)
|
reactor.callWhenRunning(os.kill, os.getpid(), SIGINT)
|
||||||
self.assertThat(lambda:spinner.run(timeout * 5, defer.Deferred),
|
self.assertThat(lambda:spinner.run(timeout * 5, defer.Deferred),
|
||||||
Raises(MatchesException(NoResultError)))
|
Raises(MatchesException(_spinner.NoResultError)))
|
||||||
self.assertEqual([], spinner._clean())
|
self.assertEqual([], spinner._clean())
|
||||||
|
|
||||||
@skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only")
|
@skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only")
|
||||||
|
|||||||
@@ -45,12 +45,44 @@ from testtools.tests.helpers import (
|
|||||||
ExtendedTestResult,
|
ExtendedTestResult,
|
||||||
an_exc_info
|
an_exc_info
|
||||||
)
|
)
|
||||||
|
from testtools.testresult.real import utc
|
||||||
|
|
||||||
StringIO = try_imports(['StringIO.StringIO', 'io.StringIO'])
|
StringIO = try_imports(['StringIO.StringIO', 'io.StringIO'])
|
||||||
|
|
||||||
|
|
||||||
class TestTestResultContract(TestCase):
|
class Python26Contract(object):
|
||||||
"""Tests for the contract of TestResults."""
|
|
||||||
|
def test_fresh_result_is_successful(self):
|
||||||
|
# A result is considered successful before any tests are run.
|
||||||
|
result = self.makeResult()
|
||||||
|
self.assertTrue(result.wasSuccessful())
|
||||||
|
|
||||||
|
def test_addError_is_failure(self):
|
||||||
|
# addError fails the test run.
|
||||||
|
result = self.makeResult()
|
||||||
|
result.startTest(self)
|
||||||
|
result.addError(self, an_exc_info)
|
||||||
|
result.stopTest(self)
|
||||||
|
self.assertFalse(result.wasSuccessful())
|
||||||
|
|
||||||
|
def test_addFailure_is_failure(self):
|
||||||
|
# addFailure fails the test run.
|
||||||
|
result = self.makeResult()
|
||||||
|
result.startTest(self)
|
||||||
|
result.addFailure(self, an_exc_info)
|
||||||
|
result.stopTest(self)
|
||||||
|
self.assertFalse(result.wasSuccessful())
|
||||||
|
|
||||||
|
def test_addSuccess_is_success(self):
|
||||||
|
# addSuccess does not fail the test run.
|
||||||
|
result = self.makeResult()
|
||||||
|
result.startTest(self)
|
||||||
|
result.addSuccess(self)
|
||||||
|
result.stopTest(self)
|
||||||
|
self.assertTrue(result.wasSuccessful())
|
||||||
|
|
||||||
|
|
||||||
|
class Python27Contract(Python26Contract):
|
||||||
|
|
||||||
def test_addExpectedFailure(self):
|
def test_addExpectedFailure(self):
|
||||||
# Calling addExpectedFailure(test, exc_info) completes ok.
|
# Calling addExpectedFailure(test, exc_info) completes ok.
|
||||||
@@ -58,6 +90,52 @@ class TestTestResultContract(TestCase):
|
|||||||
result.startTest(self)
|
result.startTest(self)
|
||||||
result.addExpectedFailure(self, an_exc_info)
|
result.addExpectedFailure(self, an_exc_info)
|
||||||
|
|
||||||
|
def test_addExpectedFailure_is_success(self):
|
||||||
|
# addExpectedFailure does not fail the test run.
|
||||||
|
result = self.makeResult()
|
||||||
|
result.startTest(self)
|
||||||
|
result.addExpectedFailure(self, an_exc_info)
|
||||||
|
result.stopTest(self)
|
||||||
|
self.assertTrue(result.wasSuccessful())
|
||||||
|
|
||||||
|
def test_addSkipped(self):
|
||||||
|
# Calling addSkip(test, reason) completes ok.
|
||||||
|
result = self.makeResult()
|
||||||
|
result.startTest(self)
|
||||||
|
result.addSkip(self, _u("Skipped for some reason"))
|
||||||
|
|
||||||
|
def test_addSkip_is_success(self):
|
||||||
|
# addSkip does not fail the test run.
|
||||||
|
result = self.makeResult()
|
||||||
|
result.startTest(self)
|
||||||
|
result.addSkip(self, _u("Skipped for some reason"))
|
||||||
|
result.stopTest(self)
|
||||||
|
self.assertTrue(result.wasSuccessful())
|
||||||
|
|
||||||
|
def test_addUnexpectedSuccess(self):
|
||||||
|
# Calling addUnexpectedSuccess(test) completes ok.
|
||||||
|
result = self.makeResult()
|
||||||
|
result.startTest(self)
|
||||||
|
result.addUnexpectedSuccess(self)
|
||||||
|
|
||||||
|
def test_addUnexpectedSuccess_was_successful(self):
|
||||||
|
# addUnexpectedSuccess does not fail the test run in Python 2.7.
|
||||||
|
result = self.makeResult()
|
||||||
|
result.startTest(self)
|
||||||
|
result.addUnexpectedSuccess(self)
|
||||||
|
result.stopTest(self)
|
||||||
|
self.assertTrue(result.wasSuccessful())
|
||||||
|
|
||||||
|
def test_startStopTestRun(self):
|
||||||
|
# Calling startTestRun completes ok.
|
||||||
|
result = self.makeResult()
|
||||||
|
result.startTestRun()
|
||||||
|
result.stopTestRun()
|
||||||
|
|
||||||
|
|
||||||
|
class DetailsContract(Python27Contract):
|
||||||
|
"""Tests for the contract of TestResults."""
|
||||||
|
|
||||||
def test_addExpectedFailure_details(self):
|
def test_addExpectedFailure_details(self):
|
||||||
# Calling addExpectedFailure(test, details=xxx) completes ok.
|
# Calling addExpectedFailure(test, details=xxx) completes ok.
|
||||||
result = self.makeResult()
|
result = self.makeResult()
|
||||||
@@ -76,24 +154,12 @@ class TestTestResultContract(TestCase):
|
|||||||
result.startTest(self)
|
result.startTest(self)
|
||||||
result.addFailure(self, details={})
|
result.addFailure(self, details={})
|
||||||
|
|
||||||
def test_addSkipped(self):
|
|
||||||
# Calling addSkip(test, reason) completes ok.
|
|
||||||
result = self.makeResult()
|
|
||||||
result.startTest(self)
|
|
||||||
result.addSkip(self, _u("Skipped for some reason"))
|
|
||||||
|
|
||||||
def test_addSkipped_details(self):
|
def test_addSkipped_details(self):
|
||||||
# Calling addSkip(test, reason) completes ok.
|
# Calling addSkip(test, reason) completes ok.
|
||||||
result = self.makeResult()
|
result = self.makeResult()
|
||||||
result.startTest(self)
|
result.startTest(self)
|
||||||
result.addSkip(self, details={})
|
result.addSkip(self, details={})
|
||||||
|
|
||||||
def test_addUnexpectedSuccess(self):
|
|
||||||
# Calling addUnexpectedSuccess(test) completes ok.
|
|
||||||
result = self.makeResult()
|
|
||||||
result.startTest(self)
|
|
||||||
result.addUnexpectedSuccess(self)
|
|
||||||
|
|
||||||
def test_addUnexpectedSuccess_details(self):
|
def test_addUnexpectedSuccess_details(self):
|
||||||
# Calling addUnexpectedSuccess(test) completes ok.
|
# Calling addUnexpectedSuccess(test) completes ok.
|
||||||
result = self.makeResult()
|
result = self.makeResult()
|
||||||
@@ -106,32 +172,73 @@ class TestTestResultContract(TestCase):
|
|||||||
result.startTest(self)
|
result.startTest(self)
|
||||||
result.addSuccess(self, details={})
|
result.addSuccess(self, details={})
|
||||||
|
|
||||||
def test_startStopTestRun(self):
|
|
||||||
# Calling startTestRun completes ok.
|
class FallbackContract(DetailsContract):
|
||||||
|
"""When we fallback we take our policy choice to map calls.
|
||||||
|
|
||||||
|
For instance, we map unexpectedSuccess to an error code, not to success.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_addUnexpectedSuccess_was_successful(self):
|
||||||
|
# addUnexpectedSuccess fails test run in testtools.
|
||||||
result = self.makeResult()
|
result = self.makeResult()
|
||||||
|
result.startTest(self)
|
||||||
|
result.addUnexpectedSuccess(self)
|
||||||
|
result.stopTest(self)
|
||||||
|
self.assertFalse(result.wasSuccessful())
|
||||||
|
|
||||||
|
|
||||||
|
class StartTestRunContract(FallbackContract):
|
||||||
|
"""Defines the contract for testtools policy choices.
|
||||||
|
|
||||||
|
That is things which are not simply extensions to unittest but choices we
|
||||||
|
have made differently.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_startTestRun_resets_unexpected_success(self):
|
||||||
|
result = self.makeResult()
|
||||||
|
result.startTest(self)
|
||||||
|
result.addUnexpectedSuccess(self)
|
||||||
|
result.stopTest(self)
|
||||||
result.startTestRun()
|
result.startTestRun()
|
||||||
result.stopTestRun()
|
self.assertTrue(result.wasSuccessful())
|
||||||
|
|
||||||
|
def test_startTestRun_resets_failure(self):
|
||||||
|
result = self.makeResult()
|
||||||
|
result.startTest(self)
|
||||||
|
result.addFailure(self, an_exc_info)
|
||||||
|
result.stopTest(self)
|
||||||
|
result.startTestRun()
|
||||||
|
self.assertTrue(result.wasSuccessful())
|
||||||
|
|
||||||
|
def test_startTestRun_resets_errors(self):
|
||||||
|
result = self.makeResult()
|
||||||
|
result.startTest(self)
|
||||||
|
result.addError(self, an_exc_info)
|
||||||
|
result.stopTest(self)
|
||||||
|
result.startTestRun()
|
||||||
|
self.assertTrue(result.wasSuccessful())
|
||||||
|
|
||||||
|
|
||||||
class TestTestResultContract(TestTestResultContract):
|
class TestTestResultContract(TestCase, StartTestRunContract):
|
||||||
|
|
||||||
def makeResult(self):
|
def makeResult(self):
|
||||||
return TestResult()
|
return TestResult()
|
||||||
|
|
||||||
|
|
||||||
class TestMultiTestresultContract(TestTestResultContract):
|
class TestMultiTestResultContract(TestCase, StartTestRunContract):
|
||||||
|
|
||||||
def makeResult(self):
|
def makeResult(self):
|
||||||
return MultiTestResult(TestResult(), TestResult())
|
return MultiTestResult(TestResult(), TestResult())
|
||||||
|
|
||||||
|
|
||||||
class TestTextTestResultContract(TestTestResultContract):
|
class TestTextTestResultContract(TestCase, StartTestRunContract):
|
||||||
|
|
||||||
def makeResult(self):
|
def makeResult(self):
|
||||||
return TextTestResult(StringIO())
|
return TextTestResult(StringIO())
|
||||||
|
|
||||||
|
|
||||||
class TestThreadSafeForwardingResultContract(TestTestResultContract):
|
class TestThreadSafeForwardingResultContract(TestCase, StartTestRunContract):
|
||||||
|
|
||||||
def makeResult(self):
|
def makeResult(self):
|
||||||
result_semaphore = threading.Semaphore(1)
|
result_semaphore = threading.Semaphore(1)
|
||||||
@@ -139,6 +246,36 @@ class TestThreadSafeForwardingResultContract(TestTestResultContract):
|
|||||||
return ThreadsafeForwardingResult(target, result_semaphore)
|
return ThreadsafeForwardingResult(target, result_semaphore)
|
||||||
|
|
||||||
|
|
||||||
|
class TestExtendedTestResultContract(TestCase, StartTestRunContract):
|
||||||
|
|
||||||
|
def makeResult(self):
|
||||||
|
return ExtendedTestResult()
|
||||||
|
|
||||||
|
|
||||||
|
class TestPython26TestResultContract(TestCase, Python26Contract):
|
||||||
|
|
||||||
|
def makeResult(self):
|
||||||
|
return Python26TestResult()
|
||||||
|
|
||||||
|
|
||||||
|
class TestAdaptedPython26TestResultContract(TestCase, FallbackContract):
|
||||||
|
|
||||||
|
def makeResult(self):
|
||||||
|
return ExtendedToOriginalDecorator(Python26TestResult())
|
||||||
|
|
||||||
|
|
||||||
|
class TestPython27TestResultContract(TestCase, Python27Contract):
|
||||||
|
|
||||||
|
def makeResult(self):
|
||||||
|
return Python27TestResult()
|
||||||
|
|
||||||
|
|
||||||
|
class TestAdaptedPython27TestResultContract(TestCase, DetailsContract):
|
||||||
|
|
||||||
|
def makeResult(self):
|
||||||
|
return ExtendedToOriginalDecorator(Python27TestResult())
|
||||||
|
|
||||||
|
|
||||||
class TestTestResult(TestCase):
|
class TestTestResult(TestCase):
|
||||||
"""Tests for `TestResult`."""
|
"""Tests for `TestResult`."""
|
||||||
|
|
||||||
@@ -169,10 +306,10 @@ class TestTestResult(TestCase):
|
|||||||
self.addCleanup(restore)
|
self.addCleanup(restore)
|
||||||
class Module:
|
class Module:
|
||||||
pass
|
pass
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now(utc)
|
||||||
stubdatetime = Module()
|
stubdatetime = Module()
|
||||||
stubdatetime.datetime = Module()
|
stubdatetime.datetime = Module()
|
||||||
stubdatetime.datetime.now = lambda: now
|
stubdatetime.datetime.now = lambda tz: now
|
||||||
testresult.real.datetime = stubdatetime
|
testresult.real.datetime = stubdatetime
|
||||||
# Calling _now() looks up the time.
|
# Calling _now() looks up the time.
|
||||||
self.assertEqual(now, result._now())
|
self.assertEqual(now, result._now())
|
||||||
@@ -187,7 +324,7 @@ class TestTestResult(TestCase):
|
|||||||
|
|
||||||
def test_now_datetime_time(self):
|
def test_now_datetime_time(self):
|
||||||
result = self.makeResult()
|
result = self.makeResult()
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now(utc)
|
||||||
result.time(now)
|
result.time(now)
|
||||||
self.assertEqual(now, result._now())
|
self.assertEqual(now, result._now())
|
||||||
|
|
||||||
@@ -288,6 +425,11 @@ class TestMultiTestResult(TestWithFakeExceptions):
|
|||||||
result = multi_result.stopTestRun()
|
result = multi_result.stopTestRun()
|
||||||
self.assertEqual(('foo', 'foo'), result)
|
self.assertEqual(('foo', 'foo'), result)
|
||||||
|
|
||||||
|
def test_time(self):
|
||||||
|
# the time call is dispatched, not eaten by the base class
|
||||||
|
self.multiResult.time('foo')
|
||||||
|
self.assertResultLogsEqual([('time', 'foo')])
|
||||||
|
|
||||||
|
|
||||||
class TestTextTestResult(TestCase):
|
class TestTextTestResult(TestCase):
|
||||||
"""Tests for `TextTestResult`."""
|
"""Tests for `TextTestResult`."""
|
||||||
@@ -308,6 +450,12 @@ class TestTextTestResult(TestCase):
|
|||||||
self.fail("yo!")
|
self.fail("yo!")
|
||||||
return Test("failed")
|
return Test("failed")
|
||||||
|
|
||||||
|
def make_unexpectedly_successful_test(self):
|
||||||
|
class Test(TestCase):
|
||||||
|
def succeeded(self):
|
||||||
|
self.expectFailure("yo!", lambda: None)
|
||||||
|
return Test("succeeded")
|
||||||
|
|
||||||
def make_test(self):
|
def make_test(self):
|
||||||
class Test(TestCase):
|
class Test(TestCase):
|
||||||
def test(self):
|
def test(self):
|
||||||
@@ -359,7 +507,7 @@ class TestTextTestResult(TestCase):
|
|||||||
|
|
||||||
def test_stopTestRun_current_time(self):
|
def test_stopTestRun_current_time(self):
|
||||||
test = self.make_test()
|
test = self.make_test()
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now(utc)
|
||||||
self.result.time(now)
|
self.result.time(now)
|
||||||
self.result.startTestRun()
|
self.result.startTestRun()
|
||||||
self.result.startTest(test)
|
self.result.startTest(test)
|
||||||
@@ -393,9 +541,18 @@ class TestTextTestResult(TestCase):
|
|||||||
self.assertThat(self.getvalue(),
|
self.assertThat(self.getvalue(),
|
||||||
DocTestMatches("...\n\nFAILED (failures=1)\n", doctest.ELLIPSIS))
|
DocTestMatches("...\n\nFAILED (failures=1)\n", doctest.ELLIPSIS))
|
||||||
|
|
||||||
|
def test_stopTestRun_not_successful_unexpected_success(self):
|
||||||
|
test = self.make_unexpectedly_successful_test()
|
||||||
|
self.result.startTestRun()
|
||||||
|
test.run(self.result)
|
||||||
|
self.result.stopTestRun()
|
||||||
|
self.assertThat(self.getvalue(),
|
||||||
|
DocTestMatches("...\n\nFAILED (failures=1)\n", doctest.ELLIPSIS))
|
||||||
|
|
||||||
def test_stopTestRun_shows_details(self):
|
def test_stopTestRun_shows_details(self):
|
||||||
self.result.startTestRun()
|
self.result.startTestRun()
|
||||||
self.make_erroring_test().run(self.result)
|
self.make_erroring_test().run(self.result)
|
||||||
|
self.make_unexpectedly_successful_test().run(self.result)
|
||||||
self.make_failing_test().run(self.result)
|
self.make_failing_test().run(self.result)
|
||||||
self.reset_output()
|
self.reset_output()
|
||||||
self.result.stopTestRun()
|
self.result.stopTestRun()
|
||||||
@@ -407,7 +564,7 @@ Text attachment: traceback
|
|||||||
------------
|
------------
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "...testtools...runtest.py", line ..., in _run_user...
|
File "...testtools...runtest.py", line ..., in _run_user...
|
||||||
return fn(*args)
|
return fn(*args, **kwargs)
|
||||||
File "...testtools...testcase.py", line ..., in _run_test_method
|
File "...testtools...testcase.py", line ..., in _run_test_method
|
||||||
return self._get_test_method()()
|
return self._get_test_method()()
|
||||||
File "...testtools...tests...test_testresult.py", line ..., in error
|
File "...testtools...tests...test_testresult.py", line ..., in error
|
||||||
@@ -421,14 +578,17 @@ Text attachment: traceback
|
|||||||
------------
|
------------
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "...testtools...runtest.py", line ..., in _run_user...
|
File "...testtools...runtest.py", line ..., in _run_user...
|
||||||
return fn(*args)
|
return fn(*args, **kwargs)
|
||||||
File "...testtools...testcase.py", line ..., in _run_test_method
|
File "...testtools...testcase.py", line ..., in _run_test_method
|
||||||
return self._get_test_method()()
|
return self._get_test_method()()
|
||||||
File "...testtools...tests...test_testresult.py", line ..., in failed
|
File "...testtools...tests...test_testresult.py", line ..., in failed
|
||||||
self.fail("yo!")
|
self.fail("yo!")
|
||||||
AssertionError: yo!
|
AssertionError: yo!
|
||||||
------------
|
------------
|
||||||
...""", doctest.ELLIPSIS))
|
======================================================================
|
||||||
|
UNEXPECTED SUCCESS: testtools.tests.test_testresult.Test.succeeded
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
...""", doctest.ELLIPSIS | doctest.REPORT_NDIFF))
|
||||||
|
|
||||||
|
|
||||||
class TestThreadSafeForwardingResult(TestWithFakeExceptions):
|
class TestThreadSafeForwardingResult(TestWithFakeExceptions):
|
||||||
@@ -1076,6 +1236,8 @@ class TestNonAsciiResults(TestCase):
|
|||||||
"class UnprintableError(Exception):\n"
|
"class UnprintableError(Exception):\n"
|
||||||
" def __str__(self):\n"
|
" def __str__(self):\n"
|
||||||
" raise RuntimeError\n"
|
" raise RuntimeError\n"
|
||||||
|
" def __unicode__(self):\n"
|
||||||
|
" raise RuntimeError\n"
|
||||||
" def __repr__(self):\n"
|
" def __repr__(self):\n"
|
||||||
" raise RuntimeError\n")
|
" raise RuntimeError\n")
|
||||||
textoutput = self._test_external_case(
|
textoutput = self._test_external_case(
|
||||||
|
|||||||
@@ -375,6 +375,10 @@ class TestAssertions(TestCase):
|
|||||||
'42 is not an instance of %s' % self._formatTypes([Foo, Bar]),
|
'42 is not an instance of %s' % self._formatTypes([Foo, Bar]),
|
||||||
self.assertIsInstance, 42, (Foo, Bar))
|
self.assertIsInstance, 42, (Foo, Bar))
|
||||||
|
|
||||||
|
def test_assertIsInstance_overridden_message(self):
|
||||||
|
# assertIsInstance(obj, klass, msg) permits a custom message.
|
||||||
|
self.assertFails("foo", self.assertIsInstance, 42, str, "foo")
|
||||||
|
|
||||||
def test_assertIs(self):
|
def test_assertIs(self):
|
||||||
# assertIs asserts that an object is identical to another object.
|
# assertIs asserts that an object is identical to another object.
|
||||||
self.assertIs(None, None)
|
self.assertIs(None, None)
|
||||||
|
|||||||
Reference in New Issue
Block a user