Skip to content

Commit

Permalink
junitxml: adjust junitxml output file to comply with JUnit xsd
Browse files Browse the repository at this point in the history
Change XML file structure in the manner that failures in call and errors
in teardown in one test will appear under separate testcase elements in
the XML report.
  • Loading branch information
KKoukiou committed Mar 2, 2017
1 parent 0f3d7ac commit 439ce9a
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 2 deletions.
38 changes: 36 additions & 2 deletions _pytest/junitxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,9 @@ def __init__(self, logfile, prefix):
self.node_reporters = {} # nodeid -> _NodeReporter
self.node_reporters_ordered = []
self.global_properties = []
# List of reports that failed on call but teardown is pending.
self.open_reports = []
self.cnt_double_fail_tests = 0

def finalize(self, report):
nodeid = getattr(report, 'nodeid', report)
Expand Down Expand Up @@ -332,14 +335,33 @@ def pytest_runtest_logreport(self, report):
-> teardown node2
-> teardown node1
"""
close_report = None
if report.passed:
if report.when == "call": # ignore setup/teardown
reporter = self._opentestcase(report)
reporter.append_pass(report)
elif report.failed:
if report.when == "teardown":
# The following vars are needed when xdist plugin is used
report_wid = getattr(report, "worker_id", None)
report_ii = getattr(report, "item_index", None)
close_report = next(
(rep for rep in self.open_reports
if (rep.nodeid == report.nodeid and
getattr(rep, "item_index", None) == report_ii and
getattr(rep, "worker_id", None) == report_wid
)
), None)
if close_report:
# We need to open new testcase in case we have failure in
# call and error in teardown in order to follow junit
# schema
self.finalize(close_report)
self.cnt_double_fail_tests += 1
reporter = self._opentestcase(report)
if report.when == "call":
reporter.append_failure(report)
self.open_reports.append(report)
else:
reporter.append_error(report)
elif report.skipped:
Expand All @@ -348,6 +370,17 @@ def pytest_runtest_logreport(self, report):
self.update_testcase_duration(report)
if report.when == "teardown":
self.finalize(report)
report_wid = getattr(report, "worker_id", None)
report_ii = getattr(report, "item_index", None)
close_report = next(
(rep for rep in self.open_reports
if (rep.nodeid == report.nodeid and
getattr(rep, "item_index", None) == report_ii and
getattr(rep, "worker_id", None) == report_wid
)
), None)
if close_report:
self.open_reports.remove(close_report)

def update_testcase_duration(self, report):
"""accumulates total duration for nodeid from given report and updates
Expand Down Expand Up @@ -380,8 +413,9 @@ def pytest_sessionfinish(self):
suite_stop_time = time.time()
suite_time_delta = suite_stop_time - self.suite_start_time

numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped'] + self.stats['error']

numtests = (self.stats['passed'] + self.stats['failure'] +
self.stats['skipped'] + self.stats['error'] -
self.cnt_double_fail_tests)
logfile.write('<?xml version="1.0" encoding="utf-8"?>')

logfile.write(Junit.testsuite(
Expand Down
23 changes: 23 additions & 0 deletions testing/test_junitxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,29 @@ def test_function(arg):
fnode.assert_attr(message="test teardown failure")
assert "ValueError" in fnode.toxml()

def test_call_failure_teardown_error(self, testdir):
testdir.makepyfile("""
import pytest
@pytest.fixture
def arg():
yield
raise Exception("Teardown Exception")
def test_function(arg):
raise Exception("Call Exception")
""")
result, dom = runandparse(testdir)
assert result.ret
node = dom.find_first_by_tag("testsuite")
node.assert_attr(errors=1, failures=1, tests=1)
first, second = dom.find_by_tag("testcase")
if not first or not second or first == second:
assert 0
fnode = first.find_first_by_tag("failure")
fnode.assert_attr(message="Exception: Call Exception")
snode = second.find_first_by_tag("error")
snode.assert_attr(message="test teardown failure")

def test_skip_contains_name_reason(self, testdir):
testdir.makepyfile("""
import pytest
Expand Down

0 comments on commit 439ce9a

Please sign in to comment.