Merge pull request #2236 from KKoukiou/junitxml-change-schema
Change junitxml.py to produce results that comply with Junitxml schema
This commit is contained in:
commit
0c94f517a1
|
@ -87,7 +87,10 @@ Bug Fixes
|
||||||
3.0.7 (unreleased)
|
3.0.7 (unreleased)
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
*
|
* Change junitxml.py to produce reports that comply with Junitxml schema.
|
||||||
|
If the same test fails with failure in call and then errors in teardown
|
||||||
|
we split testcase element into two, one containing the error and the other
|
||||||
|
the failure. (`#2228`_) Thanks to `@kkoukiou`_ for the PR.
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
|
@ -95,6 +98,10 @@ Bug Fixes
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
|
.. _@kkoukiou: https://github.com/KKoukiou
|
||||||
|
|
||||||
|
.. _#2228: https://github.com/pytest-dev/pytest/issues/2228
|
||||||
|
|
||||||
|
|
||||||
3.0.6 (2017-01-22)
|
3.0.6 (2017-01-22)
|
||||||
==================
|
==================
|
||||||
|
|
|
@ -273,6 +273,9 @@ class LogXML(object):
|
||||||
self.node_reporters = {} # nodeid -> _NodeReporter
|
self.node_reporters = {} # nodeid -> _NodeReporter
|
||||||
self.node_reporters_ordered = []
|
self.node_reporters_ordered = []
|
||||||
self.global_properties = []
|
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):
|
def finalize(self, report):
|
||||||
nodeid = getattr(report, 'nodeid', report)
|
nodeid = getattr(report, 'nodeid', report)
|
||||||
|
@ -332,14 +335,33 @@ class LogXML(object):
|
||||||
-> teardown node2
|
-> teardown node2
|
||||||
-> teardown node1
|
-> teardown node1
|
||||||
"""
|
"""
|
||||||
|
close_report = None
|
||||||
if report.passed:
|
if report.passed:
|
||||||
if report.when == "call": # ignore setup/teardown
|
if report.when == "call": # ignore setup/teardown
|
||||||
reporter = self._opentestcase(report)
|
reporter = self._opentestcase(report)
|
||||||
reporter.append_pass(report)
|
reporter.append_pass(report)
|
||||||
elif report.failed:
|
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)
|
reporter = self._opentestcase(report)
|
||||||
if report.when == "call":
|
if report.when == "call":
|
||||||
reporter.append_failure(report)
|
reporter.append_failure(report)
|
||||||
|
self.open_reports.append(report)
|
||||||
else:
|
else:
|
||||||
reporter.append_error(report)
|
reporter.append_error(report)
|
||||||
elif report.skipped:
|
elif report.skipped:
|
||||||
|
@ -348,6 +370,17 @@ class LogXML(object):
|
||||||
self.update_testcase_duration(report)
|
self.update_testcase_duration(report)
|
||||||
if report.when == "teardown":
|
if report.when == "teardown":
|
||||||
self.finalize(report)
|
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):
|
def update_testcase_duration(self, report):
|
||||||
"""accumulates total duration for nodeid from given report and updates
|
"""accumulates total duration for nodeid from given report and updates
|
||||||
|
@ -380,8 +413,9 @@ class LogXML(object):
|
||||||
suite_stop_time = time.time()
|
suite_stop_time = time.time()
|
||||||
suite_time_delta = suite_stop_time - self.suite_start_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('<?xml version="1.0" encoding="utf-8"?>')
|
||||||
|
|
||||||
logfile.write(Junit.testsuite(
|
logfile.write(Junit.testsuite(
|
||||||
|
|
|
@ -189,6 +189,29 @@ class TestPython(object):
|
||||||
fnode.assert_attr(message="test teardown failure")
|
fnode.assert_attr(message="test teardown failure")
|
||||||
assert "ValueError" in fnode.toxml()
|
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):
|
def test_skip_contains_name_reason(self, testdir):
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
import pytest
|
import pytest
|
||||||
|
|
Loading…
Reference in New Issue