diff options
author | John Szakmeister <john@szakmeister.net> | 2012-09-29 07:09:40 -0400 |
---|---|---|
committer | John Szakmeister <john@szakmeister.net> | 2012-10-02 15:41:21 -0400 |
commit | 17232a8adf2ed3550a998243e897994e43000dba (patch) | |
tree | 3cf58fac3a44e7300d40a0b7d73fb53ed55ca845 | |
parent | 908b2cda43eff9ab7a5045b6f6dfe3a718fb9afd (diff) | |
download | nose-17232a8adf2ed3550a998243e897994e43000dba.tar.gz |
xunit: capture stdout out into the system-out tag
Change the score of Xunit to place it before the Capture plugin. We
don't want the Capture plugin to change the streams before the tests are
run. At a score of 2000, that's exactly what happens, so let's make it
499 instead (Capture's score - 1).
Have Xunit replace the stream with a Tee object of just swapping the
stream. This allows the Capture plugin to see the data from stdout,
even though we've replaced the stream, allowing it to function exactly
as you would expect it to.
Finally, we maintain a stack of streams, instead of just a single
stream. I don't believe this is entirely necessary though.
-rw-r--r-- | functional_tests/doc_tests/test_xunit_plugin/test_skips.rst | 2 | ||||
-rw-r--r-- | nose/plugins/xunit.py | 57 | ||||
-rw-r--r-- | unit_tests/test_xunit.py | 10 |
3 files changed, 59 insertions, 10 deletions
diff --git a/functional_tests/doc_tests/test_xunit_plugin/test_skips.rst b/functional_tests/doc_tests/test_xunit_plugin/test_skips.rst index c0c3fbc..dd0590e 100644 --- a/functional_tests/doc_tests/test_xunit_plugin/test_skips.rst +++ b/functional_tests/doc_tests/test_xunit_plugin/test_skips.rst @@ -37,4 +37,4 @@ Ran 4 tests in ...s FAILED (SKIP=1, errors=1, failures=1) >>> open(outfile, 'r').read() # doctest: +ELLIPSIS -'<?xml version="1.0" encoding="UTF-8"?><testsuite name="nosetests" tests="4" errors="1" failures="1" skip="1"><testcase classname="test_skip" name="test_ok" time="..." /><testcase classname="test_skip" name="test_err" time="..."><error type="...Exception" message="oh no">...</error></testcase><testcase classname="test_skip" name="test_fail" time="..."><failure type="...AssertionError" message="bye">...</failure></testcase><testcase classname="test_skip" name="test_skip" time="..."><skipped type="...SkipTest" message="not me">...</skipped></testcase></testsuite>' +'<?xml version="1.0" encoding="UTF-8"?><testsuite name="nosetests" tests="4" errors="1" failures="1" skip="1"><testcase classname="test_skip" name="test_ok" time="..."></testcase><testcase classname="test_skip" name="test_err" time="..."><error type="...Exception" message="oh no">...</error></testcase><testcase classname="test_skip" name="test_fail" time="..."><failure type="...AssertionError" message="bye">...</failure></testcase><testcase classname="test_skip" name="test_skip" time="..."><skipped type="...SkipTest" message="not me">...</skipped></testcase></testsuite>' diff --git a/nose/plugins/xunit.py b/nose/plugins/xunit.py index ded973e..006ae77 100644 --- a/nose/plugins/xunit.py +++ b/nose/plugins/xunit.py @@ -39,9 +39,11 @@ Here is an abbreviated version of what an XML test report might look like:: import codecs import doctest import os +import sys import traceback import re import inspect +from cStringIO import StringIO from time import time from xml.sax import saxutils @@ -112,13 +114,26 @@ def exc_message(exc_info): result = exc.args[0] return xml_safe(result) +class Tee(object): + def __init__(self, *args): + self._streams = args + + def write(self, *args): + for s in self._streams: + s.write(*args) + class Xunit(Plugin): """This plugin provides test results in the standard XUnit XML format.""" name = 'xunit' - score = 2000 + score = 499 encoding = 'UTF-8' error_report_file = None + def __init__(self): + super(Xunit, self).__init__() + self._capture_stack = [] + self._current = None + def _timeTaken(self): if hasattr(self, '_timer'): taken = time() - self._timer @@ -183,9 +198,38 @@ class Xunit(Plugin): stream.writeln("-" * 70) stream.writeln("XML: %s" % self.error_report_file.name) - def startTest(self, test): + def _startCapture(self): + self._capture_stack.append(sys.stdout) + self._current = StringIO() + sys.stdout = Tee(self._current, sys.stdout) + + def startContext(self, context): + self._startCapture() + + def beforeTest(self, test): """Initializes a timer before starting a test.""" self._timer = time() + self._startCapture() + + def _endCapture(self): + if self._capture_stack: + sys.stdout = self._capture_stack.pop() + + def afterTest(self, test): + self._endCapture() + self._current = None + + def finalize(self, test): + while self._capture_stack: + self._capture_stack.pop() + + def _getCapturedOutput(self): + if self._current: + value = self._current.getvalue() + if value: + return '<system-out><![CDATA[%s]]></system-out>' % escape_cdata( + value) + return '' def addError(self, test, err, capt=None): """Add error output to Xunit report. @@ -203,7 +247,7 @@ class Xunit(Plugin): self.errorlist.append( '<testcase classname=%(cls)s name=%(name)s time="%(taken).3f">' '<%(type)s type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>' - '</%(type)s></testcase>' % + '</%(type)s>%(systemout)s</testcase>' % {'cls': self._quoteattr(id_split(id)[0]), 'name': self._quoteattr(id_split(id)[-1]), 'taken': taken, @@ -211,6 +255,7 @@ class Xunit(Plugin): 'errtype': self._quoteattr(nice_classname(err[0])), 'message': self._quoteattr(exc_message(err)), 'tb': escape_cdata(tb), + 'systemout': self._getCapturedOutput(), }) def addFailure(self, test, err, capt=None, tb_info=None): @@ -223,13 +268,14 @@ class Xunit(Plugin): self.errorlist.append( '<testcase classname=%(cls)s name=%(name)s time="%(taken).3f">' '<failure type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>' - '</failure></testcase>' % + '</failure>%(systemout)s</testcase>' % {'cls': self._quoteattr(id_split(id)[0]), 'name': self._quoteattr(id_split(id)[-1]), 'taken': taken, 'errtype': self._quoteattr(nice_classname(err[0])), 'message': self._quoteattr(exc_message(err)), 'tb': escape_cdata(tb), + 'systemout': self._getCapturedOutput(), }) def addSuccess(self, test, capt=None): @@ -240,10 +286,11 @@ class Xunit(Plugin): id = test.id() self.errorlist.append( '<testcase classname=%(cls)s name=%(name)s ' - 'time="%(taken).3f" />' % + 'time="%(taken).3f">%(systemout)s</testcase>' % {'cls': self._quoteattr(id_split(id)[0]), 'name': self._quoteattr(id_split(id)[-1]), 'taken': taken, + 'systemout': self._getCapturedOutput(), }) def _forceUnicode(self, s): diff --git a/unit_tests/test_xunit.py b/unit_tests/test_xunit.py index 3ee7101..f0df7a4 100644 --- a/unit_tests/test_xunit.py +++ b/unit_tests/test_xunit.py @@ -136,7 +136,7 @@ class TestXMLOutputWithXML(unittest.TestCase): def test_addFailure(self): test = mktest() - self.x.startTest(test) + self.x.beforeTest(test) try: raise AssertionError("one is not 'equal' to two") except AssertionError: @@ -203,7 +203,7 @@ class TestXMLOutputWithXML(unittest.TestCase): def test_addError(self): test = mktest() - self.x.startTest(test) + self.x.beforeTest(test) try: raise RuntimeError("some error happened") except RuntimeError: @@ -246,7 +246,7 @@ class TestXMLOutputWithXML(unittest.TestCase): def test_non_utf8_error(self): # See http://code.google.com/p/python-nose/issues/detail?id=395 test = mktest() - self.x.startTest(test) + self.x.beforeTest(test) try: raise RuntimeError(chr(128)) # cannot encode as utf8 except RuntimeError: @@ -275,6 +275,8 @@ class TestXMLOutputWithXML(unittest.TestCase): except RuntimeError: some_err = sys.exc_info() + self.x.startContext(None) + # call addError without startTest # which can happen if setup() raises an error self.x.addError(test, some_err) @@ -295,7 +297,7 @@ class TestXMLOutputWithXML(unittest.TestCase): def test_addSuccess(self): test = mktest() - self.x.startTest(test) + self.x.beforeTest(test) self.x.addSuccess(test, (None,None,None)) result = self.get_xml_report() |