Skip to content

Commit f7fced5

Browse files
fetzerchionelmc
authored andcommitted
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

+1
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

+3
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

+2-2
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

+5-3
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

+12
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

+6-3
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

+37-4
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)