Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add captured log msgs to junit xml file #3156

Merged
merged 2 commits into from
Feb 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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