pytest-coverage-commentator icon indicating copy to clipboard operation
pytest-coverage-commentator copied to clipboard

Support --cov-report=xml:DEST reports

Open mjpieters opened this issue 3 years ago • 1 comments

Currently, the commentator only supports parsing term coverage reports (the default report type), which do not support a destination file. As a result, you must redirect your test output to a file for the commentator to be able to load that report. This is a pain if you also rely on the output being visible in your GH action (but you can use pytest | tee coverage.txt to avoid having to run the tests twice).

But, pytest-cov lets you set the type of report it generates (more than once to produce multple outputs), and the annotate, html and xml reports all support a destination. E.g. pytest --cov-report=xml:coverage.xml will generate a coverage.xml file.

The XML output is the most suitable to parsing; the downside is that there is no platform information in that output. I don't think that's really a big problem. You'll have to run the file through an XML parser, of course.

You can otherwise produce the exact same output as the default term report if you parse the XML file by iterating over all packages -> package -> classes -> class tags. For the Name, Stmts, Miss, Branch, BrPart and Cover columns, you need to collect the following information:

  • Name: the filename attribute of the class element

  • Stmts: count the number of child line elements under the class element.

  • Miss: count the number of child line elements with the hits attribute set to 0, or alternatively, with hits not set to 1 (hits is always 1 or 0).

  • Branch: for all child line elements with branch="true", parse the condition-coverage tag string, using the regex ^\d+% \((?P<brachhits>:\d+)/(?P<branches>\d+)\)$, and sum the branches numbers. E.g. for the value 100% (2/2), branches is 2.

  • BrPart: for all child line elements with branch="true", parse the condition-coverage tag string, using the regex ^\d*% \((?P<brachhits>:\d+)/(?P<branches>\d+)$, and only if branchhits is not equal to branches, sum the branchhits value (these are partial branch hits). E.g. for the value 50% (1/2), branchhits is 1, and not equal to branches=2, so count 1 partial branch hit. For 0% (0/2) or 100% (2/2), you ignore the branchhits value.

  • Cover: when processing all child line elements, the coverage is calculated across 4 different numbers:

    1. total lines (the Stmts value)
    2. total branches (the Branch value)
    3. total hits (Stmts - Miss, all lines with hits="1")
    4. total branch hits (all branchhits values. This differs from the BrPart value which only sums non-zero branchhits values that are not equal to branches).

    The percentage is then calculated using (total hits + total branch hits) / (total lines + total branches) * 100. This is a fraction between 0 and 100. If the value is not exactly 0 but below 1, display 1. If the value is not exactly 100, but above 99, display 99. All other values are rounded to the nearest whole number. The extra rules for 1 and 99 there prevent 0.5 becoming 0 and 99.5 becoming 100.

    This is detailed in the coverage FAQ and verified by reading the results.py source code (specifically the ratio_covered and display_covered methods).

The term report can be configured to show missing line numbers too, I didn't search for how those are summarised (look for the format_lines() function). The coverage percentage precision is also adjustable, altering the near-0 and near-100 rules. I used the default rules above.

The advantage of all this work is that you can then display any Cobertura-compatible XML coverage report in this way.

mjpieters avatar Nov 24 '21 13:11 mjpieters

Have the same issue. Found solution on similar GitHub Action: Pytest Coverage Comment

There are possibilities to generate a coverage report or junit.xml report, also there are coverage-percentage calculations. You can check it, maybe it will solve your issue.

image

MishaKav avatar Apr 03 '22 20:04 MishaKav