Skip to content

Bugfix: junitxml violates Jenkins/xUnit JUnit schema #4493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

jhunkeler
Copy link
Contributor

@jhunkeler jhunkeler commented Dec 2, 2018

At my institution we use Jenkins and xUnit to record test results created with pytest. Downgrading xUnit and JUnit to a version that uses an older schema has become impossible, due to security vulnerabilities, and plugin compatibility with the latest Jenkins releases. This schema problem has been a known issue for a while judging from the chatter in a few issues here.

My point is, we can't ingest test results from our pipelines at all. There's no going back.

This PR should take care of the problem.

Related: #1126 #3808

Results (mine)

$ pytest --junitxml=result.xml testing/test_junitxml.py 
============================= test session starts ==============================
platform darwin -- Python 3.6.7, pytest-4.0.2.dev16+g8305d00.d20181202, py-1.7.0, pluggy-0.8.0
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/Users/____/Downloads/pytest/.hypothesis/examples')
rootdir: /Users/____/Downloads/pytest, inifile: tox.ini
plugins: xdist-1.24.1, forked-0.2, hypothesis-3.82.1
collected 57 items                                                             

testing/test_junitxml.py ............................................... [ 82%]
...x......                                                               [100%]

--------- generated xml file: /Users/____/Downloads/pytest/result.xml ---------
=========================== short test summary info ============================
XFAIL testing/test_junitxml.py::test_runs_twice_xdist
  reason: [NOTRUN] hangs
===================== 56 passed, 1 xfailed in 4.29 seconds =====================

XML output example:

# Dump reformatted XML data
$ xmllint --format result.txt
<?xml version="1.0" encoding="utf-8"?>
<testsuite errors="0" failures="0" name="pytest" skipped="1" tests="57" time="4.298">
  <testcase classname="testing.test_junitxml.TestPython" name="test_summing_simple" time="0.190">
    <system-out><!-- Truncated log for brevity -->
</system-out>
  </testcase>
  <!-- ... Additional testcase records ... -->
</testsuite>

Validation:

Jenkins xUnit plugin

# See: https://github.com/jenkinsci/xunit-plugin/tree/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd
$ xmllint --noout --schema junit-10.xsd result.xml 
result.xml validates
$ xmllint --noout --schema junit-9.xsd result.xml 
result.xml validates
$ xmllint --noout --schema junit-8.xsd result.xml 
result.xml validates

* Remove non-standard testcase elements: 'file' and 'line'
* Replace testcase element 'skips' with 'skipped'
* Time resolution uses the standard format: 0.000
* Tests use corrected XML output with proper attributes
@codecov
Copy link

codecov bot commented Dec 2, 2018

Codecov Report

Merging #4493 into master will decrease coverage by 0.33%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4493      +/-   ##
==========================================
- Coverage    95.9%   95.57%   -0.34%     
==========================================
  Files         111      111              
  Lines       25085    25076       -9     
  Branches     2447     2443       -4     
==========================================
- Hits        24059    23966      -93     
- Misses        724      797      +73     
- Partials      302      313      +11
Flag Coverage Δ
#docs ?
#doctesting ?
#linting ?
#linux 94.22% <100%> (-1.52%) ⬇️
#nobyte ?
#numpy 41.04% <0%> (-52.42%) ⬇️
#pexpect 41.04% <0%> (-0.85%) ⬇️
#py27 92.89% <100%> (-1.16%) ⬇️
#py34 ?
#py35 ?
#py36 ?
#py37 92.28% <100%> (-1.81%) ⬇️
#trial 41.04% <0%> (-52.42%) ⬇️
#windows 93.51% <100%> (-0.61%) ⬇️
#xdist 93.95% <100%> (-0.04%) ⬇️
Impacted Files Coverage Δ
testing/test_junitxml.py 97.71% <100%> (-0.19%) ⬇️
src/_pytest/junitxml.py 93.33% <100%> (-0.05%) ⬇️
src/_pytest/assertion/util.py 92.92% <0%> (-5.19%) ⬇️
src/_pytest/pytester.py 82.31% <0%> (-5.14%) ⬇️
testing/test_pdb.py 95.11% <0%> (-3.92%) ⬇️
src/_pytest/compat.py 93.75% <0%> (-3.13%) ⬇️
src/_pytest/doctest.py 94.46% <0%> (-2.38%) ⬇️
testing/python/integration.py 89.36% <0%> (-2.13%) ⬇️
src/_pytest/cacheprovider.py 96.15% <0%> (-0.97%) ⬇️
src/_pytest/assertion/rewrite.py 95.21% <0%> (-0.5%) ⬇️
... and 5 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d8e00c9...c18f40d. Read the comment docs.

@@ -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]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this one iis a breaking change, well have to sort out the details

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please elaborate. What does it break?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jhunkeler off hand we cant rule out that people rely on the invalid extra attributes we output

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or to elaborate - junitxml output has been brokenly incorrect for so long that we now are likely to break other peoples code with the fix and we have to account for that in some way (be it a major release for this fix or a opt-in for correct behaviour that will later be a opt out and finally be phased out

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about creating an pytest.ini option to configure this then? junitxml_strict=true?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this would need to be based on features btw.

}
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: url cannot be present in testcase either.

@RonnyPfannschmidt
Copy link
Member

i'd like to note that its painful to leave a good change like this in the limbo over release management and compatibility shenanigans

my correctness hat would like to see this in asap, but with my maintenance/compatibility hat on i have to assess impact on potential users of the wrong things as well

@nicoddemus
Copy link
Member

my correctness hat would like to see this in asap, but with my maintenance/compatibility hat on i have to assess impact on potential users of the wrong things as well

An option would fit this bill I guess?

@RonnyPfannschmidt
Copy link
Member

@nicoddemus correct - your idea about an option would be nice - but based on the diff i fear that it might generate a unjust/unreasonable maintenance burden with trickiness for both @jhunkeler as the person creating the pr as well as for the pytest core team that will keep on maintaining the junitxml plugin

i'd like to get @jhunkeler 's input as his situation (aka junitxml breaks with modern plugin versions is certainly absolutely unacceptable and should be sorted out asap

@jhunkeler
Copy link
Contributor Author

We're dead in the water so to speak. In the worst case I could temporarily impose the changes I've made by forcing our build/test infrastructure to install pytest from my branch, but I'd really rather not. I understand your concerns, @RonnyPfannschmidt, and can appreciate the battle between "we need it" and "this will break the world". These are tough choices to make -- I respect that.

I've started to implement a variation of what @nicoddemus has suggested. I feel like this won't be the last time this will come up. Maybe not now, however, in a few years they could change their schema again and we'll find ourselves in the same situation.

Short of verifying XSDs directly using [yet-another-dependency-here], we could maintain a tiny dictionary of allowed terms for certain elements like testcase and testsuite(s). This should maintain compatibility going forward, and open the door to potentially supporting different JUnit-powered systems.

Maintaining a test suite for all of these subtle modifications, on the other hand, will be horrendous.

@RonnyPfannschmidt
Copy link
Member

@jhunkeler a while back i had some initial contact about bringing efforts together with one of the maintainers of the junitxml reporter for unittests

i believe that the junitxml format integration and generators should eventually move to a own package that can sort out the details of junitxml format versions

@nicoddemus
Copy link
Member

superseded by #4511

@nicoddemus nicoddemus closed this Dec 13, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants