Skip to content

Commit

Permalink
Add captured log msgs to junit xml file
Browse files Browse the repository at this point in the history
For each test this adds the captured log msgs to a system-* tag in the junit
xml output file. The destination of the system-* tag is specified by
junit_logging ini option.
  • Loading branch information
twmr committed Feb 3, 2018
1 parent f2fb841 commit c0ef4a4
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 8 deletions.
53 changes: 48 additions & 5 deletions _pytest/junitxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,47 @@ def _add_simple(self, kind, message, data=None):
self.append(node)

def write_captured_output(self, report):
for capname in ('out', 'err'):
content = getattr(report, 'capstd' + capname)
content_out = report.capstdout
content_log = report.caplog
content_err = report.capstderr

if content_log or content_out:
if content_log and self.xml.logging == 'system-out':
if content_out:
# syncing stdout and the log-output is not done yet. It's
# probably not worth the effort. Therefore, first the captured
# stdout is shown and then the captured logs.
content = '\n'.join([
' Captured Stdout '.center(80, '-'),
content_out,
'',
' Captured Log '.center(80, '-'),
content_log])
else:
content = content_log
else:
content = content_out

if content:
tag = getattr(Junit, 'system-out')
self.append(tag(bin_xml_escape(content)))

if content_log or content_err:
if content_log and self.xml.logging == 'system-err':
if content_err:
content = '\n'.join([
' Captured Stderr '.center(80, '-'),
content_err,
'',
' Captured Log '.center(80, '-'),
content_log])
else:
content = content_log
else:
content = content_err

if content:
tag = getattr(Junit, 'system-' + capname)
tag = getattr(Junit, 'system-err')
self.append(tag(bin_xml_escape(content)))

def append_pass(self, report):
Expand Down Expand Up @@ -254,13 +291,18 @@ def pytest_addoption(parser):
default=None,
help="prepend prefix to classnames in junit-xml output")
parser.addini("junit_suite_name", "Test suite name for JUnit report", default="pytest")
parser.addini("junit_logging", "Write captured log messages to JUnit report: "
"one of no|system-out|system-err",
default="no") # choices=['no', 'stdout', 'stderr'])


def pytest_configure(config):
xmlpath = config.option.xmlpath
# prevent opening xmllog on slave nodes (xdist)
if xmlpath and not hasattr(config, 'slaveinput'):
config._xml = LogXML(xmlpath, config.option.junitprefix, config.getini("junit_suite_name"))
config._xml = LogXML(xmlpath, config.option.junitprefix,
config.getini("junit_suite_name"),
config.getini("junit_logging"))
config.pluginmanager.register(config._xml)


Expand All @@ -287,11 +329,12 @@ def mangle_test_address(address):


class LogXML(object):
def __init__(self, logfile, prefix, suite_name="pytest"):
def __init__(self, logfile, prefix, suite_name="pytest", logging="no"):
logfile = os.path.expanduser(os.path.expandvars(logfile))
self.logfile = os.path.normpath(os.path.abspath(logfile))
self.prefix = prefix
self.suite_name = suite_name
self.logging = logging
self.stats = dict.fromkeys([
'error',
'passed',
Expand Down
8 changes: 8 additions & 0 deletions _pytest/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,14 @@ def longreprtext(self):
exc = tw.stringio.getvalue()
return exc.strip()

@property
def caplog(self):
"""Return captured log lines, if log capturing is enabled
.. versionadded:: 3.4
"""
return '\n'.join(content for (prefix, content) in self.get_sections('Captured log'))

@property
def capstdout(self):
"""Return captured text from stdout, if capturing is enabled
Expand Down
1 change: 1 addition & 0 deletions changelog/3156.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Captured log messages are added to the ``<system-out>`` tag in the generated junit xml file if the ``junit_logging`` ini option is set to ``system-out``. If the value of this ini option is ``system-err`, the logs are written to ``<system-err>``. The default value for ``junit_logging`` is ``no``, meaning captured logs are not written to the output file.
23 changes: 20 additions & 3 deletions testing/test_junitxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,23 +328,28 @@ def test_internal_error(self, testdir):
fnode.assert_attr(message="internal error")
assert "Division" in fnode.toxml()

def test_failure_function(self, testdir):
@pytest.mark.parametrize('junit_logging', ['no', 'system-out', 'system-err'])
def test_failure_function(self, testdir, junit_logging):
testdir.makepyfile("""
import logging
import sys
def test_fail():
print ("hello-stdout")
sys.stderr.write("hello-stderr\\n")
logging.info('info msg')
logging.warning('warning msg')
raise ValueError(42)
""")

result, dom = runandparse(testdir)
result, dom = runandparse(testdir, '-o', 'junit_logging=%s' % junit_logging)
assert result.ret
node = dom.find_first_by_tag("testsuite")
node.assert_attr(failures=1, tests=1)
tnode = node.find_first_by_tag("testcase")
tnode.assert_attr(
file="test_failure_function.py",
line="1",
line="3",
classname="test_failure_function",
name="test_fail")
fnode = tnode.find_first_by_tag("failure")
Expand All @@ -353,9 +358,21 @@ def test_fail():
systemout = fnode.next_siebling
assert systemout.tag == "system-out"
assert "hello-stdout" in systemout.toxml()
assert "info msg" not in systemout.toxml()
systemerr = systemout.next_siebling
assert systemerr.tag == "system-err"
assert "hello-stderr" in systemerr.toxml()
assert "info msg" not in systemerr.toxml()

if junit_logging == 'system-out':
assert "warning msg" in systemout.toxml()
assert "warning msg" not in systemerr.toxml()
elif junit_logging == 'system-err':
assert "warning msg" not in systemout.toxml()
assert "warning msg" in systemerr.toxml()
elif junit_logging == 'no':
assert "warning msg" not in systemout.toxml()
assert "warning msg" not in systemerr.toxml()

def test_failure_verbose_message(self, testdir):
testdir.makepyfile("""
Expand Down

0 comments on commit c0ef4a4

Please sign in to comment.