Add `file` and `line` attributes to junit-xml output.

This adds the `file` and `line` attributes to the junit-xml output
which can be used by tooling to identify where tests come from. This can be
used for many things such as IDEs jumping to failures and test
runners evenly balancing tests among multiple executors.

Update test_junitxml.py

Foo.
This commit is contained in:
Kevin Cox 2015-06-30 19:30:20 -04:00
parent 76497c2542
commit 7fa27af408
4 changed files with 39 additions and 5 deletions

View File

@ -39,6 +39,7 @@ Janne Vanhala
Jason R. Coombs
Jurko Gospodnetić
Katarzyna Jachim
Kevin Cox
Maciek Fijalkowski
Maho
Marc Schlaich

View File

@ -67,6 +67,8 @@
- add a new ``--noconftest`` argument which ignores all ``conftest.py`` files.
- add ``file`` and ``line`` attributes to JUnit-XML output.
2.7.2 (compared to 2.7.1)
-----------------------------

View File

@ -1,5 +1,7 @@
""" report test results in JUnit-XML format, for use with Hudson and build integration servers.
Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
Based on initial code from Ross Lawley.
"""
import py
@ -93,11 +95,15 @@ class LogXML(object):
classnames = names[:-1]
if self.prefix:
classnames.insert(0, self.prefix)
self.tests.append(Junit.testcase(
classname=".".join(classnames),
name=bin_xml_escape(names[-1]),
time=0
))
attrs = {
"classname": ".".join(classnames),
"name": bin_xml_escape(names[-1]),
"file": report.location[0],
"time": 0,
}
if report.location[1] is not None:
attrs["line"] = report.location[1]
self.tests.append(Junit.testcase(**attrs))
def _write_captured_output(self, report):
for capname in ('out', 'err'):

View File

@ -70,6 +70,8 @@ class TestPython:
assert_attr(node, errors=1, tests=0)
tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode,
file="test_setup_error.py",
line="2",
classname="test_setup_error",
name="test_function")
fnode = tnode.getElementsByTagName("error")[0]
@ -88,6 +90,8 @@ class TestPython:
assert_attr(node, skips=1)
tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode,
file="test_skip_contains_name_reason.py",
line="1",
classname="test_skip_contains_name_reason",
name="test_skip")
snode = tnode.getElementsByTagName("skipped")[0]
@ -108,6 +112,8 @@ class TestPython:
assert_attr(node, failures=1)
tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode,
file="test_classname_instance.py",
line="1",
classname="test_classname_instance.TestClass",
name="test_method")
@ -120,6 +126,8 @@ class TestPython:
assert_attr(node, failures=1)
tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode,
file=os.path.join("sub", "test_hello.py"),
line="0",
classname="sub.test_hello",
name="test_func")
@ -151,6 +159,8 @@ class TestPython:
assert_attr(node, failures=1, tests=1)
tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode,
file="test_failure_function.py",
line="1",
classname="test_failure_function",
name="test_fail")
fnode = tnode.getElementsByTagName("failure")[0]
@ -193,6 +203,8 @@ class TestPython:
tnode = node.getElementsByTagName("testcase")[index]
assert_attr(tnode,
file="test_failure_escape.py",
line="1",
classname="test_failure_escape",
name="test_func[%s]" % char)
sysout = tnode.getElementsByTagName('system-out')[0]
@ -214,10 +226,14 @@ class TestPython:
assert_attr(node, failures=1, tests=2)
tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode,
file="test_junit_prefixing.py",
line="0",
classname="xyz.test_junit_prefixing",
name="test_func")
tnode = node.getElementsByTagName("testcase")[1]
assert_attr(tnode,
file="test_junit_prefixing.py",
line="3",
classname="xyz.test_junit_prefixing."
"TestHello",
name="test_hello")
@ -234,6 +250,8 @@ class TestPython:
assert_attr(node, skips=1, tests=0)
tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode,
file="test_xfailure_function.py",
line="1",
classname="test_xfailure_function",
name="test_xfail")
fnode = tnode.getElementsByTagName("skipped")[0]
@ -253,6 +271,8 @@ class TestPython:
assert_attr(node, skips=1, tests=0)
tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode,
file="test_xfailure_xpass.py",
line="1",
classname="test_xfailure_xpass",
name="test_xpass")
fnode = tnode.getElementsByTagName("skipped")[0]
@ -267,8 +287,10 @@ class TestPython:
assert_attr(node, errors=1, tests=0)
tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode,
file="test_collect_error.py",
#classname="test_collect_error",
name="test_collect_error")
assert tnode.getAttributeNode("line") is None
fnode = tnode.getElementsByTagName("error")[0]
assert_attr(fnode, message="collection failure")
assert "SyntaxError" in fnode.toxml()
@ -281,8 +303,10 @@ class TestPython:
assert_attr(node, skips=1, tests=0)
tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode,
file="test_collect_skipped.py",
#classname="test_collect_error",
name="test_collect_skipped")
assert tnode.getAttributeNode("line") is None # py.test doesn't give us a line here.
fnode = tnode.getElementsByTagName("skipped")[0]
assert_attr(fnode, message="collection skipped")
@ -510,6 +534,7 @@ def test_unicode_issue368(testdir):
longrepr = ustr
sections = []
nodeid = "something"
location = 'tests/filename.py', 42, 'TestClass.method'
report = Report()
# hopefully this is not too brittle ...