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 Feb 17, 2017
1 parent 208fae5 commit 7b0837e
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 3 deletions.
22 changes: 20 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,24 @@ 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":
close_report = next((rep for rep in self.open_reports
if rep.nodeid == report.nodeid), 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 +361,10 @@ def pytest_runtest_logreport(self, report):
self.update_testcase_duration(report)
if report.when == "teardown":
self.finalize(report)
close_report = next((rep for rep in self.open_reports
if (rep.nodeid == report.nodeid)), 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 +397,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
25 changes: 24 additions & 1 deletion 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 Expand Up @@ -986,4 +1009,4 @@ class Report(BaseReport):

test_case = minidom.parse(str(path)).getElementsByTagName('testcase')[0]

assert (test_case.getAttribute('url') == test_url), "The URL did not get written to the xml"
assert (test_case.getAttribute('url') == test_url), "The URL did not get written to the xml"

0 comments on commit 7b0837e

Please sign in to comment.