Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f7fced5

Browse files
fetzerchionelmc
authored andcommittedSep 28, 2022
Add support for LCOV output
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
1 parent 1211d31 commit f7fced5

File tree

7 files changed

+66
-12
lines changed

7 files changed

+66
-12
lines changed
 

‎AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ Authors
5151
* Danilo Šegan - https://github.com/dsegan
5252
* Michał Bielawski - https://github.com/D3X
5353
* Zac Hatfield-Dodds - https://github.com/Zac-HD
54+
* Christian Fetzer - https://github.com/fetzerch

‎CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ Changelog
2121
concurrency = multiprocessing
2222
parallel = true
2323
sigterm = true
24+
* Added support for LCOV output format via `--cov-report=lcov`. Only works with coverage 6.3+.
25+
Contributed by Christian Fetzer in
26+
`#536 <https://github.com/pytest-dev/pytest-cov/issues/536>`_.
2427

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

‎docs/config.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ The complete list of command line options is:
5656

5757
--cov=PATH Measure coverage for filesystem path. (multi-allowed)
5858
--cov-report=type Type of report to generate: term, term-missing,
59-
annotate, html, xml (multi-allowed). term, term-
59+
annotate, html, xml, lcov (multi-allowed). term, term-
6060
missing may be followed by ":skip-covered". annotate,
61-
html and xml may be followed by ":DEST" where DEST
61+
html, xml and lcov may be followed by ":DEST" where DEST
6262
specifies the output location. Use --cov-report= to
6363
not generate any output.
6464
--cov-config=path Config file for coverage. Default: .coveragerc

‎docs/reporting.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Reporting
33

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

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

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

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

52-
These three report options output to files without showing anything on the terminal::
52+
These four report options output to files without showing anything on the terminal::
5353

5454
pytest --cov-report html
5555
--cov-report xml
56+
--cov-report lcov
5657
--cov-report annotate
5758
--cov=myproj tests/
5859

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

6364
pytest --cov-report html:cov_html
6465
--cov-report xml:cov.xml
66+
--cov-report lcov:cov.info
6567
--cov-report annotate:cov_annotate
6668
--cov=myproj tests/
6769

‎src/pytest_cov/engine.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,18 @@ def summary(self, stream):
196196
total = self.cov.xml_report(ignore_errors=True, outfile=output)
197197
stream.write('Coverage XML written to file %s\n' % (self.cov.config.xml_output if output is None else output))
198198

199+
# Produce lcov report if wanted.
200+
if 'lcov' in self.cov_report:
201+
output = self.cov_report['lcov']
202+
with _backup(self.cov, "config"):
203+
self.cov.lcov_report(ignore_errors=True, outfile=output)
204+
205+
# We need to call Coverage.report here, just to get the total
206+
# Coverage.lcov_report doesn't return any total and we need it for --cov-fail-under.
207+
total = self.cov.report(ignore_errors=True, file=_NullFile)
208+
209+
stream.write('Coverage LCOV written to file %s\n' % (self.cov.config.lcov_output if output is None else output))
210+
199211
return total
200212

201213

‎src/pytest_cov/plugin.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class CovReportWarning(PytestCovWarning):
2929

3030

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

42+
if report_type == 'lcov' and coverage.version_info <= (6, 3):
43+
raise argparse.ArgumentTypeError('LCOV output is only supported with coverage.py >= 6.3')
44+
4245
if len(values) == 1:
4346
return report_type, None
4447

@@ -96,9 +99,9 @@ def pytest_addoption(parser):
9699
group.addoption('--cov-report', action=StoreReport, default={},
97100
metavar='TYPE', type=validate_report,
98101
help='Type of report to generate: term, term-missing, '
99-
'annotate, html, xml (multi-allowed). '
102+
'annotate, html, xml, lcov (multi-allowed). '
100103
'term, term-missing may be followed by ":skip-covered". '
101-
'annotate, html and xml may be followed by ":DEST" '
104+
'annotate, html, xml and lcov may be followed by ":DEST" '
102105
'where DEST specifies the output location. '
103106
'Use --cov-report= to not generate any output.')
104107
group.addoption('--cov-config', action='store', default='.coveragerc',

‎tests/test_pytest_cov.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ def test_foo(cov):
150150
CHILD_SCRIPT_RESULT = '[56] * 100%'
151151
PARENT_SCRIPT_RESULT = '9 * 100%'
152152
DEST_DIR = 'cov_dest'
153-
REPORT_NAME = 'cov.xml'
153+
XML_REPORT_NAME = 'cov.xml'
154+
LCOV_REPORT_NAME = 'cov.info'
154155

155156
xdist_params = pytest.mark.parametrize('opts', [
156157
'',
@@ -333,18 +334,50 @@ def test_xml_output_dir(testdir):
333334

334335
result = testdir.runpytest('-v',
335336
'--cov=%s' % script.dirpath(),
336-
'--cov-report=xml:' + REPORT_NAME,
337+
'--cov-report=xml:' + XML_REPORT_NAME,
337338
script)
338339

339340
result.stdout.fnmatch_lines([
340341
'*- coverage: platform *, python * -*',
341-
'Coverage XML written to file ' + REPORT_NAME,
342+
'Coverage XML written to file ' + XML_REPORT_NAME,
342343
'*10 passed*',
343344
])
344-
assert testdir.tmpdir.join(REPORT_NAME).check()
345+
assert testdir.tmpdir.join(XML_REPORT_NAME).check()
345346
assert result.ret == 0
346347

347348

349+
@pytest.mark.skipif("coverage.version_info < (6, 3)")
350+
def test_lcov_output_dir(testdir):
351+
script = testdir.makepyfile(SCRIPT)
352+
353+
result = testdir.runpytest('-v',
354+
'--cov=%s' % script.dirpath(),
355+
'--cov-report=lcov:' + LCOV_REPORT_NAME,
356+
script)
357+
358+
result.stdout.fnmatch_lines([
359+
'*- coverage: platform *, python * -*',
360+
'Coverage LCOV written to file ' + LCOV_REPORT_NAME,
361+
'*10 passed*',
362+
])
363+
assert testdir.tmpdir.join(LCOV_REPORT_NAME).check()
364+
assert result.ret == 0
365+
366+
367+
@pytest.mark.skipif("coverage.version_info >= (6, 3)")
368+
def test_lcov_not_supported(testdir):
369+
script = testdir.makepyfile("a = 1")
370+
result = testdir.runpytest('-v',
371+
'--cov=%s' % script.dirpath(),
372+
'--cov-report=lcov',
373+
script,
374+
)
375+
result.stderr.fnmatch_lines([
376+
'*argument --cov-report: LCOV output is only supported with coverage.py >= 6.3',
377+
])
378+
assert result.ret != 0
379+
380+
348381
def test_term_output_dir(testdir):
349382
script = testdir.makepyfile(SCRIPT)
350383

0 commit comments

Comments
 (0)
Please sign in to comment.