Skip to content

Commit

Permalink
Add support for LCOV output
Browse files Browse the repository at this point in the history
Coverage.py 6.3 gained support for the LCOV output format. Add support
for this to pytest-cov via '--cov-report=lcov[:dest]'.

Fix: #535
  • Loading branch information
fetzerch authored and ionelmc committed Sep 28, 2022
1 parent 1211d31 commit f7fced5
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 12 deletions.
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ Authors
* Danilo Šegan - https://github.com/dsegan
* Michał Bielawski - https://github.com/D3X
* Zac Hatfield-Dodds - https://github.com/Zac-HD
* Christian Fetzer - https://github.com/fetzerch
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Changelog
concurrency = multiprocessing
parallel = true
sigterm = true
* Added support for LCOV output format via `--cov-report=lcov`. Only works with coverage 6.3+.
Contributed by Christian Fetzer in
`#536 <https://github.com/pytest-dev/pytest-cov/issues/536>`_.

* Use modern way to specify hook options to avoid deprecation warnings with pytest >=7.2.

Expand Down
4 changes: 2 additions & 2 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ The complete list of command line options is:

--cov=PATH Measure coverage for filesystem path. (multi-allowed)
--cov-report=type Type of report to generate: term, term-missing,
annotate, html, xml (multi-allowed). term, term-
annotate, html, xml, lcov (multi-allowed). term, term-
missing may be followed by ":skip-covered". annotate,
html and xml may be followed by ":DEST" where DEST
html, xml and lcov may be followed by ":DEST" where DEST
specifies the output location. Use --cov-report= to
not generate any output.
--cov-config=path Config file for coverage. Default: .coveragerc
Expand Down
8 changes: 5 additions & 3 deletions docs/reporting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Reporting

It is possible to generate any combination of the reports for a single test run.

The available reports are terminal (with or without missing line numbers shown), HTML, XML and
The available reports are terminal (with or without missing line numbers shown), HTML, XML, LCOV and
annotated source code.

The terminal report without line numbers (default)::
Expand Down Expand Up @@ -49,19 +49,21 @@ The terminal report with skip covered::

You can use ``skip-covered`` with ``term-missing`` as well. e.g. ``--cov-report term-missing:skip-covered``

These three report options output to files without showing anything on the terminal::
These four report options output to files without showing anything on the terminal::

pytest --cov-report html
--cov-report xml
--cov-report lcov
--cov-report annotate
--cov=myproj tests/

The output location for each of these reports can be specified. The output location for the XML
The output location for each of these reports can be specified. The output location for the XML and LCOV
report is a file. Where as the output location for the HTML and annotated source code reports are
directories::

pytest --cov-report html:cov_html
--cov-report xml:cov.xml
--cov-report lcov:cov.info
--cov-report annotate:cov_annotate
--cov=myproj tests/

Expand Down
12 changes: 12 additions & 0 deletions src/pytest_cov/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,18 @@ def summary(self, stream):
total = self.cov.xml_report(ignore_errors=True, outfile=output)
stream.write('Coverage XML written to file %s\n' % (self.cov.config.xml_output if output is None else output))

# Produce lcov report if wanted.
if 'lcov' in self.cov_report:
output = self.cov_report['lcov']
with _backup(self.cov, "config"):
self.cov.lcov_report(ignore_errors=True, outfile=output)

# We need to call Coverage.report here, just to get the total
# Coverage.lcov_report doesn't return any total and we need it for --cov-fail-under.
total = self.cov.report(ignore_errors=True, file=_NullFile)

stream.write('Coverage LCOV written to file %s\n' % (self.cov.config.lcov_output if output is None else output))

return total


Expand Down
9 changes: 6 additions & 3 deletions src/pytest_cov/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class CovReportWarning(PytestCovWarning):


def validate_report(arg):
file_choices = ['annotate', 'html', 'xml']
file_choices = ['annotate', 'html', 'xml', 'lcov']
term_choices = ['term', 'term-missing']
term_modifier_choices = ['skip-covered']
all_choices = term_choices + file_choices
Expand All @@ -39,6 +39,9 @@ def validate_report(arg):
msg = f'invalid choice: "{arg}" (choose from "{all_choices}")'
raise argparse.ArgumentTypeError(msg)

if report_type == 'lcov' and coverage.version_info <= (6, 3):
raise argparse.ArgumentTypeError('LCOV output is only supported with coverage.py >= 6.3')

if len(values) == 1:
return report_type, None

Expand Down Expand Up @@ -96,9 +99,9 @@ def pytest_addoption(parser):
group.addoption('--cov-report', action=StoreReport, default={},
metavar='TYPE', type=validate_report,
help='Type of report to generate: term, term-missing, '
'annotate, html, xml (multi-allowed). '
'annotate, html, xml, lcov (multi-allowed). '
'term, term-missing may be followed by ":skip-covered". '
'annotate, html and xml may be followed by ":DEST" '
'annotate, html, xml and lcov may be followed by ":DEST" '
'where DEST specifies the output location. '
'Use --cov-report= to not generate any output.')
group.addoption('--cov-config', action='store', default='.coveragerc',
Expand Down
41 changes: 37 additions & 4 deletions tests/test_pytest_cov.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ def test_foo(cov):
CHILD_SCRIPT_RESULT = '[56] * 100%'
PARENT_SCRIPT_RESULT = '9 * 100%'
DEST_DIR = 'cov_dest'
REPORT_NAME = 'cov.xml'
XML_REPORT_NAME = 'cov.xml'
LCOV_REPORT_NAME = 'cov.info'

xdist_params = pytest.mark.parametrize('opts', [
'',
Expand Down Expand Up @@ -333,18 +334,50 @@ def test_xml_output_dir(testdir):

result = testdir.runpytest('-v',
'--cov=%s' % script.dirpath(),
'--cov-report=xml:' + REPORT_NAME,
'--cov-report=xml:' + XML_REPORT_NAME,
script)

result.stdout.fnmatch_lines([
'*- coverage: platform *, python * -*',
'Coverage XML written to file ' + REPORT_NAME,
'Coverage XML written to file ' + XML_REPORT_NAME,
'*10 passed*',
])
assert testdir.tmpdir.join(REPORT_NAME).check()
assert testdir.tmpdir.join(XML_REPORT_NAME).check()
assert result.ret == 0


@pytest.mark.skipif("coverage.version_info < (6, 3)")
def test_lcov_output_dir(testdir):
script = testdir.makepyfile(SCRIPT)

result = testdir.runpytest('-v',
'--cov=%s' % script.dirpath(),
'--cov-report=lcov:' + LCOV_REPORT_NAME,
script)

result.stdout.fnmatch_lines([
'*- coverage: platform *, python * -*',
'Coverage LCOV written to file ' + LCOV_REPORT_NAME,
'*10 passed*',
])
assert testdir.tmpdir.join(LCOV_REPORT_NAME).check()
assert result.ret == 0


@pytest.mark.skipif("coverage.version_info >= (6, 3)")
def test_lcov_not_supported(testdir):
script = testdir.makepyfile("a = 1")
result = testdir.runpytest('-v',
'--cov=%s' % script.dirpath(),
'--cov-report=lcov',
script,
)
result.stderr.fnmatch_lines([
'*argument --cov-report: LCOV output is only supported with coverage.py >= 6.3',
])
assert result.ret != 0


def test_term_output_dir(testdir):
script = testdir.makepyfile(SCRIPT)

Expand Down

0 comments on commit f7fced5

Please sign in to comment.