summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Szakmeister <john@szakmeister.net>2012-09-29 07:09:40 -0400
committerJohn Szakmeister <john@szakmeister.net>2012-10-02 15:41:21 -0400
commit17232a8adf2ed3550a998243e897994e43000dba (patch)
tree3cf58fac3a44e7300d40a0b7d73fb53ed55ca845
parent908b2cda43eff9ab7a5045b6f6dfe3a718fb9afd (diff)
downloadnose-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.rst2
-rw-r--r--nose/plugins/xunit.py57
-rw-r--r--unit_tests/test_xunit.py10
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()