From 147d169b7516868b3bd4be444330f565d0b83931 Mon Sep 17 00:00:00 2001 From: Takeshi Kanemoto Date: Tue, 20 Oct 2015 08:35:10 +0900 Subject: [PATCH 1/2] Add a test case for error added during __call__() In some sub classes of TestCase where __call__() method is overridden, errors may occur that prevents the super class __call__ being called, e.g. Django's custom TestCase subclasses do pre-setup processing such as loading the fixture etc. which, on encountering exceptions, will skip calling __call__ on the super class, leaving sys.stdout and sys.stderr untouched. In the current implementation, this causes an unhandled AttributeError when trying to call .getvalue() on sys.stdout and sys.stderr. Refs: #83 --- tests/testsuite.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/testsuite.py b/tests/testsuite.py index 7ce556e..c2bdf02 100755 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -64,6 +64,17 @@ def test_subTest_fail(self): with self.subTest(i=i): self.fail('this is a subtest.') + class DummyErrorInCallTest(unittest.TestCase): + def __call__(self, result): + try: + raise Exception('Massive fail') + except Exception: + result.addError(self, sys.exc_info()) + return + super(DummyErrorInCallTest, self).__call__(result) + def test_pass(self): + pass + def setUp(self): self.stream = StringIO() self.outdir = mkdtemp() @@ -311,3 +322,7 @@ def test_xmlrunner_patched_stdout(self): finally: sys.stdout, sys.stderr = old_stdout, old_stderr + def test_xmlrunner_error_in_call(self): + suite = unittest.TestSuite() + suite.addTest(self.DummyErrorInCallTest('test_pass')) + self._test_xmlrunner(suite) From be41af510e12ceeb11a8f13117d42d95a365c2e3 Mon Sep 17 00:00:00 2001 From: Takeshi Kanemoto Date: Tue, 20 Oct 2015 08:35:10 +0900 Subject: [PATCH 2/2] Fix 'getvalue' AttributeError when test fails during __call__ sys.stdout and sys.sterr may not necessarily be StringIO yet. We should catch AttributeError when trying to call getvalue() on them. Fixes: #83 --- tests/testsuite.py | 2 ++ xmlrunner/result.py | 34 +++++++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/tests/testsuite.py b/tests/testsuite.py index c2bdf02..17a5d87 100755 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -326,3 +326,5 @@ def test_xmlrunner_error_in_call(self): suite = unittest.TestSuite() suite.addTest(self.DummyErrorInCallTest('test_pass')) self._test_xmlrunner(suite) + testsuite_output = self.stream.getvalue() + self.assertIn('Exception: Massive fail', testsuite_output) diff --git a/xmlrunner/result.py b/xmlrunner/result.py index cfb8857..d04ede3 100644 --- a/xmlrunner/result.py +++ b/xmlrunner/result.py @@ -190,8 +190,13 @@ def startTest(self, test): self.stream.write(" ... ") def _save_output_data(self): - self._stdout_data = sys.stdout.getvalue() - self._stderr_data = sys.stderr.getvalue() + # Only try to get sys.stdout and sys.sterr as they not be + # StringIO yet, e.g. when test fails during __call__ + try: + self._stdout_data = sys.stdout.getvalue() + self._stderr_data = sys.stderr.getvalue() + except AttributeError: + pass def stopTest(self, test): """ @@ -339,7 +344,8 @@ def _report_testsuite(suite_name, tests, xml_document, parentElement, stdout = StringIO() for test in tests: # Merge the stdout from the tests in a class - stdout.write(test.stdout) + if test.stdout is not None: + stdout.write(test.stdout) _XMLTestResult._createCDATAsections( xml_document, systemout, stdout.getvalue()) @@ -349,7 +355,8 @@ def _report_testsuite(suite_name, tests, xml_document, parentElement, stderr = StringIO() for test in tests: # Merge the stderr from the tests in a class - stderr.write(test.stderr) + if test.stderr is not None: + stderr.write(test.stderr) _XMLTestResult._createCDATAsections( xml_document, systemerr, stderr.getvalue()) @@ -469,7 +476,12 @@ def _exc_info_to_string(self, err, test): """Converts a sys.exc_info()-style tuple of values into a string.""" if six.PY3: # It works fine in python 3 - return super(_XMLTestResult, self)._exc_info_to_string(err, test) + try: + return super(_XMLTestResult, self)._exc_info_to_string( + err, test) + except AttributeError: + # We keep going using the legacy python <= 2 way + pass # This comes directly from python2 unittest exctype, value, tb = err @@ -485,8 +497,16 @@ def _exc_info_to_string(self, err, test): msgLines = traceback.format_exception(exctype, value, tb) if self.buffer: - output = sys.stdout.getvalue() - error = sys.stderr.getvalue() + # Only try to get sys.stdout and sys.sterr as they not be + # StringIO yet, e.g. when test fails during __call__ + try: + output = sys.stdout.getvalue() + except AttributeError: + output = None + try: + error = sys.stderr.getvalue() + except AttributeError: + error = None if output: if not output.endswith('\n'): output += '\n'