summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorRobert Collins <robertc@robertcollins.net>2009-12-15 20:43:47 +1100
committerRobert Collins <robertc@robertcollins.net>2009-12-15 20:43:47 +1100
commit8ca4845046b7132b55232406fc17400f26775425 (patch)
treefeb88c7e13d1c2f18d36c695768151819571e45c /python
parent0928b56a9df541bc0815c6ca81e3000612102874 (diff)
parentb05256add5ef737448755b62cccce189b45df69b (diff)
downloadsubunit-git-8ca4845046b7132b55232406fc17400f26775425.tar.gz
Add experimental multipart support and addExpectFailure and addUnexpectedSuccess support.
Diffstat (limited to 'python')
-rw-r--r--python/subunit/__init__.py792
-rw-r--r--python/subunit/chunked.py164
-rw-r--r--python/subunit/details.py109
-rw-r--r--python/subunit/test_results.py236
-rw-r--r--python/subunit/tests/__init__.py4
-rw-r--r--python/subunit/tests/test_chunked.py127
-rw-r--r--python/subunit/tests/test_details.py110
-rw-r--r--python/subunit/tests/test_subunit_filter.py46
-rw-r--r--python/subunit/tests/test_test_protocol.py802
-rw-r--r--python/subunit/tests/test_test_results.py53
10 files changed, 1570 insertions, 873 deletions
diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py
index b3ffceb..6e8df90 100644
--- a/python/subunit/__init__.py
+++ b/python/subunit/__init__.py
@@ -17,7 +17,7 @@
"""Subunit - a streaming test protocol
Overview
-========
+++++++++
The ``subunit`` Python package provides a number of ``unittest`` extensions
which can be used to cause tests to output Subunit, to parse Subunit streams
@@ -41,10 +41,20 @@ Twisted. See the ``TestProtocolServer`` parser class for more details.
Subunit includes extensions to the Python ``TestResult`` protocol. These are
all done in a compatible manner: ``TestResult`` objects that do not implement
-the extension methods will not cause errors to be raised, instead the extesion
+the extension methods will not cause errors to be raised, instead the extension
will either lose fidelity (for instance, folding expected failures to success
-in Python versions < 2.7 or 3.1), or discard the extended data (for tags,
-timestamping and progress markers).
+in Python versions < 2.7 or 3.1), or discard the extended data (for extra
+details, tags, timestamping and progress markers).
+
+The test outcome methods ``addSuccess``, ``addError``, ``addExpectedFailure``,
+``addFailure``, ``addSkip`` take an optional keyword parameter ``details``
+which can be used instead of the usual python unittest parameter.
+When used the value of details should be a dict from ``string`` to
+``testtools.content.Content`` objects. This is a draft API being worked on with
+the Python Testing In Python mail list, with the goal of permitting a common
+way to provide additional data beyond a traceback, such as captured data from
+disk, logging messages etc. The reference for this API is in testtools (0.9.0
+and newer).
The ``tags(new_tags, gone_tags)`` method is called (if present) to add or
remove tags in the test run that is currently executing. If called when no
@@ -98,6 +108,12 @@ result object::
# And run your suite as normal, Subunit will exec each external script as
# needed and report to your result object.
suite.run(result)
+
+Utility modules
+---------------
+
+* subunit.chunked contains HTTP chunked encoding/decoding logic.
+* subunit.test_results contains TestResult helper classes.
"""
import datetime
@@ -109,6 +125,19 @@ import sys
import unittest
import iso8601
+from testtools import content, content_type, ExtendedToOriginalDecorator
+try:
+ from testtools.testresult.real import _StringException
+ RemoteException = _StringException
+ _remote_exception_str = '_StringException' # For testing.
+except ImportError:
+ raise ImportError ("testtools.testresult.real does not contain "
+ "_StringException, check your version.")
+
+
+from testtools.testresult.real import _StringException
+
+import chunked, details, test_results
PROGRESS_SET = 0
@@ -155,20 +184,258 @@ class DiscardStream(object):
pass
+class _ParserState(object):
+ """State for the subunit parser."""
+
+ def __init__(self, parser):
+ self.parser = parser
+
+ def addError(self, offset, line):
+ """An 'error:' directive has been read."""
+ self.parser.stdOutLineReceived(line)
+
+ def addExpectedFail(self, offset, line):
+ """An 'xfail:' directive has been read."""
+ self.parser.stdOutLineReceived(line)
+
+ def addFailure(self, offset, line):
+ """A 'failure:' directive has been read."""
+ self.parser.stdOutLineReceived(line)
+
+ def addSkip(self, offset, line):
+ """A 'skip:' directive has been read."""
+ self.parser.stdOutLineReceived(line)
+
+ def addSuccess(self, offset, line):
+ """A 'success:' directive has been read."""
+ self.parser.stdOutLineReceived(line)
+
+ def lineReceived(self, line):
+ """a line has been received."""
+ parts = line.split(None, 1)
+ if len(parts) == 2:
+ cmd, rest = parts
+ offset = len(cmd) + 1
+ cmd = cmd.strip(':')
+ if cmd in ('test', 'testing'):
+ self.startTest(offset, line)
+ elif cmd == 'error':
+ self.addError(offset, line)
+ elif cmd == 'failure':
+ self.addFailure(offset, line)
+ elif cmd == 'progress':
+ self.parser._handleProgress(offset, line)
+ elif cmd == 'skip':
+ self.addSkip(offset, line)
+ elif cmd in ('success', 'successful'):
+ self.addSuccess(offset, line)
+ elif cmd in ('tags',):
+ self.parser._handleTags(offset, line)
+ self.parser.subunitLineReceived(line)
+ elif cmd in ('time',):
+ self.parser._handleTime(offset, line)
+ self.parser.subunitLineReceived(line)
+ elif cmd == 'xfail':
+ self.addExpectedFail(offset, line)
+ else:
+ self.parser.stdOutLineReceived(line)
+ else:
+ self.parser.stdOutLineReceived(line)
+
+ def lostConnection(self):
+ """Connection lost."""
+ self.parser._lostConnectionInTest('unknown state of ')
+
+ def startTest(self, offset, line):
+ """A test start command received."""
+ self.parser.stdOutLineReceived(line)
+
+
+class _InTest(_ParserState):
+ """State for the subunit parser after reading a test: directive."""
+
+ def _outcome(self, offset, line, no_details, details_state):
+ """An outcome directive has been read.
+
+ :param no_details: Callable to call when no details are presented.
+ :param details_state: The state to switch to for details
+ processing of this outcome.
+ """
+ if self.parser.current_test_description == line[offset:-1]:
+ self.parser._state = self.parser._outside_test
+ self.parser.current_test_description = None
+ no_details()
+ self.parser.client.stopTest(self.parser._current_test)
+ self.parser._current_test = None
+ self.parser.subunitLineReceived(line)
+ elif self.parser.current_test_description + " [" == line[offset:-1]:
+ self.parser._state = details_state
+ details_state.set_simple()
+ self.parser.subunitLineReceived(line)
+ elif self.parser.current_test_description + " [ multipart" == \
+ line[offset:-1]:
+ self.parser._state = details_state
+ details_state.set_multipart()
+ self.parser.subunitLineReceived(line)
+ else:
+ self.parser.stdOutLineReceived(line)
+
+ def _error(self):
+ self.parser.client.addError(self.parser._current_test,
+ details={})
+
+ def addError(self, offset, line):
+ """An 'error:' directive has been read."""
+ self._outcome(offset, line, self._error,
+ self.parser._reading_error_details)
+
+ def _xfail(self):
+ self.parser.client.addExpectedFailure(self.parser._current_test,
+ details={})
+
+ def addExpectedFail(self, offset, line):
+ """An 'xfail:' directive has been read."""
+ self._outcome(offset, line, self._xfail,
+ self.parser._reading_xfail_details)
+
+ def _failure(self):
+ self.parser.client.addFailure(self.parser._current_test, details={})
+
+ def addFailure(self, offset, line):
+ """A 'failure:' directive has been read."""
+ self._outcome(offset, line, self._failure,
+ self.parser._reading_failure_details)
+
+ def _skip(self):
+ self.parser.client.addSkip(self.parser._current_test, details={})
+
+ def addSkip(self, offset, line):
+ """A 'skip:' directive has been read."""
+ self._outcome(offset, line, self._skip,
+ self.parser._reading_skip_details)
+
+ def _succeed(self):
+ self.parser.client.addSuccess(self.parser._current_test, details={})
+
+ def addSuccess(self, offset, line):
+ """A 'success:' directive has been read."""
+ self._outcome(offset, line, self._succeed,
+ self.parser._reading_success_details)
+
+ def lostConnection(self):
+ """Connection lost."""
+ self.parser._lostConnectionInTest('')
+
+
+class _OutSideTest(_ParserState):
+ """State for the subunit parser outside of a test context."""
+
+ def lostConnection(self):
+ """Connection lost."""
+
+ def startTest(self, offset, line):
+ """A test start command received."""
+ self.parser._state = self.parser._in_test
+ self.parser._current_test = RemotedTestCase(line[offset:-1])
+ self.parser.current_test_description = line[offset:-1]
+ self.parser.client.startTest(self.parser._current_test)
+ self.parser.subunitLineReceived(line)
+
+
+class _ReadingDetails(_ParserState):
+ """Common logic for readin state details."""
+
+ def endDetails(self):
+ """The end of a details section has been reached."""
+ self.parser._state = self.parser._outside_test
+ self.parser.current_test_description = None
+ self._report_outcome()
+ self.parser.client.stopTest(self.parser._current_test)
+
+ def lineReceived(self, line):
+ """a line has been received."""
+ self.details_parser.lineReceived(line)
+ self.parser.subunitLineReceived(line)
+
+ def lostConnection(self):
+ """Connection lost."""
+ self.parser._lostConnectionInTest('%s report of ' %
+ self._outcome_label())
+
+ def _outcome_label(self):
+ """The label to describe this outcome."""
+ raise NotImplementedError(self._outcome_label)
+
+ def set_simple(self):
+ """Start a simple details parser."""
+ self.details_parser = details.SimpleDetailsParser(self)
+
+ def set_multipart(self):
+ """Start a multipart details parser."""
+ self.details_parser = details.MultipartDetailsParser(self)
+
+
+class _ReadingFailureDetails(_ReadingDetails):
+ """State for the subunit parser when reading failure details."""
+
+ def _report_outcome(self):
+ self.parser.client.addFailure(self.parser._current_test,
+ details=self.details_parser.get_details())
+
+ def _outcome_label(self):
+ return "failure"
+
+
+class _ReadingErrorDetails(_ReadingDetails):
+ """State for the subunit parser when reading error details."""
+
+ def _report_outcome(self):
+ self.parser.client.addError(self.parser._current_test,
+ details=self.details_parser.get_details())
+
+ def _outcome_label(self):
+ return "error"
+
+
+class _ReadingExpectedFailureDetails(_ReadingDetails):
+ """State for the subunit parser when reading xfail details."""
+
+ def _report_outcome(self):
+ self.parser.client.addExpectedFailure(self.parser._current_test,
+ details=self.details_parser.get_details())
+
+ def _outcome_label(self):
+ return "xfail"
+
+
+class _ReadingSkipDetails(_ReadingDetails):
+ """State for the subunit parser when reading skip details."""
+
+ def _report_outcome(self):
+ self.parser.client.addSkip(self.parser._current_test,
+ details=self.details_parser.get_details("skip"))
+
+ def _outcome_label(self):
+ return "skip"
+
+
+class _ReadingSuccessDetails(_ReadingDetails):
+ """State for the subunit parser when reading success details."""
+
+ def _report_outcome(self):
+ self.parser.client.addSuccess(self.parser._current_test,
+ details=self.details_parser.get_details("success"))
+
+ def _outcome_label(self):
+ return "success"
+
+
class TestProtocolServer(object):
"""A parser for subunit.
:ivar tags: The current tags associated with the protocol stream.
"""
- OUTSIDE_TEST = 0
- TEST_STARTED = 1
- READING_FAILURE = 2
- READING_ERROR = 3
- READING_SKIP = 4
- READING_XFAIL = 5
- READING_SUCCESS = 6
-
def __init__(self, client, stream=None, forward_stream=None):
"""Create a TestProtocolServer instance.
@@ -182,147 +449,21 @@ class TestProtocolServer(object):
and acting on it. By default forward_stream is set to
DiscardStream() and no forwarding happens.
"""
- self.state = TestProtocolServer.OUTSIDE_TEST
- self.client = client
+ self.client = ExtendedToOriginalDecorator(client)
if stream is None:
stream = sys.stdout
self._stream = stream
self._forward_stream = forward_stream or DiscardStream()
-
- def _addError(self, offset, line):
- if (self.state == TestProtocolServer.TEST_STARTED and
- self.current_test_description == line[offset:-1]):
- self.state = TestProtocolServer.OUTSIDE_TEST
- self.current_test_description = None
- self.client.addError(self._current_test, RemoteError(""))
- self.client.stopTest(self._current_test)
- self._current_test = None
- self.subunitLineReceived(line)
- elif (self.state == TestProtocolServer.TEST_STARTED and
- self.current_test_description + " [" == line[offset:-1]):
- self.state = TestProtocolServer.READING_ERROR
- self._message = ""
- self.subunitLineReceived(line)
- else:
- self.stdOutLineReceived(line)
-
- def _addExpectedFail(self, offset, line):
- if (self.state == TestProtocolServer.TEST_STARTED and
- self.current_test_description == line[offset:-1]):
- self.state = TestProtocolServer.OUTSIDE_TEST
- self.current_test_description = None
- xfail = getattr(self.client, 'addExpectedFailure', None)
- if callable(xfail):
- xfail(self._current_test, RemoteError())
- else:
- self.client.addSuccess(self._current_test)
- self.client.stopTest(self._current_test)
- self.subunitLineReceived(line)
- elif (self.state == TestProtocolServer.TEST_STARTED and
- self.current_test_description + " [" == line[offset:-1]):
- self.state = TestProtocolServer.READING_XFAIL
- self._message = ""
- self.subunitLineReceived(line)
- else:
- self.stdOutLineReceived(line)
-
- def _addFailure(self, offset, line):
- if (self.state == TestProtocolServer.TEST_STARTED and
- self.current_test_description == line[offset:-1]):
- self.state = TestProtocolServer.OUTSIDE_TEST
- self.current_test_description = None
- self.client.addFailure(self._current_test, RemoteError())
- self.client.stopTest(self._current_test)
- self.subunitLineReceived(line)
- elif (self.state == TestProtocolServer.TEST_STARTED and
- self.current_test_description + " [" == line[offset:-1]):
- self.state = TestProtocolServer.READING_FAILURE
- self._message = ""
- self.subunitLineReceived(line)
- else:
- self.stdOutLineReceived(line)
-
- def _addSkip(self, offset, line):
- if (self.state == TestProtocolServer.TEST_STARTED and
- self.current_test_description == line[offset:-1]):
- self.state = TestProtocolServer.OUTSIDE_TEST
- self.current_test_description = None
- self._skip_or_error()
- self.client.stopTest(self._current_test)
- self.subunitLineReceived(line)
- elif (self.state == TestProtocolServer.TEST_STARTED and
- self.current_test_description + " [" == line[offset:-1]):
- self.state = TestProtocolServer.READING_SKIP
- self._message = ""
- self.subunitLineReceived(line)
- else:
- self.stdOutLineReceived(line)
-
- def _skip_or_error(self, message=None):
- """Report the current test as a skip if possible, or else an error."""
- addSkip = getattr(self.client, 'addSkip', None)
- if not callable(addSkip):
- self.client.addError(self._current_test, RemoteError(message))
- else:
- if not message:
- message = "No reason given"
- addSkip(self._current_test, message)
-
- def _addSuccess(self, offset, line):
- if (self.state == TestProtocolServer.TEST_STARTED and
- self.current_test_description == line[offset:-1]):
- self._succeedTest()
- self.subunitLineReceived(line)
- elif (self.state == TestProtocolServer.TEST_STARTED and
- self.current_test_description + " [" == line[offset:-1]):
- self.state = TestProtocolServer.READING_SUCCESS
- self._message = ""
- self.subunitLineReceived(line)
- else:
- self.stdOutLineReceived(line)
-
- def _appendMessage(self, line):
- if line[0:2] == " ]":
- # quoted ] start
- self._message += line[1:]
- else:
- self._message += line
-
- def endQuote(self, line):
- stdout = False
- if self.state == TestProtocolServer.READING_FAILURE:
- self.state = TestProtocolServer.OUTSIDE_TEST
- self.current_test_description = None
- self.client.addFailure(self._current_test,
- RemoteError(self._message))
- self.client.stopTest(self._current_test)
- elif self.state == TestProtocolServer.READING_ERROR:
- self.state = TestProtocolServer.OUTSIDE_TEST
- self.current_test_description = None
- self.client.addError(self._current_test,
- RemoteError(self._message))
- self.client.stopTest(self._current_test)
- elif self.state == TestProtocolServer.READING_SKIP:
- self.state = TestProtocolServer.OUTSIDE_TEST
- self.current_test_description = None
- self._skip_or_error(self._message)
- self.client.stopTest(self._current_test)
- elif self.state == TestProtocolServer.READING_XFAIL:
- self.state = TestProtocolServer.OUTSIDE_TEST
- self.current_test_description = None
- xfail = getattr(self.client, 'addExpectedFailure', None)
- if callable(xfail):
- xfail(self._current_test, RemoteError(self._message))
- else:
- self.client.addSuccess(self._current_test)
- self.client.stopTest(self._current_test)
- elif self.state == TestProtocolServer.READING_SUCCESS:
- self._succeedTest()
- else:
- self.stdOutLineReceived(line)
- stdout = True
- if not stdout:
- self.subunitLineReceived(line)
+ # state objects we can switch too
+ self._in_test = _InTest(self)
+ self._outside_test = _OutSideTest(self)
+ self._reading_error_details = _ReadingErrorDetails(self)
+ self._reading_failure_details = _ReadingFailureDetails(self)
+ self._reading_skip_details = _ReadingSkipDetails(self)
+ self._reading_success_details = _ReadingSuccessDetails(self)
+ self._reading_xfail_details = _ReadingExpectedFailureDetails(self)
+ # start with outside test.
+ self._state = self._outside_test
def _handleProgress(self, offset, line):
"""Process a progress directive."""
@@ -339,17 +480,13 @@ class TestProtocolServer(object):
else:
whence = PROGRESS_SET
delta = int(line)
- progress_method = getattr(self.client, 'progress', None)
- if callable(progress_method):
- progress_method(delta, whence)
+ self.client.progress(delta, whence)
def _handleTags(self, offset, line):
"""Process a tags command."""
tags = line[offset:].split()
new_tags, gone_tags = tags_to_new_gone(tags)
- tags_method = getattr(self.client, 'tags', None)
- if tags_method is not None:
- tags_method(new_tags, gone_tags)
+ self.client.tags(new_tags, gone_tags)
def _handleTime(self, offset, line):
# Accept it, but do not do anything with it yet.
@@ -357,53 +494,11 @@ class TestProtocolServer(object):
event_time = iso8601.parse_date(line[offset:-1])
except TypeError, e:
raise TypeError("Failed to parse %r, got %r" % (line, e))
- time_method = getattr(self.client, 'time', None)
- if callable(time_method):
- time_method(event_time)
+ self.client.time(event_time)
def lineReceived(self, line):
"""Call the appropriate local method for the received line."""
- if line == "]\n":
- self.endQuote(line)
- elif self.state in (TestProtocolServer.READING_FAILURE,
- TestProtocolServer.READING_ERROR, TestProtocolServer.READING_SKIP,
- TestProtocolServer.READING_SUCCESS,
- TestProtocolServer.READING_XFAIL
- ):
- self._appendMessage(line)
- self.subunitLineReceived(line)
- else:
- parts = line.split(None, 1)
- stdout = False
- if len(parts) == 2:
- cmd, rest = parts
- offset = len(cmd) + 1
- cmd = cmd.strip(':')
- if cmd in ('test', 'testing'):
- self._startTest(offset, line)
- elif cmd == 'error':
- self._addError(offset, line)
- elif cmd == 'failure':
- self._addFailure(offset, line)
- elif cmd == 'progress':
- self._handleProgress(offset, line)
- self.subunitLineReceived(line)
- elif cmd == 'skip':
- self._addSkip(offset, line)
- elif cmd in ('success', 'successful'):
- self._addSuccess(offset, line)
- elif cmd in ('tags',):
- self._handleTags(offset, line)
- self.subunitLineReceived(line)
- elif cmd in ('time',):
- self._handleTime(offset, line)
- self.subunitLineReceived(line)
- elif cmd == 'xfail':
- self._addExpectedFail(offset, line)
- else:
- self.stdOutLineReceived(line)
- else:
- self.stdOutLineReceived(line)
+ self._state.lineReceived(line)
def _lostConnectionInTest(self, state_string):
error_string = "lost connection during %stest '%s'" % (
@@ -413,38 +508,21 @@ class TestProtocolServer(object):
def lostConnection(self):
"""The input connection has finished."""
- if self.state == TestProtocolServer.OUTSIDE_TEST:
- return
- if self.state == TestProtocolServer.TEST_STARTED:
- self._lostConnectionInTest('')
- elif self.state == TestProtocolServer.READING_ERROR:
- self._lostConnectionInTest('error report of ')
- elif self.state == TestProtocolServer.READING_FAILURE:
- self._lostConnectionInTest('failure report of ')
- elif self.state == TestProtocolServer.READING_SUCCESS:
- self._lostConnectionInTest('success report of ')
- elif self.state == TestProtocolServer.READING_SKIP:
- self._lostConnectionInTest('skip report of ')
- elif self.state == TestProtocolServer.READING_XFAIL:
- self._lostConnectionInTest('xfail report of ')
- else:
- self._lostConnectionInTest('unknown state of ')
+ self._state.lostConnection()
def readFrom(self, pipe):
+ """Blocking convenience API to parse an entire stream.
+
+ :param pipe: A file-like object supporting readlines().
+ :return: None.
+ """
for line in pipe.readlines():
self.lineReceived(line)
self.lostConnection()
def _startTest(self, offset, line):
"""Internal call to change state machine. Override startTest()."""
- if self.state == TestProtocolServer.OUTSIDE_TEST:
- self.state = TestProtocolServer.TEST_STARTED
- self._current_test = RemotedTestCase(line[offset:-1])
- self.current_test_description = line[offset:-1]
- self.client.startTest(self._current_test)
- self.subunitLineReceived(line)
- else:
- self.stdOutLineReceived(line)
+ self._state.startTest(offset, line)
def subunitLineReceived(self, line):
self._forward_stream.write(line)
@@ -452,23 +530,6 @@ class TestProtocolServer(object):
def stdOutLineReceived(self, line):
self._stream.write(line)
- def _succeedTest(self):
- self.client.addSuccess(self._current_test)
- self.client.stopTest(self._current_test)
- self.current_test_description = None
- self._current_test = None
- self.state = TestProtocolServer.OUTSIDE_TEST
-
-
-class RemoteException(Exception):
- """An exception that occured remotely to Python."""
-
- def __eq__(self, other):
- try:
- return self.args == other.args
- except AttributeError:
- return False
-
class TestProtocolClient(unittest.TestResult):
"""A TestResult which generates a subunit stream for a test run.
@@ -492,29 +553,95 @@ class TestProtocolClient(unittest.TestResult):
unittest.TestResult.__init__(self)
self._stream = stream
- def addError(self, test, error):
- """Report an error in test test."""
- self._stream.write("error: %s [\n" % test.id())
- for line in self._exc_info_to_string(error, test).splitlines():
- self._stream.write("%s\n" % line)
- self._stream.write("]\n")
+ def addError(self, test, error=None, details=None):
+ """Report an error in test test.
+
+ Only one of error and details should be provided: conceptually there
+ are two separate methods:
+ addError(self, test, error)
+ addError(self, test, details)
+
+ :param error: Standard unittest positional argument form - an
+ exc_info tuple.
+ :param details: New Testing-in-python drafted API; a dict from string
+ to subunit.Content objects.
+ """
+ self._addOutcome("error", test, error=error, details=details)
- def addFailure(self, test, error):
- """Report a failure in test test."""
- self._stream.write("failure: %s [\n" % test.id())
- for line in self._exc_info_to_string(error, test).splitlines():
- self._stream.write("%s\n" % line)
+ def addExpectedFailure(self, test, error=None, details=None):
+ """Report an expected failure in test test.
+
+ Only one of error and details should be provided: conceptually there
+ are two separate methods:
+ addError(self, test, error)
+ addError(self, test, details)
+
+ :param error: Standard unittest positional argument form - an
+ exc_info tuple.
+ :param details: New Testing-in-python drafted API; a dict from string
+ to subunit.Content objects.
+ """
+ self._addOutcome("xfail", test, error=error, details=details)
+
+ def addFailure(self, test, error=None, details=None):
+ """Report a failure in test test.
+
+ Only one of error and details should be provided: conceptually there
+ are two separate methods:
+ addFailure(self, test, error)
+ addFailure(self, test, details)
+
+ :param error: Standard unittest positional argument form - an
+ exc_info tuple.
+ :param details: New Testing-in-python drafted API; a dict from string
+ to subunit.Content objects.
+ """
+ self._addOutcome("failure", test, error=error, details=details)
+
+ def _addOutcome(self, outcome, test, error=None, details=None):
+ """Report a failure in test test.
+
+ Only one of error and details should be provided: conceptually there
+ are two separate methods:
+ addOutcome(self, test, error)
+ addOutcome(self, test, details)
+
+ :param outcome: A string describing the outcome - used as the
+ event name in the subunit stream.
+ :param error: Standard unittest positional argument form - an
+ exc_info tuple.
+ :param details: New Testing-in-python drafted API; a dict from string
+ to subunit.Content objects.
+ """
+ self._stream.write("%s: %s" % (outcome, test.id()))
+ if error is None and details is None:
+ raise ValueError
+ if error is not None:
+ self._stream.write(" [\n")
+ for line in self._exc_info_to_string(error, test).splitlines():
+ self._stream.write("%s\n" % line)
+ else:
+ self._write_details(details)
self._stream.write("]\n")
- def addSkip(self, test, reason):
+ def addSkip(self, test, reason=None, details=None):
"""Report a skipped test."""
- self._stream.write("skip: %s [\n" % test.id())
- self._stream.write("%s\n" % reason)
- self._stream.write("]\n")
+ if reason is None:
+ self._addOutcome("skip", test, error=None, details=details)
+ else:
+ self._stream.write("skip: %s [\n" % test.id())
+ self._stream.write("%s\n" % reason)
+ self._stream.write("]\n")
- def addSuccess(self, test):
+ def addSuccess(self, test, details=None):
"""Report a success in a test."""
- self._stream.write("successful: %s\n" % test.id())
+ self._stream.write("successful: %s" % test.id())
+ if not details:
+ self._stream.write("\n")
+ else:
+ self._write_details(details)
+ self._stream.write("]\n")
+ addUnexpectedSuccess = addSuccess
def startTest(self, test):
"""Mark a test as starting its test run."""
@@ -552,14 +679,33 @@ class TestProtocolClient(unittest.TestResult):
time.year, time.month, time.day, time.hour, time.minute,
time.second, time.microsecond))
+ def _write_details(self, details):
+ """Output details to the stream.
+
+ :param details: An extended details dict for a test outcome.
+ """
+ self._stream.write(" [ multipart\n")
+ for name, content in sorted(details.iteritems()):
+ self._stream.write("Content-Type: %s/%s" %
+ (content.content_type.type, content.content_type.subtype))
+ parameters = content.content_type.parameters
+ if parameters:
+ self._stream.write(";")
+ param_strs = []
+ for param, value in parameters.iteritems():
+ param_strs.append("%s=%s" % (param, value))
+ self._stream.write(",".join(param_strs))
+ self._stream.write("\n%s\n" % name)
+ encoder = chunked.Encoder(self._stream)
+ map(encoder.write, content.iter_bytes())
+ encoder.close()
+
def done(self):
"""Obey the testtools result.done() interface."""
def RemoteError(description=""):
- if description == "":
- description = "\n"
- return (RemoteException, RemoteException(description), None)
+ return (_StringException, _StringException(description), None)
class RemotedTestCase(unittest.TestCase):
@@ -937,13 +1083,13 @@ class TestResultStats(unittest.TestResult):
def total_tests(self):
return self.testsRun
- def addError(self, test, err):
+ def addError(self, test, err, details=None):
self.failed_tests += 1
- def addFailure(self, test, err):
+ def addFailure(self, test, err, details=None):
self.failed_tests += 1
- def addSkip(self, test, reason):
+ def addSkip(self, test, reason, details=None):
self.skipped_tests += 1
def formatStats(self):
@@ -965,123 +1111,3 @@ class TestResultStats(unittest.TestResult):
def wasSuccessful(self):
"""Tells whether or not this result was a success"""
return self.failed_tests == 0
-
-
-class TestResultFilter(unittest.TestResult):
- """A pyunit TestResult interface implementation which filters tests.
-
- Tests that pass the filter are handed on to another TestResult instance
- for further processing/reporting. To obtain the filtered results,
- the other instance must be interrogated.
-
- :ivar result: The result that tests are passed to after filtering.
- :ivar filter_predicate: The callback run to decide whether to pass
- a result.
- """
-
- def __init__(self, result, filter_error=False, filter_failure=False,
- filter_success=True, filter_skip=False,
- filter_predicate=None):
- """Create a FilterResult object filtering to result.
-
- :param filter_error: Filter out errors.
- :param filter_failure: Filter out failures.
- :param filter_success: Filter out successful tests.
- :param filter_skip: Filter out skipped tests.
- :param filter_predicate: A callable taking (test, err) and
- returning True if the result should be passed through.
- err is None for success.
- """
- unittest.TestResult.__init__(self)
- self.result = result
- self._filter_error = filter_error
- self._filter_failure = filter_failure
- self._filter_success = filter_success
- self._filter_skip = filter_skip
- if filter_predicate is None:
- filter_predicate = lambda test, err: True
- self.filter_predicate = filter_predicate
- # The current test (for filtering tags)
- self._current_test = None
- # Has the current test been filtered (for outputting test tags)
- self._current_test_filtered = None
- # The (new, gone) tags for the current test.
- self._current_test_tags = None
-
- def addError(self, test, err):
- if not self._filter_error and self.filter_predicate(test, err):
- self.result.startTest(test)
- self.result.addError(test, err)
-
- def addFailure(self, test, err):
- if not self._filter_failure and self.filter_predicate(test, err):
- self.result.startTest(test)
- self.result.addFailure(test, err)
-
- def addSkip(self, test, reason):
- if not self._filter_skip and self.filter_predicate(test, reason):
- self.result.startTest(test)
- # This is duplicated, it would be nice to have on a 'calls
- # TestResults' mixin perhaps.
- addSkip = getattr(self.result, 'addSkip', None)
- if not callable(addSkip):
- self.result.addError(test, RemoteError(reason))
- else:
- self.result.addSkip(test, reason)
-
- def addSuccess(self, test):
- if not self._filter_success and self.filter_predicate(test, None):
- self.result.startTest(test)
- self.result.addSuccess(test)
-
- def startTest(self, test):
- """Start a test.
-
- Not directly passed to the client, but used for handling of tags
- correctly.
- """
- self._current_test = test
- self._current_test_filtered = False
- self._current_test_tags = set(), set()
-
- def stopTest(self, test):
- """Stop a test.
-
- Not directly passed to the client, but used for handling of tags
- correctly.
- """
- if not self._current_test_filtered:
- # Tags to output for this test.
- if self._current_test_tags[0] or self._current_test_tags[1]:
- tags_method = getattr(self.result, 'tags', None)
- if callable(tags_method):
- self.result.tags(*self._current_test_tags)
- self.result.stopTest(test)
- self._current_test = None
- self._current_test_filtered = None
- self._current_test_tags = None
-
- def tags(self, new_tags, gone_tags):
- """Handle tag instructions.
-
- Adds and removes tags as appropriate. If a test is currently running,
- tags are not affected for subsequent tests.
-
- :param new_tags: Tags to add,
- :param gone_tags: Tags to remove.
- """
- if self._current_test is not None:
- # gather the tags until the test stops.
- self._current_test_tags[0].update(new_tags)
- self._current_test_tags[0].difference_update(gone_tags)
- self._current_test_tags[1].update(gone_tags)
- self._current_test_tags[1].difference_update(new_tags)
- tags_method = getattr(self.result, 'tags', None)
- if tags_method is None:
- return
- return tags_method(new_tags, gone_tags)
-
- def id_to_orig_id(self, id):
- if id.startswith("subunit.RemotedTestCase."):
- return id[len("subunit.RemotedTestCase."):]
- return id
diff --git a/python/subunit/chunked.py b/python/subunit/chunked.py
new file mode 100644
index 0000000..82e4b0d
--- /dev/null
+++ b/python/subunit/chunked.py
@@ -0,0 +1,164 @@
+#
+# subunit: extensions to python unittest to get test results from subprocesses.
+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+#
+
+"""Encoder/decoder for http style chunked encoding."""
+
+class Decoder(object):
+ """Decode chunked content to a byte stream."""
+
+ def __init__(self, output):
+ """Create a decoder decoding to output.
+
+ :param output: A file-like object. Bytes written to the Decoder are
+ decoded to strip off the chunking and written to the output.
+ Up to a full write worth of data or a single control line may be
+ buffered (whichever is larger). The close method should be called
+ when no more data is available, to detect short streams; the
+ write method will return none-None when the end of a stream is
+ detected.
+ """
+ self.output = output
+ self.buffered_bytes = []
+ self.state = self._read_length
+ self.body_length = 0
+
+ def close(self):
+ """Close the decoder.
+
+ :raises ValueError: If the stream is incomplete ValueError is raised.
+ """
+ if self.state != self._finished:
+ raise ValueError("incomplete stream")
+
+ def _finished(self):
+ """Finished reading, return any remaining bytes."""
+ if self.buffered_bytes:
+ buffered_bytes = self.buffered_bytes
+ self.buffered_bytes = []
+ return ''.join(buffered_bytes)
+ else:
+ raise ValueError("stream is finished")
+
+ def _read_body(self):
+ """Pass body bytes to the output."""
+ while self.body_length and self.buffered_bytes:
+ if self.body_length >= len(self.buffered_bytes[0]):
+ self.output.write(self.buffered_bytes[0])
+ self.body_length -= len(self.buffered_bytes[0])
+ del self.buffered_bytes[0]
+ # No more data available.
+ if not self.body_length:
+ self.state = self._read_length
+ else:
+ self.output.write(self.buffered_bytes[0][:self.body_length])
+ self.buffered_bytes[0] = \
+ self.buffered_bytes[0][self.body_length:]
+ self.body_length = 0
+ self.state = self._read_length
+ return self.state()
+
+ def _read_length(self):
+ """Try to decode a length from the bytes."""
+ count = -1
+ match_chars = "0123456789abcdefABCDEF\r\n"
+ count_chars = []
+ for bytes in self.buffered_bytes:
+ for byte in bytes:
+ if byte not in match_chars:
+ break
+ count_chars.append(byte)
+ if byte == '\n':
+ break
+ if not count_chars:
+ return
+ if count_chars[-1][-1] != '\n':
+ return
+ count_str = ''.join(count_chars)
+ self.body_length = int(count_str[:-2], 16)
+ excess_bytes = len(count_str)
+ while excess_bytes:
+ if excess_bytes >= len(self.buffered_bytes[0]):
+ excess_bytes -= len(self.buffered_bytes[0])
+ del self.buffered_bytes[0]
+ else:
+ self.buffered_bytes[0] = self.buffered_bytes[0][excess_bytes:]
+ excess_bytes = 0
+ if not self.body_length:
+ self.state = self._finished
+ if not self.buffered_bytes:
+ # May not call into self._finished with no buffered data.
+ return ''
+ else:
+ self.state = self._read_body
+ return self.state()
+
+ def write(self, bytes):
+ """Decode bytes to the output stream.
+
+ :raises ValueError: If the stream has already seen the end of file
+ marker.
+ :returns: None, or the excess bytes beyond the end of file marker.
+ """
+ if bytes:
+ self.buffered_bytes.append(bytes)
+ return self.state()
+
+
+class Encoder(object):
+ """Encode content to a stream using HTTP Chunked coding."""
+
+ def __init__(self, output):
+ """Create an encoder encoding to output.
+
+ :param output: A file-like object. Bytes written to the Encoder
+ will be encoded using HTTP chunking. Small writes may be buffered
+ and the ``close`` method must be called to finish the stream.
+ """
+ self.output = output
+ self.buffered_bytes = []
+ self.buffer_size = 0
+
+ def flush(self, extra_len=0):
+ """Flush the encoder to the output stream.
+
+ :param extra_len: Increase the size of the chunk by this many bytes
+ to allow for a subsequent write.
+ """
+ if not self.buffer_size and not extra_len:
+ return
+ buffered_bytes = self.buffered_bytes
+ buffer_size = self.buffer_size
+ self.buffered_bytes = []
+ self.buffer_size = 0
+ self.output.write("%X\r\n" % (buffer_size + extra_len))
+ if buffer_size:
+ self.output.write(''.join(buffered_bytes))
+ return True
+
+ def write(self, bytes):
+ """Encode bytes to the output stream."""
+ bytes_len = len(bytes)
+ if self.buffer_size + bytes_len >= 65536:
+ self.flush(bytes_len)
+ self.output.write(bytes)
+ else:
+ self.buffered_bytes.append(bytes)
+ self.buffer_size += bytes_len
+
+ def close(self):
+ """Finish the stream. This does not close the output stream."""
+ self.flush()
+ self.output.write("0\r\n")
diff --git a/python/subunit/details.py b/python/subunit/details.py
new file mode 100644
index 0000000..65a0404
--- /dev/null
+++ b/python/subunit/details.py
@@ -0,0 +1,109 @@
+#
+# subunit: extensions to Python unittest to get test results from subprocesses.
+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+#
+
+"""Handlers for outcome details."""
+
+from cStringIO import StringIO
+
+from testtools import content, content_type
+
+import chunked
+
+
+class DetailsParser(object):
+ """Base class/API reference for details parsing."""
+
+
+class SimpleDetailsParser(DetailsParser):
+ """Parser for single-part [] delimited details."""
+
+ def __init__(self, state):
+ self._message = ""
+ self._state = state
+
+ def lineReceived(self, line):
+ if line == "]\n":
+ self._state.endDetails()
+ return
+ if line[0:2] == " ]":
+ # quoted ] start
+ self._message += line[1:]
+ else:
+ self._message += line
+
+ def get_details(self, style=None):
+ result = {}
+ if not style:
+ result['traceback'] = content.Content(
+ content_type.ContentType("text", "x-traceback"),
+ lambda:[self._message])
+ else:
+ if style == 'skip':
+ name = 'reason'
+ else:
+ name = 'message'
+ result[name] = content.Content(
+ content_type.ContentType("text", "plain"),
+ lambda:[self._message])
+ return result
+
+ def get_message(self):
+ return self._message
+
+
+class MultipartDetailsParser(DetailsParser):
+ """Parser for multi-part [] surrounded MIME typed chunked details."""
+
+ def __init__(self, state):
+ self._state = state
+ self._details = {}
+ self._parse_state = self._look_for_content
+
+ def _look_for_content(self, line):
+ if line == "]\n":
+ self._state.endDetails()
+ return
+ # TODO error handling
+ field, value = line[:-1].split(' ', 1)
+ main, sub = value.split('/')
+ self._content_type = content_type.ContentType(main, sub)
+ self._parse_state = self._get_name
+
+ def _get_name(self, line):
+ self._name = line[:-1]
+ self._body = StringIO()
+ self._chunk_parser = chunked.Decoder(self._body)
+ self._parse_state = self._feed_chunks
+
+ def _feed_chunks(self, line):
+ residue = self._chunk_parser.write(line)
+ if residue is not None:
+ # Line based use always ends on no residue.
+ assert residue == ''
+ body = self._body
+ self._details[self._name] = content.Content(
+ self._content_type, lambda:[body.getvalue()])
+ self._chunk_parser.close()
+ self._parse_state = self._look_for_content
+
+ def get_details(self, for_skip=False):
+ return self._details
+
+ def get_message(self):
+ return None
+
+ def lineReceived(self, line):
+ self._parse_state(line)
diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py
index 3904457..4ccc2aa 100644
--- a/python/subunit/test_results.py
+++ b/python/subunit/test_results.py
@@ -19,6 +19,9 @@
import datetime
import iso8601
+import testtools
+
+import subunit
# NOT a TestResult, because we are implementing the interface, not inheriting
@@ -34,68 +37,41 @@ class TestResultDecorator(object):
def __init__(self, decorated):
"""Create a TestResultDecorator forwarding to decorated."""
- self.decorated = decorated
-
- def _call_maybe(self, method_name, fallback, *params):
- """Call method_name on self.decorated, if present.
-
- This is used to guard newer methods which older pythons do not
- support. While newer clients won't call these methods if they don't
- exist, they do exist on the decorator, and thus the decorator has to be
- the one to filter them out.
-
- :param method_name: The name of the method to call.
- :param fallback: If not None, the fallback to call to handle downgrading
- this method. Otherwise when method_name is not available, no
- exception is raised and None is returned.
- :param *params: Parameters to pass to method_name.
- :return: The result of self.decorated.method_name(*params), if it
- exists, and None otherwise.
- """
- method = getattr(self.decorated, method_name, None)
- if method is None:
- if fallback is not None:
- return fallback(*params)
- return
- return method(*params)
+ # Make every decorator degrade gracefully.
+ self.decorated = testtools.ExtendedToOriginalDecorator(decorated)
def startTest(self, test):
return self.decorated.startTest(test)
def startTestRun(self):
- return self._call_maybe("startTestRun", None)
+ return self.decorated.startTestRun()
def stopTest(self, test):
return self.decorated.stopTest(test)
def stopTestRun(self):
- return self._call_maybe("stopTestRun", None)
-
- def addError(self, test, err):
- return self.decorated.addError(test, err)
+ return self.decorated.stopTestRun()
- def addFailure(self, test, err):
- return self.decorated.addFailure(test, err)
+ def addError(self, test, err=None, details=None):
+ return self.decorated.addError(test, err, details=details)
- def addSuccess(self, test):
- return self.decorated.addSuccess(test)
+ def addFailure(self, test, err=None, details=None):
+ return self.decorated.addFailure(test, err, details=details)
- def addSkip(self, test, reason):
- return self._call_maybe("addSkip", self._degrade_skip, test, reason)
+ def addSuccess(self, test, details=None):
+ return self.decorated.addSuccess(test, details=details)
- def _degrade_skip(self, test, reason):
- return self.decorated.addSuccess(test)
+ def addSkip(self, test, reason=None, details=None):
+ return self.decorated.addSkip(test, reason, details=details)
- def addExpectedFailure(self, test, err):
- return self._call_maybe("addExpectedFailure",
- self.decorated.addFailure, test, err)
+ def addExpectedFailure(self, test, err=None, details=None):
+ return self.decorated.addExpectedFailure(test, err, details=details)
- def addUnexpectedSuccess(self, test):
- return self._call_maybe("addUnexpectedSuccess",
- self.decorated.addSuccess, test)
+ def addUnexpectedSuccess(self, test, details=None):
+ return self.decorated.addUnexpectedSuccess(test, details=details)
def progress(self, offset, whence):
- return self._call_maybe("progress", None, offset, whence)
+ return self.decorated.progress(offset, whence)
def wasSuccessful(self):
return self.decorated.wasSuccessful()
@@ -107,8 +83,11 @@ class TestResultDecorator(object):
def stop(self):
return self.decorated.stop()
+ def tags(self, gone_tags, new_tags):
+ return self.decorated.time(gone_tags, new_tags)
+
def time(self, a_datetime):
- return self._call_maybe("time", None, a_datetime)
+ return self.decorated.time(a_datetime)
class HookedTestResultDecorator(TestResultDecorator):
@@ -134,29 +113,29 @@ class HookedTestResultDecorator(TestResultDecorator):
self._before_event()
return self.super.stopTestRun()
- def addError(self, test, err):
+ def addError(self, test, err=None, details=None):
self._before_event()
- return self.super.addError(test, err)
+ return self.super.addError(test, err, details=details)
- def addFailure(self, test, err):
+ def addFailure(self, test, err=None, details=None):
self._before_event()
- return self.super.addFailure(test, err)
+ return self.super.addFailure(test, err, details=details)
- def addSuccess(self, test):
+ def addSuccess(self, test, details=None):
self._before_event()
- return self.super.addSuccess(test)
+ return self.super.addSuccess(test, details=details)
- def addSkip(self, test, reason):
+ def addSkip(self, test, reason=None, details=None):
self._before_event()
- return self.super.addSkip(test, reason)
+ return self.super.addSkip(test, reason, details=details)
- def addExpectedFailure(self, test, err):
+ def addExpectedFailure(self, test, err=None, details=None):
self._before_event()
- return self.super.addExpectedFailure(test, err)
+ return self.super.addExpectedFailure(test, err, details=details)
- def addUnexpectedSuccess(self, test):
+ def addUnexpectedSuccess(self, test, details=None):
self._before_event()
- return self.super.addUnexpectedSuccess(test)
+ return self.super.addUnexpectedSuccess(test, details=details)
def progress(self, offset, whence):
self._before_event()
@@ -197,10 +176,10 @@ class AutoTimingTestResultDecorator(HookedTestResultDecorator):
if time is not None:
return
time = datetime.datetime.utcnow().replace(tzinfo=iso8601.Utc())
- self._call_maybe("time", None, time)
+ self.decorated.time(time)
def progress(self, offset, whence):
- return self._call_maybe("progress", None, offset, whence)
+ return self.decorated.progress(offset, whence)
@property
def shouldStop(self):
@@ -215,4 +194,141 @@ class AutoTimingTestResultDecorator(HookedTestResultDecorator):
result object and disable automatic timestamps.
"""
self._time = a_datetime
- return self._call_maybe("time", None, a_datetime)
+ return self.decorated.time(a_datetime)
+
+
+class TestResultFilter(TestResultDecorator):
+ """A pyunit TestResult interface implementation which filters tests.
+
+ Tests that pass the filter are handed on to another TestResult instance
+ for further processing/reporting. To obtain the filtered results,
+ the other instance must be interrogated.
+
+ :ivar result: The result that tests are passed to after filtering.
+ :ivar filter_predicate: The callback run to decide whether to pass
+ a result.
+ """
+
+ def __init__(self, result, filter_error=False, filter_failure=False,
+ filter_success=True, filter_skip=False,
+ filter_predicate=None):
+ """Create a FilterResult object filtering to result.
+
+ :param filter_error: Filter out errors.
+ :param filter_failure: Filter out failures.
+ :param filter_success: Filter out successful tests.
+ :param filter_skip: Filter out skipped tests.
+ :param filter_predicate: A callable taking (test, outcome, err,
+ details) and returning True if the result should be passed
+ through. err and details may be none if no error or extra
+ metadata is available. outcome is the name of the outcome such
+ as 'success' or 'failure'.
+ """
+ TestResultDecorator.__init__(self, result)
+ self._filter_error = filter_error
+ self._filter_failure = filter_failure
+ self._filter_success = filter_success
+ self._filter_skip = filter_skip
+ if filter_predicate is None:
+ filter_predicate = lambda test, outcome, err, details: True
+ self.filter_predicate = filter_predicate
+ # The current test (for filtering tags)
+ self._current_test = None
+ # Has the current test been filtered (for outputting test tags)
+ self._current_test_filtered = None
+ # The (new, gone) tags for the current test.
+ self._current_test_tags = None
+
+ def addError(self, test, err=None, details=None):
+ if (not self._filter_error and
+ self.filter_predicate(test, 'error', err, details)):
+ self.decorated.startTest(test)
+ self.decorated.addError(test, err, details=details)
+ else:
+ self._filtered()
+
+ def addFailure(self, test, err=None, details=None):
+ if (not self._filter_failure and
+ self.filter_predicate(test, 'failure', err, details)):
+ self.decorated.startTest(test)
+ self.decorated.addFailure(test, err, details=details)
+ else:
+ self._filtered()
+
+ def addSkip(self, test, reason=None, details=None):
+ if (not self._filter_skip and
+ self.filter_predicate(test, 'skip', reason, details)):
+ self.decorated.startTest(test)
+ self.decorated.addSkip(test, reason, details=details)
+ else:
+ self._filtered()
+
+ def addSuccess(self, test, details=None):
+ if (not self._filter_success and
+ self.filter_predicate(test, 'success', None, details)):
+ self.decorated.startTest(test)
+ self.decorated.addSuccess(test, details=details)
+ else:
+ self._filtered()
+
+ def addExpectedFailure(self, test, err=None, details=None):
+ if self.filter_predicate(test, 'expectedfailure', err, details):
+ self.decorated.startTest(test)
+ return self.decorated.addExpectedFailure(test, err,
+ details=details)
+ else:
+ self._filtered()
+
+ def addUnexpectedSuccess(self, test, details=None):
+ self.decorated.startTest(test)
+ return self.decorated.addUnexpectedSuccess(test, details=details)
+
+ def _filtered(self):
+ self._current_test_filtered = True
+
+ def startTest(self, test):
+ """Start a test.
+
+ Not directly passed to the client, but used for handling of tags
+ correctly.
+ """
+ self._current_test = test
+ self._current_test_filtered = False
+ self._current_test_tags = set(), set()
+
+ def stopTest(self, test):
+ """Stop a test.
+
+ Not directly passed to the client, but used for handling of tags
+ correctly.
+ """
+ if not self._current_test_filtered:
+ # Tags to output for this test.
+ if self._current_test_tags[0] or self._current_test_tags[1]:
+ self.decorated.tags(*self._current_test_tags)
+ self.decorated.stopTest(test)
+ self._current_test = None
+ self._current_test_filtered = None
+ self._current_test_tags = None
+
+ def tags(self, new_tags, gone_tags):
+ """Handle tag instructions.
+
+ Adds and removes tags as appropriate. If a test is currently running,
+ tags are not affected for subsequent tests.
+
+ :param new_tags: Tags to add,
+ :param gone_tags: Tags to remove.
+ """
+ if self._current_test is not None:
+ # gather the tags until the test stops.
+ self._current_test_tags[0].update(new_tags)
+ self._current_test_tags[0].difference_update(gone_tags)
+ self._current_test_tags[1].update(gone_tags)
+ self._current_test_tags[1].difference_update(new_tags)
+ return self.decorated.tags(new_tags, gone_tags)
+
+ def id_to_orig_id(self, id):
+ if id.startswith("subunit.RemotedTestCase."):
+ return id[len("subunit.RemotedTestCase."):]
+ return id
diff --git a/python/subunit/tests/__init__.py b/python/subunit/tests/__init__.py
index 36e93ba..a78cec8 100644
--- a/python/subunit/tests/__init__.py
+++ b/python/subunit/tests/__init__.py
@@ -16,6 +16,8 @@
from subunit.tests import (
TestUtil,
+ test_chunked,
+ test_details,
test_progress_model,
test_subunit_filter,
test_subunit_stats,
@@ -27,6 +29,8 @@ from subunit.tests import (
def test_suite():
result = TestUtil.TestSuite()
+ result.addTest(test_chunked.test_suite())
+ result.addTest(test_details.test_suite())
result.addTest(test_progress_model.test_suite())
result.addTest(test_test_results.test_suite())
result.addTest(test_test_protocol.test_suite())
diff --git a/python/subunit/tests/test_chunked.py b/python/subunit/tests/test_chunked.py
new file mode 100644
index 0000000..a24e31e
--- /dev/null
+++ b/python/subunit/tests/test_chunked.py
@@ -0,0 +1,127 @@
+#
+# subunit: extensions to python unittest to get test results from subprocesses.
+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+#
+
+from cStringIO import StringIO
+import unittest
+
+import subunit.chunked
+
+
+def test_suite():
+ loader = subunit.tests.TestUtil.TestLoader()
+ result = loader.loadTestsFromName(__name__)
+ return result
+
+
+class TestDecode(unittest.TestCase):
+
+ def setUp(self):
+ unittest.TestCase.setUp(self)
+ self.output = StringIO()
+ self.decoder = subunit.chunked.Decoder(self.output)
+
+ def test_close_read_length_short_errors(self):
+ self.assertRaises(ValueError, self.decoder.close)
+
+ def test_close_body_short_errors(self):
+ self.assertEqual(None, self.decoder.write('2\r\na'))
+ self.assertRaises(ValueError, self.decoder.close)
+
+ def test_close_body_buffered_data_errors(self):
+ self.assertEqual(None, self.decoder.write('2\r'))
+ self.assertRaises(ValueError, self.decoder.close)
+
+ def test_close_after_finished_stream_safe(self):
+ self.assertEqual(None, self.decoder.write('2\r\nab'))
+ self.assertEqual('', self.decoder.write('0\r\n'))
+ self.decoder.close()
+
+ def test_decode_nothing(self):
+ self.assertEqual('', self.decoder.write('0\r\n'))
+ self.assertEqual('', self.output.getvalue())
+
+ def test_decode_serialised_form(self):
+ self.assertEqual(None, self.decoder.write("F\r\n"))
+ self.assertEqual(None, self.decoder.write("serialised\n"))
+ self.assertEqual('', self.decoder.write("form0\r\n"))
+
+ def test_decode_short(self):
+ self.assertEqual('', self.decoder.write('3\r\nabc0\r\n'))
+ self.assertEqual('abc', self.output.getvalue())
+
+ def test_decode_combines_short(self):
+ self.assertEqual('', self.decoder.write('6\r\nabcdef0\r\n'))
+ self.assertEqual('abcdef', self.output.getvalue())
+
+ def test_decode_excess_bytes_from_write(self):
+ self.assertEqual('1234', self.decoder.write('3\r\nabc0\r\n1234'))
+ self.assertEqual('abc', self.output.getvalue())
+
+ def test_decode_write_after_finished_errors(self):
+ self.assertEqual('1234', self.decoder.write('3\r\nabc0\r\n1234'))
+ self.assertRaises(ValueError, self.decoder.write, '')
+
+ def test_decode_hex(self):
+ self.assertEqual('', self.decoder.write('A\r\n12345678900\r\n'))
+ self.assertEqual('1234567890', self.output.getvalue())
+
+ def test_decode_long_ranges(self):
+ self.assertEqual(None, self.decoder.write('10000\r\n'))
+ self.assertEqual(None, self.decoder.write('1' * 65536))
+ self.assertEqual(None, self.decoder.write('10000\r\n'))
+ self.assertEqual(None, self.decoder.write('2' * 65536))
+ self.assertEqual('', self.decoder.write('0\r\n'))
+ self.assertEqual('1' * 65536 + '2' * 65536, self.output.getvalue())
+
+
+class TestEncode(unittest.TestCase):
+
+ def setUp(self):
+ unittest.TestCase.setUp(self)
+ self.output = StringIO()
+ self.encoder = subunit.chunked.Encoder(self.output)
+
+ def test_encode_nothing(self):
+ self.encoder.close()
+ self.assertEqual('0\r\n', self.output.getvalue())
+
+ def test_encode_empty(self):
+ self.encoder.write('')
+ self.encoder.close()
+ self.assertEqual('0\r\n', self.output.getvalue())
+
+ def test_encode_short(self):
+ self.encoder.write('abc')
+ self.encoder.close()
+ self.assertEqual('3\r\nabc0\r\n', self.output.getvalue())
+
+ def test_encode_combines_short(self):
+ self.encoder.write('abc')
+ self.encoder.write('def')
+ self.encoder.close()
+ self.assertEqual('6\r\nabcdef0\r\n', self.output.getvalue())
+
+ def test_encode_over_9_is_in_hex(self):
+ self.encoder.write('1234567890')
+ self.encoder.close()
+ self.assertEqual('A\r\n12345678900\r\n', self.output.getvalue())
+
+ def test_encode_long_ranges_not_combined(self):
+ self.encoder.write('1' * 65536)
+ self.encoder.write('2' * 65536)
+ self.encoder.close()
+ self.assertEqual('10000\r\n' + '1' * 65536 + '10000\r\n' +
+ '2' * 65536 + '0\r\n', self.output.getvalue())
diff --git a/python/subunit/tests/test_details.py b/python/subunit/tests/test_details.py
new file mode 100644
index 0000000..2700d4a
--- /dev/null
+++ b/python/subunit/tests/test_details.py
@@ -0,0 +1,110 @@
+#
+# subunit: extensions to python unittest to get test results from subprocesses.
+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+#
+
+from cStringIO import StringIO
+import unittest
+
+import subunit.tests
+from subunit import content, content_type, details
+
+
+def test_suite():
+ loader = subunit.tests.TestUtil.TestLoader()
+ result = loader.loadTestsFromName(__name__)
+ return result
+
+
+class TestSimpleDetails(unittest.TestCase):
+
+ def test_lineReceived(self):
+ parser = details.SimpleDetailsParser(None)
+ parser.lineReceived("foo\n")
+ parser.lineReceived("bar\n")
+ self.assertEqual("foo\nbar\n", parser._message)
+
+ def test_lineReceived_escaped_bracket(self):
+ parser = details.SimpleDetailsParser(None)
+ parser.lineReceived("foo\n")
+ parser.lineReceived(" ]are\n")
+ parser.lineReceived("bar\n")
+ self.assertEqual("foo\n]are\nbar\n", parser._message)
+
+ def test_get_message(self):
+ parser = details.SimpleDetailsParser(None)
+ self.assertEqual("", parser.get_message())
+
+ def test_get_details(self):
+ parser = details.SimpleDetailsParser(None)
+ traceback = ""
+ expected = {}
+ expected['traceback'] = content.Content(
+ content_type.ContentType("text", "x-traceback"),
+ lambda:[""])
+ found = parser.get_details()
+ self.assertEqual(expected.keys(), found.keys())
+ self.assertEqual(expected['traceback'].content_type,
+ found['traceback'].content_type)
+ self.assertEqual(''.join(expected['traceback'].iter_bytes()),
+ ''.join(found['traceback'].iter_bytes()))
+
+ def test_get_details_skip(self):
+ parser = details.SimpleDetailsParser(None)
+ traceback = ""
+ expected = {}
+ expected['reason'] = content.Content(
+ content_type.ContentType("text", "plain"),
+ lambda:[""])
+ found = parser.get_details("skip")
+ self.assertEqual(expected, found)
+
+ def test_get_details_success(self):
+ parser = details.SimpleDetailsParser(None)
+ traceback = ""
+ expected = {}
+ expected['message'] = content.Content(
+ content_type.ContentType("text", "plain"),
+ lambda:[""])
+ found = parser.get_details("success")
+ self.assertEqual(expected, found)
+
+
+class TestMultipartDetails(unittest.TestCase):
+
+ def test_get_message_is_None(self):
+ parser = details.MultipartDetailsParser(None)
+ self.assertEqual(None, parser.get_message())
+
+ def test_get_details(self):
+ parser = details.MultipartDetailsParser(None)
+ self.assertEqual({}, parser.get_details())
+
+ def test_parts(self):
+ parser = details.MultipartDetailsParser(None)
+ parser.lineReceived("Content-Type: text/plain\n")
+ parser.lineReceived("something\n")
+ parser.lineReceived("F\r\n")
+ parser.lineReceived("serialised\n")
+ parser.lineReceived("form0\r\n")
+ expected = {}
+ expected['something'] = content.Content(
+ content_type.ContentType("text", "plain"),
+ lambda:["serialised\nform"])
+ found = parser.get_details()
+ self.assertEqual(expected.keys(), found.keys())
+ self.assertEqual(expected['something'].content_type,
+ found['something'].content_type)
+ self.assertEqual(''.join(expected['something'].iter_bytes()),
+ ''.join(found['something'].iter_bytes()))
diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py
index cc13b6c..3c65ed3 100644
--- a/python/subunit/tests/test_subunit_filter.py
+++ b/python/subunit/tests/test_subunit_filter.py
@@ -20,6 +20,7 @@ import unittest
from StringIO import StringIO
import subunit
+from subunit.test_results import TestResultFilter
class TestTestResultFilter(unittest.TestCase):
@@ -31,57 +32,56 @@ class TestTestResultFilter(unittest.TestCase):
def test_default(self):
"""The default is to exclude success and include everything else."""
self.filtered_result = unittest.TestResult()
- self.filter = subunit.TestResultFilter(self.filtered_result)
+ self.filter = TestResultFilter(self.filtered_result)
self.run_tests()
- # skips are seen as errors by default python TestResult.
- self.assertEqual(['error', 'skipped'],
+ # skips are seen as success by default python TestResult.
+ self.assertEqual(['error'],
[error[0].id() for error in self.filtered_result.errors])
self.assertEqual(['failed'],
[failure[0].id() for failure in
self.filtered_result.failures])
- self.assertEqual(3, self.filtered_result.testsRun)
+ self.assertEqual(4, self.filtered_result.testsRun)
def test_exclude_errors(self):
self.filtered_result = unittest.TestResult()
- self.filter = subunit.TestResultFilter(self.filtered_result,
+ self.filter = TestResultFilter(self.filtered_result,
filter_error=True)
self.run_tests()
# skips are seen as errors by default python TestResult.
- self.assertEqual(['skipped'],
- [error[0].id() for error in self.filtered_result.errors])
+ self.assertEqual([], self.filtered_result.errors)
self.assertEqual(['failed'],
[failure[0].id() for failure in
self.filtered_result.failures])
- self.assertEqual(2, self.filtered_result.testsRun)
+ self.assertEqual(3, self.filtered_result.testsRun)
def test_exclude_failure(self):
self.filtered_result = unittest.TestResult()
- self.filter = subunit.TestResultFilter(self.filtered_result,
+ self.filter = TestResultFilter(self.filtered_result,
filter_failure=True)
self.run_tests()
- self.assertEqual(['error', 'skipped'],
+ self.assertEqual(['error'],
[error[0].id() for error in self.filtered_result.errors])
self.assertEqual([],
[failure[0].id() for failure in
self.filtered_result.failures])
- self.assertEqual(2, self.filtered_result.testsRun)
+ self.assertEqual(3, self.filtered_result.testsRun)
def test_exclude_skips(self):
self.filtered_result = subunit.TestResultStats(None)
- self.filter = subunit.TestResultFilter(self.filtered_result,
+ self.filter = TestResultFilter(self.filtered_result,
filter_skip=True)
self.run_tests()
self.assertEqual(0, self.filtered_result.skipped_tests)
self.assertEqual(2, self.filtered_result.failed_tests)
- self.assertEqual(2, self.filtered_result.testsRun)
+ self.assertEqual(3, self.filtered_result.testsRun)
def test_include_success(self):
"""Success's can be included if requested."""
self.filtered_result = unittest.TestResult()
- self.filter = subunit.TestResultFilter(self.filtered_result,
+ self.filter = TestResultFilter(self.filtered_result,
filter_success=False)
self.run_tests()
- self.assertEqual(['error', 'skipped'],
+ self.assertEqual(['error'],
[error[0].id() for error in self.filtered_result.errors])
self.assertEqual(['failed'],
[failure[0].id() for failure in
@@ -91,15 +91,14 @@ class TestTestResultFilter(unittest.TestCase):
def test_filter_predicate(self):
"""You can filter by predicate callbacks"""
self.filtered_result = unittest.TestResult()
- filter_cb = lambda test, err: str(err).find('error details') != -1
- self.filter = subunit.TestResultFilter(self.filtered_result,
+ def filter_cb(test, outcome, err, details):
+ return outcome == 'success'
+ self.filter = TestResultFilter(self.filtered_result,
filter_predicate=filter_cb,
filter_success=False)
self.run_tests()
- self.assertEqual(1,
- self.filtered_result.testsRun)
- # I'd like to test filtering the xfail but it's blocked by
- # https://bugs.edge.launchpad.net/subunit/+bug/409193 -- mbp 20090805
+ # Only success should pass
+ self.assertEqual(1, self.filtered_result.testsRun)
def run_tests(self):
self.setUpTestStream()
@@ -109,8 +108,9 @@ class TestTestResultFilter(unittest.TestCase):
def setUpTestStream(self):
# While TestResultFilter works on python objects, using a subunit
# stream is an easy pithy way of getting a series of test objects to
- # call into the TestResult, and as TestResultFilter is intended for use
- # with subunit also has the benefit of detecting any interface skew issues.
+ # call into the TestResult, and as TestResultFilter is intended for
+ # use with subunit also has the benefit of detecting any interface
+ # skew issues.
self.input_stream = StringIO()
self.input_stream.write("""tags: global
test passed
diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py
index 01678b1..9e9db18 100644
--- a/python/subunit/tests/test_test_protocol.py
+++ b/python/subunit/tests/test_test_protocol.py
@@ -18,129 +18,19 @@ import datetime
import unittest
from StringIO import StringIO
import os
-import subunit
import sys
-import subunit.iso8601 as iso8601
-
-
-class MockTestProtocolServerClient(object):
- """A mock protocol server client to test callbacks.
-
- Note that this is deliberately not Python 2.7 complete, to allow
- testing compatibility - we need a TestResult that will not have new methods
- like addExpectedFailure.
- """
-
- def __init__(self):
- self.end_calls = []
- self.error_calls = []
- self.failure_calls = []
- self.skip_calls = []
- self.start_calls = []
- self.success_calls = []
- self.progress_calls = []
- self._time = None
- super(MockTestProtocolServerClient, self).__init__()
-
- def addError(self, test, error):
- self.error_calls.append((test, error))
-
- def addFailure(self, test, error):
- self.failure_calls.append((test, error))
-
- def addSkip(self, test, reason):
- self.skip_calls.append((test, reason))
-
- def addSuccess(self, test):
- self.success_calls.append(test)
-
- def stopTest(self, test):
- self.end_calls.append(test)
-
- def startTest(self, test):
- self.start_calls.append(test)
-
- def progress(self, offset, whence):
- self.progress_calls.append((offset, whence))
-
- def time(self, time):
- self._time = time
-
-
-class MockExtendedTestProtocolServerClient(MockTestProtocolServerClient):
- """An extended TestResult for testing which implements tags() etc."""
-
- def __init__(self):
- MockTestProtocolServerClient.__init__(self)
- self.new_tags = set()
- self.gone_tags = set()
-
- def tags(self, new_tags, gone_tags):
- self.new_tags = new_tags
- self.gone_tags = gone_tags
-
-
-class TestMockTestProtocolServer(unittest.TestCase):
-
- def test_start_test(self):
- protocol = MockTestProtocolServerClient()
- protocol.startTest(subunit.RemotedTestCase("test old mcdonald"))
- self.assertEqual(protocol.start_calls,
- [subunit.RemotedTestCase("test old mcdonald")])
- self.assertEqual(protocol.end_calls, [])
- self.assertEqual(protocol.error_calls, [])
- self.assertEqual(protocol.failure_calls, [])
- self.assertEqual(protocol.success_calls, [])
-
- def test_add_error(self):
- protocol = MockTestProtocolServerClient()
- protocol.addError(subunit.RemotedTestCase("old mcdonald"),
- subunit.RemoteError("omg it works"))
- self.assertEqual(protocol.start_calls, [])
- self.assertEqual(protocol.end_calls, [])
- self.assertEqual(protocol.error_calls, [(
- subunit.RemotedTestCase("old mcdonald"),
- subunit.RemoteError("omg it works"))])
- self.assertEqual(protocol.failure_calls, [])
- self.assertEqual(protocol.success_calls, [])
-
- def test_add_failure(self):
- protocol = MockTestProtocolServerClient()
- protocol.addFailure(subunit.RemotedTestCase("old mcdonald"),
- subunit.RemoteError("omg it works"))
- self.assertEqual(protocol.start_calls, [])
- self.assertEqual(protocol.end_calls, [])
- self.assertEqual(protocol.error_calls, [])
- self.assertEqual(protocol.failure_calls, [
- (subunit.RemotedTestCase("old mcdonald"),
- subunit.RemoteError("omg it works"))])
- self.assertEqual(protocol.success_calls, [])
+from testtools.content import Content, TracebackContent
+from testtools.content_type import ContentType
+from testtools.tests.helpers import (
+ Python26TestResult,
+ Python27TestResult,
+ ExtendedTestResult,
+ )
- def test_add_success(self):
- protocol = MockTestProtocolServerClient()
- protocol.addSuccess(subunit.RemotedTestCase("test old mcdonald"))
- self.assertEqual(protocol.start_calls, [])
- self.assertEqual(protocol.end_calls, [])
- self.assertEqual(protocol.error_calls, [])
- self.assertEqual(protocol.failure_calls, [])
- self.assertEqual(protocol.success_calls,
- [subunit.RemotedTestCase("test old mcdonald")])
-
- def test_end_test(self):
- protocol = MockTestProtocolServerClient()
- protocol.stopTest(subunit.RemotedTestCase("test old mcdonald"))
- self.assertEqual(protocol.end_calls,
- [subunit.RemotedTestCase("test old mcdonald")])
- self.assertEqual(protocol.error_calls, [])
- self.assertEqual(protocol.failure_calls, [])
- self.assertEqual(protocol.success_calls, [])
- self.assertEqual(protocol.start_calls, [])
-
- def test_progress(self):
- protocol = MockTestProtocolServerClient()
- protocol.progress(-1, subunit.PROGRESS_CUR)
- self.assertEqual(protocol.progress_calls, [(-1, subunit.PROGRESS_CUR)])
+import subunit
+from subunit import _remote_exception_str
+import subunit.iso8601 as iso8601
class TestTestImports(unittest.TestCase):
@@ -204,38 +94,40 @@ class TestTestProtocolServerPipe(unittest.TestCase):
bing = subunit.RemotedTestCase("bing crosby")
an_error = subunit.RemotedTestCase("an error")
self.assertEqual(client.errors,
- [(an_error, 'RemoteException: \n\n')])
+ [(an_error, _remote_exception_str + '\n')])
self.assertEqual(
client.failures,
- [(bing, "RemoteException: foo.c:53:ERROR invalid state\n\n")])
+ [(bing, _remote_exception_str + ": Text attachment: traceback\n"
+ "------------\nfoo.c:53:ERROR invalid state\n"
+ "------------\n\n")])
self.assertEqual(client.testsRun, 3)
class TestTestProtocolServerStartTest(unittest.TestCase):
def setUp(self):
- self.client = MockTestProtocolServerClient()
+ self.client = Python26TestResult()
self.protocol = subunit.TestProtocolServer(self.client)
def test_start_test(self):
self.protocol.lineReceived("test old mcdonald\n")
- self.assertEqual(self.client.start_calls,
- [subunit.RemotedTestCase("old mcdonald")])
+ self.assertEqual(self.client._events,
+ [('startTest', subunit.RemotedTestCase("old mcdonald"))])
def test_start_testing(self):
self.protocol.lineReceived("testing old mcdonald\n")
- self.assertEqual(self.client.start_calls,
- [subunit.RemotedTestCase("old mcdonald")])
+ self.assertEqual(self.client._events,
+ [('startTest', subunit.RemotedTestCase("old mcdonald"))])
def test_start_test_colon(self):
self.protocol.lineReceived("test: old mcdonald\n")
- self.assertEqual(self.client.start_calls,
- [subunit.RemotedTestCase("old mcdonald")])
+ self.assertEqual(self.client._events,
+ [('startTest', subunit.RemotedTestCase("old mcdonald"))])
def test_start_testing_colon(self):
self.protocol.lineReceived("testing: old mcdonald\n")
- self.assertEqual(self.client.start_calls,
- [subunit.RemotedTestCase("old mcdonald")])
+ self.assertEqual(self.client._events,
+ [('startTest', subunit.RemotedTestCase("old mcdonald"))])
class TestTestProtocolServerPassThrough(unittest.TestCase):
@@ -243,7 +135,7 @@ class TestTestProtocolServerPassThrough(unittest.TestCase):
def setUp(self):
self.stdout = StringIO()
self.test = subunit.RemotedTestCase("old mcdonald")
- self.client = MockTestProtocolServerClient()
+ self.client = ExtendedTestResult()
self.protocol = subunit.TestProtocolServer(self.client, self.stdout)
def keywords_before_test(self):
@@ -268,42 +160,37 @@ class TestTestProtocolServerPassThrough(unittest.TestCase):
def test_keywords_before_test(self):
self.keywords_before_test()
- self.assertEqual(self.client.start_calls, [])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [])
+ self.assertEqual(self.client._events, [])
def test_keywords_after_error(self):
self.protocol.lineReceived("test old mcdonald\n")
self.protocol.lineReceived("error old mcdonald\n")
self.keywords_before_test()
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls,
- [(self.test, subunit.RemoteError(""))])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [])
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addError', self.test, {}),
+ ('stopTest', self.test),
+ ], self.client._events)
def test_keywords_after_failure(self):
self.protocol.lineReceived("test old mcdonald\n")
self.protocol.lineReceived("failure old mcdonald\n")
self.keywords_before_test()
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls,
- [(self.test, subunit.RemoteError())])
- self.assertEqual(self.client.success_calls, [])
+ self.assertEqual(self.client._events, [
+ ('startTest', self.test),
+ ('addFailure', self.test, {}),
+ ('stopTest', self.test),
+ ])
def test_keywords_after_success(self):
self.protocol.lineReceived("test old mcdonald\n")
self.protocol.lineReceived("success old mcdonald\n")
self.keywords_before_test()
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [self.test])
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addSuccess', self.test),
+ ('stopTest', self.test),
+ ], self.client._events)
def test_keywords_after_test(self):
self.protocol.lineReceived("test old mcdonald\n")
@@ -328,14 +215,15 @@ class TestTestProtocolServerPassThrough(unittest.TestCase):
"successful a\n"
"successful: a\n"
"]\n")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.failure_calls,
- [(self.test, subunit.RemoteError())])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.success_calls, [])
+ self.assertEqual(self.client._events, [
+ ('startTest', self.test),
+ ('addFailure', self.test, {}),
+ ('stopTest', self.test),
+ ])
def test_keywords_during_failure(self):
+ # A smoke test to make sure that the details parsers have control
+ # appropriately.
self.protocol.lineReceived("test old mcdonald\n")
self.protocol.lineReceived("failure: old mcdonald [\n")
self.protocol.lineReceived("test old mcdonald\n")
@@ -350,21 +238,24 @@ class TestTestProtocolServerPassThrough(unittest.TestCase):
self.protocol.lineReceived(" ]\n")
self.protocol.lineReceived("]\n")
self.assertEqual(self.stdout.getvalue(), "")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.failure_calls,
- [(self.test, subunit.RemoteError("test old mcdonald\n"
- "failure a\n"
- "failure: a\n"
- "error a\n"
- "error: a\n"
- "success a\n"
- "success: a\n"
- "successful a\n"
- "successful: a\n"
- "]\n"))])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.success_calls, [])
+ details = {}
+ details['traceback'] = Content(ContentType("text", "x-traceback"),
+ lambda:[
+ "test old mcdonald\n"
+ "failure a\n"
+ "failure: a\n"
+ "error a\n"
+ "error: a\n"
+ "success a\n"
+ "success: a\n"
+ "successful a\n"
+ "successful: a\n"
+ "]\n"])
+ self.assertEqual(self.client._events, [
+ ('startTest', self.test),
+ ('addFailure', self.test, details),
+ ('stopTest', self.test),
+ ])
def test_stdout_passthrough(self):
"""Lines received which cannot be interpreted as any protocol action
@@ -378,139 +269,133 @@ class TestTestProtocolServerPassThrough(unittest.TestCase):
class TestTestProtocolServerLostConnection(unittest.TestCase):
def setUp(self):
- self.client = MockTestProtocolServerClient()
+ self.client = Python26TestResult()
self.protocol = subunit.TestProtocolServer(self.client)
self.test = subunit.RemotedTestCase("old mcdonald")
def test_lost_connection_no_input(self):
self.protocol.lostConnection()
- self.assertEqual(self.client.start_calls, [])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [])
+ self.assertEqual([], self.client._events)
def test_lost_connection_after_start(self):
self.protocol.lineReceived("test old mcdonald\n")
self.protocol.lostConnection()
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [
- (self.test, subunit.RemoteError("lost connection during "
- "test 'old mcdonald'"))])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [])
+ failure = subunit.RemoteError(
+ "lost connection during test 'old mcdonald'")
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addError', self.test, failure),
+ ('stopTest', self.test),
+ ], self.client._events)
def test_lost_connected_after_error(self):
self.protocol.lineReceived("test old mcdonald\n")
self.protocol.lineReceived("error old mcdonald\n")
self.protocol.lostConnection()
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [
- (self.test, subunit.RemoteError(""))])
- self.assertEqual(self.client.success_calls, [])
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addError', self.test, subunit.RemoteError("")),
+ ('stopTest', self.test),
+ ], self.client._events)
- def test_lost_connection_during_error(self):
+ def do_connection_lost(self, outcome, opening):
self.protocol.lineReceived("test old mcdonald\n")
- self.protocol.lineReceived("error old mcdonald [\n")
+ self.protocol.lineReceived("%s old mcdonald %s" % (outcome, opening))
self.protocol.lostConnection()
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [
- (self.test, subunit.RemoteError("lost connection during error "
- "report of test 'old mcdonald'"))])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [])
+ failure = subunit.RemoteError(
+ "lost connection during %s report of test 'old mcdonald'" %
+ outcome)
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addError', self.test, failure),
+ ('stopTest', self.test),
+ ], self.client._events)
+
+ def test_lost_connection_during_error(self):
+ self.do_connection_lost("error", "[\n")
+
+ def test_lost_connection_during_error_details(self):
+ self.do_connection_lost("error", "[ multipart\n")
def test_lost_connected_after_failure(self):
self.protocol.lineReceived("test old mcdonald\n")
self.protocol.lineReceived("failure old mcdonald\n")
self.protocol.lostConnection()
- test = subunit.RemotedTestCase("old mcdonald")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls,
- [(self.test, subunit.RemoteError())])
- self.assertEqual(self.client.success_calls, [])
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addFailure', self.test, subunit.RemoteError("")),
+ ('stopTest', self.test),
+ ], self.client._events)
def test_lost_connection_during_failure(self):
- self.protocol.lineReceived("test old mcdonald\n")
- self.protocol.lineReceived("failure old mcdonald [\n")
- self.protocol.lostConnection()
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls,
- [(self.test,
- subunit.RemoteError("lost connection during "
- "failure report"
- " of test 'old mcdonald'"))])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [])
+ self.do_connection_lost("failure", "[\n")
+
+ def test_lost_connection_during_failure_details(self):
+ self.do_connection_lost("failure", "[ multipart\n")
def test_lost_connection_after_success(self):
self.protocol.lineReceived("test old mcdonald\n")
self.protocol.lineReceived("success old mcdonald\n")
self.protocol.lostConnection()
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [self.test])
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addSuccess', self.test),
+ ('stopTest', self.test),
+ ], self.client._events)
+
+ def test_lost_connection_during_success(self):
+ self.do_connection_lost("success", "[\n")
+
+ def test_lost_connection_during_success_details(self):
+ self.do_connection_lost("success", "[ multipart\n")
def test_lost_connection_during_skip(self):
- self.protocol.lineReceived("test old mcdonald\n")
- self.protocol.lineReceived("skip old mcdonald [\n")
- self.protocol.lostConnection()
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [
- (self.test, subunit.RemoteError("lost connection during skip "
- "report of test 'old mcdonald'"))])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [])
+ self.do_connection_lost("skip", "[\n")
+
+ def test_lost_connection_during_skip_details(self):
+ self.do_connection_lost("skip", "[ multipart\n")
def test_lost_connection_during_xfail(self):
- self.protocol.lineReceived("test old mcdonald\n")
- self.protocol.lineReceived("xfail old mcdonald [\n")
- self.protocol.lostConnection()
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [
- (self.test, subunit.RemoteError("lost connection during xfail "
- "report of test 'old mcdonald'"))])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [])
+ self.do_connection_lost("xfail", "[\n")
- def test_lost_connection_during_success(self):
- self.protocol.lineReceived("test old mcdonald\n")
- self.protocol.lineReceived("success old mcdonald [\n")
- self.protocol.lostConnection()
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [
- (self.test, subunit.RemoteError("lost connection during success "
- "report of test 'old mcdonald'"))])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [])
+ def test_lost_connection_during_xfail_details(self):
+ self.do_connection_lost("xfail", "[ multipart\n")
+
+
+class TestInTestMultipart(unittest.TestCase):
+
+ def setUp(self):
+ self.client = ExtendedTestResult()
+ self.protocol = subunit.TestProtocolServer(self.client)
+ self.protocol.lineReceived("test mcdonalds farm\n")
+ self.test = subunit.RemotedTestCase("mcdonalds farm")
+
+ def test__outcome_sets_details_parser(self):
+ self.protocol._reading_success_details.details_parser = None
+ self.protocol._state._outcome(0, "mcdonalds farm [ multipart\n",
+ None, self.protocol._reading_success_details)
+ parser = self.protocol._reading_success_details.details_parser
+ self.assertNotEqual(None, parser)
+ self.assertTrue(isinstance(parser,
+ subunit.details.MultipartDetailsParser))
class TestTestProtocolServerAddError(unittest.TestCase):
def setUp(self):
- self.client = MockTestProtocolServerClient()
+ self.client = ExtendedTestResult()
self.protocol = subunit.TestProtocolServer(self.client)
self.protocol.lineReceived("test mcdonalds farm\n")
self.test = subunit.RemotedTestCase("mcdonalds farm")
def simple_error_keyword(self, keyword):
self.protocol.lineReceived("%s mcdonalds farm\n" % keyword)
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [
- (self.test, subunit.RemoteError(""))])
- self.assertEqual(self.client.failure_calls, [])
+ details = {}
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addError', self.test, details),
+ ('stopTest', self.test),
+ ], self.client._events)
def test_simple_error(self):
self.simple_error_keyword("error")
@@ -521,21 +406,27 @@ class TestTestProtocolServerAddError(unittest.TestCase):
def test_error_empty_message(self):
self.protocol.lineReceived("error mcdonalds farm [\n")
self.protocol.lineReceived("]\n")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [
- (self.test, subunit.RemoteError(""))])
- self.assertEqual(self.client.failure_calls, [])
+ details = {}
+ details['traceback'] = Content(ContentType("text", "x-traceback"),
+ lambda:[""])
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addError', self.test, details),
+ ('stopTest', self.test),
+ ], self.client._events)
def error_quoted_bracket(self, keyword):
self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword)
self.protocol.lineReceived(" ]\n")
self.protocol.lineReceived("]\n")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [
- (self.test, subunit.RemoteError("]\n"))])
- self.assertEqual(self.client.failure_calls, [])
+ details = {}
+ details['traceback'] = Content(ContentType("text", "x-traceback"),
+ lambda:["]\n"])
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addError', self.test, details),
+ ('stopTest', self.test),
+ ], self.client._events)
def test_error_quoted_bracket(self):
self.error_quoted_bracket("error")
@@ -547,18 +438,22 @@ class TestTestProtocolServerAddError(unittest.TestCase):
class TestTestProtocolServerAddFailure(unittest.TestCase):
def setUp(self):
- self.client = MockTestProtocolServerClient()
+ self.client = ExtendedTestResult()
self.protocol = subunit.TestProtocolServer(self.client)
self.protocol.lineReceived("test mcdonalds farm\n")
self.test = subunit.RemotedTestCase("mcdonalds farm")
+ def assertFailure(self, details):
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addFailure', self.test, details),
+ ('stopTest', self.test),
+ ], self.client._events)
+
def simple_failure_keyword(self, keyword):
self.protocol.lineReceived("%s mcdonalds farm\n" % keyword)
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls,
- [(self.test, subunit.RemoteError())])
+ details = {}
+ self.assertFailure(details)
def test_simple_failure(self):
self.simple_failure_keyword("failure")
@@ -569,21 +464,19 @@ class TestTestProtocolServerAddFailure(unittest.TestCase):
def test_failure_empty_message(self):
self.protocol.lineReceived("failure mcdonalds farm [\n")
self.protocol.lineReceived("]\n")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls,
- [(self.test, subunit.RemoteError())])
+ details = {}
+ details['traceback'] = Content(ContentType("text", "x-traceback"),
+ lambda:[""])
+ self.assertFailure(details)
def failure_quoted_bracket(self, keyword):
self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword)
self.protocol.lineReceived(" ]\n")
self.protocol.lineReceived("]\n")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls,
- [(self.test, subunit.RemoteError("]\n"))])
+ details = {}
+ details['traceback'] = Content(ContentType("text", "x-traceback"),
+ lambda:["]\n"])
+ self.assertFailure(details)
def test_failure_quoted_bracket(self):
self.failure_quoted_bracket("failure")
@@ -600,67 +493,87 @@ class TestTestProtocolServerAddxFail(unittest.TestCase):
"""
def capture_expected_failure(self, test, err):
- self._calls.append((test, err))
+ self._events.append((test, err))
def setup_python26(self):
"""Setup a test object ready to be xfailed and thunk to success."""
- self.client = MockTestProtocolServerClient()
+ self.client = Python26TestResult()
self.setup_protocol()
def setup_python27(self):
- """Setup a test object ready to be xfailed and thunk to success."""
- self.client = MockTestProtocolServerClient()
- self.client.addExpectedFailure = self.capture_expected_failure
- self._calls = []
+ """Setup a test object ready to be xfailed."""
+ self.client = Python27TestResult()
+ self.setup_protocol()
+
+ def setup_python_ex(self):
+ """Setup a test object ready to be xfailed with details."""
+ self.client = ExtendedTestResult()
self.setup_protocol()
def setup_protocol(self):
"""Setup the protocol based on self.client."""
self.protocol = subunit.TestProtocolServer(self.client)
self.protocol.lineReceived("test mcdonalds farm\n")
- self.test = self.client.start_calls[-1]
+ self.test = self.client._events[-1][-1]
def simple_xfail_keyword(self, keyword, as_success):
self.protocol.lineReceived("%s mcdonalds farm\n" % keyword)
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls, [])
self.check_success_or_xfail(as_success)
- def check_success_or_xfail(self, as_success):
+ def check_success_or_xfail(self, as_success, error_message=None):
if as_success:
- self.assertEqual(self.client.success_calls, [self.test])
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addSuccess', self.test),
+ ('stopTest', self.test),
+ ], self.client._events)
else:
- self.assertEqual(1, len(self._calls))
- self.assertEqual(self.test, self._calls[0][0])
+ details = {}
+ if error_message is not None:
+ details['traceback'] = Content(
+ ContentType("text", "x-traceback"), lambda:[error_message])
+ if isinstance(self.client, ExtendedTestResult):
+ value = details
+ else:
+ if error_message is not None:
+ value = subunit.RemoteError('Text attachment: traceback\n'
+ '------------\n' + error_message + '------------\n')
+ else:
+ value = subunit.RemoteError()
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addExpectedFailure', self.test, value),
+ ('stopTest', self.test),
+ ], self.client._events)
def test_simple_xfail(self):
self.setup_python26()
self.simple_xfail_keyword("xfail", True)
self.setup_python27()
self.simple_xfail_keyword("xfail", False)
+ self.setup_python_ex()
+ self.simple_xfail_keyword("xfail", False)
def test_simple_xfail_colon(self):
self.setup_python26()
self.simple_xfail_keyword("xfail:", True)
self.setup_python27()
self.simple_xfail_keyword("xfail:", False)
+ self.setup_python_ex()
+ self.simple_xfail_keyword("xfail:", False)
def test_xfail_empty_message(self):
self.setup_python26()
self.empty_message(True)
self.setup_python27()
self.empty_message(False)
+ self.setup_python_ex()
+ self.empty_message(False, error_message="")
- def empty_message(self, as_success):
+ def empty_message(self, as_success, error_message="\n"):
self.protocol.lineReceived("xfail mcdonalds farm [\n")
self.protocol.lineReceived("]\n")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls, [])
- self.check_success_or_xfail(as_success)
+ self.check_success_or_xfail(as_success, error_message)
def xfail_quoted_bracket(self, keyword, as_success):
# This tests it is accepted, but cannot test it is used today, because
@@ -668,23 +581,23 @@ class TestTestProtocolServerAddxFail(unittest.TestCase):
self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword)
self.protocol.lineReceived(" ]\n")
self.protocol.lineReceived("]\n")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls, [])
- self.check_success_or_xfail(as_success)
+ self.check_success_or_xfail(as_success, "]\n")
def test_xfail_quoted_bracket(self):
self.setup_python26()
self.xfail_quoted_bracket("xfail", True)
self.setup_python27()
self.xfail_quoted_bracket("xfail", False)
+ self.setup_python_ex()
+ self.xfail_quoted_bracket("xfail", False)
def test_xfail_colon_quoted_bracket(self):
self.setup_python26()
self.xfail_quoted_bracket("xfail:", True)
self.setup_python27()
self.xfail_quoted_bracket("xfail:", False)
+ self.setup_python_ex()
+ self.xfail_quoted_bracket("xfail:", False)
class TestTestProtocolServerAddSkip(unittest.TestCase):
@@ -696,20 +609,25 @@ class TestTestProtocolServerAddSkip(unittest.TestCase):
def setUp(self):
"""Setup a test object ready to be skipped."""
- self.client = MockTestProtocolServerClient()
+ self.client = ExtendedTestResult()
self.protocol = subunit.TestProtocolServer(self.client)
self.protocol.lineReceived("test mcdonalds farm\n")
- self.test = self.client.start_calls[-1]
+ self.test = self.client._events[-1][-1]
+
+ def assertSkip(self, reason):
+ details = {}
+ if reason is not None:
+ details['reason'] = Content(
+ ContentType("text", "plain"), lambda:[reason])
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addSkip', self.test, details),
+ ('stopTest', self.test),
+ ], self.client._events)
def simple_skip_keyword(self, keyword):
self.protocol.lineReceived("%s mcdonalds farm\n" % keyword)
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [])
- self.assertEqual(self.client.skip_calls,
- [(self.test, 'No reason given')])
+ self.assertSkip(None)
def test_simple_skip(self):
self.simple_skip_keyword("skip")
@@ -720,13 +638,7 @@ class TestTestProtocolServerAddSkip(unittest.TestCase):
def test_skip_empty_message(self):
self.protocol.lineReceived("skip mcdonalds farm [\n")
self.protocol.lineReceived("]\n")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [])
- self.assertEqual(self.client.skip_calls,
- [(self.test, "No reason given")])
+ self.assertSkip("")
def skip_quoted_bracket(self, keyword):
# This tests it is accepted, but cannot test it is used today, because
@@ -734,13 +646,7 @@ class TestTestProtocolServerAddSkip(unittest.TestCase):
self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword)
self.protocol.lineReceived(" ]\n")
self.protocol.lineReceived("]\n")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [])
- self.assertEqual(self.client.skip_calls,
- [(self.test, "]\n")])
+ self.assertSkip("]\n")
def test_skip_quoted_bracket(self):
self.skip_quoted_bracket("skip")
@@ -752,17 +658,18 @@ class TestTestProtocolServerAddSkip(unittest.TestCase):
class TestTestProtocolServerAddSuccess(unittest.TestCase):
def setUp(self):
- self.client = MockTestProtocolServerClient()
+ self.client = ExtendedTestResult()
self.protocol = subunit.TestProtocolServer(self.client)
self.protocol.lineReceived("test mcdonalds farm\n")
self.test = subunit.RemotedTestCase("mcdonalds farm")
def simple_success_keyword(self, keyword):
self.protocol.lineReceived("%s mcdonalds farm\n" % keyword)
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.success_calls, [self.test])
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addSuccess', self.test),
+ ('stopTest', self.test),
+ ], self.client._events)
def test_simple_success(self):
self.simple_success_keyword("failure")
@@ -776,14 +683,20 @@ class TestTestProtocolServerAddSuccess(unittest.TestCase):
def test_simple_success_colon(self):
self.simple_success_keyword("successful:")
+ def assertSuccess(self, details):
+ self.assertEqual([
+ ('startTest', self.test),
+ ('addSuccess', self.test, details),
+ ('stopTest', self.test),
+ ], self.client._events)
+
def test_success_empty_message(self):
self.protocol.lineReceived("success mcdonalds farm [\n")
self.protocol.lineReceived("]\n")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [self.test])
+ details = {}
+ details['message'] = Content(ContentType("text", "plain"),
+ lambda:[""])
+ self.assertSuccess(details)
def success_quoted_bracket(self, keyword):
# This tests it is accepted, but cannot test it is used today, because
@@ -791,11 +704,10 @@ class TestTestProtocolServerAddSuccess(unittest.TestCase):
self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword)
self.protocol.lineReceived(" ]\n")
self.protocol.lineReceived("]\n")
- self.assertEqual(self.client.start_calls, [self.test])
- self.assertEqual(self.client.end_calls, [self.test])
- self.assertEqual(self.client.error_calls, [])
- self.assertEqual(self.client.failure_calls, [])
- self.assertEqual(self.client.success_calls, [self.test])
+ details = {}
+ details['message'] = Content(ContentType("text", "plain"),
+ lambda:["]\n"])
+ self.assertSuccess(details)
def test_success_quoted_bracket(self):
self.success_quoted_bracket("success")
@@ -808,8 +720,7 @@ class TestTestProtocolServerProgress(unittest.TestCase):
"""Test receipt of progress: directives."""
def test_progress_accepted_stdlib(self):
- # With a stdlib TestResult, progress events are swallowed.
- self.result = unittest.TestResult()
+ self.result = Python26TestResult()
self.stream = StringIO()
self.protocol = subunit.TestProtocolServer(self.result,
stream=self.stream)
@@ -820,7 +731,7 @@ class TestTestProtocolServerProgress(unittest.TestCase):
def test_progress_accepted_extended(self):
# With a progress capable TestResult, progress events are emitted.
- self.result = MockTestProtocolServerClient()
+ self.result = ExtendedTestResult()
self.stream = StringIO()
self.protocol = subunit.TestProtocolServer(self.result,
stream=self.stream)
@@ -830,49 +741,48 @@ class TestTestProtocolServerProgress(unittest.TestCase):
self.protocol.lineReceived("progress: pop")
self.protocol.lineReceived("progress: +4")
self.assertEqual("", self.stream.getvalue())
- self.assertEqual(
- [(23, subunit.PROGRESS_SET), (None, subunit.PROGRESS_PUSH),
- (-2, subunit.PROGRESS_CUR), (None, subunit.PROGRESS_POP),
- (4, subunit.PROGRESS_CUR)],
- self.result.progress_calls)
+ self.assertEqual([
+ ('progress', 23, subunit.PROGRESS_SET),
+ ('progress', None, subunit.PROGRESS_PUSH),
+ ('progress', -2, subunit.PROGRESS_CUR),
+ ('progress', None, subunit.PROGRESS_POP),
+ ('progress', 4, subunit.PROGRESS_CUR),
+ ], self.result._events)
class TestTestProtocolServerStreamTags(unittest.TestCase):
"""Test managing tags on the protocol level."""
def setUp(self):
- self.client = MockExtendedTestProtocolServerClient()
+ self.client = ExtendedTestResult()
self.protocol = subunit.TestProtocolServer(self.client)
def test_initial_tags(self):
self.protocol.lineReceived("tags: foo bar:baz quux\n")
- self.assertEqual(set(["foo", "bar:baz", "quux"]),
- self.client.new_tags)
- self.assertEqual(set(), self.client.gone_tags)
+ self.assertEqual([
+ ('tags', set(["foo", "bar:baz", "quux"]), set()),
+ ], self.client._events)
def test_minus_removes_tags(self):
- self.protocol.lineReceived("tags: foo bar\n")
- self.assertEqual(set(["foo", "bar"]),
- self.client.new_tags)
- self.assertEqual(set(), self.client.gone_tags)
self.protocol.lineReceived("tags: -bar quux\n")
- self.assertEqual(set(["quux"]), self.client.new_tags)
- self.assertEqual(set(["bar"]), self.client.gone_tags)
+ self.assertEqual([
+ ('tags', set(["quux"]), set(["bar"])),
+ ], self.client._events)
def test_tags_do_not_get_set_on_test(self):
self.protocol.lineReceived("test mcdonalds farm\n")
- test = self.client.start_calls[-1]
+ test = self.client._events[0][-1]
self.assertEqual(None, getattr(test, 'tags', None))
def test_tags_do_not_get_set_on_global_tags(self):
self.protocol.lineReceived("tags: foo bar\n")
self.protocol.lineReceived("test mcdonalds farm\n")
- test = self.client.start_calls[-1]
+ test = self.client._events[-1][-1]
self.assertEqual(None, getattr(test, 'tags', None))
def test_tags_get_set_on_test_tags(self):
self.protocol.lineReceived("test mcdonalds farm\n")
- test = self.client.start_calls[-1]
+ test = self.client._events[-1][-1]
self.protocol.lineReceived("tags: foo bar\n")
self.protocol.lineReceived("success mcdonalds farm\n")
self.assertEqual(None, getattr(test, 'tags', None))
@@ -882,7 +792,7 @@ class TestTestProtocolServerStreamTime(unittest.TestCase):
"""Test managing time information at the protocol level."""
def test_time_accepted_stdlib(self):
- self.result = unittest.TestResult()
+ self.result = Python26TestResult()
self.stream = StringIO()
self.protocol = subunit.TestProtocolServer(self.result,
stream=self.stream)
@@ -890,14 +800,16 @@ class TestTestProtocolServerStreamTime(unittest.TestCase):
self.assertEqual("", self.stream.getvalue())
def test_time_accepted_extended(self):
- self.result = MockTestProtocolServerClient()
+ self.result = ExtendedTestResult()
self.stream = StringIO()
self.protocol = subunit.TestProtocolServer(self.result,
stream=self.stream)
self.protocol.lineReceived("time: 2001-12-12 12:59:59Z\n")
self.assertEqual("", self.stream.getvalue())
- self.assertEqual(datetime.datetime(2001, 12, 12, 12, 59, 59, 0,
- iso8601.Utc()), self.result._time)
+ self.assertEqual([
+ ('time', datetime.datetime(2001, 12, 12, 12, 59, 59, 0,
+ iso8601.Utc()))
+ ], self.result._events)
class TestRemotedTestCase(unittest.TestCase):
@@ -915,7 +827,7 @@ class TestRemotedTestCase(unittest.TestCase):
"'A test description'>", "%r" % test)
result = unittest.TestResult()
test.run(result)
- self.assertEqual([(test, "RemoteException: "
+ self.assertEqual([(test, _remote_exception_str + ": "
"Cannot run RemotedTestCases.\n\n")],
result.errors)
self.assertEqual(1, result.testsRun)
@@ -965,20 +877,27 @@ class TestExecTestCase(unittest.TestCase):
self.assertEqual(1, result.testsRun)
def test_run(self):
- runner = MockTestProtocolServerClient()
+ result = ExtendedTestResult()
test = self.SampleExecTestCase("test_sample_method")
- test.run(runner)
+ test.run(result)
mcdonald = subunit.RemotedTestCase("old mcdonald")
bing = subunit.RemotedTestCase("bing crosby")
+ bing_details = {}
+ bing_details['traceback'] = Content(ContentType("text", "x-traceback"),
+ lambda:["foo.c:53:ERROR invalid state\n"])
an_error = subunit.RemotedTestCase("an error")
- self.assertEqual(runner.error_calls,
- [(an_error, subunit.RemoteError())])
- self.assertEqual(runner.failure_calls,
- [(bing,
- subunit.RemoteError(
- "foo.c:53:ERROR invalid state\n"))])
- self.assertEqual(runner.start_calls, [mcdonald, bing, an_error])
- self.assertEqual(runner.end_calls, [mcdonald, bing, an_error])
+ error_details = {}
+ self.assertEqual([
+ ('startTest', mcdonald),
+ ('addSuccess', mcdonald),
+ ('stopTest', mcdonald),
+ ('startTest', bing),
+ ('addFailure', bing, bing_details),
+ ('stopTest', bing),
+ ('startTest', an_error),
+ ('addError', an_error, error_details),
+ ('stopTest', an_error),
+ ], result._events)
def test_debug(self):
test = self.SampleExecTestCase("test_sample_method")
@@ -1077,6 +996,11 @@ class TestTestProtocolClient(unittest.TestCase):
self.io = StringIO()
self.protocol = subunit.TestProtocolClient(self.io)
self.test = TestTestProtocolClient("test_start_test")
+ self.sample_details = {'something':Content(
+ ContentType('text', 'plain'), lambda:['serialised\nform'])}
+ self.sample_tb_details = dict(self.sample_details)
+ self.sample_tb_details['traceback'] = TracebackContent(
+ subunit.RemoteError("boo qux"), self.test)
def test_start_test(self):
"""Test startTest on a TestProtocolClient."""
@@ -1094,13 +1018,38 @@ class TestTestProtocolClient(unittest.TestCase):
self.assertEqual(
self.io.getvalue(), "successful: %s\n" % self.test.id())
+ def test_add_success_details(self):
+ """Test addSuccess on a TestProtocolClient with details."""
+ self.protocol.addSuccess(self.test, details=self.sample_details)
+ self.assertEqual(
+ self.io.getvalue(), "successful: %s [ multipart\n"
+ "Content-Type: text/plain\n"
+ "something\n"
+ "F\r\nserialised\nform0\r\n]\n" % self.test.id())
+
def test_add_failure(self):
"""Test addFailure on a TestProtocolClient."""
self.protocol.addFailure(
self.test, subunit.RemoteError("boo qux"))
self.assertEqual(
self.io.getvalue(),
- 'failure: %s [\nRemoteException: boo qux\n]\n' % self.test.id())
+ ('failure: %s [\n' + _remote_exception_str + ': boo qux\n]\n')
+ % self.test.id())
+
+ def test_add_failure_details(self):
+ """Test addFailure on a TestProtocolClient with details."""
+ self.protocol.addFailure(
+ self.test, details=self.sample_tb_details)
+ self.assertEqual(
+ self.io.getvalue(),
+ ("failure: %s [ multipart\n"
+ "Content-Type: text/plain\n"
+ "something\n"
+ "F\r\nserialised\nform0\r\n"
+ "Content-Type: text/x-traceback;charset=utf8,language=python\n"
+ "traceback\n"
+ "1A\r\n" + _remote_exception_str + ": boo qux\n0\r\n"
+ "]\n") % self.test.id())
def test_add_error(self):
"""Test stopTest on a TestProtocolClient."""
@@ -1108,9 +1057,49 @@ class TestTestProtocolClient(unittest.TestCase):
self.test, subunit.RemoteError("phwoar crikey"))
self.assertEqual(
self.io.getvalue(),
- 'error: %s [\n'
- "RemoteException: phwoar crikey\n"
- "]\n" % self.test.id())
+ ('error: %s [\n' +
+ _remote_exception_str + ": phwoar crikey\n"
+ "]\n") % self.test.id())
+
+ def test_add_error_details(self):
+ """Test stopTest on a TestProtocolClient with details."""
+ self.protocol.addError(
+ self.test, details=self.sample_tb_details)
+ self.assertEqual(
+ self.io.getvalue(),
+ ("error: %s [ multipart\n"
+ "Content-Type: text/plain\n"
+ "something\n"
+ "F\r\nserialised\nform0\r\n"
+ "Content-Type: text/x-traceback;charset=utf8,language=python\n"
+ "traceback\n"
+ "1A\r\n" + _remote_exception_str + ": boo qux\n0\r\n"
+ "]\n") % self.test.id())
+
+ def test_add_expected_failure(self):
+ """Test addExpectedFailure on a TestProtocolClient."""
+ self.protocol.addExpectedFailure(
+ self.test, subunit.RemoteError("phwoar crikey"))
+ self.assertEqual(
+ self.io.getvalue(),
+ ('xfail: %s [\n' +
+ _remote_exception_str + ": phwoar crikey\n"
+ "]\n") % self.test.id())
+
+ def test_add_expected_failure_details(self):
+ """Test addExpectedFailure on a TestProtocolClient with details."""
+ self.protocol.addExpectedFailure(
+ self.test, details=self.sample_tb_details)
+ self.assertEqual(
+ self.io.getvalue(),
+ ("xfail: %s [ multipart\n"
+ "Content-Type: text/plain\n"
+ "something\n"
+ "F\r\nserialised\nform0\r\n"
+ "Content-Type: text/x-traceback;charset=utf8,language=python\n"
+ "traceback\n"
+ "1A\r\n"+ _remote_exception_str + ": boo qux\n0\r\n"
+ "]\n") % self.test.id())
def test_add_skip(self):
"""Test addSkip on a TestProtocolClient."""
@@ -1119,6 +1108,20 @@ class TestTestProtocolClient(unittest.TestCase):
self.assertEqual(
self.io.getvalue(),
'skip: %s [\nHas it really?\n]\n' % self.test.id())
+
+ def test_add_skip_details(self):
+ """Test addSkip on a TestProtocolClient with details."""
+ details = {'reason':Content(
+ ContentType('text', 'plain'), lambda:['Has it really?'])}
+ self.protocol.addSkip(
+ self.test, details=details)
+ self.assertEqual(
+ self.io.getvalue(),
+ "skip: %s [ multipart\n"
+ "Content-Type: text/plain\n"
+ "reason\n"
+ "E\r\nHas it really?0\r\n"
+ "]\n" % self.test.id())
def test_progress_set(self):
self.protocol.progress(23, subunit.PROGRESS_SET)
@@ -1148,6 +1151,21 @@ class TestTestProtocolClient(unittest.TestCase):
"time: 2009-10-11 12:13:14.000015Z\n",
self.io.getvalue())
+ def test_add_unexpected_success(self):
+ """Test addUnexpectedSuccess on a TestProtocolClient."""
+ self.protocol.addUnexpectedSuccess(self.test)
+ self.assertEqual(
+ self.io.getvalue(), "successful: %s\n" % self.test.id())
+
+ def test_add_unexpected_success_details(self):
+ """Test addUnexpectedSuccess on a TestProtocolClient with details."""
+ self.protocol.addUnexpectedSuccess(self.test, details=self.sample_details)
+ self.assertEqual(
+ self.io.getvalue(), "successful: %s [ multipart\n"
+ "Content-Type: text/plain\n"
+ "something\n"
+ "F\r\nserialised\nform0\r\n]\n" % self.test.id())
+
def test_suite():
loader = subunit.tests.TestUtil.TestLoader()
diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py
index 58e14c1..fe82c04 100644
--- a/python/subunit/tests/test_test_results.py
+++ b/python/subunit/tests/test_test_results.py
@@ -20,6 +20,9 @@ from StringIO import StringIO
import os
import sys
+from testtools.content_type import ContentType
+from testtools.content import Content
+
import subunit
import subunit.iso8601 as iso8601
import subunit.test_results
@@ -60,7 +63,7 @@ class TimeCapturingResult(unittest.TestResult):
class TestHookedTestResultDecorator(unittest.TestCase):
def setUp(self):
- # And end to the chain
+ # An end to the chain
terminal = unittest.TestResult()
# Asserts that the call was made to self.result before asserter was
# called.
@@ -68,13 +71,14 @@ class TestHookedTestResultDecorator(unittest.TestCase):
# The result object we call, which much increase its call count.
self.result = LoggingDecorator(asserter)
asserter.earlier = self.result
+ self.decorated = asserter
def tearDown(self):
# The hook in self.result must have been called
self.assertEqual(1, self.result._calls)
# The hook in asserter must have been called too, otherwise the
# assertion about ordering won't have completed.
- self.assertEqual(1, self.result.decorated._calls)
+ self.assertEqual(1, self.decorated._calls)
def test_startTest(self):
self.result.startTest(self)
@@ -91,21 +95,39 @@ class TestHookedTestResultDecorator(unittest.TestCase):
def test_addError(self):
self.result.addError(self, subunit.RemoteError())
+ def test_addError_details(self):
+ self.result.addError(self, details={})
+
def test_addFailure(self):
self.result.addFailure(self, subunit.RemoteError())
+ def test_addFailure_details(self):
+ self.result.addFailure(self, details={})
+
def test_addSuccess(self):
self.result.addSuccess(self)
+ def test_addSuccess_details(self):
+ self.result.addSuccess(self, details={})
+
def test_addSkip(self):
self.result.addSkip(self, "foo")
+ def test_addSkip_details(self):
+ self.result.addSkip(self, details={})
+
def test_addExpectedFailure(self):
self.result.addExpectedFailure(self, subunit.RemoteError())
+ def test_addExpectedFailure_details(self):
+ self.result.addExpectedFailure(self, details={})
+
def test_addUnexpectedSuccess(self):
self.result.addUnexpectedSuccess(self)
+ def test_addUnexpectedSuccess_details(self):
+ self.result.addUnexpectedSuccess(self, details={})
+
def test_progress(self):
self.result.progress(1, subunit.PROGRESS_SET)
@@ -130,20 +152,21 @@ class TestAutoTimingTestResultDecorator(unittest.TestCase):
# The result object under test.
self.result = subunit.test_results.AutoTimingTestResultDecorator(
terminal)
+ self.decorated = terminal
def test_without_time_calls_time_is_called_and_not_None(self):
self.result.startTest(self)
- self.assertEqual(1, len(self.result.decorated._calls))
- self.assertNotEqual(None, self.result.decorated._calls[0])
+ self.assertEqual(1, len(self.decorated._calls))
+ self.assertNotEqual(None, self.decorated._calls[0])
def test_no_time_from_progress(self):
self.result.progress(1, subunit.PROGRESS_CUR)
- self.assertEqual(0, len(self.result.decorated._calls))
+ self.assertEqual(0, len(self.decorated._calls))
def test_no_time_from_shouldStop(self):
- self.result.decorated.stop()
+ self.decorated.stop()
self.result.shouldStop
- self.assertEqual(0, len(self.result.decorated._calls))
+ self.assertEqual(0, len(self.decorated._calls))
def test_calling_time_inhibits_automatic_time(self):
# Calling time() outputs a time signal immediately and prevents
@@ -152,22 +175,22 @@ class TestAutoTimingTestResultDecorator(unittest.TestCase):
self.result.time(time)
self.result.startTest(self)
self.result.stopTest(self)
- self.assertEqual(1, len(self.result.decorated._calls))
- self.assertEqual(time, self.result.decorated._calls[0])
+ self.assertEqual(1, len(self.decorated._calls))
+ self.assertEqual(time, self.decorated._calls[0])
def test_calling_time_None_enables_automatic_time(self):
time = datetime.datetime(2009,10,11,12,13,14,15, iso8601.Utc())
self.result.time(time)
- self.assertEqual(1, len(self.result.decorated._calls))
- self.assertEqual(time, self.result.decorated._calls[0])
+ self.assertEqual(1, len(self.decorated._calls))
+ self.assertEqual(time, self.decorated._calls[0])
# Calling None passes the None through, in case other results care.
self.result.time(None)
- self.assertEqual(2, len(self.result.decorated._calls))
- self.assertEqual(None, self.result.decorated._calls[1])
+ self.assertEqual(2, len(self.decorated._calls))
+ self.assertEqual(None, self.decorated._calls[1])
# Calling other methods doesn't generate an automatic time event.
self.result.startTest(self)
- self.assertEqual(3, len(self.result.decorated._calls))
- self.assertNotEqual(None, self.result.decorated._calls[2])
+ self.assertEqual(3, len(self.decorated._calls))
+ self.assertNotEqual(None, self.decorated._calls[2])
def test_suite():