From f6d4ccff18e4a2f3f22a9ad5e34693e0d3860a7e Mon Sep 17 00:00:00 2001 From: Art Pelling Date: Tue, 9 Sep 2025 16:49:58 +0200 Subject: [PATCH 01/19] Fix syntax for pytest coverage report command --- docs/reporting.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reporting.rst b/docs/reporting.rst index 06bad7cc..70ac7165 100644 --- a/docs/reporting.rst +++ b/docs/reporting.rst @@ -82,7 +82,7 @@ Example for GitHub Actions with ``markdown-append``: .. code-block:: bash - pytest --cov-report=markdown-append:${GITHUB_STEP_SUMMARY}. + pytest --cov-report markdown-append:$GITHUB_STEP_SUMMARY --cov=myproj tests/ To disable the default ``term`` report provide an empty report: From 57e3fe1cb94b8dab10d656eab93efadd4d1bbb78 Mon Sep 17 00:00:00 2001 From: Andy Salnikov Date: Wed, 5 Nov 2025 16:35:33 -0800 Subject: [PATCH 02/19] Improve handling of ResourceWarning from sqlite3. The patch allows suppressing ResourceWarnings from sqlite3 by specifying filter in command line with -W option. --- CHANGELOG.rst | 11 +++++++++++ src/pytest_cov/plugin.py | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4524aad3..b9af4229 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,17 @@ Changelog ========= +* Improve handling of ResourceWarning from sqlite3. + + The plugin adds warning filter for sqlite3 ``ResourceWarning`` unclosed database (since 6.2.0). + It checks if there is already existing plugin for this message by comparing filter regular expression. + When filter is specified on command line the message is escaped and does not match an expected message. + A check for an escaped regular expression is added to handle this case. + + With this fix one can suppress ``ResourceWarning`` from sqlite3 from command line:: + + pytest -W "ignore:unclosed database in Date: Mon, 13 Oct 2025 16:12:57 +0200 Subject: [PATCH 03/19] match coverage 7.10.7 warnings --- tests/test_pytest_cov.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pytest_cov.py b/tests/test_pytest_cov.py index 6ca09fe5..c71d776b 100644 --- a/tests/test_pytest_cov.py +++ b/tests/test_pytest_cov.py @@ -1435,8 +1435,8 @@ def test_filterwarnings_error(testdir): result.stdout.fnmatch_lines(['* 1 passed *']) result.stderr.fnmatch_lines( [ - '* (module-not-measured)', - '* (no-data-collected)', + '* (module-not-measured)*', + '* (no-data-collected)*', '* CovReportWarning: Failed to generate report: No data to report.', ] ) From b305afea9eb22015ef506f0ccdfc7c39d98a886b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 2 Mar 2026 16:29:23 +0200 Subject: [PATCH 04/19] Add test and fix #641: total coverage is not saved from the last ran reporter, instead a simpler reporter is used. --- CHANGELOG.rst | 6 ++++++ src/pytest_cov/engine.py | 22 ++++++++++------------ tests/test_pytest_cov.py | 22 ++++++++++++++++++++++ 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b9af4229..523d0a55 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog ========= + +* Fixed total coverage computation to always be consistent, regardless of reporting settings. + Previously some reports could produce different total counts, and consequently can make --cov-fail-under behave different depending on + reporting options. + See `#641 `_. + * Improve handling of ResourceWarning from sqlite3. The plugin adds warning filter for sqlite3 ``ResourceWarning`` unclosed database (since 6.2.0). diff --git a/src/pytest_cov/engine.py b/src/pytest_cov/engine.py index ca631272..1de4db87 100644 --- a/src/pytest_cov/engine.py +++ b/src/pytest_cov/engine.py @@ -150,11 +150,9 @@ def sep(self, stream, s, txt): @_ensure_topdir def summary(self, stream): """Produce coverage reports.""" - total = None - if not self.cov_report: - with _backup(self.cov, 'config'): - return self.cov.report(show_missing=True, ignore_errors=True, file=_NullFile) + with _backup(self.cov, 'config'): + total = self.cov.report(ignore_errors=True, output_format='total', file=_NullFile) # Output coverage section header. if len(self.node_descs) == 1: @@ -182,7 +180,7 @@ def summary(self, stream): skip_covered = isinstance(self.cov_report, dict) and 'skip-covered' in self.cov_report.values() options.update({'skip_covered': skip_covered or None}) with _backup(self.cov, 'config'): - total = self.cov.report(**options) + self.cov.report(**options) # Produce annotated source code report if wanted. if 'annotate' in self.cov_report: @@ -194,7 +192,7 @@ def summary(self, stream): # Coverage.annotate don't return any total and we need it for --cov-fail-under. with _backup(self.cov, 'config'): - total = self.cov.report(ignore_errors=True, file=_NullFile) + self.cov.report(ignore_errors=True, file=_NullFile) if annotate_dir: stream.write(f'Coverage annotated source written to dir {annotate_dir}\n') else: @@ -204,21 +202,21 @@ def summary(self, stream): if 'html' in self.cov_report: output = self.cov_report['html'] with _backup(self.cov, 'config'): - total = self.cov.html_report(ignore_errors=True, directory=output) + self.cov.html_report(ignore_errors=True, directory=output) stream.write(f'Coverage HTML written to dir {self.cov.config.html_dir if output is None else output}\n') # Produce xml report if wanted. if 'xml' in self.cov_report: output = self.cov_report['xml'] with _backup(self.cov, 'config'): - total = self.cov.xml_report(ignore_errors=True, outfile=output) + self.cov.xml_report(ignore_errors=True, outfile=output) stream.write(f'Coverage XML written to file {self.cov.config.xml_output if output is None else output}\n') # Produce json report if wanted if 'json' in self.cov_report: output = self.cov_report['json'] with _backup(self.cov, 'config'): - total = self.cov.json_report(ignore_errors=True, outfile=output) + self.cov.json_report(ignore_errors=True, outfile=output) stream.write('Coverage JSON written to file %s\n' % (self.cov.config.json_output if output is None else output)) # Produce Markdown report if wanted. @@ -226,7 +224,7 @@ def summary(self, stream): output = self.cov_report['markdown'] with _backup(self.cov, 'config'): with Path(output).open('w') as output_file: - total = self.cov.report(ignore_errors=True, file=output_file, output_format='markdown') + self.cov.report(ignore_errors=True, file=output_file, output_format='markdown') stream.write(f'Coverage Markdown information written to file {output}\n') # Produce Markdown report if wanted, appending to output file @@ -234,7 +232,7 @@ def summary(self, stream): output = self.cov_report['markdown-append'] with _backup(self.cov, 'config'): with Path(output).open('a') as output_file: - total = self.cov.report(ignore_errors=True, file=output_file, output_format='markdown') + self.cov.report(ignore_errors=True, file=output_file, output_format='markdown') stream.write(f'Coverage Markdown information appended to file {output}\n') # Produce lcov report if wanted. @@ -245,7 +243,7 @@ def summary(self, stream): # 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) + self.cov.report(ignore_errors=True, file=_NullFile) stream.write(f'Coverage LCOV written to file {self.cov.config.lcov_output if output is None else output}\n') diff --git a/tests/test_pytest_cov.py b/tests/test_pytest_cov.py index c71d776b..1b2177be 100644 --- a/tests/test_pytest_cov.py +++ b/tests/test_pytest_cov.py @@ -494,6 +494,28 @@ def test_cov_min_50(testdir): result.stdout.fnmatch_lines(['Required test coverage of 50% reached. Total coverage: *%']) +def test_cov_reporting_bug_641(testdir, monkeypatch): + testdir.tmpdir.mkdir('src').join('foo.py').write('') + testdir.tmpdir.mkdir('tests').join('test_foo.py').write(""" +import foo + +def test_foo(): + pass +""") + testdir.tmpdir.join('.coveragerc').write(""" +[run] +relative_files = True +source = src, tests +""") + monkeypatch.setitem(os.environ, 'PYTHONPATH', os.pathsep.join([os.environ.get('PYTHONPATH', ''), 'src'])) + result = testdir.runpytest('-v', '--cov=src', '--cov-report=xml', '--cov-fail-under=100') + assert result.ret == 0 + result.stdout.fnmatch_lines(['Required test coverage of 100% reached. Total coverage: *%']) + result = testdir.runpytest('-v', '--cov=src', '--cov-report=', '--cov-fail-under=100') + assert result.ret == 0 + result.stdout.fnmatch_lines(['Required test coverage of 100% reached. Total coverage: *%']) + + def test_cov_min_float_value(testdir): script = testdir.makepyfile(SCRIPT) From be3366838e41a9cbefa714636a39c5c2f6d5f588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 2 Mar 2026 16:36:08 +0200 Subject: [PATCH 05/19] Update some test deps and add py 3.14 test grid. --- .editorconfig | 3 + .github/workflows/test.yml | 116 ++++++++++++++---------- .pre-commit-config.yaml | 6 +- .readthedocs.yml | 2 +- CHANGELOG.rst | 2 + ci/bootstrap.py | 2 +- ci/templates/.github/workflows/test.yml | 2 +- pyproject.toml | 1 + tox.ini | 20 ++-- 9 files changed, 86 insertions(+), 68 deletions(-) diff --git a/.editorconfig b/.editorconfig index 586c7367..08936d99 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,9 @@ insert_final_newline = true indent_size = 4 charset = utf-8 +[*.py] +max_line_length = 140 + [*.{bat,cmd,ps1}] end_of_line = crlf diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0b0ba059..b1f267e3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,152 +60,170 @@ jobs: toxpython: 'python3.11' tox_env: 'docs' os: 'ubuntu-latest' - - name: 'py39-pytest84-xdist38-coverage710 (ubuntu)' + - name: 'py39-pytest90-xdist38-coverage713 (ubuntu)' python: '3.9' toxpython: 'python3.9' python_arch: 'x64' - tox_env: 'py39-pytest84-xdist38-coverage710' + tox_env: 'py39-pytest90-xdist38-coverage713' os: 'ubuntu-latest' - - name: 'py39-pytest84-xdist38-coverage710 (windows)' + - name: 'py39-pytest90-xdist38-coverage713 (windows)' python: '3.9' toxpython: 'python3.9' python_arch: 'x64' - tox_env: 'py39-pytest84-xdist38-coverage710' + tox_env: 'py39-pytest90-xdist38-coverage713' os: 'windows-latest' - - name: 'py39-pytest84-xdist38-coverage710 (macos)' + - name: 'py39-pytest90-xdist38-coverage713 (macos)' python: '3.9' toxpython: 'python3.9' python_arch: 'arm64' - tox_env: 'py39-pytest84-xdist38-coverage710' + tox_env: 'py39-pytest90-xdist38-coverage713' os: 'macos-latest' - - name: 'py310-pytest84-xdist38-coverage710 (ubuntu)' + - name: 'py310-pytest90-xdist38-coverage713 (ubuntu)' python: '3.10' toxpython: 'python3.10' python_arch: 'x64' - tox_env: 'py310-pytest84-xdist38-coverage710' + tox_env: 'py310-pytest90-xdist38-coverage713' os: 'ubuntu-latest' - - name: 'py310-pytest84-xdist38-coverage710 (windows)' + - name: 'py310-pytest90-xdist38-coverage713 (windows)' python: '3.10' toxpython: 'python3.10' python_arch: 'x64' - tox_env: 'py310-pytest84-xdist38-coverage710' + tox_env: 'py310-pytest90-xdist38-coverage713' os: 'windows-latest' - - name: 'py310-pytest84-xdist38-coverage710 (macos)' + - name: 'py310-pytest90-xdist38-coverage713 (macos)' python: '3.10' toxpython: 'python3.10' python_arch: 'arm64' - tox_env: 'py310-pytest84-xdist38-coverage710' + tox_env: 'py310-pytest90-xdist38-coverage713' os: 'macos-latest' - - name: 'py311-pytest84-xdist38-coverage710 (ubuntu)' + - name: 'py311-pytest90-xdist38-coverage713 (ubuntu)' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' - tox_env: 'py311-pytest84-xdist38-coverage710' + tox_env: 'py311-pytest90-xdist38-coverage713' os: 'ubuntu-latest' - - name: 'py311-pytest84-xdist38-coverage710 (windows)' + - name: 'py311-pytest90-xdist38-coverage713 (windows)' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' - tox_env: 'py311-pytest84-xdist38-coverage710' + tox_env: 'py311-pytest90-xdist38-coverage713' os: 'windows-latest' - - name: 'py311-pytest84-xdist38-coverage710 (macos)' + - name: 'py311-pytest90-xdist38-coverage713 (macos)' python: '3.11' toxpython: 'python3.11' python_arch: 'arm64' - tox_env: 'py311-pytest84-xdist38-coverage710' + tox_env: 'py311-pytest90-xdist38-coverage713' os: 'macos-latest' - - name: 'py312-pytest84-xdist38-coverage710 (ubuntu)' + - name: 'py312-pytest90-xdist38-coverage713 (ubuntu)' python: '3.12' toxpython: 'python3.12' python_arch: 'x64' - tox_env: 'py312-pytest84-xdist38-coverage710' + tox_env: 'py312-pytest90-xdist38-coverage713' os: 'ubuntu-latest' - - name: 'py312-pytest84-xdist38-coverage710 (windows)' + - name: 'py312-pytest90-xdist38-coverage713 (windows)' python: '3.12' toxpython: 'python3.12' python_arch: 'x64' - tox_env: 'py312-pytest84-xdist38-coverage710' + tox_env: 'py312-pytest90-xdist38-coverage713' os: 'windows-latest' - - name: 'py312-pytest84-xdist38-coverage710 (macos)' + - name: 'py312-pytest90-xdist38-coverage713 (macos)' python: '3.12' toxpython: 'python3.12' python_arch: 'arm64' - tox_env: 'py312-pytest84-xdist38-coverage710' + tox_env: 'py312-pytest90-xdist38-coverage713' os: 'macos-latest' - - name: 'py313-pytest84-xdist38-coverage710 (ubuntu)' + - name: 'py313-pytest90-xdist38-coverage713 (ubuntu)' python: '3.13' toxpython: 'python3.13' python_arch: 'x64' - tox_env: 'py313-pytest84-xdist38-coverage710' + tox_env: 'py313-pytest90-xdist38-coverage713' os: 'ubuntu-latest' - - name: 'py313-pytest84-xdist38-coverage710 (windows)' + - name: 'py313-pytest90-xdist38-coverage713 (windows)' python: '3.13' toxpython: 'python3.13' python_arch: 'x64' - tox_env: 'py313-pytest84-xdist38-coverage710' + tox_env: 'py313-pytest90-xdist38-coverage713' os: 'windows-latest' - - name: 'py313-pytest84-xdist38-coverage710 (macos)' + - name: 'py313-pytest90-xdist38-coverage713 (macos)' python: '3.13' toxpython: 'python3.13' python_arch: 'arm64' - tox_env: 'py313-pytest84-xdist38-coverage710' + tox_env: 'py313-pytest90-xdist38-coverage713' os: 'macos-latest' - - name: 'pypy39-pytest84-xdist38-coverage710 (ubuntu)' + - name: 'py314-pytest90-xdist38-coverage713 (ubuntu)' + python: '3.14' + toxpython: 'python3.14' + python_arch: 'x64' + tox_env: 'py314-pytest90-xdist38-coverage713' + os: 'ubuntu-latest' + - name: 'py314-pytest90-xdist38-coverage713 (windows)' + python: '3.14' + toxpython: 'python3.14' + python_arch: 'x64' + tox_env: 'py314-pytest90-xdist38-coverage713' + os: 'windows-latest' + - name: 'py314-pytest90-xdist38-coverage713 (macos)' + python: '3.14' + toxpython: 'python3.14' + python_arch: 'arm64' + tox_env: 'py314-pytest90-xdist38-coverage713' + os: 'macos-latest' + - name: 'pypy39-pytest90-xdist38-coverage713 (ubuntu)' python: 'pypy-3.9' toxpython: 'pypy3.9' python_arch: 'x64' - tox_env: 'pypy39-pytest84-xdist38-coverage710' + tox_env: 'pypy39-pytest90-xdist38-coverage713' os: 'ubuntu-latest' - - name: 'pypy39-pytest84-xdist38-coverage710 (windows)' + - name: 'pypy39-pytest90-xdist38-coverage713 (windows)' python: 'pypy-3.9' toxpython: 'pypy3.9' python_arch: 'x64' - tox_env: 'pypy39-pytest84-xdist38-coverage710' + tox_env: 'pypy39-pytest90-xdist38-coverage713' os: 'windows-latest' - - name: 'pypy39-pytest84-xdist38-coverage710 (macos)' + - name: 'pypy39-pytest90-xdist38-coverage713 (macos)' python: 'pypy-3.9' toxpython: 'pypy3.9' python_arch: 'arm64' - tox_env: 'pypy39-pytest84-xdist38-coverage710' + tox_env: 'pypy39-pytest90-xdist38-coverage713' os: 'macos-latest' - - name: 'pypy310-pytest84-xdist38-coverage710 (ubuntu)' + - name: 'pypy310-pytest90-xdist38-coverage713 (ubuntu)' python: 'pypy-3.10' toxpython: 'pypy3.10' python_arch: 'x64' - tox_env: 'pypy310-pytest84-xdist38-coverage710' + tox_env: 'pypy310-pytest90-xdist38-coverage713' os: 'ubuntu-latest' - - name: 'pypy310-pytest84-xdist38-coverage710 (windows)' + - name: 'pypy310-pytest90-xdist38-coverage713 (windows)' python: 'pypy-3.10' toxpython: 'pypy3.10' python_arch: 'x64' - tox_env: 'pypy310-pytest84-xdist38-coverage710' + tox_env: 'pypy310-pytest90-xdist38-coverage713' os: 'windows-latest' - - name: 'pypy310-pytest84-xdist38-coverage710 (macos)' + - name: 'pypy310-pytest90-xdist38-coverage713 (macos)' python: 'pypy-3.10' toxpython: 'pypy3.10' python_arch: 'arm64' - tox_env: 'pypy310-pytest84-xdist38-coverage710' + tox_env: 'pypy310-pytest90-xdist38-coverage713' os: 'macos-latest' - - name: 'pypy311-pytest84-xdist38-coverage710 (ubuntu)' + - name: 'pypy311-pytest90-xdist38-coverage713 (ubuntu)' python: 'pypy-3.11' toxpython: 'pypy3.11' python_arch: 'x64' - tox_env: 'pypy311-pytest84-xdist38-coverage710' + tox_env: 'pypy311-pytest90-xdist38-coverage713' os: 'ubuntu-latest' - - name: 'pypy311-pytest84-xdist38-coverage710 (windows)' + - name: 'pypy311-pytest90-xdist38-coverage713 (windows)' python: 'pypy-3.11' toxpython: 'pypy3.11' python_arch: 'x64' - tox_env: 'pypy311-pytest84-xdist38-coverage710' + tox_env: 'pypy311-pytest90-xdist38-coverage713' os: 'windows-latest' - - name: 'pypy311-pytest84-xdist38-coverage710 (macos)' + - name: 'pypy311-pytest90-xdist38-coverage713 (macos)' python: 'pypy-3.11' toxpython: 'pypy3.11' python_arch: 'arm64' - tox_env: 'pypy311-pytest84-xdist38-coverage710' + tox_env: 'pypy311-pytest90-xdist38-coverage713' os: 'macos-latest' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: actions/setup-python@v6 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e8687d3d..f76d6a15 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,12 @@ # To install the git pre-commit hooks run: -# pre-commit install --install-hooks +# prek install --install-hooks --overwrite # To update the versions: -# pre-commit autoupdate +# prek autoupdate exclude: '^(\.tox|ci/templates|\.bumpversion\.cfg)(/|$)' # Note the order is intentional to avoid multiple passes of the hooks repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.12 + rev: v0.15.4 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix, --show-fixes, --unsafe-fixes] diff --git a/.readthedocs.yml b/.readthedocs.yml index 009a913c..a2d480bb 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -4,7 +4,7 @@ sphinx: configuration: docs/conf.py formats: all build: - os: ubuntu-22.04 + os: ubuntu-24.04 tools: python: "3" python: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 523d0a55..8a83194d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,8 @@ Changelog ========= +7.0.1 (2026-03-02) +------------------ * Fixed total coverage computation to always be consistent, regardless of reporting settings. Previously some reports could produce different total counts, and consequently can make --cov-fail-under behave different depending on diff --git a/ci/bootstrap.py b/ci/bootstrap.py index ee69e2c5..b5c83343 100755 --- a/ci/bootstrap.py +++ b/ci/bootstrap.py @@ -57,7 +57,7 @@ def main(): # This uses sys.executable the same way that the call in # cookiecutter-pylibrary/hooks/post_gen_project.py # invokes this bootstrap.py itself. - for line in subprocess.check_output([sys.executable, '-m', 'tox', '--listenvs'], universal_newlines=True).splitlines() + for line in subprocess.check_output([sys.executable, '-m', 'tox', '--listenvs'], text=True).splitlines() ] tox_environments = [line for line in tox_environments if line.startswith('py')] for template in templates_path.rglob('*'): diff --git a/ci/templates/.github/workflows/test.yml b/ci/templates/.github/workflows/test.yml index df6150ae..7e5d1416 100644 --- a/ci/templates/.github/workflows/test.yml +++ b/ci/templates/.github/workflows/test.yml @@ -88,7 +88,7 @@ jobs: {% endfor %} {% endfor %} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: actions/setup-python@v6 diff --git a/pyproject.toml b/pyproject.toml index 1125a4c5..f202cd8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Testing", diff --git a/tox.ini b/tox.ini index be756012..e674de8e 100644 --- a/tox.ini +++ b/tox.ini @@ -15,22 +15,11 @@ envlist = clean, check, docs, - {py39,py310,py311,py312,py313,pypy39,pypy310,pypy311}-{pytest84}-{xdist38}-{coverage710}, + {py39,py310,py311,py312,py313,py314,pypy39,pypy310,pypy311}-{pytest90}-{xdist38}-{coverage713}, report ignore_basepython_conflict = true [testenv] -basepython = - pypy38: {env:TOXPYTHON:pypy3.8} - pypy39: {env:TOXPYTHON:pypy3.9} - pypy310: {env:TOXPYTHON:pypy3.10} - py38: {env:TOXPYTHON:python3.8} - py39: {env:TOXPYTHON:python3.9} - py310: {env:TOXPYTHON:python3.10} - py311: {env:TOXPYTHON:python3.11} - py312: {env:TOXPYTHON:python3.12} - py313: {env:TOXPYTHON:python3.13} - {bootstrap,clean,check,report,docs}: {env:TOXPYTHON:python3} extras = testing setenv = PYTHONPATH={toxinidir}/tests @@ -43,6 +32,7 @@ setenv = pytest82: _DEP_PYTEST=pytest==8.2.2 pytest83: _DEP_PYTEST=pytest==8.3.5 pytest84: _DEP_PYTEST=pytest==8.4.2 + pytest90: _DEP_PYTEST=pytest==9.0.2 xdist32: _DEP_PYTESTXDIST=pytest-xdist==3.2.0 xdist33: _DEP_PYTESTXDIST=pytest-xdist==3.3.1 @@ -61,7 +51,11 @@ setenv = coverage77: _DEP_COVERAGE=coverage==7.7.1 coverage78: _DEP_COVERAGE=coverage==7.8.2 coverage79: _DEP_COVERAGE=coverage==7.9.2 - coverage710: _DEP_COVERAGE=coverage==7.10.6 + coverage710: _DEP_COVERAGE=coverage==7.10.7 + coverage711: _DEP_COVERAGE=coverage==7.11.3 + coverage712: _DEP_COVERAGE=coverage==7.12.0 + coverage713: _DEP_COVERAGE=coverage==7.13.4 + # For testing against a coverage.py working tree. coveragedev: _DEP_COVERAGE=-e{env:COVERAGE_HOME} passenv = From 699322cf4387b78a9c8567f47598f7688e080891 Mon Sep 17 00:00:00 2001 From: vivodi <103735539+vivodi@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:12:33 +0800 Subject: [PATCH 06/19] Fix typo in reporting.rst regarding coverage options --- docs/reporting.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reporting.rst b/docs/reporting.rst index 70ac7165..6c503179 100644 --- a/docs/reporting.rst +++ b/docs/reporting.rst @@ -49,7 +49,7 @@ The terminal report with skip covered:: You can use ``skip-covered`` with ``term-missing`` as well. e.g. ``--cov-report term-missing:skip-covered`` -If any reporting options are used then the default (``--cov-report=term`` is not added automatically). For example this would not show any +If any reporting options are used then the default (``--cov-report=term``) is not added automatically. For example this would not show any terminal output: .. code-block:: bash From 425d4e6968333b836f26580a780a245f18797f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 18:28:42 +0200 Subject: [PATCH 07/19] Bump actions. --- .github/workflows/test.yml | 2 +- ci/templates/.github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b1f267e3..e0dff506 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.cache/pip key: diff --git a/ci/templates/.github/workflows/test.yml b/ci/templates/.github/workflows/test.yml index 7e5d1416..70b1e2bc 100644 --- a/ci/templates/.github/workflows/test.yml +++ b/ci/templates/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.cache/pip key: From b04f3456f246de90a38a699835d6153b108e6616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 18:29:28 +0200 Subject: [PATCH 08/19] Regorganize the config page a bit and remove those ommit tests examples. Closes #736. --- docs/config.rst | 69 +++++++++++++++++++------------------------------ 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 4369d1e7..ef8754e2 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -2,24 +2,25 @@ Configuration ============= -This plugin provides a clean minimal set of command line options that are added to pytest. For -further control of coverage use a coverage config file. +This plugin provides a clean minimal set of command line options that are added to pytest. For +further control of coverage use a `coverage config file`_. + +CLI options: + --cov [SOURCE] Path or package name to measure during execution (multi-allowed). Use --cov= to not do any source filtering and record everything. + --cov-reset Reset cov sources accumulated in options so far. + --cov-report TYPE Type of report to generate: term, term-missing, annotate, html, xml, json, markdown, markdown-append, lcov (multi-allowed). term, term-missing may be followed by ":skip-covered". + annotate, html, xml, json, markdown, markdown-append 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 + --no-cov-on-fail Do not report coverage if test run fails. Default: False + --no-cov Disable coverage report completely (useful for debuggers). Default: False + --cov-fail-under MIN Fail if the total coverage is less than MIN. + --cov-append Do not delete coverage but append to current. Default: False + --cov-branch Enable branch coverage. Can also be specified in the `coverage config file`_ ``[run]`` section. + --cov-precision COV_PRECISION + Override the reporting precision. Can also be specified in the `coverage config file`_ ``[report]`` section. + --cov-context CONTEXT + Dynamic contexts to use. "test" for now. -For example if tests are contained within the directory tree being measured the tests may be -excluded if desired by using a .coveragerc file with the omit option set:: - - pytest --cov-config=.coveragerc - --cov=myproj - myproj/tests/ - -Where the .coveragerc file contains file globs:: - - [run] - omit = tests/* - -For full details refer to the `coverage config file`_ documentation. - -.. _`coverage config file`: https://coverage.readthedocs.io/en/latest/config.html .. note:: Important Note @@ -32,7 +33,7 @@ For full details refer to the `coverage config file`_ documentation. If you use the ``--cov-branch`` option then coverage's ``branch`` option will also get overridden. -If you wish to always add pytest-cov with pytest, you can use ``addopts`` under the ``pytest`` or ``tool:pytest`` section of +If you wish to always run pytest-cov with pytest, you can use ``addopts`` under the ``pytest`` or ``tool:pytest`` section of your ``setup.cfg``, or the ``tool.pytest.ini_options`` section of your ``pyproject.toml`` file. For example, in ``setup.cfg``: :: @@ -45,6 +46,12 @@ Or for ``pyproject.toml``: :: [tool.pytest.ini_options] addopts = "--cov= --cov-report html" + +.. note:: Important Note + + The ``--cov`` option has an optional argument. If it's your last option in addopts_ it might eat the next CLI argument, make sure to + force it to take a blank value if that's what you wanted by using ``--cov=`` (essentially the same as ``--cov=""``). + Caveats ======= @@ -57,27 +64,5 @@ might need to use ``--cov-config`` to make coverage use the correct configuratio Also, if you change the working directory and also use subprocesses in a test you might also need to use ``--cov-config`` to make pytest-cov use the expected configuration file in the subprocess. -Reference -========= - -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, json, markdown, markdown-append, lcov (multi-allowed). term, term- - missing may be followed by ":skip-covered". annotate, - html, xml, json, markdown, markdown-append 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 ---no-cov-on-fail Do not report coverage if test run fails. Default: - False ---no-cov Disable coverage report completely (useful for - debuggers). Default: False ---cov-reset Reset cov sources accumulated in options so far. - Mostly useful for scripts and configuration files. ---cov-fail-under=MIN Fail if the total coverage is less than MIN. ---cov-append Do not delete coverage but append to current. Default: - False ---cov-branch Enable branch coverage. ---cov-context Choose the method for setting the dynamic context. +.. _`coverage config file`: https://coverage.readthedocs.io/en/latest/config.html +.. _addopts: https://docs.pytest.org/en/stable/reference/reference.html#confval-addopts> From 5f297ed42814356af513ec0b869075c3ebfa2018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 18:29:35 +0200 Subject: [PATCH 09/19] Bump. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index e674de8e..fb4adc5b 100644 --- a/tox.ini +++ b/tox.ini @@ -54,7 +54,7 @@ setenv = coverage710: _DEP_COVERAGE=coverage==7.10.7 coverage711: _DEP_COVERAGE=coverage==7.11.3 coverage712: _DEP_COVERAGE=coverage==7.12.0 - coverage713: _DEP_COVERAGE=coverage==7.13.4 + coverage713: _DEP_COVERAGE=coverage==7.13.5 # For testing against a coverage.py working tree. coveragedev: _DEP_COVERAGE=-e{env:COVERAGE_HOME} From 14dc1c92d44108384e39803888635fdbfc578b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 18:51:03 +0200 Subject: [PATCH 10/19] Update examples to use 3.11 and make the adhoc layout example look a bit more like the src one. --- examples/adhoc-layout/.coveragerc | 3 +-- examples/adhoc-layout/tox.ini | 17 ++++++++++------- examples/src-layout/.coveragerc | 3 +-- examples/src-layout/tox.ini | 6 +++--- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/examples/adhoc-layout/.coveragerc b/examples/adhoc-layout/.coveragerc index 199d42e8..2d87ca04 100644 --- a/examples/adhoc-layout/.coveragerc +++ b/examples/adhoc-layout/.coveragerc @@ -5,11 +5,10 @@ source = [run] branch = true -parallel = true source = example . [report] -show_missing = true precision = 2 +show_missing = true diff --git a/examples/adhoc-layout/tox.ini b/examples/adhoc-layout/tox.ini index e855c315..20717284 100644 --- a/examples/adhoc-layout/tox.ini +++ b/examples/adhoc-layout/tox.ini @@ -1,14 +1,12 @@ [tox] -envlist = pypy3,py39,report +envlist = clean,pypy311,py311,report [tool:pytest] addopts = --cov-report=term-missing [testenv] -setenv = - py{py3,39}: COVERAGE_FILE = .coverage.{envname} -commands = pytest --cov --cov-config={toxinidir}/.coveragerc {posargs:-vv} +commands = pytest --cov --cov-append --cov-config={toxinidir}/.coveragerc {posargs:-vv} deps = pytest coverage @@ -19,15 +17,20 @@ deps = ../.. depends = - report: pypy3,py39 + report: pypy311,py311 + {pypy311,py311}: clean # note that this is necessary to prevent the tests importing the code from your badly laid project changedir = tests +[testenv:clean] +skip_install = true +deps = coverage +commands = + coverage erase + [testenv:report] skip_install = true deps = coverage commands = - coverage combine - coverage html coverage report --fail-under=100 diff --git a/examples/src-layout/.coveragerc b/examples/src-layout/.coveragerc index 7ecf0087..54c3902a 100644 --- a/examples/src-layout/.coveragerc +++ b/examples/src-layout/.coveragerc @@ -5,11 +5,10 @@ source = [run] branch = true -parallel = true source = src/example tests [report] -show_missing = true precision = 2 +show_missing = true diff --git a/examples/src-layout/tox.ini b/examples/src-layout/tox.ini index 953955c9..6287ac5e 100644 --- a/examples/src-layout/tox.ini +++ b/examples/src-layout/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = clean,pypy310,py310,report +envlist = clean,pypy311,py311,report [tool:pytest] testpaths = tests @@ -18,8 +18,8 @@ deps = ../.. depends = - report: pypy310,py310 - {pypy310,py310}: clean + report: pypy311,py311 + {pypy311,py311}: clean [testenv:clean] skip_install = true From 197c35e2f37031fd1927715307ab6eed7cb3d2b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 19:02:32 +0200 Subject: [PATCH 11/19] Update changelog and hopefully I don't forget to publish release again :)) --- AUTHORS.rst | 2 ++ CHANGELOG.rst | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 42933ffa..3291067a 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -65,3 +65,5 @@ Authors * Tsvika Shapira - https://github.com/tsvikas * Marcos Boger - https://github.com/marcosboger * Ofek Lev - https://github.com/ofek +* Art Pelling - https://github.com/artpelling +* Markéta Machová - https://github.com/MeggyCal diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8a83194d..6a992fa6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,7 +2,7 @@ Changelog ========= -7.0.1 (2026-03-02) +7.0.1 (2026-03-21) ------------------ * Fixed total coverage computation to always be consistent, regardless of reporting settings. @@ -20,7 +20,12 @@ Changelog With this fix one can suppress ``ResourceWarning`` from sqlite3 from command line:: pytest -W "ignore:unclosed database in `_ and + "vivodi" in `#738 `_. + Also closed `#736 `_. +* Fixed some assertions in tests. + Contributed by in Markéta Machová in `#722 `_ 7.0.0 (2025-09-09) ------------------ From 4849a922e8be725c662a3d9175da571ace6545dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 19:46:18 +0200 Subject: [PATCH 12/19] Punctuation. --- CHANGELOG.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6a992fa6..cc73f77b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -25,7 +25,9 @@ Changelog "vivodi" in `#738 `_. Also closed `#736 `_. * Fixed some assertions in tests. - Contributed by in Markéta Machová in `#722 `_ + Contributed by in Markéta Machová in `#722 `_. + + 7.0.0 (2025-09-09) ------------------ From 78c9c4ecb005faf4962fd86ff7bf9c9cce9554d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 19:46:43 +0200 Subject: [PATCH 13/19] Only run the 3.9 on older deps. --- .github/workflows/test.yml | 72 +++++++++++++++++++------------------- tox.ini | 3 +- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e0dff506..466aaaed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,24 +60,6 @@ jobs: toxpython: 'python3.11' tox_env: 'docs' os: 'ubuntu-latest' - - name: 'py39-pytest90-xdist38-coverage713 (ubuntu)' - python: '3.9' - toxpython: 'python3.9' - python_arch: 'x64' - tox_env: 'py39-pytest90-xdist38-coverage713' - os: 'ubuntu-latest' - - name: 'py39-pytest90-xdist38-coverage713 (windows)' - python: '3.9' - toxpython: 'python3.9' - python_arch: 'x64' - tox_env: 'py39-pytest90-xdist38-coverage713' - os: 'windows-latest' - - name: 'py39-pytest90-xdist38-coverage713 (macos)' - python: '3.9' - toxpython: 'python3.9' - python_arch: 'arm64' - tox_env: 'py39-pytest90-xdist38-coverage713' - os: 'macos-latest' - name: 'py310-pytest90-xdist38-coverage713 (ubuntu)' python: '3.10' toxpython: 'python3.10' @@ -168,24 +150,6 @@ jobs: python_arch: 'arm64' tox_env: 'py314-pytest90-xdist38-coverage713' os: 'macos-latest' - - name: 'pypy39-pytest90-xdist38-coverage713 (ubuntu)' - python: 'pypy-3.9' - toxpython: 'pypy3.9' - python_arch: 'x64' - tox_env: 'pypy39-pytest90-xdist38-coverage713' - os: 'ubuntu-latest' - - name: 'pypy39-pytest90-xdist38-coverage713 (windows)' - python: 'pypy-3.9' - toxpython: 'pypy3.9' - python_arch: 'x64' - tox_env: 'pypy39-pytest90-xdist38-coverage713' - os: 'windows-latest' - - name: 'pypy39-pytest90-xdist38-coverage713 (macos)' - python: 'pypy-3.9' - toxpython: 'pypy3.9' - python_arch: 'arm64' - tox_env: 'pypy39-pytest90-xdist38-coverage713' - os: 'macos-latest' - name: 'pypy310-pytest90-xdist38-coverage713 (ubuntu)' python: 'pypy-3.10' toxpython: 'pypy3.10' @@ -222,6 +186,42 @@ jobs: python_arch: 'arm64' tox_env: 'pypy311-pytest90-xdist38-coverage713' os: 'macos-latest' + - name: 'py39-pytest80-xdist38-coverage710 (ubuntu)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-pytest80-xdist38-coverage710' + os: 'ubuntu-latest' + - name: 'py39-pytest80-xdist38-coverage710 (windows)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-pytest80-xdist38-coverage710' + os: 'windows-latest' + - name: 'py39-pytest80-xdist38-coverage710 (macos)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'arm64' + tox_env: 'py39-pytest80-xdist38-coverage710' + os: 'macos-latest' + - name: 'pypy39-pytest80-xdist38-coverage710 (ubuntu)' + python: 'pypy-3.9' + toxpython: 'pypy3.9' + python_arch: 'x64' + tox_env: 'pypy39-pytest80-xdist38-coverage710' + os: 'ubuntu-latest' + - name: 'pypy39-pytest80-xdist38-coverage710 (windows)' + python: 'pypy-3.9' + toxpython: 'pypy3.9' + python_arch: 'x64' + tox_env: 'pypy39-pytest80-xdist38-coverage710' + os: 'windows-latest' + - name: 'pypy39-pytest80-xdist38-coverage710 (macos)' + python: 'pypy-3.9' + toxpython: 'pypy3.9' + python_arch: 'arm64' + tox_env: 'pypy39-pytest80-xdist38-coverage710' + os: 'macos-latest' steps: - uses: actions/checkout@v6 with: diff --git a/tox.ini b/tox.ini index fb4adc5b..07548537 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,8 @@ envlist = clean, check, docs, - {py39,py310,py311,py312,py313,py314,pypy39,pypy310,pypy311}-{pytest90}-{xdist38}-{coverage713}, + {py310,py311,py312,py313,py314,pypy310,pypy311}-{pytest90}-{xdist38}-{coverage713}, + {py39,pypy39}-{pytest80}-{xdist38}-{coverage710}, report ignore_basepython_conflict = true From fd4c956014035527f0c3c8d7faef3f8cfdadac7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 19:48:16 +0200 Subject: [PATCH 14/19] Pass the precision on the nulled total (seems that there's some caching goiong on that will mess up the following report calls). --- src/pytest_cov/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pytest_cov/engine.py b/src/pytest_cov/engine.py index 1de4db87..fd09bfcb 100644 --- a/src/pytest_cov/engine.py +++ b/src/pytest_cov/engine.py @@ -152,7 +152,7 @@ def summary(self, stream): """Produce coverage reports.""" with _backup(self.cov, 'config'): - total = self.cov.report(ignore_errors=True, output_format='total', file=_NullFile) + total = self.cov.report(ignore_errors=True, output_format='total', precision=self.cov_precision, file=_NullFile) # Output coverage section header. if len(self.node_descs) == 1: From 861d30e60d571f97259c6b718b71c819d5dbc3b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 19:50:16 +0200 Subject: [PATCH 15/19] Remove the backup context manager - shouldn't be needed since coverage 5.0, see https://coverage.readthedocs.io/en/latest/changes.html#version-5-0a7-2019-09-21 --- src/pytest_cov/engine.py | 51 ++++++++++++---------------------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/src/pytest_cov/engine.py b/src/pytest_cov/engine.py index fd09bfcb..a6a465fa 100644 --- a/src/pytest_cov/engine.py +++ b/src/pytest_cov/engine.py @@ -2,7 +2,6 @@ import argparse import contextlib -import copy import functools import os import random @@ -31,16 +30,6 @@ def write(v): pass -@contextlib.contextmanager -def _backup(obj, attr): - backup = getattr(obj, attr) - try: - setattr(obj, attr, copy.copy(backup)) - yield - finally: - setattr(obj, attr, backup) - - def _ensure_topdir(meth): @functools.wraps(meth) def ensure_topdir_wrapper(self, *args, **kwargs): @@ -151,8 +140,7 @@ def sep(self, stream, s, txt): def summary(self, stream): """Produce coverage reports.""" - with _backup(self.cov, 'config'): - total = self.cov.report(ignore_errors=True, output_format='total', precision=self.cov_precision, file=_NullFile) + total = self.cov.report(ignore_errors=True, output_format='total', precision=self.cov_precision, file=_NullFile) # Output coverage section header. if len(self.node_descs) == 1: @@ -179,20 +167,17 @@ def summary(self, stream): } skip_covered = isinstance(self.cov_report, dict) and 'skip-covered' in self.cov_report.values() options.update({'skip_covered': skip_covered or None}) - with _backup(self.cov, 'config'): - self.cov.report(**options) + self.cov.report(**options) # Produce annotated source code report if wanted. if 'annotate' in self.cov_report: annotate_dir = self.cov_report['annotate'] - with _backup(self.cov, 'config'): - self.cov.annotate(ignore_errors=True, directory=annotate_dir) + self.cov.annotate(ignore_errors=True, directory=annotate_dir) # We need to call Coverage.report here, just to get the total # Coverage.annotate don't return any total and we need it for --cov-fail-under. - with _backup(self.cov, 'config'): - self.cov.report(ignore_errors=True, file=_NullFile) + self.cov.report(ignore_errors=True, file=_NullFile) if annotate_dir: stream.write(f'Coverage annotated source written to dir {annotate_dir}\n') else: @@ -201,49 +186,43 @@ def summary(self, stream): # Produce html report if wanted. if 'html' in self.cov_report: output = self.cov_report['html'] - with _backup(self.cov, 'config'): - self.cov.html_report(ignore_errors=True, directory=output) + self.cov.html_report(ignore_errors=True, directory=output) stream.write(f'Coverage HTML written to dir {self.cov.config.html_dir if output is None else output}\n') # Produce xml report if wanted. if 'xml' in self.cov_report: output = self.cov_report['xml'] - with _backup(self.cov, 'config'): - self.cov.xml_report(ignore_errors=True, outfile=output) + self.cov.xml_report(ignore_errors=True, outfile=output) stream.write(f'Coverage XML written to file {self.cov.config.xml_output if output is None else output}\n') # Produce json report if wanted if 'json' in self.cov_report: output = self.cov_report['json'] - with _backup(self.cov, 'config'): - self.cov.json_report(ignore_errors=True, outfile=output) + self.cov.json_report(ignore_errors=True, outfile=output) stream.write('Coverage JSON written to file %s\n' % (self.cov.config.json_output if output is None else output)) # Produce Markdown report if wanted. if 'markdown' in self.cov_report: output = self.cov_report['markdown'] - with _backup(self.cov, 'config'): - with Path(output).open('w') as output_file: - self.cov.report(ignore_errors=True, file=output_file, output_format='markdown') + with Path(output).open('w') as output_file: + self.cov.report(ignore_errors=True, file=output_file, output_format='markdown') stream.write(f'Coverage Markdown information written to file {output}\n') # Produce Markdown report if wanted, appending to output file if 'markdown-append' in self.cov_report: output = self.cov_report['markdown-append'] - with _backup(self.cov, 'config'): - with Path(output).open('a') as output_file: - self.cov.report(ignore_errors=True, file=output_file, output_format='markdown') + with Path(output).open('a') as output_file: + self.cov.report(ignore_errors=True, file=output_file, output_format='markdown') stream.write(f'Coverage Markdown information appended to file {output}\n') # 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) + 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. - self.cov.report(ignore_errors=True, file=_NullFile) + # 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. + self.cov.report(ignore_errors=True, file=_NullFile) stream.write(f'Coverage LCOV written to file {self.cov.config.lcov_output if output is None else output}\n') From 8ebf20bbbc73478b3f8fd36d30237d9ea083f06b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 19:52:12 +0200 Subject: [PATCH 16/19] Update changelog. --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cc73f77b..15a028dd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,7 +2,7 @@ Changelog ========= -7.0.1 (2026-03-21) +7.1.0 (2026-03-21) ------------------ * Fixed total coverage computation to always be consistent, regardless of reporting settings. @@ -26,7 +26,7 @@ Changelog Also closed `#736 `_. * Fixed some assertions in tests. Contributed by in Markéta Machová in `#722 `_. - +* Removed unnecessary coverage configuration copying (meant as a backup because reporting commands had configuration side-effects before coverage 5.0). 7.0.0 (2025-09-09) ------------------ From 6049a7847872e3139e6c82e93787123df5dc8672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 21:42:31 +0200 Subject: [PATCH 17/19] Make context test use the old ctracer (seems the new sysmon tracer behaves differently). --- tests/test_pytest_cov.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_pytest_cov.py b/tests/test_pytest_cov.py index 1b2177be..b291f432 100644 --- a/tests/test_pytest_cov.py +++ b/tests/test_pytest_cov.py @@ -1987,6 +1987,9 @@ def test_contexts(pytester, testdir, opts): with Path(__file__).parent.joinpath('contextful.py').open() as f: contextful_tests = f.read() script = testdir.makepyfile(contextful_tests) + testdir.tmpdir.join('.coveragerc').write("""[run] +core = ctrace""") + result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-context=test', script, *opts.split()) assert result.ret == 0 result.stdout.fnmatch_lines( From f7076624784332594aa4cb3585d4757d295db15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 21:44:50 +0200 Subject: [PATCH 18/19] Make the examples use pypy 3.11. --- .github/workflows/test.yml | 4 ++-- ci/templates/.github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 466aaaed..7e12f6de 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,14 +6,14 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy-3.9", "3.11"] + python-version: ["pypy-3.11", "3.11"] target: [ "src-layout", "adhoc-layout", ] include: # Add new helper variables to existing jobs - - {python-version: "pypy-3.9", tox-python-version: "pypy3"} + - {python-version: "pypy-3.11", tox-python-version: "pypy11"} - {python-version: "3.11", tox-python-version: "py311"} steps: - uses: actions/checkout@v5 diff --git a/ci/templates/.github/workflows/test.yml b/ci/templates/.github/workflows/test.yml index 70b1e2bc..08f90d1b 100644 --- a/ci/templates/.github/workflows/test.yml +++ b/ci/templates/.github/workflows/test.yml @@ -7,14 +7,14 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy-3.9", "3.11"] + python-version: ["pypy-3.11", "3.11"] target: [ "src-layout", "adhoc-layout", ] include: # Add new helper variables to existing jobs - - {python-version: "pypy-3.9", tox-python-version: "pypy3"} + - {python-version: "pypy-3.11", tox-python-version: "pypy11"} - {python-version: "3.11", tox-python-version: "py311"} steps: - uses: actions/checkout@v5 From 66c8a526b1246b5eb8fb1bc218878131bc628622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 21 Mar 2026 21:57:53 +0200 Subject: [PATCH 19/19] =?UTF-8?q?Bump=20version:=207.0.0=20=E2=86=92=207.1?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- .cookiecutterrc | 2 +- README.rst | 4 ++-- docs/conf.py | 2 +- pyproject.toml | 2 +- src/pytest_cov/__init__.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index fc1f52cb..502b9bfb 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 7.0.0 +current_version = 7.1.0 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index 8441f282..e1923207 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -40,7 +40,7 @@ default_context: sphinx_doctest: 'no' sphinx_theme: sphinx-py3doc-enhanced-theme test_matrix_separate_coverage: 'no' - version: 7.0.0 + version: 7.1.0 version_manager: bump2version website: http://blog.ionelmc.ro year_from: '2010' diff --git a/README.rst b/README.rst index 143d9422..23d9fdea 100644 --- a/README.rst +++ b/README.rst @@ -40,9 +40,9 @@ Overview :alt: Supported implementations :target: https://pypi.org/project/pytest-cov -.. |commits-since| image:: https://img.shields.io/github/commits-since/pytest-dev/pytest-cov/v7.0.0.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/pytest-dev/pytest-cov/v7.1.0.svg :alt: Commits since latest release - :target: https://github.com/pytest-dev/pytest-cov/compare/v7.0.0...master + :target: https://github.com/pytest-dev/pytest-cov/compare/v7.1.0...master .. end-badges diff --git a/docs/conf.py b/docs/conf.py index f4250bc7..281ebb4a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,7 +21,7 @@ year = '2010-2024' author = 'pytest-cov contributors' copyright = f'{year}, {author}' -version = release = '7.0.0' +version = release = '7.1.0' pygments_style = 'trac' templates_path = ['.'] diff --git a/pyproject.toml b/pyproject.toml index f202cd8a..c1641b10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "pytest-cov" dynamic = ["readme"] -version = "7.0.0" +version = "7.1.0" description = "Pytest plugin for measuring coverage." license = "MIT" requires-python = ">=3.9" diff --git a/src/pytest_cov/__init__.py b/src/pytest_cov/__init__.py index 62d553a4..978cd510 100644 --- a/src/pytest_cov/__init__.py +++ b/src/pytest_cov/__init__.py @@ -1,6 +1,6 @@ """pytest-cov: avoid already-imported warning: PYTEST_DONT_REWRITE.""" -__version__ = '7.0.0' +__version__ = '7.1.0' import pytest