diff --git a/AUTHORS b/AUTHORS index 1316f7b8f2a..57cfa76bcb7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -114,6 +114,7 @@ Jonas Obrist Jordan Guymon Jordan Moldow Jordan Speicher +Joseph Hunkeler Joshua Bronson Jurko Gospodnetić Justyna Janczyszyn diff --git a/changelog/3547.bugfix.rst b/changelog/3547.bugfix.rst new file mode 100644 index 00000000000..2c20661d583 --- /dev/null +++ b/changelog/3547.bugfix.rst @@ -0,0 +1 @@ +``--junitxml`` emits XML data compatible with JUnit's offical schema releases. diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 09847c942da..30c87149373 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -110,20 +110,14 @@ def record_testreport(self, testreport): classnames = names[:-1] if self.xml.prefix: classnames.insert(0, self.xml.prefix) - attrs = { - "classname": ".".join(classnames), - "name": bin_xml_escape(names[-1]), - "file": testreport.location[0], - } - if testreport.location[1] is not None: - attrs["line"] = testreport.location[1] + attrs = {"classname": ".".join(classnames), "name": bin_xml_escape(names[-1])} if hasattr(testreport, "url"): attrs["url"] = testreport.url self.attrs = attrs self.attrs.update(existing_attrs) # restore any user-defined attributes def to_xml(self): - testcase = Junit.testcase(time=self.duration, **self.attrs) + testcase = Junit.testcase(time="%.3f" % self.duration, **self.attrs) testcase.append(self.make_properties_node()) for node in self.nodes: testcase.append(node) @@ -543,7 +537,7 @@ def pytest_sessionfinish(self): name=self.suite_name, errors=self.stats["error"], failures=self.stats["failure"], - skips=self.stats["skipped"], + skipped=self.stats["skipped"], tests=numtests, time="%.3f" % suite_time_delta, ).unicode(indent=0) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index c9dc39f82b7..da4a2bfb3d6 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -107,7 +107,7 @@ def test_xpass(): result, dom = runandparse(testdir) assert result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(name="pytest", errors=0, failures=1, skips=2, tests=5) + node.assert_attr(name="pytest", errors=0, failures=1, skipped=2, tests=5) def test_summing_simple_with_errors(self, testdir): testdir.makepyfile( @@ -133,7 +133,7 @@ def test_xpass(): result, dom = runandparse(testdir) assert result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(name="pytest", errors=1, failures=2, skips=1, tests=5) + node.assert_attr(name="pytest", errors=1, failures=2, skipped=1, tests=5) def test_timing_function(self, testdir): testdir.makepyfile( @@ -170,12 +170,7 @@ def test_function(arg): node = dom.find_first_by_tag("testsuite") node.assert_attr(errors=1, tests=1) tnode = node.find_first_by_tag("testcase") - tnode.assert_attr( - file="test_setup_error.py", - line="5", - classname="test_setup_error", - name="test_function", - ) + tnode.assert_attr(classname="test_setup_error", name="test_function") fnode = tnode.find_first_by_tag("error") fnode.assert_attr(message="test setup failure") assert "ValueError" in fnode.toxml() @@ -197,12 +192,7 @@ def test_function(arg): assert result.ret node = dom.find_first_by_tag("testsuite") tnode = node.find_first_by_tag("testcase") - tnode.assert_attr( - file="test_teardown_error.py", - line="6", - classname="test_teardown_error", - name="test_function", - ) + tnode.assert_attr(classname="test_teardown_error", name="test_function") fnode = tnode.find_first_by_tag("error") fnode.assert_attr(message="test teardown failure") assert "ValueError" in fnode.toxml() @@ -243,14 +233,9 @@ def test_skip(): result, dom = runandparse(testdir) assert result.ret == 0 node = dom.find_first_by_tag("testsuite") - node.assert_attr(skips=1) + node.assert_attr(skipped=1) tnode = node.find_first_by_tag("testcase") - tnode.assert_attr( - file="test_skip_contains_name_reason.py", - line="1", - classname="test_skip_contains_name_reason", - name="test_skip", - ) + tnode.assert_attr(classname="test_skip_contains_name_reason", name="test_skip") snode = tnode.find_first_by_tag("skipped") snode.assert_attr(type="pytest.skip", message="hello23") @@ -266,13 +251,10 @@ def test_skip(): result, dom = runandparse(testdir) assert result.ret == 0 node = dom.find_first_by_tag("testsuite") - node.assert_attr(skips=1) + node.assert_attr(skipped=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr( - file="test_mark_skip_contains_name_reason.py", - line="1", - classname="test_mark_skip_contains_name_reason", - name="test_skip", + classname="test_mark_skip_contains_name_reason", name="test_skip" ) snode = tnode.find_first_by_tag("skipped") snode.assert_attr(type="pytest.skip", message="hello24") @@ -290,13 +272,10 @@ def test_skip(): result, dom = runandparse(testdir) assert result.ret == 0 node = dom.find_first_by_tag("testsuite") - node.assert_attr(skips=1) + node.assert_attr(skipped=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr( - file="test_mark_skipif_contains_name_reason.py", - line="2", - classname="test_mark_skipif_contains_name_reason", - name="test_skip", + classname="test_mark_skipif_contains_name_reason", name="test_skip" ) snode = tnode.find_first_by_tag("skipped") snode.assert_attr(type="pytest.skip", message="hello25") @@ -329,10 +308,7 @@ def test_method(self): node.assert_attr(failures=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr( - file="test_classname_instance.py", - line="1", - classname="test_classname_instance.TestClass", - name="test_method", + classname="test_classname_instance.TestClass", name="test_method" ) def test_classname_nested_dir(self, testdir): @@ -343,12 +319,7 @@ def test_classname_nested_dir(self, testdir): node = dom.find_first_by_tag("testsuite") node.assert_attr(failures=1) tnode = node.find_first_by_tag("testcase") - tnode.assert_attr( - file=os.path.join("sub", "test_hello.py"), - line="0", - classname="sub.test_hello", - name="test_func", - ) + tnode.assert_attr(classname="sub.test_hello", name="test_func") def test_internal_error(self, testdir): testdir.makeconftest("def pytest_runtest_protocol(): 0 / 0") @@ -384,12 +355,7 @@ def test_fail(): node = dom.find_first_by_tag("testsuite") node.assert_attr(failures=1, tests=1) tnode = node.find_first_by_tag("testcase") - tnode.assert_attr( - file="test_failure_function.py", - line="3", - classname="test_failure_function", - name="test_fail", - ) + tnode.assert_attr(classname="test_failure_function", name="test_fail") fnode = tnode.find_first_by_tag("failure") fnode.assert_attr(message="ValueError: 42") assert "ValueError" in fnode.toxml() @@ -446,10 +412,7 @@ def test_func(arg1): tnode = node.find_nth_by_tag("testcase", index) tnode.assert_attr( - file="test_failure_escape.py", - line="1", - classname="test_failure_escape", - name="test_func[%s]" % char, + classname="test_failure_escape", name="test_func[%s]" % char ) sysout = tnode.find_first_by_tag("system-out") text = sysout.text @@ -470,18 +433,10 @@ def test_hello(self): node = dom.find_first_by_tag("testsuite") node.assert_attr(failures=1, tests=2) tnode = node.find_first_by_tag("testcase") - tnode.assert_attr( - file="test_junit_prefixing.py", - line="0", - classname="xyz.test_junit_prefixing", - name="test_func", - ) + tnode.assert_attr(classname="xyz.test_junit_prefixing", name="test_func") tnode = node.find_nth_by_tag("testcase", 1) tnode.assert_attr( - file="test_junit_prefixing.py", - line="3", - classname="xyz.test_junit_prefixing.TestHello", - name="test_hello", + classname="xyz.test_junit_prefixing.TestHello", name="test_hello" ) def test_xfailure_function(self, testdir): @@ -495,14 +450,9 @@ def test_xfail(): result, dom = runandparse(testdir) assert not result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(skips=1, tests=1) + node.assert_attr(skipped=1, tests=1) tnode = node.find_first_by_tag("testcase") - tnode.assert_attr( - file="test_xfailure_function.py", - line="1", - classname="test_xfailure_function", - name="test_xfail", - ) + tnode.assert_attr(classname="test_xfailure_function", name="test_xfail") fnode = tnode.find_first_by_tag("skipped") fnode.assert_attr(message="expected test failure") # assert "ValueError" in fnode.toxml() @@ -538,14 +488,9 @@ def test_xpass(): result, dom = runandparse(testdir) # assert result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(skips=0, tests=1) + node.assert_attr(skipped=0, tests=1) tnode = node.find_first_by_tag("testcase") - tnode.assert_attr( - file="test_xfailure_xpass.py", - line="1", - classname="test_xfailure_xpass", - name="test_xpass", - ) + tnode.assert_attr(classname="test_xfailure_xpass", name="test_xpass") def test_xfailure_xpass_strict(self, testdir): testdir.makepyfile( @@ -559,14 +504,9 @@ def test_xpass(): result, dom = runandparse(testdir) # assert result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(skips=0, tests=1) + node.assert_attr(skipped=0, tests=1) tnode = node.find_first_by_tag("testcase") - tnode.assert_attr( - file="test_xfailure_xpass_strict.py", - line="1", - classname="test_xfailure_xpass_strict", - name="test_xpass", - ) + tnode.assert_attr(classname="test_xfailure_xpass_strict", name="test_xpass") fnode = tnode.find_first_by_tag("failure") fnode.assert_attr(message="[XPASS(strict)] This needs to fail!") @@ -577,8 +517,6 @@ def test_collect_error(self, testdir): node = dom.find_first_by_tag("testsuite") node.assert_attr(errors=1, tests=1) tnode = node.find_first_by_tag("testcase") - tnode.assert_attr(file="test_collect_error.py", name="test_collect_error") - assert tnode["line"] is None fnode = tnode.find_first_by_tag("error") fnode.assert_attr(message="collection failure") assert "SyntaxError" in fnode.toxml() @@ -761,7 +699,7 @@ def repr_failure(self, excinfo): result, dom = runandparse(testdir) assert result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(errors=0, failures=1, skips=0, tests=1) + node.assert_attr(errors=0, failures=1, skipped=0, tests=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr(name="myfile.xyz") fnode = tnode.find_first_by_tag("failure") @@ -1123,20 +1061,18 @@ def test_pass(): assert "INTERNALERROR" not in result.stdout.str() - items = sorted( - "%(classname)s %(name)s %(file)s" % x for x in dom.find_by_tag("testcase") - ) + items = sorted("%(classname)s %(name)s" % x for x in dom.find_by_tag("testcase")) import pprint pprint.pprint(items) assert items == [ - u"conftest a conftest.py", - u"conftest a conftest.py", - u"conftest b conftest.py", - u"test_fancy_items_regression a test_fancy_items_regression.py", - u"test_fancy_items_regression a test_fancy_items_regression.py", - u"test_fancy_items_regression b test_fancy_items_regression.py", - u"test_fancy_items_regression test_pass" u" test_fancy_items_regression.py", + u"conftest a", + u"conftest a", + u"conftest b", + u"test_fancy_items_regression a", + u"test_fancy_items_regression a", + u"test_fancy_items_regression b", + u"test_fancy_items_regression test_pass", ]