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 Jason R. Coombs
Jurko Gospodnetić Jurko Gospodnetić
Katarzyna Jachim Katarzyna Jachim
Kevin Cox
Maciek Fijalkowski Maciek Fijalkowski
Maho Maho
Marc Schlaich Marc Schlaich

View File

@ -67,6 +67,8 @@
- add a new ``--noconftest`` argument which ignores all ``conftest.py`` files. - 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) 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. """ 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. Based on initial code from Ross Lawley.
""" """
import py import py
@ -93,11 +95,15 @@ class LogXML(object):
classnames = names[:-1] classnames = names[:-1]
if self.prefix: if self.prefix:
classnames.insert(0, self.prefix) classnames.insert(0, self.prefix)
self.tests.append(Junit.testcase( attrs = {
classname=".".join(classnames), "classname": ".".join(classnames),
name=bin_xml_escape(names[-1]), "name": bin_xml_escape(names[-1]),
time=0 "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): def _write_captured_output(self, report):
for capname in ('out', 'err'): for capname in ('out', 'err'):

View File

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