Move details parsing into a separate class.
authorRobert Collins <robertc@robertcollins.net>
Tue, 13 Oct 2009 01:46:15 +0000 (12:46 +1100)
committerRobert Collins <robertc@robertcollins.net>
Tue, 13 Oct 2009 01:46:15 +0000 (12:46 +1100)
Makefile.am
python/subunit/__init__.py
python/subunit/details.py [new file with mode: 0644]
python/subunit/tests/__init__.py
python/subunit/tests/test_details.py [new file with mode: 0644]
python/subunit/tests/test_test_protocol.py

index 8042f14e7cf204c71405c727ec651255fa50a80c..4085620ad912e27c04f8497ca4941717c6aa12a8 100644 (file)
@@ -19,6 +19,7 @@ EXTRA_DIST =  \
        python/subunit/tests/test_chunked.py \
        python/subunit/tests/test_content.py \
        python/subunit/tests/test_content_type.py \
+       python/subunit/tests/test_details.py \
        python/subunit/tests/test_subunit_filter.py \
        python/subunit/tests/test_subunit_stats.py \
        python/subunit/tests/test_subunit_tags.py \
@@ -68,6 +69,7 @@ pkgpython_PYTHON = \
        python/subunit/chunked.py \
        python/subunit/content.py \
        python/subunit/content_type.py \
+       python/subunit/details.py \
        python/subunit/iso8601.py \
        python/subunit/progress_model.py \
        python/subunit/run.py \
index c7edf6ea2a1c090582597877ee43f546a1e9c209..b4150a3b2155db781f8506d143f47a4e524ed99d 100644 (file)
@@ -126,7 +126,7 @@ import unittest
 
 import iso8601
 
-import chunked, content, content_type
+import chunked, content, content_type, details
 
 
 PROGRESS_SET = 0
@@ -256,11 +256,11 @@ class _InTest(_ParserState):
             self.parser._current_test = None
         elif self.parser.current_test_description + " [" == line[offset:-1]:
             self.parser._state = details_state
-            self.parser._message = ""
+            details_state.set_simple()
         elif self.parser.current_test_description + " [ multipart" == \
             line[offset:-1]:
             self.parser._state = details_state
-            self.parser._message = ""
+            details_state.set_multipart()
         else:
             self.parser.stdOutLineReceived(line)
 
@@ -330,7 +330,7 @@ class _OutSideTest(_ParserState):
 class _ReadingDetails(_ParserState):
     """Common logic for readin state details."""
 
-    def _endQuote(self, line):
+    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
@@ -339,9 +339,7 @@ class _ReadingDetails(_ParserState):
 
     def lineReceived(self, line):
         """a line has been received."""
-        if line == "]\n":
-            self._endQuote(line)
-        self.parser._appendMessage(line)
+        self.details_parser.lineReceived(line)
 
     def lostConnection(self):
         """Connection lost."""
@@ -352,13 +350,21 @@ class _ReadingDetails(_ParserState):
         """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,
-                               RemoteError(self.parser._message))
+            RemoteError(self.details_parser.get_message()))
 
     def _outcome_label(self):
         return "failure"
@@ -369,7 +375,7 @@ class _ReadingErrorDetails(_ReadingDetails):
 
     def _report_outcome(self):
         self.parser.client.addError(self.parser._current_test,
-                             RemoteError(self.parser._message))
+            RemoteError(self.details_parser.get_message()))
 
     def _outcome_label(self):
         return "error"
@@ -381,7 +387,8 @@ class _ReadingExpectedFailureDetails(_ReadingDetails):
     def _report_outcome(self):
         xfail = getattr(self.parser.client, 'addExpectedFailure', None)
         if callable(xfail):
-            xfail(self.parser._current_test, RemoteError(self.parser._message))
+            xfail(self.parser._current_test,
+                RemoteError(self.details_parser.get_message()))
         else:
             self.parser.client.addSuccess(self.parser._current_test)
 
@@ -393,7 +400,7 @@ class _ReadingSkipDetails(_ReadingDetails):
     """State for the subunit parser when reading skip details."""
 
     def _report_outcome(self):
-        self.parser._skip_or_error(self.parser._message)
+        self.parser._skip_or_error(self.details_parser.get_message())
 
     def _outcome_label(self):
         return "skip"
@@ -449,13 +456,6 @@ class TestProtocolServer(object):
                 message = "No reason given"
             addSkip(self._current_test, message)
 
-    def _appendMessage(self, line):
-        if line[0:2] == " ]":
-            # quoted ] start
-            self._message += line[1:]
-        else:
-            self._message += line
-
     def _handleProgress(self, offset, line):
         """Process a progress directive."""
         line = line[offset:].strip()
diff --git a/python/subunit/details.py b/python/subunit/details.py
new file mode 100644 (file)
index 0000000..4f121cc
--- /dev/null
@@ -0,0 +1,58 @@
+#
+#  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."""
+
+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):
+        return None
+
+    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 = {}
+
+    def get_details(self):
+        return self._details
+
+    def get_message(self):
+        return None
index 88694258ae3f5b20ea20fb145212228d17ffd89d..fa31d689239201ac0edda883f34006144ff5a697 100644 (file)
@@ -19,6 +19,7 @@ from subunit.tests import (
     test_chunked,
     test_content_type,
     test_content,
+    test_details,
     test_progress_model,
     test_subunit_filter,
     test_subunit_stats,
@@ -33,6 +34,7 @@ def test_suite():
     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())
     result.addTest(test_test_protocol.test_suite())
diff --git a/python/subunit/tests/test_details.py b/python/subunit/tests/test_details.py
new file mode 100644 (file)
index 0000000..bc0930a
--- /dev/null
@@ -0,0 +1,62 @@
+#
+#  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 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_is_None(self):
+        parser = details.SimpleDetailsParser(None)
+        self.assertEqual(None, parser.get_details())
+
+
+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())
index ac8733d6b8cf9e5db18b52bc6d8e7f057eb6b6e2..0994c54f0eb297d29e70ccd3c43da17c8bee98b1 100644 (file)
@@ -453,6 +453,24 @@ class TestTestProtocolServerLostConnection(unittest.TestCase):
         self.do_connection_lost("xfail", "[ multipart\n")
 
 
+class TestInTestMultipart(unittest.TestCase):
+
+    def setUp(self):
+        self.client = MockTestProtocolServerClient()
+        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):