Skip to content

Commit ffab7be

Browse files
authored
Merge branch 'pytest-dev:master' into feat/enable-pytest-cov_toml_
2 parents 844f4cc + 28db055 commit ffab7be

File tree

13 files changed

+101
-38
lines changed

13 files changed

+101
-38
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 3.0.0
2+
current_version = 4.0.0
33
commit = True
44
tag = True
55

AUTHORS.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,9 @@ 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+
* Ben Greiner - https://github.com/bnavigator
55+
* Delgan - https://github.com/Delgan
56+
* Andre Brisco - https://github.com/abrisco
57+
* Colin O'Dell - https://github.com/colinodell
58+
* Ronny Pfannschmidt - https://github.com/RonnyPfannschmidt
59+
* Christian Fetzer - https://github.com/fetzerch

CHANGELOG.rst

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,36 @@ Changelog
22
=========
33

44

5-
4.0.0 (future)
6-
-------------------
5+
4.0.0 (2022-09-28)
6+
------------------
77

88
**Note that this release drops support for multiprocessing.**
99

1010

1111
* `--cov-fail-under` no longer causes `pytest --collect-only` to fail
12-
Contributed by Zac Hatfield-Dodds in
13-
`#511 <https://github.com/pytest-dev/pytest-cov/pull/511>`_.
12+
Contributed by Zac Hatfield-Dodds in `#511 <https://github.com/pytest-dev/pytest-cov/pull/511>`_.
1413
* Dropped support for multiprocessing (mostly because `issue 82408 <https://github.com/python/cpython/issues/82408>`_). This feature was
15-
mostly working but made out test suite very flaky and slow.
14+
mostly working but very broken in certain scenarios and made the test suite very flaky and slow.
1615

17-
There is builtin multiprocessing support in coverage and you can switch to that if you feel lucky. All you need is this in your
16+
There is builtin multiprocessing support in coverage and you can migrate to that. All you need is this in your
1817
``.coveragerc``::
1918

2019
[run]
2120
concurrency = multiprocessing
2221
parallel = true
2322
sigterm = true
23+
* Fixed deprecation in ``setup.py`` by trying to import setuptools before distutils.
24+
Contributed by Ben Greiner in `#545 <https://github.com/pytest-dev/pytest-cov/pull/545>`_.
25+
* Removed undesirable new lines that were displayed while reporting was disabled.
26+
Contributed by Delgan in `#540 <https://github.com/pytest-dev/pytest-cov/pull/540>`_.
27+
* Documentation fixes.
28+
Contributed by Andre Brisco in `#543 <https://github.com/pytest-dev/pytest-cov/pull/543>`_
29+
and Colin O'Dell in `#525 <https://github.com/pytest-dev/pytest-cov/pull/525>`_.
30+
* Added support for LCOV output format via `--cov-report=lcov`. Only works with coverage 6.3+.
31+
Contributed by Christian Fetzer in `#536 <https://github.com/pytest-dev/pytest-cov/issues/536>`_.
32+
* Modernized pytest hook implementation.
33+
Contributed by Bruno Oliveira in `#549 <https://github.com/pytest-dev/pytest-cov/pull/549>`_
34+
and Ronny Pfannschmidt in `#550 <https://github.com/pytest-dev/pytest-cov/pull/550>`_.
2435

2536

2637
3.0.0 (2021-10-04)

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ Overview
3939
.. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pytest-cov.svg
4040
:target: https://anaconda.org/conda-forge/pytest-cov
4141

42-
.. |commits-since| image:: https://img.shields.io/github/commits-since/pytest-dev/pytest-cov/v3.0.0.svg
42+
.. |commits-since| image:: https://img.shields.io/github/commits-since/pytest-dev/pytest-cov/v4.0.0.svg
4343
:alt: Commits since latest release
44-
:target: https://github.com/pytest-dev/pytest-cov/compare/v3.0.0...master
44+
:target: https://github.com/pytest-dev/pytest-cov/compare/v4.0.0...master
4545

4646
.. |wheel| image:: https://img.shields.io/pypi/wheel/pytest-cov.svg
4747
:alt: PyPI Wheel

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
year = '2016'
2424
author = 'pytest-cov contributors'
2525
copyright = f'{year}, {author}'
26-
version = release = '3.0.0'
26+
version = release = '4.0.0'
2727

