diff --git a/AUTHORS b/AUTHORS index 75f66b0b9..7f866e74f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -39,6 +39,7 @@ Janne Vanhala Jason R. Coombs Jurko Gospodnetić Katarzyna Jachim +Kevin Cox Maciek Fijalkowski Maho Marc Schlaich diff --git a/CHANGELOG b/CHANGELOG index cba430e14..07eecd171 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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) ----------------------------- diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 2a1220625..c12fa084a 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -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'): diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index d8b18b177..6258cb473 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -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 ...