From 5de2ec0def3e4ad0ead20b426e81509fd8e48c6d Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Sat, 4 Sep 2010 23:04:28 +0200 Subject: subunit: Import latest upstream. --- lib/subunit/python/subunit/__init__.py | 48 +++++++++----- lib/subunit/python/subunit/details.py | 8 ++- lib/subunit/python/subunit/run.py | 39 +++++++++-- lib/subunit/python/subunit/test_results.py | 76 ++++++++++++++++++---- lib/subunit/python/subunit/tests/test_details.py | 3 +- .../python/subunit/tests/test_test_protocol.py | 55 +++++++++------- 6 files changed, 163 insertions(+), 66 deletions(-) (limited to 'lib/subunit/python') diff --git a/lib/subunit/python/subunit/__init__.py b/lib/subunit/python/subunit/__init__.py index 4b25ca3a39..b6f0108f61 100644 --- a/lib/subunit/python/subunit/__init__.py +++ b/lib/subunit/python/subunit/__init__.py @@ -133,9 +133,7 @@ try: except ImportError: raise ImportError ("testtools.testresult.real does not contain " "_StringException, check your version.") - - -from testtools.testresult.real import _StringException +from testtools import testresult import chunked, details, test_results @@ -244,7 +242,7 @@ class _ParserState(object): def lostConnection(self): """Connection lost.""" - self.parser._lostConnectionInTest('unknown state of ') + self.parser._lostConnectionInTest(u'unknown state of ') def startTest(self, offset, line): """A test start command received.""" @@ -324,7 +322,7 @@ class _InTest(_ParserState): def lostConnection(self): """Connection lost.""" - self.parser._lostConnectionInTest('') + self.parser._lostConnectionInTest(u'') class _OutSideTest(_ParserState): @@ -359,7 +357,7 @@ class _ReadingDetails(_ParserState): def lostConnection(self): """Connection lost.""" - self.parser._lostConnectionInTest('%s report of ' % + self.parser._lostConnectionInTest(u'%s report of ' % self._outcome_label()) def _outcome_label(self): @@ -501,7 +499,7 @@ class TestProtocolServer(object): self._state.lineReceived(line) def _lostConnectionInTest(self, state_string): - error_string = "lost connection during %stest '%s'" % ( + 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) @@ -531,7 +529,7 @@ class TestProtocolServer(object): self._stream.write(line) -class TestProtocolClient(unittest.TestResult): +class TestProtocolClient(testresult.TestResult): """A TestResult which generates a subunit stream for a test run. # Get a TestSuite or TestCase to run @@ -550,8 +548,9 @@ class TestProtocolClient(unittest.TestResult): """ def __init__(self, stream): - unittest.TestResult.__init__(self) + testresult.TestResult.__init__(self) self._stream = stream + _make_stream_binary(stream) def addError(self, test, error=None, details=None): """Report an error in test test. @@ -618,8 +617,10 @@ class TestProtocolClient(unittest.TestResult): raise ValueError if error is not None: self._stream.write(" [\n") - for line in self._exc_info_to_string(error, test).splitlines(): - self._stream.write("%s\n" % line) + # 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") @@ -704,7 +705,7 @@ class TestProtocolClient(unittest.TestResult): """Obey the testtools result.done() interface.""" -def RemoteError(description=""): +def RemoteError(description=u""): return (_StringException, _StringException(description), None) @@ -754,7 +755,7 @@ class RemotedTestCase(unittest.TestCase): def run(self, result=None): if result is None: result = self.defaultTestResult() result.startTest(self) - result.addError(self, RemoteError("Cannot run RemotedTestCases.\n")) + result.addError(self, RemoteError(u"Cannot run RemotedTestCases.\n")) result.stopTest(self) def _strclass(self): @@ -784,7 +785,7 @@ class ExecTestCase(unittest.TestCase): def debug(self): """Run the test without collecting errors in a TestResult""" - self._run(unittest.TestResult()) + self._run(testresult.TestResult()) def _run(self, result): protocol = TestProtocolServer(result) @@ -816,7 +817,7 @@ class IsolatedTestSuite(unittest.TestSuite): """ def run(self, result=None): - if result is None: result = unittest.TestResult() + if result is None: result = testresult.TestResult() run_isolated(unittest.TestSuite, self, result) @@ -1045,8 +1046,10 @@ class ProtocolTestCase(object): subunit input is not forwarded. """ self._stream = stream + _make_stream_binary(stream) self._passthrough = passthrough self._forward = forward + _make_stream_binary(forward) def __call__(self, result=None): return self.run(result) @@ -1062,7 +1065,7 @@ class ProtocolTestCase(object): protocol.lostConnection() -class TestResultStats(unittest.TestResult): +class TestResultStats(testresult.TestResult): """A pyunit TestResult interface implementation for making statistics. :ivar total_tests: The total tests seen. @@ -1073,7 +1076,7 @@ class TestResultStats(unittest.TestResult): def __init__(self, stream): """Create a TestResultStats which outputs to stream.""" - unittest.TestResult.__init__(self) + testresult.TestResult.__init__(self) self._stream = stream self.failed_tests = 0 self.skipped_tests = 0 @@ -1124,3 +1127,14 @@ def get_default_formatter(): else: return sys.stdout + +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: + _make_binary_on_windows(stream.fileno()) + +def _make_binary_on_windows(fileno): + """Win32 mangles \r\n to \n and that breaks streams. See bug lp:505078.""" + if sys.platform == "win32": + import msvcrt + msvcrt.setmode(fileno, os.O_BINARY) diff --git a/lib/subunit/python/subunit/details.py b/lib/subunit/python/subunit/details.py index 65a04046d9..a37b2acb93 100644 --- a/lib/subunit/python/subunit/details.py +++ b/lib/subunit/python/subunit/details.py @@ -47,8 +47,12 @@ class SimpleDetailsParser(DetailsParser): def get_details(self, style=None): result = {} if not style: + # We know that subunit/testtools serialise [] formatted + # tracebacks as utf8, but perhaps we need a ReplacingContent + # or something like that. result['traceback'] = content.Content( - content_type.ContentType("text", "x-traceback"), + content_type.ContentType("text", "x-traceback", + {"charset": "utf8"}), lambda:[self._message]) else: if style == 'skip': @@ -92,7 +96,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 == '' + assert residue == '', 'residue: %r' % (residue,) body = self._body self._details[self._name] = content.Content( self._content_type, lambda:[body.getvalue()]) diff --git a/lib/subunit/python/subunit/run.py b/lib/subunit/python/subunit/run.py index e57939fdfa..daa241a606 100755 --- a/lib/subunit/python/subunit/run.py +++ b/lib/subunit/python/subunit/run.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python # # Simple subunit testrunner for python # Copyright (C) Jelmer Vernooij 2007 @@ -23,6 +23,13 @@ import sys from subunit import TestProtocolClient, get_default_formatter +from testtools.run import ( + BUFFEROUTPUT, + CATCHBREAK, + FAILFAST, + TestProgram, + USAGE_AS_MAIN, + ) class SubunitTestRunner(object): @@ -36,12 +43,30 @@ class SubunitTestRunner(object): return result +class SubunitTestProgram(TestProgram): + + USAGE = USAGE_AS_MAIN + + def usageExit(self, msg=None): + if msg: + print msg + usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '', + 'buffer': ''} + if self.failfast != False: + usage['failfast'] = FAILFAST + if self.catchbreak != False: + usage['catchbreak'] = CATCHBREAK + if self.buffer != False: + usage['buffer'] = BUFFEROUTPUT + usage_text = self.USAGE % usage + usage_lines = usage_text.split('\n') + usage_lines.insert(2, "Run a test suite with a subunit reporter.") + usage_lines.insert(3, "") + print('\n'.join(usage_lines)) + sys.exit(2) + + if __name__ == '__main__': - import optparse - from unittest import TestProgram - parser = optparse.OptionParser(__doc__) - args = parser.parse_args()[1] stream = get_default_formatter() runner = SubunitTestRunner(stream) - program = TestProgram(module=None, argv=[sys.argv[0]] + args, - testRunner=runner) + SubunitTestProgram(module=None, argv=sys.argv, testRunner=runner) diff --git a/lib/subunit/python/subunit/test_results.py b/lib/subunit/python/subunit/test_results.py index 6cf84c519e..1c91daadc6 100644 --- a/lib/subunit/python/subunit/test_results.py +++ b/lib/subunit/python/subunit/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 @@ -21,16 +21,14 @@ import datetime import iso8601 import testtools -import subunit - # NOT a TestResult, because we are implementing the interface, not inheriting # it. class TestResultDecorator(object): """General pass-through decorator. - This provides a base that other TestResults can inherit from to - gain basic forwarding functionality. It also takes care of + This provides a base that other TestResults can inherit from to + gain basic forwarding functionality. It also takes care of handling the case where the target doesn't support newer methods or features by degrading them. """ @@ -201,11 +199,11 @@ class TestResultFilter(TestResultDecorator): """A pyunit TestResult interface implementation which filters tests. Tests that pass the filter are handed on to another TestResult instance - for further processing/reporting. To obtain the filtered results, + for further processing/reporting. To obtain the filtered results, the other instance must be interrogated. :ivar result: The result that tests are passed to after filtering. - :ivar filter_predicate: The callback run to decide whether to pass + :ivar filter_predicate: The callback run to decide whether to pass a result. """ @@ -213,7 +211,7 @@ class TestResultFilter(TestResultDecorator): filter_success=True, filter_skip=False, filter_predicate=None): """Create a FilterResult object filtering to result. - + :param filter_error: Filter out errors. :param filter_failure: Filter out failures. :param filter_success: Filter out successful tests. @@ -238,9 +236,9 @@ class TestResultFilter(TestResultDecorator): self._current_test_filtered = None # The (new, gone) tags for the current test. self._current_test_tags = None - + def addError(self, test, err=None, details=None): - if (not self._filter_error and + if (not self._filter_error and self.filter_predicate(test, 'error', err, details)): self.decorated.startTest(test) self.decorated.addError(test, err, details=details) @@ -288,17 +286,17 @@ class TestResultFilter(TestResultDecorator): def startTest(self, test): """Start a test. - + Not directly passed to the client, but used for handling of tags correctly. """ self._current_test = test self._current_test_filtered = False self._current_test_tags = set(), set() - + def stopTest(self, test): """Stop a test. - + Not directly passed to the client, but used for handling of tags correctly. """ @@ -316,7 +314,7 @@ class TestResultFilter(TestResultDecorator): 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. """ @@ -332,3 +330,53 @@ class TestResultFilter(TestResultDecorator): if id.startswith("subunit.RemotedTestCase."): return id[len("subunit.RemotedTestCase."):] return id + + +class TestIdPrintingResult(testtools.TestResult): + + def __init__(self, stream, show_times=False): + """Create a FilterResult object outputting to stream.""" + testtools.TestResult.__init__(self) + self._stream = stream + self.failed_tests = 0 + self.__time = 0 + self.show_times = show_times + self._test = None + self._test_duration = 0 + + def addError(self, test, err): + self.failed_tests += 1 + self._test = test + + def addFailure(self, test, err): + self.failed_tests += 1 + self._test = test + + def addSuccess(self, test): + self._test = test + + def reportTest(self, test, duration): + if self.show_times: + seconds = duration.seconds + seconds += duration.days * 3600 * 24 + seconds += duration.microseconds / 1000000.0 + self._stream.write(test.id() + ' %0.3f\n' % seconds) + else: + self._stream.write(test.id() + '\n') + + def startTest(self, test): + self._start_time = self._time() + + def stopTest(self, test): + test_duration = self._time() - self._start_time + self.reportTest(self._test, test_duration) + + def time(self, time): + self.__time = time + + def _time(self): + return self.__time + + def wasSuccessful(self): + "Tells whether or not this result was a success" + return self.failed_tests == 0 diff --git a/lib/subunit/python/subunit/tests/test_details.py b/lib/subunit/python/subunit/tests/test_details.py index 2700d4afc7..41c32129d0 100644 --- a/lib/subunit/python/subunit/tests/test_details.py +++ b/lib/subunit/python/subunit/tests/test_details.py @@ -51,7 +51,8 @@ class TestSimpleDetails(unittest.TestCase): traceback = "" expected = {} expected['traceback'] = content.Content( - content_type.ContentType("text", "x-traceback"), + content_type.ContentType("text", "x-traceback", + {'charset': 'utf8'}), lambda:[""]) found = parser.get_details() self.assertEqual(expected.keys(), found.keys()) diff --git a/lib/subunit/python/subunit/tests/test_test_protocol.py b/lib/subunit/python/subunit/tests/test_test_protocol.py index f10380b09b..e1287b6c81 100644 --- a/lib/subunit/python/subunit/tests/test_test_protocol.py +++ b/lib/subunit/python/subunit/tests/test_test_protocol.py @@ -102,6 +102,9 @@ class TestTestProtocolServerPipe(unittest.TestCase): "------------\n\n")]) self.assertEqual(client.testsRun, 3) + def test_non_test_characters_forwarded_immediately(self): + pass + class TestTestProtocolServerStartTest(unittest.TestCase): @@ -243,7 +246,8 @@ class TestTestProtocolServerPassThrough(unittest.TestCase): self.protocol.lineReceived("]\n") self.assertEqual(self.stdout.getvalue(), "") details = {} - details['traceback'] = Content(ContentType("text", "x-traceback"), + details['traceback'] = Content(ContentType("text", "x-traceback", + {'charset': 'utf8'}), lambda:[ "test old mcdonald\n" "failure a\n" @@ -285,7 +289,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.protocol.lineReceived("test old mcdonald\n") self.protocol.lostConnection() failure = subunit.RemoteError( - "lost connection during test 'old mcdonald'") + u"lost connection during test 'old mcdonald'") self.assertEqual([ ('startTest', self.test), ('addError', self.test, failure), @@ -298,7 +302,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.protocol.lostConnection() self.assertEqual([ ('startTest', self.test), - ('addError', self.test, subunit.RemoteError("")), + ('addError', self.test, subunit.RemoteError(u"")), ('stopTest', self.test), ], self.client._events) @@ -307,7 +311,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.protocol.lineReceived("%s old mcdonald %s" % (outcome, opening)) self.protocol.lostConnection() failure = subunit.RemoteError( - "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), @@ -327,7 +331,7 @@ class TestTestProtocolServerLostConnection(unittest.TestCase): self.protocol.lostConnection() self.assertEqual([ ('startTest', self.test), - ('addFailure', self.test, subunit.RemoteError("")), + ('addFailure', self.test, subunit.RemoteError(u"")), ('stopTest', self.test), ], self.client._events) @@ -411,8 +415,8 @@ class TestTestProtocolServerAddError(unittest.TestCase): self.protocol.lineReceived("error mcdonalds farm [\n") self.protocol.lineReceived("]\n") details = {} - details['traceback'] = Content(ContentType("text", "x-traceback"), - lambda:[""]) + details['traceback'] = Content(ContentType("text", "x-traceback", + {'charset': 'utf8'}), lambda:[""]) self.assertEqual([ ('startTest', self.test), ('addError', self.test, details), @@ -424,8 +428,8 @@ class TestTestProtocolServerAddError(unittest.TestCase): self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") details = {} - details['traceback'] = Content(ContentType("text", "x-traceback"), - lambda:["]\n"]) + details['traceback'] = Content(ContentType("text", "x-traceback", + {'charset': 'utf8'}), lambda:["]\n"]) self.assertEqual([ ('startTest', self.test), ('addError', self.test, details), @@ -469,8 +473,8 @@ class TestTestProtocolServerAddFailure(unittest.TestCase): self.protocol.lineReceived("failure mcdonalds farm [\n") self.protocol.lineReceived("]\n") details = {} - details['traceback'] = Content(ContentType("text", "x-traceback"), - lambda:[""]) + details['traceback'] = Content(ContentType("text", "x-traceback", + {'charset': 'utf8'}), lambda:[""]) self.assertFailure(details) def failure_quoted_bracket(self, keyword): @@ -478,8 +482,8 @@ class TestTestProtocolServerAddFailure(unittest.TestCase): self.protocol.lineReceived(" ]\n") self.protocol.lineReceived("]\n") details = {} - details['traceback'] = Content(ContentType("text", "x-traceback"), - lambda:["]\n"]) + details['traceback'] = Content(ContentType("text", "x-traceback", + {'charset': 'utf8'}), lambda:["]\n"]) self.assertFailure(details) def test_failure_quoted_bracket(self): @@ -535,12 +539,13 @@ class TestTestProtocolServerAddxFail(unittest.TestCase): details = {} if error_message is not None: details['traceback'] = Content( - ContentType("text", "x-traceback"), lambda:[error_message]) + ContentType("text", "x-traceback", {'charset': 'utf8'}), + lambda:[error_message]) if isinstance(self.client, ExtendedTestResult): value = details else: if error_message is not None: - value = subunit.RemoteError('Text attachment: traceback\n' + value = subunit.RemoteError(u'Text attachment: traceback\n' '------------\n' + error_message + '------------\n') else: value = subunit.RemoteError() @@ -845,15 +850,15 @@ class TestRemotedTestCase(unittest.TestCase): class TestRemoteError(unittest.TestCase): def test_eq(self): - error = subunit.RemoteError("Something went wrong") - another_error = subunit.RemoteError("Something went wrong") - different_error = subunit.RemoteError("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("")) + self.assertEqual(subunit.RemoteError(), subunit.RemoteError(u"")) class TestExecTestCase(unittest.TestCase): @@ -887,8 +892,8 @@ class TestExecTestCase(unittest.TestCase): mcdonald = subunit.RemotedTestCase("old mcdonald") bing = subunit.RemotedTestCase("bing crosby") bing_details = {} - bing_details['traceback'] = Content(ContentType("text", "x-traceback"), - lambda:["foo.c:53:ERROR invalid state\n"]) + bing_details['traceback'] = Content(ContentType("text", "x-traceback", + {'charset': 'utf8'}), lambda:["foo.c:53:ERROR invalid state\n"]) an_error = subunit.RemotedTestCase("an error") error_details = {} self.assertEqual([ @@ -1004,7 +1009,7 @@ class TestTestProtocolClient(unittest.TestCase): ContentType('text', 'plain'), lambda:['serialised\nform'])} self.sample_tb_details = dict(self.sample_details) self.sample_tb_details['traceback'] = TracebackContent( - subunit.RemoteError("boo qux"), self.test) + subunit.RemoteError(u"boo qux"), self.test) def test_start_test(self): """Test startTest on a TestProtocolClient.""" @@ -1034,7 +1039,7 @@ class TestTestProtocolClient(unittest.TestCase): def test_add_failure(self): """Test addFailure on a TestProtocolClient.""" self.protocol.addFailure( - self.test, subunit.RemoteError("boo qux")) + self.test, subunit.RemoteError(u"boo qux")) self.assertEqual( self.io.getvalue(), ('failure: %s [\n' + _remote_exception_str + ': boo qux\n]\n') @@ -1058,7 +1063,7 @@ class TestTestProtocolClient(unittest.TestCase): def test_add_error(self): """Test stopTest on a TestProtocolClient.""" self.protocol.addError( - self.test, subunit.RemoteError("phwoar crikey")) + self.test, subunit.RemoteError(u"phwoar crikey")) self.assertEqual( self.io.getvalue(), ('error: %s [\n' + @@ -1083,7 +1088,7 @@ class TestTestProtocolClient(unittest.TestCase): def test_add_expected_failure(self): """Test addExpectedFailure on a TestProtocolClient.""" self.protocol.addExpectedFailure( - self.test, subunit.RemoteError("phwoar crikey")) + self.test, subunit.RemoteError(u"phwoar crikey")) self.assertEqual( self.io.getvalue(), ('xfail: %s [\n' + -- cgit