2828
pygments_style = 'trac'
2929
templates_path = ['.']

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

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def run(self):
8787

8888
setup(
8989
name='pytest-cov',
90-
version='3.0.0',
90+
version='4.0.0',
9191
license='MIT',
9292
description='Pytest plugin for measuring coverage.',
9393
long_description='{}\n{}'.format(read('README.rst'), re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst'))),

src/pytest_cov/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"""pytest-cov: avoid already-imported warning: PYTEST_DONT_REWRITE."""
2-
__version__ = '3.0.0'
2+
__version__ = '4.0.0'

src/pytest_cov/compat.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,10 @@
33
except ImportError:
44
from io import StringIO
55

6-
import pytest
76

87
StringIO # pyflakes, this is for re-export
98

109

11-
if hasattr(pytest, 'hookimpl'):
12-
hookwrapper = pytest.hookimpl(hookwrapper=True)
13-
else:
14-
hookwrapper = pytest.mark.hookwrapper
15-
16-
1710
class SessionWrapper:
1811
def __init__(self, session):
1912
self._session = session

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: 11 additions & 8 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',
@@ -133,7 +136,7 @@ def _prepare_cov_source(cov_source):
133136
return None if True in cov_source else [path for path in cov_source if path is not True]
134137

135138

136-
@pytest.mark.tryfirst
139+
@pytest.hookimpl(tryfirst=True)
137140
def pytest_load_initial_conftests(early_config, parser, args):
138141
options = early_config.known_args_namespace
139142
no_cov = options.no_cov_should_warn = False
@@ -253,23 +256,23 @@ def pytest_sessionstart(self, session):
253256
if self.options.cov_context == 'test':
254257
session.config.pluginmanager.register(TestContextPlugin(self.cov_controller.cov), '_cov_contexts')
255258

259+
@pytest.hookimpl(optionalhook=True)
256260
def pytest_configure_node(self, node):
257261
"""Delegate to our implementation.
258262
259263
Mark this hook as optional in case xdist is not installed.
260264
"""
261265
if not self._disabled:
262266
self.cov_controller.configure_node(node)
263-
pytest_configure_node.optionalhook = True
264267

268+
@pytest.hookimpl(optionalhook=True)
265269
def pytest_testnodedown(self, node, error):
266270
"""Delegate to our implementation.
267271
268272
Mark this hook as optional in case xdist is not installed.
269273
"""
270274
if not self._disabled:
271275
self.cov_controller.testnodedown(node, error)
272-
pytest_testnodedown.optionalhook = True
273276

274277
def _should_report(self):
275278
return not (self.failed and self.options.no_cov_on_fail)
@@ -280,7 +283,7 @@ def _failed_cov_total(self):
280283

281284
# we need to wrap pytest_runtestloop. by the time pytest_sessionfinish
282285
# runs, it's too late to set testsfailed
283-
@compat.hookwrapper
286+
@pytest.hookimpl(hookwrapper=True)
284287
def pytest_runtestloop(self, session):
285288
yield
286289

@@ -356,7 +359,7 @@ def pytest_runtest_setup(self, item):
356359
def pytest_runtest_teardown(self, item):
357360
embed.cleanup()
358361

359-
@compat.hookwrapper
362+
@pytest.hookimpl(hookwrapper=True)
360363
def pytest_runtest_call(self, item):
361364
if (item.get_closest_marker('no_cover')
362365
or 'no_cover' in getattr(item, 'fixturenames', ())):

tests/test_pytest_cov.py

Lines changed: 42 additions & 6 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

@@ -1761,8 +1794,11 @@ def bad_init():
17611794
monkeypatch.setattr(sys, 'stderr', buff)
17621795
monkeypatch.setitem(os.environ, 'COV_CORE_SOURCE', 'foobar')
17631796
exec(payload)
1764-
assert buff.getvalue() == '''pytest-cov: Failed to setup subprocess coverage. Environ: {'COV_CORE_SOURCE': 'foobar'} Exception: SpecificError()
1765-
'''
1797+
expected = (
1798+
"pytest-cov: Failed to setup subprocess coverage. "
1799+
"Environ: {'COV_CORE_SOURCE': 'foobar'} Exception: SpecificError()\n"
1800+
)
1801+
assert buff.getvalue() == expected
17661802

17671803

17681804
def test_double_cov(testdir):

0 commit comments

Comments
 (0)