From 47dc87aec4dc886ae625074c7d03410422453a31 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 4 Oct 2009 21:09:40 +1100 Subject: Add ContentType class. --- python/subunit/content_type.py | 35 ++++++++++++++++++++++ python/subunit/tests/__init__.py | 2 ++ python/subunit/tests/test_content_type.py | 49 +++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 python/subunit/content_type.py create mode 100644 python/subunit/tests/test_content_type.py (limited to 'python') diff --git a/python/subunit/content_type.py b/python/subunit/content_type.py new file mode 100644 index 0000000..cafc437 --- /dev/null +++ b/python/subunit/content_type.py @@ -0,0 +1,35 @@ +# +# 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 {} diff --git a/python/subunit/tests/__init__.py b/python/subunit/tests/__init__.py index 36e93ba..d0648f2 100644 --- a/python/subunit/tests/__init__.py +++ b/python/subunit/tests/__init__.py @@ -16,6 +16,7 @@ from subunit.tests import ( TestUtil, + test_content_type, test_progress_model, test_subunit_filter, test_subunit_stats, @@ -27,6 +28,7 @@ from subunit.tests import ( def test_suite(): result = TestUtil.TestSuite() + result.addTest(test_content_type.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_content_type.py b/python/subunit/tests/test_content_type.py new file mode 100644 index 0000000..fea9a9b --- /dev/null +++ b/python/subunit/tests/test_content_type.py @@ -0,0 +1,49 @@ +# +# 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 datetime +import unittest +from StringIO import StringIO +import os +import subunit +from subunit.content_type import ContentType +import sys + +import subunit.iso8601 as iso8601 + + +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) -- cgit v1.2.1 From 5bf0f62627c4fecdcac1c5044f4e3aba03ad1e30 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 4 Oct 2009 21:23:16 +1100 Subject: Add subunit.Content python class for serialisation. --- python/subunit/content.py | 41 +++++++++++++++++++++++++++++++ python/subunit/tests/__init__.py | 2 ++ python/subunit/tests/test_content.py | 41 +++++++++++++++++++++++++++++++ python/subunit/tests/test_content_type.py | 6 ----- 4 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 python/subunit/content.py create mode 100644 python/subunit/tests/test_content.py (limited to 'python') diff --git a/python/subunit/content.py b/python/subunit/content.py new file mode 100644 index 0000000..517862a --- /dev/null +++ b/python/subunit/content.py @@ -0,0 +1,41 @@ +# +# 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.""" + +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 iter_bytes(self): + """Iterate over bytestrings of the serialised content.""" + return self._get_bytes() diff --git a/python/subunit/tests/__init__.py b/python/subunit/tests/__init__.py index d0648f2..d842c7e 100644 --- a/python/subunit/tests/__init__.py +++ b/python/subunit/tests/__init__.py @@ -17,6 +17,7 @@ from subunit.tests import ( TestUtil, test_content_type, + test_content, test_progress_model, test_subunit_filter, test_subunit_stats, @@ -29,6 +30,7 @@ from subunit.tests import ( def test_suite(): result = TestUtil.TestSuite() result.addTest(test_content_type.test_suite()) + result.addTest(test_content.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_content.py b/python/subunit/tests/test_content.py new file mode 100644 index 0000000..869717e --- /dev/null +++ b/python/subunit/tests/test_content.py @@ -0,0 +1,41 @@ +# +# 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 +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())) diff --git a/python/subunit/tests/test_content_type.py b/python/subunit/tests/test_content_type.py index fea9a9b..7e24316 100644 --- a/python/subunit/tests/test_content_type.py +++ b/python/subunit/tests/test_content_type.py @@ -14,15 +14,9 @@ # limitations under that license. # -import datetime import unittest -from StringIO import StringIO -import os import subunit from subunit.content_type import ContentType -import sys - -import subunit.iso8601 as iso8601 def test_suite(): -- cgit v1.2.1 From 869d2a4f792a4d5d2c9f7f55d95ba216f0fbe4f1 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 5 Oct 2009 05:22:14 +1100 Subject: Hook up addSuccess with a details parameter. --- python/subunit/__init__.py | 30 +++++++++++++++++++++++++----- python/subunit/tests/test_test_protocol.py | 15 ++++++++++++++- 2 files changed, 39 insertions(+), 6 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index d92e1bd..4edd69d 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -41,10 +41,18 @@ Twisted. See the ``TestProtocolServer`` parser class for more details. Subunit includes extensions to the Python ``TestResult`` protocol. These are all done in a compatible manner: ``TestResult`` objects that do not implement -the extension methods will not cause errors to be raised, instead the extesion +the extension methods will not cause errors to be raised, instead the extension will either lose fidelity (for instance, folding expected failures to success -in Python versions < 2.7 or 3.1), or discard the extended data (for tags, -timestamping and progress markers). +in Python versions < 2.7 or 3.1), or discard the extended data (for extra +details, tags, timestamping and progress markers). + +The test outcome methods ``addSuccess`` 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 +way to provide additional data beyond a traceback, such as captured data from +disk, logging messages etc. The ``tags(new_tags, gone_tags)`` method is called (if present) to add or remove tags in the test run that is currently executing. If called when no @@ -484,9 +492,21 @@ class TestProtocolClient(unittest.TestResult): self._stream.write("%s\n" % reason) self._stream.write("]\n") - def addSuccess(self, test): + def addSuccess(self, test, details=None): """Report a success in a test.""" - self._stream.write("successful: %s\n" % test.id()) + self._stream.write("successful: %s" % test.id()) + if not details: + self._stream.write("\n") + else: + self._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._stream.write("]\n") def startTest(self, test): """Mark a test as starting its test run.""" diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 00a21ed..f505626 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -18,9 +18,11 @@ import datetime import unittest from StringIO import StringIO import os -import subunit import sys +import subunit +from subunit.content import Content +from subunit.content_type import ContentType import subunit.iso8601 as iso8601 @@ -1052,6 +1054,8 @@ class TestTestProtocolClient(unittest.TestCase): self.io = StringIO() self.protocol = subunit.TestProtocolClient(self.io) self.test = TestTestProtocolClient("test_start_test") + self.sample_details = {'something':Content( + ContentType('text', 'plain'), lambda:['serialised\nform'])} def test_start_test(self): """Test startTest on a TestProtocolClient.""" @@ -1069,6 +1073,15 @@ class TestTestProtocolClient(unittest.TestCase): self.assertEqual( self.io.getvalue(), "successful: %s\n" % self.test.id()) + def test_add_success_details(self): + """Test addSuccess on a TestProtocolClient.""" + 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()) + def test_add_failure(self): """Test addFailure on a TestProtocolClient.""" self.protocol.addFailure( -- cgit v1.2.1 From 1bb81bd80c26f41555f676d9f6b164be9c061e61 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 5 Oct 2009 05:53:44 +1100 Subject: Create TracebackContent for adapting exc_info tuples. --- python/subunit/content.py | 26 ++++++++++++++++++++++++++ python/subunit/content_type.py | 8 ++++++++ python/subunit/tests/test_content.py | 18 +++++++++++++++++- python/subunit/tests/test_content_type.py | 7 +++++++ 4 files changed, 58 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/content.py b/python/subunit/content.py index 517862a..cd3ad60 100644 --- a/python/subunit/content.py +++ b/python/subunit/content.py @@ -16,6 +16,12 @@ """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. @@ -39,3 +45,23 @@ class Content(object): def iter_bytes(self): """Iterate over bytestrings of the serialised content.""" return self._get_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 index cafc437..3aade6c 100644 --- a/python/subunit/content_type.py +++ b/python/subunit/content_type.py @@ -33,3 +33,11 @@ class ContentType(object): 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/tests/test_content.py b/python/subunit/tests/test_content.py index 869717e..8075cf9 100644 --- a/python/subunit/tests/test_content.py +++ b/python/subunit/tests/test_content.py @@ -16,7 +16,7 @@ import unittest import subunit -from subunit.content import Content +from subunit.content import Content, TracebackContent from subunit.content_type import ContentType @@ -39,3 +39,19 @@ class TestContent(unittest.TestCase): content = Content(content_type, lambda:["bytes"]) self.assertEqual(content_type, content.content_type) self.assertEqual(["bytes"], list(content.iter_bytes())) + + +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 index 7e24316..3a3fa2c 100644 --- a/python/subunit/tests/test_content_type.py +++ b/python/subunit/tests/test_content_type.py @@ -41,3 +41,10 @@ class TestContentType(unittest.TestCase): 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)) -- cgit v1.2.1 From aa757dce0d1ca43a66a5f008e05699a0230b0d59 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 5 Oct 2009 06:20:25 +1100 Subject: Hook addFailure to to details. --- python/subunit/__init__.py | 62 ++++++++++++++++++++++-------- python/subunit/content.py | 4 +- python/subunit/tests/test_test_protocol.py | 25 ++++++++++-- 3 files changed, 71 insertions(+), 20 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 4edd69d..9d021fe 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -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.""" diff --git a/python/subunit/content.py b/python/subunit/content.py index cd3ad60..160a58a 100644 --- a/python/subunit/content.py +++ b/python/subunit/content.py @@ -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(''))]) diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index f505626..1b48290 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -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( -- cgit v1.2.1 From 446d6f69537d2fb2bff212e3f8ebc3f36b525740 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Wed, 7 Oct 2009 23:19:12 +1100 Subject: Hook addError up to the details protocol. --- python/subunit/__init__.py | 45 +++++++++++++++++++++++------- python/subunit/tests/test_test_protocol.py | 16 ++++++++++- 2 files changed, 50 insertions(+), 11 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 9d021fe..1c6937c 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -46,9 +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``, ``addFailure`` take an optional -keyword parameter ``details`` which can be used instead of the usual python -unittest parameter. +The test outcome methods ``addSuccess``, ``addError``, ``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 @@ -473,12 +473,20 @@ class TestProtocolClient(unittest.TestResult): unittest.TestResult.__init__(self) self._stream = stream - def addError(self, test, error): - """Report an error in test test.""" - self._stream.write("error: %s [\n" % test.id()) - for line in self._exc_info_to_string(error, test).splitlines(): - self._stream.write("%s\n" % line) - self._stream.write("]\n") + def addError(self, test, error=None, details=None): + """Report an error in test test. + + Only one of error and details should be provided: conceptually there + are two separate methods: + addError(self, test, error) + addError(self, test, details) + + :param error: Standard unittest positional argument form - an + exc_info tuple. + :param details: New Testing-in-python drafted API; a dict from string + to subunit.Content objects. + """ + self._addOutcome("error", test, error=error, details=details) def addFailure(self, test, error=None, details=None): """Report a failure in test test. @@ -493,7 +501,24 @@ class TestProtocolClient(unittest.TestResult): :param details: New Testing-in-python drafted API; a dict from string to subunit.Content objects. """ - self._stream.write("failure: %s" % test.id()) + self._addOutcome("failure", test, error=error, details=details) + + def _addOutcome(self, outcome, test, error=None, details=None): + """Report a failure in test test. + + Only one of error and details should be provided: conceptually there + are two separate methods: + addOutcome(self, test, error) + addOutcome(self, test, details) + + :param outcome: A string describing the outcome - used as the + event name in the subunit stream. + :param error: Standard unittest positional argument form - an + exc_info tuple. + :param details: New Testing-in-python drafted API; a dict from string + to subunit.Content objects. + """ + self._stream.write("%s: %s" % (outcome, test.id())) if error is None and details is None: raise ValueError if error is not None: diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 1b48290..1700de6 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -1108,7 +1108,6 @@ class TestTestProtocolClient(unittest.TestCase): "25\nRemoteException: boo qux\n0\n" "]\n" % self.test.id()) - def test_add_error(self): """Test stopTest on a TestProtocolClient.""" self.protocol.addError( @@ -1119,6 +1118,21 @@ class TestTestProtocolClient(unittest.TestCase): "RemoteException: phwoar crikey\n" "]\n" % self.test.id()) + def test_add_error_details(self): + """Test stopTest on a TestProtocolClient with details.""" + self.protocol.addError( + self.test, details=self.sample_tb_details) + self.assertEqual( + self.io.getvalue(), + "error: %s [ multipart\n" + "Content-Type: text/plain\n" + "something\n" + "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_skip(self): """Test addSkip on a TestProtocolClient.""" self.protocol.addSkip( -- cgit v1.2.1 From 1af90d7941a17338fc9a296ec5dce7ab79ade550 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Thu, 8 Oct 2009 23:23:07 +1100 Subject: Wire up addSkip to details. --- python/subunit/__init__.py | 19 ++++++++++++------- python/subunit/tests/test_test_protocol.py | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 1c6937c..a63a433 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -46,9 +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``, ``addError``, ``addFailure`` take an -optional keyword parameter ``details`` which can be used instead of the usual -python unittest parameter. +The test outcome methods ``addSuccess``, ``addError``, ``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 the Python Testing In Python mail list, with the goal of permitting a common @@ -119,6 +119,8 @@ import unittest import iso8601 +import content, content_type + PROGRESS_SET = 0 PROGRESS_CUR = 1 @@ -529,11 +531,14 @@ class TestProtocolClient(unittest.TestResult): self._write_details(details) self._stream.write("]\n") - def addSkip(self, test, reason): + def addSkip(self, test, reason=None, details=None): """Report a skipped test.""" - self._stream.write("skip: %s [\n" % test.id()) - self._stream.write("%s\n" % reason) - self._stream.write("]\n") + if reason is None: + self._addOutcome("skip", test, error=None, details=details) + else: + self._stream.write("skip: %s [\n" % test.id()) + self._stream.write("%s\n" % reason) + self._stream.write("]\n") def addSuccess(self, test, details=None): """Report a success in a test.""" diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 1700de6..4434710 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -1140,6 +1140,20 @@ class TestTestProtocolClient(unittest.TestCase): self.assertEqual( self.io.getvalue(), 'skip: %s [\nHas it really?\n]\n' % self.test.id()) + + def test_add_skip_details(self): + """Test addSkip on a TestProtocolClient with details.""" + details = {'reason':Content( + ContentType('text', 'plain'), lambda:['Has it really?'])} + self.protocol.addSkip( + self.test, details=details) + self.assertEqual( + self.io.getvalue(), + "skip: %s [ multipart\n" + "Content-Type: text/plain\n" + "reason\n" + "14\nHas it really?0\n" + "]\n" % self.test.id()) def test_progress_set(self): self.protocol.progress(23, subunit.PROGRESS_SET) -- cgit v1.2.1 From af6f8521c7d26966b63c66d164cc5a7e01d2e86b Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Fri, 9 Oct 2009 14:42:46 +1100 Subject: Add support for addExpectedFailure in the Subunit python serialiser. --- python/subunit/__init__.py | 21 ++++++++++++++++++--- python/subunit/tests/test_test_protocol.py | 25 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index a63a433..b5d7d76 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -46,9 +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``, ``addError``, ``addFailure``, -``addSkip`` take an optional keyword parameter ``details`` which can be used -instead of the usual python unittest parameter. +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 the Python Testing In Python mail list, with the goal of permitting a common @@ -490,6 +490,21 @@ class TestProtocolClient(unittest.TestResult): """ self._addOutcome("error", test, error=error, details=details) + def addExpectedFailure(self, test, error=None, details=None): + """Report an expected failure in test test. + + Only one of error and details should be provided: conceptually there + are two separate methods: + addError(self, test, error) + addError(self, test, details) + + :param error: Standard unittest positional argument form - an + exc_info tuple. + :param details: New Testing-in-python drafted API; a dict from string + to subunit.Content objects. + """ + self._addOutcome("xfail", test, error=error, details=details) + def addFailure(self, test, error=None, details=None): """Report a failure in test test. diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 4434710..e909d3f 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -1133,6 +1133,31 @@ class TestTestProtocolClient(unittest.TestCase): "25\nRemoteException: boo qux\n0\n" "]\n" % self.test.id()) + def test_add_expected_failure(self): + """Test addExpectedFailure on a TestProtocolClient.""" + self.protocol.addExpectedFailure( + self.test, subunit.RemoteError("phwoar crikey")) + self.assertEqual( + self.io.getvalue(), + 'xfail: %s [\n' + "RemoteException: phwoar crikey\n" + "]\n" % self.test.id()) + + def test_add_expected_failure_details(self): + """Test addExpectedFailure on a TestProtocolClient with details.""" + self.protocol.addExpectedFailure( + self.test, details=self.sample_tb_details) + self.assertEqual( + self.io.getvalue(), + "xfail: %s [ multipart\n" + "Content-Type: text/plain\n" + "something\n" + "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_skip(self): """Test addSkip on a TestProtocolClient.""" self.protocol.addSkip( -- cgit v1.2.1 From cc36797e0831ed019dbfb4b37b33636f5c25abfa Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Fri, 9 Oct 2009 15:05:30 +1100 Subject: Support addUnexpectedSuccess. --- python/subunit/__init__.py | 1 + python/subunit/tests/test_test_protocol.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index b5d7d76..5b482e3 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -563,6 +563,7 @@ class TestProtocolClient(unittest.TestResult): else: self._write_details(details) self._stream.write("]\n") + addUnexpectedSuccess = addSuccess def startTest(self, test): """Mark a test as starting its test run.""" diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index e909d3f..c2c62bc 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -1208,6 +1208,21 @@ class TestTestProtocolClient(unittest.TestCase): "time: 2009-10-11 12:13:14.000015Z\n", self.io.getvalue()) + def test_add_unexpected_success(self): + """Test addUnexpectedSuccess on a TestProtocolClient.""" + self.protocol.addUnexpectedSuccess(self.test) + self.assertEqual( + self.io.getvalue(), "successful: %s\n" % self.test.id()) + + def test_add_unexpected_success_details(self): + """Test addUnexpectedSuccess on a TestProtocolClient with details.""" + self.protocol.addUnexpectedSuccess(self.test, details=self.sample_details) + self.assertEqual( + self.io.getvalue(), "successful: %s [ multipart\n" + "Content-Type: text/plain\n" + "something\n" + "15\nserialised\nform0\n]\n" % self.test.id()) + def test_suite(): loader = subunit.tests.TestUtil.TestLoader() -- cgit v1.2.1 From 40ae70b04c7c88ed80a5e5b3f340f0c523b95e59 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sat, 10 Oct 2009 14:42:32 +1100 Subject: Move chunking to be \r\n based and create a dedicated module with that logic. --- python/subunit/__init__.py | 15 +++++-- python/subunit/chunked.py | 63 +++++++++++++++++++++++++++++ python/subunit/tests/__init__.py | 2 + python/subunit/tests/test_chunked.py | 65 ++++++++++++++++++++++++++++++ python/subunit/tests/test_test_protocol.py | 18 ++++----- 5 files changed, 150 insertions(+), 13 deletions(-) create mode 100644 python/subunit/chunked.py create mode 100644 python/subunit/tests/test_chunked.py (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index f06437c..a93b6eb 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -107,6 +107,13 @@ result object:: # And run your suite as normal, Subunit will exec each external script as # needed and report to your result object. suite.run(result) + +Utility modules +--------------- + +* subunit.chunked contains HTTP chunked encoding/decoding logic. +* subunit.content contains a minimal assumptions MIME content representation. +* subunit.content_type contains a MIME Content-Type representation. """ import datetime @@ -119,7 +126,7 @@ import unittest import iso8601 -import content, content_type +import chunked, content, content_type PROGRESS_SET = 0 @@ -618,9 +625,9 @@ class TestProtocolClient(unittest.TestResult): 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") + encoder = chunked.Encoder(self._stream) + map(encoder.write, content.iter_bytes()) + encoder.close() def done(self): """Obey the testtools result.done() interface.""" diff --git a/python/subunit/chunked.py b/python/subunit/chunked.py new file mode 100644 index 0000000..2cfc7ff --- /dev/null +++ b/python/subunit/chunked.py @@ -0,0 +1,63 @@ +# +# subunit: extensions to python unittest to get test results from subprocesses. +# Copyright (C) 2005 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. +# + +"""Encoder/decoder for http style chunked encoding.""" + +class Encoder(object): + """Encode content to a stream using HTTP Chunked coding.""" + + def __init__(self, output): + """Create an encoder encoding to output. + + :param output: A file-like object. Bytes written to the Encoder + will be encoded using HTTP chunking. Small writes may be buffered + and the ``close`` method must be called to finish the stream. + """ + self.output = output + self.buffered_bytes = [] + self.buffer_size = 0 + + def flush(self, extra_len=0): + """Flush the encoder to the output stream. + + :param extra_len: Increase the size of the chunk by this many bytes + to allow for a subsequent write. + """ + if not self.buffer_size and not extra_len: + return + buffered_bytes = self.buffered_bytes + buffer_size = self.buffer_size + self.buffered_bytes = [] + self.buffer_size = 0 + self.output.write("%X\r\n" % (buffer_size + extra_len)) + if buffer_size: + self.output.write(''.join(buffered_bytes)) + return True + + def write(self, bytes): + """Encode bytes to the output stream.""" + bytes_len = len(bytes) + if self.buffer_size + bytes_len >= 65536: + self.flush(bytes_len) + self.output.write(bytes) + else: + self.buffered_bytes.append(bytes) + self.buffer_size += bytes_len + + def close(self): + """Finish the stream. This does not close the output stream.""" + self.flush() + self.output.write("0\r\n") diff --git a/python/subunit/tests/__init__.py b/python/subunit/tests/__init__.py index d842c7e..8869425 100644 --- a/python/subunit/tests/__init__.py +++ b/python/subunit/tests/__init__.py @@ -16,6 +16,7 @@ from subunit.tests import ( TestUtil, + test_chunked, test_content_type, test_content, test_progress_model, @@ -29,6 +30,7 @@ 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_progress_model.test_suite()) diff --git a/python/subunit/tests/test_chunked.py b/python/subunit/tests/test_chunked.py new file mode 100644 index 0000000..2bf82b2 --- /dev/null +++ b/python/subunit/tests/test_chunked.py @@ -0,0 +1,65 @@ +# +# subunit: extensions to python unittest to get test results from subprocesses. +# Copyright (C) 2005 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. +# + +from cStringIO import StringIO +import unittest + +import subunit.chunked + + +def test_suite(): + loader = subunit.tests.TestUtil.TestLoader() + result = loader.loadTestsFromName(__name__) + return result + + +class TestEncode(unittest.TestCase): + + def setUp(self): + self.output = StringIO() + self.encoder = subunit.chunked.Encoder(self.output) + + def test_encode_nothing(self): + self.encoder.close() + self.assertEqual('0\r\n', self.output.getvalue()) + + def test_encode_empty(self): + self.encoder.write('') + self.encoder.close() + self.assertEqual('0\r\n', self.output.getvalue()) + + def test_encode_short(self): + self.encoder.write('abc') + self.encoder.close() + self.assertEqual('3\r\nabc0\r\n', self.output.getvalue()) + + def test_encode_combines_short(self): + self.encoder.write('abc') + self.encoder.write('def') + self.encoder.close() + self.assertEqual('6\r\nabcdef0\r\n', self.output.getvalue()) + + def test_encode_over_9_is_in_hex(self): + self.encoder.write('1234567890') + self.encoder.close() + self.assertEqual('A\r\n12345678900\r\n', self.output.getvalue()) + + def test_encode_long_ranges_not_combined(self): + self.encoder.write('1' * 65536) + self.encoder.write('2' * 65536) + self.encoder.close() + self.assertEqual('10000\r\n' + '1' * 65536 + '10000\r\n' + + '2' * 65536 + '0\r\n', self.output.getvalue()) diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index c2c62bc..41fc6b4 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -1083,7 +1083,7 @@ class TestTestProtocolClient(unittest.TestCase): self.io.getvalue(), "successful: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" - "15\nserialised\nform0\n]\n" % self.test.id()) + "F\r\nserialised\nform0\r\n]\n" % self.test.id()) def test_add_failure(self): """Test addFailure on a TestProtocolClient.""" @@ -1102,10 +1102,10 @@ class TestTestProtocolClient(unittest.TestCase): "failure: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" - "15\nserialised\nform0\n" + "F\r\nserialised\nform0\r\n" "Content-Type: text/x-traceback;language=python\n" "traceback\n" - "25\nRemoteException: boo qux\n0\n" + "19\r\nRemoteException: boo qux\n0\r\n" "]\n" % self.test.id()) def test_add_error(self): @@ -1127,10 +1127,10 @@ class TestTestProtocolClient(unittest.TestCase): "error: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" - "15\nserialised\nform0\n" + "F\r\nserialised\nform0\r\n" "Content-Type: text/x-traceback;language=python\n" "traceback\n" - "25\nRemoteException: boo qux\n0\n" + "19\r\nRemoteException: boo qux\n0\r\n" "]\n" % self.test.id()) def test_add_expected_failure(self): @@ -1152,10 +1152,10 @@ class TestTestProtocolClient(unittest.TestCase): "xfail: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" - "15\nserialised\nform0\n" + "F\r\nserialised\nform0\r\n" "Content-Type: text/x-traceback;language=python\n" "traceback\n" - "25\nRemoteException: boo qux\n0\n" + "19\r\nRemoteException: boo qux\n0\r\n" "]\n" % self.test.id()) def test_add_skip(self): @@ -1177,7 +1177,7 @@ class TestTestProtocolClient(unittest.TestCase): "skip: %s [ multipart\n" "Content-Type: text/plain\n" "reason\n" - "14\nHas it really?0\n" + "E\r\nHas it really?0\r\n" "]\n" % self.test.id()) def test_progress_set(self): @@ -1221,7 +1221,7 @@ class TestTestProtocolClient(unittest.TestCase): self.io.getvalue(), "successful: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" - "15\nserialised\nform0\n]\n" % self.test.id()) + "F\r\nserialised\nform0\r\n]\n" % self.test.id()) def test_suite(): -- cgit v1.2.1 From a5370700d7c9a70af8b7baa897b5edef81a1ebe0 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sat, 10 Oct 2009 18:59:09 +1100 Subject: Implement a chunked decoder. --- python/subunit/chunked.py | 101 ++++++++++++++++++++++++++++++++++- python/subunit/tests/test_chunked.py | 57 ++++++++++++++++++++ 2 files changed, 157 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/chunked.py b/python/subunit/chunked.py index 2cfc7ff..89fb97b 100644 --- a/python/subunit/chunked.py +++ b/python/subunit/chunked.py @@ -16,13 +16,112 @@ """Encoder/decoder for http style chunked encoding.""" +class Decoder(object): + """Decode chunked content to a byte stream.""" + + def __init__(self, output): + """Create a decoder decoding to output. + + :param output: A file-like object. Bytes written to the Decoder are + decoded to strip off the chunking and written to the output. + Up to a full write worth of data or a single control line may be + buffered (whichever is larger). The close method should be called + when no more data is available, to detect short streams; the + write method will return none-None when the end of a stream is + detected. + """ + self.output = output + self.buffered_bytes = [] + self.state = self._read_length + self.body_length = 0 + + def close(self): + """Close the decoder. + + :raises ValueError: If the stream is incomplete ValueError is raised. + """ + if self.state != self._finished: + raise ValueError("incomplete stream") + + def _finished(self): + """Finished reading, return any remaining bytes.""" + if self.buffered_bytes: + buffered_bytes = self.buffered_bytes + self.buffered_bytes = [] + return ''.join(buffered_bytes) + else: + raise ValueError("stream is finished") + + def _read_body(self): + """Pass body bytes to the output.""" + while self.body_length and self.buffered_bytes: + if self.body_length >= self.buffered_bytes[0]: + self.output.write(self.buffered_bytes[0]) + self.body_length -= len(self.buffered_bytes[0]) + self.state = self._read_length + # No more data. + else: + self.output.write(self.buffered_bytes[0][:self.body_length]) + self.buffered_bytes[0] = \ + self.buffered_bytes[0][self.body_length:] + self.body_length = 0 + self.state = self._read_length + return self.state() + + def _read_length(self): + """Try to decode a length from the bytes.""" + count = -1 + match_chars = "0123456789abcdefABCDEF\r\n" + count_chars = [] + for bytes in self.buffered_bytes: + for byte in bytes: + if byte not in match_chars: + break + count_chars.append(byte) + if byte == '\n': + break + if not count_chars: + return + if count_chars[-1][-1] != '\n': + return + count_str = ''.join(count_chars) + self.body_length = int(count_str[:-2], 16) + excess_bytes = len(count_str) + while excess_bytes: + if excess_bytes >= len(self.buffered_bytes[0]): + excess_bytes -= len(self.buffered_bytes[0]) + del self.buffered_bytes[0] + else: + self.buffered_bytes[0] = self.buffered_bytes[0][excess_bytes:] + excess_bytes = 0 + if not self.body_length: + self.state = self._finished + if not self.buffered_bytes: + # May not call into self._finished with no buffered data. + return '' + else: + self.state = self._read_body + return self.state() + + def write(self, bytes): + """Decode bytes to the output stream. + + :raises ValueError: If the stream has already seen the end of file + marker. + :returns: None, or the excess bytes beyond the end of file marker. + """ + if bytes: + self.buffered_bytes.append(bytes) + return self.state() + + class Encoder(object): """Encode content to a stream using HTTP Chunked coding.""" def __init__(self, output): """Create an encoder encoding to output. - :param output: A file-like object. Bytes written to the Encoder + :param output: A file-like object. Bytes written to the Encoder will be encoded using HTTP chunking. Small writes may be buffered and the ``close`` method must be called to finish the stream. """ diff --git a/python/subunit/tests/test_chunked.py b/python/subunit/tests/test_chunked.py index 2bf82b2..35de613 100644 --- a/python/subunit/tests/test_chunked.py +++ b/python/subunit/tests/test_chunked.py @@ -26,9 +26,66 @@ def test_suite(): return result +class TestDecode(unittest.TestCase): + + def setUp(self): + unittest.TestCase.setUp(self) + self.output = StringIO() + self.decoder = subunit.chunked.Decoder(self.output) + + def test_close_read_length_short_errors(self): + self.assertRaises(ValueError, self.decoder.close) + + def test_close_body_short_errors(self): + self.assertEqual(None, self.decoder.write('2\r\na')) + self.assertRaises(ValueError, self.decoder.close) + + def test_close_body_buffered_data_errors(self): + self.assertEqual(None, self.decoder.write('2\r')) + self.assertRaises(ValueError, self.decoder.close) + + def test_close_after_finished_stream_safe(self): + self.assertEqual(None, self.decoder.write('2\r\nab')) + self.assertEqual('', self.decoder.write('0\r\n')) + self.decoder.close() + + def test_decode_nothing(self): + self.assertEqual('', self.decoder.write('0\r\n')) + self.assertEqual('', self.output.getvalue()) + + def test_decode_short(self): + self.assertEqual('', self.decoder.write('3\r\nabc0\r\n')) + self.assertEqual('abc', self.output.getvalue()) + + def test_decode_combines_short(self): + self.assertEqual('', self.decoder.write('6\r\nabcdef0\r\n')) + self.assertEqual('abcdef', self.output.getvalue()) + + def test_decode_excess_bytes_from_write(self): + self.assertEqual('1234', self.decoder.write('3\r\nabc0\r\n1234')) + self.assertEqual('abc', self.output.getvalue()) + + def test_decode_write_after_finished_errors(self): + self.assertEqual('1234', self.decoder.write('3\r\nabc0\r\n1234')) + self.assertRaises(ValueError, self.decoder.write, '') + + def test_decode_hex(self): + self.assertEqual('', self.decoder.write('A\r\n12345678900\r\n')) + self.assertEqual('1234567890', self.output.getvalue()) + + def test_decode_long_ranges(self): + self.assertEqual(None, self.decoder.write('10000\r\n')) + self.assertEqual(None, self.decoder.write('1' * 65536)) + self.assertEqual(None, self.decoder.write('10000\r\n')) + self.assertEqual(None, self.decoder.write('2' * 65536)) + self.assertEqual('', self.decoder.write('0\r\n')) + self.assertEqual('1' * 65536 + '2' * 65536, self.output.getvalue()) + + class TestEncode(unittest.TestCase): def setUp(self): + unittest.TestCase.setUp(self) self.output = StringIO() self.encoder = subunit.chunked.Encoder(self.output) -- cgit v1.2.1 From cb60d38fd8da1c3146344569ed52fface0bcbef7 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 11 Oct 2009 05:57:10 +1100 Subject: Pull the outside-test state logic out of the Python parser into a separate state object. --- python/subunit/__init__.py | 61 +++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 17 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index a93b6eb..911308e 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -173,13 +173,31 @@ class DiscardStream(object): pass +class _OutSideTest(object): + """State for the subunit parser.""" + + def __init__(self, parser): + self.parser = parser + + def lostConnection(self): + """Connection lost.""" + + def startTest(self, offset, line): + """A test start command received.""" + self.parser.state = TestProtocolServer.TEST_STARTED + self.parser._state = None + self.parser._current_test = RemotedTestCase(line[offset:-1]) + self.parser.current_test_description = line[offset:-1] + self.parser.client.startTest(self.parser._current_test) + + class TestProtocolServer(object): """A parser for subunit. :ivar tags: The current tags associated with the protocol stream. """ - OUTSIDE_TEST = 0 + STATE_OBJECT = 0 TEST_STARTED = 1 READING_FAILURE = 2 READING_ERROR = 3 @@ -196,16 +214,19 @@ class TestProtocolServer(object): of mixed protocols. By default, sys.stdout will be used for convenience. """ - self.state = TestProtocolServer.OUTSIDE_TEST self.client = client if stream is None: stream = sys.stdout self._stream = stream + self._outside_test = _OutSideTest(self) + self._state = self._outside_test + self.state = TestProtocolServer.STATE_OBJECT def _addError(self, offset, line): if (self.state == TestProtocolServer.TEST_STARTED and self.current_test_description == line[offset:-1]): - self.state = TestProtocolServer.OUTSIDE_TEST + self._state = self._outside_test + self.state = TestProtocolServer.STATE_OBJECT self.current_test_description = None self.client.addError(self._current_test, RemoteError("")) self.client.stopTest(self._current_test) @@ -220,7 +241,8 @@ class TestProtocolServer(object): def _addExpectedFail(self, offset, line): if (self.state == TestProtocolServer.TEST_STARTED and self.current_test_description == line[offset:-1]): - self.state = TestProtocolServer.OUTSIDE_TEST + self._state = self._outside_test + self.state = TestProtocolServer.STATE_OBJECT self.current_test_description = None xfail = getattr(self.client, 'addExpectedFailure', None) if callable(xfail): @@ -238,7 +260,8 @@ class TestProtocolServer(object): def _addFailure(self, offset, line): if (self.state == TestProtocolServer.TEST_STARTED and self.current_test_description == line[offset:-1]): - self.state = TestProtocolServer.OUTSIDE_TEST + self._state = self._outside_test + self.state = TestProtocolServer.STATE_OBJECT self.current_test_description = None self.client.addFailure(self._current_test, RemoteError()) self.client.stopTest(self._current_test) @@ -252,7 +275,8 @@ class TestProtocolServer(object): def _addSkip(self, offset, line): if (self.state == TestProtocolServer.TEST_STARTED and self.current_test_description == line[offset:-1]): - self.state = TestProtocolServer.OUTSIDE_TEST + self._state = self._outside_test + self.state = TestProtocolServer.STATE_OBJECT self.current_test_description = None self._skip_or_error() self.client.stopTest(self._current_test) @@ -293,24 +317,28 @@ class TestProtocolServer(object): def endQuote(self, line): if self.state == TestProtocolServer.READING_FAILURE: - self.state = TestProtocolServer.OUTSIDE_TEST + self._state = self._outside_test + self.state = TestProtocolServer.STATE_OBJECT self.current_test_description = None self.client.addFailure(self._current_test, RemoteError(self._message)) self.client.stopTest(self._current_test) elif self.state == TestProtocolServer.READING_ERROR: - self.state = TestProtocolServer.OUTSIDE_TEST + self._state = self._outside_test + self.state = TestProtocolServer.STATE_OBJECT self.current_test_description = None self.client.addError(self._current_test, RemoteError(self._message)) self.client.stopTest(self._current_test) elif self.state == TestProtocolServer.READING_SKIP: - self.state = TestProtocolServer.OUTSIDE_TEST + self._state = self._outside_test + self.state = TestProtocolServer.STATE_OBJECT self.current_test_description = None self._skip_or_error(self._message) self.client.stopTest(self._current_test) elif self.state == TestProtocolServer.READING_XFAIL: - self.state = TestProtocolServer.OUTSIDE_TEST + self._state = self._outside_test + self.state = TestProtocolServer.STATE_OBJECT self.current_test_description = None xfail = getattr(self.client, 'addExpectedFailure', None) if callable(xfail): @@ -407,7 +435,8 @@ class TestProtocolServer(object): def lostConnection(self): """The input connection has finished.""" - if self.state == TestProtocolServer.OUTSIDE_TEST: + if self.state == TestProtocolServer.STATE_OBJECT: + self._state.lostConnection() return if self.state == TestProtocolServer.TEST_STARTED: self._lostConnectionInTest('') @@ -431,11 +460,8 @@ class TestProtocolServer(object): def _startTest(self, offset, line): """Internal call to change state machine. Override startTest().""" - if self.state == TestProtocolServer.OUTSIDE_TEST: - self.state = TestProtocolServer.TEST_STARTED - self._current_test = RemotedTestCase(line[offset:-1]) - self.current_test_description = line[offset:-1] - self.client.startTest(self._current_test) + if self.state == TestProtocolServer.STATE_OBJECT: + self._state.startTest(offset, line) else: self.stdOutLineReceived(line) @@ -447,7 +473,8 @@ class TestProtocolServer(object): self.client.stopTest(self._current_test) self.current_test_description = None self._current_test = None - self.state = TestProtocolServer.OUTSIDE_TEST + self._state = self._outside_test + self.state = TestProtocolServer.STATE_OBJECT class RemoteException(Exception): -- cgit v1.2.1 From 0f525a77443d3e6f2949cdf57fa3466db224c471 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 11 Oct 2009 06:24:25 +1100 Subject: Move the TEST_STARTED parser state to a state object. --- python/subunit/__init__.py | 192 ++++++++++++++++++++++++++++++--------------- 1 file changed, 127 insertions(+), 65 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 911308e..012555e 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -173,19 +173,126 @@ class DiscardStream(object): pass -class _OutSideTest(object): +class _ParserState(object): """State for the subunit parser.""" def __init__(self, parser): self.parser = parser + def addError(self, offset, line): + """An 'error:' directive has been read.""" + self.parser.stdOutLineReceived(line) + + def addExpectedFail(self, offset, line): + """An 'xfail:' directive has been read.""" + self.parser.stdOutLineReceived(line) + + def addFailure(self, offset, line): + """A 'failure:' directive has been read.""" + self.parser.stdOutLineReceived(line) + + def addSkip(self, offset, line): + """A 'skip:' directive has been read.""" + self.parser.stdOutLineReceived(line) + + def addSuccess(self, offset, line): + """A 'success:' directive has been read.""" + self.parser.stdOutLineReceived(line) + + def startTest(self, offset, line): + """A test start command received.""" + self.parser.stdOutLineReceived(line) + + +class _InTest(_ParserState): + """State for the subunit parser after reading a test: directive.""" + + def addError(self, offset, line): + """An 'error:' directive has been read.""" + if self.parser.current_test_description == line[offset:-1]: + self.parser._state = self.parser._outside_test + self.parser.state = TestProtocolServer.STATE_OBJECT + self.parser.current_test_description = None + self.parser.client.addError(self.parser._current_test, RemoteError("")) + self.parser.client.stopTest(self.parser._current_test) + self.parser._current_test = None + elif self.parser.current_test_description + " [" == line[offset:-1]: + self.parser.state = TestProtocolServer.READING_ERROR + self.parser._message = "" + else: + self.parser.stdOutLineReceived(line) + + def addExpectedFail(self, offset, line): + """An 'xfail:' directive has been read.""" + if self.parser.current_test_description == line[offset:-1]: + self.parser._state = self.parser._outside_test + self.parser.state = TestProtocolServer.STATE_OBJECT + self.parser.current_test_description = None + xfail = getattr(self.parser.client, 'addExpectedFailure', None) + if callable(xfail): + xfail(self.parser._current_test, RemoteError()) + else: + self.parser.client.addSuccess(self.parser._current_test) + self.parser.client.stopTest(self.parser._current_test) + elif self.parser.current_test_description + " [" == line[offset:-1]: + self.parser.state = TestProtocolServer.READING_XFAIL + self.parser._message = "" + else: + self.parser.stdOutLineReceived(line) + + def addFailure(self, offset, line): + """A 'failure:' directive has been read.""" + if self.parser.current_test_description == line[offset:-1]: + self.parser._state = self.parser._outside_test + self.parser.state = TestProtocolServer.STATE_OBJECT + self.parser.current_test_description = None + self.parser.client.addFailure(self.parser._current_test, RemoteError()) + self.parser.client.stopTest(self.parser._current_test) + elif self.parser.current_test_description + " [" == line[offset:-1]: + self.parser.state = TestProtocolServer.READING_FAILURE + self.parser._message = "" + else: + self.parser.stdOutLineReceived(line) + + def addSkip(self, offset, line): + """A 'skip:' directive has been read.""" + if self.parser.current_test_description == line[offset:-1]: + self.parser._state = self.parser._outside_test + self.parser.state = TestProtocolServer.STATE_OBJECT + self.parser.current_test_description = None + self.parser._skip_or_error() + self.parser.client.stopTest(self.parser._current_test) + elif self.parser.current_test_description + " [" == line[offset:-1]: + self.parser.state = TestProtocolServer.READING_SKIP + self.parser._message = "" + else: + self.parser.stdOutLineReceived(line) + + def addSuccess(self, offset, line): + """A 'success:' directive has been read.""" + if self.parser.current_test_description == line[offset:-1]: + self.parser._succeedTest() + elif self.parser.current_test_description + " [" == line[offset:-1]: + self.parser.state = TestProtocolServer.READING_SUCCESS + self.parser._message = "" + else: + self.parser.stdOutLineReceived(line) + + def lostConnection(self): + """Connection lost.""" + self.parser._lostConnectionInTest('') + + +class _OutSideTest(_ParserState): + """State for the subunit parser outside of a test context.""" + def lostConnection(self): """Connection lost.""" def startTest(self, offset, line): """A test start command received.""" - self.parser.state = TestProtocolServer.TEST_STARTED - self.parser._state = None + self.parser._state = self.parser._in_test + self.parser.state = TestProtocolServer.STATE_OBJECT self.parser._current_test = RemotedTestCase(line[offset:-1]) self.parser.current_test_description = line[offset:-1] self.parser.client.startTest(self.parser._current_test) @@ -198,7 +305,7 @@ class TestProtocolServer(object): """ STATE_OBJECT = 0 - TEST_STARTED = 1 + STATE_OBJECTS = [0] READING_FAILURE = 2 READING_ERROR = 3 READING_SKIP = 4 @@ -218,72 +325,34 @@ class TestProtocolServer(object): if stream is None: stream = sys.stdout self._stream = stream + # state objects we can switch too + self._in_test = _InTest(self) self._outside_test = _OutSideTest(self) + # start with outside test. self._state = self._outside_test self.state = TestProtocolServer.STATE_OBJECT def _addError(self, offset, line): - if (self.state == TestProtocolServer.TEST_STARTED and - self.current_test_description == line[offset:-1]): - self._state = self._outside_test - self.state = TestProtocolServer.STATE_OBJECT - self.current_test_description = None - self.client.addError(self._current_test, RemoteError("")) - self.client.stopTest(self._current_test) - self._current_test = None - elif (self.state == TestProtocolServer.TEST_STARTED and - self.current_test_description + " [" == line[offset:-1]): - self.state = TestProtocolServer.READING_ERROR - self._message = "" + if self.state in TestProtocolServer.STATE_OBJECTS: + self._state.addError(offset, line) else: self.stdOutLineReceived(line) def _addExpectedFail(self, offset, line): - if (self.state == TestProtocolServer.TEST_STARTED and - self.current_test_description == line[offset:-1]): - self._state = self._outside_test - self.state = TestProtocolServer.STATE_OBJECT - self.current_test_description = None - xfail = getattr(self.client, 'addExpectedFailure', None) - if callable(xfail): - xfail(self._current_test, RemoteError()) - else: - self.client.addSuccess(self._current_test) - self.client.stopTest(self._current_test) - elif (self.state == TestProtocolServer.TEST_STARTED and - self.current_test_description + " [" == line[offset:-1]): - self.state = TestProtocolServer.READING_XFAIL - self._message = "" + if self.state in TestProtocolServer.STATE_OBJECTS: + self._state.addExpectedFail(offset, line) else: self.stdOutLineReceived(line) def _addFailure(self, offset, line): - if (self.state == TestProtocolServer.TEST_STARTED and - self.current_test_description == line[offset:-1]): - self._state = self._outside_test - self.state = TestProtocolServer.STATE_OBJECT - self.current_test_description = None - self.client.addFailure(self._current_test, RemoteError()) - self.client.stopTest(self._current_test) - elif (self.state == TestProtocolServer.TEST_STARTED and - self.current_test_description + " [" == line[offset:-1]): - self.state = TestProtocolServer.READING_FAILURE - self._message = "" + if self.state in TestProtocolServer.STATE_OBJECTS: + self._state.addFailure(offset, line) else: self.stdOutLineReceived(line) def _addSkip(self, offset, line): - if (self.state == TestProtocolServer.TEST_STARTED and - self.current_test_description == line[offset:-1]): - self._state = self._outside_test - self.state = TestProtocolServer.STATE_OBJECT - self.current_test_description = None - self._skip_or_error() - self.client.stopTest(self._current_test) - elif (self.state == TestProtocolServer.TEST_STARTED and - self.current_test_description + " [" == line[offset:-1]): - self.state = TestProtocolServer.READING_SKIP - self._message = "" + if self.state in TestProtocolServer.STATE_OBJECTS: + self._state.addSkip(offset, line) else: self.stdOutLineReceived(line) @@ -298,13 +367,8 @@ class TestProtocolServer(object): addSkip(self._current_test, message) def _addSuccess(self, offset, line): - if (self.state == TestProtocolServer.TEST_STARTED and - self.current_test_description == line[offset:-1]): - self._succeedTest() - elif (self.state == TestProtocolServer.TEST_STARTED and - self.current_test_description + " [" == line[offset:-1]): - self.state = TestProtocolServer.READING_SUCCESS - self._message = "" + if self.state in TestProtocolServer.STATE_OBJECTS: + self._state.addSuccess(offset, line) else: self.stdOutLineReceived(line) @@ -435,12 +499,10 @@ class TestProtocolServer(object): def lostConnection(self): """The input connection has finished.""" - if self.state == TestProtocolServer.STATE_OBJECT: + if self.state in TestProtocolServer.STATE_OBJECTS: self._state.lostConnection() return - if self.state == TestProtocolServer.TEST_STARTED: - self._lostConnectionInTest('') - elif self.state == TestProtocolServer.READING_ERROR: + if self.state == TestProtocolServer.READING_ERROR: self._lostConnectionInTest('error report of ') elif self.state == TestProtocolServer.READING_FAILURE: self._lostConnectionInTest('failure report of ') @@ -460,7 +522,7 @@ class TestProtocolServer(object): def _startTest(self, offset, line): """Internal call to change state machine. Override startTest().""" - if self.state == TestProtocolServer.STATE_OBJECT: + if self.state in TestProtocolServer.STATE_OBJECTS: self._state.startTest(offset, line) else: self.stdOutLineReceived(line) -- cgit v1.2.1 From cc61cc2d5ca816d191aa14dfbbf75f3c6deae759 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 11 Oct 2009 06:42:03 +1100 Subject: Move failure details parsing into a state object in the Python parser. --- python/subunit/__init__.py | 79 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 12 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 012555e..4eadbda 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -199,6 +199,43 @@ class _ParserState(object): """A 'success:' directive has been read.""" self.parser.stdOutLineReceived(line) + def endQuote(self, line): + """The end of a details section has been reached.""" + self.parser.stdOutLineReceived(line) + + def lineReceived(self, line): + """a line has been received.""" + if line == "]\n": + self.endQuote(line) + else: + parts = line.split(None, 1) + if len(parts) == 2: + cmd, rest = parts + offset = len(cmd) + 1 + cmd = cmd.strip(':') + if cmd in ('test', 'testing'): + self.startTest(offset, line) + elif cmd == 'error': + self.addError(offset, line) + elif cmd == 'failure': + self.addFailure(offset, line) + elif cmd == 'progress': + self.parser._handleProgress(offset, line) + elif cmd == 'skip': + self.addSkip(offset, line) + elif cmd in ('success', 'successful'): + self.addSuccess(offset, line) + elif cmd in ('tags',): + self.parser._handleTags(offset, line) + elif cmd in ('time',): + self.parser._handleTime(offset, line) + elif cmd == 'xfail': + self.addExpectedFail(offset, line) + else: + self.parser.stdOutLineReceived(line) + else: + self.parser.stdOutLineReceived(line) + def startTest(self, offset, line): """A test start command received.""" self.parser.stdOutLineReceived(line) @@ -249,7 +286,8 @@ class _InTest(_ParserState): self.parser.client.addFailure(self.parser._current_test, RemoteError()) self.parser.client.stopTest(self.parser._current_test) elif self.parser.current_test_description + " [" == line[offset:-1]: - self.parser.state = TestProtocolServer.READING_FAILURE + self.parser._state = self.parser._reading_failure_details + self.parser.state = TestProtocolServer.STATE_OBJECT self.parser._message = "" else: self.parser.stdOutLineReceived(line) @@ -298,6 +336,28 @@ class _OutSideTest(_ParserState): self.parser.client.startTest(self.parser._current_test) +class _ReadingFailureDetails(_ParserState): + """State for the subunit parser when reading failure details.""" + + def endQuote(self, line): + """The end of a details section has been reached.""" + self.parser._state = self.parser._outside_test + self.parser.state = TestProtocolServer.STATE_OBJECT + self.parser.current_test_description = None + self.parser.client.addFailure(self.parser._current_test, + RemoteError(self.parser._message)) + self.parser.client.stopTest(self.parser._current_test) + + def lineReceived(self, line): + """a line has been received.""" + self.parser._appendMessage(line) + + def lostConnection(self): + """Connection lost.""" + self.parser._lostConnectionInTest('failure report of ') + + + class TestProtocolServer(object): """A parser for subunit. @@ -306,7 +366,6 @@ class TestProtocolServer(object): STATE_OBJECT = 0 STATE_OBJECTS = [0] - READING_FAILURE = 2 READING_ERROR = 3 READING_SKIP = 4 READING_XFAIL = 5 @@ -328,6 +387,7 @@ class TestProtocolServer(object): # state objects we can switch too self._in_test = _InTest(self) self._outside_test = _OutSideTest(self) + self._reading_failure_details = _ReadingFailureDetails(self) # start with outside test. self._state = self._outside_test self.state = TestProtocolServer.STATE_OBJECT @@ -380,13 +440,8 @@ class TestProtocolServer(object): self._message += line def endQuote(self, line): - if self.state == TestProtocolServer.READING_FAILURE: - self._state = self._outside_test - self.state = TestProtocolServer.STATE_OBJECT - self.current_test_description = None - self.client.addFailure(self._current_test, - RemoteError(self._message)) - self.client.stopTest(self._current_test) + if self.state in TestProtocolServer.STATE_OBJECTS: + self._state.endQuote(line) elif self.state == TestProtocolServer.READING_ERROR: self._state = self._outside_test self.state = TestProtocolServer.STATE_OBJECT @@ -456,7 +511,9 @@ class TestProtocolServer(object): """Call the appropriate local method for the received line.""" if line == "]\n": self.endQuote(line) - elif self.state in (TestProtocolServer.READING_FAILURE, + elif self.state in TestProtocolServer.STATE_OBJECTS: + self._state.lineReceived(line) + elif self.state in ( TestProtocolServer.READING_ERROR, TestProtocolServer.READING_SKIP, TestProtocolServer.READING_SUCCESS, TestProtocolServer.READING_XFAIL @@ -504,8 +561,6 @@ class TestProtocolServer(object): return if self.state == TestProtocolServer.READING_ERROR: self._lostConnectionInTest('error report of ') - elif self.state == TestProtocolServer.READING_FAILURE: - self._lostConnectionInTest('failure report of ') elif self.state == TestProtocolServer.READING_SUCCESS: self._lostConnectionInTest('success report of ') elif self.state == TestProtocolServer.READING_SKIP: -- cgit v1.2.1 From ecf70c477675dbdd054601330c06151f8b0f744e Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 11 Oct 2009 06:50:04 +1100 Subject: Move error details parsing to a state object in the Python parser. --- python/subunit/__init__.py | 54 ++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 19 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 4eadbda..e63a10a 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -254,7 +254,8 @@ class _InTest(_ParserState): self.parser.client.stopTest(self.parser._current_test) self.parser._current_test = None elif self.parser.current_test_description + " [" == line[offset:-1]: - self.parser.state = TestProtocolServer.READING_ERROR + self.parser._state = self.parser._reading_error_details + self.parser.state = TestProtocolServer.STATE_OBJECT self.parser._message = "" else: self.parser.stdOutLineReceived(line) @@ -336,16 +337,15 @@ class _OutSideTest(_ParserState): self.parser.client.startTest(self.parser._current_test) -class _ReadingFailureDetails(_ParserState): - """State for the subunit parser when reading failure details.""" +class _ReadingDetails(_ParserState): + """Common logic for readin state details.""" def endQuote(self, line): """The end of a details section has been reached.""" self.parser._state = self.parser._outside_test self.parser.state = TestProtocolServer.STATE_OBJECT self.parser.current_test_description = None - self.parser.client.addFailure(self.parser._current_test, - RemoteError(self.parser._message)) + self._report_outcome() self.parser.client.stopTest(self.parser._current_test) def lineReceived(self, line): @@ -354,8 +354,34 @@ class _ReadingFailureDetails(_ParserState): def lostConnection(self): """Connection lost.""" - self.parser._lostConnectionInTest('failure report of ') - + self.parser._lostConnectionInTest('%s report of ' % + self._outcome_label()) + + def _outcome_label(self): + """The label to describe this outcome.""" + raise NotImplementedError(self._outcome_label) + + +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)) + + def _outcome_label(self): + return "failure" + + +class _ReadingErrorDetails(_ReadingDetails): + """State for the subunit parser when reading error details.""" + + def _report_outcome(self): + self.parser.client.addError(self.parser._current_test, + RemoteError(self.parser._message)) + + def _outcome_label(self): + return "error" class TestProtocolServer(object): @@ -366,7 +392,6 @@ class TestProtocolServer(object): STATE_OBJECT = 0 STATE_OBJECTS = [0] - READING_ERROR = 3 READING_SKIP = 4 READING_XFAIL = 5 READING_SUCCESS = 6 @@ -387,6 +412,7 @@ class TestProtocolServer(object): # state objects we can switch too self._in_test = _InTest(self) self._outside_test = _OutSideTest(self) + self._reading_error_details = _ReadingErrorDetails(self) self._reading_failure_details = _ReadingFailureDetails(self) # start with outside test. self._state = self._outside_test @@ -442,13 +468,6 @@ class TestProtocolServer(object): def endQuote(self, line): if self.state in TestProtocolServer.STATE_OBJECTS: self._state.endQuote(line) - elif self.state == TestProtocolServer.READING_ERROR: - self._state = self._outside_test - self.state = TestProtocolServer.STATE_OBJECT - self.current_test_description = None - self.client.addError(self._current_test, - RemoteError(self._message)) - self.client.stopTest(self._current_test) elif self.state == TestProtocolServer.READING_SKIP: self._state = self._outside_test self.state = TestProtocolServer.STATE_OBJECT @@ -514,7 +533,7 @@ class TestProtocolServer(object): elif self.state in TestProtocolServer.STATE_OBJECTS: self._state.lineReceived(line) elif self.state in ( - TestProtocolServer.READING_ERROR, TestProtocolServer.READING_SKIP, + TestProtocolServer.READING_SKIP, TestProtocolServer.READING_SUCCESS, TestProtocolServer.READING_XFAIL ): @@ -558,9 +577,6 @@ class TestProtocolServer(object): """The input connection has finished.""" if self.state in TestProtocolServer.STATE_OBJECTS: self._state.lostConnection() - return - if self.state == TestProtocolServer.READING_ERROR: - self._lostConnectionInTest('error report of ') elif self.state == TestProtocolServer.READING_SUCCESS: self._lostConnectionInTest('success report of ') elif self.state == TestProtocolServer.READING_SKIP: -- cgit v1.2.1 From 711726ed2bda2dfe0f80961d7e58d7fbdeae905e Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 11 Oct 2009 06:52:46 +1100 Subject: Move skip details parsing to a state object in the Python parser. --- python/subunit/__init__.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index e63a10a..87ca8ec 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -302,7 +302,8 @@ class _InTest(_ParserState): self.parser._skip_or_error() self.parser.client.stopTest(self.parser._current_test) elif self.parser.current_test_description + " [" == line[offset:-1]: - self.parser.state = TestProtocolServer.READING_SKIP + self.parser._state = self.parser._reading_skip_details + self.parser.state = TestProtocolServer.STATE_OBJECT self.parser._message = "" else: self.parser.stdOutLineReceived(line) @@ -384,6 +385,16 @@ class _ReadingErrorDetails(_ReadingDetails): return "error" +class _ReadingSkipDetails(_ReadingDetails): + """State for the subunit parser when reading skip details.""" + + def _report_outcome(self): + self.parser._skip_or_error(self.parser._message) + + def _outcome_label(self): + return "skip" + + class TestProtocolServer(object): """A parser for subunit. @@ -392,7 +403,6 @@ class TestProtocolServer(object): STATE_OBJECT = 0 STATE_OBJECTS = [0] - READING_SKIP = 4 READING_XFAIL = 5 READING_SUCCESS = 6 @@ -414,6 +424,7 @@ class TestProtocolServer(object): self._outside_test = _OutSideTest(self) self._reading_error_details = _ReadingErrorDetails(self) self._reading_failure_details = _ReadingFailureDetails(self) + self._reading_skip_details = _ReadingSkipDetails(self) # start with outside test. self._state = self._outside_test self.state = TestProtocolServer.STATE_OBJECT @@ -468,12 +479,6 @@ class TestProtocolServer(object): def endQuote(self, line): if self.state in TestProtocolServer.STATE_OBJECTS: self._state.endQuote(line) - elif self.state == TestProtocolServer.READING_SKIP: - self._state = self._outside_test - self.state = TestProtocolServer.STATE_OBJECT - self.current_test_description = None - self._skip_or_error(self._message) - self.client.stopTest(self._current_test) elif self.state == TestProtocolServer.READING_XFAIL: self._state = self._outside_test self.state = TestProtocolServer.STATE_OBJECT @@ -533,7 +538,6 @@ class TestProtocolServer(object): elif self.state in TestProtocolServer.STATE_OBJECTS: self._state.lineReceived(line) elif self.state in ( - TestProtocolServer.READING_SKIP, TestProtocolServer.READING_SUCCESS, TestProtocolServer.READING_XFAIL ): @@ -579,8 +583,6 @@ class TestProtocolServer(object): self._state.lostConnection() elif self.state == TestProtocolServer.READING_SUCCESS: self._lostConnectionInTest('success report of ') - elif self.state == TestProtocolServer.READING_SKIP: - self._lostConnectionInTest('skip report of ') elif self.state == TestProtocolServer.READING_XFAIL: self._lostConnectionInTest('xfail report of ') else: -- cgit v1.2.1 From c97f1fa37c488d40fae00fd5db5dc1a967111614 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 11 Oct 2009 06:56:16 +1100 Subject: Move xfail details parsing to a state object in the Python parser. --- python/subunit/__init__.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 87ca8ec..cad9498 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -273,7 +273,8 @@ class _InTest(_ParserState): self.parser.client.addSuccess(self.parser._current_test) self.parser.client.stopTest(self.parser._current_test) elif self.parser.current_test_description + " [" == line[offset:-1]: - self.parser.state = TestProtocolServer.READING_XFAIL + self.parser._state = self.parser._reading_xfail_details + self.parser.state = TestProtocolServer.STATE_OBJECT self.parser._message = "" else: self.parser.stdOutLineReceived(line) @@ -385,6 +386,20 @@ class _ReadingErrorDetails(_ReadingDetails): return "error" +class _ReadingExpectedFailureDetails(_ReadingDetails): + """State for the subunit parser when reading xfail details.""" + + def _report_outcome(self): + xfail = getattr(self.parser.client, 'addExpectedFailure', None) + if callable(xfail): + xfail(self.parser._current_test, RemoteError(self.parser._message)) + else: + self.parser.client.addSuccess(self.parser._current_test) + + def _outcome_label(self): + return "xfail" + + class _ReadingSkipDetails(_ReadingDetails): """State for the subunit parser when reading skip details.""" @@ -403,7 +418,6 @@ class TestProtocolServer(object): STATE_OBJECT = 0 STATE_OBJECTS = [0] - READING_XFAIL = 5 READING_SUCCESS = 6 def __init__(self, client, stream=None): @@ -425,6 +439,7 @@ class TestProtocolServer(object): self._reading_error_details = _ReadingErrorDetails(self) self._reading_failure_details = _ReadingFailureDetails(self) self._reading_skip_details = _ReadingSkipDetails(self) + self._reading_xfail_details = _ReadingExpectedFailureDetails(self) # start with outside test. self._state = self._outside_test self.state = TestProtocolServer.STATE_OBJECT @@ -479,16 +494,6 @@ class TestProtocolServer(object): def endQuote(self, line): if self.state in TestProtocolServer.STATE_OBJECTS: self._state.endQuote(line) - elif self.state == TestProtocolServer.READING_XFAIL: - self._state = self._outside_test - self.state = TestProtocolServer.STATE_OBJECT - self.current_test_description = None - xfail = getattr(self.client, 'addExpectedFailure', None) - if callable(xfail): - xfail(self._current_test, RemoteError(self._message)) - else: - self.client.addSuccess(self._current_test) - self.client.stopTest(self._current_test) elif self.state == TestProtocolServer.READING_SUCCESS: self._succeedTest() else: @@ -539,7 +544,6 @@ class TestProtocolServer(object): self._state.lineReceived(line) elif self.state in ( TestProtocolServer.READING_SUCCESS, - TestProtocolServer.READING_XFAIL ): self._appendMessage(line) else: @@ -583,8 +587,6 @@ class TestProtocolServer(object): self._state.lostConnection() elif self.state == TestProtocolServer.READING_SUCCESS: self._lostConnectionInTest('success report of ') - elif self.state == TestProtocolServer.READING_XFAIL: - self._lostConnectionInTest('xfail report of ') else: self._lostConnectionInTest('unknown state of ') -- cgit v1.2.1 From 62f78e15b6431e33c78a9ccf6486eca02f86b903 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 11 Oct 2009 07:01:58 +1100 Subject: Move success details parsing to a state object in the Python parser. --- python/subunit/__init__.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index cad9498..8a8f45a 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -312,9 +312,14 @@ class _InTest(_ParserState): def addSuccess(self, offset, line): """A 'success:' directive has been read.""" if self.parser.current_test_description == line[offset:-1]: - self.parser._succeedTest() + self.parser._state = self.parser._outside_test + self.parser.state = TestProtocolServer.STATE_OBJECT + self.parser.current_test_description = None + self.parser.client.addSuccess(self.parser._current_test) + self.parser.client.stopTest(self.parser._current_test) elif self.parser.current_test_description + " [" == line[offset:-1]: - self.parser.state = TestProtocolServer.READING_SUCCESS + self.parser._state = self.parser._reading_success_details + self.parser.state = TestProtocolServer.STATE_OBJECT self.parser._message = "" else: self.parser.stdOutLineReceived(line) @@ -410,6 +415,16 @@ class _ReadingSkipDetails(_ReadingDetails): return "skip" +class _ReadingSuccessDetails(_ReadingDetails): + """State for the subunit parser when reading success details.""" + + def _report_outcome(self): + self.parser.client.addSuccess(self.parser._current_test) + + def _outcome_label(self): + return "success" + + class TestProtocolServer(object): """A parser for subunit. @@ -418,7 +433,6 @@ class TestProtocolServer(object): STATE_OBJECT = 0 STATE_OBJECTS = [0] - READING_SUCCESS = 6 def __init__(self, client, stream=None): """Create a TestProtocolServer instance. @@ -439,6 +453,7 @@ class TestProtocolServer(object): self._reading_error_details = _ReadingErrorDetails(self) self._reading_failure_details = _ReadingFailureDetails(self) self._reading_skip_details = _ReadingSkipDetails(self) + self._reading_success_details = _ReadingSuccessDetails(self) self._reading_xfail_details = _ReadingExpectedFailureDetails(self) # start with outside test. self._state = self._outside_test @@ -494,8 +509,6 @@ class TestProtocolServer(object): def endQuote(self, line): if self.state in TestProtocolServer.STATE_OBJECTS: self._state.endQuote(line) - elif self.state == TestProtocolServer.READING_SUCCESS: - self._succeedTest() else: self.stdOutLineReceived(line) @@ -542,10 +555,6 @@ class TestProtocolServer(object): self.endQuote(line) elif self.state in TestProtocolServer.STATE_OBJECTS: self._state.lineReceived(line) - elif self.state in ( - TestProtocolServer.READING_SUCCESS, - ): - self._appendMessage(line) else: parts = line.split(None, 1) if len(parts) == 2: @@ -585,8 +594,6 @@ class TestProtocolServer(object): """The input connection has finished.""" if self.state in TestProtocolServer.STATE_OBJECTS: self._state.lostConnection() - elif self.state == TestProtocolServer.READING_SUCCESS: - self._lostConnectionInTest('success report of ') else: self._lostConnectionInTest('unknown state of ') @@ -605,14 +612,6 @@ class TestProtocolServer(object): def stdOutLineReceived(self, line): self._stream.write(line) - def _succeedTest(self): - self.client.addSuccess(self._current_test) - self.client.stopTest(self._current_test) - self.current_test_description = None - self._current_test = None - self._state = self._outside_test - self.state = TestProtocolServer.STATE_OBJECT - class RemoteException(Exception): """An exception that occured remotely to Python.""" -- cgit v1.2.1 From 6a59dcfa6b1951e331d7f3ba5fe3285c905f64b5 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 11 Oct 2009 07:07:55 +1100 Subject: Remove the STATE_OBJECTS transition support from the Python parser. --- python/subunit/__init__.py | 90 +++++++--------------------------------------- 1 file changed, 13 insertions(+), 77 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 8a8f45a..a175655 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -236,6 +236,10 @@ class _ParserState(object): else: self.parser.stdOutLineReceived(line) + def lostConnection(self): + """Connection lost.""" + self.parser._lostConnectionInTest('unknown state of ') + def startTest(self, offset, line): """A test start command received.""" self.parser.stdOutLineReceived(line) @@ -248,14 +252,12 @@ class _InTest(_ParserState): """An 'error:' directive has been read.""" if self.parser.current_test_description == line[offset:-1]: self.parser._state = self.parser._outside_test - self.parser.state = TestProtocolServer.STATE_OBJECT self.parser.current_test_description = None self.parser.client.addError(self.parser._current_test, RemoteError("")) self.parser.client.stopTest(self.parser._current_test) self.parser._current_test = None elif self.parser.current_test_description + " [" == line[offset:-1]: self.parser._state = self.parser._reading_error_details - self.parser.state = TestProtocolServer.STATE_OBJECT self.parser._message = "" else: self.parser.stdOutLineReceived(line) @@ -264,7 +266,6 @@ class _InTest(_ParserState): """An 'xfail:' directive has been read.""" if self.parser.current_test_description == line[offset:-1]: self.parser._state = self.parser._outside_test - self.parser.state = TestProtocolServer.STATE_OBJECT self.parser.current_test_description = None xfail = getattr(self.parser.client, 'addExpectedFailure', None) if callable(xfail): @@ -274,7 +275,6 @@ class _InTest(_ParserState): self.parser.client.stopTest(self.parser._current_test) elif self.parser.current_test_description + " [" == line[offset:-1]: self.parser._state = self.parser._reading_xfail_details - self.parser.state = TestProtocolServer.STATE_OBJECT self.parser._message = "" else: self.parser.stdOutLineReceived(line) @@ -283,13 +283,11 @@ class _InTest(_ParserState): """A 'failure:' directive has been read.""" if self.parser.current_test_description == line[offset:-1]: self.parser._state = self.parser._outside_test - self.parser.state = TestProtocolServer.STATE_OBJECT self.parser.current_test_description = None self.parser.client.addFailure(self.parser._current_test, RemoteError()) self.parser.client.stopTest(self.parser._current_test) elif self.parser.current_test_description + " [" == line[offset:-1]: self.parser._state = self.parser._reading_failure_details - self.parser.state = TestProtocolServer.STATE_OBJECT self.parser._message = "" else: self.parser.stdOutLineReceived(line) @@ -298,13 +296,11 @@ class _InTest(_ParserState): """A 'skip:' directive has been read.""" if self.parser.current_test_description == line[offset:-1]: self.parser._state = self.parser._outside_test - self.parser.state = TestProtocolServer.STATE_OBJECT self.parser.current_test_description = None self.parser._skip_or_error() self.parser.client.stopTest(self.parser._current_test) elif self.parser.current_test_description + " [" == line[offset:-1]: self.parser._state = self.parser._reading_skip_details - self.parser.state = TestProtocolServer.STATE_OBJECT self.parser._message = "" else: self.parser.stdOutLineReceived(line) @@ -313,13 +309,11 @@ class _InTest(_ParserState): """A 'success:' directive has been read.""" if self.parser.current_test_description == line[offset:-1]: self.parser._state = self.parser._outside_test - self.parser.state = TestProtocolServer.STATE_OBJECT self.parser.current_test_description = None self.parser.client.addSuccess(self.parser._current_test) self.parser.client.stopTest(self.parser._current_test) elif self.parser.current_test_description + " [" == line[offset:-1]: self.parser._state = self.parser._reading_success_details - self.parser.state = TestProtocolServer.STATE_OBJECT self.parser._message = "" else: self.parser.stdOutLineReceived(line) @@ -338,7 +332,6 @@ class _OutSideTest(_ParserState): def startTest(self, offset, line): """A test start command received.""" self.parser._state = self.parser._in_test - self.parser.state = TestProtocolServer.STATE_OBJECT self.parser._current_test = RemotedTestCase(line[offset:-1]) self.parser.current_test_description = line[offset:-1] self.parser.client.startTest(self.parser._current_test) @@ -350,7 +343,6 @@ class _ReadingDetails(_ParserState): def endQuote(self, line): """The end of a details section has been reached.""" self.parser._state = self.parser._outside_test - self.parser.state = TestProtocolServer.STATE_OBJECT self.parser.current_test_description = None self._report_outcome() self.parser.client.stopTest(self.parser._current_test) @@ -431,9 +423,6 @@ class TestProtocolServer(object): :ivar tags: The current tags associated with the protocol stream. """ - STATE_OBJECT = 0 - STATE_OBJECTS = [0] - def __init__(self, client, stream=None): """Create a TestProtocolServer instance. @@ -457,31 +446,18 @@ class TestProtocolServer(object): self._reading_xfail_details = _ReadingExpectedFailureDetails(self) # start with outside test. self._state = self._outside_test - self.state = TestProtocolServer.STATE_OBJECT def _addError(self, offset, line): - if self.state in TestProtocolServer.STATE_OBJECTS: - self._state.addError(offset, line) - else: - self.stdOutLineReceived(line) + self._state.addError(offset, line) def _addExpectedFail(self, offset, line): - if self.state in TestProtocolServer.STATE_OBJECTS: - self._state.addExpectedFail(offset, line) - else: - self.stdOutLineReceived(line) + self._state.addExpectedFail(offset, line) def _addFailure(self, offset, line): - if self.state in TestProtocolServer.STATE_OBJECTS: - self._state.addFailure(offset, line) - else: - self.stdOutLineReceived(line) + self._state.addFailure(offset, line) def _addSkip(self, offset, line): - if self.state in TestProtocolServer.STATE_OBJECTS: - self._state.addSkip(offset, line) - else: - self.stdOutLineReceived(line) + self._state.addSkip(offset, line) def _skip_or_error(self, message=None): """Report the current test as a skip if possible, or else an error.""" @@ -494,10 +470,7 @@ class TestProtocolServer(object): addSkip(self._current_test, message) def _addSuccess(self, offset, line): - if self.state in TestProtocolServer.STATE_OBJECTS: - self._state.addSuccess(offset, line) - else: - self.stdOutLineReceived(line) + self._state.addSuccess(offset, line) def _appendMessage(self, line): if line[0:2] == " ]": @@ -507,10 +480,7 @@ class TestProtocolServer(object): self._message += line def endQuote(self, line): - if self.state in TestProtocolServer.STATE_OBJECTS: - self._state.endQuote(line) - else: - self.stdOutLineReceived(line) + self._state.endQuote(line) def _handleProgress(self, offset, line): """Process a progress directive.""" @@ -553,36 +523,8 @@ class TestProtocolServer(object): """Call the appropriate local method for the received line.""" if line == "]\n": self.endQuote(line) - elif self.state in TestProtocolServer.STATE_OBJECTS: - self._state.lineReceived(line) else: - parts = line.split(None, 1) - if len(parts) == 2: - cmd, rest = parts - offset = len(cmd) + 1 - cmd = cmd.strip(':') - if cmd in ('test', 'testing'): - self._startTest(offset, line) - elif cmd == 'error': - self._addError(offset, line) - elif cmd == 'failure': - self._addFailure(offset, line) - elif cmd == 'progress': - self._handleProgress(offset, line) - elif cmd == 'skip': - self._addSkip(offset, line) - elif cmd in ('success', 'successful'): - self._addSuccess(offset, line) - elif cmd in ('tags',): - self._handleTags(offset, line) - elif cmd in ('time',): - self._handleTime(offset, line) - elif cmd == 'xfail': - self._addExpectedFail(offset, line) - else: - self.stdOutLineReceived(line) - else: - self.stdOutLineReceived(line) + self._state.lineReceived(line) def _lostConnectionInTest(self, state_string): error_string = "lost connection during %stest '%s'" % ( @@ -592,10 +534,7 @@ class TestProtocolServer(object): def lostConnection(self): """The input connection has finished.""" - if self.state in TestProtocolServer.STATE_OBJECTS: - self._state.lostConnection() - else: - self._lostConnectionInTest('unknown state of ') + self._state.lostConnection() def readFrom(self, pipe): for line in pipe.readlines(): @@ -604,10 +543,7 @@ class TestProtocolServer(object): def _startTest(self, offset, line): """Internal call to change state machine. Override startTest().""" - if self.state in TestProtocolServer.STATE_OBJECTS: - self._state.startTest(offset, line) - else: - self.stdOutLineReceived(line) + self._state.startTest(offset, line) def stdOutLineReceived(self, line): self._stream.write(line) -- cgit v1.2.1 From 6bd5e4bca85e29d01ed108d4b9709e840aa1ba8f Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 11 Oct 2009 07:23:05 +1100 Subject: Simplify Python parser calling paths. --- python/subunit/__init__.py | 84 ++++++++++++++++------------------------------ 1 file changed, 29 insertions(+), 55 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index a175655..38ec9b8 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -199,42 +199,35 @@ class _ParserState(object): """A 'success:' directive has been read.""" self.parser.stdOutLineReceived(line) - def endQuote(self, line): - """The end of a details section has been reached.""" - self.parser.stdOutLineReceived(line) - def lineReceived(self, line): """a line has been received.""" - if line == "]\n": - self.endQuote(line) - else: - parts = line.split(None, 1) - if len(parts) == 2: - cmd, rest = parts - offset = len(cmd) + 1 - cmd = cmd.strip(':') - if cmd in ('test', 'testing'): - self.startTest(offset, line) - elif cmd == 'error': - self.addError(offset, line) - elif cmd == 'failure': - self.addFailure(offset, line) - elif cmd == 'progress': - self.parser._handleProgress(offset, line) - elif cmd == 'skip': - self.addSkip(offset, line) - elif cmd in ('success', 'successful'): - self.addSuccess(offset, line) - elif cmd in ('tags',): - self.parser._handleTags(offset, line) - elif cmd in ('time',): - self.parser._handleTime(offset, line) - elif cmd == 'xfail': - self.addExpectedFail(offset, line) - else: - self.parser.stdOutLineReceived(line) + parts = line.split(None, 1) + if len(parts) == 2: + cmd, rest = parts + offset = len(cmd) + 1 + cmd = cmd.strip(':') + if cmd in ('test', 'testing'): + self.startTest(offset, line) + elif cmd == 'error': + self.addError(offset, line) + elif cmd == 'failure': + self.addFailure(offset, line) + elif cmd == 'progress': + self.parser._handleProgress(offset, line) + elif cmd == 'skip': + self.addSkip(offset, line) + elif cmd in ('success', 'successful'): + self.addSuccess(offset, line) + elif cmd in ('tags',): + self.parser._handleTags(offset, line) + elif cmd in ('time',): + self.parser._handleTime(offset, line) + elif cmd == 'xfail': + self.addExpectedFail(offset, line) else: self.parser.stdOutLineReceived(line) + else: + self.parser.stdOutLineReceived(line) def lostConnection(self): """Connection lost.""" @@ -340,7 +333,7 @@ class _OutSideTest(_ParserState): class _ReadingDetails(_ParserState): """Common logic for readin state details.""" - def endQuote(self, line): + def _endQuote(self, line): """The end of a details section has been reached.""" self.parser._state = self.parser._outside_test self.parser.current_test_description = None @@ -349,6 +342,8 @@ class _ReadingDetails(_ParserState): def lineReceived(self, line): """a line has been received.""" + if line == "]\n": + self._endQuote(line) self.parser._appendMessage(line) def lostConnection(self): @@ -447,18 +442,6 @@ class TestProtocolServer(object): # start with outside test. self._state = self._outside_test - def _addError(self, offset, line): - self._state.addError(offset, line) - - def _addExpectedFail(self, offset, line): - self._state.addExpectedFail(offset, line) - - def _addFailure(self, offset, line): - self._state.addFailure(offset, line) - - def _addSkip(self, offset, line): - self._state.addSkip(offset, line) - def _skip_or_error(self, message=None): """Report the current test as a skip if possible, or else an error.""" addSkip = getattr(self.client, 'addSkip', None) @@ -469,9 +452,6 @@ class TestProtocolServer(object): message = "No reason given" addSkip(self._current_test, message) - def _addSuccess(self, offset, line): - self._state.addSuccess(offset, line) - def _appendMessage(self, line): if line[0:2] == " ]": # quoted ] start @@ -479,9 +459,6 @@ class TestProtocolServer(object): else: self._message += line - def endQuote(self, line): - self._state.endQuote(line) - def _handleProgress(self, offset, line): """Process a progress directive.""" line = line[offset:].strip() @@ -521,10 +498,7 @@ class TestProtocolServer(object): def lineReceived(self, line): """Call the appropriate local method for the received line.""" - if line == "]\n": - self.endQuote(line) - else: - self._state.lineReceived(line) + self._state.lineReceived(line) def _lostConnectionInTest(self, state_string): error_string = "lost connection during %stest '%s'" % ( -- cgit v1.2.1 From 2fd2de61fd774d5d7bbc8d205e98235a4fa60cfe Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 11 Oct 2009 07:26:52 +1100 Subject: More docs. --- python/subunit/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 38ec9b8..a833159 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -511,6 +511,11 @@ class TestProtocolServer(object): self._state.lostConnection() def readFrom(self, pipe): + """Blocking convenience API to parse an entire stream. + + :param pipe: A file-like object supporting readlines(). + :return: None. + """ for line in pipe.readlines(): self.lineReceived(line) self.lostConnection() -- cgit v1.2.1 From 14ee0c9ccc1b3130a46938adaab9f5d629e9a2a7 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 11 Oct 2009 08:11:16 +1100 Subject: Remove duplicate handling in the outcome details mode detection. --- python/subunit/__init__.py | 89 +++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 48 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index a833159..bc67921 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -241,75 +241,68 @@ class _ParserState(object): class _InTest(_ParserState): """State for the subunit parser after reading a test: directive.""" - def addError(self, offset, line): - """An 'error:' directive has been read.""" + def _outcome(self, offset, line, no_details, simple_details_state): + """An outcome directive has been read. + + :param no_details: Callable to call when no details are presented. + :param simple_details_state: The state to switch to for simple details + processing of this outcome. + """ if self.parser.current_test_description == line[offset:-1]: self.parser._state = self.parser._outside_test self.parser.current_test_description = None - self.parser.client.addError(self.parser._current_test, RemoteError("")) + no_details() self.parser.client.stopTest(self.parser._current_test) self.parser._current_test = None elif self.parser.current_test_description + " [" == line[offset:-1]: - self.parser._state = self.parser._reading_error_details + self.parser._state = simple_details_state self.parser._message = "" else: self.parser.stdOutLineReceived(line) + def _error(self): + self.parser.client.addError(self.parser._current_test, RemoteError("")) + + def addError(self, offset, line): + """An 'error:' directive has been read.""" + self._outcome(offset, line, self._error, + self.parser._reading_error_details) + + def _xfail(self): + xfail = getattr(self.parser.client, 'addExpectedFailure', None) + if callable(xfail): + xfail(self.parser._current_test, RemoteError()) + else: + self.parser.client.addSuccess(self.parser._current_test) + def addExpectedFail(self, offset, line): """An 'xfail:' directive has been read.""" - if self.parser.current_test_description == line[offset:-1]: - self.parser._state = self.parser._outside_test - self.parser.current_test_description = None - xfail = getattr(self.parser.client, 'addExpectedFailure', None) - if callable(xfail): - xfail(self.parser._current_test, RemoteError()) - else: - self.parser.client.addSuccess(self.parser._current_test) - self.parser.client.stopTest(self.parser._current_test) - elif self.parser.current_test_description + " [" == line[offset:-1]: - self.parser._state = self.parser._reading_xfail_details - self.parser._message = "" - else: - self.parser.stdOutLineReceived(line) + self._outcome(offset, line, self._xfail, + self.parser._reading_xfail_details) + + def _failure(self): + self.parser.client.addFailure(self.parser._current_test, RemoteError()) def addFailure(self, offset, line): """A 'failure:' directive has been read.""" - if self.parser.current_test_description == line[offset:-1]: - self.parser._state = self.parser._outside_test - self.parser.current_test_description = None - self.parser.client.addFailure(self.parser._current_test, RemoteError()) - self.parser.client.stopTest(self.parser._current_test) - elif self.parser.current_test_description + " [" == line[offset:-1]: - self.parser._state = self.parser._reading_failure_details - self.parser._message = "" - else: - self.parser.stdOutLineReceived(line) + self._outcome(offset, line, self._failure, + self.parser._reading_failure_details) + + def _skip(self): + self.parser._skip_or_error() def addSkip(self, offset, line): """A 'skip:' directive has been read.""" - if self.parser.current_test_description == line[offset:-1]: - self.parser._state = self.parser._outside_test - self.parser.current_test_description = None - self.parser._skip_or_error() - self.parser.client.stopTest(self.parser._current_test) - elif self.parser.current_test_description + " [" == line[offset:-1]: - self.parser._state = self.parser._reading_skip_details - self.parser._message = "" - else: - self.parser.stdOutLineReceived(line) + self._outcome(offset, line, self._skip, + self.parser._reading_skip_details) + + def _succeed(self): + self.parser.client.addSuccess(self.parser._current_test) def addSuccess(self, offset, line): """A 'success:' directive has been read.""" - if self.parser.current_test_description == line[offset:-1]: - self.parser._state = self.parser._outside_test - self.parser.current_test_description = None - self.parser.client.addSuccess(self.parser._current_test) - self.parser.client.stopTest(self.parser._current_test) - elif self.parser.current_test_description + " [" == line[offset:-1]: - self.parser._state = self.parser._reading_success_details - self.parser._message = "" - else: - self.parser.stdOutLineReceived(line) + self._outcome(offset, line, self._succeed, + self.parser._reading_success_details) def lostConnection(self): """Connection lost.""" -- cgit v1.2.1 From 31a6e9bb2dcce48fe6aabe1cf6cd6bc566820b2f Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 11 Oct 2009 08:35:19 +1100 Subject: multipart details trigger the parser to detect interrupted streams too. --- python/subunit/__init__.py | 10 ++-- python/subunit/tests/test_test_protocol.py | 74 +++++++++++------------------- 2 files changed, 34 insertions(+), 50 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index bc67921..c7edf6e 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -241,11 +241,11 @@ class _ParserState(object): class _InTest(_ParserState): """State for the subunit parser after reading a test: directive.""" - def _outcome(self, offset, line, no_details, simple_details_state): + def _outcome(self, offset, line, no_details, details_state): """An outcome directive has been read. :param no_details: Callable to call when no details are presented. - :param simple_details_state: The state to switch to for simple details + :param details_state: The state to switch to for details processing of this outcome. """ if self.parser.current_test_description == line[offset:-1]: @@ -255,7 +255,11 @@ class _InTest(_ParserState): self.parser.client.stopTest(self.parser._current_test) self.parser._current_test = None elif self.parser.current_test_description + " [" == line[offset:-1]: - self.parser._state = simple_details_state + self.parser._state = details_state + self.parser._message = "" + elif self.parser.current_test_description + " [ multipart" == \ + line[offset:-1]: + self.parser._state = details_state self.parser._message = "" else: self.parser.stdOutLineReceived(line) diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 41fc6b4..ac8733d 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -388,18 +388,24 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): (self.test, subunit.RemoteError(""))]) self.assertEqual(self.client.success_calls, []) - def test_lost_connection_during_error(self): + def do_connection_lost(self, outcome, opening): self.protocol.lineReceived("test old mcdonald\n") - self.protocol.lineReceived("error old mcdonald [\n") + self.protocol.lineReceived("%s old mcdonald %s" % (outcome, opening)) self.protocol.lostConnection() self.assertEqual(self.client.start_calls, [self.test]) self.assertEqual(self.client.end_calls, [self.test]) self.assertEqual(self.client.error_calls, [ - (self.test, subunit.RemoteError("lost connection during error " - "report of test 'old mcdonald'"))]) + (self.test, subunit.RemoteError("lost connection during %s " + "report of test 'old mcdonald'" % outcome))]) self.assertEqual(self.client.failure_calls, []) self.assertEqual(self.client.success_calls, []) + def test_lost_connection_during_error(self): + self.do_connection_lost("error", "[\n") + + def test_lost_connection_during_error_details(self): + self.do_connection_lost("error", "[ multipart\n") + def test_lost_connected_after_failure(self): self.protocol.lineReceived("test old mcdonald\n") self.protocol.lineReceived("failure old mcdonald\n") @@ -413,18 +419,10 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.assertEqual(self.client.success_calls, []) def test_lost_connection_during_failure(self): - self.protocol.lineReceived("test old mcdonald\n") - self.protocol.lineReceived("failure old mcdonald [\n") - self.protocol.lostConnection() - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, - [(self.test, - subunit.RemoteError("lost connection during " - "failure report" - " of test 'old mcdonald'"))]) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, []) + self.do_connection_lost("failure", "[\n") + + def test_lost_connection_during_failure_details(self): + self.do_connection_lost("failure", "[ multipart\n") def test_lost_connection_after_success(self): self.protocol.lineReceived("test old mcdonald\n") @@ -436,41 +434,23 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.assertEqual(self.client.failure_calls, []) self.assertEqual(self.client.success_calls, [self.test]) + def test_lost_connection_during_success(self): + self.do_connection_lost("success", "[\n") + + def test_lost_connection_during_success_details(self): + self.do_connection_lost("success", "[ multipart\n") + def test_lost_connection_during_skip(self): - self.protocol.lineReceived("test old mcdonald\n") - self.protocol.lineReceived("skip old mcdonald [\n") - self.protocol.lostConnection() - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, [ - (self.test, subunit.RemoteError("lost connection during skip " - "report of test 'old mcdonald'"))]) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, []) + self.do_connection_lost("skip", "[\n") + + def test_lost_connection_during_skip_details(self): + self.do_connection_lost("skip", "[ multipart\n") def test_lost_connection_during_xfail(self): - self.protocol.lineReceived("test old mcdonald\n") - self.protocol.lineReceived("xfail old mcdonald [\n") - self.protocol.lostConnection() - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, [ - (self.test, subunit.RemoteError("lost connection during xfail " - "report of test 'old mcdonald'"))]) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, []) + self.do_connection_lost("xfail", "[\n") - def test_lost_connection_during_success(self): - self.protocol.lineReceived("test old mcdonald\n") - self.protocol.lineReceived("success old mcdonald [\n") - self.protocol.lostConnection() - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, [ - (self.test, subunit.RemoteError("lost connection during success " - "report of test 'old mcdonald'"))]) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, []) + def test_lost_connection_during_xfail_details(self): + self.do_connection_lost("xfail", "[ multipart\n") class TestTestProtocolServerAddError(unittest.TestCase): -- cgit v1.2.1 From 129250bcfcdd915a1c8e71348efacdfda73d0594 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Tue, 13 Oct 2009 12:46:15 +1100 Subject: Move details parsing into a separate class. --- python/subunit/__init__.py | 36 ++++++++--------- python/subunit/details.py | 58 ++++++++++++++++++++++++++++ python/subunit/tests/__init__.py | 2 + python/subunit/tests/test_details.py | 62 ++++++++++++++++++++++++++++++ python/subunit/tests/test_test_protocol.py | 18 +++++++++ 5 files changed, 158 insertions(+), 18 deletions(-) create mode 100644 python/subunit/details.py create mode 100644 python/subunit/tests/test_details.py (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index c7edf6e..b4150a3 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -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 index 0000000..4f121cc --- /dev/null +++ b/python/subunit/details.py @@ -0,0 +1,58 @@ +# +# subunit: extensions to Python unittest to get test results from subprocesses. +# Copyright (C) 2005 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. +# + +"""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 diff --git a/python/subunit/tests/__init__.py b/python/subunit/tests/__init__.py index 8869425..fa31d68 100644 --- a/python/subunit/tests/__init__.py +++ b/python/subunit/tests/__init__.py @@ -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 index 0000000..bc0930a --- /dev/null +++ b/python/subunit/tests/test_details.py @@ -0,0 +1,62 @@ +# +# subunit: extensions to python unittest to get test results from subprocesses. +# Copyright (C) 2005 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. +# + +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()) diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index ac8733d..0994c54 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -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): -- cgit v1.2.1 From 236a0392e2d4ae550addac6bea02c7edb093c75f Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Tue, 13 Oct 2009 15:52:06 +1100 Subject: Small buffering bugs in chunked decoder. --- python/subunit/chunked.py | 8 +++++--- python/subunit/tests/test_chunked.py | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/subunit/chunked.py b/python/subunit/chunked.py index 89fb97b..82e4b0d 100644 --- a/python/subunit/chunked.py +++ b/python/subunit/chunked.py @@ -55,11 +55,13 @@ class Decoder(object): def _read_body(self): """Pass body bytes to the output.""" while self.body_length and self.buffered_bytes: - if self.body_length >= self.buffered_bytes[0]: + if self.body_length >= len(self.buffered_bytes[0]): self.output.write(self.buffered_bytes[0]) self.body_length -= len(self.buffered_bytes[0]) - self.state = self._read_length - # No more data. + del self.buffered_bytes[0] + # No more data available. + if not self.body_length: + self.state = self._read_length else: self.output.write(self.buffered_bytes[0][:self.body_length]) self.buffered_bytes[0] = \ diff --git a/python/subunit/tests/test_chunked.py b/python/subunit/tests/test_chunked.py index 35de613..a24e31e 100644 --- a/python/subunit/tests/test_chunked.py +++ b/python/subunit/tests/test_chunked.py @@ -53,6 +53,11 @@ class TestDecode(unittest.TestCase): self.assertEqual('', self.decoder.write('0\r\n')) self.assertEqual('', self.output.getvalue()) + def test_decode_serialised_form(self): + self.assertEqual(None, self.decoder.write("F\r\n")) + self.assertEqual(None, self.decoder.write("serialised\n")) + self.assertEqual('', self.decoder.write("form0\r\n")) + def test_decode_short(self): self.assertEqual('', self.decoder.write('3\r\nabc0\r\n')) self.assertEqual('abc', self.output.getvalue()) -- cgit v1.2.1 From 39b7a1cc39710acc6ed0deff19d1dcaaa46970a8 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Tue, 13 Oct 2009 16:43:05 +1100 Subject: Gather multipart details. --- python/subunit/details.py | 36 ++++++++++++++++++++++++++++++++++++ python/subunit/tests/test_details.py | 20 +++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/details.py b/python/subunit/details.py index 4f121cc..1460170 100644 --- a/python/subunit/details.py +++ b/python/subunit/details.py @@ -16,6 +16,11 @@ """Handlers for outcome details.""" +from cStringIO import StringIO + +import chunked, content, content_type + + class DetailsParser(object): """Base class/API reference for details parsing.""" @@ -50,9 +55,40 @@ class MultipartDetailsParser(DetailsParser): def __init__(self, state): self._state = state self._details = {} + self._parse_state = self._look_for_content + + def _look_for_content(self, line): + if line == "]\n": + self._state.endDetails() + return + # TODO error handling + field, value = line[:-1].split(' ', 1) + main, sub = value.split('/') + self._content_type = content_type.ContentType(main, sub) + self._parse_state = self._get_name + + def _get_name(self, line): + self._name = line[:-1] + self._body = StringIO() + self._chunk_parser = chunked.Decoder(self._body) + self._parse_state = self._feed_chunks + + def _feed_chunks(self, line): + residue = self._chunk_parser.write(line) + if residue is not None: + # Line based use always ends on no residue. + assert residue == '' + body = self._body + self._details[self._name] = content.Content( + self._content_type, lambda:[body.getvalue()]) + self._chunk_parser.close() + self._parse_state = self._look_for_content def get_details(self): return self._details def get_message(self): return None + + def lineReceived(self, line): + self._parse_state(line) diff --git a/python/subunit/tests/test_details.py b/python/subunit/tests/test_details.py index bc0930a..f76d505 100644 --- a/python/subunit/tests/test_details.py +++ b/python/subunit/tests/test_details.py @@ -18,7 +18,7 @@ from cStringIO import StringIO import unittest import subunit.tests -from subunit import details +from subunit import content, content_type, details def test_suite(): @@ -60,3 +60,21 @@ class TestMultipartDetails(unittest.TestCase): def test_get_details(self): parser = details.MultipartDetailsParser(None) self.assertEqual({}, parser.get_details()) + + def test_parts(self): + parser = details.MultipartDetailsParser(None) + parser.lineReceived("Content-Type: text/plain\n") + parser.lineReceived("something\n") + parser.lineReceived("F\r\n") + parser.lineReceived("serialised\n") + parser.lineReceived("form0\r\n") + expected = {} + expected['something'] = content.Content( + content_type.ContentType("text", "plain"), + lambda:["serialised\nform"]) + found = parser.get_details() + self.assertEqual(expected.keys(), found.keys()) + self.assertEqual(expected['something'].content_type, + found['something'].content_type) + self.assertEqual(''.join(expected['something'].iter_bytes()), + ''.join(found['something'].iter_bytes())) -- cgit v1.2.1 From b0a8bdf6461efd429e86b61f21783174fa060422 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 18 Oct 2009 21:02:03 +1100 Subject: Implement a python TestResult decorator for outcome details. --- python/subunit/test_results.py | 130 +++++++++++ python/subunit/tests/test_test_results.py | 358 ++++++++++++++++++++++++++++++ 2 files changed, 488 insertions(+) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 3904457..7ec85fe 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -20,6 +20,8 @@ import datetime import iso8601 +import subunit + # NOT a TestResult, because we are implementing the interface, not inheriting # it. @@ -216,3 +218,131 @@ class AutoTimingTestResultDecorator(HookedTestResultDecorator): """ self._time = a_datetime return self._call_maybe("time", None, a_datetime) + + +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: + addExpectedFailure = self.decorated.addFailure + 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 startTest(self, test): + return self.decorated.startTest(test) + + def startTestRun(self): + try: + return self.decorated.startTestRun() + except AttributeError: + return + + def stopTest(self, test): + return self.decorated.stopTest(test) + + def stopTestRun(self): + try: + return self.decorated.stopTestRun() + except AttributeError: + return diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index 58e14c1..7a9d4e6 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -23,6 +23,8 @@ import sys 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): @@ -57,6 +59,362 @@ class TimeCapturingResult(unittest.TestResult): self._calls.append(a_datetime) +class LoggingResult(object): + """Basic support for logging of results.""" + + def __init__(self): + self._calls = [] + + +class Python26TestResult(LoggingResult): + """A python 2.6 like test result, that logs.""" + + def addError(self, test, err): + self._calls.append(('addError', test, err)) + + def addFailure(self, test, err): + self._calls.append(('addFailure', test, err)) + + def addSuccess(self, test): + self._calls.append(('addSuccess', test)) + + def startTest(self, test): + self._calls.append(('startTest', test)) + + def stopTest(self, test): + self._calls.append(('stopTest', test)) + + +class Python27TestResult(Python26TestResult): + """A python 2.7 like test result, that logs.""" + + def addExpectedFailure(self, test, err): + self._calls.append(('addExpectedFailure', test, err)) + + def addSkip(self, test, reason): + self._calls.append(('addSkip', test, reason)) + + def addUnexpectedSuccess(self, test): + self._calls.append(('addUnexpectedSuccess', test)) + + def startTestRun(self): + self._calls.append(('startTestRun',)) + + def stopTestRun(self): + self._calls.append(('stopTestRun',)) + + +class ExtendedTestResult(Python27TestResult): + """A test result like the proposed extended unittest result API.""" + + def addError(self, test, err=None, details=None): + self._calls.append(('addError', test, err or details)) + + def addFailure(self, test, err=None, details=None): + self._calls.append(('addFailure', test, err or details)) + + def addExpectedFailure(self, test, err=None, details=None): + self._calls.append(('addExpectedFailure', test, err or details)) + + def addSkip(self, test, reason=None, details=None): + self._calls.append(('addSkip', test, reason or details)) + + def addSuccess(self, test, details=None): + if details: + self._calls.append(('addSuccess', test, details)) + else: + self._calls.append(('addSuccess', test)) + + def addUnexpectedSuccess(self, test, details=None): + if details: + self._calls.append(('addUnexpectedSuccess', test, details)) + else: + self._calls.append(('addUnexpectedSuccess', test)) + + +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_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_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) + + +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(self.outcome, 'addFailure') + + def test_outcome_Extended_py26(self): + self.make_26_result() + self.check_outcome_details_to_exec_info(self.outcome, 'addFailure') + + + +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): -- cgit v1.2.1 From aff764783084ed070f4d7967f85620a9f1d93c61 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 19 Oct 2009 18:40:32 +1100 Subject: Change the ExtendedToOriginal decorator to fallback xfails as success. --- python/subunit/test_results.py | 2 +- python/subunit/tests/test_test_results.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 7ec85fe..66bb403 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -247,7 +247,7 @@ class ExtendedToOriginalDecorator(object): self._check_args(err, details) addExpectedFailure = getattr(self.decorated, 'addExpectedFailure', None) if addExpectedFailure is None: - addExpectedFailure = self.decorated.addFailure + return self.addSuccess(test) if details is not None: try: return addExpectedFailure(test, details=details) diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index 7a9d4e6..bdb603f 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -206,6 +206,16 @@ class TestExtendedToOriginalResultDecoratorBase(unittest.TestCase): 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: @@ -335,11 +345,11 @@ class TestExtendedToOriginalAddExpectedFailure( def test_outcome_Original_py26(self): self.make_26_result() - self.check_outcome_exc_info(self.outcome, 'addFailure') + 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_exec_info(self.outcome, 'addFailure') + self.check_outcome_details_to_nothing(self.outcome, 'addSuccess') -- cgit v1.2.1 From 4e7bd221e171a96e44231218025157f4886a9cc5 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 19 Oct 2009 18:48:32 +1100 Subject: Support progress on the ExtendedToOriginal decorator. --- python/subunit/test_results.py | 6 ++++++ python/subunit/tests/test_test_results.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 66bb403..49fc731 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -329,6 +329,12 @@ class ExtendedToOriginalDecorator(object): 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) + def startTest(self, test): return self.decorated.startTest(test) diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index bdb603f..7400f0b 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -131,6 +131,9 @@ class ExtendedTestResult(Python27TestResult): else: self._calls.append(('addUnexpectedSuccess', test)) + def progress(self, offset, whence): + self._calls.append(('progress', offset, whence)) + class TestExtendedToOriginalResultDecoratorBase(unittest.TestCase): @@ -237,6 +240,19 @@ class TestExtendedToOriginalResultDecoratorBase(unittest.TestCase): 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_startTest_py26(self): self.make_26_result() self.converter.startTest(self) -- cgit v1.2.1 From 46dd4f37b4828e8ac265374dfef6d9fe3995292b Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 19 Oct 2009 18:51:29 +1100 Subject: Support tags on the ExtendedToOriginal decorator. --- python/subunit/test_results.py | 10 ++++++++++ python/subunit/tests/test_test_results.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 49fc731..f73d747 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -109,6 +109,9 @@ class TestResultDecorator(object): def stop(self): return self.decorated.stop() + def tags(self, gone_tags, new_tags): + return self._call_maybe("tags", None, gone_tags, new_tags) + def time(self, a_datetime): return self._call_maybe("time", None, a_datetime) @@ -352,3 +355,10 @@ class ExtendedToOriginalDecorator(object): 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) + diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index 7400f0b..b7a26d5 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -134,6 +134,9 @@ class ExtendedTestResult(Python27TestResult): def progress(self, offset, whence): self._calls.append(('progress', offset, whence)) + def tags(self, new_tags, gone_tags): + self._calls.append(('tags', new_tags, gone_tags)) + class TestExtendedToOriginalResultDecoratorBase(unittest.TestCase): @@ -313,6 +316,19 @@ class TestExtendedToOriginalResultDecorator( 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) + class TestExtendedToOriginalAddError(TestExtendedToOriginalResultDecoratorBase): -- cgit v1.2.1 From ecf9af693a3e5f89a8854f53f0a5a1f14fed081d Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 19 Oct 2009 18:54:57 +1100 Subject: Support the time protocol on ExtendedToOriginalDecorator. --- python/subunit/test_results.py | 5 +++++ python/subunit/tests/test_test_results.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index f73d747..fad4760 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -362,3 +362,8 @@ class ExtendedToOriginalDecorator(object): 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) diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index b7a26d5..d333c10 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -137,6 +137,9 @@ class ExtendedTestResult(Python27TestResult): def tags(self, new_tags, gone_tags): self._calls.append(('tags', new_tags, gone_tags)) + def time(self, time): + self._calls.append(('time', time)) + class TestExtendedToOriginalResultDecoratorBase(unittest.TestCase): @@ -329,6 +332,19 @@ class TestExtendedToOriginalResultDecorator( 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): -- cgit v1.2.1 From 3f57adebc84e889c22ae60eb43d8b88eb69f0efe Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 19 Oct 2009 19:13:32 +1100 Subject: Use ExtendedToOriginalDecorator in TestProtocolServer removing a bunch of fallback checking code. --- python/subunit/__init__.py | 42 +++++++++++++----------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index b4150a3..4cb273b 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -114,6 +114,7 @@ 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. +* subunit.test_results contains TestResult helper classes. """ import datetime @@ -126,7 +127,7 @@ import unittest import iso8601 -import chunked, content, content_type, details +import chunked, content, content_type, details, test_results PROGRESS_SET = 0 @@ -273,11 +274,8 @@ class _InTest(_ParserState): self.parser._reading_error_details) def _xfail(self): - xfail = getattr(self.parser.client, 'addExpectedFailure', None) - if callable(xfail): - xfail(self.parser._current_test, RemoteError()) - else: - self.parser.client.addSuccess(self.parser._current_test) + self.parser.client.addExpectedFailure(self.parser._current_test, + RemoteError()) def addExpectedFail(self, offset, line): """An 'xfail:' directive has been read.""" @@ -385,12 +383,8 @@ class _ReadingExpectedFailureDetails(_ReadingDetails): """State for the subunit parser when reading xfail details.""" def _report_outcome(self): - xfail = getattr(self.parser.client, 'addExpectedFailure', None) - if callable(xfail): - xfail(self.parser._current_test, - RemoteError(self.details_parser.get_message())) - else: - self.parser.client.addSuccess(self.parser._current_test) + self.parser.client.addExpectedFailure(self.parser._current_test, + RemoteError(self.details_parser.get_message())) def _outcome_label(self): return "xfail" @@ -431,7 +425,7 @@ class TestProtocolServer(object): of mixed protocols. By default, sys.stdout will be used for convenience. """ - self.client = client + self.client = test_results.ExtendedToOriginalDecorator(client) if stream is None: stream = sys.stdout self._stream = stream @@ -448,13 +442,9 @@ class TestProtocolServer(object): def _skip_or_error(self, message=None): """Report the current test as a skip if possible, or else an error.""" - addSkip = getattr(self.client, 'addSkip', None) - if not callable(addSkip): - self.client.addError(self._current_test, RemoteError(message)) - else: - if not message: - message = "No reason given" - addSkip(self._current_test, message) + if not message: + message = "No reason given" + self.client.addSkip(self._current_test, message) def _handleProgress(self, offset, line): """Process a progress directive.""" @@ -471,17 +461,13 @@ class TestProtocolServer(object): else: whence = PROGRESS_SET delta = int(line) - progress_method = getattr(self.client, 'progress', None) - if callable(progress_method): - progress_method(delta, whence) + self.client.progress(delta, whence) def _handleTags(self, offset, line): """Process a tags command.""" tags = line[offset:].split() new_tags, gone_tags = tags_to_new_gone(tags) - tags_method = getattr(self.client, 'tags', None) - if tags_method is not None: - tags_method(new_tags, gone_tags) + self.client.tags(new_tags, gone_tags) def _handleTime(self, offset, line): # Accept it, but do not do anything with it yet. @@ -489,9 +475,7 @@ class TestProtocolServer(object): event_time = iso8601.parse_date(line[offset:-1]) except TypeError, e: raise TypeError("Failed to parse %r, got %r" % (line, e)) - time_method = getattr(self.client, 'time', None) - if callable(time_method): - time_method(event_time) + self.client.time(event_time) def lineReceived(self, line): """Call the appropriate local method for the received line.""" -- cgit v1.2.1 From 1397750c1112952370adbd70f5e19745de8691be Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sat, 24 Oct 2009 15:21:09 +1100 Subject: Use cleaner test doubles in test_test_protocol. --- python/subunit/tests/test_test_protocol.py | 548 +++++++++++------------------ 1 file changed, 212 insertions(+), 336 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 0994c54..fea715c 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -24,125 +24,11 @@ import subunit from subunit.content import Content, TracebackContent from subunit.content_type import ContentType import subunit.iso8601 as iso8601 - - -class MockTestProtocolServerClient(object): - """A mock protocol server client to test callbacks. - - Note that this is deliberately not Python 2.7 complete, to allow - testing compatibility - we need a TestResult that will not have new methods - like addExpectedFailure. - """ - - def __init__(self): - self.end_calls = [] - self.error_calls = [] - self.failure_calls = [] - self.skip_calls = [] - self.start_calls = [] - self.success_calls = [] - self.progress_calls = [] - self._time = None - super(MockTestProtocolServerClient, self).__init__() - - def addError(self, test, error): - self.error_calls.append((test, error)) - - def addFailure(self, test, error): - self.failure_calls.append((test, error)) - - def addSkip(self, test, reason): - self.skip_calls.append((test, reason)) - - def addSuccess(self, test): - self.success_calls.append(test) - - def stopTest(self, test): - self.end_calls.append(test) - - def startTest(self, test): - self.start_calls.append(test) - - def progress(self, offset, whence): - self.progress_calls.append((offset, whence)) - - def time(self, time): - self._time = time - - -class MockExtendedTestProtocolServerClient(MockTestProtocolServerClient): - """An extended TestResult for testing which implements tags() etc.""" - - def __init__(self): - MockTestProtocolServerClient.__init__(self) - self.new_tags = set() - self.gone_tags = set() - - def tags(self, new_tags, gone_tags): - self.new_tags = new_tags - self.gone_tags = gone_tags - - -class TestMockTestProtocolServer(unittest.TestCase): - - def test_start_test(self): - protocol = MockTestProtocolServerClient() - protocol.startTest(subunit.RemotedTestCase("test old mcdonald")) - self.assertEqual(protocol.start_calls, - [subunit.RemotedTestCase("test old mcdonald")]) - self.assertEqual(protocol.end_calls, []) - self.assertEqual(protocol.error_calls, []) - self.assertEqual(protocol.failure_calls, []) - self.assertEqual(protocol.success_calls, []) - - def test_add_error(self): - protocol = MockTestProtocolServerClient() - protocol.addError(subunit.RemotedTestCase("old mcdonald"), - subunit.RemoteError("omg it works")) - self.assertEqual(protocol.start_calls, []) - self.assertEqual(protocol.end_calls, []) - self.assertEqual(protocol.error_calls, [( - subunit.RemotedTestCase("old mcdonald"), - subunit.RemoteError("omg it works"))]) - self.assertEqual(protocol.failure_calls, []) - self.assertEqual(protocol.success_calls, []) - - def test_add_failure(self): - protocol = MockTestProtocolServerClient() - protocol.addFailure(subunit.RemotedTestCase("old mcdonald"), - subunit.RemoteError("omg it works")) - self.assertEqual(protocol.start_calls, []) - self.assertEqual(protocol.end_calls, []) - self.assertEqual(protocol.error_calls, []) - self.assertEqual(protocol.failure_calls, [ - (subunit.RemotedTestCase("old mcdonald"), - subunit.RemoteError("omg it works"))]) - self.assertEqual(protocol.success_calls, []) - - def test_add_success(self): - protocol = MockTestProtocolServerClient() - protocol.addSuccess(subunit.RemotedTestCase("test old mcdonald")) - self.assertEqual(protocol.start_calls, []) - self.assertEqual(protocol.end_calls, []) - self.assertEqual(protocol.error_calls, []) - self.assertEqual(protocol.failure_calls, []) - self.assertEqual(protocol.success_calls, - [subunit.RemotedTestCase("test old mcdonald")]) - - def test_end_test(self): - protocol = MockTestProtocolServerClient() - protocol.stopTest(subunit.RemotedTestCase("test old mcdonald")) - self.assertEqual(protocol.end_calls, - [subunit.RemotedTestCase("test old mcdonald")]) - self.assertEqual(protocol.error_calls, []) - self.assertEqual(protocol.failure_calls, []) - self.assertEqual(protocol.success_calls, []) - self.assertEqual(protocol.start_calls, []) - - def test_progress(self): - protocol = MockTestProtocolServerClient() - protocol.progress(-1, subunit.PROGRESS_CUR) - self.assertEqual(protocol.progress_calls, [(-1, subunit.PROGRESS_CUR)]) +from subunit.tests.test_test_results import ( + ExtendedTestResult, + Python26TestResult, + Python27TestResult, + ) class TestTestImports(unittest.TestCase): @@ -191,28 +77,28 @@ class TestTestProtocolServerPipe(unittest.TestCase): class TestTestProtocolServerStartTest(unittest.TestCase): def setUp(self): - self.client = MockTestProtocolServerClient() + self.client = Python26TestResult() self.protocol = subunit.TestProtocolServer(self.client) def test_start_test(self): self.protocol.lineReceived("test old mcdonald\n") - self.assertEqual(self.client.start_calls, - [subunit.RemotedTestCase("old mcdonald")]) + self.assertEqual(self.client._calls, + [('startTest', subunit.RemotedTestCase("old mcdonald"))]) def test_start_testing(self): self.protocol.lineReceived("testing old mcdonald\n") - self.assertEqual(self.client.start_calls, - [subunit.RemotedTestCase("old mcdonald")]) + self.assertEqual(self.client._calls, + [('startTest', subunit.RemotedTestCase("old mcdonald"))]) def test_start_test_colon(self): self.protocol.lineReceived("test: old mcdonald\n") - self.assertEqual(self.client.start_calls, - [subunit.RemotedTestCase("old mcdonald")]) + self.assertEqual(self.client._calls, + [('startTest', subunit.RemotedTestCase("old mcdonald"))]) def test_start_testing_colon(self): self.protocol.lineReceived("testing: old mcdonald\n") - self.assertEqual(self.client.start_calls, - [subunit.RemotedTestCase("old mcdonald")]) + self.assertEqual(self.client._calls, + [('startTest', subunit.RemotedTestCase("old mcdonald"))]) class TestTestProtocolServerPassThrough(unittest.TestCase): @@ -220,7 +106,7 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): def setUp(self): self.stdout = StringIO() self.test = subunit.RemotedTestCase("old mcdonald") - self.client = MockTestProtocolServerClient() + self.client = Python26TestResult() self.protocol = subunit.TestProtocolServer(self.client, self.stdout) def keywords_before_test(self): @@ -245,42 +131,37 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): def test_keywords_before_test(self): self.keywords_before_test() - self.assertEqual(self.client.start_calls, []) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, []) + self.assertEqual(self.client._calls, []) def test_keywords_after_error(self): self.protocol.lineReceived("test old mcdonald\n") self.protocol.lineReceived("error old mcdonald\n") self.keywords_before_test() - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, - [(self.test, subunit.RemoteError(""))]) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, []) + self.assertEqual([ + ('startTest', self.test), + ('addError', self.test, subunit.RemoteError("")), + ('stopTest', self.test), + ], self.client._calls) def test_keywords_after_failure(self): self.protocol.lineReceived("test old mcdonald\n") self.protocol.lineReceived("failure old mcdonald\n") self.keywords_before_test() - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, - [(self.test, subunit.RemoteError())]) - self.assertEqual(self.client.success_calls, []) + self.assertEqual(self.client._calls, [ + ('startTest', self.test), + ('addFailure', self.test, subunit.RemoteError("")), + ('stopTest', self.test), + ]) def test_keywords_after_success(self): self.protocol.lineReceived("test old mcdonald\n") self.protocol.lineReceived("success old mcdonald\n") self.keywords_before_test() - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, [self.test]) + self.assertEqual([ + ('startTest', self.test), + ('addSuccess', self.test), + ('stopTest', self.test), + ], self.client._calls) def test_keywords_after_test(self): self.protocol.lineReceived("test old mcdonald\n") @@ -305,12 +186,11 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): "successful a\n" "successful: a\n" "]\n") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.failure_calls, - [(self.test, subunit.RemoteError())]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.success_calls, []) + self.assertEqual(self.client._calls, [ + ('startTest', self.test), + ('addFailure', self.test, subunit.RemoteError("")), + ('stopTest', self.test), + ]) def test_keywords_during_failure(self): self.protocol.lineReceived("test old mcdonald\n") @@ -327,21 +207,21 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") self.assertEqual(self.stdout.getvalue(), "") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.failure_calls, - [(self.test, subunit.RemoteError("test old mcdonald\n" - "failure a\n" - "failure: a\n" - "error a\n" - "error: a\n" - "success a\n" - "success: a\n" - "successful a\n" - "successful: a\n" - "]\n"))]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.success_calls, []) + failure = subunit.RemoteError("test old mcdonald\n" + "failure a\n" + "failure: a\n" + "error a\n" + "error: a\n" + "success a\n" + "success: a\n" + "successful a\n" + "successful: a\n" + "]\n") + self.assertEqual(self.client._calls, [ + ('startTest', self.test), + ('addFailure', self.test, failure), + ('stopTest', self.test), + ]) def test_stdout_passthrough(self): """Lines received which cannot be interpreted as any protocol action @@ -355,50 +235,47 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): class TestTestProtocolServerLostConnection(unittest.TestCase): def setUp(self): - self.client = MockTestProtocolServerClient() + self.client = Python26TestResult() self.protocol = subunit.TestProtocolServer(self.client) self.test = subunit.RemotedTestCase("old mcdonald") def test_lost_connection_no_input(self): self.protocol.lostConnection() - self.assertEqual(self.client.start_calls, []) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, []) + self.assertEqual([], self.client._calls) def test_lost_connection_after_start(self): self.protocol.lineReceived("test old mcdonald\n") self.protocol.lostConnection() - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, [ - (self.test, subunit.RemoteError("lost connection during " - "test 'old mcdonald'"))]) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, []) + failure = subunit.RemoteError( + "lost connection during test 'old mcdonald'") + self.assertEqual([ + ('startTest', self.test), + ('addError', self.test, failure), + ('stopTest', self.test), + ], self.client._calls) def test_lost_connected_after_error(self): self.protocol.lineReceived("test old mcdonald\n") self.protocol.lineReceived("error old mcdonald\n") self.protocol.lostConnection() - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, [ - (self.test, subunit.RemoteError(""))]) - self.assertEqual(self.client.success_calls, []) + self.assertEqual([ + ('startTest', self.test), + ('addError', self.test, subunit.RemoteError("")), + ('stopTest', self.test), + ], self.client._calls) def do_connection_lost(self, outcome, opening): self.protocol.lineReceived("test old mcdonald\n") self.protocol.lineReceived("%s old mcdonald %s" % (outcome, opening)) self.protocol.lostConnection() - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, [ - (self.test, subunit.RemoteError("lost connection during %s " - "report of test 'old mcdonald'" % outcome))]) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, []) + failure = subunit.RemoteError( + "lost connection during %s report of test 'old mcdonald'" % + outcome) + self.assertEqual([ + ('startTest', self.test), + ('addError', self.test, failure), + ('stopTest', self.test), + ], self.client._calls) def test_lost_connection_during_error(self): self.do_connection_lost("error", "[\n") @@ -410,13 +287,11 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.protocol.lineReceived("test old mcdonald\n") self.protocol.lineReceived("failure old mcdonald\n") self.protocol.lostConnection() - test = subunit.RemotedTestCase("old mcdonald") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, - [(self.test, subunit.RemoteError())]) - self.assertEqual(self.client.success_calls, []) + self.assertEqual([ + ('startTest', self.test), + ('addFailure', self.test, subunit.RemoteError("")), + ('stopTest', self.test), + ], self.client._calls) def test_lost_connection_during_failure(self): self.do_connection_lost("failure", "[\n") @@ -428,11 +303,11 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.protocol.lineReceived("test old mcdonald\n") self.protocol.lineReceived("success old mcdonald\n") self.protocol.lostConnection() - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, [self.test]) + self.assertEqual([ + ('startTest', self.test), + ('addSuccess', self.test), + ('stopTest', self.test), + ], self.client._calls) def test_lost_connection_during_success(self): self.do_connection_lost("success", "[\n") @@ -456,7 +331,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): class TestInTestMultipart(unittest.TestCase): def setUp(self): - self.client = MockTestProtocolServerClient() + self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client) self.protocol.lineReceived("test mcdonalds farm\n") self.test = subunit.RemotedTestCase("mcdonalds farm") @@ -474,18 +349,19 @@ class TestInTestMultipart(unittest.TestCase): class TestTestProtocolServerAddError(unittest.TestCase): def setUp(self): - self.client = MockTestProtocolServerClient() + self.client = Python26TestResult() self.protocol = subunit.TestProtocolServer(self.client) self.protocol.lineReceived("test mcdonalds farm\n") self.test = subunit.RemotedTestCase("mcdonalds farm") def simple_error_keyword(self, keyword): self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, [ - (self.test, subunit.RemoteError(""))]) - self.assertEqual(self.client.failure_calls, []) + failure = subunit.RemoteError("") + self.assertEqual([ + ('startTest', self.test), + ('addError', self.test, failure), + ('stopTest', self.test), + ], self.client._calls) def test_simple_error(self): self.simple_error_keyword("error") @@ -496,21 +372,23 @@ class TestTestProtocolServerAddError(unittest.TestCase): def test_error_empty_message(self): self.protocol.lineReceived("error mcdonalds farm [\n") self.protocol.lineReceived("]\n") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, [ - (self.test, subunit.RemoteError(""))]) - self.assertEqual(self.client.failure_calls, []) + failure = subunit.RemoteError("") + self.assertEqual([ + ('startTest', self.test), + ('addError', self.test, failure), + ('stopTest', self.test), + ], self.client._calls) def error_quoted_bracket(self, keyword): self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword) self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, [ - (self.test, subunit.RemoteError("]\n"))]) - self.assertEqual(self.client.failure_calls, []) + failure = subunit.RemoteError("]\n") + self.assertEqual([ + ('startTest', self.test), + ('addError', self.test, failure), + ('stopTest', self.test), + ], self.client._calls) def test_error_quoted_bracket(self): self.error_quoted_bracket("error") @@ -522,18 +400,19 @@ class TestTestProtocolServerAddError(unittest.TestCase): class TestTestProtocolServerAddFailure(unittest.TestCase): def setUp(self): - self.client = MockTestProtocolServerClient() + self.client = Python26TestResult() self.protocol = subunit.TestProtocolServer(self.client) self.protocol.lineReceived("test mcdonalds farm\n") self.test = subunit.RemotedTestCase("mcdonalds farm") def simple_failure_keyword(self, keyword): self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, - [(self.test, subunit.RemoteError())]) + failure = subunit.RemoteError("") + self.assertEqual([ + ('startTest', self.test), + ('addFailure', self.test, failure), + ('stopTest', self.test), + ], self.client._calls) def test_simple_failure(self): self.simple_failure_keyword("failure") @@ -544,21 +423,23 @@ class TestTestProtocolServerAddFailure(unittest.TestCase): def test_failure_empty_message(self): self.protocol.lineReceived("failure mcdonalds farm [\n") self.protocol.lineReceived("]\n") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, - [(self.test, subunit.RemoteError())]) + failure = subunit.RemoteError("") + self.assertEqual([ + ('startTest', self.test), + ('addFailure', self.test, failure), + ('stopTest', self.test), + ], self.client._calls) def failure_quoted_bracket(self, keyword): self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword) self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, - [(self.test, subunit.RemoteError("]\n"))]) + failure = subunit.RemoteError("]\n") + self.assertEqual([ + ('startTest', self.test), + ('addFailure', self.test, failure), + ('stopTest', self.test), + ], self.client._calls) def test_failure_quoted_bracket(self): self.failure_quoted_bracket("failure") @@ -579,36 +460,38 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): def setup_python26(self): """Setup a test object ready to be xfailed and thunk to success.""" - self.client = MockTestProtocolServerClient() + self.client = Python26TestResult() self.setup_protocol() def setup_python27(self): """Setup a test object ready to be xfailed and thunk to success.""" - self.client = MockTestProtocolServerClient() - self.client.addExpectedFailure = self.capture_expected_failure - self._calls = [] + self.client = Python27TestResult() self.setup_protocol() def setup_protocol(self): """Setup the protocol based on self.client.""" self.protocol = subunit.TestProtocolServer(self.client) self.protocol.lineReceived("test mcdonalds farm\n") - self.test = self.client.start_calls[-1] + self.test = self.client._calls[-1][-1] def simple_xfail_keyword(self, keyword, as_success): self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, []) self.check_success_or_xfail(as_success) - def check_success_or_xfail(self, as_success): + def check_success_or_xfail(self, as_success, error_message=""): if as_success: - self.assertEqual(self.client.success_calls, [self.test]) + self.assertEqual([ + ('startTest', self.test), + ('addSuccess', self.test), + ('stopTest', self.test), + ], self.client._calls) else: - self.assertEqual(1, len(self._calls)) - self.assertEqual(self.test, self._calls[0][0]) + error = subunit.RemoteError(error_message) + self.assertEqual([ + ('startTest', self.test), + ('addExpectedFailure', self.test, error), + ('stopTest', self.test), + ], self.client._calls) def test_simple_xfail(self): self.setup_python26() @@ -631,10 +514,6 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): def empty_message(self, as_success): self.protocol.lineReceived("xfail mcdonalds farm [\n") self.protocol.lineReceived("]\n") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, []) self.check_success_or_xfail(as_success) def xfail_quoted_bracket(self, keyword, as_success): @@ -643,11 +522,7 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword) self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, []) - self.check_success_or_xfail(as_success) + self.check_success_or_xfail(as_success, "]\n") def test_xfail_quoted_bracket(self): self.setup_python26() @@ -671,20 +546,18 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): def setUp(self): """Setup a test object ready to be skipped.""" - self.client = MockTestProtocolServerClient() + self.client = Python27TestResult() self.protocol = subunit.TestProtocolServer(self.client) self.protocol.lineReceived("test mcdonalds farm\n") - self.test = self.client.start_calls[-1] + self.test = self.client._calls[-1][-1] def simple_skip_keyword(self, keyword): self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, []) - self.assertEqual(self.client.skip_calls, - [(self.test, 'No reason given')]) + self.assertEqual([ + ('startTest', self.test), + ('addSkip', self.test, "No reason given"), + ('stopTest', self.test), + ], self.client._calls) def test_simple_skip(self): self.simple_skip_keyword("skip") @@ -695,13 +568,11 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): def test_skip_empty_message(self): self.protocol.lineReceived("skip mcdonalds farm [\n") self.protocol.lineReceived("]\n") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, []) - self.assertEqual(self.client.skip_calls, - [(self.test, "No reason given")]) + self.assertEqual([ + ('startTest', self.test), + ('addSkip', self.test, "No reason given"), + ('stopTest', self.test), + ], self.client._calls) def skip_quoted_bracket(self, keyword): # This tests it is accepted, but cannot test it is used today, because @@ -709,13 +580,11 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword) self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, []) - self.assertEqual(self.client.skip_calls, - [(self.test, "]\n")]) + self.assertEqual([ + ('startTest', self.test), + ('addSkip', self.test, "]\n"), + ('stopTest', self.test), + ], self.client._calls) def test_skip_quoted_bracket(self): self.skip_quoted_bracket("skip") @@ -727,17 +596,18 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): class TestTestProtocolServerAddSuccess(unittest.TestCase): def setUp(self): - self.client = MockTestProtocolServerClient() + self.client = Python26TestResult() self.protocol = subunit.TestProtocolServer(self.client) self.protocol.lineReceived("test mcdonalds farm\n") self.test = subunit.RemotedTestCase("mcdonalds farm") def simple_success_keyword(self, keyword): self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.success_calls, [self.test]) + self.assertEqual([ + ('startTest', self.test), + ('addSuccess', self.test), + ('stopTest', self.test), + ], self.client._calls) def test_simple_success(self): self.simple_success_keyword("failure") @@ -754,11 +624,11 @@ class TestTestProtocolServerAddSuccess(unittest.TestCase): def test_success_empty_message(self): self.protocol.lineReceived("success mcdonalds farm [\n") self.protocol.lineReceived("]\n") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, [self.test]) + self.assertEqual([ + ('startTest', self.test), + ('addSuccess', self.test), + ('stopTest', self.test), + ], self.client._calls) def success_quoted_bracket(self, keyword): # This tests it is accepted, but cannot test it is used today, because @@ -766,11 +636,11 @@ class TestTestProtocolServerAddSuccess(unittest.TestCase): self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword) self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") - self.assertEqual(self.client.start_calls, [self.test]) - self.assertEqual(self.client.end_calls, [self.test]) - self.assertEqual(self.client.error_calls, []) - self.assertEqual(self.client.failure_calls, []) - self.assertEqual(self.client.success_calls, [self.test]) + self.assertEqual([ + ('startTest', self.test), + ('addSuccess', self.test), + ('stopTest', self.test), + ], self.client._calls) def test_success_quoted_bracket(self): self.success_quoted_bracket("success") @@ -783,8 +653,7 @@ class TestTestProtocolServerProgress(unittest.TestCase): """Test receipt of progress: directives.""" def test_progress_accepted_stdlib(self): - # With a stdlib TestResult, progress events are swallowed. - self.result = unittest.TestResult() + self.result = Python26TestResult() self.stream = StringIO() self.protocol = subunit.TestProtocolServer(self.result, stream=self.stream) @@ -795,7 +664,7 @@ class TestTestProtocolServerProgress(unittest.TestCase): def test_progress_accepted_extended(self): # With a progress capable TestResult, progress events are emitted. - self.result = MockTestProtocolServerClient() + self.result = ExtendedTestResult() self.stream = StringIO() self.protocol = subunit.TestProtocolServer(self.result, stream=self.stream) @@ -805,49 +674,48 @@ class TestTestProtocolServerProgress(unittest.TestCase): self.protocol.lineReceived("progress: pop") self.protocol.lineReceived("progress: +4") self.assertEqual("", self.stream.getvalue()) - self.assertEqual( - [(23, subunit.PROGRESS_SET), (None, subunit.PROGRESS_PUSH), - (-2, subunit.PROGRESS_CUR), (None, subunit.PROGRESS_POP), - (4, subunit.PROGRESS_CUR)], - self.result.progress_calls) + self.assertEqual([ + ('progress', 23, subunit.PROGRESS_SET), + ('progress', None, subunit.PROGRESS_PUSH), + ('progress', -2, subunit.PROGRESS_CUR), + ('progress', None, subunit.PROGRESS_POP), + ('progress', 4, subunit.PROGRESS_CUR), + ], self.result._calls) class TestTestProtocolServerStreamTags(unittest.TestCase): """Test managing tags on the protocol level.""" def setUp(self): - self.client = MockExtendedTestProtocolServerClient() + self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client) def test_initial_tags(self): self.protocol.lineReceived("tags: foo bar:baz quux\n") - self.assertEqual(set(["foo", "bar:baz", "quux"]), - self.client.new_tags) - self.assertEqual(set(), self.client.gone_tags) + self.assertEqual([ + ('tags', set(["foo", "bar:baz", "quux"]), set()), + ], self.client._calls) def test_minus_removes_tags(self): - self.protocol.lineReceived("tags: foo bar\n") - self.assertEqual(set(["foo", "bar"]), - self.client.new_tags) - self.assertEqual(set(), self.client.gone_tags) self.protocol.lineReceived("tags: -bar quux\n") - self.assertEqual(set(["quux"]), self.client.new_tags) - self.assertEqual(set(["bar"]), self.client.gone_tags) + self.assertEqual([ + ('tags', set(["quux"]), set(["bar"])), + ], self.client._calls) def test_tags_do_not_get_set_on_test(self): self.protocol.lineReceived("test mcdonalds farm\n") - test = self.client.start_calls[-1] + test = self.client._calls[0][-1] self.assertEqual(None, getattr(test, 'tags', None)) def test_tags_do_not_get_set_on_global_tags(self): self.protocol.lineReceived("tags: foo bar\n") self.protocol.lineReceived("test mcdonalds farm\n") - test = self.client.start_calls[-1] + test = self.client._calls[-1][-1] self.assertEqual(None, getattr(test, 'tags', None)) def test_tags_get_set_on_test_tags(self): self.protocol.lineReceived("test mcdonalds farm\n") - test = self.client.start_calls[-1] + test = self.client._calls[-1][-1] self.protocol.lineReceived("tags: foo bar\n") self.protocol.lineReceived("success mcdonalds farm\n") self.assertEqual(None, getattr(test, 'tags', None)) @@ -857,7 +725,7 @@ class TestTestProtocolServerStreamTime(unittest.TestCase): """Test managing time information at the protocol level.""" def test_time_accepted_stdlib(self): - self.result = unittest.TestResult() + self.result = Python26TestResult() self.stream = StringIO() self.protocol = subunit.TestProtocolServer(self.result, stream=self.stream) @@ -865,14 +733,16 @@ class TestTestProtocolServerStreamTime(unittest.TestCase): self.assertEqual("", self.stream.getvalue()) def test_time_accepted_extended(self): - self.result = MockTestProtocolServerClient() + self.result = ExtendedTestResult() self.stream = StringIO() self.protocol = subunit.TestProtocolServer(self.result, stream=self.stream) self.protocol.lineReceived("time: 2001-12-12 12:59:59Z\n") self.assertEqual("", self.stream.getvalue()) - self.assertEqual(datetime.datetime(2001, 12, 12, 12, 59, 59, 0, - iso8601.Utc()), self.result._time) + self.assertEqual([ + ('time', datetime.datetime(2001, 12, 12, 12, 59, 59, 0, + iso8601.Utc())) + ], self.result._calls) class TestRemotedTestCase(unittest.TestCase): @@ -940,20 +810,26 @@ class TestExecTestCase(unittest.TestCase): self.assertEqual(1, result.testsRun) def test_run(self): - runner = MockTestProtocolServerClient() + result = Python26TestResult() test = self.SampleExecTestCase("test_sample_method") - test.run(runner) + test.run(result) mcdonald = subunit.RemotedTestCase("old mcdonald") bing = subunit.RemotedTestCase("bing crosby") an_error = subunit.RemotedTestCase("an error") - self.assertEqual(runner.error_calls, - [(an_error, subunit.RemoteError())]) - self.assertEqual(runner.failure_calls, - [(bing, - subunit.RemoteError( - "foo.c:53:ERROR invalid state\n"))]) - self.assertEqual(runner.start_calls, [mcdonald, bing, an_error]) - self.assertEqual(runner.end_calls, [mcdonald, bing, an_error]) + error_error = subunit.RemoteError() + bing_failure = subunit.RemoteError( + "foo.c:53:ERROR invalid state\n") + self.assertEqual([ + ('startTest', mcdonald), + ('addSuccess', mcdonald), + ('stopTest', mcdonald), + ('startTest', bing), + ('addFailure', bing, bing_failure), + ('stopTest', bing), + ('startTest', an_error), + ('addError', an_error, error_error), + ('stopTest', an_error), + ], result._calls) def test_debug(self): test = self.SampleExecTestCase("test_sample_method") -- cgit v1.2.1 From ab0aada6011a4d0db688de56cffef931d5585629 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sat, 24 Oct 2009 19:24:09 +1100 Subject: change error reports to use the extended details interface. --- python/subunit/__init__.py | 5 +++-- python/subunit/content.py | 4 ++++ python/subunit/details.py | 6 +++++- python/subunit/tests/test_content.py | 12 ++++++++++++ python/subunit/tests/test_details.py | 14 ++++++++++++-- python/subunit/tests/test_test_protocol.py | 18 +++++++++++------- 6 files changed, 47 insertions(+), 12 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 4cb273b..a981a82 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -266,7 +266,8 @@ class _InTest(_ParserState): self.parser.stdOutLineReceived(line) def _error(self): - self.parser.client.addError(self.parser._current_test, RemoteError("")) + self.parser.client.addError(self.parser._current_test, + details={}) def addError(self, offset, line): """An 'error:' directive has been read.""" @@ -373,7 +374,7 @@ class _ReadingErrorDetails(_ReadingDetails): def _report_outcome(self): self.parser.client.addError(self.parser._current_test, - RemoteError(self.details_parser.get_message())) + details=self.details_parser.get_details()) def _outcome_label(self): return "error" diff --git a/python/subunit/content.py b/python/subunit/content.py index 160a58a..d7d3893 100644 --- a/python/subunit/content.py +++ b/python/subunit/content.py @@ -42,6 +42,10 @@ class Content(object): 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() diff --git a/python/subunit/details.py b/python/subunit/details.py index 1460170..c54a220 100644 --- a/python/subunit/details.py +++ b/python/subunit/details.py @@ -43,7 +43,11 @@ class SimpleDetailsParser(DetailsParser): self._message += line def get_details(self): - return None + result = {} + result['traceback'] = content.Content( + content_type.ContentType("text", "x-traceback"), + lambda:[self._message]) + return result def get_message(self): return self._message diff --git a/python/subunit/tests/test_content.py b/python/subunit/tests/test_content.py index 8075cf9..c13b254 100644 --- a/python/subunit/tests/test_content.py +++ b/python/subunit/tests/test_content.py @@ -40,6 +40,18 @@ class TestContent(unittest.TestCase): 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): diff --git a/python/subunit/tests/test_details.py b/python/subunit/tests/test_details.py index f76d505..5873ee7 100644 --- a/python/subunit/tests/test_details.py +++ b/python/subunit/tests/test_details.py @@ -46,9 +46,19 @@ class TestSimpleDetails(unittest.TestCase): parser = details.SimpleDetailsParser(None) self.assertEqual("", parser.get_message()) - def test_get_details_is_None(self): + def test_get_details(self): parser = details.SimpleDetailsParser(None) - self.assertEqual(None, parser.get_details()) + traceback = "" + expected = {} + expected['traceback'] = content.Content( + content_type.ContentType("text", "x-traceback"), + lambda:[""]) + found = parser.get_details() + self.assertEqual(expected.keys(), found.keys()) + self.assertEqual(expected['traceback'].content_type, + found['traceback'].content_type) + self.assertEqual(''.join(expected['traceback'].iter_bytes()), + ''.join(found['traceback'].iter_bytes())) class TestMultipartDetails(unittest.TestCase): diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index fea715c..39734b5 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -349,17 +349,17 @@ class TestInTestMultipart(unittest.TestCase): class TestTestProtocolServerAddError(unittest.TestCase): def setUp(self): - self.client = Python26TestResult() + self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client) self.protocol.lineReceived("test mcdonalds farm\n") self.test = subunit.RemotedTestCase("mcdonalds farm") def simple_error_keyword(self, keyword): self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) - failure = subunit.RemoteError("") + details = {} self.assertEqual([ ('startTest', self.test), - ('addError', self.test, failure), + ('addError', self.test, details), ('stopTest', self.test), ], self.client._calls) @@ -372,10 +372,12 @@ class TestTestProtocolServerAddError(unittest.TestCase): def test_error_empty_message(self): self.protocol.lineReceived("error mcdonalds farm [\n") self.protocol.lineReceived("]\n") - failure = subunit.RemoteError("") + details = {} + details['traceback'] = Content(ContentType("text", "x-traceback"), + lambda:[""]) self.assertEqual([ ('startTest', self.test), - ('addError', self.test, failure), + ('addError', self.test, details), ('stopTest', self.test), ], self.client._calls) @@ -383,10 +385,12 @@ class TestTestProtocolServerAddError(unittest.TestCase): self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword) self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") - failure = subunit.RemoteError("]\n") + details = {} + details['traceback'] = Content(ContentType("text", "x-traceback"), + lambda:["]\n"]) self.assertEqual([ ('startTest', self.test), - ('addError', self.test, failure), + ('addError', self.test, details), ('stopTest', self.test), ], self.client._calls) -- cgit v1.2.1 From 2a9fac07e208637dcecf5d57fafa2b6bbbe0ef2c Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sat, 24 Oct 2009 20:10:23 +1100 Subject: Move Failure reporting to the new details API. --- python/subunit/__init__.py | 5 ++- python/subunit/content.py | 4 ++ python/subunit/tests/test_test_protocol.py | 69 ++++++++++++++++-------------- 3 files changed, 45 insertions(+), 33 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index a981a82..5c8585f 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -284,7 +284,8 @@ class _InTest(_ParserState): self.parser._reading_xfail_details) def _failure(self): - self.parser.client.addFailure(self.parser._current_test, RemoteError()) + self.parser.client.addFailure(self.parser._current_test, + details={}) def addFailure(self, offset, line): """A 'failure:' directive has been read.""" @@ -363,7 +364,7 @@ class _ReadingFailureDetails(_ReadingDetails): def _report_outcome(self): self.parser.client.addFailure(self.parser._current_test, - RemoteError(self.details_parser.get_message())) + details=self.details_parser.get_details()) def _outcome_label(self): return "failure" diff --git a/python/subunit/content.py b/python/subunit/content.py index d7d3893..5961e24 100644 --- a/python/subunit/content.py +++ b/python/subunit/content.py @@ -50,6 +50,10 @@ class Content(object): """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. diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 39734b5..d117a40 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -70,7 +70,9 @@ class TestTestProtocolServerPipe(unittest.TestCase): [(an_error, 'RemoteException: \n\n')]) self.assertEqual( client.failures, - [(bing, "RemoteException: foo.c:53:ERROR invalid state\n\n")]) + [(bing, "RemoteException: Text attachment: traceback\n" + "------------\nfoo.c:53:ERROR invalid state\n" + "------------\n\n")]) self.assertEqual(client.testsRun, 3) @@ -106,7 +108,7 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): def setUp(self): self.stdout = StringIO() self.test = subunit.RemotedTestCase("old mcdonald") - self.client = Python26TestResult() + self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client, self.stdout) def keywords_before_test(self): @@ -139,7 +141,7 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): self.keywords_before_test() self.assertEqual([ ('startTest', self.test), - ('addError', self.test, subunit.RemoteError("")), + ('addError', self.test, {}), ('stopTest', self.test), ], self.client._calls) @@ -149,7 +151,7 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): self.keywords_before_test() self.assertEqual(self.client._calls, [ ('startTest', self.test), - ('addFailure', self.test, subunit.RemoteError("")), + ('addFailure', self.test, {}), ('stopTest', self.test), ]) @@ -188,11 +190,13 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): "]\n") self.assertEqual(self.client._calls, [ ('startTest', self.test), - ('addFailure', self.test, subunit.RemoteError("")), + ('addFailure', self.test, {}), ('stopTest', self.test), ]) def test_keywords_during_failure(self): + # A smoke test to make sure that the details parsers have control + # appropriately. self.protocol.lineReceived("test old mcdonald\n") self.protocol.lineReceived("failure: old mcdonald [\n") self.protocol.lineReceived("test old mcdonald\n") @@ -207,7 +211,10 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") self.assertEqual(self.stdout.getvalue(), "") - failure = subunit.RemoteError("test old mcdonald\n" + details = {} + details['traceback'] = Content(ContentType("text", "x-traceback"), + lambda:[ + "test old mcdonald\n" "failure a\n" "failure: a\n" "error a\n" @@ -216,10 +223,10 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): "success: a\n" "successful a\n" "successful: a\n" - "]\n") + "]\n"]) self.assertEqual(self.client._calls, [ ('startTest', self.test), - ('addFailure', self.test, failure), + ('addFailure', self.test, details), ('stopTest', self.test), ]) @@ -404,20 +411,23 @@ class TestTestProtocolServerAddError(unittest.TestCase): class TestTestProtocolServerAddFailure(unittest.TestCase): def setUp(self): - self.client = Python26TestResult() + self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client) self.protocol.lineReceived("test mcdonalds farm\n") self.test = subunit.RemotedTestCase("mcdonalds farm") - def simple_failure_keyword(self, keyword): - self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) - failure = subunit.RemoteError("") + def assertFailure(self, details): self.assertEqual([ ('startTest', self.test), - ('addFailure', self.test, failure), + ('addFailure', self.test, details), ('stopTest', self.test), ], self.client._calls) + def simple_failure_keyword(self, keyword): + self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) + details = {} + self.assertFailure(details) + def test_simple_failure(self): self.simple_failure_keyword("failure") @@ -427,23 +437,19 @@ class TestTestProtocolServerAddFailure(unittest.TestCase): def test_failure_empty_message(self): self.protocol.lineReceived("failure mcdonalds farm [\n") self.protocol.lineReceived("]\n") - failure = subunit.RemoteError("") - self.assertEqual([ - ('startTest', self.test), - ('addFailure', self.test, failure), - ('stopTest', self.test), - ], self.client._calls) + details = {} + details['traceback'] = Content(ContentType("text", "x-traceback"), + lambda:[""]) + self.assertFailure(details) def failure_quoted_bracket(self, keyword): self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword) self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") - failure = subunit.RemoteError("]\n") - self.assertEqual([ - ('startTest', self.test), - ('addFailure', self.test, failure), - ('stopTest', self.test), - ], self.client._calls) + details = {} + details['traceback'] = Content(ContentType("text", "x-traceback"), + lambda:["]\n"]) + self.assertFailure(details) def test_failure_quoted_bracket(self): self.failure_quoted_bracket("failure") @@ -814,24 +820,25 @@ class TestExecTestCase(unittest.TestCase): self.assertEqual(1, result.testsRun) def test_run(self): - result = Python26TestResult() + result = ExtendedTestResult() test = self.SampleExecTestCase("test_sample_method") test.run(result) mcdonald = subunit.RemotedTestCase("old mcdonald") bing = subunit.RemotedTestCase("bing crosby") + bing_details = {} + bing_details['traceback'] = Content(ContentType("text", "x-traceback"), + lambda:["foo.c:53:ERROR invalid state\n"]) an_error = subunit.RemotedTestCase("an error") - error_error = subunit.RemoteError() - bing_failure = subunit.RemoteError( - "foo.c:53:ERROR invalid state\n") + error_details = {} self.assertEqual([ ('startTest', mcdonald), ('addSuccess', mcdonald), ('stopTest', mcdonald), ('startTest', bing), - ('addFailure', bing, bing_failure), + ('addFailure', bing, bing_details), ('stopTest', bing), ('startTest', an_error), - ('addError', an_error, error_error), + ('addError', an_error, error_details), ('stopTest', an_error), ], result._calls) -- cgit v1.2.1 From b233594fc6031cc034e390387be187ae24c11e75 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sat, 24 Oct 2009 20:28:19 +1100 Subject: Move expected failures to the details API. --- python/subunit/__init__.py | 4 ++-- python/subunit/tests/test_test_protocol.py | 38 +++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 8 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 5c8585f..570af8b 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -276,7 +276,7 @@ class _InTest(_ParserState): def _xfail(self): self.parser.client.addExpectedFailure(self.parser._current_test, - RemoteError()) + details={}) def addExpectedFail(self, offset, line): """An 'xfail:' directive has been read.""" @@ -386,7 +386,7 @@ class _ReadingExpectedFailureDetails(_ReadingDetails): def _report_outcome(self): self.parser.client.addExpectedFailure(self.parser._current_test, - RemoteError(self.details_parser.get_message())) + details=self.details_parser.get_details()) def _outcome_label(self): return "xfail" diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index d117a40..1a4e1fd 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -474,10 +474,15 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): self.setup_protocol() def setup_python27(self): - """Setup a test object ready to be xfailed and thunk to success.""" + """Setup a test object ready to be xfailed.""" self.client = Python27TestResult() self.setup_protocol() + def setup_python_ex(self): + """Setup a test object ready to be xfailed with details.""" + self.client = ExtendedTestResult() + self.setup_protocol() + def setup_protocol(self): """Setup the protocol based on self.client.""" self.protocol = subunit.TestProtocolServer(self.client) @@ -488,7 +493,7 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) self.check_success_or_xfail(as_success) - def check_success_or_xfail(self, as_success, error_message=""): + def check_success_or_xfail(self, as_success, error_message=None): if as_success: self.assertEqual([ ('startTest', self.test), @@ -496,10 +501,21 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): ('stopTest', self.test), ], self.client._calls) else: - error = subunit.RemoteError(error_message) + details = {} + if error_message is not None: + details['traceback'] = Content( + ContentType("text", "x-traceback"), lambda:[error_message]) + if isinstance(self.client, ExtendedTestResult): + value = details + else: + if error_message is not None: + value = subunit.RemoteError('Text attachment: traceback\n' + '------------\n' + error_message + '------------\n') + else: + value = subunit.RemoteError() self.assertEqual([ ('startTest', self.test), - ('addExpectedFailure', self.test, error), + ('addExpectedFailure', self.test, value), ('stopTest', self.test), ], self.client._calls) @@ -508,23 +524,29 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): self.simple_xfail_keyword("xfail", True) self.setup_python27() self.simple_xfail_keyword("xfail", False) + self.setup_python_ex() + self.simple_xfail_keyword("xfail", False) def test_simple_xfail_colon(self): self.setup_python26() self.simple_xfail_keyword("xfail:", True) self.setup_python27() self.simple_xfail_keyword("xfail:", False) + self.setup_python_ex() + self.simple_xfail_keyword("xfail:", False) def test_xfail_empty_message(self): self.setup_python26() self.empty_message(True) self.setup_python27() self.empty_message(False) + self.setup_python_ex() + self.empty_message(False, error_message="") - def empty_message(self, as_success): + def empty_message(self, as_success, error_message="\n"): self.protocol.lineReceived("xfail mcdonalds farm [\n") self.protocol.lineReceived("]\n") - self.check_success_or_xfail(as_success) + self.check_success_or_xfail(as_success, error_message) def xfail_quoted_bracket(self, keyword, as_success): # This tests it is accepted, but cannot test it is used today, because @@ -539,12 +561,16 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): self.xfail_quoted_bracket("xfail", True) self.setup_python27() self.xfail_quoted_bracket("xfail", False) + self.setup_python_ex() + self.xfail_quoted_bracket("xfail", False) def test_xfail_colon_quoted_bracket(self): self.setup_python26() self.xfail_quoted_bracket("xfail:", True) self.setup_python27() self.xfail_quoted_bracket("xfail:", False) + self.setup_python_ex() + self.xfail_quoted_bracket("xfail:", False) class TestTestProtocolServerAddSkip(unittest.TestCase): -- cgit v1.2.1 From 887d9b402b18a94c4cb36eab1dbfd9df342be31e Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sat, 24 Oct 2009 20:40:51 +1100 Subject: Move skips to the details API. --- python/subunit/__init__.py | 14 ++++---------- python/subunit/details.py | 15 ++++++++++----- python/subunit/tests/test_details.py | 10 ++++++++++ python/subunit/tests/test_test_protocol.py | 27 +++++++++++++-------------- 4 files changed, 37 insertions(+), 29 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 570af8b..ab7fbd9 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -284,8 +284,7 @@ class _InTest(_ParserState): self.parser._reading_xfail_details) def _failure(self): - self.parser.client.addFailure(self.parser._current_test, - details={}) + self.parser.client.addFailure(self.parser._current_test, details={}) def addFailure(self, offset, line): """A 'failure:' directive has been read.""" @@ -293,7 +292,7 @@ class _InTest(_ParserState): self.parser._reading_failure_details) def _skip(self): - self.parser._skip_or_error() + self.parser.client.addSkip(self.parser._current_test, details={}) def addSkip(self, offset, line): """A 'skip:' directive has been read.""" @@ -396,7 +395,8 @@ class _ReadingSkipDetails(_ReadingDetails): """State for the subunit parser when reading skip details.""" def _report_outcome(self): - self.parser._skip_or_error(self.details_parser.get_message()) + self.parser.client.addSkip(self.parser._current_test, + details=self.details_parser.get_details(True)) def _outcome_label(self): return "skip" @@ -442,12 +442,6 @@ class TestProtocolServer(object): # start with outside test. self._state = self._outside_test - def _skip_or_error(self, message=None): - """Report the current test as a skip if possible, or else an error.""" - if not message: - message = "No reason given" - self.client.addSkip(self._current_test, message) - 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 index c54a220..43e723d 100644 --- a/python/subunit/details.py +++ b/python/subunit/details.py @@ -42,11 +42,16 @@ class SimpleDetailsParser(DetailsParser): else: self._message += line - def get_details(self): + def get_details(self, for_skip=False): result = {} - result['traceback'] = content.Content( - content_type.ContentType("text", "x-traceback"), - lambda:[self._message]) + if not for_skip: + result['traceback'] = content.Content( + content_type.ContentType("text", "x-traceback"), + lambda:[self._message]) + else: + result['reason'] = content.Content( + content_type.ContentType("text", "plain"), + lambda:[self._message]) return result def get_message(self): @@ -88,7 +93,7 @@ class MultipartDetailsParser(DetailsParser): self._chunk_parser.close() self._parse_state = self._look_for_content - def get_details(self): + def get_details(self, for_skip=False): return self._details def get_message(self): diff --git a/python/subunit/tests/test_details.py b/python/subunit/tests/test_details.py index 5873ee7..af87655 100644 --- a/python/subunit/tests/test_details.py +++ b/python/subunit/tests/test_details.py @@ -60,6 +60,16 @@ class TestSimpleDetails(unittest.TestCase): self.assertEqual(''.join(expected['traceback'].iter_bytes()), ''.join(found['traceback'].iter_bytes())) + def test_get_details_skip(self): + parser = details.SimpleDetailsParser(None) + traceback = "" + expected = {} + expected['reason'] = content.Content( + content_type.ContentType("text", "plain"), + lambda:[""]) + found = parser.get_details(True) + self.assertEqual(expected, found) + class TestMultipartDetails(unittest.TestCase): diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 1a4e1fd..da57745 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -582,19 +582,26 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): def setUp(self): """Setup a test object ready to be skipped.""" - self.client = Python27TestResult() + self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client) self.protocol.lineReceived("test mcdonalds farm\n") self.test = self.client._calls[-1][-1] - def simple_skip_keyword(self, keyword): - self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) + def assertSkip(self, reason): + details = {} + if reason is not None: + details['reason'] = Content( + ContentType("text", "plain"), lambda:[reason]) self.assertEqual([ ('startTest', self.test), - ('addSkip', self.test, "No reason given"), + ('addSkip', self.test, details), ('stopTest', self.test), ], self.client._calls) + def simple_skip_keyword(self, keyword): + self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) + self.assertSkip(None) + def test_simple_skip(self): self.simple_skip_keyword("skip") @@ -604,11 +611,7 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): def test_skip_empty_message(self): self.protocol.lineReceived("skip mcdonalds farm [\n") self.protocol.lineReceived("]\n") - self.assertEqual([ - ('startTest', self.test), - ('addSkip', self.test, "No reason given"), - ('stopTest', self.test), - ], self.client._calls) + self.assertSkip("") def skip_quoted_bracket(self, keyword): # This tests it is accepted, but cannot test it is used today, because @@ -616,11 +619,7 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword) self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") - self.assertEqual([ - ('startTest', self.test), - ('addSkip', self.test, "]\n"), - ('stopTest', self.test), - ], self.client._calls) + self.assertSkip("]\n") def test_skip_quoted_bracket(self): self.skip_quoted_bracket("skip") -- cgit v1.2.1 From 30dae38d5c6a1f20bd4becf34f2cb5d57247c54a Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sat, 24 Oct 2009 20:46:23 +1100 Subject: Start reporting additional messages on successes via the details API. --- python/subunit/__init__.py | 7 ++++--- python/subunit/details.py | 10 +++++++--- python/subunit/tests/test_details.py | 12 +++++++++++- python/subunit/tests/test_test_protocol.py | 25 +++++++++++++++---------- 4 files changed, 37 insertions(+), 17 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index ab7fbd9..b5c678c 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -300,7 +300,7 @@ class _InTest(_ParserState): self.parser._reading_skip_details) def _succeed(self): - self.parser.client.addSuccess(self.parser._current_test) + self.parser.client.addSuccess(self.parser._current_test, details={}) def addSuccess(self, offset, line): """A 'success:' directive has been read.""" @@ -396,7 +396,7 @@ class _ReadingSkipDetails(_ReadingDetails): def _report_outcome(self): self.parser.client.addSkip(self.parser._current_test, - details=self.details_parser.get_details(True)) + details=self.details_parser.get_details("skip")) def _outcome_label(self): return "skip" @@ -406,7 +406,8 @@ class _ReadingSuccessDetails(_ReadingDetails): """State for the subunit parser when reading success details.""" def _report_outcome(self): - self.parser.client.addSuccess(self.parser._current_test) + self.parser.client.addSuccess(self.parser._current_test, + details=self.details_parser.get_details("success")) def _outcome_label(self): return "success" diff --git a/python/subunit/details.py b/python/subunit/details.py index 43e723d..2cedba2 100644 --- a/python/subunit/details.py +++ b/python/subunit/details.py @@ -42,14 +42,18 @@ class SimpleDetailsParser(DetailsParser): else: self._message += line - def get_details(self, for_skip=False): + def get_details(self, style=None): result = {} - if not for_skip: + if not style: result['traceback'] = content.Content( content_type.ContentType("text", "x-traceback"), lambda:[self._message]) else: - result['reason'] = content.Content( + if style == 'skip': + name = 'reason' + else: + name = 'message' + result[name] = content.Content( content_type.ContentType("text", "plain"), lambda:[self._message]) return result diff --git a/python/subunit/tests/test_details.py b/python/subunit/tests/test_details.py index af87655..2700d4a 100644 --- a/python/subunit/tests/test_details.py +++ b/python/subunit/tests/test_details.py @@ -67,7 +67,17 @@ class TestSimpleDetails(unittest.TestCase): expected['reason'] = content.Content( content_type.ContentType("text", "plain"), lambda:[""]) - found = parser.get_details(True) + found = parser.get_details("skip") + self.assertEqual(expected, found) + + def test_get_details_success(self): + parser = details.SimpleDetailsParser(None) + traceback = "" + expected = {} + expected['message'] = content.Content( + content_type.ContentType("text", "plain"), + lambda:[""]) + found = parser.get_details("success") self.assertEqual(expected, found) diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index da57745..c109724 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -631,7 +631,7 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): class TestTestProtocolServerAddSuccess(unittest.TestCase): def setUp(self): - self.client = Python26TestResult() + self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client) self.protocol.lineReceived("test mcdonalds farm\n") self.test = subunit.RemotedTestCase("mcdonalds farm") @@ -656,26 +656,31 @@ class TestTestProtocolServerAddSuccess(unittest.TestCase): def test_simple_success_colon(self): self.simple_success_keyword("successful:") - def test_success_empty_message(self): - self.protocol.lineReceived("success mcdonalds farm [\n") - self.protocol.lineReceived("]\n") + def assertSuccess(self, details): self.assertEqual([ ('startTest', self.test), - ('addSuccess', self.test), + ('addSuccess', self.test, details), ('stopTest', self.test), ], self.client._calls) + def test_success_empty_message(self): + self.protocol.lineReceived("success mcdonalds farm [\n") + self.protocol.lineReceived("]\n") + details = {} + details['message'] = Content(ContentType("text", "plain"), + lambda:[""]) + self.assertSuccess(details) + def success_quoted_bracket(self, keyword): # This tests it is accepted, but cannot test it is used today, because # of not having a way to expose it in Python so far. self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword) self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") - self.assertEqual([ - ('startTest', self.test), - ('addSuccess', self.test), - ('stopTest', self.test), - ], self.client._calls) + details = {} + details['message'] = Content(ContentType("text", "plain"), + lambda:["]\n"]) + self.assertSuccess(details) def test_success_quoted_bracket(self): self.success_quoted_bracket("success") -- cgit v1.2.1 From 29d060e97474b03b8f2ebd310d5f2361ba44de14 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 25 Oct 2009 15:36:07 +1100 Subject: Move transport decoration fallback to use the ExtendedToOriginal fallback. --- python/subunit/test_results.py | 64 ++++++++++++------------------- python/subunit/tests/test_test_results.py | 42 ++++++++++++-------- 2 files changed, 51 insertions(+), 55 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index fad4760..e0891a3 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -36,42 +36,20 @@ class TestResultDecorator(object): def __init__(self, decorated): """Create a TestResultDecorator forwarding to decorated.""" - self.decorated = decorated - - def _call_maybe(self, method_name, fallback, *params): - """Call method_name on self.decorated, if present. - - This is used to guard newer methods which older pythons do not - support. While newer clients won't call these methods if they don't - exist, they do exist on the decorator, and thus the decorator has to be - the one to filter them out. - - :param method_name: The name of the method to call. - :param fallback: If not None, the fallback to call to handle downgrading - this method. Otherwise when method_name is not available, no - exception is raised and None is returned. - :param *params: Parameters to pass to method_name. - :return: The result of self.decorated.method_name(*params), if it - exists, and None otherwise. - """ - method = getattr(self.decorated, method_name, None) - if method is None: - if fallback is not None: - return fallback(*params) - return - return method(*params) + # Make every decorator degrade gracefully. + self.decorated = ExtendedToOriginalDecorator(decorated) def startTest(self, test): return self.decorated.startTest(test) def startTestRun(self): - return self._call_maybe("startTestRun", None) + return self.decorated.startTestRun() def stopTest(self, test): return self.decorated.stopTest(test) def stopTestRun(self): - return self._call_maybe("stopTestRun", None) + return self.decorated.stopTestRun() def addError(self, test, err): return self.decorated.addError(test, err) @@ -83,21 +61,16 @@ class TestResultDecorator(object): return self.decorated.addSuccess(test) def addSkip(self, test, reason): - return self._call_maybe("addSkip", self._degrade_skip, test, reason) - - def _degrade_skip(self, test, reason): - return self.decorated.addSuccess(test) + return self.decorated.addSkip(test, reason) def addExpectedFailure(self, test, err): - return self._call_maybe("addExpectedFailure", - self.decorated.addFailure, test, err) + return self.decorated.addExpectedFailure(test, err) def addUnexpectedSuccess(self, test): - return self._call_maybe("addUnexpectedSuccess", - self.decorated.addSuccess, test) + return self.decorated.addUnexpectedSuccess(test) def progress(self, offset, whence): - return self._call_maybe("progress", None, offset, whence) + return self.decorated.progress(offset, whence) def wasSuccessful(self): return self.decorated.wasSuccessful() @@ -110,10 +83,10 @@ class TestResultDecorator(object): return self.decorated.stop() def tags(self, gone_tags, new_tags): - return self._call_maybe("tags", None, gone_tags, new_tags) + return self.decorated.time(gone_tags, new_tags) def time(self, a_datetime): - return self._call_maybe("time", None, a_datetime) + return self.decorated.time(a_datetime) class HookedTestResultDecorator(TestResultDecorator): @@ -202,10 +175,10 @@ class AutoTimingTestResultDecorator(HookedTestResultDecorator): if time is not None: return time = datetime.datetime.utcnow().replace(tzinfo=iso8601.Utc()) - self._call_maybe("time", None, time) + self.decorated.time(time) def progress(self, offset, whence): - return self._call_maybe("progress", None, offset, whence) + return self.decorated.progress(offset, whence) @property def shouldStop(self): @@ -220,7 +193,7 @@ class AutoTimingTestResultDecorator(HookedTestResultDecorator): result object and disable automatic timestamps. """ self._time = a_datetime - return self._call_maybe("time", None, a_datetime) + return self.decorated.time(a_datetime) class ExtendedToOriginalDecorator(object): @@ -338,6 +311,10 @@ class ExtendedToOriginalDecorator(object): return return method(offset, whence) + @property + def shouldStop(self): + return self.decorated.shouldStop + def startTest(self, test): return self.decorated.startTest(test) @@ -347,6 +324,9 @@ class ExtendedToOriginalDecorator(object): except AttributeError: return + def stop(self): + return self.decorated.stop() + def stopTest(self, test): return self.decorated.stopTest(test) @@ -367,3 +347,7 @@ class ExtendedToOriginalDecorator(object): if method is None: return return method(a_datetime) + + def wasSuccessful(self): + return self.decorated.wasSuccessful() + diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index d333c10..cb7b47e 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -64,6 +64,7 @@ class LoggingResult(object): def __init__(self): self._calls = [] + self.shouldStop = False class Python26TestResult(LoggingResult): @@ -81,6 +82,9 @@ class Python26TestResult(LoggingResult): def startTest(self, test): self._calls.append(('startTest', test)) + def stop(self): + self.shouldStop = True + def stopTest(self, test): self._calls.append(('stopTest', test)) @@ -259,6 +263,12 @@ class TestExtendedToOriginalResultDecorator( 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) @@ -476,7 +486,7 @@ class TestExtendedToOriginalAddUnexpectedSuccess( class TestHookedTestResultDecorator(unittest.TestCase): def setUp(self): - # And end to the chain + # An end to the chain terminal = unittest.TestResult() # Asserts that the call was made to self.result before asserter was # called. @@ -484,13 +494,14 @@ class TestHookedTestResultDecorator(unittest.TestCase): # The result object we call, which much increase its call count. self.result = LoggingDecorator(asserter) asserter.earlier = self.result + self.decorated = asserter def tearDown(self): # The hook in self.result must have been called self.assertEqual(1, self.result._calls) # The hook in asserter must have been called too, otherwise the # assertion about ordering won't have completed. - self.assertEqual(1, self.result.decorated._calls) + self.assertEqual(1, self.decorated._calls) def test_startTest(self): self.result.startTest(self) @@ -546,20 +557,21 @@ class TestAutoTimingTestResultDecorator(unittest.TestCase): # The result object under test. self.result = subunit.test_results.AutoTimingTestResultDecorator( terminal) + self.decorated = terminal def test_without_time_calls_time_is_called_and_not_None(self): self.result.startTest(self) - self.assertEqual(1, len(self.result.decorated._calls)) - self.assertNotEqual(None, self.result.decorated._calls[0]) + self.assertEqual(1, len(self.decorated._calls)) + self.assertNotEqual(None, self.decorated._calls[0]) def test_no_time_from_progress(self): self.result.progress(1, subunit.PROGRESS_CUR) - self.assertEqual(0, len(self.result.decorated._calls)) + self.assertEqual(0, len(self.decorated._calls)) def test_no_time_from_shouldStop(self): - self.result.decorated.stop() + self.decorated.stop() self.result.shouldStop - self.assertEqual(0, len(self.result.decorated._calls)) + self.assertEqual(0, len(self.decorated._calls)) def test_calling_time_inhibits_automatic_time(self): # Calling time() outputs a time signal immediately and prevents @@ -568,22 +580,22 @@ class TestAutoTimingTestResultDecorator(unittest.TestCase): self.result.time(time) self.result.startTest(self) self.result.stopTest(self) - self.assertEqual(1, len(self.result.decorated._calls)) - self.assertEqual(time, self.result.decorated._calls[0]) + self.assertEqual(1, len(self.decorated._calls)) + self.assertEqual(time, self.decorated._calls[0]) def test_calling_time_None_enables_automatic_time(self): time = datetime.datetime(2009,10,11,12,13,14,15, iso8601.Utc()) self.result.time(time) - self.assertEqual(1, len(self.result.decorated._calls)) - self.assertEqual(time, self.result.decorated._calls[0]) + self.assertEqual(1, len(self.decorated._calls)) + self.assertEqual(time, self.decorated._calls[0]) # Calling None passes the None through, in case other results care. self.result.time(None) - self.assertEqual(2, len(self.result.decorated._calls)) - self.assertEqual(None, self.result.decorated._calls[1]) + self.assertEqual(2, len(self.decorated._calls)) + self.assertEqual(None, self.decorated._calls[1]) # Calling other methods doesn't generate an automatic time event. self.result.startTest(self) - self.assertEqual(3, len(self.result.decorated._calls)) - self.assertNotEqual(None, self.result.decorated._calls[2]) + self.assertEqual(3, len(self.decorated._calls)) + self.assertNotEqual(None, self.decorated._calls[2]) def test_suite(): -- cgit v1.2.1 From 761e21ba507fa0d40509301b1f470944c8a4deb0 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 25 Oct 2009 16:05:49 +1100 Subject: Add details API support to the Stats result object. --- python/subunit/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index b5c678c..c79ee0c 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -1067,13 +1067,13 @@ class TestResultStats(unittest.TestResult): def total_tests(self): return self.testsRun - def addError(self, test, err): + def addError(self, test, err, details=None): self.failed_tests += 1 - def addFailure(self, test, err): + def addFailure(self, test, err, details=None): self.failed_tests += 1 - def addSkip(self, test, reason): + def addSkip(self, test, reason, details=None): self.skipped_tests += 1 def formatStats(self): -- cgit v1.2.1 From f6b397940103643565955391e73099bf54de6dfb Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 25 Oct 2009 16:49:09 +1100 Subject: Support the extended TestResult details API on TestResultFilter (but not yet on predicates). --- python/subunit/__init__.py | 120 ---------------------------- python/subunit/test_results.py | 118 +++++++++++++++++++++++++++ python/subunit/tests/test_subunit_filter.py | 42 +++++----- 3 files changed, 138 insertions(+), 142 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index c79ee0c..dd3c0f1 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -1095,123 +1095,3 @@ class TestResultStats(unittest.TestResult): def wasSuccessful(self): """Tells whether or not this result was a success""" return self.failed_tests == 0 - - -class TestResultFilter(unittest.TestResult): - """A pyunit TestResult interface implementation which filters tests. - - Tests that pass the filter are handed on to another TestResult instance - for further processing/reporting. To obtain the filtered results, - the other instance must be interrogated. - - :ivar result: The result that tests are passed to after filtering. - :ivar filter_predicate: The callback run to decide whether to pass - a result. - """ - - def __init__(self, result, filter_error=False, filter_failure=False, - filter_success=True, filter_skip=False, - filter_predicate=None): - """Create a FilterResult object filtering to result. - - :param filter_error: Filter out errors. - :param filter_failure: Filter out failures. - :param filter_success: Filter out successful tests. - :param filter_skip: Filter out skipped tests. - :param filter_predicate: A callable taking (test, err) and - returning True if the result should be passed through. - err is None for success. - """ - unittest.TestResult.__init__(self) - self.result = result - self._filter_error = filter_error - self._filter_failure = filter_failure - self._filter_success = filter_success - self._filter_skip = filter_skip - if filter_predicate is None: - filter_predicate = lambda test, err: True - self.filter_predicate = filter_predicate - # The current test (for filtering tags) - self._current_test = None - # Has the current test been filtered (for outputting test tags) - self._current_test_filtered = None - # The (new, gone) tags for the current test. - self._current_test_tags = None - - def addError(self, test, err): - if not self._filter_error and self.filter_predicate(test, err): - self.result.startTest(test) - self.result.addError(test, err) - - def addFailure(self, test, err): - if not self._filter_failure and self.filter_predicate(test, err): - self.result.startTest(test) - self.result.addFailure(test, err) - - def addSkip(self, test, reason): - if not self._filter_skip and self.filter_predicate(test, reason): - self.result.startTest(test) - # This is duplicated, it would be nice to have on a 'calls - # TestResults' mixin perhaps. - addSkip = getattr(self.result, 'addSkip', None) - if not callable(addSkip): - self.result.addError(test, RemoteError(reason)) - else: - self.result.addSkip(test, reason) - - def addSuccess(self, test): - if not self._filter_success and self.filter_predicate(test, None): - self.result.startTest(test) - self.result.addSuccess(test) - - def startTest(self, test): - """Start a test. - - Not directly passed to the client, but used for handling of tags - correctly. - """ - self._current_test = test - self._current_test_filtered = False - self._current_test_tags = set(), set() - - def stopTest(self, test): - """Stop a test. - - Not directly passed to the client, but used for handling of tags - correctly. - """ - if not self._current_test_filtered: - # Tags to output for this test. - if self._current_test_tags[0] or self._current_test_tags[1]: - tags_method = getattr(self.result, 'tags', None) - if callable(tags_method): - self.result.tags(*self._current_test_tags) - self.result.stopTest(test) - self._current_test = None - self._current_test_filtered = None - self._current_test_tags = None - - def tags(self, new_tags, gone_tags): - """Handle tag instructions. - - Adds and removes tags as appropriate. If a test is currently running, - tags are not affected for subsequent tests. - - :param new_tags: Tags to add, - :param gone_tags: Tags to remove. - """ - if self._current_test is not None: - # gather the tags until the test stops. - self._current_test_tags[0].update(new_tags) - self._current_test_tags[0].difference_update(gone_tags) - self._current_test_tags[1].update(gone_tags) - self._current_test_tags[1].difference_update(new_tags) - tags_method = getattr(self.result, 'tags', None) - if tags_method is None: - return - return tags_method(new_tags, gone_tags) - - def id_to_orig_id(self, id): - if id.startswith("subunit.RemotedTestCase."): - return id[len("subunit.RemotedTestCase."):] - return id diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index e0891a3..5401c32 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -196,6 +196,124 @@ class AutoTimingTestResultDecorator(HookedTestResultDecorator): return self.decorated.time(a_datetime) +class TestResultFilter(TestResultDecorator): + """A pyunit TestResult interface implementation which filters tests. + + Tests that pass the filter are handed on to another TestResult instance + for further processing/reporting. To obtain the filtered results, + the other instance must be interrogated. + + :ivar result: The result that tests are passed to after filtering. + :ivar filter_predicate: The callback run to decide whether to pass + a result. + """ + + def __init__(self, result, filter_error=False, filter_failure=False, + filter_success=True, filter_skip=False, + filter_predicate=None): + """Create a FilterResult object filtering to result. + + :param filter_error: Filter out errors. + :param filter_failure: Filter out failures. + :param filter_success: Filter out successful tests. + :param filter_skip: Filter out skipped tests. + :param filter_predicate: A callable taking (test, err) and + returning True if the result should be passed through. + err is None for success. + """ + TestResultDecorator.__init__(self, result) + self._filter_error = filter_error + self._filter_failure = filter_failure + self._filter_success = filter_success + self._filter_skip = filter_skip + if filter_predicate is None: + filter_predicate = lambda test, err: True + self.filter_predicate = filter_predicate + # The current test (for filtering tags) + self._current_test = None + # Has the current test been filtered (for outputting test tags) + self._current_test_filtered = None + # The (new, gone) tags for the current test. + self._current_test_tags = None + + def addError(self, test, err, details=None): + if not self._filter_error and self.filter_predicate(test, err): + self.decorated.startTest(test) + self.decorated.addError(test, err, details=details) + + def addFailure(self, test, err, details=None): + if not self._filter_failure and self.filter_predicate(test, err): + self.decorated.startTest(test) + self.decorated.addFailure(test, err, details=details) + + def addSkip(self, test, reason, details=None): + if not self._filter_skip and self.filter_predicate(test, reason): + self.decorated.startTest(test) + self.decorated.addSkip(test, reason, details=details) + + def addSuccess(self, test, details=None): + if not self._filter_success and self.filter_predicate(test, None): + self.decorated.startTest(test) + self.decorated.addSuccess(test, details=details) + + def addExpectedFailure(self, test, err, details=None): + if self.filter_predicate(test, err): + self.decorated.startTest(test) + return self.decorated.addExpectedFailure(test, err, + details=details) + + def addUnexpectedSuccess(self, test, details=None): + self.decorated.startTest(test) + return self.decorated.addUnexpectedSuccess(test, details=details) + + def startTest(self, test): + """Start a test. + + Not directly passed to the client, but used for handling of tags + correctly. + """ + self._current_test = test + self._current_test_filtered = False + self._current_test_tags = set(), set() + + def stopTest(self, test): + """Stop a test. + + Not directly passed to the client, but used for handling of tags + correctly. + """ + if not self._current_test_filtered: + # Tags to output for this test. + if self._current_test_tags[0] or self._current_test_tags[1]: + self.decorated.tags(*self._current_test_tags) + self.decorated.stopTest(test) + self._current_test = None + self._current_test_filtered = None + self._current_test_tags = None + + def tags(self, new_tags, gone_tags): + """Handle tag instructions. + + Adds and removes tags as appropriate. If a test is currently running, + tags are not affected for subsequent tests. + + :param new_tags: Tags to add, + :param gone_tags: Tags to remove. + """ + if self._current_test is not None: + # gather the tags until the test stops. + self._current_test_tags[0].update(new_tags) + self._current_test_tags[0].difference_update(gone_tags) + self._current_test_tags[1].update(gone_tags) + self._current_test_tags[1].difference_update(new_tags) + return self.decorated.tags(new_tags, gone_tags) + + def id_to_orig_id(self, id): + if id.startswith("subunit.RemotedTestCase."): + return id[len("subunit.RemotedTestCase."):] + return id + + class ExtendedToOriginalDecorator(object): """Permit new TestResult API code to degrade gracefully with old results. diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index cc13b6c..9baff24 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -20,6 +20,7 @@ import unittest from StringIO import StringIO import subunit +from subunit.test_results import TestResultFilter class TestTestResultFilter(unittest.TestCase): @@ -31,57 +32,56 @@ class TestTestResultFilter(unittest.TestCase): def test_default(self): """The default is to exclude success and include everything else.""" self.filtered_result = unittest.TestResult() - self.filter = subunit.TestResultFilter(self.filtered_result) + self.filter = TestResultFilter(self.filtered_result) self.run_tests() - # skips are seen as errors by default python TestResult. - self.assertEqual(['error', 'skipped'], + # skips are seen as success by default python TestResult. + self.assertEqual(['error'], [error[0].id() for error in self.filtered_result.errors]) self.assertEqual(['failed'], [failure[0].id() for failure in self.filtered_result.failures]) - self.assertEqual(3, self.filtered_result.testsRun) + self.assertEqual(4, self.filtered_result.testsRun) def test_exclude_errors(self): self.filtered_result = unittest.TestResult() - self.filter = subunit.TestResultFilter(self.filtered_result, + self.filter = TestResultFilter(self.filtered_result, filter_error=True) self.run_tests() # skips are seen as errors by default python TestResult. - self.assertEqual(['skipped'], - [error[0].id() for error in self.filtered_result.errors]) + self.assertEqual([], self.filtered_result.errors) self.assertEqual(['failed'], [failure[0].id() for failure in self.filtered_result.failures]) - self.assertEqual(2, self.filtered_result.testsRun) + self.assertEqual(3, self.filtered_result.testsRun) def test_exclude_failure(self): self.filtered_result = unittest.TestResult() - self.filter = subunit.TestResultFilter(self.filtered_result, + self.filter = TestResultFilter(self.filtered_result, filter_failure=True) self.run_tests() - self.assertEqual(['error', 'skipped'], + self.assertEqual(['error'], [error[0].id() for error in self.filtered_result.errors]) self.assertEqual([], [failure[0].id() for failure in self.filtered_result.failures]) - self.assertEqual(2, self.filtered_result.testsRun) + self.assertEqual(3, self.filtered_result.testsRun) def test_exclude_skips(self): self.filtered_result = subunit.TestResultStats(None) - self.filter = subunit.TestResultFilter(self.filtered_result, + self.filter = TestResultFilter(self.filtered_result, filter_skip=True) self.run_tests() self.assertEqual(0, self.filtered_result.skipped_tests) self.assertEqual(2, self.filtered_result.failed_tests) - self.assertEqual(2, self.filtered_result.testsRun) + self.assertEqual(3, self.filtered_result.testsRun) def test_include_success(self): """Success's can be included if requested.""" self.filtered_result = unittest.TestResult() - self.filter = subunit.TestResultFilter(self.filtered_result, + self.filter = TestResultFilter(self.filtered_result, filter_success=False) self.run_tests() - self.assertEqual(['error', 'skipped'], + self.assertEqual(['error'], [error[0].id() for error in self.filtered_result.errors]) self.assertEqual(['failed'], [failure[0].id() for failure in @@ -92,14 +92,11 @@ class TestTestResultFilter(unittest.TestCase): """You can filter by predicate callbacks""" self.filtered_result = unittest.TestResult() filter_cb = lambda test, err: str(err).find('error details') != -1 - self.filter = subunit.TestResultFilter(self.filtered_result, + self.filter = TestResultFilter(self.filtered_result, filter_predicate=filter_cb, filter_success=False) self.run_tests() - self.assertEqual(1, - self.filtered_result.testsRun) - # I'd like to test filtering the xfail but it's blocked by - # https://bugs.edge.launchpad.net/subunit/+bug/409193 -- mbp 20090805 + self.assertEqual(1, self.filtered_result.testsRun) def run_tests(self): self.setUpTestStream() @@ -109,8 +106,9 @@ class TestTestResultFilter(unittest.TestCase): def setUpTestStream(self): # While TestResultFilter works on python objects, using a subunit # stream is an easy pithy way of getting a series of test objects to - # call into the TestResult, and as TestResultFilter is intended for use - # with subunit also has the benefit of detecting any interface skew issues. + # call into the TestResult, and as TestResultFilter is intended for + # use with subunit also has the benefit of detecting any interface + # skew issues. self.input_stream = StringIO() self.input_stream.write("""tags: global test passed -- cgit v1.2.1 From 7e1d290cb2345840319b58fc8c6cab422cb5b68e Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 25 Oct 2009 18:05:21 +1100 Subject: Teach filters about details and outcomes. --- python/subunit/test_results.py | 93 +++++++++++++++++------------ python/subunit/tests/test_subunit_filter.py | 4 +- python/subunit/tests/test_test_results.py | 18 ++++++ 3 files changed, 77 insertions(+), 38 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 5401c32..8bfdce5 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -51,23 +51,23 @@ class TestResultDecorator(object): def stopTestRun(self): return self.decorated.stopTestRun() - def addError(self, test, err): - return self.decorated.addError(test, err) + def addError(self, test, err=None, details=None): + return self.decorated.addError(test, err, details=details) - def addFailure(self, test, err): - return self.decorated.addFailure(test, err) + def addFailure(self, test, err=None, details=None): + return self.decorated.addFailure(test, err, details=details) - def addSuccess(self, test): - return self.decorated.addSuccess(test) + def addSuccess(self, test, details=None): + return self.decorated.addSuccess(test, details=details) - def addSkip(self, test, reason): - return self.decorated.addSkip(test, reason) + def addSkip(self, test, reason=None, details=None): + return self.decorated.addSkip(test, reason, details=details) - def addExpectedFailure(self, test, err): - return self.decorated.addExpectedFailure(test, err) + def addExpectedFailure(self, test, err=None, details=None): + return self.decorated.addExpectedFailure(test, err, details=details) - def addUnexpectedSuccess(self, test): - return self.decorated.addUnexpectedSuccess(test) + def addUnexpectedSuccess(self, test, details=None): + return self.decorated.addUnexpectedSuccess(test, details=details) def progress(self, offset, whence): return self.decorated.progress(offset, whence) @@ -112,29 +112,29 @@ class HookedTestResultDecorator(TestResultDecorator): self._before_event() return self.super.stopTestRun() - def addError(self, test, err): + def addError(self, test, err=None, details=None): self._before_event() - return self.super.addError(test, err) + return self.super.addError(test, err, details=details) - def addFailure(self, test, err): + def addFailure(self, test, err=None, details=None): self._before_event() - return self.super.addFailure(test, err) + return self.super.addFailure(test, err, details=details) - def addSuccess(self, test): + def addSuccess(self, test, details=None): self._before_event() - return self.super.addSuccess(test) + return self.super.addSuccess(test, details=details) - def addSkip(self, test, reason): + def addSkip(self, test, reason=None, details=None): self._before_event() - return self.super.addSkip(test, reason) + return self.super.addSkip(test, reason, details=details) - def addExpectedFailure(self, test, err): + def addExpectedFailure(self, test, err=None, details=None): self._before_event() - return self.super.addExpectedFailure(test, err) + return self.super.addExpectedFailure(test, err, details=details) - def addUnexpectedSuccess(self, test): + def addUnexpectedSuccess(self, test, details=None): self._before_event() - return self.super.addUnexpectedSuccess(test) + return self.super.addUnexpectedSuccess(test, details=details) def progress(self, offset, whence): self._before_event() @@ -217,9 +217,11 @@ class TestResultFilter(TestResultDecorator): :param filter_failure: Filter out failures. :param filter_success: Filter out successful tests. :param filter_skip: Filter out skipped tests. - :param filter_predicate: A callable taking (test, err) and - returning True if the result should be passed through. - err is None for success. + :param filter_predicate: A callable taking (test, outcome, err, + details) and returning True if the result should be passed + through. err and details may be none if no error or extra + metadata is available. outcome is the name of the outcome such + as 'success' or 'failure'. """ TestResultDecorator.__init__(self, result) self._filter_error = filter_error @@ -227,7 +229,7 @@ class TestResultFilter(TestResultDecorator): self._filter_success = filter_success self._filter_skip = filter_skip if filter_predicate is None: - filter_predicate = lambda test, err: True + filter_predicate = lambda test, outcome, err, details: True self.filter_predicate = filter_predicate # The current test (for filtering tags) self._current_test = None @@ -236,36 +238,53 @@ class TestResultFilter(TestResultDecorator): # The (new, gone) tags for the current test. self._current_test_tags = None - def addError(self, test, err, details=None): - if not self._filter_error and self.filter_predicate(test, err): + def addError(self, test, err=None, details=None): + if (not self._filter_error and + self.filter_predicate(test, 'error', err, details)): self.decorated.startTest(test) self.decorated.addError(test, err, details=details) + else: + self._filtered() - def addFailure(self, test, err, details=None): - if not self._filter_failure and self.filter_predicate(test, err): + def addFailure(self, test, err=None, details=None): + if (not self._filter_failure and + self.filter_predicate(test, 'failure', err, details)): self.decorated.startTest(test) self.decorated.addFailure(test, err, details=details) + else: + self._filtered() - def addSkip(self, test, reason, details=None): - if not self._filter_skip and self.filter_predicate(test, reason): + def addSkip(self, test, reason=None, details=None): + if (not self._filter_skip and + self.filter_predicate(test, 'skip', reason, details)): self.decorated.startTest(test) self.decorated.addSkip(test, reason, details=details) + else: + self._filtered() def addSuccess(self, test, details=None): - if not self._filter_success and self.filter_predicate(test, None): + if (not self._filter_success and + self.filter_predicate(test, 'success', None, details)): self.decorated.startTest(test) self.decorated.addSuccess(test, details=details) + else: + self._filtered() - def addExpectedFailure(self, test, err, details=None): - if self.filter_predicate(test, err): + def addExpectedFailure(self, test, err=None, details=None): + if self.filter_predicate(test, 'expectedfailure', err, details): self.decorated.startTest(test) return self.decorated.addExpectedFailure(test, err, details=details) + else: + self._filtered() def addUnexpectedSuccess(self, test, details=None): self.decorated.startTest(test) return self.decorated.addUnexpectedSuccess(test, details=details) + def _filtered(self): + self._current_test_filtered = True + def startTest(self, test): """Start a test. diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 9baff24..3c65ed3 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -91,11 +91,13 @@ class TestTestResultFilter(unittest.TestCase): def test_filter_predicate(self): """You can filter by predicate callbacks""" self.filtered_result = unittest.TestResult() - filter_cb = lambda test, err: str(err).find('error details') != -1 + def filter_cb(test, outcome, err, details): + return outcome == 'success' self.filter = TestResultFilter(self.filtered_result, filter_predicate=filter_cb, filter_success=False) self.run_tests() + # Only success should pass self.assertEqual(1, self.filtered_result.testsRun) def run_tests(self): diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index cb7b47e..f0944dd 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -518,21 +518,39 @@ class TestHookedTestResultDecorator(unittest.TestCase): def test_addError(self): self.result.addError(self, subunit.RemoteError()) + def test_addError_details(self): + self.result.addError(self, details={}) + def test_addFailure(self): self.result.addFailure(self, subunit.RemoteError()) + def test_addFailure_details(self): + self.result.addFailure(self, details={}) + def test_addSuccess(self): self.result.addSuccess(self) + def test_addSuccess_details(self): + self.result.addSuccess(self, details={}) + def test_addSkip(self): self.result.addSkip(self, "foo") + def test_addSkip_details(self): + self.result.addSkip(self, details={}) + def test_addExpectedFailure(self): self.result.addExpectedFailure(self, subunit.RemoteError()) + def test_addExpectedFailure_details(self): + self.result.addExpectedFailure(self, details={}) + def test_addUnexpectedSuccess(self): self.result.addUnexpectedSuccess(self) + def test_addUnexpectedSuccess_details(self): + self.result.addUnexpectedSuccess(self, details={}) + def test_progress(self): self.result.progress(1, subunit.PROGRESS_SET) -- cgit v1.2.1 From 0fdc43947a17af18013bb101a101d3b709ffebc1 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Fri, 11 Dec 2009 13:41:42 +1100 Subject: Use testtools facilities for the details API. --- 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 +---------------------------- 10 files changed, 46 insertions(+), 778 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 (limited to 'python') 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): -- cgit v1.2.1 From 1b9926ca49b7060edb5f5e068a9cc30d775f1308 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 13 Dec 2009 12:40:34 +1100 Subject: Use the newly exposed test helpers from testtools trunk. --- python/subunit/__init__.py | 17 +++--- python/subunit/tests/test_test_protocol.py | 88 +++++++++++++++--------------- python/subunit/tests/test_test_results.py | 78 -------------------------- 3 files changed, 53 insertions(+), 130 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index b5f622d..c7e9e66 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -127,7 +127,15 @@ import unittest import iso8601 from testtools import content, content_type, ExtendedToOriginalDecorator -from testtools.testresult import _StringException +try: + from testtools.testresult.real import _StringException + RemoteException = _StringException + _remote_exception_str = '_StringException' # For testing. +except ImportError: + raise ImportError ("testtools.testresult does not contain _StringException, check your version.") + + +from testtools.testresult.real import _StringException import chunked, details, test_results @@ -508,13 +516,6 @@ class TestProtocolServer(object): self._stream.write(line) -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): """A TestResult which generates a subunit stream for a test run. diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 28eb459..99856e2 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -22,15 +22,15 @@ import sys from testtools.content import Content, TracebackContent from testtools.content_type import ContentType +from testtools.tests.helpers import ( + Python26TestResult, + Python27TestResult, + ExtendedTestResult, + ) import subunit from subunit import _remote_exception_str import subunit.iso8601 as iso8601 -from subunit.tests.test_test_results import ( - ExtendedTestResult, - Python26TestResult, - Python27TestResult, - ) class TestTestImports(unittest.TestCase): @@ -86,22 +86,22 @@ class TestTestProtocolServerStartTest(unittest.TestCase): def test_start_test(self): self.protocol.lineReceived("test old mcdonald\n") - self.assertEqual(self.client._calls, + self.assertEqual(self.client._events, [('startTest', subunit.RemotedTestCase("old mcdonald"))]) def test_start_testing(self): self.protocol.lineReceived("testing old mcdonald\n") - self.assertEqual(self.client._calls, + self.assertEqual(self.client._events, [('startTest', subunit.RemotedTestCase("old mcdonald"))]) def test_start_test_colon(self): self.protocol.lineReceived("test: old mcdonald\n") - self.assertEqual(self.client._calls, + self.assertEqual(self.client._events, [('startTest', subunit.RemotedTestCase("old mcdonald"))]) def test_start_testing_colon(self): self.protocol.lineReceived("testing: old mcdonald\n") - self.assertEqual(self.client._calls, + self.assertEqual(self.client._events, [('startTest', subunit.RemotedTestCase("old mcdonald"))]) @@ -135,7 +135,7 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): def test_keywords_before_test(self): self.keywords_before_test() - self.assertEqual(self.client._calls, []) + self.assertEqual(self.client._events, []) def test_keywords_after_error(self): self.protocol.lineReceived("test old mcdonald\n") @@ -145,13 +145,13 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): ('startTest', self.test), ('addError', self.test, {}), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def test_keywords_after_failure(self): self.protocol.lineReceived("test old mcdonald\n") self.protocol.lineReceived("failure old mcdonald\n") self.keywords_before_test() - self.assertEqual(self.client._calls, [ + self.assertEqual(self.client._events, [ ('startTest', self.test), ('addFailure', self.test, {}), ('stopTest', self.test), @@ -165,7 +165,7 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): ('startTest', self.test), ('addSuccess', self.test), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def test_keywords_after_test(self): self.protocol.lineReceived("test old mcdonald\n") @@ -190,7 +190,7 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): "successful a\n" "successful: a\n" "]\n") - self.assertEqual(self.client._calls, [ + self.assertEqual(self.client._events, [ ('startTest', self.test), ('addFailure', self.test, {}), ('stopTest', self.test), @@ -226,7 +226,7 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): "successful a\n" "successful: a\n" "]\n"]) - self.assertEqual(self.client._calls, [ + self.assertEqual(self.client._events, [ ('startTest', self.test), ('addFailure', self.test, details), ('stopTest', self.test), @@ -250,7 +250,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): def test_lost_connection_no_input(self): self.protocol.lostConnection() - self.assertEqual([], self.client._calls) + self.assertEqual([], self.client._events) def test_lost_connection_after_start(self): self.protocol.lineReceived("test old mcdonald\n") @@ -261,7 +261,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): ('startTest', self.test), ('addError', self.test, failure), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def test_lost_connected_after_error(self): self.protocol.lineReceived("test old mcdonald\n") @@ -271,7 +271,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): ('startTest', self.test), ('addError', self.test, subunit.RemoteError("")), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def do_connection_lost(self, outcome, opening): self.protocol.lineReceived("test old mcdonald\n") @@ -284,7 +284,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): ('startTest', self.test), ('addError', self.test, failure), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def test_lost_connection_during_error(self): self.do_connection_lost("error", "[\n") @@ -300,7 +300,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): ('startTest', self.test), ('addFailure', self.test, subunit.RemoteError("")), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def test_lost_connection_during_failure(self): self.do_connection_lost("failure", "[\n") @@ -316,7 +316,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): ('startTest', self.test), ('addSuccess', self.test), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def test_lost_connection_during_success(self): self.do_connection_lost("success", "[\n") @@ -370,7 +370,7 @@ class TestTestProtocolServerAddError(unittest.TestCase): ('startTest', self.test), ('addError', self.test, details), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def test_simple_error(self): self.simple_error_keyword("error") @@ -388,7 +388,7 @@ class TestTestProtocolServerAddError(unittest.TestCase): ('startTest', self.test), ('addError', self.test, details), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def error_quoted_bracket(self, keyword): self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword) @@ -401,7 +401,7 @@ class TestTestProtocolServerAddError(unittest.TestCase): ('startTest', self.test), ('addError', self.test, details), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def test_error_quoted_bracket(self): self.error_quoted_bracket("error") @@ -423,7 +423,7 @@ class TestTestProtocolServerAddFailure(unittest.TestCase): ('startTest', self.test), ('addFailure', self.test, details), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def simple_failure_keyword(self, keyword): self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) @@ -468,7 +468,7 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): """ def capture_expected_failure(self, test, err): - self._calls.append((test, err)) + self._events.append((test, err)) def setup_python26(self): """Setup a test object ready to be xfailed and thunk to success.""" @@ -489,7 +489,7 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): """Setup the protocol based on self.client.""" self.protocol = subunit.TestProtocolServer(self.client) self.protocol.lineReceived("test mcdonalds farm\n") - self.test = self.client._calls[-1][-1] + self.test = self.client._events[-1][-1] def simple_xfail_keyword(self, keyword, as_success): self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) @@ -501,7 +501,7 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): ('startTest', self.test), ('addSuccess', self.test), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) else: details = {} if error_message is not None: @@ -519,7 +519,7 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): ('startTest', self.test), ('addExpectedFailure', self.test, value), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def test_simple_xfail(self): self.setup_python26() @@ -587,7 +587,7 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client) self.protocol.lineReceived("test mcdonalds farm\n") - self.test = self.client._calls[-1][-1] + self.test = self.client._events[-1][-1] def assertSkip(self, reason): details = {} @@ -598,7 +598,7 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): ('startTest', self.test), ('addSkip', self.test, details), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def simple_skip_keyword(self, keyword): self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) @@ -644,7 +644,7 @@ class TestTestProtocolServerAddSuccess(unittest.TestCase): ('startTest', self.test), ('addSuccess', self.test), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def test_simple_success(self): self.simple_success_keyword("failure") @@ -663,7 +663,7 @@ class TestTestProtocolServerAddSuccess(unittest.TestCase): ('startTest', self.test), ('addSuccess', self.test, details), ('stopTest', self.test), - ], self.client._calls) + ], self.client._events) def test_success_empty_message(self): self.protocol.lineReceived("success mcdonalds farm [\n") @@ -722,7 +722,7 @@ class TestTestProtocolServerProgress(unittest.TestCase): ('progress', -2, subunit.PROGRESS_CUR), ('progress', None, subunit.PROGRESS_POP), ('progress', 4, subunit.PROGRESS_CUR), - ], self.result._calls) + ], self.result._events) class TestTestProtocolServerStreamTags(unittest.TestCase): @@ -736,28 +736,28 @@ class TestTestProtocolServerStreamTags(unittest.TestCase): self.protocol.lineReceived("tags: foo bar:baz quux\n") self.assertEqual([ ('tags', set(["foo", "bar:baz", "quux"]), set()), - ], self.client._calls) + ], self.client._events) def test_minus_removes_tags(self): self.protocol.lineReceived("tags: -bar quux\n") self.assertEqual([ ('tags', set(["quux"]), set(["bar"])), - ], self.client._calls) + ], self.client._events) def test_tags_do_not_get_set_on_test(self): self.protocol.lineReceived("test mcdonalds farm\n") - test = self.client._calls[0][-1] + test = self.client._events[0][-1] self.assertEqual(None, getattr(test, 'tags', None)) def test_tags_do_not_get_set_on_global_tags(self): self.protocol.lineReceived("tags: foo bar\n") self.protocol.lineReceived("test mcdonalds farm\n") - test = self.client._calls[-1][-1] + test = self.client._events[-1][-1] self.assertEqual(None, getattr(test, 'tags', None)) def test_tags_get_set_on_test_tags(self): self.protocol.lineReceived("test mcdonalds farm\n") - test = self.client._calls[-1][-1] + test = self.client._events[-1][-1] self.protocol.lineReceived("tags: foo bar\n") self.protocol.lineReceived("success mcdonalds farm\n") self.assertEqual(None, getattr(test, 'tags', None)) @@ -784,7 +784,7 @@ class TestTestProtocolServerStreamTime(unittest.TestCase): self.assertEqual([ ('time', datetime.datetime(2001, 12, 12, 12, 59, 59, 0, iso8601.Utc())) - ], self.result._calls) + ], self.result._events) class TestRemotedTestCase(unittest.TestCase): @@ -872,7 +872,7 @@ class TestExecTestCase(unittest.TestCase): ('startTest', an_error), ('addError', an_error, error_details), ('stopTest', an_error), - ], result._calls) + ], result._events) def test_debug(self): test = self.SampleExecTestCase("test_sample_method") @@ -1021,7 +1021,7 @@ class TestTestProtocolClient(unittest.TestCase): "Content-Type: text/plain\n" "something\n" "F\r\nserialised\nform0\r\n" - "Content-Type: text/x-traceback;language=python\n" + "Content-Type: text/x-traceback;charset=utf8,language=python\n" "traceback\n" "1A\r\n" + _remote_exception_str + ": boo qux\n0\r\n" "]\n") % self.test.id()) @@ -1046,7 +1046,7 @@ class TestTestProtocolClient(unittest.TestCase): "Content-Type: text/plain\n" "something\n" "F\r\nserialised\nform0\r\n" - "Content-Type: text/x-traceback;language=python\n" + "Content-Type: text/x-traceback;charset=utf8,language=python\n" "traceback\n" "1A\r\n" + _remote_exception_str + ": boo qux\n0\r\n" "]\n") % self.test.id()) @@ -1071,7 +1071,7 @@ class TestTestProtocolClient(unittest.TestCase): "Content-Type: text/plain\n" "something\n" "F\r\nserialised\nform0\r\n" - "Content-Type: text/x-traceback;language=python\n" + "Content-Type: text/x-traceback;charset=utf8,language=python\n" "traceback\n" "1A\r\n"+ _remote_exception_str + ": boo qux\n0\r\n" "]\n") % self.test.id()) diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index 0d7d1a9..dac6288 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -68,84 +68,6 @@ class LoggingResult(object): self.shouldStop = False -class Python26TestResult(LoggingResult): - """A python 2.6 like test result, that logs.""" - - def addError(self, test, err): - self._calls.append(('addError', test, err)) - - def addFailure(self, test, err): - self._calls.append(('addFailure', test, err)) - - def addSuccess(self, test): - self._calls.append(('addSuccess', test)) - - def startTest(self, test): - self._calls.append(('startTest', test)) - - def stop(self): - self.shouldStop = True - - def stopTest(self, test): - self._calls.append(('stopTest', test)) - - -class Python27TestResult(Python26TestResult): - """A python 2.7 like test result, that logs.""" - - def addExpectedFailure(self, test, err): - self._calls.append(('addExpectedFailure', test, err)) - - def addSkip(self, test, reason): - self._calls.append(('addSkip', test, reason)) - - def addUnexpectedSuccess(self, test): - self._calls.append(('addUnexpectedSuccess', test)) - - def startTestRun(self): - self._calls.append(('startTestRun',)) - - def stopTestRun(self): - self._calls.append(('stopTestRun',)) - - -class ExtendedTestResult(Python27TestResult): - """A test result like the proposed extended unittest result API.""" - - def addError(self, test, err=None, details=None): - self._calls.append(('addError', test, err or details)) - - def addFailure(self, test, err=None, details=None): - self._calls.append(('addFailure', test, err or details)) - - def addExpectedFailure(self, test, err=None, details=None): - self._calls.append(('addExpectedFailure', test, err or details)) - - def addSkip(self, test, reason=None, details=None): - self._calls.append(('addSkip', test, reason or details)) - - def addSuccess(self, test, details=None): - if details: - self._calls.append(('addSuccess', test, details)) - else: - self._calls.append(('addSuccess', test)) - - def addUnexpectedSuccess(self, test, details=None): - if details: - self._calls.append(('addUnexpectedSuccess', test, details)) - else: - self._calls.append(('addUnexpectedSuccess', test)) - - def progress(self, offset, whence): - self._calls.append(('progress', offset, whence)) - - def tags(self, new_tags, gone_tags): - self._calls.append(('tags', new_tags, gone_tags)) - - def time(self, time): - self._calls.append(('time', time)) - - class TestHookedTestResultDecorator(unittest.TestCase): def setUp(self): -- cgit v1.2.1 From c5479506621ca357874025721edfeff3108abe9f Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 13 Dec 2009 12:59:07 +1100 Subject: Cruft. Go. --- python/subunit/tests/test_test_results.py | 8 -------- 1 file changed, 8 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index dac6288..fe82c04 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -60,14 +60,6 @@ class TimeCapturingResult(unittest.TestResult): self._calls.append(a_datetime) -class LoggingResult(object): - """Basic support for logging of results.""" - - def __init__(self): - self._calls = [] - self.shouldStop = False - - class TestHookedTestResultDecorator(unittest.TestCase): def setUp(self): -- cgit v1.2.1 From ac4ac73498d2cf5f82145f491417f051883ec763 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 13 Dec 2009 13:53:28 +1100 Subject: Remove more references to cleaned up modules. --- python/subunit/__init__.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index c7e9e66..974b2d1 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -112,8 +112,6 @@ Utility modules --------------- * subunit.chunked contains HTTP chunked encoding/decoding logic. -* 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. """ -- cgit v1.2.1 From 090f67a539c1b77bcdfaf6766f413a2f6202d5e8 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Sun, 13 Dec 2009 13:56:52 +1100 Subject: Line wrapping. --- python/subunit/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 974b2d1..5ce8e0e 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -130,7 +130,8 @@ try: RemoteException = _StringException _remote_exception_str = '_StringException' # For testing. except ImportError: - raise ImportError ("testtools.testresult does not contain _StringException, check your version.") + raise ImportError ("testtools.testresult.real does not contain " + "_StringException, check your version.") from testtools.testresult.real import _StringException -- cgit v1.2.1