From 6954432d06307b33e53bbeb11799b2f61148ef31 Mon Sep 17 00:00:00 2001 From: John Arbash Meinel Date: Tue, 24 Aug 2010 17:41:12 -0500 Subject: (bug #623642) Add --no-xfail and -F to subunit-filter. The existing --no-success only filtered out genuine success cases. Now we can filter everything down to stuff that would actually be considered a 'failure'. --- python/subunit/test_results.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 1c91daa..cc588d2 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -208,7 +208,7 @@ class TestResultFilter(TestResultDecorator): """ def __init__(self, result, filter_error=False, filter_failure=False, - filter_success=True, filter_skip=False, + filter_success=True, filter_skip=False, filter_xfail=False, filter_predicate=None): """Create a FilterResult object filtering to result. @@ -216,6 +216,7 @@ 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_xfail: Filter out expected failure tests. :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 @@ -227,6 +228,7 @@ class TestResultFilter(TestResultDecorator): self._filter_failure = filter_failure self._filter_success = filter_success self._filter_skip = filter_skip + self._filter_xfail = filter_xfail if filter_predicate is None: filter_predicate = lambda test, outcome, err, details: True self.filter_predicate = filter_predicate @@ -270,7 +272,8 @@ class TestResultFilter(TestResultDecorator): self._filtered() def addExpectedFailure(self, test, err=None, details=None): - if self.filter_predicate(test, 'expectedfailure', err, details): + if (not self._filter_xfail and + self.filter_predicate(test, 'expectedfailure', err, details)): self.decorated.startTest(test) return self.decorated.addExpectedFailure(test, err, details=details) -- cgit v1.2.1 From 193441ebd6c8786e106a1214711f1350bf3585f2 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Tue, 30 Nov 2010 13:52:18 +1300 Subject: The Subunit Python test runner ``python -m subunit.run`` can now report the test ids and also filter via a test id list file thanks to improvements in ``testtools.run``. See the testtools manual, or testrepository - a major user of such functionality. --- python/subunit/run.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/run.py b/python/subunit/run.py index daa241a..b390de3 100755 --- a/python/subunit/run.py +++ b/python/subunit/run.py @@ -69,4 +69,5 @@ class SubunitTestProgram(TestProgram): if __name__ == '__main__': stream = get_default_formatter() runner = SubunitTestRunner(stream) - SubunitTestProgram(module=None, argv=sys.argv, testRunner=runner) + SubunitTestProgram(module=None, argv=sys.argv, testRunner=runner, + stdout=sys.stdout) -- cgit v1.2.1 From 729967637c1ead8042bca8e1ece3906f6f6ed6e7 Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Mon, 10 Jan 2011 17:15:55 -0600 Subject: Tolerate streams with the \r missing from the chunk length. --- python/subunit/chunked.py | 2 +- python/subunit/tests/test_chunked.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/chunked.py b/python/subunit/chunked.py index 82e4b0d..9c51357 100644 --- a/python/subunit/chunked.py +++ b/python/subunit/chunked.py @@ -87,7 +87,7 @@ class Decoder(object): if count_chars[-1][-1] != '\n': return count_str = ''.join(count_chars) - self.body_length = int(count_str[:-2], 16) + self.body_length = int(count_str.rstrip('\n\r'), 16) excess_bytes = len(count_str) while excess_bytes: if excess_bytes >= len(self.buffered_bytes[0]): diff --git a/python/subunit/tests/test_chunked.py b/python/subunit/tests/test_chunked.py index a24e31e..681af6b 100644 --- a/python/subunit/tests/test_chunked.py +++ b/python/subunit/tests/test_chunked.py @@ -1,6 +1,7 @@ # # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2005 Robert Collins +# Copyright (C) 2011 Martin Pool # # 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 @@ -86,6 +87,13 @@ class TestDecode(unittest.TestCase): self.assertEqual('', self.decoder.write('0\r\n')) self.assertEqual('1' * 65536 + '2' * 65536, self.output.getvalue()) + def test_decode_newline(self): + """Tolerate chunk markers with no cr character.""" + self.assertEqual(None, self.decoder.write('a\n')) + self.assertEqual(None, self.decoder.write('abcdeabcde')) + self.assertEqual(None, self.decoder.write('0\n')) + self.assertEqual('abcdeabcde', self.output.getvalue()) + class TestEncode(unittest.TestCase): -- cgit v1.2.1 From 0de847bb324e3bf3345c355f899c939180ecb406 Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Tue, 11 Jan 2011 22:49:26 -0600 Subject: Default to (more) strict decoding of chunked parts --- python/subunit/chunked.py | 15 ++++++++++++++- python/subunit/tests/test_chunked.py | 20 ++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/subunit/chunked.py b/python/subunit/chunked.py index 9c51357..c15675b 100644 --- a/python/subunit/chunked.py +++ b/python/subunit/chunked.py @@ -1,6 +1,7 @@ # # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2005 Robert Collins +# Copyright (C) 2011 Martin Pool # # 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 @@ -19,7 +20,7 @@ class Decoder(object): """Decode chunked content to a byte stream.""" - def __init__(self, output): + def __init__(self, output, strict=True): """Create a decoder decoding to output. :param output: A file-like object. Bytes written to the Decoder are @@ -29,11 +30,18 @@ class Decoder(object): 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. + + :param strict: If True (the default), the decoder will not knowingly + accept input that is not conformant to the HTTP specification. + (This does not imply that it will catch every nonconformance.) + If False, it will accept incorrect input that is still + unambiguous. """ self.output = output self.buffered_bytes = [] self.state = self._read_length self.body_length = 0 + self.strict = strict def close(self): """Close the decoder. @@ -87,6 +95,11 @@ class Decoder(object): if count_chars[-1][-1] != '\n': return count_str = ''.join(count_chars) + if self.strict: + if count_str[-2:] != '\r\n': + raise ValueError("chunk header invalid: %r" % count_str) + if '\r' in count_str[:-2]: + raise ValueError("too many crs in chunk header %r" % count_str) self.body_length = int(count_str.rstrip('\n\r'), 16) excess_bytes = len(count_str) while excess_bytes: diff --git a/python/subunit/tests/test_chunked.py b/python/subunit/tests/test_chunked.py index 681af6b..4fb99bf 100644 --- a/python/subunit/tests/test_chunked.py +++ b/python/subunit/tests/test_chunked.py @@ -87,13 +87,29 @@ class TestDecode(unittest.TestCase): self.assertEqual('', self.decoder.write('0\r\n')) self.assertEqual('1' * 65536 + '2' * 65536, self.output.getvalue()) - def test_decode_newline(self): + def test_decode_newline_nonstrict(self): """Tolerate chunk markers with no cr character.""" + # From + self.decoder = subunit.chunked.Decoder(self.output, strict=False) self.assertEqual(None, self.decoder.write('a\n')) self.assertEqual(None, self.decoder.write('abcdeabcde')) - self.assertEqual(None, self.decoder.write('0\n')) + self.assertEqual('', self.decoder.write('0\n')) self.assertEqual('abcdeabcde', self.output.getvalue()) + def test_decode_strict_newline_only(self): + """Reject chunk markers with no cr character in strict mode.""" + # From + self.assertRaises(ValueError, + self.decoder.write, 'a\n') + + def test_decode_strict_multiple_crs(self): + self.assertRaises(ValueError, + self.decoder.write, 'a\r\r\n') + + def test_decode_short_header(self): + self.assertRaises(ValueError, + self.decoder.write, '\n') + class TestEncode(unittest.TestCase): -- cgit v1.2.1 From 4720c35d1bfd2af2f27ceddfd039c54553ce2deb Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 28 Jan 2011 17:36:39 +0000 Subject: CR, not cr. Flakes. Whitespace. --- python/subunit/chunked.py | 11 +++++------ python/subunit/tests/test_chunked.py | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'python') diff --git a/python/subunit/chunked.py b/python/subunit/chunked.py index c15675b..5f8c6f1 100644 --- a/python/subunit/chunked.py +++ b/python/subunit/chunked.py @@ -7,7 +7,7 @@ # 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 @@ -34,7 +34,7 @@ class Decoder(object): :param strict: If True (the default), the decoder will not knowingly accept input that is not conformant to the HTTP specification. (This does not imply that it will catch every nonconformance.) - If False, it will accept incorrect input that is still + If False, it will accept incorrect input that is still unambiguous. """ self.output = output @@ -80,7 +80,6 @@ class Decoder(object): 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: @@ -99,7 +98,7 @@ class Decoder(object): if count_str[-2:] != '\r\n': raise ValueError("chunk header invalid: %r" % count_str) if '\r' in count_str[:-2]: - raise ValueError("too many crs in chunk header %r" % count_str) + raise ValueError("too many CRs in chunk header %r" % count_str) self.body_length = int(count_str.rstrip('\n\r'), 16) excess_bytes = len(count_str) while excess_bytes: @@ -120,7 +119,7 @@ class Decoder(object): 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. @@ -146,7 +145,7 @@ class Encoder(object): 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. """ diff --git a/python/subunit/tests/test_chunked.py b/python/subunit/tests/test_chunked.py index 4fb99bf..eee7fe9 100644 --- a/python/subunit/tests/test_chunked.py +++ b/python/subunit/tests/test_chunked.py @@ -88,7 +88,7 @@ class TestDecode(unittest.TestCase): self.assertEqual('1' * 65536 + '2' * 65536, self.output.getvalue()) def test_decode_newline_nonstrict(self): - """Tolerate chunk markers with no cr character.""" + """Tolerate chunk markers with no CR character.""" # From self.decoder = subunit.chunked.Decoder(self.output, strict=False) self.assertEqual(None, self.decoder.write('a\n')) @@ -97,7 +97,7 @@ class TestDecode(unittest.TestCase): self.assertEqual('abcdeabcde', self.output.getvalue()) def test_decode_strict_newline_only(self): - """Reject chunk markers with no cr character in strict mode.""" + """Reject chunk markers with no CR character in strict mode.""" # From self.assertRaises(ValueError, self.decoder.write, 'a\n') -- cgit v1.2.1 From 5ab0d77e721455ba1b1be5d962becbe404769a31 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 10 Feb 2011 17:39:46 +0000 Subject: Flakes --- python/subunit/tests/test_test_protocol.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index e1287b6..ca56c8f 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -6,7 +6,7 @@ # 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 @@ -18,7 +18,6 @@ import datetime import unittest from StringIO import StringIO import os -import sys from testtools.content import Content, TracebackContent from testtools.content_type import ContentType @@ -61,7 +60,6 @@ class TestProtocolServerForward(unittest.TestCase): pipe = StringIO("test old mcdonald\n" "success old mcdonald\n") protocol.readFrom(pipe) - mcdonald = subunit.RemotedTestCase("old mcdonald") self.assertEqual(client.testsRun, 1) self.assertEqual(pipe.getvalue(), out.getvalue()) @@ -74,7 +72,7 @@ class TestProtocolServerForward(unittest.TestCase): protocol.readFrom(pipe) self.assertEqual(client.testsRun, 0) self.assertEqual("", out.getvalue()) - + class TestTestProtocolServerPipe(unittest.TestCase): @@ -90,7 +88,6 @@ class TestTestProtocolServerPipe(unittest.TestCase): "test an error\n" "error an error\n") protocol.readFrom(pipe) - mcdonald = subunit.RemotedTestCase("old mcdonald") bing = subunit.RemotedTestCase("bing crosby") an_error = subunit.RemotedTestCase("an error") self.assertEqual(client.errors, @@ -311,7 +308,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.protocol.lineReceived("%s old mcdonald %s" % (outcome, opening)) self.protocol.lostConnection() failure = subunit.RemoteError( - u"lost connection during %s report of test 'old mcdonald'" % + u"lost connection during %s report of test 'old mcdonald'" % outcome) self.assertEqual([ ('startTest', self.test), @@ -686,10 +683,10 @@ class TestTestProtocolServerAddSuccess(unittest.TestCase): def test_simple_success_colon(self): self.simple_success_keyword("failure:") - def test_simple_success(self): + def test_simple_failure(self): self.simple_success_keyword("successful") - def test_simple_success_colon(self): + def test_simple_failure_colon(self): self.simple_success_keyword("successful:") def assertSuccess(self, details): @@ -946,7 +943,7 @@ class TestIsolatedTestCase(unittest.TestCase): def test_construct(self): - test = self.SampleIsolatedTestCase("test_sets_global_state") + self.SampleIsolatedTestCase("test_sets_global_state") def test_run(self): result = unittest.TestResult() @@ -982,7 +979,7 @@ class TestIsolatedTestSuite(unittest.TestCase): def test_construct(self): - suite = subunit.IsolatedTestSuite() + subunit.IsolatedTestSuite() def test_run(self): result = unittest.TestResult() @@ -1117,7 +1114,7 @@ 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( -- cgit v1.2.1 From 5358e383f7bdea693ec5f0e6182d8a45df9e8c3b Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 10 Feb 2011 17:42:15 +0000 Subject: More flakes to fix up --- python/subunit/tests/test_test_protocol.py | 8 +------- python/subunit/tests/test_test_results.py | 20 +++++++------------- 2 files changed, 8 insertions(+), 20 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index ca56c8f..f7bab5c 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -678,15 +678,9 @@ class TestTestProtocolServerAddSuccess(unittest.TestCase): ], self.client._events) def test_simple_success(self): - self.simple_success_keyword("failure") - - def test_simple_success_colon(self): - self.simple_success_keyword("failure:") - - def test_simple_failure(self): self.simple_success_keyword("successful") - def test_simple_failure_colon(self): + def test_simple_success_colon(self): self.simple_success_keyword("successful:") def assertSuccess(self, details): diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index fe82c04..a2dad96 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -6,7 +6,7 @@ # 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 @@ -16,12 +16,6 @@ import datetime import unittest -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 @@ -82,22 +76,22 @@ class TestHookedTestResultDecorator(unittest.TestCase): def test_startTest(self): self.result.startTest(self) - + def test_startTestRun(self): self.result.startTestRun() - + def test_stopTest(self): self.result.stopTest(self) - + def test_stopTestRun(self): self.result.stopTestRun() 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()) @@ -142,7 +136,7 @@ class TestHookedTestResultDecorator(unittest.TestCase): def test_time(self): self.result.time(None) - + class TestAutoTimingTestResultDecorator(unittest.TestCase): -- cgit v1.2.1 From d9936a42c5204813c56ac9dd361e4ce6545cbea0 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 10 Feb 2011 17:44:07 +0000 Subject: More flakes. --- python/subunit/tests/test_subunit_filter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 3c65ed3..91f6f1f 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -6,7 +6,7 @@ # 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 @@ -76,7 +76,7 @@ class TestTestResultFilter(unittest.TestCase): self.assertEqual(3, self.filtered_result.testsRun) def test_include_success(self): - """Success's can be included if requested.""" + """Successes can be included if requested.""" self.filtered_result = unittest.TestResult() self.filter = TestResultFilter(self.filtered_result, filter_success=False) @@ -128,7 +128,7 @@ test todo xfail todo """) self.input_stream.seek(0) - + def test_suite(): loader = subunit.tests.TestUtil.TestLoader() -- cgit v1.2.1 From 273427a2f62b46c6e9e89ba5837a96684929c63c Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 10 Feb 2011 17:46:01 +0000 Subject: Return, rather than setting an attribute --- python/subunit/tests/test_subunit_filter.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 91f6f1f..34283a2 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -101,18 +101,18 @@ class TestTestResultFilter(unittest.TestCase): self.assertEqual(1, self.filtered_result.testsRun) def run_tests(self): - self.setUpTestStream() - self.test = subunit.ProtocolTestCase(self.input_stream) + input_stream = self.getTestStream() + self.test = subunit.ProtocolTestCase(input_stream) self.test.run(self.filter) - def setUpTestStream(self): + def getTestStream(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. - self.input_stream = StringIO() - self.input_stream.write("""tags: global + input_stream = StringIO() + input_stream.write("""tags: global test passed success passed test failed @@ -127,7 +127,8 @@ skip skipped test todo xfail todo """) - self.input_stream.seek(0) + input_stream.seek(0) + return input_stream def test_suite(): -- cgit v1.2.1 From 607649aa4499c299a9da9daa6c12b0d4f63c60ff Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 10 Feb 2011 17:57:52 +0000 Subject: Pass the filter in. --- python/subunit/tests/test_subunit_filter.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 34283a2..52e9f91 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -32,8 +32,8 @@ 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 = TestResultFilter(self.filtered_result) - self.run_tests() + result_filter = TestResultFilter(self.filtered_result) + self.run_tests(result_filter) # skips are seen as success by default python TestResult. self.assertEqual(['error'], [error[0].id() for error in self.filtered_result.errors]) @@ -44,9 +44,9 @@ class TestTestResultFilter(unittest.TestCase): def test_exclude_errors(self): self.filtered_result = unittest.TestResult() - self.filter = TestResultFilter(self.filtered_result, + result_filter = TestResultFilter(self.filtered_result, filter_error=True) - self.run_tests() + self.run_tests(result_filter) # skips are seen as errors by default python TestResult. self.assertEqual([], self.filtered_result.errors) self.assertEqual(['failed'], @@ -56,9 +56,9 @@ class TestTestResultFilter(unittest.TestCase): def test_exclude_failure(self): self.filtered_result = unittest.TestResult() - self.filter = TestResultFilter(self.filtered_result, + result_filter = TestResultFilter(self.filtered_result, filter_failure=True) - self.run_tests() + self.run_tests(result_filter) self.assertEqual(['error'], [error[0].id() for error in self.filtered_result.errors]) self.assertEqual([], @@ -68,9 +68,9 @@ class TestTestResultFilter(unittest.TestCase): def test_exclude_skips(self): self.filtered_result = subunit.TestResultStats(None) - self.filter = TestResultFilter(self.filtered_result, + result_filter = TestResultFilter(self.filtered_result, filter_skip=True) - self.run_tests() + self.run_tests(result_filter) self.assertEqual(0, self.filtered_result.skipped_tests) self.assertEqual(2, self.filtered_result.failed_tests) self.assertEqual(3, self.filtered_result.testsRun) @@ -78,9 +78,9 @@ class TestTestResultFilter(unittest.TestCase): def test_include_success(self): """Successes can be included if requested.""" self.filtered_result = unittest.TestResult() - self.filter = TestResultFilter(self.filtered_result, + result_filter = TestResultFilter(self.filtered_result, filter_success=False) - self.run_tests() + self.run_tests(result_filter) self.assertEqual(['error'], [error[0].id() for error in self.filtered_result.errors]) self.assertEqual(['failed'], @@ -93,17 +93,17 @@ class TestTestResultFilter(unittest.TestCase): self.filtered_result = unittest.TestResult() def filter_cb(test, outcome, err, details): return outcome == 'success' - self.filter = TestResultFilter(self.filtered_result, + result_filter = TestResultFilter(self.filtered_result, filter_predicate=filter_cb, filter_success=False) - self.run_tests() + self.run_tests(result_filter) # Only success should pass self.assertEqual(1, self.filtered_result.testsRun) - def run_tests(self): + def run_tests(self, result_filter): input_stream = self.getTestStream() self.test = subunit.ProtocolTestCase(input_stream) - self.test.run(self.filter) + self.test.run(result_filter) def getTestStream(self): # While TestResultFilter works on python objects, using a subunit -- cgit v1.2.1 From 273f9cb733fe18c7dd315d1c1d9c846514b6e5fa Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 10 Feb 2011 17:58:09 +0000 Subject: Delete unused method. --- python/subunit/tests/test_subunit_filter.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 52e9f91..448f06c 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -26,9 +26,6 @@ from subunit.test_results import TestResultFilter class TestTestResultFilter(unittest.TestCase): """Test for TestResultFilter, a TestResult object which filters tests.""" - def _setUp(self): - self.output = StringIO() - def test_default(self): """The default is to exclude success and include everything else.""" self.filtered_result = unittest.TestResult() -- cgit v1.2.1 From fce3670662a65c9cb3232c2a172e95aadc651bbb Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 10 Feb 2011 17:58:49 +0000 Subject: No need to store on the object either. --- python/subunit/tests/test_subunit_filter.py | 56 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 448f06c..348f4bd 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -28,74 +28,74 @@ class TestTestResultFilter(unittest.TestCase): def test_default(self): """The default is to exclude success and include everything else.""" - self.filtered_result = unittest.TestResult() - result_filter = TestResultFilter(self.filtered_result) + filtered_result = unittest.TestResult() + result_filter = TestResultFilter(filtered_result) self.run_tests(result_filter) # skips are seen as success by default python TestResult. self.assertEqual(['error'], - [error[0].id() for error in self.filtered_result.errors]) + [error[0].id() for error in filtered_result.errors]) self.assertEqual(['failed'], [failure[0].id() for failure in - self.filtered_result.failures]) - self.assertEqual(4, self.filtered_result.testsRun) + filtered_result.failures]) + self.assertEqual(4, filtered_result.testsRun) def test_exclude_errors(self): - self.filtered_result = unittest.TestResult() - result_filter = TestResultFilter(self.filtered_result, + filtered_result = unittest.TestResult() + result_filter = TestResultFilter(filtered_result, filter_error=True) self.run_tests(result_filter) # skips are seen as errors by default python TestResult. - self.assertEqual([], self.filtered_result.errors) + self.assertEqual([], filtered_result.errors) self.assertEqual(['failed'], [failure[0].id() for failure in - self.filtered_result.failures]) - self.assertEqual(3, self.filtered_result.testsRun) + filtered_result.failures]) + self.assertEqual(3, filtered_result.testsRun) def test_exclude_failure(self): - self.filtered_result = unittest.TestResult() - result_filter = TestResultFilter(self.filtered_result, + filtered_result = unittest.TestResult() + result_filter = TestResultFilter(filtered_result, filter_failure=True) self.run_tests(result_filter) self.assertEqual(['error'], - [error[0].id() for error in self.filtered_result.errors]) + [error[0].id() for error in filtered_result.errors]) self.assertEqual([], [failure[0].id() for failure in - self.filtered_result.failures]) - self.assertEqual(3, self.filtered_result.testsRun) + filtered_result.failures]) + self.assertEqual(3, filtered_result.testsRun) def test_exclude_skips(self): - self.filtered_result = subunit.TestResultStats(None) - result_filter = TestResultFilter(self.filtered_result, + filtered_result = subunit.TestResultStats(None) + result_filter = TestResultFilter(filtered_result, filter_skip=True) self.run_tests(result_filter) - self.assertEqual(0, self.filtered_result.skipped_tests) - self.assertEqual(2, self.filtered_result.failed_tests) - self.assertEqual(3, self.filtered_result.testsRun) + self.assertEqual(0, filtered_result.skipped_tests) + self.assertEqual(2, filtered_result.failed_tests) + self.assertEqual(3, filtered_result.testsRun) def test_include_success(self): """Successes can be included if requested.""" - self.filtered_result = unittest.TestResult() - result_filter = TestResultFilter(self.filtered_result, + filtered_result = unittest.TestResult() + result_filter = TestResultFilter(filtered_result, filter_success=False) self.run_tests(result_filter) self.assertEqual(['error'], - [error[0].id() for error in self.filtered_result.errors]) + [error[0].id() for error in filtered_result.errors]) self.assertEqual(['failed'], [failure[0].id() for failure in - self.filtered_result.failures]) - self.assertEqual(5, self.filtered_result.testsRun) + filtered_result.failures]) + self.assertEqual(5, filtered_result.testsRun) def test_filter_predicate(self): """You can filter by predicate callbacks""" - self.filtered_result = unittest.TestResult() + filtered_result = unittest.TestResult() def filter_cb(test, outcome, err, details): return outcome == 'success' - result_filter = TestResultFilter(self.filtered_result, + result_filter = TestResultFilter(filtered_result, filter_predicate=filter_cb, filter_success=False) self.run_tests(result_filter) # Only success should pass - self.assertEqual(1, self.filtered_result.testsRun) + self.assertEqual(1, filtered_result.testsRun) def run_tests(self, result_filter): input_stream = self.getTestStream() -- cgit v1.2.1 From cfed1cc123546138685e177b2832b922576fb83c Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 10 Feb 2011 17:59:10 +0000 Subject: No need for that either --- python/subunit/tests/test_subunit_filter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 348f4bd..9c9e165 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -99,8 +99,8 @@ class TestTestResultFilter(unittest.TestCase): def run_tests(self, result_filter): input_stream = self.getTestStream() - self.test = subunit.ProtocolTestCase(input_stream) - self.test.run(result_filter) + test = subunit.ProtocolTestCase(input_stream) + test.run(result_filter) def getTestStream(self): # While TestResultFilter works on python objects, using a subunit -- cgit v1.2.1 From 14c50a345f9867424d69531822b0e8dd420742d2 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 11 Feb 2011 16:19:01 +0000 Subject: Formatting. --- python/subunit/tests/test_subunit_filter.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 9c9e165..ac46f15 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -41,8 +41,7 @@ class TestTestResultFilter(unittest.TestCase): def test_exclude_errors(self): filtered_result = unittest.TestResult() - result_filter = TestResultFilter(filtered_result, - filter_error=True) + result_filter = TestResultFilter(filtered_result, filter_error=True) self.run_tests(result_filter) # skips are seen as errors by default python TestResult. self.assertEqual([], filtered_result.errors) @@ -53,8 +52,7 @@ class TestTestResultFilter(unittest.TestCase): def test_exclude_failure(self): filtered_result = unittest.TestResult() - result_filter = TestResultFilter(filtered_result, - filter_failure=True) + result_filter = TestResultFilter(filtered_result, filter_failure=True) self.run_tests(result_filter) self.assertEqual(['error'], [error[0].id() for error in filtered_result.errors]) @@ -65,8 +63,7 @@ class TestTestResultFilter(unittest.TestCase): def test_exclude_skips(self): filtered_result = subunit.TestResultStats(None) - result_filter = TestResultFilter(filtered_result, - filter_skip=True) + result_filter = TestResultFilter(filtered_result, filter_skip=True) self.run_tests(result_filter) self.assertEqual(0, filtered_result.skipped_tests) self.assertEqual(2, filtered_result.failed_tests) -- cgit v1.2.1 From 8313ffeb9ad36af77d29724e29b1801d80f8df7c Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 11 Feb 2011 16:22:21 +0000 Subject: Try to make the test set up a little more data driven. --- python/subunit/tests/test_subunit_filter.py | 52 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index ac46f15..2f26f39 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -23,9 +23,34 @@ import subunit from subunit.test_results import TestResultFilter +def make_stream(bytes): + """Take a string and return a stream from which that string can be read.""" + stream = StringIO() + stream.write(bytes) + stream.seek(0) + return stream + + class TestTestResultFilter(unittest.TestCase): """Test for TestResultFilter, a TestResult object which filters tests.""" + example_subunit_stream = """\ +tags: global +test passed +success passed +test failed +tags: local +failure failed +test error +error error [ +error details +] +test skipped +skip skipped +test todo +xfail todo +""" + def test_default(self): """The default is to exclude success and include everything else.""" filtered_result = unittest.TestResult() @@ -95,35 +120,10 @@ class TestTestResultFilter(unittest.TestCase): self.assertEqual(1, filtered_result.testsRun) def run_tests(self, result_filter): - input_stream = self.getTestStream() + input_stream = make_stream(self.example_subunit_stream) test = subunit.ProtocolTestCase(input_stream) test.run(result_filter) - def getTestStream(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. - input_stream = StringIO() - input_stream.write("""tags: global -test passed -success passed -test failed -tags: local -failure failed -test error -error error [ -error details -] -test skipped -skip skipped -test todo -xfail todo -""") - input_stream.seek(0) - return input_stream - def test_suite(): loader = subunit.tests.TestUtil.TestLoader() -- cgit v1.2.1 From b32d51316660c0923eb132f0e3aaadd5b269bea9 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 11 Feb 2011 16:23:19 +0000 Subject: Parametrize the subunit input. --- python/subunit/tests/test_subunit_filter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 2f26f39..69e32a0 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -119,9 +119,10 @@ xfail todo # Only success should pass self.assertEqual(1, filtered_result.testsRun) - def run_tests(self, result_filter): - input_stream = make_stream(self.example_subunit_stream) - test = subunit.ProtocolTestCase(input_stream) + def run_tests(self, result_filter, input_stream=None): + if input_stream is None: + input_stream = self.example_subunit_stream + test = subunit.ProtocolTestCase(make_stream(input_stream)) test.run(result_filter) -- cgit v1.2.1 From a079bfb4c2a37a7d12b0f2cff51f079bf81c889f Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 11 Feb 2011 16:25:36 +0000 Subject: Clarity --- python/subunit/tests/test_subunit_filter.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 69e32a0..89f5212 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -34,6 +34,10 @@ def make_stream(bytes): class TestTestResultFilter(unittest.TestCase): """Test for TestResultFilter, a TestResult object which filters tests.""" + # 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. example_subunit_stream = """\ tags: global test passed @@ -51,6 +55,18 @@ test todo xfail todo """ + def run_tests(self, result_filter, input_stream=None): + """Run tests through the given filter. + + :param result_filter: A filtering TestResult object. + :param input_stream: Bytes of subunit stream data. If not provided, + uses TestTestResultFilter.example_subunit_stream. + """ + if input_stream is None: + input_stream = self.example_subunit_stream + test = subunit.ProtocolTestCase(make_stream(input_stream)) + test.run(result_filter) + def test_default(self): """The default is to exclude success and include everything else.""" filtered_result = unittest.TestResult() @@ -119,12 +135,6 @@ xfail todo # Only success should pass self.assertEqual(1, filtered_result.testsRun) - def run_tests(self, result_filter, input_stream=None): - if input_stream is None: - input_stream = self.example_subunit_stream - test = subunit.ProtocolTestCase(make_stream(input_stream)) - test.run(result_filter) - def test_suite(): loader = subunit.tests.TestUtil.TestLoader() -- cgit v1.2.1 From 6d6297fef98e8e86cdea243922cc2e79d5c4641f Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 11 Feb 2011 16:45:49 +0000 Subject: Failing test that demonstrates the bug. --- python/subunit/tests/test_subunit_filter.py | 30 ++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 89f5212..786ef46 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -16,9 +16,14 @@ """Tests for subunit.TestResultFilter.""" +from datetime import datetime +from subunit import iso8601 import unittest from StringIO import StringIO +from testtools import TestCase +from testtools.testresult.doubles import ExtendedTestResult + import subunit from subunit.test_results import TestResultFilter @@ -31,7 +36,7 @@ def make_stream(bytes): return stream -class TestTestResultFilter(unittest.TestCase): +class TestTestResultFilter(TestCase): """Test for TestResultFilter, a TestResult object which filters tests.""" # While TestResultFilter works on python objects, using a subunit stream @@ -135,6 +140,29 @@ xfail todo # Only success should pass self.assertEqual(1, filtered_result.testsRun) + def test_time_ordering_preserved(self): + # Passing a subunit stream through TestResultFilter preserves the + # relative ordering of 'time' directives and any other subunit + # directives that are still included. + dates = [ + datetime(year=2000, month=1, day=i, tzinfo=iso8601.Utc()) + for i in range(1, 4)] + subunit_stream = '\n'.join([ + "time: %s", + "test: foo", + "time: %s", + "error: foo", + "time: %s"]) % tuple(dates) + result = ExtendedTestResult() + result_filter = TestResultFilter(result) + self.run_tests(result_filter, subunit_stream) + self.assertEquals( + [('time', dates[0]), + ('startTest', 'foo'), + ('time', dates[1]), + ('addError', 'foo'), + ('time', dates[2])], result._events) + def test_suite(): loader = subunit.tests.TestUtil.TestLoader() -- cgit v1.2.1 From dd089db28901fa5c05dc5ead9a6db1ca9ef5e80b Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 11 Feb 2011 17:43:01 +0000 Subject: Fix the immediate bug. --- python/subunit/test_results.py | 35 ++++++++++++++++++----------- python/subunit/tests/test_subunit_filter.py | 18 +++++++-------- 2 files changed, 31 insertions(+), 22 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 1c91daa..4079cea 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -237,49 +237,51 @@ class TestResultFilter(TestResultDecorator): # The (new, gone) tags for the current test. self._current_test_tags = None + # Calls to this result that we don't know whether to forward on yet. + self._buffered_calls = [] + 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) + self._buffered_calls.append( + ('addError', [test, err], {'details': details})) else: self._filtered() 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) + self._buffered_calls.append( + ('addFailure', [test, err], {'details': details})) else: self._filtered() 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) + self._buffered_calls.append( + ('addSkip', [reason], {'details': details})) else: self._filtered() def addSuccess(self, test, details=None): if (not self._filter_success and self.filter_predicate(test, 'success', None, details)): - self.decorated.startTest(test) - self.decorated.addSuccess(test, details=details) + self._buffered_calls.append( + ('addSuccess', [test], {'details': details})) else: self._filtered() 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) + self._buffered_calls.append( + ('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) + self._buffered_calls.append( + ('addUnexpectedSuccess', [test], {'details': details})) def _filtered(self): self._current_test_filtered = True @@ -293,6 +295,7 @@ class TestResultFilter(TestResultDecorator): self._current_test = test self._current_test_filtered = False self._current_test_tags = set(), set() + self._buffered_calls.append(('startTest', [test], {})) def stopTest(self, test): """Stop a test. @@ -302,12 +305,15 @@ class TestResultFilter(TestResultDecorator): """ if not self._current_test_filtered: # Tags to output for this test. + for method, args, kwargs in self._buffered_calls: + getattr(self.decorated, method)(*args, **kwargs) 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 + self._buffered_calls = [] def tags(self, new_tags, gone_tags): """Handle tag instructions. @@ -326,6 +332,9 @@ class TestResultFilter(TestResultDecorator): self._current_test_tags[1].difference_update(new_tags) return self.decorated.tags(new_tags, gone_tags) + def time(self, a_time): + self._buffered_calls.append(('time', [a_time], {})) + def id_to_orig_id(self, id): if id.startswith("subunit.RemotedTestCase."): return id[len("subunit.RemotedTestCase."):] diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 786ef46..0548966 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -144,24 +144,24 @@ xfail todo # Passing a subunit stream through TestResultFilter preserves the # relative ordering of 'time' directives and any other subunit # directives that are still included. - dates = [ - datetime(year=2000, month=1, day=i, tzinfo=iso8601.Utc()) - for i in range(1, 4)] + date_a = datetime(year=2000, month=1, day=1, tzinfo=iso8601.UTC) + date_b = datetime(year=2000, month=1, day=2, tzinfo=iso8601.UTC) subunit_stream = '\n'.join([ "time: %s", "test: foo", "time: %s", "error: foo", - "time: %s"]) % tuple(dates) + ""]) % (date_a, date_b) result = ExtendedTestResult() result_filter = TestResultFilter(result) self.run_tests(result_filter, subunit_stream) + foo = subunit.RemotedTestCase('foo') self.assertEquals( - [('time', dates[0]), - ('startTest', 'foo'), - ('time', dates[1]), - ('addError', 'foo'), - ('time', dates[2])], result._events) + [('time', date_a), + ('startTest', foo), + ('time', date_b), + ('addError', foo, {}), + ('stopTest', foo)], result._events) def test_suite(): -- cgit v1.2.1 From 77c9c58133de6bee368c524918c0fa8978d254da Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 11 Feb 2011 17:45:41 +0000 Subject: Pass through time when we aren't in tests. --- python/subunit/test_results.py | 5 ++++- python/subunit/tests/test_subunit_filter.py | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 4079cea..f2f5d82 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -333,7 +333,10 @@ class TestResultFilter(TestResultDecorator): return self.decorated.tags(new_tags, gone_tags) def time(self, a_time): - self._buffered_calls.append(('time', [a_time], {})) + if self._current_test is not None: + self._buffered_calls.append(('time', [a_time], {})) + else: + return self.decorated.time(a_time) def id_to_orig_id(self, id): if id.startswith("subunit.RemotedTestCase."): diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 0548966..a728860 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -146,12 +146,14 @@ xfail todo # directives that are still included. date_a = datetime(year=2000, month=1, day=1, tzinfo=iso8601.UTC) date_b = datetime(year=2000, month=1, day=2, tzinfo=iso8601.UTC) + date_c = datetime(year=2000, month=1, day=3, tzinfo=iso8601.UTC) subunit_stream = '\n'.join([ "time: %s", "test: foo", "time: %s", "error: foo", - ""]) % (date_a, date_b) + "time: %s", + ""]) % (date_a, date_b, date_c) result = ExtendedTestResult() result_filter = TestResultFilter(result) self.run_tests(result_filter, subunit_stream) @@ -161,7 +163,8 @@ xfail todo ('startTest', foo), ('time', date_b), ('addError', foo, {}), - ('stopTest', foo)], result._events) + ('stopTest', foo), + ('time', date_c)], result._events) def test_suite(): -- cgit v1.2.1 From 6a8c8d0f5c13598484d659d80f995588a919a1f4 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 11 Feb 2011 17:46:54 +0000 Subject: Unnecessary whitespace. --- python/subunit/test_results.py | 1 - 1 file changed, 1 deletion(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index f2f5d82..a6ad0c6 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -236,7 +236,6 @@ class TestResultFilter(TestResultDecorator): self._current_test_filtered = None # The (new, gone) tags for the current test. self._current_test_tags = None - # Calls to this result that we don't know whether to forward on yet. self._buffered_calls = [] -- cgit v1.2.1 From 971b2fa35673d8508f0f1324efe8028458b4ee94 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 11 Feb 2011 17:53:17 +0000 Subject: More flakes. --- python/subunit/__init__.py | 48 ++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 25 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index b2c7a29..9dc849a 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -6,7 +6,7 @@ # 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 @@ -49,7 +49,7 @@ details, tags, timestamping and progress markers). 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 +When used the value of details should be a dict from ``string`` to ``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 @@ -58,7 +58,7 @@ and newer). 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 -test is in progress (that is, if called outside of the ``startTest``, +test is in progress (that is, if called outside of the ``startTest``, ``stopTest`` pair), the the tags apply to all sebsequent tests. If called when a test is in progress, then the tags only apply to that test. @@ -87,7 +87,7 @@ tests, allowing isolation between the test runner and some tests. Similarly, ``IsolatedTestCase`` is a base class which can be subclassed to get tests that will fork() before that individual test is run. -`ExecTestCase`` is a convenience wrapper for running an external +`ExecTestCase`` is a convenience wrapper for running an external program to get a Subunit stream and then report that back to an arbitrary result object:: @@ -98,7 +98,7 @@ result object:: def test_script_two(self): './bin/script_two' - + # Normally your normal test loading would take of this automatically, # It is only spelt out in detail here for clarity. suite = unittest.TestSuite([AggregateTests("test_script_one"), @@ -116,7 +116,6 @@ Utility modules * subunit.test_results contains TestResult helper classes. """ -import datetime import os import re from StringIO import StringIO @@ -254,7 +253,7 @@ class _InTest(_ParserState): 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 details_state: The state to switch to for details processing of this outcome. @@ -382,7 +381,7 @@ class _ReadingFailureDetails(_ReadingDetails): def _outcome_label(self): return "failure" - + class _ReadingErrorDetails(_ReadingDetails): """State for the subunit parser when reading error details.""" @@ -430,7 +429,7 @@ class _ReadingSuccessDetails(_ReadingDetails): class TestProtocolServer(object): """A parser for subunit. - + :ivar tags: The current tags associated with the protocol stream. """ @@ -442,7 +441,7 @@ class TestProtocolServer(object): subunit protocol should be written to. This allows custom handling of mixed protocols. By default, sys.stdout will be used for convenience. - :param forward_stream: A stream to forward subunit lines to. This + :param forward_stream: A stream to forward subunit lines to. This allows a filter to forward the entire stream while still parsing and acting on it. By default forward_stream is set to DiscardStream() and no forwarding happens. @@ -510,7 +509,7 @@ class TestProtocolServer(object): def readFrom(self, pipe): """Blocking convenience API to parse an entire stream. - + :param pipe: A file-like object supporting readlines(). :return: None. """ @@ -531,7 +530,7 @@ class TestProtocolServer(object): class TestProtocolClient(testresult.TestResult): """A TestResult which generates a subunit stream for a test run. - + # Get a TestSuite or TestCase to run suite = make_suite() # Create a stream (any object with a 'write' method) @@ -554,7 +553,7 @@ class TestProtocolClient(testresult.TestResult): 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) @@ -569,7 +568,7 @@ class TestProtocolClient(testresult.TestResult): 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) @@ -584,7 +583,7 @@ class TestProtocolClient(testresult.TestResult): 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) @@ -599,7 +598,7 @@ class TestProtocolClient(testresult.TestResult): 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) @@ -717,7 +716,7 @@ def RemoteError(description=u""): class RemotedTestCase(unittest.TestCase): """A class to represent test cases run in child processes. - + Instances of this class are used to provide the Python test API a TestCase that can be printed to the screen, introspected for metadata and so on. However, as they are a simply a memoisation of a test that was actually @@ -802,7 +801,7 @@ class ExecTestCase(unittest.TestCase): class IsolatedTestCase(unittest.TestCase): """A TestCase which executes in a forked process. - + Each test gets its own process, which has a performance overhead but will provide excellent isolation from global state (such as django configs, zope utilities and so on). @@ -815,7 +814,7 @@ class IsolatedTestCase(unittest.TestCase): class IsolatedTestSuite(unittest.TestSuite): """A TestSuite which runs its tests in a forked process. - + This decorator that will fork() before running the tests and report the results from the child process using a Subunit stream. This is useful for handling tests that mutate global state, or are testing C extensions that @@ -867,7 +866,7 @@ def run_isolated(klass, self, result): def TAP2SubUnit(tap, subunit): """Filter a TAP pipe into a subunit pipe. - + :param tap: A tap pipe/stream/file object. :param subunit: A pipe/stream/file object to write subunit results to. :return: The exit code to exit with. @@ -875,7 +874,6 @@ def TAP2SubUnit(tap, subunit): BEFORE_PLAN = 0 AFTER_PLAN = 1 SKIP_STREAM = 2 - client = TestProtocolClient(subunit) state = BEFORE_PLAN plan_start = 1 plan_stop = 0 @@ -1025,11 +1023,11 @@ class ProtocolTestCase(object): that has been encoded into the stream. The ``unittest.TestCase`` ``debug`` and ``countTestCases`` methods are not supported because there isn't a sensible mapping for those methods. - + # Get a stream (any object with a readline() method), in this case the # stream output by the example from ``subunit.TestProtocolClient``. stream = file('tests.log', 'rb') - # Create a parser which will read from the stream and emit + # Create a parser which will read from the stream and emit # activity to a unittest.TestResult when run() is called. suite = subunit.ProtocolTestCase(stream) # Create a result object to accept the contents of that stream. @@ -1073,7 +1071,7 @@ class ProtocolTestCase(object): class TestResultStats(testresult.TestResult): """A pyunit TestResult interface implementation for making statistics. - + :ivar total_tests: The total tests seen. :ivar passed_tests: The tests that passed. :ivar failed_tests: The tests that failed. @@ -1124,7 +1122,7 @@ class TestResultStats(testresult.TestResult): def get_default_formatter(): """Obtain the default formatter to write to. - + :return: A file-like object. """ formatter = os.getenv("SUBUNIT_FORMATTER") -- cgit v1.2.1 From c592e36438ec3931c1a8f0e324e382f005eaa31f Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sat, 12 Feb 2011 11:10:13 +0000 Subject: Delete unnecessary code. --- python/subunit/tests/test_subunit_filter.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index a728860..cf6c2b6 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -28,14 +28,6 @@ import subunit from subunit.test_results import TestResultFilter -def make_stream(bytes): - """Take a string and return a stream from which that string can be read.""" - stream = StringIO() - stream.write(bytes) - stream.seek(0) - return stream - - class TestTestResultFilter(TestCase): """Test for TestResultFilter, a TestResult object which filters tests.""" @@ -69,7 +61,7 @@ xfail todo """ if input_stream is None: input_stream = self.example_subunit_stream - test = subunit.ProtocolTestCase(make_stream(input_stream)) + test = subunit.ProtocolTestCase(StringIO(input_stream)) test.run(result_filter) def test_default(self): -- cgit v1.2.1 From 255d00653139619a8d1eb6a0efaefbbc842e5bbb Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sat, 12 Feb 2011 12:10:06 +0000 Subject: Get started, fix a bug in subunit's decorator. --- python/subunit/test_results.py | 6 +++++- python/subunit/tests/test_test_results.py | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index a6ad0c6..d248846 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -82,7 +82,7 @@ class TestResultDecorator(object): return self.decorated.stop() def tags(self, new_tags, gone_tags): - return self.decorated.time(new_tags, gone_tags) + return self.decorated.tags(new_tags, gone_tags) def time(self, a_datetime): return self.decorated.time(a_datetime) @@ -195,6 +195,10 @@ class AutoTimingTestResultDecorator(HookedTestResultDecorator): return self.decorated.time(a_datetime) +class TagCollapsingDecorator(TestResultDecorator): + """Collapses many 'tags' calls into one where possible.""" + + class TestResultFilter(TestResultDecorator): """A pyunit TestResult interface implementation which filters tests. diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index a2dad96..0bc7e24 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -17,6 +17,9 @@ import datetime import unittest +from testtools import TestCase +from testtools.testresult.doubles import ExtendedTestResult + import subunit import subunit.iso8601 as iso8601 import subunit.test_results @@ -187,6 +190,16 @@ class TestAutoTimingTestResultDecorator(unittest.TestCase): self.assertNotEqual(None, self.decorated._calls[2]) +class TestTagCollapsingDecorator(TestCase): + + def test_tags_forwarded_outside_of_tests(self): + result = ExtendedTestResult() + tag_collapser = subunit.test_results.TagCollapsingDecorator(result) + tag_collapser.tags(set(['a', 'b']), set()) + self.assertEquals( + [('tags', set(['a', 'b']), set([]))], result._events) + + def test_suite(): loader = subunit.tests.TestUtil.TestLoader() result = loader.loadTestsFromName(__name__) -- cgit v1.2.1 From 0f644eda8f3af105622ef30c7c6739c6b26fe7e8 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sat, 12 Feb 2011 12:16:36 +0000 Subject: Implement the tag collapsing logic by stealing stuff from TRF and fixing it. --- python/subunit/test_results.py | 48 +++++++++++++++++++++++++++++++ python/subunit/tests/test_test_results.py | 15 ++++++++++ 2 files changed, 63 insertions(+) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index d248846..662585b 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -198,6 +198,54 @@ class AutoTimingTestResultDecorator(HookedTestResultDecorator): class TagCollapsingDecorator(TestResultDecorator): """Collapses many 'tags' calls into one where possible.""" + def __init__(self, result): + TestResultDecorator.__init__(self, result) + # The current test (for filtering tags) + self._current_test = None + # The (new, gone) tags for the current test. + self._current_test_tags = None + + def startTest(self, test): + """Start a test. + + Not directly passed to the client, but used for handling of tags + correctly. + """ + self.decorated.startTest(test) + self._current_test = test + 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. + """ + # 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_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) + else: + return self.decorated.tags(new_tags, gone_tags) + class TestResultFilter(TestResultDecorator): """A pyunit TestResult interface implementation which filters tests. diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index 0bc7e24..7aed8a4 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -199,6 +199,21 @@ class TestTagCollapsingDecorator(TestCase): self.assertEquals( [('tags', set(['a', 'b']), set([]))], result._events) + def test_tags_collapsed_inside_of_tests(self): + result = ExtendedTestResult() + tag_collapser = subunit.test_results.TagCollapsingDecorator(result) + test = subunit.RemotedTestCase('foo') + tag_collapser.startTest(test) + tag_collapser.tags(set(['a']), set()) + tag_collapser.tags(set(['b']), set(['a'])) + tag_collapser.tags(set(['c']), set()) + tag_collapser.stopTest(test) + self.assertEquals( + [('startTest', test), + ('tags', set(['b', 'c']), set(['a'])), + ('stopTest', test)], + result._events) + def test_suite(): loader = subunit.tests.TestUtil.TestLoader() -- cgit v1.2.1 From 83942a36ce48d3761b4ade166ccb0bf286578d2e Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sat, 12 Feb 2011 12:21:07 +0000 Subject: Simplify TRF by using TagCollapsingDecorator. --- python/subunit/test_results.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 662585b..6a89200 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -275,6 +275,7 @@ class TestResultFilter(TestResultDecorator): as 'success' or 'failure'. """ TestResultDecorator.__init__(self, result) + self.decorated = TagCollapsingDecorator(self.decorated) self._filter_error = filter_error self._filter_failure = filter_failure self._filter_success = filter_success @@ -286,8 +287,6 @@ class TestResultFilter(TestResultDecorator): 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 # Calls to this result that we don't know whether to forward on yet. self._buffered_calls = [] @@ -345,7 +344,6 @@ class TestResultFilter(TestResultDecorator): """ self._current_test = test self._current_test_filtered = False - self._current_test_tags = set(), set() self._buffered_calls.append(('startTest', [test], {})) def stopTest(self, test): @@ -358,31 +356,11 @@ class TestResultFilter(TestResultDecorator): # Tags to output for this test. for method, args, kwargs in self._buffered_calls: getattr(self.decorated, method)(*args, **kwargs) - 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 self._buffered_calls = [] - 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 time(self, a_time): if self._current_test is not None: self._buffered_calls.append(('time', [a_time], {})) -- cgit v1.2.1 From cd10a295cc588e4ca93e741b094b6c2997153f37 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sat, 12 Feb 2011 12:36:50 +0000 Subject: Only one mechanism for checking to see if a thing is filtered. --- python/subunit/test_results.py | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 6a89200..12849f5 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -247,6 +247,14 @@ class TagCollapsingDecorator(TestResultDecorator): return self.decorated.tags(new_tags, gone_tags) +def all_true(bools): + """Return True if all of 'bools' are True. False otherwise.""" + for b in bools: + if not b: + return False + return True + + class TestResultFilter(TestResultDecorator): """A pyunit TestResult interface implementation which filters tests. @@ -276,13 +284,20 @@ class TestResultFilter(TestResultDecorator): """ TestResultDecorator.__init__(self, result) self.decorated = TagCollapsingDecorator(self.decorated) - 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, outcome, err, details: True - self.filter_predicate = filter_predicate + predicates = [] + if filter_error: + predicates.append(lambda t, outcome, e, d: outcome != 'error') + if filter_failure: + predicates.append(lambda t, outcome, e, d: outcome != 'failure') + if filter_success: + predicates.append(lambda t, outcome, e, d: outcome != 'success') + if filter_skip: + predicates.append(lambda t, outcome, e, d: outcome != 'skip') + if filter_predicate is not None: + predicates.append(filter_predicate) + self.filter_predicate = ( + lambda test, outcome, err, details: + all_true(p(test, outcome, err, details) for p in predicates)) # The current test (for filtering tags) self._current_test = None # Has the current test been filtered (for outputting test tags) @@ -291,32 +306,28 @@ class TestResultFilter(TestResultDecorator): self._buffered_calls = [] def addError(self, test, err=None, details=None): - if (not self._filter_error and - self.filter_predicate(test, 'error', err, details)): + if (self.filter_predicate(test, 'error', err, details)): self._buffered_calls.append( ('addError', [test, err], {'details': details})) else: self._filtered() def addFailure(self, test, err=None, details=None): - if (not self._filter_failure and - self.filter_predicate(test, 'failure', err, details)): + if (self.filter_predicate(test, 'failure', err, details)): self._buffered_calls.append( ('addFailure', [test, err], {'details': details})) else: self._filtered() def addSkip(self, test, reason=None, details=None): - if (not self._filter_skip and - self.filter_predicate(test, 'skip', reason, details)): + if (self.filter_predicate(test, 'skip', reason, details)): self._buffered_calls.append( ('addSkip', [reason], {'details': details})) else: self._filtered() def addSuccess(self, test, details=None): - if (not self._filter_success and - self.filter_predicate(test, 'success', None, details)): + if (self.filter_predicate(test, 'success', None, details)): self._buffered_calls.append( ('addSuccess', [test], {'details': details})) else: @@ -362,6 +373,7 @@ class TestResultFilter(TestResultDecorator): self._buffered_calls = [] def time(self, a_time): + # XXX: Make a TimeCollapsingDecorator. if self._current_test is not None: self._buffered_calls.append(('time', [a_time], {})) else: -- cgit v1.2.1 From 7fd32930b2d9bff0c4f8f823a313c03beedce451 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sun, 13 Feb 2011 14:39:43 +0000 Subject: Add a time collapsing decorator. --- python/subunit/test_results.py | 19 +++++++++++++++++++ python/subunit/tests/test_test_results.py | 25 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 12849f5..b3228c6 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -247,6 +247,25 @@ class TagCollapsingDecorator(TestResultDecorator): return self.decorated.tags(new_tags, gone_tags) +class TimeCollapsingDecorator(HookedTestResultDecorator): + """Only pass on the first and last of a consecutive sequence of times.""" + + def __init__(self, decorated): + HookedTestResultDecorator.__init__(self, decorated) + self._last_time = None + + def _before_event(self): + self.decorated.time(self._last_time) + self._last_time = None + + def time(self, a_time): + # Don't upcall, because we don't want to call _before_event, it's only + # for non-time events. + if self._last_time is None: + self.decorated.time(a_time) + self._last_time = a_time + + def all_true(bools): """Return True if all of 'bools' are True. False otherwise.""" for b in bools: diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index 7aed8a4..8535d56 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -215,6 +215,31 @@ class TestTagCollapsingDecorator(TestCase): result._events) +class TestTimeCollapsingDecorator(TestCase): + + def make_time(self): + # Heh heh. + return datetime.datetime( + 2000, 1, self.getUniqueInteger(), tzinfo=iso8601.UTC) + + def test_initial_time_forwarded(self): + result = ExtendedTestResult() + tag_collapser = subunit.test_results.TimeCollapsingDecorator(result) + a_time = self.make_time() + tag_collapser.time(a_time) + self.assertEquals([('time', a_time)], result._events) + + def test_time_collapsed(self): + result = ExtendedTestResult() + tag_collapser = subunit.test_results.TimeCollapsingDecorator(result) + times = [self.make_time() for i in range(5)] + for a_time in times: + tag_collapser.time(a_time) + tag_collapser.startTest(subunit.RemotedTestCase('foo')) + self.assertEquals( + [('time', times[0]), ('time', times[-1])], result._events[:-1]) + + def test_suite(): loader = subunit.tests.TestUtil.TestLoader() result = loader.loadTestsFromName(__name__) -- cgit v1.2.1 From d86c97154604bd029774d8095b394de26af4867c Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sun, 13 Feb 2011 14:48:21 +0000 Subject: Properly handle multiple events. --- python/subunit/test_results.py | 14 +++++++++----- python/subunit/tests/test_test_results.py | 26 +++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 6 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index b3228c6..cf051ba 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -252,18 +252,22 @@ class TimeCollapsingDecorator(HookedTestResultDecorator): def __init__(self, decorated): HookedTestResultDecorator.__init__(self, decorated) - self._last_time = None + self._last_received_time = None + self._last_sent_time = None def _before_event(self): - self.decorated.time(self._last_time) - self._last_time = None + if self._last_received_time != self._last_sent_time: + self.decorated.time(self._last_received_time) + self._last_sent_time = self._last_received_time + self._last_received_time = None def time(self, a_time): # Don't upcall, because we don't want to call _before_event, it's only # for non-time events. - if self._last_time is None: + if self._last_received_time is None: self.decorated.time(a_time) - self._last_time = a_time + self._last_sent_time = a_time + self._last_received_time = a_time def all_true(bools): diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index 8535d56..8633b44 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -223,13 +223,16 @@ class TestTimeCollapsingDecorator(TestCase): 2000, 1, self.getUniqueInteger(), tzinfo=iso8601.UTC) def test_initial_time_forwarded(self): + # We always forward the first time event we see. result = ExtendedTestResult() tag_collapser = subunit.test_results.TimeCollapsingDecorator(result) a_time = self.make_time() tag_collapser.time(a_time) self.assertEquals([('time', a_time)], result._events) - def test_time_collapsed(self): + def test_time_collapsed_to_first_and_last(self): + # If there are many consecutive time events, only the first and last + # are sent through. result = ExtendedTestResult() tag_collapser = subunit.test_results.TimeCollapsingDecorator(result) times = [self.make_time() for i in range(5)] @@ -239,6 +242,27 @@ class TestTimeCollapsingDecorator(TestCase): self.assertEquals( [('time', times[0]), ('time', times[-1])], result._events[:-1]) + def test_only_one_time_sent(self): + # If we receive a single time event followed by a non-time event, we + # send exactly one time event. + result = ExtendedTestResult() + tag_collapser = subunit.test_results.TimeCollapsingDecorator(result) + a_time = self.make_time() + tag_collapser.time(a_time) + tag_collapser.startTest(subunit.RemotedTestCase('foo')) + self.assertEquals([('time', a_time)], result._events[:-1]) + + def test_duplicate_times_not_sent(self): + # Many time events with the exact same time are collapsed into one + # time event. + result = ExtendedTestResult() + tag_collapser = subunit.test_results.TimeCollapsingDecorator(result) + a_time = self.make_time() + for i in range(5): + tag_collapser.time(a_time) + tag_collapser.startTest(subunit.RemotedTestCase('foo')) + self.assertEquals([('time', a_time)], result._events[:-1]) + def test_suite(): loader = subunit.tests.TestUtil.TestLoader() -- cgit v1.2.1 From e0581e6819db7c2872ced4d43e803ae7b5e7bd05 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sun, 13 Feb 2011 14:52:42 +0000 Subject: Make sure that we don't send time if there are none to send. --- python/subunit/test_results.py | 2 ++ python/subunit/tests/test_test_results.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index cf051ba..a3f640d 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -256,6 +256,8 @@ class TimeCollapsingDecorator(HookedTestResultDecorator): self._last_sent_time = None def _before_event(self): + if self._last_received_time is None: + return if self._last_received_time != self._last_sent_time: self.decorated.time(self._last_received_time) self._last_sent_time = self._last_received_time diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index 8633b44..bed2bd8 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -263,6 +263,21 @@ class TestTimeCollapsingDecorator(TestCase): tag_collapser.startTest(subunit.RemotedTestCase('foo')) self.assertEquals([('time', a_time)], result._events[:-1]) + def test_no_times_inserted(self): + result = ExtendedTestResult() + tag_collapser = subunit.test_results.TimeCollapsingDecorator(result) + a_time = self.make_time() + tag_collapser.time(a_time) + foo = subunit.RemotedTestCase('foo') + tag_collapser.startTest(foo) + tag_collapser.addSuccess(foo) + tag_collapser.stopTest(foo) + self.assertEquals( + [('time', a_time), + ('startTest', foo), + ('addSuccess', foo), + ('stopTest', foo)], result._events) + def test_suite(): loader = subunit.tests.TestUtil.TestLoader() -- cgit v1.2.1 From 7e0f91fee35ac314e52043f01502857871f75cd8 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sun, 13 Feb 2011 14:53:05 +0000 Subject: Use the time collapsing decorator as well. --- python/subunit/test_results.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index a3f640d..e1e59df 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -308,7 +308,8 @@ class TestResultFilter(TestResultDecorator): as 'success' or 'failure'. """ TestResultDecorator.__init__(self, result) - self.decorated = TagCollapsingDecorator(self.decorated) + self.decorated = TimeCollapsingDecorator( + TagCollapsingDecorator(self.decorated)) predicates = [] if filter_error: predicates.append(lambda t, outcome, e, d: outcome != 'error') @@ -398,7 +399,6 @@ class TestResultFilter(TestResultDecorator): self._buffered_calls = [] def time(self, a_time): - # XXX: Make a TimeCollapsingDecorator. if self._current_test is not None: self._buffered_calls.append(('time', [a_time], {})) else: -- cgit v1.2.1 From 2d2aa86675f7e320ad146a6f42bca8f86f51682c Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sun, 13 Feb 2011 14:54:48 +0000 Subject: Consistently use super() rather than upcalling. --- python/subunit/test_results.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index e1e59df..e7f9171 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -199,7 +199,7 @@ class TagCollapsingDecorator(TestResultDecorator): """Collapses many 'tags' calls into one where possible.""" def __init__(self, result): - TestResultDecorator.__init__(self, result) + super(TagCollapsingDecorator, self).__init__(result) # The current test (for filtering tags) self._current_test = None # The (new, gone) tags for the current test. @@ -251,7 +251,7 @@ class TimeCollapsingDecorator(HookedTestResultDecorator): """Only pass on the first and last of a consecutive sequence of times.""" def __init__(self, decorated): - HookedTestResultDecorator.__init__(self, decorated) + super(TimeCollapsingDecorator, self).__init__(decorated) self._last_received_time = None self._last_sent_time = None @@ -307,7 +307,7 @@ class TestResultFilter(TestResultDecorator): metadata is available. outcome is the name of the outcome such as 'success' or 'failure'. """ - TestResultDecorator.__init__(self, result) + super(TestResultFilter, self).__init__(result) self.decorated = TimeCollapsingDecorator( TagCollapsingDecorator(self.decorated)) predicates = [] @@ -414,7 +414,7 @@ class TestIdPrintingResult(testtools.TestResult): def __init__(self, stream, show_times=False): """Create a FilterResult object outputting to stream.""" - testtools.TestResult.__init__(self) + super(TestIdPrintingResult, self).__init__() self._stream = stream self.failed_tests = 0 self.__time = 0 -- cgit v1.2.1 From 6301e2918b5f2c880b5084f63201b2ee7de4be93 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Tue, 15 Feb 2011 10:52:45 +0000 Subject: Add different support. --- python/subunit/tests/test_test_results.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'python') diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index bed2bd8..94d2274 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -214,6 +214,21 @@ class TestTagCollapsingDecorator(TestCase): ('stopTest', test)], result._events) + def test_tags_collapsed_inside_of_tests_different_ordering(self): + result = ExtendedTestResult() + tag_collapser = subunit.test_results.TagCollapsingDecorator(result) + test = subunit.RemotedTestCase('foo') + tag_collapser.startTest(test) + tag_collapser.tags(set(), set(['a'])) + tag_collapser.tags(set(['a', 'b']), set()) + tag_collapser.tags(set(['c']), set()) + tag_collapser.stopTest(test) + self.assertEquals( + [('startTest', test), + ('tags', set(['a', 'b', 'c']), set()), + ('stopTest', test)], + result._events) + class TestTimeCollapsingDecorator(TestCase): -- cgit v1.2.1 From 93d8ed3ffed812c37cf54b4ccc22a25026f940ae Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 10 Mar 2011 17:37:19 -0500 Subject: Work around Python3 syntax errors. Notions borrowed from the 'six' portability library. --- python/subunit/__init__.py | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 9dc849a..aadd228 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -118,12 +118,11 @@ Utility modules import os import re -from StringIO import StringIO import subprocess import sys import unittest -import iso8601 +import subunit.iso8601 from testtools import content, content_type, ExtendedToOriginalDecorator try: from testtools.testresult.real import _StringException @@ -142,6 +141,22 @@ PROGRESS_CUR = 1 PROGRESS_PUSH = 2 PROGRESS_POP = 3 +if sys.version_info >= (3, 0): + def b(s): + return s.encode("latin-1") + def u(s): + return s + import io + StringIO = io.StringIO + BytesIO = io.BytesIO +else: + def b(s): + return s + def u(s): + return unicode(s) + import StringIO + StringIO = BytesIO = StringIO.StringIO + def test_suite(): import subunit.tests @@ -241,7 +256,7 @@ class _ParserState(object): def lostConnection(self): """Connection lost.""" - self.parser._lostConnectionInTest(u'unknown state of ') + self.parser._lostConnectionInTest(u('unknown state of ')) def startTest(self, offset, line): """A test start command received.""" @@ -321,7 +336,7 @@ class _InTest(_ParserState): def lostConnection(self): """Connection lost.""" - self.parser._lostConnectionInTest(u'') + self.parser._lostConnectionInTest(u('')) class _OutSideTest(_ParserState): @@ -356,8 +371,8 @@ class _ReadingDetails(_ParserState): def lostConnection(self): """Connection lost.""" - self.parser._lostConnectionInTest(u'%s report of ' % - self._outcome_label()) + self.parser._lostConnectionInTest(u('%s report of ' % + self._outcome_label())) def _outcome_label(self): """The label to describe this outcome.""" @@ -488,9 +503,10 @@ class TestProtocolServer(object): def _handleTime(self, offset, line): # Accept it, but do not do anything with it yet. try: - event_time = iso8601.parse_date(line[offset:-1]) - except TypeError, e: - raise TypeError("Failed to parse %r, got %r" % (line, e)) + event_time = subunit.iso8601.parse_date(line[offset:-1]) + except TypeError: + raise TypeError("Failed to parse %r, got %r" + % (line, sys.exec_info[1])) self.client.time(event_time) def lineReceived(self, line): @@ -498,8 +514,8 @@ class TestProtocolServer(object): self._state.lineReceived(line) def _lostConnectionInTest(self, state_string): - error_string = u"lost connection during %stest '%s'" % ( - state_string, self.current_test_description) + error_string = u("lost connection during %stest '%s'" % ( + state_string, self.current_test_description)) self.client.addError(self._current_test, RemoteError(error_string)) self.client.stopTest(self._current_test) @@ -680,7 +696,7 @@ class TestProtocolClient(testresult.TestResult): ":param datetime: A datetime.datetime object. """ - time = a_datetime.astimezone(iso8601.Utc()) + time = a_datetime.astimezone(subunit.iso8601.Utc()) self._stream.write("time: %04d-%02d-%02d %02d:%02d:%02d.%06dZ\n" % ( time.year, time.month, time.day, time.hour, time.minute, time.second, time.microsecond)) @@ -710,7 +726,7 @@ class TestProtocolClient(testresult.TestResult): """Obey the testtools result.done() interface.""" -def RemoteError(description=u""): +def RemoteError(description=u("")): return (_StringException, _StringException(description), None) @@ -760,7 +776,7 @@ class RemotedTestCase(unittest.TestCase): def run(self, result=None): if result is None: result = self.defaultTestResult() result.startTest(self) - result.addError(self, RemoteError(u"Cannot run RemotedTestCases.\n")) + result.addError(self, RemoteError(u("Cannot run RemotedTestCases.\n"))) result.stopTest(self) def _strclass(self): -- cgit v1.2.1 From 931ee641258b77bd440d76a0d5c1f3305010f7c4 Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Mon, 28 Mar 2011 15:02:55 +0200 Subject: Add test for TagCollapsingDecorator.testsRun. --- python/subunit/tests/test_test_results.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'python') diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index 94d2274..23b8b5e 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -229,6 +229,11 @@ class TestTagCollapsingDecorator(TestCase): ('stopTest', test)], result._events) + def test_tags_collapsed_tests_run(self): + result = ExtendedTestResult() + tag_collapser = subunit.test_results.TagCollapsingDecorator(result) + tag_collapser.testsRun += 1 + class TestTimeCollapsingDecorator(TestCase): -- cgit v1.2.1 From 67a43dcef5f1511398ef32052d1220a0ef35ffcf Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Mon, 11 Apr 2011 15:32:47 +0200 Subject: Support --fixup-expected-failures argument to subunit-filter. --- python/subunit/test_results.py | 16 +++++++++++++--- python/subunit/tests/test_subunit_filter.py | 12 ++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index e7f9171..b3308ea 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -294,7 +294,7 @@ class TestResultFilter(TestResultDecorator): def __init__(self, result, filter_error=False, filter_failure=False, filter_success=True, filter_skip=False, - filter_predicate=None): + filter_predicate=None, fixup_expected_failures=None): """Create a FilterResult object filtering to result. :param filter_error: Filter out errors. @@ -306,6 +306,8 @@ class TestResultFilter(TestResultDecorator): 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'. + :param fixup_expected_failures: Set of test ids to consider known + failing. """ super(TestResultFilter, self).__init__(result) self.decorated = TimeCollapsingDecorator( @@ -330,6 +332,10 @@ class TestResultFilter(TestResultDecorator): self._current_test_filtered = None # Calls to this result that we don't know whether to forward on yet. self._buffered_calls = [] + if fixup_expected_failures is None: + self._fixup_expected_failures = frozenset() + else: + self._fixup_expected_failures = fixup_expected_failures def addError(self, test, err=None, details=None): if (self.filter_predicate(test, 'error', err, details)): @@ -340,8 +346,12 @@ class TestResultFilter(TestResultDecorator): def addFailure(self, test, err=None, details=None): if (self.filter_predicate(test, 'failure', err, details)): - self._buffered_calls.append( - ('addFailure', [test, err], {'details': details})) + if test.id() in self._fixup_expected_failures: + self._buffered_calls.append( + ('addExpectedFailure', [test, err], {'details': details})) + else: + self._buffered_calls.append( + ('addFailure', [test, err], {'details': details})) else: self._filtered() diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index cf6c2b6..855aa1e 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -88,6 +88,18 @@ xfail todo filtered_result.failures]) self.assertEqual(3, filtered_result.testsRun) + def test_fixup_expected_failures(self): + filtered_result = unittest.TestResult() + result_filter = TestResultFilter(filtered_result, + fixup_expected_failures=set(["failed"])) + self.run_tests(result_filter) + self.assertEqual(['failed', 'todo'], + [error[0].id() for error in filtered_result.expectedFailures]) + self.assertEqual([], + [failure[0].id() for failure in + filtered_result.failures]) + self.assertEqual(4, filtered_result.testsRun) + def test_exclude_failure(self): filtered_result = unittest.TestResult() result_filter = TestResultFilter(filtered_result, filter_failure=True) -- cgit v1.2.1 From 990cf93fb02250ccbd42153fc1f6b87b867d977e Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Mon, 11 Apr 2011 15:56:05 +0200 Subject: Filter errors as well. --- python/subunit/test_results.py | 8 ++++++-- python/subunit/tests/test_subunit_filter.py | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index b3308ea..4e642ea 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -339,8 +339,12 @@ class TestResultFilter(TestResultDecorator): def addError(self, test, err=None, details=None): if (self.filter_predicate(test, 'error', err, details)): - self._buffered_calls.append( - ('addError', [test, err], {'details': details})) + if test.id() in self._fixup_expected_failures: + self._buffered_calls.append( + ('addExpectedFailure', [test, err], {'details': details})) + else: + self._buffered_calls.append( + ('addError', [test, err], {'details': details})) else: self._filtered() diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 855aa1e..386c5a2 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -94,10 +94,18 @@ xfail todo fixup_expected_failures=set(["failed"])) self.run_tests(result_filter) self.assertEqual(['failed', 'todo'], - [error[0].id() for error in filtered_result.expectedFailures]) - self.assertEqual([], - [failure[0].id() for failure in - filtered_result.failures]) + [failure[0].id() for failure in filtered_result.expectedFailures]) + self.assertEqual([], filtered_result.failures) + self.assertEqual(4, filtered_result.testsRun) + + def test_fixup_expected_errors(self): + filtered_result = unittest.TestResult() + result_filter = TestResultFilter(filtered_result, + fixup_expected_failures=set(["error"])) + self.run_tests(result_filter) + self.assertEqual(['error', 'todo'], + [failure[0].id() for failure in filtered_result.expectedFailures]) + self.assertEqual([], filtered_result.errors) self.assertEqual(4, filtered_result.testsRun) def test_exclude_failure(self): -- cgit v1.2.1 From d3a4695b7a8678304001e6cf8dc68b64d2a7cde7 Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Mon, 11 Apr 2011 16:38:56 +0200 Subject: Mark unexpected successes, too. --- python/subunit/test_results.py | 8 ++++++-- python/subunit/tests/test_subunit_filter.py | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 4e642ea..a3ad928 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -368,8 +368,12 @@ class TestResultFilter(TestResultDecorator): def addSuccess(self, test, details=None): if (self.filter_predicate(test, 'success', None, details)): - self._buffered_calls.append( - ('addSuccess', [test], {'details': details})) + if test.id() in self._fixup_expected_failures: + self._buffered_calls.append( + ('addUnexpectedSuccess', [test], {'details': details})) + else: + self._buffered_calls.append( + ('addSuccess', [test], {'details': details})) else: self._filtered() diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 386c5a2..f8db05b 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -108,6 +108,15 @@ xfail todo self.assertEqual([], filtered_result.errors) self.assertEqual(4, filtered_result.testsRun) + def test_fixup_unexpected_success(self): + filtered_result = unittest.TestResult() + result_filter = TestResultFilter(filtered_result, filter_success=False, + fixup_expected_failures=set(["passed"])) + self.run_tests(result_filter) + self.assertEqual(['passed'], + [passed.id() for passed in filtered_result.unexpectedSuccesses]) + self.assertEqual(5, filtered_result.testsRun) + def test_exclude_failure(self): filtered_result = unittest.TestResult() result_filter = TestResultFilter(filtered_result, filter_failure=True) -- cgit v1.2.1 From 049a3c5342faac9aa46afc9a882ab37950331fb5 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 25 Apr 2011 09:29:56 +1200 Subject: Get pydoc3.1 subunit working. --- python/subunit/details.py | 2 +- python/subunit/test_results.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/subunit/details.py b/python/subunit/details.py index aedba9d..36c9974 100644 --- a/python/subunit/details.py +++ b/python/subunit/details.py @@ -19,7 +19,7 @@ from testtools import content, content_type from testtools.compat import StringIO -import chunked +from subunit import chunked class DetailsParser(object): diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index e7f9171..63ef0b9 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -18,9 +18,10 @@ import datetime -import iso8601 import testtools +from subunit import iso8601 + # NOT a TestResult, because we are implementing the interface, not inheriting # it. -- cgit v1.2.1 From aaa87b0d6f110bd52fe659c0fca873d820ad31f2 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 25 Apr 2011 09:40:52 +1200 Subject: Make test suite importable in py3. --- python/subunit/tests/TestUtil.py | 2 +- python/subunit/tests/test_chunked.py | 3 ++- python/subunit/tests/test_details.py | 3 ++- python/subunit/tests/test_subunit_filter.py | 2 +- python/subunit/tests/test_subunit_stats.py | 3 ++- python/subunit/tests/test_subunit_tags.py | 3 ++- python/subunit/tests/test_tap2subunit.py | 4 +++- python/subunit/tests/test_test_protocol.py | 30 ++++++++++++++--------------- 8 files changed, 28 insertions(+), 22 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/TestUtil.py b/python/subunit/tests/TestUtil.py index 1b5ba9c..39d901e 100644 --- a/python/subunit/tests/TestUtil.py +++ b/python/subunit/tests/TestUtil.py @@ -53,7 +53,7 @@ def visitTests(suite, visitor): visitor.visitSuite(test) visitTests(test, visitor) else: - print "unvisitable non-unittest.TestCase element %r (%r)" % (test, test.__class__) + print ("unvisitable non-unittest.TestCase element %r (%r)" % (test, test.__class__)) class TestSuite(unittest.TestSuite): diff --git a/python/subunit/tests/test_chunked.py b/python/subunit/tests/test_chunked.py index eee7fe9..6323b02 100644 --- a/python/subunit/tests/test_chunked.py +++ b/python/subunit/tests/test_chunked.py @@ -15,9 +15,10 @@ # limitations under that license. # -from cStringIO import StringIO import unittest +from testtools.compat import StringIO + import subunit.chunked diff --git a/python/subunit/tests/test_details.py b/python/subunit/tests/test_details.py index 41c3212..06eb430 100644 --- a/python/subunit/tests/test_details.py +++ b/python/subunit/tests/test_details.py @@ -14,9 +14,10 @@ # limitations under that license. # -from cStringIO import StringIO import unittest +from testtools.compat import StringIO + import subunit.tests from subunit import content, content_type, details diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index cf6c2b6..682f726 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -19,9 +19,9 @@ from datetime import datetime from subunit import iso8601 import unittest -from StringIO import StringIO from testtools import TestCase +from testtools.compat import StringIO from testtools.testresult.doubles import ExtendedTestResult import subunit diff --git a/python/subunit/tests/test_subunit_stats.py b/python/subunit/tests/test_subunit_stats.py index a7f8fca..778bb7f 100644 --- a/python/subunit/tests/test_subunit_stats.py +++ b/python/subunit/tests/test_subunit_stats.py @@ -17,7 +17,8 @@ """Tests for subunit.TestResultStats.""" import unittest -from StringIO import StringIO + +from testtools.compat import StringIO import subunit diff --git a/python/subunit/tests/test_subunit_tags.py b/python/subunit/tests/test_subunit_tags.py index 227e2b7..c98506a 100644 --- a/python/subunit/tests/test_subunit_tags.py +++ b/python/subunit/tests/test_subunit_tags.py @@ -17,7 +17,8 @@ """Tests for subunit.tag_stream.""" import unittest -from StringIO import StringIO + +from testtools.compat import StringIO import subunit import subunit.test_results diff --git a/python/subunit/tests/test_tap2subunit.py b/python/subunit/tests/test_tap2subunit.py index c4ca4cd..11bc191 100644 --- a/python/subunit/tests/test_tap2subunit.py +++ b/python/subunit/tests/test_tap2subunit.py @@ -17,7 +17,9 @@ """Tests for TAP2SubUnit.""" import unittest -from StringIO import StringIO + +from testtools.compat import StringIO + import subunit diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index f7bab5c..8171696 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -16,9 +16,9 @@ import datetime import unittest -from StringIO import StringIO import os +from testtools.compat import _u, StringIO from testtools.content import Content, TracebackContent from testtools.content_type import ContentType from testtools.tests.helpers import ( @@ -286,7 +286,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.protocol.lineReceived("test old mcdonald\n") self.protocol.lostConnection() failure = subunit.RemoteError( - u"lost connection during test 'old mcdonald'") + _u("lost connection during test 'old mcdonald'")) self.assertEqual([ ('startTest', self.test), ('addError', self.test, failure), @@ -299,7 +299,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.protocol.lostConnection() self.assertEqual([ ('startTest', self.test), - ('addError', self.test, subunit.RemoteError(u"")), + ('addError', self.test, subunit.RemoteError(_u(""))), ('stopTest', self.test), ], self.client._events) @@ -308,7 +308,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.protocol.lineReceived("%s old mcdonald %s" % (outcome, opening)) self.protocol.lostConnection() failure = subunit.RemoteError( - u"lost connection during %s report of test 'old mcdonald'" % + _u("lost connection during %s report of test 'old mcdonald'") % outcome) self.assertEqual([ ('startTest', self.test), @@ -328,7 +328,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.protocol.lostConnection() self.assertEqual([ ('startTest', self.test), - ('addFailure', self.test, subunit.RemoteError(u"")), + ('addFailure', self.test, subunit.RemoteError(_u(""))), ('stopTest', self.test), ], self.client._events) @@ -542,8 +542,8 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): value = details else: if error_message is not None: - value = subunit.RemoteError(u'Text attachment: traceback\n' - '------------\n' + error_message + '------------\n') + value = subunit.RemoteError(_u("Text attachment: traceback\n" + "------------\n") + error_message + _u("------------\n")) else: value = subunit.RemoteError() self.assertEqual([ @@ -841,15 +841,15 @@ class TestRemotedTestCase(unittest.TestCase): class TestRemoteError(unittest.TestCase): def test_eq(self): - error = subunit.RemoteError(u"Something went wrong") - another_error = subunit.RemoteError(u"Something went wrong") - different_error = subunit.RemoteError(u"boo!") + error = subunit.RemoteError(_u("Something went wrong")) + another_error = subunit.RemoteError(_u("Something went wrong")) + different_error = subunit.RemoteError(_u("boo!")) self.assertEqual(error, another_error) self.assertNotEqual(error, different_error) self.assertNotEqual(different_error, another_error) def test_empty_constructor(self): - self.assertEqual(subunit.RemoteError(), subunit.RemoteError(u"")) + self.assertEqual(subunit.RemoteError(), subunit.RemoteError(_u(""))) class TestExecTestCase(unittest.TestCase): @@ -1000,7 +1000,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(u"boo qux"), self.test) + subunit.RemoteError(_u("boo qux")), self.test) def test_start_test(self): """Test startTest on a TestProtocolClient.""" @@ -1030,7 +1030,7 @@ class TestTestProtocolClient(unittest.TestCase): def test_add_failure(self): """Test addFailure on a TestProtocolClient.""" self.protocol.addFailure( - self.test, subunit.RemoteError(u"boo qux")) + self.test, subunit.RemoteError(_u("boo qux"))) self.assertEqual( self.io.getvalue(), ('failure: %s [\n' + _remote_exception_str + ': boo qux\n]\n') @@ -1054,7 +1054,7 @@ class TestTestProtocolClient(unittest.TestCase): def test_add_error(self): """Test stopTest on a TestProtocolClient.""" self.protocol.addError( - self.test, subunit.RemoteError(u"phwoar crikey")) + self.test, subunit.RemoteError(_u("phwoar crikey"))) self.assertEqual( self.io.getvalue(), ('error: %s [\n' + @@ -1079,7 +1079,7 @@ class TestTestProtocolClient(unittest.TestCase): def test_add_expected_failure(self): """Test addExpectedFailure on a TestProtocolClient.""" self.protocol.addExpectedFailure( - self.test, subunit.RemoteError(u"phwoar crikey")) + self.test, subunit.RemoteError(_u("phwoar crikey"))) self.assertEqual( self.io.getvalue(), ('xfail: %s [\n' + -- cgit v1.2.1 From b21965301c3ab72e5e19848d1019ca2d8e83da50 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 25 Apr 2011 10:36:45 +1200 Subject: More progress. --- python/subunit/__init__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index ba7f63e..281c3dd 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -123,7 +123,7 @@ import sys import unittest from testtools import content, content_type, ExtendedToOriginalDecorator -from testtools.compat import _b, _u, StringIO +from testtools.compat import _b, _u, BytesIO, StringIO try: from testtools.testresult.real import _StringException RemoteException = _StringException @@ -212,7 +212,7 @@ class _ParserState(object): if len(parts) == 2 and line.startswith(parts[0]): cmd, rest = parts offset = len(cmd) + 1 - cmd = cmd.rstrip(':') + cmd = cmd.rstrip(_b(':')) if cmd in ('test', 'testing'): self.startTest(offset, line) elif cmd == 'error': @@ -439,7 +439,7 @@ class TestProtocolServer(object): :param stream: The stream that lines received which are not part of the subunit protocol should be written to. This allows custom handling of mixed protocols. By default, sys.stdout will be used for - convenience. + convenience. It should accept bytes to its write() method. :param forward_stream: A stream to forward subunit lines to. This allows a filter to forward the entire stream while still parsing and acting on it. By default forward_stream is set to @@ -447,7 +447,10 @@ class TestProtocolServer(object): """ self.client = ExtendedToOriginalDecorator(client) if stream is None: - stream = sys.stdout + if sys.version_info > (3, 0): + stream = sys.stdout.buffer + else: + stream = sys.stdout self._stream = stream self._forward_stream = forward_stream or DiscardStream() # state objects we can switch too @@ -796,7 +799,7 @@ class ExecTestCase(unittest.TestCase): protocol = TestProtocolServer(result) output = subprocess.Popen(self.script, shell=True, stdout=subprocess.PIPE).communicate()[0] - protocol.readFrom(StringIO(output)) + protocol.readFrom(BytesIO(output)) class IsolatedTestCase(unittest.TestCase): -- cgit v1.2.1 From 86cff2f184f6bbd34fd330bae739a29a34a78d2a Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 25 Apr 2011 11:09:54 +1200 Subject: Progress. --- python/subunit/__init__.py | 9 ++++++--- python/subunit/details.py | 11 +++++++---- python/subunit/tests/test_details.py | 28 ++++++++++++++-------------- python/subunit/tests/test_test_protocol.py | 14 +++++++------- 4 files changed, 34 insertions(+), 28 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 281c3dd..6a46a40 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -185,6 +185,7 @@ class _ParserState(object): def __init__(self, parser): self.parser = parser + self._test_sym = (_b('test'), _b('testing')) def addError(self, offset, line): """An 'error:' directive has been read.""" @@ -213,7 +214,7 @@ class _ParserState(object): cmd, rest = parts offset = len(cmd) + 1 cmd = cmd.rstrip(_b(':')) - if cmd in ('test', 'testing'): + if cmd in self._test_sym: self.startTest(offset, line) elif cmd == 'error': self.addError(offset, line) @@ -332,8 +333,9 @@ class _OutSideTest(_ParserState): def startTest(self, offset, line): """A test start command received.""" self.parser._state = self.parser._in_test - self.parser._current_test = RemotedTestCase(line[offset:-1]) - self.parser.current_test_description = line[offset:-1] + test_name = line[offset:-1].decode('utf8') + self.parser._current_test = RemotedTestCase(test_name) + self.parser.current_test_description = test_name self.parser.client.startTest(self.parser._current_test) self.parser.subunitLineReceived(line) @@ -1138,6 +1140,7 @@ def get_default_formatter(): def _make_stream_binary(stream): """Ensure that a stream will be binary safe. See _make_binary_on_windows.""" if getattr(stream, 'fileno', None) is not None: + print (stream, type(stream)) _make_binary_on_windows(stream.fileno()) def _make_binary_on_windows(fileno): diff --git a/python/subunit/details.py b/python/subunit/details.py index 36c9974..9790543 100644 --- a/python/subunit/details.py +++ b/python/subunit/details.py @@ -17,10 +17,13 @@ """Handlers for outcome details.""" from testtools import content, content_type -from testtools.compat import StringIO +from testtools.compat import _b, StringIO from subunit import chunked +end_marker = _b("]\n") +quoted_marker = _b(" ]") + class DetailsParser(object): """Base class/API reference for details parsing.""" @@ -30,14 +33,14 @@ class SimpleDetailsParser(DetailsParser): """Parser for single-part [] delimited details.""" def __init__(self, state): - self._message = "" + self._message = _b("") self._state = state def lineReceived(self, line): - if line == "]\n": + if line == end_marker: self._state.endDetails() return - if line[0:2] == " ]": + if line[0:2] == quoted_marker: # quoted ] start self._message += line[1:] else: diff --git a/python/subunit/tests/test_details.py b/python/subunit/tests/test_details.py index 06eb430..49010d2 100644 --- a/python/subunit/tests/test_details.py +++ b/python/subunit/tests/test_details.py @@ -16,7 +16,7 @@ import unittest -from testtools.compat import StringIO +from testtools.compat import _b, StringIO import subunit.tests from subunit import content, content_type, details @@ -32,20 +32,20 @@ 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) + parser.lineReceived(_b("foo\n")) + parser.lineReceived(_b("bar\n")) + self.assertEqual(_b("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) + parser.lineReceived(_b("foo\n")) + parser.lineReceived(_b(" ]are\n")) + parser.lineReceived(_b("bar\n")) + self.assertEqual(_b("foo\n]are\nbar\n"), parser._message) def test_get_message(self): parser = details.SimpleDetailsParser(None) - self.assertEqual("", parser.get_message()) + self.assertEqual(_b(""), parser.get_message()) def test_get_details(self): parser = details.SimpleDetailsParser(None) @@ -54,13 +54,13 @@ class TestSimpleDetails(unittest.TestCase): expected['traceback'] = content.Content( content_type.ContentType("text", "x-traceback", {'charset': 'utf8'}), - lambda:[""]) + lambda:[_b("")]) 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())) + self.assertEqual(_b('').join(expected['traceback'].iter_bytes()), + _b('').join(found['traceback'].iter_bytes())) def test_get_details_skip(self): parser = details.SimpleDetailsParser(None) @@ -68,7 +68,7 @@ class TestSimpleDetails(unittest.TestCase): expected = {} expected['reason'] = content.Content( content_type.ContentType("text", "plain"), - lambda:[""]) + lambda:[_b("")]) found = parser.get_details("skip") self.assertEqual(expected, found) @@ -78,7 +78,7 @@ class TestSimpleDetails(unittest.TestCase): expected = {} expected['message'] = content.Content( content_type.ContentType("text", "plain"), - lambda:[""]) + lambda:[_b("")]) 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 8171696..5541653 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -18,7 +18,7 @@ import datetime import unittest import os -from testtools.compat import _u, StringIO +from testtools.compat import _b, _u, StringIO from testtools.content import Content, TracebackContent from testtools.content_type import ContentType from testtools.tests.helpers import ( @@ -372,8 +372,8 @@ class TestInTestMultipart(unittest.TestCase): def setUp(self): self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client) - self.protocol.lineReceived("test mcdonalds farm\n") - self.test = subunit.RemotedTestCase("mcdonalds farm") + self.protocol.lineReceived(_b("test mcdonalds farm\n")) + self.test = subunit.RemotedTestCase(_u("mcdonalds farm")) def test__outcome_sets_details_parser(self): self.protocol._reading_success_details.details_parser = None @@ -770,13 +770,13 @@ class TestTestProtocolServerStreamTags(unittest.TestCase): ], self.client._events) def test_tags_do_not_get_set_on_test(self): - self.protocol.lineReceived("test mcdonalds farm\n") + self.protocol.lineReceived(_b("test mcdonalds farm\n")) 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") + self.protocol.lineReceived(_b("tags: foo bar\n")) + self.protocol.lineReceived(_b("test mcdonalds farm\n")) test = self.client._events[-1][-1] self.assertEqual(None, getattr(test, 'tags', None)) @@ -884,7 +884,7 @@ class TestExecTestCase(unittest.TestCase): bing = subunit.RemotedTestCase("bing crosby") bing_details = {} bing_details['traceback'] = Content(ContentType("text", "x-traceback", - {'charset': 'utf8'}), lambda:["foo.c:53:ERROR invalid state\n"]) + {'charset': 'utf8'}), lambda:[_b("foo.c:53:ERROR invalid state\n")]) an_error = subunit.RemotedTestCase("an error") error_details = {} self.assertEqual([ -- cgit v1.2.1 From 970fe166268ac41a20aa6dc78e4c491ae5449173 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 25 Apr 2011 11:19:53 +1200 Subject: More small stuff. --- python/subunit/__init__.py | 4 ++-- python/subunit/tests/test_test_protocol.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 6a46a40..5387cae 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -696,14 +696,14 @@ class TestProtocolClient(testresult.TestResult): :param details: An extended details dict for a test outcome. """ self._stream.write(" [ multipart\n") - for name, content in sorted(details.iteritems()): + for name, content in sorted(details.items()): 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(): + for param, value in parameters.items(): param_strs.append("%s=%s" % (param, value)) self._stream.write(",".join(param_strs)) self._stream.write("\n%s\n" % name) diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 5541653..9152cce 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -18,7 +18,7 @@ import datetime import unittest import os -from testtools.compat import _b, _u, StringIO +from testtools.compat import _b, _u, BytesIO, StringIO from testtools.content import Content, TracebackContent from testtools.content_type import ContentType from testtools.tests.helpers import ( @@ -55,23 +55,23 @@ class TestProtocolServerForward(unittest.TestCase): def test_story(self): client = unittest.TestResult() - out = StringIO() + out = BytesIO() protocol = subunit.TestProtocolServer(client, forward_stream=out) - pipe = StringIO("test old mcdonald\n" - "success old mcdonald\n") + pipe = BytesIO(_b("test old mcdonald\n" + "success old mcdonald\n")) protocol.readFrom(pipe) self.assertEqual(client.testsRun, 1) self.assertEqual(pipe.getvalue(), out.getvalue()) def test_not_command(self): client = unittest.TestResult() - out = StringIO() + out = BytesIO() protocol = subunit.TestProtocolServer(client, stream=subunit.DiscardStream(), forward_stream=out) - pipe = StringIO("success old mcdonald\n") + pipe = BytesIO(_b("success old mcdonald\n")) protocol.readFrom(pipe) self.assertEqual(client.testsRun, 0) - self.assertEqual("", out.getvalue()) + self.assertEqual(_b(""), out.getvalue()) class TestTestProtocolServerPipe(unittest.TestCase): -- cgit v1.2.1 From 83ee9fc3528955994c55ba46c1784cbf6d3c3ae8 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 25 Apr 2011 00:37:52 +0100 Subject: Use try/except rather than getattr to test for fileno _make_stream_binary --- python/subunit/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 5387cae..146d2c1 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -1139,9 +1139,11 @@ def get_default_formatter(): def _make_stream_binary(stream): """Ensure that a stream will be binary safe. See _make_binary_on_windows.""" - if getattr(stream, 'fileno', None) is not None: - print (stream, type(stream)) - _make_binary_on_windows(stream.fileno()) + try: + fileno = stream.fileno() + except AttributeError: + return + _make_binary_on_windows(fileno) def _make_binary_on_windows(fileno): """Win32 mangles \r\n to \n and that breaks streams. See bug lp:505078.""" -- cgit v1.2.1 From 4953d515c61733237dc78842f0785ddd3e9d69c5 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 25 Apr 2011 00:38:27 +0100 Subject: Vary fileno attempt exception type to support Python 3 --- python/subunit/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 146d2c1..7e2f141 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -1137,11 +1137,16 @@ def get_default_formatter(): return sys.stdout +if sys.version_info > (3, 0): + from io import UnsupportedOperation as _NoFilenoError +else: + _NoFilenoError = AttributeError + def _make_stream_binary(stream): """Ensure that a stream will be binary safe. See _make_binary_on_windows.""" try: fileno = stream.fileno() - except AttributeError: + except _NoFilenoError: return _make_binary_on_windows(fileno) -- cgit v1.2.1 From 264161d485bfb5e2e952b329eb53d0a60555bced Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 25 Apr 2011 00:39:31 +0100 Subject: Remove presumably erroneous _make_stream_binary call --- python/subunit/__init__.py | 1 - 1 file changed, 1 deletion(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 7e2f141..a49641c 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -1058,7 +1058,6 @@ class ProtocolTestCase(object): _make_stream_binary(stream) self._passthrough = passthrough self._forward = forward - _make_stream_binary(forward) def __call__(self, result=None): return self.run(result) -- cgit v1.2.1 From d8df52062fbcee4da77b6e1cdb701defa2e8a2da Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 25 Apr 2011 12:40:59 +1200 Subject: More fixups. --- python/subunit/__init__.py | 49 ++++-- python/subunit/tests/test_test_protocol.py | 269 +++++++++++++++-------------- 2 files changed, 168 insertions(+), 150 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 5387cae..14fb94d 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -186,6 +186,17 @@ class _ParserState(object): def __init__(self, parser): self.parser = parser self._test_sym = (_b('test'), _b('testing')) + self._colon_sym = _b(':') + self._error_sym = (_b('error'),) + self._failure_sym = (_b('failure'),) + self._progress_sym = (_b('progress'),) + self._skip_sym = _b('skip') + self._success_sym = (_b('success'), _b('successful')) + self._tags_sym = (_b('tags'),) + self._time_sym = (_b('time'),) + self._xfail_sym = (_b('xfail'),) + self._start_simple = _u(" [") + self._start_multipart = _u(" [ multipart") def addError(self, offset, line): """An 'error:' directive has been read.""" @@ -213,26 +224,26 @@ class _ParserState(object): if len(parts) == 2 and line.startswith(parts[0]): cmd, rest = parts offset = len(cmd) + 1 - cmd = cmd.rstrip(_b(':')) + cmd = cmd.rstrip(self._colon_sym) if cmd in self._test_sym: self.startTest(offset, line) - elif cmd == 'error': + elif cmd in self._error_sym: self.addError(offset, line) - elif cmd == 'failure': + elif cmd in self._failure_sym: self.addFailure(offset, line) - elif cmd == 'progress': + elif cmd in self._progress_sym: self.parser._handleProgress(offset, line) - elif cmd == 'skip': + elif cmd in self._skip_sym: self.addSkip(offset, line) - elif cmd in ('success', 'successful'): + elif cmd in self._success_sym: self.addSuccess(offset, line) - elif cmd in ('tags',): + elif cmd in self._tags_sym: self.parser._handleTags(offset, line) self.parser.subunitLineReceived(line) - elif cmd in ('time',): + elif cmd in self._time_sym: self.parser._handleTime(offset, line) self.parser.subunitLineReceived(line) - elif cmd == 'xfail': + elif cmd in self._xfail_sym: self.addExpectedFail(offset, line) else: self.parser.stdOutLineReceived(line) @@ -258,19 +269,21 @@ class _InTest(_ParserState): :param details_state: The state to switch to for details processing of this outcome. """ - if self.parser.current_test_description == line[offset:-1]: + test_name = line[offset:-1].decode('utf8') + if self.parser.current_test_description == test_name: self.parser._state = self.parser._outside_test self.parser.current_test_description = None no_details() self.parser.client.stopTest(self.parser._current_test) self.parser._current_test = None self.parser.subunitLineReceived(line) - elif self.parser.current_test_description + " [" == line[offset:-1]: + elif self.parser.current_test_description + self._start_simple == \ + test_name: self.parser._state = details_state details_state.set_simple() self.parser.subunitLineReceived(line) - elif self.parser.current_test_description + " [ multipart" == \ - line[offset:-1]: + elif self.parser.current_test_description + self._start_multipart == \ + test_name: self.parser._state = details_state details_state.set_multipart() self.parser.subunitLineReceived(line) @@ -465,17 +478,21 @@ class TestProtocolServer(object): self._reading_xfail_details = _ReadingExpectedFailureDetails(self) # start with outside test. self._state = self._outside_test + # Avoid casts on every call + self._plusminus = _b('+-') + self._push_sym = _b('push') + self._pop_sym = _b('pop') def _handleProgress(self, offset, line): """Process a progress directive.""" line = line[offset:].strip() - if line[0] in '+-': + if line[0] in self._plusminus: whence = PROGRESS_CUR delta = int(line) - elif line == "push": + elif line == self._push_sym: whence = PROGRESS_PUSH delta = None - elif line == "pop": + elif line == self._pop_sym: whence = PROGRESS_POP delta = None else: diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 9152cce..7778fcc 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -79,14 +79,14 @@ class TestTestProtocolServerPipe(unittest.TestCase): def test_story(self): client = unittest.TestResult() protocol = subunit.TestProtocolServer(client) - pipe = StringIO("test old mcdonald\n" + pipe = BytesIO(_b("test old mcdonald\n" "success old mcdonald\n" "test bing crosby\n" "failure bing crosby [\n" "foo.c:53:ERROR invalid state\n" "]\n" "test an error\n" - "error an error\n") + "error an error\n")) protocol.readFrom(pipe) bing = subunit.RemotedTestCase("bing crosby") an_error = subunit.RemotedTestCase("an error") @@ -110,26 +110,26 @@ class TestTestProtocolServerStartTest(unittest.TestCase): self.protocol = subunit.TestProtocolServer(self.client) def test_start_test(self): - self.protocol.lineReceived("test old mcdonald\n") + self.protocol.lineReceived(_b("test old mcdonald\n")) self.assertEqual(self.client._events, [('startTest', subunit.RemotedTestCase("old mcdonald"))]) def test_start_testing(self): - self.protocol.lineReceived("testing old mcdonald\n") + self.protocol.lineReceived(_b("testing old mcdonald\n")) self.assertEqual(self.client._events, [('startTest', subunit.RemotedTestCase("old mcdonald"))]) def test_start_test_colon(self): - self.protocol.lineReceived("test: old mcdonald\n") + self.protocol.lineReceived(_b("test: old mcdonald\n")) self.assertEqual(self.client._events, [('startTest', subunit.RemotedTestCase("old mcdonald"))]) def test_indented_test_colon_ignored(self): - self.protocol.lineReceived(" test: old mcdonald\n") + self.protocol.lineReceived(_b(" test: old mcdonald\n")) self.assertEqual([], self.client._events) def test_start_testing_colon(self): - self.protocol.lineReceived("testing: old mcdonald\n") + self.protocol.lineReceived(_b("testing: old mcdonald\n")) self.assertEqual(self.client._events, [('startTest', subunit.RemotedTestCase("old mcdonald"))]) @@ -137,22 +137,22 @@ class TestTestProtocolServerStartTest(unittest.TestCase): class TestTestProtocolServerPassThrough(unittest.TestCase): def setUp(self): - self.stdout = StringIO() + self.stdout = BytesIO() self.test = subunit.RemotedTestCase("old mcdonald") self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client, self.stdout) def keywords_before_test(self): - self.protocol.lineReceived("failure a\n") - self.protocol.lineReceived("failure: a\n") - self.protocol.lineReceived("error a\n") - self.protocol.lineReceived("error: a\n") - self.protocol.lineReceived("success a\n") - self.protocol.lineReceived("success: a\n") - self.protocol.lineReceived("successful a\n") - self.protocol.lineReceived("successful: a\n") - self.protocol.lineReceived("]\n") - self.assertEqual(self.stdout.getvalue(), "failure a\n" + self.protocol.lineReceived(_b("failure a\n")) + self.protocol.lineReceived(_b("failure: a\n")) + self.protocol.lineReceived(_b("error a\n")) + self.protocol.lineReceived(_b("error: a\n")) + self.protocol.lineReceived(_b("success a\n")) + self.protocol.lineReceived(_b("success: a\n")) + self.protocol.lineReceived(_b("successful a\n")) + self.protocol.lineReceived(_b("successful: a\n")) + self.protocol.lineReceived(_b("]\n")) + self.assertEqual(self.stdout.getvalue(), _b("failure a\n" "failure: a\n" "error a\n" "error: a\n" @@ -160,15 +160,15 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): "success: a\n" "successful a\n" "successful: a\n" - "]\n") + "]\n")) def test_keywords_before_test(self): self.keywords_before_test() self.assertEqual(self.client._events, []) def test_keywords_after_error(self): - self.protocol.lineReceived("test old mcdonald\n") - self.protocol.lineReceived("error old mcdonald\n") + self.protocol.lineReceived(_b("test old mcdonald\n")) + self.protocol.lineReceived(_b("error old mcdonald\n")) self.keywords_before_test() self.assertEqual([ ('startTest', self.test), @@ -177,8 +177,8 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): ], self.client._events) def test_keywords_after_failure(self): - self.protocol.lineReceived("test old mcdonald\n") - self.protocol.lineReceived("failure old mcdonald\n") + self.protocol.lineReceived(_b("test old mcdonald\n")) + self.protocol.lineReceived(_b("failure old mcdonald\n")) self.keywords_before_test() self.assertEqual(self.client._events, [ ('startTest', self.test), @@ -187,8 +187,8 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): ]) def test_keywords_after_success(self): - self.protocol.lineReceived("test old mcdonald\n") - self.protocol.lineReceived("success old mcdonald\n") + self.protocol.lineReceived(_b("test old mcdonald\n")) + self.protocol.lineReceived(_b("success old mcdonald\n")) self.keywords_before_test() self.assertEqual([ ('startTest', self.test), @@ -197,19 +197,19 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): ], self.client._events) def test_keywords_after_test(self): - self.protocol.lineReceived("test old mcdonald\n") - self.protocol.lineReceived("test old mcdonald\n") - self.protocol.lineReceived("failure a\n") - self.protocol.lineReceived("failure: a\n") - self.protocol.lineReceived("error a\n") - self.protocol.lineReceived("error: a\n") - self.protocol.lineReceived("success a\n") - self.protocol.lineReceived("success: a\n") - self.protocol.lineReceived("successful a\n") - self.protocol.lineReceived("successful: a\n") - self.protocol.lineReceived("]\n") - self.protocol.lineReceived("failure old mcdonald\n") - self.assertEqual(self.stdout.getvalue(), "test old mcdonald\n" + self.protocol.lineReceived(_b("test old mcdonald\n")) + self.protocol.lineReceived(_b("test old mcdonald\n")) + self.protocol.lineReceived(_b("failure a\n")) + self.protocol.lineReceived(_b("failure: a\n")) + self.protocol.lineReceived(_b("error a\n")) + self.protocol.lineReceived(_b("error: a\n")) + self.protocol.lineReceived(_b("success a\n")) + self.protocol.lineReceived(_b("success: a\n")) + self.protocol.lineReceived(_b("successful a\n")) + self.protocol.lineReceived(_b("successful: a\n")) + self.protocol.lineReceived(_b("]\n")) + self.protocol.lineReceived(_b("failure old mcdonald\n")) + self.assertEqual(self.stdout.getvalue(), _b("test old mcdonald\n" "failure a\n" "failure: a\n" "error a\n" @@ -218,7 +218,7 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): "success: a\n" "successful a\n" "successful: a\n" - "]\n") + "]\n")) self.assertEqual(self.client._events, [ ('startTest', self.test), ('addFailure', self.test, {}), @@ -228,24 +228,24 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): 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") - self.protocol.lineReceived("failure a\n") - self.protocol.lineReceived("failure: a\n") - self.protocol.lineReceived("error a\n") - self.protocol.lineReceived("error: a\n") - self.protocol.lineReceived("success a\n") - self.protocol.lineReceived("success: a\n") - self.protocol.lineReceived("successful a\n") - self.protocol.lineReceived("successful: a\n") - self.protocol.lineReceived(" ]\n") - self.protocol.lineReceived("]\n") - self.assertEqual(self.stdout.getvalue(), "") + self.protocol.lineReceived(_b("test old mcdonald\n")) + self.protocol.lineReceived(_b("failure: old mcdonald [\n")) + self.protocol.lineReceived(_b("test old mcdonald\n")) + self.protocol.lineReceived(_b("failure a\n")) + self.protocol.lineReceived(_b("failure: a\n")) + self.protocol.lineReceived(_b("error a\n")) + self.protocol.lineReceived(_b("error: a\n")) + self.protocol.lineReceived(_b("success a\n")) + self.protocol.lineReceived(_b("success: a\n")) + self.protocol.lineReceived(_b("successful a\n")) + self.protocol.lineReceived(_b("successful: a\n")) + self.protocol.lineReceived(_b(" ]\n")) + self.protocol.lineReceived(_b("]\n")) + self.assertEqual(self.stdout.getvalue(), _b("")) details = {} details['traceback'] = Content(ContentType("text", "x-traceback", {'charset': 'utf8'}), - lambda:[ + lambda:[_b( "test old mcdonald\n" "failure a\n" "failure: a\n" @@ -255,7 +255,7 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): "success: a\n" "successful a\n" "successful: a\n" - "]\n"]) + "]\n")]) self.assertEqual(self.client._events, [ ('startTest', self.test), ('addFailure', self.test, details), @@ -266,7 +266,7 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): """Lines received which cannot be interpreted as any protocol action should be passed through to sys.stdout. """ - bytes = "randombytes\n" + bytes = _b("randombytes\n") self.protocol.lineReceived(bytes) self.assertEqual(self.stdout.getvalue(), bytes) @@ -283,7 +283,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.assertEqual([], self.client._events) def test_lost_connection_after_start(self): - self.protocol.lineReceived("test old mcdonald\n") + self.protocol.lineReceived(_b("test old mcdonald\n")) self.protocol.lostConnection() failure = subunit.RemoteError( _u("lost connection during test 'old mcdonald'")) @@ -294,8 +294,8 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): ], self.client._events) def test_lost_connected_after_error(self): - self.protocol.lineReceived("test old mcdonald\n") - self.protocol.lineReceived("error old mcdonald\n") + self.protocol.lineReceived(_b("test old mcdonald\n")) + self.protocol.lineReceived(_b("error old mcdonald\n")) self.protocol.lostConnection() self.assertEqual([ ('startTest', self.test), @@ -304,8 +304,8 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): ], self.client._events) 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.lineReceived(_b("test old mcdonald\n")) + self.protocol.lineReceived(_b("%s old mcdonald %s" % (outcome, opening))) self.protocol.lostConnection() failure = subunit.RemoteError( _u("lost connection during %s report of test 'old mcdonald'") % @@ -323,8 +323,8 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): 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") + self.protocol.lineReceived(_b("test old mcdonald\n")) + self.protocol.lineReceived(_b("failure old mcdonald\n")) self.protocol.lostConnection() self.assertEqual([ ('startTest', self.test), @@ -339,8 +339,8 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.do_connection_lost("failure", "[ multipart\n") def test_lost_connection_after_success(self): - self.protocol.lineReceived("test old mcdonald\n") - self.protocol.lineReceived("success old mcdonald\n") + self.protocol.lineReceived(_b("test old mcdonald\n")) + self.protocol.lineReceived(_b("success old mcdonald\n")) self.protocol.lostConnection() self.assertEqual([ ('startTest', self.test), @@ -377,7 +377,7 @@ class TestInTestMultipart(unittest.TestCase): def test__outcome_sets_details_parser(self): self.protocol._reading_success_details.details_parser = None - self.protocol._state._outcome(0, "mcdonalds farm [ multipart\n", + self.protocol._state._outcome(0, _b("mcdonalds farm [ multipart\n"), None, self.protocol._reading_success_details) parser = self.protocol._reading_success_details.details_parser self.assertNotEqual(None, parser) @@ -390,11 +390,11 @@ class TestTestProtocolServerAddError(unittest.TestCase): def setUp(self): self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client) - self.protocol.lineReceived("test mcdonalds farm\n") + self.protocol.lineReceived(_b("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.protocol.lineReceived(_b("%s mcdonalds farm\n" % keyword)) details = {} self.assertEqual([ ('startTest', self.test), @@ -409,11 +409,11 @@ class TestTestProtocolServerAddError(unittest.TestCase): self.simple_error_keyword("error:") def test_error_empty_message(self): - self.protocol.lineReceived("error mcdonalds farm [\n") - self.protocol.lineReceived("]\n") + self.protocol.lineReceived(_b("error mcdonalds farm [\n")) + self.protocol.lineReceived(_b("]\n")) details = {} details['traceback'] = Content(ContentType("text", "x-traceback", - {'charset': 'utf8'}), lambda:[""]) + {'charset': 'utf8'}), lambda:[_b("")]) self.assertEqual([ ('startTest', self.test), ('addError', self.test, details), @@ -421,12 +421,12 @@ class TestTestProtocolServerAddError(unittest.TestCase): ], self.client._events) def error_quoted_bracket(self, keyword): - self.protocol.lineReceived("%s mcdonalds farm [\n" % keyword) - self.protocol.lineReceived(" ]\n") - self.protocol.lineReceived("]\n") + self.protocol.lineReceived(_b("%s mcdonalds farm [\n" % keyword)) + self.protocol.lineReceived(_b(" ]\n")) + self.protocol.lineReceived(_b("]\n")) details = {} details['traceback'] = Content(ContentType("text", "x-traceback", - {'charset': 'utf8'}), lambda:["]\n"]) + {'charset': 'utf8'}), lambda:[_b("]\n")]) self.assertEqual([ ('startTest', self.test), ('addError', self.test, details), @@ -445,7 +445,7 @@ class TestTestProtocolServerAddFailure(unittest.TestCase): def setUp(self): self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client) - self.protocol.lineReceived("test mcdonalds farm\n") + self.protocol.lineReceived(_b("test mcdonalds farm\n")) self.test = subunit.RemotedTestCase("mcdonalds farm") def assertFailure(self, details): @@ -456,7 +456,7 @@ class TestTestProtocolServerAddFailure(unittest.TestCase): ], self.client._events) def simple_failure_keyword(self, keyword): - self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) + self.protocol.lineReceived(_b("%s mcdonalds farm\n" % keyword)) details = {} self.assertFailure(details) @@ -467,20 +467,20 @@ class TestTestProtocolServerAddFailure(unittest.TestCase): self.simple_failure_keyword("failure:") def test_failure_empty_message(self): - self.protocol.lineReceived("failure mcdonalds farm [\n") - self.protocol.lineReceived("]\n") + self.protocol.lineReceived(_b("failure mcdonalds farm [\n")) + self.protocol.lineReceived(_b("]\n")) details = {} details['traceback'] = Content(ContentType("text", "x-traceback", - {'charset': 'utf8'}), lambda:[""]) + {'charset': 'utf8'}), lambda:[_b("")]) 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") + self.protocol.lineReceived(_b("%s mcdonalds farm [\n" % keyword)) + self.protocol.lineReceived(_b(" ]\n")) + self.protocol.lineReceived(_b("]\n")) details = {} details['traceback'] = Content(ContentType("text", "x-traceback", - {'charset': 'utf8'}), lambda:["]\n"]) + {'charset': 'utf8'}), lambda:[_b("]\n")]) self.assertFailure(details) def test_failure_quoted_bracket(self): @@ -518,11 +518,11 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): 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.protocol.lineReceived(_b("test mcdonalds farm\n")) self.test = self.client._events[-1][-1] def simple_xfail_keyword(self, keyword, as_success): - self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) + self.protocol.lineReceived(_b("%s mcdonalds farm\n" % keyword)) self.check_success_or_xfail(as_success) def check_success_or_xfail(self, as_success, error_message=None): @@ -537,13 +537,14 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): if error_message is not None: details['traceback'] = Content( ContentType("text", "x-traceback", {'charset': 'utf8'}), - lambda:[error_message]) + lambda:[_b(error_message)]) if isinstance(self.client, ExtendedTestResult): value = details else: if error_message is not None: value = subunit.RemoteError(_u("Text attachment: traceback\n" - "------------\n") + error_message + _u("------------\n")) + "------------\n") + _u(error_message) + + _u("------------\n")) else: value = subunit.RemoteError() self.assertEqual([ @@ -577,16 +578,16 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): self.empty_message(False, error_message="") def empty_message(self, as_success, error_message="\n"): - self.protocol.lineReceived("xfail mcdonalds farm [\n") - self.protocol.lineReceived("]\n") + self.protocol.lineReceived(_b("xfail mcdonalds farm [\n")) + self.protocol.lineReceived(_b("]\n")) 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 # 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.protocol.lineReceived(_b("%s mcdonalds farm [\n" % keyword)) + self.protocol.lineReceived(_b(" ]\n")) + self.protocol.lineReceived(_b("]\n")) self.check_success_or_xfail(as_success, "]\n") def test_xfail_quoted_bracket(self): @@ -617,7 +618,7 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): """Setup a test object ready to be skipped.""" self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client) - self.protocol.lineReceived("test mcdonalds farm\n") + self.protocol.lineReceived(_b("test mcdonalds farm\n")) self.test = self.client._events[-1][-1] def assertSkip(self, reason): @@ -632,7 +633,7 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): ], self.client._events) def simple_skip_keyword(self, keyword): - self.protocol.lineReceived("%s mcdonalds farm\n" % keyword) + self.protocol.lineReceived(_b("%s mcdonalds farm\n" % keyword)) self.assertSkip(None) def test_simple_skip(self): @@ -642,17 +643,17 @@ class TestTestProtocolServerAddSkip(unittest.TestCase): self.simple_skip_keyword("skip:") def test_skip_empty_message(self): - self.protocol.lineReceived("skip mcdonalds farm [\n") - self.protocol.lineReceived("]\n") - self.assertSkip("") + self.protocol.lineReceived(_b("skip mcdonalds farm [\n")) + self.protocol.lineReceived(_b("]\n")) + self.assertSkip(_b("")) def skip_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.assertSkip("]\n") + self.protocol.lineReceived(_b("%s mcdonalds farm [\n" % keyword)) + self.protocol.lineReceived(_b(" ]\n")) + self.protocol.lineReceived(_b("]\n")) + self.assertSkip(_b("]\n")) def test_skip_quoted_bracket(self): self.skip_quoted_bracket("skip") @@ -666,11 +667,11 @@ class TestTestProtocolServerAddSuccess(unittest.TestCase): def setUp(self): self.client = ExtendedTestResult() self.protocol = subunit.TestProtocolServer(self.client) - self.protocol.lineReceived("test mcdonalds farm\n") + self.protocol.lineReceived(_b("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.protocol.lineReceived(_b("%s mcdonalds farm\n" % keyword)) self.assertEqual([ ('startTest', self.test), ('addSuccess', self.test), @@ -691,22 +692,22 @@ class TestTestProtocolServerAddSuccess(unittest.TestCase): ], self.client._events) def test_success_empty_message(self): - self.protocol.lineReceived("success mcdonalds farm [\n") - self.protocol.lineReceived("]\n") + self.protocol.lineReceived(_b("success mcdonalds farm [\n")) + self.protocol.lineReceived(_b("]\n")) details = {} details['message'] = Content(ContentType("text", "plain"), - lambda:[""]) + lambda:[_b("")]) 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.protocol.lineReceived(_b("%s mcdonalds farm [\n" % keyword)) + self.protocol.lineReceived(_b(" ]\n")) + self.protocol.lineReceived(_b("]\n")) details = {} details['message'] = Content(ContentType("text", "plain"), - lambda:["]\n"]) + lambda:[_b("]\n")]) self.assertSuccess(details) def test_success_quoted_bracket(self): @@ -721,26 +722,26 @@ class TestTestProtocolServerProgress(unittest.TestCase): def test_progress_accepted_stdlib(self): self.result = Python26TestResult() - self.stream = StringIO() + self.stream = BytesIO() self.protocol = subunit.TestProtocolServer(self.result, stream=self.stream) - self.protocol.lineReceived("progress: 23") - self.protocol.lineReceived("progress: -2") - self.protocol.lineReceived("progress: +4") - self.assertEqual("", self.stream.getvalue()) + self.protocol.lineReceived(_b("progress: 23")) + self.protocol.lineReceived(_b("progress: -2")) + self.protocol.lineReceived(_b("progress: +4")) + self.assertEqual(_b(""), self.stream.getvalue()) def test_progress_accepted_extended(self): # With a progress capable TestResult, progress events are emitted. self.result = ExtendedTestResult() - self.stream = StringIO() + self.stream = BytesIO() self.protocol = subunit.TestProtocolServer(self.result, stream=self.stream) - self.protocol.lineReceived("progress: 23") - self.protocol.lineReceived("progress: push") - self.protocol.lineReceived("progress: -2") - self.protocol.lineReceived("progress: pop") - self.protocol.lineReceived("progress: +4") - self.assertEqual("", self.stream.getvalue()) + self.protocol.lineReceived(_b("progress: 23")) + self.protocol.lineReceived(_b("progress: push")) + self.protocol.lineReceived(_b("progress: -2")) + self.protocol.lineReceived(_b("progress: pop")) + self.protocol.lineReceived(_b("progress: +4")) + self.assertEqual(_b(""), self.stream.getvalue()) self.assertEqual([ ('progress', 23, subunit.PROGRESS_SET), ('progress', None, subunit.PROGRESS_PUSH), @@ -758,13 +759,13 @@ class TestTestProtocolServerStreamTags(unittest.TestCase): self.protocol = subunit.TestProtocolServer(self.client) def test_initial_tags(self): - self.protocol.lineReceived("tags: foo bar:baz quux\n") + self.protocol.lineReceived(_b("tags: foo bar:baz quux\n")) self.assertEqual([ ('tags', set(["foo", "bar:baz", "quux"]), set()), ], self.client._events) def test_minus_removes_tags(self): - self.protocol.lineReceived("tags: -bar quux\n") + self.protocol.lineReceived(_b("tags: -bar quux\n")) self.assertEqual([ ('tags', set(["quux"]), set(["bar"])), ], self.client._events) @@ -781,10 +782,10 @@ class TestTestProtocolServerStreamTags(unittest.TestCase): self.assertEqual(None, getattr(test, 'tags', None)) def test_tags_get_set_on_test_tags(self): - self.protocol.lineReceived("test mcdonalds farm\n") + self.protocol.lineReceived(_b("test mcdonalds farm\n")) test = self.client._events[-1][-1] - self.protocol.lineReceived("tags: foo bar\n") - self.protocol.lineReceived("success mcdonalds farm\n") + self.protocol.lineReceived(_b("tags: foo bar\n")) + self.protocol.lineReceived(_b("success mcdonalds farm\n")) self.assertEqual(None, getattr(test, 'tags', None)) @@ -793,19 +794,19 @@ class TestTestProtocolServerStreamTime(unittest.TestCase): def test_time_accepted_stdlib(self): self.result = Python26TestResult() - self.stream = StringIO() + self.stream = BytesIO() 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.protocol.lineReceived(_b("time: 2001-12-12 12:59:59Z\n")) + self.assertEqual(_b(""), self.stream.getvalue()) def test_time_accepted_extended(self): self.result = ExtendedTestResult() - self.stream = StringIO() + self.stream = BytesIO() 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.protocol.lineReceived(_b("time: 2001-12-12 12:59:59Z\n")) + self.assertEqual(_b(""), self.stream.getvalue()) self.assertEqual([ ('time', datetime.datetime(2001, 12, 12, 12, 59, 59, 0, iso8601.Utc())) -- cgit v1.2.1 From b05ec2c069cdbc19dffa1061323aaf2f6c3b8568 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 25 Apr 2011 13:10:06 +1200 Subject: Tags in the API are strings. And python3 exception names. --- python/subunit/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index c997296..6e1668e 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -127,7 +127,11 @@ from testtools.compat import _b, _u, BytesIO, StringIO try: from testtools.testresult.real import _StringException RemoteException = _StringException - _remote_exception_str = '_StringException' # For testing. + # For testing: different pythons have different str() implementations. + if sys.version_info > (3, 0): + _remote_exception_str = 'testtools.testresult.real._StringException' + else: + _remote_exception_str = '_StringException' except ImportError: raise ImportError ("testtools.testresult.real does not contain " "_StringException, check your version.") @@ -502,7 +506,7 @@ class TestProtocolServer(object): def _handleTags(self, offset, line): """Process a tags command.""" - tags = line[offset:].split() + tags = line[offset:].decode('utf8').split() new_tags, gone_tags = tags_to_new_gone(tags) self.client.tags(new_tags, gone_tags) -- cgit v1.2.1 From abc703874bcbabc890ffb8d23264a4d2095d72a6 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 25 Apr 2011 13:29:31 +1200 Subject: Update stats tests. --- python/subunit/tests/test_subunit_stats.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_stats.py b/python/subunit/tests/test_subunit_stats.py index 778bb7f..6fd3301 100644 --- a/python/subunit/tests/test_subunit_stats.py +++ b/python/subunit/tests/test_subunit_stats.py @@ -18,7 +18,7 @@ import unittest -from testtools.compat import StringIO +from testtools.compat import _b, BytesIO, StringIO import subunit @@ -29,7 +29,7 @@ class TestTestResultStats(unittest.TestCase): def setUp(self): self.output = StringIO() self.result = subunit.TestResultStats(self.output) - self.input_stream = StringIO() + self.input_stream = BytesIO() self.test = subunit.ProtocolTestCase(self.input_stream) def test_stats_empty(self): @@ -40,7 +40,7 @@ class TestTestResultStats(unittest.TestCase): self.assertEqual(set(), self.result.seen_tags) def setUpUsedStream(self): - self.input_stream.write("""tags: global + self.input_stream.write(_b("""tags: global test passed success passed test failed @@ -52,7 +52,7 @@ test skipped skip skipped test todo xfail todo -""") +""")) self.input_stream.seek(0) self.test.run(self.result) -- cgit v1.2.1 From 86b85e3f83f4030a63026386ffc3d3488ab34d13 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 25 Apr 2011 16:25:24 +1200 Subject: Nearly done. --- python/subunit/__init__.py | 72 ++++++++++++--------- python/subunit/chunked.py | 41 +++++++----- python/subunit/details.py | 13 ++-- python/subunit/iso8601.py | 22 +++++-- python/subunit/tests/test_chunked.py | 98 ++++++++++++++--------------- python/subunit/tests/test_details.py | 16 ++--- python/subunit/tests/test_subunit_filter.py | 12 ++-- python/subunit/tests/test_test_protocol.py | 79 +++++++++++------------ 8 files changed, 192 insertions(+), 161 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 6e1668e..8fb3ab7 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -129,9 +129,11 @@ try: RemoteException = _StringException # For testing: different pythons have different str() implementations. if sys.version_info > (3, 0): - _remote_exception_str = 'testtools.testresult.real._StringException' + _remote_exception_str = "testtools.testresult.real._StringException" + _remote_exception_str_chunked = "34\r\n" + _remote_exception_str else: - _remote_exception_str = '_StringException' + _remote_exception_str = "_StringException" + _remote_exception_str_chunked = "1A\r\n" + _remote_exception_str except ImportError: raise ImportError ("testtools.testresult.real does not contain " "_StringException, check your version.") @@ -559,7 +561,8 @@ class TestProtocolClient(testresult.TestResult): # Get a TestSuite or TestCase to run suite = make_suite() - # Create a stream (any object with a 'write' method) + # Create a stream (any object with a 'write' method). This should accept + # bytes not strings: subunit is a byte orientated protocol. stream = file('tests.log', 'wb') # Create a subunit result object which will output to the stream result = subunit.TestProtocolClient(stream) @@ -576,6 +579,14 @@ class TestProtocolClient(testresult.TestResult): testresult.TestResult.__init__(self) self._stream = stream _make_stream_binary(stream) + self._progress_fmt = _b("progress: ") + self._bytes_eol = _b("\n") + self._progress_plus = _b("+") + self._progress_push = _b("push") + self._progress_pop = _b("pop") + self._empty_bytes = _b("") + self._start_simple = _b(" [\n") + self._end_simple = _b("]\n") def addError(self, test, error=None, details=None): """Report an error in test test. @@ -637,42 +648,42 @@ class TestProtocolClient(testresult.TestResult): :param details: New Testing-in-python drafted API; a dict from string to subunit.Content objects. """ - self._stream.write("%s: %s" % (outcome, test.id())) + self._stream.write(_b("%s: %s" % (outcome, test.id()))) if error is None and details is None: raise ValueError if error is not None: - self._stream.write(" [\n") + self._stream.write(self._start_simple) # XXX: this needs to be made much stricter, along the lines of # Martin[gz]'s work in testtools. Perhaps subunit can use that? for line in self._exc_info_to_unicode(error, test).splitlines(): self._stream.write(("%s\n" % line).encode('utf8')) else: self._write_details(details) - self._stream.write("]\n") + self._stream.write(self._end_simple) def addSkip(self, test, reason=None, details=None): """Report a skipped test.""" 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") + self._stream.write(_b("skip: %s [\n" % test.id())) + self._stream.write(_b("%s\n" % reason)) + self._stream.write(self._end_simple) def addSuccess(self, test, details=None): """Report a success in a test.""" - self._stream.write("successful: %s" % test.id()) + self._stream.write(_b("successful: %s" % test.id())) if not details: - self._stream.write("\n") + self._stream.write(_b("\n")) else: self._write_details(details) - self._stream.write("]\n") + self._stream.write(self._end_simple) addUnexpectedSuccess = addSuccess def startTest(self, test): """Mark a test as starting its test run.""" super(TestProtocolClient, self).startTest(test) - self._stream.write("test: %s\n" % test.id()) + self._stream.write(_b("test: %s\n" % test.id())) self._stream.flush() def stopTest(self, test): @@ -690,16 +701,19 @@ class TestProtocolClient(testresult.TestResult): PROGRESS_POP. """ if whence == PROGRESS_CUR and offset > -1: - prefix = "+" + prefix = self._progress_plus + offset = _b(str(offset)) elif whence == PROGRESS_PUSH: - prefix = "" - offset = "push" + prefix = self._empty_bytes + offset = self._progress_push elif whence == PROGRESS_POP: - prefix = "" - offset = "pop" + prefix = self._empty_bytes + offset = self._progress_pop else: - prefix = "" - self._stream.write("progress: %s%s\n" % (prefix, offset)) + prefix = self._empty_bytes + offset = _b(str(offset)) + self._stream.write(self._progress_fmt + prefix + offset + + self._bytes_eol) def time(self, a_datetime): """Inform the client of the time. @@ -707,29 +721,29 @@ class TestProtocolClient(testresult.TestResult): ":param datetime: A datetime.datetime object. """ time = a_datetime.astimezone(iso8601.Utc()) - self._stream.write("time: %04d-%02d-%02d %02d:%02d:%02d.%06dZ\n" % ( + self._stream.write(_b("time: %04d-%02d-%02d %02d:%02d:%02d.%06dZ\n" % ( time.year, time.month, time.day, time.hour, time.minute, - time.second, time.microsecond)) + 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") + self._stream.write(_b(" [ multipart\n")) for name, content in sorted(details.items()): - self._stream.write("Content-Type: %s/%s" % - (content.content_type.type, content.content_type.subtype)) + self._stream.write(_b("Content-Type: %s/%s" % + (content.content_type.type, content.content_type.subtype))) parameters = content.content_type.parameters if parameters: - self._stream.write(";") + self._stream.write(_b(";")) param_strs = [] for param, value in parameters.items(): param_strs.append("%s=%s" % (param, value)) - self._stream.write(",".join(param_strs)) - self._stream.write("\n%s\n" % name) + self._stream.write(_b(",".join(param_strs))) + self._stream.write(_b("\n%s\n" % name)) encoder = chunked.Encoder(self._stream) - map(encoder.write, content.iter_bytes()) + list(map(encoder.write, content.iter_bytes())) encoder.close() def done(self): diff --git a/python/subunit/chunked.py b/python/subunit/chunked.py index 5f8c6f1..b992129 100644 --- a/python/subunit/chunked.py +++ b/python/subunit/chunked.py @@ -17,6 +17,10 @@ """Encoder/decoder for http style chunked encoding.""" +from testtools.compat import _b + +empty = _b('') + class Decoder(object): """Decode chunked content to a byte stream.""" @@ -25,11 +29,11 @@ class Decoder(object): :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 + 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. + detected. The output object must accept bytes objects. :param strict: If True (the default), the decoder will not knowingly accept input that is not conformant to the HTTP specification. @@ -42,6 +46,11 @@ class Decoder(object): self.state = self._read_length self.body_length = 0 self.strict = strict + self._match_chars = _b("0123456789abcdefABCDEF\r\n") + self._slash_n = _b('\n') + self._slash_r = _b('\r') + self._slash_rn = _b('\r\n') + self._slash_nr = _b('\n\r') def close(self): """Close the decoder. @@ -56,7 +65,7 @@ class Decoder(object): if self.buffered_bytes: buffered_bytes = self.buffered_bytes self.buffered_bytes = [] - return ''.join(buffered_bytes) + return empty.join(buffered_bytes) else: raise ValueError("stream is finished") @@ -80,26 +89,26 @@ class Decoder(object): def _read_length(self): """Try to decode a length from the bytes.""" - match_chars = "0123456789abcdefABCDEF\r\n" count_chars = [] for bytes in self.buffered_bytes: - for byte in bytes: - if byte not in match_chars: + for pos in range(len(bytes)): + byte = bytes[pos:pos+1] + if byte not in self._match_chars: break count_chars.append(byte) - if byte == '\n': + if byte == self._slash_n: break if not count_chars: return - if count_chars[-1][-1] != '\n': + if count_chars[-1] != self._slash_n: return - count_str = ''.join(count_chars) + count_str = empty.join(count_chars) if self.strict: - if count_str[-2:] != '\r\n': + if count_str[-2:] != self._slash_rn: raise ValueError("chunk header invalid: %r" % count_str) - if '\r' in count_str[:-2]: + if self._slash_r in count_str[:-2]: raise ValueError("too many CRs in chunk header %r" % count_str) - self.body_length = int(count_str.rstrip('\n\r'), 16) + self.body_length = int(count_str.rstrip(self._slash_nr), 16) excess_bytes = len(count_str) while excess_bytes: if excess_bytes >= len(self.buffered_bytes[0]): @@ -112,7 +121,7 @@ class Decoder(object): self.state = self._finished if not self.buffered_bytes: # May not call into self._finished with no buffered data. - return '' + return empty else: self.state = self._read_body return self.state() @@ -155,9 +164,9 @@ class Encoder(object): buffer_size = self.buffer_size self.buffered_bytes = [] self.buffer_size = 0 - self.output.write("%X\r\n" % (buffer_size + extra_len)) + self.output.write(_b("%X\r\n" % (buffer_size + extra_len))) if buffer_size: - self.output.write(''.join(buffered_bytes)) + self.output.write(empty.join(buffered_bytes)) return True def write(self, bytes): @@ -173,4 +182,4 @@ class Encoder(object): def close(self): """Finish the stream. This does not close the output stream.""" self.flush() - self.output.write("0\r\n") + self.output.write(_b("0\r\n")) diff --git a/python/subunit/details.py b/python/subunit/details.py index 9790543..35bc88e 100644 --- a/python/subunit/details.py +++ b/python/subunit/details.py @@ -17,12 +17,13 @@ """Handlers for outcome details.""" from testtools import content, content_type -from testtools.compat import _b, StringIO +from testtools.compat import _b, BytesIO from subunit import chunked end_marker = _b("]\n") quoted_marker = _b(" ]") +empty = _b('') class DetailsParser(object): @@ -79,18 +80,18 @@ class MultipartDetailsParser(DetailsParser): self._parse_state = self._look_for_content def _look_for_content(self, line): - if line == "]\n": + if line == end_marker: self._state.endDetails() return # TODO error handling - field, value = line[:-1].split(' ', 1) + field, value = line[:-1].decode('utf8').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._name = line[:-1].decode('utf8') + self._body = BytesIO() self._chunk_parser = chunked.Decoder(self._body) self._parse_state = self._feed_chunks @@ -98,7 +99,7 @@ class MultipartDetailsParser(DetailsParser): residue = self._chunk_parser.write(line) if residue is not None: # Line based use always ends on no residue. - assert residue == '', 'residue: %r' % (residue,) + assert residue == empty, 'residue: %r' % (residue,) body = self._body self._details[self._name] = content.Content( self._content_type, lambda:[body.getvalue()]) diff --git a/python/subunit/iso8601.py b/python/subunit/iso8601.py index 93c92fb..cbe9a3b 100644 --- a/python/subunit/iso8601.py +++ b/python/subunit/iso8601.py @@ -31,15 +31,25 @@ datetime.datetime(2007, 1, 25, 12, 0, tzinfo=) from datetime import datetime, timedelta, tzinfo import re +import sys __all__ = ["parse_date", "ParseError"] # Adapted from http://delete.me.uk/2005/03/iso8601.html -ISO8601_REGEX = re.compile(r"(?P[0-9]{4})(-(?P[0-9]{1,2})(-(?P[0-9]{1,2})" +ISO8601_REGEX_PATTERN = (r"(?P[0-9]{4})(-(?P[0-9]{1,2})(-(?P[0-9]{1,2})" r"((?P.)(?P[0-9]{2}):(?P[0-9]{2})(:(?P[0-9]{2})(\.(?P[0-9]+))?)?" r"(?PZ|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?" ) -TIMEZONE_REGEX = re.compile("(?P[+-])(?P[0-9]{2}).(?P[0-9]{2})") +TIMEZONE_REGEX_PATTERN = "(?P[+-])(?P[0-9]{2}).(?P[0-9]{2})" +ISO8601_REGEX = re.compile(ISO8601_REGEX_PATTERN.encode('utf8')) +TIMEZONE_REGEX = re.compile(TIMEZONE_REGEX_PATTERN.encode('utf8')) + +zulu = "Z".encode('latin-1') +minus = "-".encode('latin-1') + +if sys.version_info < (3, 0): + bytes = str + class ParseError(Exception): """Raised when there is a problem parsing a date string""" @@ -84,7 +94,7 @@ def parse_timezone(tzstring, default_timezone=UTC): """Parses ISO 8601 time zone specs into tzinfo offsets """ - if tzstring == "Z": + if tzstring == zulu: return default_timezone # This isn't strictly correct, but it's common to encounter dates without # timezones so I'll assume the default (which defaults to UTC). @@ -94,7 +104,7 @@ def parse_timezone(tzstring, default_timezone=UTC): m = TIMEZONE_REGEX.match(tzstring) prefix, hours, minutes = m.groups() hours, minutes = int(hours), int(minutes) - if prefix == "-": + if prefix == minus: hours = -hours minutes = -minutes return FixedOffset(hours, minutes, tzstring) @@ -107,8 +117,8 @@ def parse_date(datestring, default_timezone=UTC): default timezone specified in default_timezone is used. This is UTC by default. """ - if not isinstance(datestring, basestring): - raise ParseError("Expecting a string %r" % datestring) + if not isinstance(datestring, bytes): + raise ParseError("Expecting bytes %r" % datestring) m = ISO8601_REGEX.match(datestring) if not m: raise ParseError("Unable to parse date string %r" % datestring) diff --git a/python/subunit/tests/test_chunked.py b/python/subunit/tests/test_chunked.py index 6323b02..e0742f1 100644 --- a/python/subunit/tests/test_chunked.py +++ b/python/subunit/tests/test_chunked.py @@ -17,7 +17,7 @@ import unittest -from testtools.compat import StringIO +from testtools.compat import _b, BytesIO import subunit.chunked @@ -32,121 +32,121 @@ class TestDecode(unittest.TestCase): def setUp(self): unittest.TestCase.setUp(self) - self.output = StringIO() + self.output = BytesIO() 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.assertEqual(None, self.decoder.write(_b('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.assertEqual(None, self.decoder.write(_b('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.assertEqual(None, self.decoder.write(_b('2\r\nab'))) + self.assertEqual(_b(''), self.decoder.write(_b('0\r\n'))) self.decoder.close() def test_decode_nothing(self): - self.assertEqual('', self.decoder.write('0\r\n')) - self.assertEqual('', self.output.getvalue()) + self.assertEqual(_b(''), self.decoder.write(_b('0\r\n'))) + self.assertEqual(_b(''), 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")) + self.assertEqual(None, self.decoder.write(_b("F\r\n"))) + self.assertEqual(None, self.decoder.write(_b("serialised\n"))) + self.assertEqual(_b(''), self.decoder.write(_b("form0\r\n"))) def test_decode_short(self): - self.assertEqual('', self.decoder.write('3\r\nabc0\r\n')) - self.assertEqual('abc', self.output.getvalue()) + self.assertEqual(_b(''), self.decoder.write(_b('3\r\nabc0\r\n'))) + self.assertEqual(_b('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()) + self.assertEqual(_b(''), self.decoder.write(_b('6\r\nabcdef0\r\n'))) + self.assertEqual(_b('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()) + self.assertEqual(_b('1234'), self.decoder.write(_b('3\r\nabc0\r\n1234'))) + self.assertEqual(_b('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, '') + self.assertEqual(_b('1234'), self.decoder.write(_b('3\r\nabc0\r\n1234'))) + self.assertRaises(ValueError, self.decoder.write, _b('')) def test_decode_hex(self): - self.assertEqual('', self.decoder.write('A\r\n12345678900\r\n')) - self.assertEqual('1234567890', self.output.getvalue()) + self.assertEqual(_b(''), self.decoder.write(_b('A\r\n12345678900\r\n'))) + self.assertEqual(_b('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()) + self.assertEqual(None, self.decoder.write(_b('10000\r\n'))) + self.assertEqual(None, self.decoder.write(_b('1' * 65536))) + self.assertEqual(None, self.decoder.write(_b('10000\r\n'))) + self.assertEqual(None, self.decoder.write(_b('2' * 65536))) + self.assertEqual(_b(''), self.decoder.write(_b('0\r\n'))) + self.assertEqual(_b('1' * 65536 + '2' * 65536), self.output.getvalue()) def test_decode_newline_nonstrict(self): """Tolerate chunk markers with no CR character.""" # From self.decoder = subunit.chunked.Decoder(self.output, strict=False) - self.assertEqual(None, self.decoder.write('a\n')) - self.assertEqual(None, self.decoder.write('abcdeabcde')) - self.assertEqual('', self.decoder.write('0\n')) - self.assertEqual('abcdeabcde', self.output.getvalue()) + self.assertEqual(None, self.decoder.write(_b('a\n'))) + self.assertEqual(None, self.decoder.write(_b('abcdeabcde'))) + self.assertEqual(_b(''), self.decoder.write(_b('0\n'))) + self.assertEqual(_b('abcdeabcde'), self.output.getvalue()) def test_decode_strict_newline_only(self): """Reject chunk markers with no CR character in strict mode.""" # From self.assertRaises(ValueError, - self.decoder.write, 'a\n') + self.decoder.write, _b('a\n')) def test_decode_strict_multiple_crs(self): self.assertRaises(ValueError, - self.decoder.write, 'a\r\r\n') + self.decoder.write, _b('a\r\r\n')) def test_decode_short_header(self): self.assertRaises(ValueError, - self.decoder.write, '\n') + self.decoder.write, _b('\n')) class TestEncode(unittest.TestCase): def setUp(self): unittest.TestCase.setUp(self) - self.output = StringIO() + self.output = BytesIO() self.encoder = subunit.chunked.Encoder(self.output) def test_encode_nothing(self): self.encoder.close() - self.assertEqual('0\r\n', self.output.getvalue()) + self.assertEqual(_b('0\r\n'), self.output.getvalue()) def test_encode_empty(self): - self.encoder.write('') + self.encoder.write(_b('')) self.encoder.close() - self.assertEqual('0\r\n', self.output.getvalue()) + self.assertEqual(_b('0\r\n'), self.output.getvalue()) def test_encode_short(self): - self.encoder.write('abc') + self.encoder.write(_b('abc')) self.encoder.close() - self.assertEqual('3\r\nabc0\r\n', self.output.getvalue()) + self.assertEqual(_b('3\r\nabc0\r\n'), self.output.getvalue()) def test_encode_combines_short(self): - self.encoder.write('abc') - self.encoder.write('def') + self.encoder.write(_b('abc')) + self.encoder.write(_b('def')) self.encoder.close() - self.assertEqual('6\r\nabcdef0\r\n', self.output.getvalue()) + self.assertEqual(_b('6\r\nabcdef0\r\n'), self.output.getvalue()) def test_encode_over_9_is_in_hex(self): - self.encoder.write('1234567890') + self.encoder.write(_b('1234567890')) self.encoder.close() - self.assertEqual('A\r\n12345678900\r\n', self.output.getvalue()) + self.assertEqual(_b('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.write(_b('1' * 65536)) + self.encoder.write(_b('2' * 65536)) self.encoder.close() - self.assertEqual('10000\r\n' + '1' * 65536 + '10000\r\n' + - '2' * 65536 + '0\r\n', self.output.getvalue()) + self.assertEqual(_b('10000\r\n' + '1' * 65536 + '10000\r\n' + + '2' * 65536 + '0\r\n'), self.output.getvalue()) diff --git a/python/subunit/tests/test_details.py b/python/subunit/tests/test_details.py index 49010d2..746aa04 100644 --- a/python/subunit/tests/test_details.py +++ b/python/subunit/tests/test_details.py @@ -95,18 +95,18 @@ class TestMultipartDetails(unittest.TestCase): 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") + parser.lineReceived(_b("Content-Type: text/plain\n")) + parser.lineReceived(_b("something\n")) + parser.lineReceived(_b("F\r\n")) + parser.lineReceived(_b("serialised\n")) + parser.lineReceived(_b("form0\r\n")) expected = {} expected['something'] = content.Content( content_type.ContentType("text", "plain"), - lambda:["serialised\nform"]) + lambda:[_b("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())) + self.assertEqual(_b('').join(expected['something'].iter_bytes()), + _b('').join(found['something'].iter_bytes())) diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 682f726..fb6ffcd 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -21,7 +21,7 @@ from subunit import iso8601 import unittest from testtools import TestCase -from testtools.compat import StringIO +from testtools.compat import _b, BytesIO, StringIO from testtools.testresult.doubles import ExtendedTestResult import subunit @@ -35,7 +35,7 @@ class TestTestResultFilter(TestCase): # 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. - example_subunit_stream = """\ + example_subunit_stream = _b("""\ tags: global test passed success passed @@ -50,7 +50,7 @@ test skipped skip skipped test todo xfail todo -""" +""") def run_tests(self, result_filter, input_stream=None): """Run tests through the given filter. @@ -61,7 +61,7 @@ xfail todo """ if input_stream is None: input_stream = self.example_subunit_stream - test = subunit.ProtocolTestCase(StringIO(input_stream)) + test = subunit.ProtocolTestCase(BytesIO(input_stream)) test.run(result_filter) def test_default(self): @@ -139,13 +139,13 @@ xfail todo date_a = datetime(year=2000, month=1, day=1, tzinfo=iso8601.UTC) date_b = datetime(year=2000, month=1, day=2, tzinfo=iso8601.UTC) date_c = datetime(year=2000, month=1, day=3, tzinfo=iso8601.UTC) - subunit_stream = '\n'.join([ + subunit_stream = _b('\n'.join([ "time: %s", "test: foo", "time: %s", "error: foo", "time: %s", - ""]) % (date_a, date_b, date_c) + ""]) % (date_a, date_b, date_c)) result = ExtendedTestResult() result_filter = TestResultFilter(result) self.run_tests(result_filter, subunit_stream) diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 7778fcc..7ec7758 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -28,7 +28,7 @@ from testtools.tests.helpers import ( ) import subunit -from subunit import _remote_exception_str +from subunit import _remote_exception_str, _remote_exception_str_chunked import subunit.iso8601 as iso8601 @@ -994,11 +994,11 @@ class TestIsolatedTestSuite(unittest.TestCase): class TestTestProtocolClient(unittest.TestCase): def setUp(self): - self.io = StringIO() + self.io = BytesIO() self.protocol = subunit.TestProtocolClient(self.io) self.test = TestTestProtocolClient("test_start_test") self.sample_details = {'something':Content( - ContentType('text', 'plain'), lambda:['serialised\nform'])} + ContentType('text', 'plain'), lambda:[_b('serialised\nform')])} self.sample_tb_details = dict(self.sample_details) self.sample_tb_details['traceback'] = TracebackContent( subunit.RemoteError(_u("boo qux")), self.test) @@ -1006,27 +1006,27 @@ class TestTestProtocolClient(unittest.TestCase): def test_start_test(self): """Test startTest on a TestProtocolClient.""" self.protocol.startTest(self.test) - self.assertEqual(self.io.getvalue(), "test: %s\n" % self.test.id()) + self.assertEqual(self.io.getvalue(), _b("test: %s\n" % self.test.id())) def test_stop_test(self): # stopTest doesn't output anything. self.protocol.stopTest(self.test) - self.assertEqual(self.io.getvalue(), "") + self.assertEqual(self.io.getvalue(), _b("")) def test_add_success(self): """Test addSuccess on a TestProtocolClient.""" self.protocol.addSuccess(self.test) self.assertEqual( - self.io.getvalue(), "successful: %s\n" % self.test.id()) + self.io.getvalue(), _b("successful: %s\n" % self.test.id())) def test_add_success_details(self): """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" + self.io.getvalue(), _b("successful: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" - "F\r\nserialised\nform0\r\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.""" @@ -1034,8 +1034,8 @@ class TestTestProtocolClient(unittest.TestCase): self.test, subunit.RemoteError(_u("boo qux"))) self.assertEqual( self.io.getvalue(), - ('failure: %s [\n' + _remote_exception_str + ': boo qux\n]\n') - % self.test.id()) + _b(('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.""" @@ -1043,14 +1043,13 @@ class TestTestProtocolClient(unittest.TestCase): self.test, details=self.sample_tb_details) self.assertEqual( self.io.getvalue(), - ("failure: %s [ multipart\n" + _b(("failure: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" "F\r\nserialised\nform0\r\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()) + "traceback\n" + _remote_exception_str_chunked + ": boo qux\n0\r\n" + "]\n") % self.test.id())) def test_add_error(self): """Test stopTest on a TestProtocolClient.""" @@ -1058,9 +1057,9 @@ class TestTestProtocolClient(unittest.TestCase): self.test, subunit.RemoteError(_u("phwoar crikey"))) self.assertEqual( self.io.getvalue(), - ('error: %s [\n' + + _b(('error: %s [\n' + _remote_exception_str + ": phwoar crikey\n" - "]\n") % self.test.id()) + "]\n") % self.test.id())) def test_add_error_details(self): """Test stopTest on a TestProtocolClient with details.""" @@ -1068,14 +1067,13 @@ class TestTestProtocolClient(unittest.TestCase): self.test, details=self.sample_tb_details) self.assertEqual( self.io.getvalue(), - ("error: %s [ multipart\n" + _b(("error: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" "F\r\nserialised\nform0\r\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()) + "traceback\n" + _remote_exception_str_chunked + ": boo qux\n0\r\n" + "]\n") % self.test.id())) def test_add_expected_failure(self): """Test addExpectedFailure on a TestProtocolClient.""" @@ -1083,9 +1081,9 @@ class TestTestProtocolClient(unittest.TestCase): self.test, subunit.RemoteError(_u("phwoar crikey"))) self.assertEqual( self.io.getvalue(), - ('xfail: %s [\n' + + _b(('xfail: %s [\n' + _remote_exception_str + ": phwoar crikey\n" - "]\n") % self.test.id()) + "]\n") % self.test.id())) def test_add_expected_failure_details(self): """Test addExpectedFailure on a TestProtocolClient with details.""" @@ -1093,14 +1091,14 @@ class TestTestProtocolClient(unittest.TestCase): self.test, details=self.sample_tb_details) self.assertEqual( self.io.getvalue(), - ("xfail: %s [ multipart\n" + _b(("xfail: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" "F\r\nserialised\nform0\r\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()) + "traceback\n" + _remote_exception_str_chunked + ": boo qux\n0\r\n" + "]\n") % self.test.id())) + def test_add_skip(self): """Test addSkip on a TestProtocolClient.""" @@ -1108,64 +1106,63 @@ class TestTestProtocolClient(unittest.TestCase): self.test, "Has it really?") self.assertEqual( self.io.getvalue(), - 'skip: %s [\nHas it really?\n]\n' % self.test.id()) + _b('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) + ContentType('text', 'plain'), lambda:[_b('Has it really?')])} + self.protocol.addSkip(self.test, details=details) self.assertEqual( self.io.getvalue(), - "skip: %s [ multipart\n" + _b("skip: %s [ multipart\n" "Content-Type: text/plain\n" "reason\n" "E\r\nHas it really?0\r\n" - "]\n" % self.test.id()) + "]\n" % self.test.id())) def test_progress_set(self): self.protocol.progress(23, subunit.PROGRESS_SET) - self.assertEqual(self.io.getvalue(), 'progress: 23\n') + self.assertEqual(self.io.getvalue(), _b('progress: 23\n')) def test_progress_neg_cur(self): self.protocol.progress(-23, subunit.PROGRESS_CUR) - self.assertEqual(self.io.getvalue(), 'progress: -23\n') + self.assertEqual(self.io.getvalue(), _b('progress: -23\n')) def test_progress_pos_cur(self): self.protocol.progress(23, subunit.PROGRESS_CUR) - self.assertEqual(self.io.getvalue(), 'progress: +23\n') + self.assertEqual(self.io.getvalue(), _b('progress: +23\n')) def test_progress_pop(self): self.protocol.progress(1234, subunit.PROGRESS_POP) - self.assertEqual(self.io.getvalue(), 'progress: pop\n') + self.assertEqual(self.io.getvalue(), _b('progress: pop\n')) def test_progress_push(self): self.protocol.progress(1234, subunit.PROGRESS_PUSH) - self.assertEqual(self.io.getvalue(), 'progress: push\n') + self.assertEqual(self.io.getvalue(), _b('progress: push\n')) def test_time(self): # Calling time() outputs a time signal immediately. self.protocol.time( datetime.datetime(2009,10,11,12,13,14,15, iso8601.Utc())) self.assertEqual( - "time: 2009-10-11 12:13:14.000015Z\n", + _b("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()) + self.io.getvalue(), _b("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" + self.io.getvalue(), _b("successful: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" - "F\r\nserialised\nform0\r\n]\n" % self.test.id()) + "F\r\nserialised\nform0\r\n]\n" % self.test.id())) def test_suite(): -- cgit v1.2.1 From 65ceb1a7645b132fb831344c0d0ddf986e93d6fe Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 25 Apr 2011 17:07:43 +1200 Subject: Test suite passing on 3.1. --- python/subunit/__init__.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 8fb3ab7..a7048d3 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -468,10 +468,9 @@ class TestProtocolServer(object): """ self.client = ExtendedToOriginalDecorator(client) if stream is None: + stream = sys.stdout if sys.version_info > (3, 0): - stream = sys.stdout.buffer - else: - stream = sys.stdout + stream = stream.buffer self._stream = stream self._forward_stream = forward_stream or DiscardStream() # state objects we can switch too @@ -885,10 +884,10 @@ def run_isolated(klass, self, result): # at this point, sys.stdin is redirected, now we want # to filter it to escape ]'s. ### XXX: test and write that bit. - - result = TestProtocolClient(sys.stdout) + stream = os.fdopen(1, 'wb') + result = TestProtocolClient(stream) klass.run(self, result) - sys.stdout.flush() + stream.flush() sys.stderr.flush() # exit HARD, exit NOW. os._exit(0) @@ -898,7 +897,8 @@ def run_isolated(klass, self, result): os.close(c2pwrite) # hookup a protocol engine protocol = TestProtocolServer(result) - protocol.readFrom(os.fdopen(c2pread, 'rU')) + fileobj = os.fdopen(c2pread, 'rb') + protocol.readFrom(fileobj) os.waitpid(pid, 0) # TODO return code evaluation. return result @@ -1168,7 +1168,10 @@ def get_default_formatter(): if formatter: return os.popen(formatter, "w") else: - return sys.stdout + stream = sys.stdout + if sys.version_info > (3, 0): + stream = stream.buffer + return stream if sys.version_info > (3, 0): -- cgit v1.2.1 From 2950580814e220313b862e13ea40f3ac8163601c Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Mon, 25 Apr 2011 22:32:49 +0200 Subject: Remove TagCollapsingDecator test. --- python/subunit/tests/test_test_results.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index 23b8b5e..94d2274 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -229,11 +229,6 @@ class TestTagCollapsingDecorator(TestCase): ('stopTest', test)], result._events) - def test_tags_collapsed_tests_run(self): - result = ExtendedTestResult() - tag_collapser = subunit.test_results.TagCollapsingDecorator(result) - tag_collapser.testsRun += 1 - class TestTimeCollapsingDecorator(TestCase): -- cgit v1.2.1 From 137f492db934863d0352e46aa00486b8f8d7c806 Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Mon, 25 Apr 2011 22:44:33 +0200 Subject: Move read_test_filter to python/subunit. --- python/subunit/__init__.py | 13 +++++++++++++ python/subunit/tests/test_details.py | 1 - 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 9dc849a..368d3b2 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -1132,6 +1132,19 @@ def get_default_formatter(): return sys.stdout +def read_test_list(path): + """Read a list of test ids from a file on disk. + + :param path: Path to the file + :return: Sequence of test ids + """ + f = open(path, 'rb') + try: + return [l.rstrip("\n") for l in f.readlines()] + finally: + f.close() + + def _make_stream_binary(stream): """Ensure that a stream will be binary safe. See _make_binary_on_windows.""" if getattr(stream, 'fileno', None) is not None: diff --git a/python/subunit/tests/test_details.py b/python/subunit/tests/test_details.py index 41c3212..2fd1a66 100644 --- a/python/subunit/tests/test_details.py +++ b/python/subunit/tests/test_details.py @@ -14,7 +14,6 @@ # limitations under that license. # -from cStringIO import StringIO import unittest import subunit.tests -- cgit v1.2.1 From 7ce9deb0fe988b194c577b778b722f079a272d74 Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Mon, 25 Apr 2011 23:08:53 +0200 Subject: Split out method that checks for expected failures into a separate method. --- python/subunit/test_results.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index a3ad928..fe5b8d1 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -339,7 +339,7 @@ class TestResultFilter(TestResultDecorator): def addError(self, test, err=None, details=None): if (self.filter_predicate(test, 'error', err, details)): - if test.id() in self._fixup_expected_failures: + if self._failure_expected(test): self._buffered_calls.append( ('addExpectedFailure', [test, err], {'details': details})) else: @@ -350,7 +350,7 @@ class TestResultFilter(TestResultDecorator): def addFailure(self, test, err=None, details=None): if (self.filter_predicate(test, 'failure', err, details)): - if test.id() in self._fixup_expected_failures: + if self._failure_expected(test): self._buffered_calls.append( ('addExpectedFailure', [test, err], {'details': details})) else: @@ -368,7 +368,7 @@ class TestResultFilter(TestResultDecorator): def addSuccess(self, test, details=None): if (self.filter_predicate(test, 'success', None, details)): - if test.id() in self._fixup_expected_failures: + if self._failure_expected(test): self._buffered_calls.append( ('addUnexpectedSuccess', [test], {'details': details})) else: @@ -391,6 +391,9 @@ class TestResultFilter(TestResultDecorator): def _filtered(self): self._current_test_filtered = True + def _failure_expected(self, test): + return (test.id() in self._fixup_expected_failures) + def startTest(self, test): """Start a test. -- cgit v1.2.1 From 4b6abb5335d793f842abaab3121276c7737481a0 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 25 Apr 2011 22:26:51 +0100 Subject: Avoid leaking test output in TestTestProtocolServerStartTest.test_indented_test_colon_ignored --- python/subunit/tests/test_test_protocol.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 7ec7758..d8af230 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -107,7 +107,8 @@ class TestTestProtocolServerStartTest(unittest.TestCase): def setUp(self): self.client = Python26TestResult() - self.protocol = subunit.TestProtocolServer(self.client) + self.stream = BytesIO() + self.protocol = subunit.TestProtocolServer(self.client, self.stream) def test_start_test(self): self.protocol.lineReceived(_b("test old mcdonald\n")) @@ -125,8 +126,10 @@ class TestTestProtocolServerStartTest(unittest.TestCase): [('startTest', subunit.RemotedTestCase("old mcdonald"))]) def test_indented_test_colon_ignored(self): - self.protocol.lineReceived(_b(" test: old mcdonald\n")) + ignored_line = _b(" test: old mcdonald\n") + self.protocol.lineReceived(ignored_line) self.assertEqual([], self.client._events) + self.assertEqual(self.stream.getvalue(), ignored_line) def test_start_testing_colon(self): self.protocol.lineReceived(_b("testing: old mcdonald\n")) -- cgit v1.2.1 From 5a28b540845585f07ec1f10b96305c77f63104dd Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 27 Apr 2011 02:25:24 +0100 Subject: Hack ExecTestCase and its tests to sort-of be portable fixing the remaing spew --- python/subunit/__init__.py | 8 ++++++-- python/subunit/tests/sample-script.py | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index a7048d3..82864f7 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -817,6 +817,8 @@ class ExecTestCase(unittest.TestCase): """ unittest.TestCase.__init__(self, methodName) testMethod = getattr(self, methodName) + # GZ 2011-04-25: Embedding shell commands in docstrings is fragile and + # and may break if the module directory contains spaces self.script = join_dir(sys.modules[self.__class__.__module__].__file__, testMethod.__doc__) @@ -833,8 +835,10 @@ class ExecTestCase(unittest.TestCase): def _run(self, result): protocol = TestProtocolServer(result) - output = subprocess.Popen(self.script, shell=True, - stdout=subprocess.PIPE).communicate()[0] + process = subprocess.Popen(self.script, shell=True, + stdout=subprocess.PIPE) + _make_stream_binary(process.stdout) + output = process.communicate()[0] protocol.readFrom(BytesIO(output)) diff --git a/python/subunit/tests/sample-script.py b/python/subunit/tests/sample-script.py index 0ee019a..618e495 100755 --- a/python/subunit/tests/sample-script.py +++ b/python/subunit/tests/sample-script.py @@ -1,5 +1,8 @@ #!/usr/bin/env python import sys +if sys.platform == "win32": + import msvcrt, os + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) if len(sys.argv) == 2: # subunit.tests.test_test_protocol.TestExecTestCase.test_sample_method_args # uses this code path to be sure that the arguments were passed to -- cgit v1.2.1 From 1d41987d8fb9da38eea2e99c4f41c0840194ef0f Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 27 Apr 2011 02:27:48 +0100 Subject: Skip os.fork tests on non-posix systems, by switching to testtools testcases there --- python/subunit/tests/test_test_protocol.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index d8af230..1b1aa1d 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -21,6 +21,7 @@ import os from testtools.compat import _b, _u, BytesIO, StringIO from testtools.content import Content, TracebackContent from testtools.content_type import ContentType +from testtools.testcase import skipIf, TestCase from testtools.tests.helpers import ( Python26TestResult, Python27TestResult, @@ -922,7 +923,7 @@ class DoExecTestCase(subunit.ExecTestCase): """sample-two-script.py""" -class TestIsolatedTestCase(unittest.TestCase): +class TestIsolatedTestCase(TestCase): class SampleIsolatedTestCase(subunit.IsolatedTestCase): @@ -943,6 +944,7 @@ class TestIsolatedTestCase(unittest.TestCase): def test_construct(self): self.SampleIsolatedTestCase("test_sets_global_state") + @skipIf(os.name != "posix", "Need a posix system for forking tests") def test_run(self): result = unittest.TestResult() test = self.SampleIsolatedTestCase("test_sets_global_state") @@ -958,7 +960,7 @@ class TestIsolatedTestCase(unittest.TestCase): #test.debug() -class TestIsolatedTestSuite(unittest.TestCase): +class TestIsolatedTestSuite(TestCase): class SampleTestToIsolate(unittest.TestCase): @@ -979,6 +981,7 @@ class TestIsolatedTestSuite(unittest.TestCase): def test_construct(self): subunit.IsolatedTestSuite() + @skipIf(os.name != "posix", "Need a posix system for forking tests") def test_run(self): result = unittest.TestResult() suite = subunit.IsolatedTestSuite() -- cgit v1.2.1 From 43cc336e58f81cd4fdb1692233980110bbc80542 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 27 Apr 2011 02:32:23 +0100 Subject: Fix join_dir test by being more specific about what constitutes a match --- python/subunit/tests/test_test_protocol.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 1b1aa1d..e0fa7ef 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -913,7 +913,8 @@ class TestExecTestCase(unittest.TestCase): def test_join_dir(self): sibling = subunit.join_dir(__file__, 'foo') - expected = '%s/foo' % (os.path.split(__file__)[0],) + filedir = os.path.abspath(os.path.dirname(__file__)) + expected = os.path.join(filedir, 'foo') self.assertEqual(sibling, expected) -- cgit v1.2.1 From 8a5727cd20a1a7ecb2050fce2977362e6b4aa068 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 2 May 2011 10:21:32 +1200 Subject: Additionally the protocol now has a keyword uxsuccess for Unexpected Success reporting. Older parsers will report tests with this status code as 'lost connection'. * The Python2.7 / testtools addUnexpectedSuccess API is now supported. This required adding a new status code to the protocol. (Robert Collins, #654474) --- python/subunit/__init__.py | 67 ++++++++++++--- python/subunit/tests/test_test_protocol.py | 126 ++++++++++++++++++++++++++++- 2 files changed, 178 insertions(+), 15 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index e92eb26..5d59e2b 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -201,6 +201,7 @@ class _ParserState(object): self._tags_sym = (_b('tags'),) self._time_sym = (_b('time'),) self._xfail_sym = (_b('xfail'),) + self._uxsuccess_sym = (_b('uxsuccess'),) self._start_simple = _u(" [") self._start_multipart = _u(" [ multipart") @@ -251,6 +252,8 @@ class _ParserState(object): self.parser.subunitLineReceived(line) elif cmd in self._xfail_sym: self.addExpectedFail(offset, line) + elif cmd in self._uxsuccess_sym: + self.addUnexpectedSuccess(offset, line) else: self.parser.stdOutLineReceived(line) else: @@ -314,6 +317,14 @@ class _InTest(_ParserState): self._outcome(offset, line, self._xfail, self.parser._reading_xfail_details) + def _uxsuccess(self): + self.parser.client.addUnexpectedSuccess(self.parser._current_test) + + def addUnexpectedSuccess(self, offset, line): + """A 'uxsuccess:' directive has been read.""" + self._outcome(offset, line, self._uxsuccess, + self.parser._reading_uxsuccess_details) + def _failure(self): self.parser.client.addFailure(self.parser._current_test, details={}) @@ -425,6 +436,17 @@ class _ReadingExpectedFailureDetails(_ReadingDetails): return "xfail" +class _ReadingUnexpectedSuccessDetails(_ReadingDetails): + """State for the subunit parser when reading uxsuccess details.""" + + def _report_outcome(self): + self.parser.client.addUnexpectedSuccess(self.parser._current_test, + details=self.details_parser.get_details()) + + def _outcome_label(self): + return "uxsuccess" + + class _ReadingSkipDetails(_ReadingDetails): """State for the subunit parser when reading skip details.""" @@ -481,6 +503,7 @@ class TestProtocolServer(object): self._reading_skip_details = _ReadingSkipDetails(self) self._reading_success_details = _ReadingSuccessDetails(self) self._reading_xfail_details = _ReadingExpectedFailureDetails(self) + self._reading_uxsuccess_details = _ReadingUnexpectedSuccessDetails(self) # start with outside test. self._state = self._outside_test # Avoid casts on every call @@ -632,7 +655,8 @@ class TestProtocolClient(testresult.TestResult): """ self._addOutcome("failure", test, error=error, details=details) - def _addOutcome(self, outcome, test, error=None, details=None): + def _addOutcome(self, outcome, test, error=None, details=None, + error_permitted=True): """Report a failure in test test. Only one of error and details should be provided: conceptually there @@ -646,19 +670,28 @@ class TestProtocolClient(testresult.TestResult): exc_info tuple. :param details: New Testing-in-python drafted API; a dict from string to subunit.Content objects. - """ + :param error_permitted: If True then one and only one of error or + details must be supplied. If False then error must not be supplied + and details is still optional. """ self._stream.write(_b("%s: %s" % (outcome, test.id()))) - if error is None and details is None: - raise ValueError + if error_permitted: + if error is None and details is None: + raise ValueError + else: + if error is not None: + raise ValueError if error is not None: self._stream.write(self._start_simple) # XXX: this needs to be made much stricter, along the lines of # Martin[gz]'s work in testtools. Perhaps subunit can use that? for line in self._exc_info_to_unicode(error, test).splitlines(): self._stream.write(("%s\n" % line).encode('utf8')) - else: + elif details is not None: self._write_details(details) - self._stream.write(self._end_simple) + else: + self._stream.write(_b("\n")) + if details or error: + self._stream.write(self._end_simple) def addSkip(self, test, reason=None, details=None): """Report a skipped test.""" @@ -671,13 +704,21 @@ class TestProtocolClient(testresult.TestResult): def addSuccess(self, test, details=None): """Report a success in a test.""" - self._stream.write(_b("successful: %s" % test.id())) - if not details: - self._stream.write(_b("\n")) - else: - self._write_details(details) - self._stream.write(self._end_simple) - addUnexpectedSuccess = addSuccess + self._addOutcome("successful", test, details=details, error_permitted=False) + + def addUnexpectedSuccess(self, test, details=None): + """Report an unexpected success in test test. + + Details can optionally be provided: conceptually there + are two separate methods: + addError(self, test) + addError(self, test, details) + + :param details: New Testing-in-python drafted API; a dict from string + to subunit.Content objects. + """ + self._addOutcome("uxsuccess", test, details=details, + error_permitted=False) 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 7ec7758..1cf5f13 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -18,6 +18,7 @@ import datetime import unittest import os +from testtools import TestCase from testtools.compat import _b, _u, BytesIO, StringIO from testtools.content import Content, TracebackContent from testtools.content_type import ContentType @@ -366,6 +367,12 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): def test_lost_connection_during_xfail_details(self): self.do_connection_lost("xfail", "[ multipart\n") + def test_lost_connection_during_uxsuccess(self): + self.do_connection_lost("uxsuccess", "[\n") + + def test_lost_connection_during_uxsuccess_details(self): + self.do_connection_lost("uxsuccess", "[ multipart\n") + class TestInTestMultipart(unittest.TestCase): @@ -607,6 +614,121 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): self.xfail_quoted_bracket("xfail:", False) +class TestTestProtocolServerAddunexpectedSuccess(TestCase): + """Tests for the uxsuccess keyword.""" + + def capture_expected_failure(self, test, err): + self._events.append((test, err)) + + def setup_python26(self): + """Setup a test object ready to be xfailed and thunk to success.""" + self.client = Python26TestResult() + self.setup_protocol() + + def setup_python27(self): + """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) + self.protocol.lineReceived(_b("test mcdonalds farm\n")) + self.test = self.client._events[-1][-1] + + def simple_uxsuccess_keyword(self, keyword, as_fail): + self.protocol.lineReceived(_b("%s mcdonalds farm\n" % keyword)) + self.check_fail_or_uxsuccess(as_fail) + + def check_fail_or_uxsuccess(self, as_fail, error_message=None): + details = {} + if error_message is not None: + details['traceback'] = Content( + ContentType("text", "x-traceback", {'charset': 'utf8'}), + lambda:[_b(error_message)]) + if isinstance(self.client, ExtendedTestResult): + value = details + else: + value = None + if as_fail: + self.client._events[1] = self.client._events[1][:2] + # The value is generated within the extended to original decorator: + # todo use the testtools matcher to check on this. + self.assertEqual([ + ('startTest', self.test), + ('addFailure', self.test), + ('stopTest', self.test), + ], self.client._events) + elif value: + self.assertEqual([ + ('startTest', self.test), + ('addUnexpectedSuccess', self.test, value), + ('stopTest', self.test), + ], self.client._events) + else: + self.assertEqual([ + ('startTest', self.test), + ('addUnexpectedSuccess', self.test), + ('stopTest', self.test), + ], self.client._events) + + def test_simple_uxsuccess(self): + self.setup_python26() + self.simple_uxsuccess_keyword("uxsuccess", True) + self.setup_python27() + self.simple_uxsuccess_keyword("uxsuccess", False) + self.setup_python_ex() + self.simple_uxsuccess_keyword("uxsuccess", False) + + def test_simple_uxsuccess_colon(self): + self.setup_python26() + self.simple_uxsuccess_keyword("uxsuccess:", True) + self.setup_python27() + self.simple_uxsuccess_keyword("uxsuccess:", False) + self.setup_python_ex() + self.simple_uxsuccess_keyword("uxsuccess:", False) + + def test_uxsuccess_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_fail, error_message="\n"): + self.protocol.lineReceived(_b("uxsuccess mcdonalds farm [\n")) + self.protocol.lineReceived(_b("]\n")) + self.check_fail_or_uxsuccess(as_fail, error_message) + + def uxsuccess_quoted_bracket(self, keyword, as_fail): + self.protocol.lineReceived(_b("%s mcdonalds farm [\n" % keyword)) + self.protocol.lineReceived(_b(" ]\n")) + self.protocol.lineReceived(_b("]\n")) + self.check_fail_or_uxsuccess(as_fail, "]\n") + + def test_uxsuccess_quoted_bracket(self): + self.setup_python26() + self.uxsuccess_quoted_bracket("uxsuccess", True) + self.setup_python27() + self.uxsuccess_quoted_bracket("uxsuccess", False) + self.setup_python_ex() + self.uxsuccess_quoted_bracket("uxsuccess", False) + + def test_uxsuccess_colon_quoted_bracket(self): + self.setup_python26() + self.uxsuccess_quoted_bracket("uxsuccess:", True) + self.setup_python27() + self.uxsuccess_quoted_bracket("uxsuccess:", False) + self.setup_python_ex() + self.uxsuccess_quoted_bracket("uxsuccess:", False) + + class TestTestProtocolServerAddSkip(unittest.TestCase): """Tests for the skip keyword. @@ -1153,13 +1275,13 @@ class TestTestProtocolClient(unittest.TestCase): """Test addUnexpectedSuccess on a TestProtocolClient.""" self.protocol.addUnexpectedSuccess(self.test) self.assertEqual( - self.io.getvalue(), _b("successful: %s\n" % self.test.id())) + self.io.getvalue(), _b("uxsuccess: %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(), _b("successful: %s [ multipart\n" + self.io.getvalue(), _b("uxsuccess: %s [ multipart\n" "Content-Type: text/plain\n" "something\n" "F\r\nserialised\nform0\r\n]\n" % self.test.id())) -- cgit v1.2.1 From 098f465f5547aa75379e4e8b0b067d22fa7f34b3 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 2 May 2011 11:06:27 +1200 Subject: Support testsRun on the tag and time collapsing decorators. --- python/subunit/test_results.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index 6b43df1..e3240e0 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -82,6 +82,10 @@ class TestResultDecorator(object): def stop(self): return self.decorated.stop() + @property + def testsRun(self): + return self.decorated.testsRun + def tags(self, new_tags, gone_tags): return self.decorated.tags(new_tags, gone_tags) @@ -201,8 +205,6 @@ class TagCollapsingDecorator(TestResultDecorator): def __init__(self, result): super(TagCollapsingDecorator, self).__init__(result) - # The current test (for filtering tags) - self._current_test = None # The (new, gone) tags for the current test. self._current_test_tags = None @@ -213,7 +215,6 @@ class TagCollapsingDecorator(TestResultDecorator): correctly. """ self.decorated.startTest(test) - self._current_test = test self._current_test_tags = set(), set() def stopTest(self, test): @@ -226,7 +227,6 @@ class TagCollapsingDecorator(TestResultDecorator): 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_tags = None def tags(self, new_tags, gone_tags): @@ -238,7 +238,7 @@ class TagCollapsingDecorator(TestResultDecorator): :param new_tags: Tags to add, :param gone_tags: Tags to remove. """ - if self._current_test is not None: + if self._current_test_tags 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) -- cgit v1.2.1 From 29334d4de556ca329e89fb4a34f97c63c095b135 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 2 May 2011 11:17:36 +1200 Subject: Fix thinko in addSuccess refactoring. --- python/subunit/__init__.py | 2 +- python/subunit/details.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 9d5d075..414b368 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -690,7 +690,7 @@ class TestProtocolClient(testresult.TestResult): self._write_details(details) else: self._stream.write(_b("\n")) - if details or error: + if details is not None or error is not None: self._stream.write(self._end_simple) def addSkip(self, test, reason=None, details=None): diff --git a/python/subunit/details.py b/python/subunit/details.py index 35bc88e..9e5e005 100644 --- a/python/subunit/details.py +++ b/python/subunit/details.py @@ -85,7 +85,10 @@ class MultipartDetailsParser(DetailsParser): return # TODO error handling field, value = line[:-1].decode('utf8').split(' ', 1) - main, sub = value.split('/') + try: + main, sub = value.split('/') + except ValueError: + raise ValueError("Invalid MIME type %r" % value) self._content_type = content_type.ContentType(main, sub) self._parse_state = self._get_name -- cgit v1.2.1 From b8eb299487248e8ca1a53b2eeebe103b2757899b Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Mon, 9 May 2011 19:47:24 +0200 Subject: Fix argument in TestResultFilter.addSkip. --- python/subunit/test_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index e3240e0..f312416 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -363,7 +363,7 @@ class TestResultFilter(TestResultDecorator): def addSkip(self, test, reason=None, details=None): if (self.filter_predicate(test, 'skip', reason, details)): self._buffered_calls.append( - ('addSkip', [reason], {'details': details})) + ('addSkip', [test, reason], {'details': details})) else: self._filtered() -- cgit v1.2.1 From d7f31020565d999ac02e460401068d3f7c70568d Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Mon, 9 May 2011 23:00:42 +0200 Subject: Add test to make sure addSkip is preserved. --- python/subunit/tests/test_subunit_filter.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'python') diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 87d4209..0675484 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -187,6 +187,20 @@ xfail todo ('stopTest', foo), ('time', date_c)], result._events) + def test_skip_preserved(self): + subunit_stream = _b('\n'.join([ + "test: foo", + "skip: foo", + ""])) + result = ExtendedTestResult() + result_filter = TestResultFilter(result) + self.run_tests(result_filter, subunit_stream) + foo = subunit.RemotedTestCase('foo') + self.assertEquals( + [('startTest', foo), + ('addSkip', foo, {}), + ('stopTest', foo), ], result._events) + def test_suite(): loader = subunit.tests.TestUtil.TestLoader() -- cgit v1.2.1 From bd71cf5f64f746a4d49d263b363668b6327ee12c Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 23 May 2011 21:57:58 +1200 Subject: * ``subunit-ls`` now handles a stream with time: instructions that start partway through the stream (which may lead to strange times) more gracefully. (Robert Collins, #785954) --- python/subunit/test_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index f312416..fd54b00 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -439,7 +439,7 @@ class TestIdPrintingResult(testtools.TestResult): super(TestIdPrintingResult, self).__init__() self._stream = stream self.failed_tests = 0 - self.__time = 0 + self.__time = None self.show_times = show_times self._test = None self._test_duration = 0 -- cgit v1.2.1 From dc1985ebe6751f309abd605a14c3fddb0b2600b9 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 23 May 2011 22:01:39 +1200 Subject: * ``subunit-ls`` should handle the new test outcomes in Python2.7 better. (Robert Collins, #785953) --- python/subunit/test_results.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'python') diff --git a/python/subunit/test_results.py b/python/subunit/test_results.py index fd54b00..9f64544 100644 --- a/python/subunit/test_results.py +++ b/python/subunit/test_results.py @@ -455,6 +455,16 @@ class TestIdPrintingResult(testtools.TestResult): def addSuccess(self, test): self._test = test + def addSkip(self, test, reason=None, details=None): + self._test = test + + def addUnexpectedSuccess(self, test, details=None): + self.failed_tests += 1 + self._test = test + + def addExpectedFailure(self, test, err=None, details=None): + self._test = test + def reportTest(self, test, duration): if self.show_times: seconds = duration.seconds -- cgit v1.2.1 From 9ce3f298dbe17b500879a32b53f440635d522b0f Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Tue, 26 Jul 2011 22:58:00 +0100 Subject: Fix Python 3 syntax error. --- python/subunit/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python') diff --git a/python/subunit/run.py b/python/subunit/run.py index b390de3..51d6837 100755 --- a/python/subunit/run.py +++ b/python/subunit/run.py @@ -49,7 +49,7 @@ class SubunitTestProgram(TestProgram): def usageExit(self, msg=None): if msg: - print msg + print (msg) usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '', 'buffer': ''} if self.failfast != False: -- cgit v1.2.1 From fcd3881178765d82d57970f47e57185352af0b9a Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 7 Aug 2011 17:29:59 +0100 Subject: Couple of spelling corrections to python subunit module docstring --- python/subunit/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index 414b368..b4c9397 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -59,12 +59,12 @@ and newer). 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 test is in progress (that is, if called outside of the ``startTest``, -``stopTest`` pair), the the tags apply to all sebsequent tests. If called +``stopTest`` pair), the the tags apply to all subsequent tests. If called when a test is in progress, then the tags only apply to that test. The ``time(a_datetime)`` method is called (if present) when a ``time:`` directive is encountered in a Subunit stream. This is used to tell a TestResult -about the time that events in the stream occured at, to allow reconstructing +about the time that events in the stream occurred at, to allow reconstructing test timing from a stream. The ``progress(offset, whence)`` method controls progress data for a stream. -- cgit v1.2.1 From 7384e95b6e49ea544739abfdd52e846eab292f69 Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Thu, 13 Oct 2011 02:16:57 +0200 Subject: Newer versions of testtools have {Python26,Python27,Extended}TestResult in testtools.testresult.doubles. --- python/subunit/tests/test_test_protocol.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 03d921a..c93aabd 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -22,11 +22,18 @@ from testtools import skipIf, TestCase from testtools.compat import _b, _u, BytesIO, StringIO from testtools.content import Content, TracebackContent from testtools.content_type import ContentType -from testtools.tests.helpers import ( - Python26TestResult, - Python27TestResult, - ExtendedTestResult, - ) +try: + from testtools.testresult.doubles import ( + Python26TestResult, + Python27TestResult, + ExtendedTestResult, + ) +except ImportError: + from testtools.tests.helpers import ( + Python26TestResult, + Python27TestResult, + ExtendedTestResult, + ) import subunit from subunit import _remote_exception_str, _remote_exception_str_chunked -- cgit v1.2.1 From 5c3f13ee9f2d195b605a1b3522fb00e8d5e79786 Mon Sep 17 00:00:00 2001 From: James Westby Date: Tue, 1 Nov 2011 11:59:58 -0400 Subject: Have the output of subunit.run include timing information. --- python/subunit/run.py | 2 ++ python/subunit/tests/__init__.py | 2 ++ python/subunit/tests/test_run.py | 53 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 python/subunit/tests/test_run.py (limited to 'python') diff --git a/python/subunit/run.py b/python/subunit/run.py index 51d6837..ca5fe5c 100755 --- a/python/subunit/run.py +++ b/python/subunit/run.py @@ -23,6 +23,7 @@ import sys from subunit import TestProtocolClient, get_default_formatter +from subunit.test_results import AutoTimingTestResultDecorator from testtools.run import ( BUFFEROUTPUT, CATCHBREAK, @@ -39,6 +40,7 @@ class SubunitTestRunner(object): def run(self, test): "Run the given test case or test suite." result = TestProtocolClient(self.stream) + result = AutoTimingTestResultDecorator(result) test(result) return result diff --git a/python/subunit/tests/__init__.py b/python/subunit/tests/__init__.py index a78cec8..e0e1eb1 100644 --- a/python/subunit/tests/__init__.py +++ b/python/subunit/tests/__init__.py @@ -19,6 +19,7 @@ from subunit.tests import ( test_chunked, test_details, test_progress_model, + test_run, test_subunit_filter, test_subunit_stats, test_subunit_tags, @@ -38,4 +39,5 @@ def test_suite(): result.addTest(test_subunit_filter.test_suite()) result.addTest(test_subunit_tags.test_suite()) result.addTest(test_subunit_stats.test_suite()) + result.addTest(test_run.test_suite()) return result diff --git a/python/subunit/tests/test_run.py b/python/subunit/tests/test_run.py new file mode 100644 index 0000000..e695703 --- /dev/null +++ b/python/subunit/tests/test_run.py @@ -0,0 +1,53 @@ +# +# subunit: extensions to python unittest to get test results from subprocesses. +# Copyright (C) 2005 Robert Collins +# Copyright (C) 2011 Martin Pool +# +# 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 + +from testtools import PlaceHolder + +import subunit +from subunit.run import SubunitTestRunner + + +def test_suite(): + loader = subunit.tests.TestUtil.TestLoader() + result = loader.loadTestsFromName(__name__) + return result + + +class TimeCollectingTestResult(unittest.TestResult): + + def __init__(self, *args, **kwargs): + super(TimeCollectingTestResult, self).__init__(*args, **kwargs) + self.time_called = [] + + def time(self, a_time): + self.time_called.append(a_time) + + +class TestSubunitTestRunner(unittest.TestCase): + + def test_includes_timing_output(self): + io = StringIO() + runner = SubunitTestRunner(stream=io) + test = PlaceHolder('name') + runner.run(test) + client = TimeCollectingTestResult() + io.seek(0) + subunit.TestProtocolServer(client).readFrom(io) + self.assertTrue(len(client.time_called) > 0) -- cgit v1.2.1 From 927d6ffbfec0ea269c9dd477d3053b6ff0e73600 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Tue, 1 Nov 2011 12:40:48 -0400 Subject: Copyright update. --- python/subunit/tests/test_run.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_run.py b/python/subunit/tests/test_run.py index e695703..5a96bcf 100644 --- a/python/subunit/tests/test_run.py +++ b/python/subunit/tests/test_run.py @@ -1,7 +1,6 @@ # # subunit: extensions to python unittest to get test results from subprocesses. -# Copyright (C) 2005 Robert Collins -# Copyright (C) 2011 Martin Pool +# Copyright (C) 2011 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 -- cgit v1.2.1 From ec629cf047c0f4e1d2d51d30a3d72e5437017936 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Wed, 11 Jan 2012 18:32:22 +1300 Subject: Handle latest testtools changes. --- python/subunit/tests/test_test_protocol.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'python') diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index c93aabd..019c080 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -102,9 +102,9 @@ class TestTestProtocolServerPipe(unittest.TestCase): [(an_error, _remote_exception_str + '\n')]) self.assertEqual( client.failures, - [(bing, _remote_exception_str + ": Text attachment: traceback\n" - "------------\nfoo.c:53:ERROR invalid state\n" - "------------\n\n")]) + [(bing, _remote_exception_str + + ": foo.c:53:ERROR invalid state\n" + "\n")]) self.assertEqual(client.testsRun, 3) def test_non_test_characters_forwarded_immediately(self): @@ -559,9 +559,9 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): value = details else: if error_message is not None: - value = subunit.RemoteError(_u("Text attachment: traceback\n" - "------------\n") + _u(error_message) + - _u("------------\n")) + if not len(error_message.strip()): + error_message = _u("Empty attachments:\n traceback\n") + value = subunit.RemoteError(_u(error_message)) else: value = subunit.RemoteError() self.assertEqual([ -- cgit v1.2.1 From 25581a35bb8b7468dd0e3ada1f5b20555e8eab30 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Wed, 11 Jan 2012 18:42:53 +1300 Subject: Tag support has been implemented for TestProtocolClient. (Robert Collins, #518016) --- python/subunit/__init__.py | 9 +++++++++ python/subunit/tests/test_test_protocol.py | 16 ++++++++++++++++ 2 files changed, 25 insertions(+) (limited to 'python') diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index b4c9397..b30b8fe 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -755,6 +755,15 @@ class TestProtocolClient(testresult.TestResult): self._stream.write(self._progress_fmt + prefix + offset + self._bytes_eol) + def tags(self, new_tags, gone_tags): + """Inform the client about tags added/removed from the stream.""" + if not new_tags and not gone_tags: + return + tags = set([tag.encode('utf8') for tag in new_tags]) + tags.update([_b("-") + tag.encode('utf8') for tag in gone_tags]) + tag_line = _b("tags: ") + _b(" ").join(tags) + _b("\n") + self._stream.write(tag_line) + def time(self, a_datetime): """Inform the client of the time. diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index 019c080..091b370 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -1299,6 +1299,22 @@ class TestTestProtocolClient(unittest.TestCase): "something\n" "F\r\nserialised\nform0\r\n]\n" % self.test.id())) + def test_tags_empty(self): + self.protocol.tags(set(), set()) + self.assertEqual(_b(""), self.io.getvalue()) + + def test_tags_add(self): + self.protocol.tags(set(['foo']), set()) + self.assertEqual(_b("tags: foo\n"), self.io.getvalue()) + + def test_tags_both(self): + self.protocol.tags(set(['quux']), set(['bar'])) + self.assertEqual(_b("tags: quux -bar\n"), self.io.getvalue()) + + def test_tags_gone(self): + self.protocol.tags(set(), set(['bar'])) + self.assertEqual(_b("tags: -bar\n"), self.io.getvalue()) + def test_suite(): loader = subunit.tests.TestUtil.TestLoader() -- cgit v1.2.1