Hook addFailure to to details.
authorRobert Collins <robertc@robertcollins.net>
Sun, 4 Oct 2009 19:20:25 +0000 (06:20 +1100)
committerRobert Collins <robertc@robertcollins.net>
Sun, 4 Oct 2009 19:20:25 +0000 (06:20 +1100)
python/subunit/__init__.py
python/subunit/content.py
python/subunit/tests/test_test_protocol.py

index 4edd69d584ba12b266f24cba79df9644562dc269..9d021fe6d452f09ed6976ddb014630d272ff4eb8 100644 (file)
@@ -46,8 +46,9 @@ 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 extra
 details, tags, timestamping and progress markers).
 
-The test outcome methods ``addSuccess`` take an optional keyword parameter
-``details`` which can be used instead of the usual python unittest parameter.
+The test outcome methods ``addSuccess``, ``addFailure`` 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
 the Python Testing In Python mail list, with the goal of permitting a common
@@ -479,11 +480,28 @@ class TestProtocolClient(unittest.TestResult):
             self._stream.write("%s\n" % line)
         self._stream.write("]\n")
 
-    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 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._stream.write("failure: %s" % 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):
@@ -498,14 +516,7 @@ class TestProtocolClient(unittest.TestResult):
         if not details:
             self._stream.write("\n")
         else:
-            self._stream.write(" [ multipart\n")
-            for name, content in details.iteritems():
-                self._stream.write("Content-Type: %s/%s\n" %
-                    (content.content_type.type, content.content_type.subtype))
-                self._stream.write("%s\n" % name)
-                for bytes in content.iter_bytes():
-                    self._stream.write("%d\n%s" % (len(bytes), bytes))
-                self._stream.write("0\n")
+            self._write_details(details)
             self._stream.write("]\n")
 
     def startTest(self, test):
@@ -544,6 +555,27 @@ 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)
+            for bytes in content.iter_bytes():
+                self._stream.write("%d\n%s" % (len(bytes), bytes))
+            self._stream.write("0\n")
+
     def done(self):
         """Obey the testtools result.done() interface."""
 
index cd3ad60ecd0164cd67fd4d09902c739ca92caf03..160a58aa21f3af7eea834866646647dc714df61f 100644 (file)
@@ -63,5 +63,5 @@ class TracebackContent(Content):
             {"language": "python"})
         self._result = TestResult()
         super(TracebackContent, self).__init__(content_type,
-            lambda:self._result._exc_info_to_string(err,
-                subunit.RemotedTestCase('')))
+            lambda:[self._result._exc_info_to_string(err,
+                subunit.RemotedTestCase(''))])
index f505626b254359cc49d89319a61b16245ee9467b..1b482903bd8266bd9ac68450e6e2c98b961c2e60 100644 (file)
@@ -21,7 +21,7 @@ import os
 import sys
 
 import subunit
-from subunit.content import Content
+from subunit.content import Content, TracebackContent
 from subunit.content_type import ContentType
 import subunit.iso8601 as iso8601
 
@@ -1056,6 +1056,9 @@ class TestTestProtocolClient(unittest.TestCase):
         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"))
 
     def test_start_test(self):
         """Test startTest on a TestProtocolClient."""
@@ -1074,13 +1077,13 @@ class TestTestProtocolClient(unittest.TestCase):
             self.io.getvalue(), "successful: %s\n" % self.test.id())
 
     def test_add_success_details(self):
-        """Test addSuccess on a TestProtocolClient."""
+        """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"
-                "15\nserialised\nform0\n]\n"% self.test.id())
+                "15\nserialised\nform0\n]\n" % self.test.id())
 
     def test_add_failure(self):
         """Test addFailure on a TestProtocolClient."""
@@ -1090,6 +1093,22 @@ class TestTestProtocolClient(unittest.TestCase):
             self.io.getvalue(),
             'failure: %s [\nRemoteException: 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"
+            "15\nserialised\nform0\n"
+            "Content-Type: text/x-traceback;language=python\n"
+            "traceback\n"
+            "25\nRemoteException: boo qux\n0\n"
+            "]\n" % self.test.id())
+
+
     def test_add_error(self):
         """Test stopTest on a TestProtocolClient."""
         self.protocol.addError(