Skip to content

Commit

Permalink
Merge pull request #3156 from thisch/junit_xml_log_fix
Browse files Browse the repository at this point in the history
Add captured log msgs to junit xml file
  • Loading branch information
nicoddemus authored Feb 6, 2018
2 parents f2fb841 + 2d0c1e9 commit 29a074e
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.5
"""
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 29a074e

Please sign in to comment.