From 0fdc43947a17af18013bb101a101d3b709ffebc1 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Fri, 11 Dec 2009 13:41:42 +1100 Subject: [PATCH] Use testtools facilities for the details API. --- INSTALL | 15 +- NEWS | 5 +- python/subunit/__init__.py | 29 +- python/subunit/content.py | 75 ----- python/subunit/content_type.py | 43 --- python/subunit/details.py | 4 +- python/subunit/test_results.py | 160 +--------- python/subunit/tests/__init__.py | 4 - python/subunit/tests/test_content.py | 69 ----- python/subunit/tests/test_content_type.py | 50 --- python/subunit/tests/test_test_protocol.py | 47 +-- python/subunit/tests/test_test_results.py | 343 +-------------------- 12 files changed, 59 insertions(+), 785 deletions(-) delete mode 100644 python/subunit/content.py delete mode 100644 python/subunit/content_type.py delete mode 100644 python/subunit/tests/test_content.py delete mode 100644 python/subunit/tests/test_content_type.py diff --git a/INSTALL b/INSTALL index dc4fb55..ab716dc 100644 --- a/INSTALL +++ b/INSTALL @@ -11,9 +11,12 @@ Install:: Dependencies ------------ -Python for the filters -A C compiler for the C bindings -Perl for the Perl tools (including subunit-diff) -Check to run the subunit test suite. -python-gtk2 if you wish to use subunit2gtk -python-junitxml if you wish to use subunit2junitxml +* Python for the filters +* 'testtools' (On Debian and Ubuntu systems the 'python-testtools' package) for + the extended test API which permits attachments. Version 0.9.2 or newer is + required. +* A C compiler for the C bindings +* Perl for the Perl tools (including subunit-diff) +* Check to run the subunit test suite. +* python-gtk2 if you wish to use subunit2gtk +* python-junitxml if you wish to use subunit2junitxml diff --git a/NEWS b/NEWS index f261afc..6036a54 100644 --- a/NEWS +++ b/NEWS @@ -31,7 +31,10 @@ subunit release notes * Multipart test outcomes are tentatively supported; the exact protocol for them, both serialiser and object is not yet finalised. Testers and - early adopters are sought. + early adopters are sought. As part of this and also in an attempt to + provider a more precise focus on the wire protocol and toolchain, + Subunit now depends on testtools (http://launchpad.net/testtools) + release 0.9.0 or newer. * The C library now has ``subunit_test_skip``. diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index dd3c0f1..b5f622d 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -50,7 +50,7 @@ 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 -``subunit.content.Content`` objects. This is a draft API being worked on with +``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. @@ -112,8 +112,8 @@ Utility modules --------------- * subunit.chunked contains HTTP chunked encoding/decoding logic. -* subunit.content contains a minimal assumptions MIME content representation. -* subunit.content_type contains a MIME Content-Type representation. +* testtools.content contains a minimal assumptions MIME content representation. +* testtools.content_type contains a MIME Content-Type representation. * subunit.test_results contains TestResult helper classes. """ @@ -126,8 +126,10 @@ import sys import unittest import iso8601 +from testtools import content, content_type, ExtendedToOriginalDecorator +from testtools.testresult import _StringException -import chunked, content, content_type, details, test_results +import chunked, details, test_results PROGRESS_SET = 0 @@ -428,7 +430,7 @@ class TestProtocolServer(object): of mixed protocols. By default, sys.stdout will be used for convenience. """ - self.client = test_results.ExtendedToOriginalDecorator(client) + self.client = ExtendedToOriginalDecorator(client) if stream is None: stream = sys.stdout self._stream = stream @@ -506,14 +508,11 @@ class TestProtocolServer(object): self._stream.write(line) -class RemoteException(Exception): - """An exception that occured remotely to Python.""" - - def __eq__(self, other): - try: - return self.args == other.args - except AttributeError: - return False +try: + from testtools.testresult import _StringException as RemoteException + _remote_exception_str = '_StringException' # For testing. +except ImportError: + raise ImportError ("testtools.testresult does not contain _StringException, check your version.") class TestProtocolClient(unittest.TestResult): @@ -690,9 +689,7 @@ class TestProtocolClient(unittest.TestResult): def RemoteError(description=""): - if description == "": - description = "\n" - return (RemoteException, RemoteException(description), None) + return (_StringException, _StringException(description), None) class RemotedTestCase(unittest.TestCase): diff --git a/python/subunit/content.py b/python/subunit/content.py deleted file mode 100644 index 5961e24..0000000 --- a/python/subunit/content.py +++ /dev/null @@ -1,75 +0,0 @@ -# -# subunit: extensions to Python unittest to get test results from subprocesses. -# Copyright (C) 2009 Robert Collins -# -# 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. -# - -"""Content - a MIME-like Content object.""" - -from unittest import TestResult - -import subunit -from subunit.content_type import ContentType - - -class Content(object): - """A MIME-like Content object. - - Content objects can be serialised to bytes using the iter_bytes method. - If the Content-Type is recognised by other code, they are welcome to - look for richer contents that mere byte serialisation - for example in - memory object graphs etc. However, such code MUST be prepared to receive - a generic Content object that has been reconstructed from a byte stream. - - :ivar content_type: The content type of this Content. - """ - - def __init__(self, content_type, get_bytes): - """Create a ContentType.""" - if None in (content_type, get_bytes): - raise ValueError("None not permitted in %r, %r" % ( - content_type, get_bytes)) - self.content_type = content_type - self._get_bytes = get_bytes - - def __eq__(self, other): - return (self.content_type == other.content_type and - ''.join(self.iter_bytes()) == ''.join(other.iter_bytes())) - - def iter_bytes(self): - """Iterate over bytestrings of the serialised content.""" - return self._get_bytes() - - def __repr__(self): - return "" % ( - self.content_type, ''.join(self.iter_bytes())) - - -class TracebackContent(Content): - """Content object for tracebacks. - - This adapts an exc_info tuple to the Content interface. - text/x-traceback;language=python is used for the mime type, in order to - provide room for other languages to format their tracebacks differently. - """ - - def __init__(self, err): - """Create a TracebackContent for err.""" - if err is None: - raise ValueError("err may not be None") - content_type = ContentType('text', 'x-traceback', - {"language": "python"}) - self._result = TestResult() - super(TracebackContent, self).__init__(content_type, - lambda:[self._result._exc_info_to_string(err, - subunit.RemotedTestCase(''))]) diff --git a/python/subunit/content_type.py b/python/subunit/content_type.py deleted file mode 100644 index 3aade6c..0000000 --- a/python/subunit/content_type.py +++ /dev/null @@ -1,43 +0,0 @@ -# -# subunit: extensions to Python unittest to get test results from subprocesses. -# Copyright (C) 2009 Robert Collins -# -# 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. -# - -"""ContentType - a MIME Content Type.""" - -class ContentType(object): - """A content type from http://www.iana.org/assignments/media-types/ - - :ivar type: The primary type, e.g. "text" or "application" - :ivar subtype: The subtype, e.g. "plain" or "octet-stream" - :ivar parameters: A dict of additional parameters specific to the - content type. - """ - - def __init__(self, primary_type, sub_type, parameters=None): - """Create a ContentType.""" - if None in (primary_type, sub_type): - raise ValueError("None not permitted in %r, %r" % ( - primary_type, sub_type)) - self.type = primary_type - self.subtype = sub_type - self.parameters = parameters or {} - - def __eq__(self, other): - if type(other) != ContentType: - return False - return self.__dict__ == other.__dict__ - - def __repr__(self): - return "%s/%s params=%s" % (self.type, self.subtype, self.parameters) diff --git a/python/subunit/details.py b/python/subunit/details.py index 2cedba2..65a0404 100644 --- a/python/subunit/details.py +++ b/python/subunit/details.py @@ -18,7 +18,9 @@ from cStringIO import StringIO -import chunked, content, content_type +from testtools import content, content_type + +import chunked class DetailsParser(object): diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 8bfdce5..4ccc2aa 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -19,6 +19,7 @@ import datetime import iso8601 +import testtools import subunit @@ -37,7 +38,7 @@ class TestResultDecorator(object): def __init__(self, decorated): """Create a TestResultDecorator forwarding to decorated.""" # Make every decorator degrade gracefully. - self.decorated = ExtendedToOriginalDecorator(decorated) + self.decorated = testtools.ExtendedToOriginalDecorator(decorated) def startTest(self, test): return self.decorated.startTest(test) @@ -331,160 +332,3 @@ class TestResultFilter(TestResultDecorator): if id.startswith("subunit.RemotedTestCase."): return id[len("subunit.RemotedTestCase."):] return id - - -class ExtendedToOriginalDecorator(object): - """Permit new TestResult API code to degrade gracefully with old results. - - This decorates an existing TestResult and converts missing outcomes - such as addSkip to older outcomes such as addSuccess. It also supports - the extended details protocol. In all cases the most recent protocol - is attempted first, and fallbacks only occur when the decorated result - does not support the newer style of calling. - """ - - def __init__(self, decorated): - self.decorated = decorated - - def addError(self, test, err=None, details=None): - self._check_args(err, details) - if details is not None: - try: - return self.decorated.addError(test, details=details) - except TypeError, e: - # have to convert - err = self._details_to_exc_info(details) - return self.decorated.addError(test, err) - - def addExpectedFailure(self, test, err=None, details=None): - self._check_args(err, details) - addExpectedFailure = getattr(self.decorated, 'addExpectedFailure', None) - if addExpectedFailure is None: - return self.addSuccess(test) - if details is not None: - try: - return addExpectedFailure(test, details=details) - except TypeError, e: - # have to convert - err = self._details_to_exc_info(details) - return addExpectedFailure(test, err) - - def addFailure(self, test, err=None, details=None): - self._check_args(err, details) - if details is not None: - try: - return self.decorated.addFailure(test, details=details) - except TypeError, e: - # have to convert - err = self._details_to_exc_info(details) - return self.decorated.addFailure(test, err) - - def addSkip(self, test, reason=None, details=None): - self._check_args(reason, details) - addSkip = getattr(self.decorated, 'addSkip', None) - if addSkip is None: - return self.decorated.addSuccess(test) - if details is not None: - try: - return addSkip(test, details=details) - except TypeError, e: - # have to convert - reason = self._details_to_str(details) - return addSkip(test, reason) - - def addUnexpectedSuccess(self, test, details=None): - outcome = getattr(self.decorated, 'addUnexpectedSuccess', None) - if outcome is None: - return self.decorated.addSuccess(test) - if details is not None: - try: - return outcome(test, details=details) - except TypeError, e: - pass - return outcome(test) - - def addSuccess(self, test, details=None): - if details is not None: - try: - return self.decorated.addSuccess(test, details=details) - except TypeError, e: - pass - return self.decorated.addSuccess(test) - - def _check_args(self, err, details): - param_count = 0 - if err is not None: - param_count += 1 - if details is not None: - param_count += 1 - if param_count != 1: - raise ValueError("Must pass only one of err '%s' and details '%s" - % (err, details)) - - def _details_to_exc_info(self, details): - """Convert a details dict to an exc_info tuple.""" - return subunit.RemoteError(self._details_to_str(details)) - - def _details_to_str(self, details): - """Convert a details dict to a string.""" - lines = [] - # sorted is for testing, may want to remove that and use a dict - # subclass with defined order for iteritems instead. - for key, content in sorted(details.iteritems()): - if content.content_type.type != 'text': - lines.append('Binary content: %s\n' % key) - continue - lines.append('Text attachment: %s\n' % key) - lines.append('------------\n') - lines.extend(content.iter_bytes()) - if not lines[-1].endswith('\n'): - lines.append('\n') - lines.append('------------\n') - return ''.join(lines) - - def progress(self, offset, whence): - method = getattr(self.decorated, 'progress', None) - if method is None: - return - return method(offset, whence) - - @property - def shouldStop(self): - return self.decorated.shouldStop - - def startTest(self, test): - return self.decorated.startTest(test) - - def startTestRun(self): - try: - return self.decorated.startTestRun() - except AttributeError: - return - - def stop(self): - return self.decorated.stop() - - def stopTest(self, test): - return self.decorated.stopTest(test) - - def stopTestRun(self): - try: - return self.decorated.stopTestRun() - except AttributeError: - return - - def tags(self, new_tags, gone_tags): - method = getattr(self.decorated, 'tags', None) - if method is None: - return - return method(new_tags, gone_tags) - - def time(self, a_datetime): - method = getattr(self.decorated, 'time', None) - if method is None: - return - return method(a_datetime) - - def wasSuccessful(self): - return self.decorated.wasSuccessful() - diff --git a/python/subunit/tests/__init__.py b/python/subunit/tests/__init__.py index fa31d68..a78cec8 100644 --- a/python/subunit/tests/__init__.py +++ b/python/subunit/tests/__init__.py @@ -17,8 +17,6 @@ from subunit.tests import ( TestUtil, test_chunked, - test_content_type, - test_content, test_details, test_progress_model, test_subunit_filter, @@ -32,8 +30,6 @@ from subunit.tests import ( def test_suite(): result = TestUtil.TestSuite() result.addTest(test_chunked.test_suite()) - result.addTest(test_content_type.test_suite()) - result.addTest(test_content.test_suite()) result.addTest(test_details.test_suite()) result.addTest(test_progress_model.test_suite()) result.addTest(test_test_results.test_suite()) diff --git a/python/subunit/tests/test_content.py b/python/subunit/tests/test_content.py deleted file mode 100644 index c13b254..0000000 --- a/python/subunit/tests/test_content.py +++ /dev/null @@ -1,69 +0,0 @@ -# -# subunit: extensions to Python unittest to get test results from subprocesses. -# Copyright (C) 2009 Robert Collins -# -# 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. -# - -import unittest -import subunit -from subunit.content import Content, TracebackContent -from subunit.content_type import ContentType - - -def test_suite(): - loader = subunit.tests.TestUtil.TestLoader() - result = loader.loadTestsFromName(__name__) - return result - - -class TestContent(unittest.TestCase): - - def test___init___None_errors(self): - self.assertRaises(ValueError, Content, None, None) - self.assertRaises(ValueError, Content, None, lambda:["traceback"]) - self.assertRaises(ValueError, Content, - ContentType("text", "traceback"), None) - - def test___init___sets_ivars(self): - content_type = ContentType("foo", "bar") - content = Content(content_type, lambda:["bytes"]) - self.assertEqual(content_type, content.content_type) - self.assertEqual(["bytes"], list(content.iter_bytes())) - - def test___eq__(self): - content_type = ContentType("foo", "bar") - content1 = Content(content_type, lambda:["bytes"]) - content2 = Content(content_type, lambda:["bytes"]) - content3 = Content(content_type, lambda:["by", "tes"]) - content4 = Content(content_type, lambda:["by", "te"]) - content5 = Content(ContentType("f","b"), lambda:["by", "tes"]) - self.assertEqual(content1, content2) - self.assertEqual(content1, content3) - self.assertNotEqual(content1, content4) - self.assertNotEqual(content1, content5) - - -class TestTracebackContent(unittest.TestCase): - - def test___init___None_errors(self): - self.assertRaises(ValueError, TracebackContent, None) - - def test___init___sets_ivars(self): - content = TracebackContent(subunit.RemoteError("weird")) - content_type = ContentType("text", "x-traceback", - {"language":"python"}) - self.assertEqual(content_type, content.content_type) - result = unittest.TestResult() - expected = result._exc_info_to_string(subunit.RemoteError("weird"), - subunit.RemotedTestCase('')) - self.assertEqual(expected, ''.join(list(content.iter_bytes()))) diff --git a/python/subunit/tests/test_content_type.py b/python/subunit/tests/test_content_type.py deleted file mode 100644 index 3a3fa2c..0000000 --- a/python/subunit/tests/test_content_type.py +++ /dev/null @@ -1,50 +0,0 @@ -# -# subunit: extensions to Python unittest to get test results from subprocesses. -# Copyright (C) 2009 Robert Collins -# -# 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. -# - -import unittest -import subunit -from subunit.content_type import ContentType - - -def test_suite(): - loader = subunit.tests.TestUtil.TestLoader() - result = loader.loadTestsFromName(__name__) - return result - - -class TestContentType(unittest.TestCase): - - def test___init___None_errors(self): - self.assertRaises(ValueError, ContentType, None, None) - self.assertRaises(ValueError, ContentType, None, "traceback") - self.assertRaises(ValueError, ContentType, "text", None) - - def test___init___sets_ivars(self): - content_type = ContentType("foo", "bar") - self.assertEqual("foo", content_type.type) - self.assertEqual("bar", content_type.subtype) - self.assertEqual({}, content_type.parameters) - - def test___init___with_parameters(self): - content_type = ContentType("foo", "bar", {"quux":"thing"}) - self.assertEqual({"quux":"thing"}, content_type.parameters) - - def test___eq__(self): - content_type1 = ContentType("foo", "bar", {"quux":"thing"}) - content_type2 = ContentType("foo", "bar", {"quux":"thing"}) - content_type3 = ContentType("foo", "bar", {"quux":"thing2"}) - self.assertTrue(content_type1.__eq__(content_type2)) - self.assertFalse(content_type1.__eq__(content_type3)) diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index c109724..28eb459 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -20,9 +20,11 @@ from StringIO import StringIO import os import sys +from testtools.content import Content, TracebackContent +from testtools.content_type import ContentType + import subunit -from subunit.content import Content, TracebackContent -from subunit.content_type import ContentType +from subunit import _remote_exception_str import subunit.iso8601 as iso8601 from subunit.tests.test_test_results import ( ExtendedTestResult, @@ -67,10 +69,10 @@ 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: Text attachment: traceback\n" + [(bing, _remote_exception_str + ": Text attachment: traceback\n" "------------\nfoo.c:53:ERROR invalid state\n" "------------\n\n")]) self.assertEqual(client.testsRun, 3) @@ -800,7 +802,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) @@ -973,7 +975,7 @@ class TestTestProtocolClient(unittest.TestCase): ContentType('text', 'plain'), lambda:['serialised\nform'])} self.sample_tb_details = dict(self.sample_details) self.sample_tb_details['traceback'] = TracebackContent( - subunit.RemoteError("boo qux")) + subunit.RemoteError("boo qux"), self.test) def test_start_test(self): """Test startTest on a TestProtocolClient.""" @@ -1006,7 +1008,8 @@ class TestTestProtocolClient(unittest.TestCase): 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.""" @@ -1014,14 +1017,14 @@ class TestTestProtocolClient(unittest.TestCase): self.test, details=self.sample_tb_details) self.assertEqual( self.io.getvalue(), - "failure: %s [ multipart\n" + ("failure: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" "F\r\nserialised\nform0\r\n" "Content-Type: text/x-traceback;language=python\n" "traceback\n" - "19\r\nRemoteException: boo qux\n0\r\n" - "]\n" % self.test.id()) + "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.""" @@ -1029,9 +1032,9 @@ 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.""" @@ -1039,14 +1042,14 @@ class TestTestProtocolClient(unittest.TestCase): self.test, details=self.sample_tb_details) self.assertEqual( self.io.getvalue(), - "error: %s [ multipart\n" + ("error: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" "F\r\nserialised\nform0\r\n" "Content-Type: text/x-traceback;language=python\n" "traceback\n" - "19\r\nRemoteException: boo qux\n0\r\n" - "]\n" % self.test.id()) + "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.""" @@ -1054,9 +1057,9 @@ class TestTestProtocolClient(unittest.TestCase): self.test, subunit.RemoteError("phwoar crikey")) self.assertEqual( self.io.getvalue(), - 'xfail: %s [\n' - "RemoteException: phwoar crikey\n" - "]\n" % self.test.id()) + ('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.""" @@ -1064,14 +1067,14 @@ class TestTestProtocolClient(unittest.TestCase): self.test, details=self.sample_tb_details) self.assertEqual( self.io.getvalue(), - "xfail: %s [ multipart\n" + ("xfail: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" "F\r\nserialised\nform0\r\n" "Content-Type: text/x-traceback;language=python\n" "traceback\n" - "19\r\nRemoteException: boo qux\n0\r\n" - "]\n" % self.test.id()) + "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.""" diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index f0944dd..0d7d1a9 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -20,11 +20,12 @@ 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 -from subunit.content_type import ContentType -from subunit.content import Content class LoggingDecorator(subunit.test_results.HookedTestResultDecorator): @@ -145,344 +146,6 @@ class ExtendedTestResult(Python27TestResult): self._calls.append(('time', time)) -class TestExtendedToOriginalResultDecoratorBase(unittest.TestCase): - - def make_26_result(self): - self.result = Python26TestResult() - self.make_converter() - - def make_27_result(self): - self.result = Python27TestResult() - self.make_converter() - - def make_converter(self): - self.converter = \ - subunit.test_results.ExtendedToOriginalDecorator(self.result) - - def make_extended_result(self): - self.result = ExtendedTestResult() - self.make_converter() - - def check_outcome_details(self, outcome): - """Call an outcome with a details dict to be passed through.""" - # This dict is /not/ convertible - thats deliberate, as it should - # not hit the conversion code path. - details = {'foo': 'bar'} - getattr(self.converter, outcome)(self, details=details) - self.assertEqual([(outcome, self, details)], self.result._calls) - - def get_details_and_string(self): - """Get a details dict and expected string.""" - text1 = lambda:["1\n2\n"] - text2 = lambda:["3\n4\n"] - bin1 = lambda:["5\n"] - details = {'text 1': Content(ContentType('text', 'plain'), text1), - 'text 2': Content(ContentType('text', 'strange'), text2), - 'bin 1': Content(ContentType('application', 'binary'), bin1)} - return (details, "Binary content: bin 1\n" - "Text attachment: text 1\n------------\n1\n2\n" - "------------\nText attachment: text 2\n------------\n" - "3\n4\n------------\n") - - def check_outcome_details_to_exec_info(self, outcome, expected=None): - """Call an outcome with a details dict to be made into exc_info.""" - # The conversion is a done using RemoteError and the string contents - # of the text types in the details dict. - if not expected: - expected = outcome - details, err_str = self.get_details_and_string() - getattr(self.converter, outcome)(self, details=details) - err = subunit.RemoteError(err_str) - self.assertEqual([(expected, self, err)], self.result._calls) - - def check_outcome_details_to_nothing(self, outcome, expected=None): - """Call an outcome with a details dict to be swallowed.""" - if not expected: - expected = outcome - details = {'foo': 'bar'} - getattr(self.converter, outcome)(self, details=details) - self.assertEqual([(expected, self)], self.result._calls) - - def check_outcome_details_to_string(self, outcome): - """Call an outcome with a details dict to be stringified.""" - details, err_str = self.get_details_and_string() - getattr(self.converter, outcome)(self, details=details) - self.assertEqual([(outcome, self, err_str)], self.result._calls) - - def check_outcome_exc_info(self, outcome, expected=None): - """Check that calling a legacy outcome still works.""" - # calling some outcome with the legacy exc_info style api (no keyword - # parameters) gets passed through. - if not expected: - expected = outcome - err = subunit.RemoteError("foo\nbar\n") - getattr(self.converter, outcome)(self, err) - self.assertEqual([(expected, self, err)], self.result._calls) - - def check_outcome_exc_info_to_nothing(self, outcome, expected=None): - """Check that calling a legacy outcome on a fallback works.""" - # calling some outcome with the legacy exc_info style api (no keyword - # parameters) gets passed through. - if not expected: - expected = outcome - err = subunit.RemoteError("foo\nbar\n") - getattr(self.converter, outcome)(self, err) - self.assertEqual([(expected, self)], self.result._calls) - - def check_outcome_nothing(self, outcome, expected=None): - """Check that calling a legacy outcome still works.""" - if not expected: - expected = outcome - getattr(self.converter, outcome)(self) - self.assertEqual([(expected, self)], self.result._calls) - - def check_outcome_string_nothing(self, outcome, expected): - """Check that calling outcome with a string calls expected.""" - getattr(self.converter, outcome)(self, "foo") - self.assertEqual([(expected, self)], self.result._calls) - - def check_outcome_string(self, outcome): - """Check that calling outcome with a string works.""" - getattr(self.converter, outcome)(self, "foo") - self.assertEqual([(outcome, self, "foo")], self.result._calls) - - -class TestExtendedToOriginalResultDecorator( - TestExtendedToOriginalResultDecoratorBase): - - def test_progress_py26(self): - self.make_26_result() - self.converter.progress(1, 2) - - def test_progress_py27(self): - self.make_27_result() - self.converter.progress(1, 2) - - def test_progress_pyextended(self): - self.make_extended_result() - self.converter.progress(1, 2) - self.assertEqual([('progress', 1, 2)], self.result._calls) - - def test_shouldStop(self): - self.make_26_result() - self.assertEqual(False, self.converter.shouldStop) - self.converter.decorated.stop() - self.assertEqual(True, self.converter.shouldStop) - - def test_startTest_py26(self): - self.make_26_result() - self.converter.startTest(self) - self.assertEqual([('startTest', self)], self.result._calls) - - def test_startTest_py27(self): - self.make_27_result() - self.converter.startTest(self) - self.assertEqual([('startTest', self)], self.result._calls) - - def test_startTest_pyextended(self): - self.make_extended_result() - self.converter.startTest(self) - self.assertEqual([('startTest', self)], self.result._calls) - - def test_startTestRun_py26(self): - self.make_26_result() - self.converter.startTestRun() - self.assertEqual([], self.result._calls) - - def test_startTestRun_py27(self): - self.make_27_result() - self.converter.startTestRun() - self.assertEqual([('startTestRun',)], self.result._calls) - - def test_startTestRun_pyextended(self): - self.make_extended_result() - self.converter.startTestRun() - self.assertEqual([('startTestRun',)], self.result._calls) - - def test_stopTest_py26(self): - self.make_26_result() - self.converter.stopTest(self) - self.assertEqual([('stopTest', self)], self.result._calls) - - def test_stopTest_py27(self): - self.make_27_result() - self.converter.stopTest(self) - self.assertEqual([('stopTest', self)], self.result._calls) - - def test_stopTest_pyextended(self): - self.make_extended_result() - self.converter.stopTest(self) - self.assertEqual([('stopTest', self)], self.result._calls) - - def test_stopTestRun_py26(self): - self.make_26_result() - self.converter.stopTestRun() - self.assertEqual([], self.result._calls) - - def test_stopTestRun_py27(self): - self.make_27_result() - self.converter.stopTestRun() - self.assertEqual([('stopTestRun',)], self.result._calls) - - def test_stopTestRun_pyextended(self): - self.make_extended_result() - self.converter.stopTestRun() - self.assertEqual([('stopTestRun',)], self.result._calls) - - def test_tags_py26(self): - self.make_26_result() - self.converter.tags(1, 2) - - def test_tags_py27(self): - self.make_27_result() - self.converter.tags(1, 2) - - def test_tags_pyextended(self): - self.make_extended_result() - self.converter.tags(1, 2) - self.assertEqual([('tags', 1, 2)], self.result._calls) - - def test_time_py26(self): - self.make_26_result() - self.converter.time(1) - - def test_time_py27(self): - self.make_27_result() - self.converter.time(1) - - def test_time_pyextended(self): - self.make_extended_result() - self.converter.time(1) - self.assertEqual([('time', 1)], self.result._calls) - - -class TestExtendedToOriginalAddError(TestExtendedToOriginalResultDecoratorBase): - - outcome = 'addError' - - def test_outcome_Original_py26(self): - self.make_26_result() - self.check_outcome_exc_info(self.outcome) - - def test_outcome_Original_py27(self): - self.make_27_result() - self.check_outcome_exc_info(self.outcome) - - def test_outcome_Original_pyextended(self): - self.make_extended_result() - self.check_outcome_exc_info(self.outcome) - - def test_outcome_Extended_py26(self): - self.make_26_result() - self.check_outcome_details_to_exec_info(self.outcome) - - def test_outcome_Extended_py27(self): - self.make_27_result() - self.check_outcome_details_to_exec_info(self.outcome) - - def test_outcome_Extended_pyextended(self): - self.make_extended_result() - self.check_outcome_details(self.outcome) - - def test_outcome__no_details(self): - self.make_extended_result() - self.assertRaises(ValueError, - getattr(self.converter, self.outcome), self) - - -class TestExtendedToOriginalAddFailure( - TestExtendedToOriginalAddError): - - outcome = 'addFailure' - - -class TestExtendedToOriginalAddExpectedFailure( - TestExtendedToOriginalAddError): - - outcome = 'addExpectedFailure' - - def test_outcome_Original_py26(self): - self.make_26_result() - self.check_outcome_exc_info_to_nothing(self.outcome, 'addSuccess') - - def test_outcome_Extended_py26(self): - self.make_26_result() - self.check_outcome_details_to_nothing(self.outcome, 'addSuccess') - - - -class TestExtendedToOriginalAddSkip( - TestExtendedToOriginalResultDecoratorBase): - - outcome = 'addSkip' - - def test_outcome_Original_py26(self): - self.make_26_result() - self.check_outcome_string_nothing(self.outcome, 'addSuccess') - - def test_outcome_Original_py27(self): - self.make_27_result() - self.check_outcome_string(self.outcome) - - def test_outcome_Original_pyextended(self): - self.make_extended_result() - self.check_outcome_string(self.outcome) - - def test_outcome_Extended_py26(self): - self.make_26_result() - self.check_outcome_string_nothing(self.outcome, 'addSuccess') - - def test_outcome_Extended_py27(self): - self.make_27_result() - self.check_outcome_details_to_string(self.outcome) - - def test_outcome_Extended_pyextended(self): - self.make_extended_result() - self.check_outcome_details(self.outcome) - - def test_outcome__no_details(self): - self.make_extended_result() - self.assertRaises(ValueError, - getattr(self.converter, self.outcome), self) - - -class TestExtendedToOriginalAddSuccess( - TestExtendedToOriginalResultDecoratorBase): - - outcome = 'addSuccess' - expected = 'addSuccess' - - def test_outcome_Original_py26(self): - self.make_26_result() - self.check_outcome_nothing(self.outcome, self.expected) - - def test_outcome_Original_py27(self): - self.make_27_result() - self.check_outcome_nothing(self.outcome) - - def test_outcome_Original_pyextended(self): - self.make_extended_result() - self.check_outcome_nothing(self.outcome) - - def test_outcome_Extended_py26(self): - self.make_26_result() - self.check_outcome_details_to_nothing(self.outcome, self.expected) - - def test_outcome_Extended_py27(self): - self.make_27_result() - self.check_outcome_details_to_nothing(self.outcome) - - def test_outcome_Extended_pyextended(self): - self.make_extended_result() - self.check_outcome_details(self.outcome) - - -class TestExtendedToOriginalAddUnexpectedSuccess( - TestExtendedToOriginalAddSuccess): - - outcome = 'addUnexpectedSuccess' - - class TestHookedTestResultDecorator(unittest.TestCase): def setUp(self): -- 2.34.1