Use testtools facilities for the details API.
authorRobert Collins <robertc@robertcollins.net>
Fri, 11 Dec 2009 02:41:42 +0000 (13:41 +1100)
committerRobert Collins <robertc@robertcollins.net>
Fri, 11 Dec 2009 02:41:42 +0000 (13:41 +1100)
12 files changed:
INSTALL
NEWS
python/subunit/__init__.py
python/subunit/content.py [deleted file]
python/subunit/content_type.py [deleted file]
python/subunit/details.py
python/subunit/test_results.py
python/subunit/tests/__init__.py
python/subunit/tests/test_content.py [deleted file]
python/subunit/tests/test_content_type.py [deleted file]
python/subunit/tests/test_test_protocol.py
python/subunit/tests/test_test_results.py

diff --git a/INSTALL b/INSTALL
index dc4fb55e981fd206133f263a229452d65be20aab..ab716dc8b6725dacdb6dcfe6ef28807ccd4b5ffc 100644 (file)
--- 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 f261afc9c864f4da30cc216ce445d6a80c8f1537..6036a5479abd674ef7f4611d4f179c8e327d716e 100644 (file)
--- 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``.
 
index dd3c0f16b7d11bc7945eec5558fb476548f1d771..b5f622da0d9f9b9ac5974f56e0a995c00914c98a 100644 (file)
@@ -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 (file)
index 5961e24..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-#
-#  subunit: extensions to Python unittest to get test results from subprocesses.
-#  Copyright (C) 2009  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.
-#
-
-"""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 "<Content type=%r, value=%r>" % (
-            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 (file)
index 3aade6c..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-#
-#  subunit: extensions to Python unittest to get test results from subprocesses.
-#  Copyright (C) 2009  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.
-#
-
-"""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)
index 2cedba2bfb032d35a120c5b0d152b0abefe44540..65a04046d9b89a2df3f5ff0442fa4ddd8a182517 100644 (file)
@@ -18,7 +18,9 @@
 
 from cStringIO import StringIO
 
-import chunked, content, content_type
+from testtools import content, content_type
+
+import chunked
 
 
 class DetailsParser(object):
index 8bfdce551ba2c26b811b2ce1f9a0f7c79e9711b7..4ccc2aab3517219e70b340042f7f6a34ed81fdb6 100644 (file)
@@ -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()
-
index fa31d689239201ac0edda883f34006144ff5a697..a78cec85722c81b366813ee9a99f6118feb304bb 100644 (file)
@@ -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 (file)
index c13b254..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-#
-#  subunit: extensions to Python unittest to get test results from subprocesses.
-#  Copyright (C) 2009  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.
-#
-
-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 (file)
index 3a3fa2c..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-#  subunit: extensions to Python unittest to get test results from subprocesses.
-#  Copyright (C) 2009  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.
-#
-
-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))
index c109724deb8cbdfbb799c15a755f0a80ba91fb18..28eb459be8fd80551edbcd7467c41785cf2e73de 100644 (file)
@@ -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."""
index f0944dd3ea0dfeabe6ec84ef42745fc9bdc2b13d..0d7d1a9b431efb607c159f9436efa90fb48676ce 100644 (file)
@@ -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):