Skip to content

Commit 0b60995

Browse files
committed
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: pytest-dev#535
1 parent 4cfbe14 commit 0b60995

File tree

10 files changed

+82
-24
lines changed

10 files changed

+82
-24
lines changed

.appveyor.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,22 @@ environment:
88
matrix:
99
- TOXENV: check
1010
- TOXENV: 'py36-pytest46-xdist127-coverage55,py36-pytest46-xdist133-coverage55,py36-pytest54-xdist133-coverage55,py36-pytest62-xdist202-coverage55'
11-
- TOXENV: 'py37-pytest46-xdist127-coverage55,py37-pytest46-xdist133-coverage55,py37-pytest54-xdist133-coverage55,py37-pytest62-xdist202-coverage55'
12-
- TOXENV: 'py38-pytest46-xdist133-coverage55,py38-pytest54-xdist133-coverage55,py38-pytest62-xdist202-coverage55'
13-
- TOXENV: 'py39-pytest62-xdist202-coverage55'
14-
- TOXENV: 'pypy3-pytest46-xdist127-coverage55,pypy3-pytest46-xdist133-coverage55,pypy3-pytest54-xdist133-coverage55,pypy3-pytest62-xdist202-coverage55'
11+
- TOXENV: 'py37-pytest46-xdist127-coverage63,py37-pytest46-xdist133-coverage63,py37-pytest54-xdist133-coverage63,py37-pytest62-xdist202-coverage63'
12+
- TOXENV: 'py38-pytest46-xdist133-coverage63,py38-pytest54-xdist133-coverage63,py38-pytest62-xdist202-coverage63'
13+
- TOXENV: 'py39-pytest62-xdist202-coverage63'
14+
- TOXENV: 'pypy3-pytest46-xdist127-coverage63,pypy3-pytest46-xdist133-coverage63,pypy3-pytest54-xdist133-coverage63,pypy3-pytest62-xdist202-coverage63'
1515
matrix:
1616
exclude:
1717
- image: Visual Studio 2015
1818
TOXENV: 'py36-pytest46-xdist127-coverage55,py36-pytest46-xdist133-coverage55,py36-pytest54-xdist133-coverage55,py36-pytest62-xdist202-coverage55'
1919
- image: Visual Studio 2015
20-
TOXENV: 'py37-pytest46-xdist127-coverage55,py37-pytest46-xdist133-coverage55,py37-pytest54-xdist133-coverage55,py37-pytest62-xdist202-coverage55'
20+
TOXENV: 'py37-pytest46-xdist127-coverage63,py37-pytest46-xdist133-coverage63,py37-pytest54-xdist133-coverage63,py37-pytest62-xdist202-coverage63'
2121
- image: Visual Studio 2015
22-
TOXENV: 'py38-pytest46-xdist133-coverage55,py38-pytest54-xdist133-coverage55,py38-pytest62-xdist202-coverage55'
22+
TOXENV: 'py38-pytest46-xdist133-coverage63,py38-pytest54-xdist133-coverage63,py38-pytest62-xdist202-coverage63'
2323
- image: Visual Studio 2015
24-
TOXENV: 'py39-pytest62-xdist202-coverage55'
24+
TOXENV: 'py39-pytest62-xdist202-coverage63'
2525
- image: Visual Studio 2015
26-
TOXENV: 'pypy3-pytest46-xdist127-coverage55,pypy3-pytest46-xdist133-coverage55,pypy3-pytest54-xdist133-coverage55,pypy3-pytest62-xdist202-coverage55'
26+
TOXENV: 'pypy3-pytest46-xdist127-coverage63,pypy3-pytest46-xdist133-coverage63,pypy3-pytest54-xdist133-coverage63,pypy3-pytest62-xdist202-coverage63'
2727
init:
2828
- ps: echo $env:TOXENV
2929
- ps: ls C:\Python*

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
7171
- name: Tox tests
7272
run: |
73-
tox -v -e ${{ matrix.tox-python-version }}-${{ matrix.tox-extra-versions }}-coverage55
73+
tox -v -e ${{ matrix.tox-python-version }}-${{ matrix.tox-extra-versions }}-coverage63
7474
7575
allgood:
7676
needs: test

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
@@ -8,6 +8,9 @@ Changelog
88
* `--cov-fail-under` no longer causes `pytest --collect-only` to fail
99
Contributed by Zac Hatfield-Dodds in
1010
`#511 <https://github.com/pytest-dev/pytest-cov/pull/511>`_.
11+
* Added support for LCOV output format via `--cov-report=lcov`. Only works with coverage 6.3+.
12+
Contributed by Christian Fetzer in
13+
`#536 <https://github.com/pytest-dev/pytest-cov/issues/536>`_.
1114

1215

1316
3.0.0 (2021-10-04)

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

tox.ini

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ passenv =
1313
[tox]
1414
envlist =
1515
check
16-
py{36,37,py,py3}-pytest46-xdist127-coverage{55}
17-
py{36,37,38,py3}-pytest{46,54}-xdist133-coverage{55}
18-
py{36,37,38,39,310,py3}-pytest{62}-xdist202-coverage{55}
16+
py36-pytest46-xdist127-coverage{55}
17+
py36-pytest{46,54}-xdist133-coverage{55}
18+
py36-pytest{62}-xdist202-coverage{55}
19+
py{37,py,py3}-pytest46-xdist127-coverage{63}
20+
py{37,38,py3}-pytest{46,54}-xdist133-coverage{63}
21+
py{37,38,39,310,py3}-pytest{62}-xdist202-coverage{63}
1922
docs
2023

2124
[testenv]
@@ -49,6 +52,7 @@ setenv =
4952
coverage53: _DEP_COVERAGE=coverage==5.3.1
5053
coverage54: _DEP_COVERAGE=coverage==5.4
5154
coverage55: _DEP_COVERAGE=coverage==5.5
55+
coverage63: _DEP_COVERAGE=coverage==6.3
5256
# For testing against a coverage.py working tree.
5357
coveragedev: _DEP_COVERAGE=-e{env:COVERAGE_HOME}
5458
passenv =

0 commit comments

Comments
 (0)