Skip to content

Commit 9293062

Browse files
committed
Merge pull request #809 from kevincox/junitxml-file-attribute
Add `file` and `line` attributes to junit-xml output.
2 parents 76497c2 + 7fa27af commit 9293062

File tree

4 files changed

+39
-5
lines changed

4 files changed

+39
-5
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Janne Vanhala
3939
Jason R. Coombs
4040
Jurko Gospodnetić
4141
Katarzyna Jachim
42+
Kevin Cox
4243
Maciek Fijalkowski
4344
Maho
4445
Marc Schlaich

CHANGELOG

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767

6868
- add a new ``--noconftest`` argument which ignores all ``conftest.py`` files.
6969

70+
- add ``file`` and ``line`` attributes to JUnit-XML output.
71+
7072
2.7.2 (compared to 2.7.1)
7173
-----------------------------
7274

_pytest/junitxml.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
""" report test results in JUnit-XML format, for use with Hudson and build integration servers.
22
3+
Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
4+
35
Based on initial code from Ross Lawley.
46
"""
57
import py
@@ -93,11 +95,15 @@ def _opentestcase(self, report):
9395
classnames = names[:-1]
9496
if self.prefix:
9597
classnames.insert(0, self.prefix)
96-
self.tests.append(Junit.testcase(
97-
classname=".".join(classnames),
98-
name=bin_xml_escape(names[-1]),
99-
time=0
100-
))
98+
attrs = {
99+
"classname": ".".join(classnames),
100+
"name": bin_xml_escape(names[-1]),
101+
"file": report.location[0],
102+
"time": 0,
103+
}
104+
if report.location[1] is not None:
105+
attrs["line"] = report.location[1]
106+
self.tests.append(Junit.testcase(**attrs))
101107

102108
def _write_captured_output(self, report):
103109
for capname in ('out', 'err'):

testing/test_junitxml.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ def test_function(arg):
7070
assert_attr(node, errors=1, tests=0)
7171
tnode = node.getElementsByTagName("testcase")[0]
7272
assert_attr(tnode,
73+
file="test_setup_error.py",
74+
line="2",
7375
classname="test_setup_error",
7476
name="test_function")
7577
fnode = tnode.getElementsByTagName("error")[0]
@@ -88,6 +90,8 @@ def test_skip():
8890
assert_attr(node, skips=1)
8991
tnode = node.getElementsByTagName("testcase")[0]
9092
assert_attr(tnode,
93+
file="test_skip_contains_name_reason.py",
94+
line="1",
9195
classname="test_skip_contains_name_reason",
9296
name="test_skip")
9397
snode = tnode.getElementsByTagName("skipped")[0]
@@ -108,6 +112,8 @@ def test_method(self):
108112
assert_attr(node, failures=1)
109113
tnode = node.getElementsByTagName("testcase")[0]
110114
assert_attr(tnode,
115+
file="test_classname_instance.py",
116+
line="1",
111117
classname="test_classname_instance.TestClass",
112118
name="test_method")
113119

@@ -120,6 +126,8 @@ def test_classname_nested_dir(self, testdir):
120126
assert_attr(node, failures=1)
121127
tnode = node.getElementsByTagName("testcase")[0]
122128
assert_attr(tnode,
129+
file=os.path.join("sub", "test_hello.py"),
130+
line="0",
123131
classname="sub.test_hello",
124132
name="test_func")
125133

@@ -151,6 +159,8 @@ def test_fail():
151159
assert_attr(node, failures=1, tests=1)
152160
tnode = node.getElementsByTagName("testcase")[0]
153161
assert_attr(tnode,
162+
file="test_failure_function.py",
163+
line="1",
154164
classname="test_failure_function",
155165
name="test_fail")
156166
fnode = tnode.getElementsByTagName("failure")[0]
@@ -193,6 +203,8 @@ def test_func(arg1):
193203

194204
tnode = node.getElementsByTagName("testcase")[index]
195205
assert_attr(tnode,
206+
file="test_failure_escape.py",
207+
line="1",
196208
classname="test_failure_escape",
197209
name="test_func[%s]" % char)
198210
sysout = tnode.getElementsByTagName('system-out')[0]
@@ -214,10 +226,14 @@ def test_hello(self):
214226
assert_attr(node, failures=1, tests=2)
215227
tnode = node.getElementsByTagName("testcase")[0]
216228
assert_attr(tnode,
229+
file="test_junit_prefixing.py",
230+
line="0",
217231
classname="xyz.test_junit_prefixing",
218232
name="test_func")
219233
tnode = node.getElementsByTagName("testcase")[1]
220234
assert_attr(tnode,
235+
file="test_junit_prefixing.py",
236+
line="3",
221237
classname="xyz.test_junit_prefixing."
222238
"TestHello",
223239
name="test_hello")
@@ -234,6 +250,8 @@ def test_xfail():
234250
assert_attr(node, skips=1, tests=0)
235251
tnode = node.getElementsByTagName("testcase")[0]
236252
assert_attr(tnode,
253+
file="test_xfailure_function.py",
254+
line="1",
237255
classname="test_xfailure_function",
238256
name="test_xfail")
239257
fnode = tnode.getElementsByTagName("skipped")[0]
@@ -253,6 +271,8 @@ def test_xpass():
253271
assert_attr(node, skips=1, tests=0)
254272
tnode = node.getElementsByTagName("testcase")[0]
255273
assert_attr(tnode,
274+
file="test_xfailure_xpass.py",
275+
line="1",
256276
classname="test_xfailure_xpass",
257277
name="test_xpass")
258278
fnode = tnode.getElementsByTagName("skipped")[0]
@@ -267,8 +287,10 @@ def test_collect_error(self, testdir):
267287
assert_attr(node, errors=1, tests=0)
268288
tnode = node.getElementsByTagName("testcase")[0]
269289
assert_attr(tnode,
290+
file="test_collect_error.py",
270291
#classname="test_collect_error",
271292
name="test_collect_error")
293+
assert tnode.getAttributeNode("line") is None
272294
fnode = tnode.getElementsByTagName("error")[0]
273295
assert_attr(fnode, message="collection failure")
274296
assert "SyntaxError" in fnode.toxml()
@@ -281,8 +303,10 @@ def test_collect_skipped(self, testdir):
281303
assert_attr(node, skips=1, tests=0)
282304
tnode = node.getElementsByTagName("testcase")[0]
283305
assert_attr(tnode,
306+
file="test_collect_skipped.py",
284307
#classname="test_collect_error",
285308
name="test_collect_skipped")
309+
assert tnode.getAttributeNode("line") is None # py.test doesn't give us a line here.
286310
fnode = tnode.getElementsByTagName("skipped")[0]
287311
assert_attr(fnode, message="collection skipped")
288312

@@ -510,6 +534,7 @@ class Report(BaseReport):
510534
longrepr = ustr
511535
sections = []
512536
nodeid = "something"
537+
location = 'tests/filename.py', 42, 'TestClass.method'
513538
report = Report()
514539

515540
# hopefully this is not too brittle ...

0 commit comments

Comments
 (0)