From 4f7a2079da969b355ce7976b1d3b34bbf003de75 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Sat, 22 Jun 2024 18:02:41 -0400
Subject: [PATCH 01/37] build: bump version
---
CHANGES.rst | 6 ++++++
coverage/version.py | 4 ++--
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index 633f418c1..c2e2b1215 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -20,6 +20,12 @@ upgrading your version of coverage.py.
.. Version 9.8.1 â 2027-07-27
.. --------------------------
+Unreleased
+----------
+
+Nothing yet.
+
+
.. scriv-start-here
.. _changes_7-5-4:
diff --git a/coverage/version.py b/coverage/version.py
index 77743953a..15be60ad2 100644
--- a/coverage/version.py
+++ b/coverage/version.py
@@ -8,8 +8,8 @@
# version_info: same semantics as sys.version_info.
# _dev: the .devN suffix if any.
-version_info = (7, 5, 4, "final", 0)
-_dev = 0
+version_info = (7, 5, 5, "alpha", 0)
+_dev = 1
def _make_version(
From 8636249878ceca417f239c4eea8d616e54fe8d89 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Sun, 23 Jun 2024 08:06:31 -0400
Subject: [PATCH 02/37] benchmark: simplify the -k args for mypy
---
benchmark/benchmark.py | 48 ++++++++++++++++--------------------------
1 file changed, 18 insertions(+), 30 deletions(-)
diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py
index d4649d265..c9748c5a4 100644
--- a/benchmark/benchmark.py
+++ b/benchmark/benchmark.py
@@ -553,45 +553,32 @@ def run_with_coverage(self, env: Env, cov_ver: Coverage) -> float:
class ProjectMypy(ToxProject):
git_url = "https://github.com/python/mypy"
- # Slow test suites
- CMDLINE = "PythonCmdline"
- PEP561 = "PEP561Suite"
- EVALUATION = "PythonEvaluation"
- DAEMON = "testdaemon"
- STUBGEN_CMD = "StubgenCmdLine"
- STUBGEN_PY = "StubgenPythonSuite"
- MYPYC_RUN = "TestRun"
- MYPYC_RUN_MULTI = "TestRunMultiFile"
- MYPYC_EXTERNAL = "TestExternal"
- MYPYC_COMMAND_LINE = "TestCommandLine"
- ERROR_STREAM = "ErrorStreamSuite"
-
- ALL_NON_FAST = (
- CMDLINE,
- PEP561,
- EVALUATION,
- DAEMON,
- STUBGEN_CMD,
- STUBGEN_PY,
- MYPYC_RUN,
- MYPYC_RUN_MULTI,
- MYPYC_EXTERNAL,
- MYPYC_COMMAND_LINE,
- ERROR_STREAM,
- )
-
- FAST = "pytest", "-k", f"\"not ({' or '.join(ALL_NON_FAST)})\""
+ SLOW_TESTS = " or ".join([
+ "PythonCmdline",
+ "PEP561Suite",
+ "PythonEvaluation",
+ "testdaemon",
+ "StubgenCmdLine",
+ "StubgenPythonSuite",
+ "TestRun",
+ "TestRunMultiFile",
+ "TestExternal",
+ "TestCommandLine",
+ "ErrorStreamSuite",
+ ])
+
+ FAST = f"-k 'not ({SLOW_TESTS})'"
def prep_environment(self, env: Env) -> None:
env.shell.run_command(f"{env.python} -m pip install -r test-requirements.txt")
def run_no_coverage(self, env: Env) -> float:
- env.shell.run_command(f"{env.python} -m {' '.join(self.FAST)} --no-cov")
+ env.shell.run_command(f"{env.python} -m pytest {self.FAST} --no-cov")
return env.shell.last_duration
def run_with_coverage(self, env: Env, cov_ver: Coverage) -> float:
env.shell.run_command(f"{env.python} -m pip install {cov_ver.pip_args}")
- env.shell.run_command(f"{env.python} -m {' '.join(self.FAST)} --cov")
+ env.shell.run_command(f"{env.python} -m pytest {self.FAST} --cov")
duration = env.shell.last_duration
report = env.shell.run_command(f"{env.python} -m coverage report --precision=6")
print("Results:", report.splitlines()[-1])
@@ -957,6 +944,7 @@ def run(self, num_runs: int = 3) -> None:
self.result_data[result_key].append(dur)
run_data[result_key].append(dur)
self.save_results()
+
# Summarize and collect the data.
print("# Results")
for proj in self.projects:
From ec1acd985bf47258e9d209db037fbf6373270b1c Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Sun, 23 Jun 2024 09:41:47 -0400
Subject: [PATCH 03/37] feat: secret COVERAGE_FORCE_CONFIG environment variable
This is for our use in our benchmarks to force third-party test suites to run
the way we want. For example, we can use it to force branch=True or
branch=False.
---
coverage/config.py | 5 +++++
tests/test_config.py | 18 ++++++++++++++++++
2 files changed, 23 insertions(+)
diff --git a/coverage/config.py b/coverage/config.py
index 7aa2471bd..5468ca490 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -612,6 +612,11 @@ def read_coverage_config(
# 4) from constructor arguments:
config.from_args(**kwargs)
+ # 5) for our benchmark, force settings using a secret environment variable:
+ force_file = os.getenv("COVERAGE_FORCE_CONFIG")
+ if force_file:
+ config.from_file(force_file, warn, our_file=True)
+
# Once all the config has been collected, there's a little post-processing
# to do.
config.post_process()
diff --git a/tests/test_config.py b/tests/test_config.py
index e2ab90e88..062c69324 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -167,6 +167,24 @@ def test_missing_rcfile_from_environment(self) -> None:
with pytest.raises(ConfigError, match=msg):
coverage.Coverage()
+ @pytest.mark.parametrize("force", [False, True])
+ def test_force_environment(self, force: bool) -> None:
+ self.make_file(".coveragerc", """\
+ [run]
+ debug = dataio, pids
+ """)
+ self.make_file("force.ini", """\
+ [run]
+ debug = callers, fooey
+ """)
+ if force:
+ self.set_environ("COVERAGE_FORCE_CONFIG", "force.ini")
+ cov = coverage.Coverage()
+ if force:
+ assert cov.config.debug == ["callers", "fooey"]
+ else:
+ assert cov.config.debug == ["dataio", "pids"]
+
@pytest.mark.parametrize("bad_config, msg", [
("[run]\ntimid = maybe?\n", r"maybe[?]"),
("timid = 1\n", r"no section headers"),
From 2f1b28fb89278be5c3fcd3f0f27a15900e77720d Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Sun, 23 Jun 2024 09:44:51 -0400
Subject: [PATCH 04/37] benchmark: if an exception happens, put it in the log
file
---
benchmark/benchmark.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py
index c9748c5a4..7a68b2087 100644
--- a/benchmark/benchmark.py
+++ b/benchmark/benchmark.py
@@ -16,6 +16,7 @@
import subprocess
import sys
import time
+import traceback
from pathlib import Path
@@ -937,6 +938,7 @@ def run(self, num_runs: int = 3) -> None:
dur = proj.run_with_coverage(env, cov_ver)
except Exception as exc:
print(f"!!! {exc = }")
+ traceback.print_exc(file=env.shell.foutput)
dur = float("NaN")
print(f"Tests took {dur:.3f}s")
if result_key not in self.result_data:
From f572e3aadfb80b844a0c3b21a6a0df70e05c2a37 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Sun, 23 Jun 2024 09:45:28 -0400
Subject: [PATCH 05/37] benchmark: CoverageSource defaults to the current
working tree
---
benchmark/benchmark.py | 2 +-
benchmark/run.py | 4 +---
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py
index 7a68b2087..cd53de1ad 100644
--- a/benchmark/benchmark.py
+++ b/benchmark/benchmark.py
@@ -808,7 +808,7 @@ class CoverageSource(Coverage):
def __init__(
self,
- directory_name: str,
+ directory_name: str = "..",
slug: str = "source",
tweaks: TweaksType = None,
env_vars: Env_VarsType = None,
diff --git a/benchmark/run.py b/benchmark/run.py
index e1ce85c6f..8c5f50c26 100644
--- a/benchmark/run.py
+++ b/benchmark/run.py
@@ -136,7 +136,7 @@
],
cov_versions=[
Coverage("pip", "coverage"),
- CoverageSource("../..", "latest"),
+ CoverageSource(slug="latest"),
],
projects=[
ProjectMashumaro(),
@@ -160,7 +160,6 @@
Coverage("732", "coverage==7.3.2"),
CoverageSource(
slug="sysmon",
- directory_name="../..",
env_vars={"COVERAGE_CORE": "sysmon"},
),
],
@@ -187,7 +186,6 @@
Coverage("732", "coverage==7.3.2"),
CoverageSource(
slug="sysmon",
- directory_name="/Users/nbatchelder/coverage/trunk",
env_vars={"COVERAGE_CORE": "sysmon"},
),
],
From b8739dd5ef58beb671e0f957ff3cecbe3fe1f8e1 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Sun, 23 Jun 2024 16:17:16 -0400
Subject: [PATCH 06/37] benchmark: force mypy to measure line coverage
---
benchmark/benchmark.py | 12 ++++++++----
benchmark/run.py | 8 ++++----
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py
index cd53de1ad..9ec932e5d 100644
--- a/benchmark/benchmark.py
+++ b/benchmark/benchmark.py
@@ -280,7 +280,8 @@ class ToxProject(ProjectToTest):
env_vars: Env_VarsType = {
**(ProjectToTest.env_vars or {}),
# Allow some environment variables into the tox execution.
- "TOX_OVERRIDE": "testenv.pass_env+=COVERAGE_DEBUG,COVERAGE_CORE",
+ "TOX_OVERRIDE": "testenv.pass_env+=COVERAGE_DEBUG,COVERAGE_CORE,COVERAGE_FORCE_CONFIG",
+ "COVERAGE_DEBUG": "config,sys",
}
def prep_environment(self, env: Env) -> None:
@@ -579,9 +580,12 @@ def run_no_coverage(self, env: Env) -> float:
def run_with_coverage(self, env: Env, cov_ver: Coverage) -> float:
env.shell.run_command(f"{env.python} -m pip install {cov_ver.pip_args}")
- env.shell.run_command(f"{env.python} -m pytest {self.FAST} --cov")
- duration = env.shell.last_duration
- report = env.shell.run_command(f"{env.python} -m coverage report --precision=6")
+ pforce = Path("force.ini")
+ pforce.write_text("[run]\nbranch=false\n")
+ with env.shell.set_env({"COVERAGE_FORCE_CONFIG": str(pforce.resolve())}):
+ env.shell.run_command(f"{env.python} -m pytest {self.FAST} --cov")
+ duration = env.shell.last_duration
+ report = env.shell.run_command(f"{env.python} -m coverage report --precision=6")
print("Results:", report.splitlines()[-1])
return duration
diff --git a/benchmark/run.py b/benchmark/run.py
index 8c5f50c26..2c4263f2c 100644
--- a/benchmark/run.py
+++ b/benchmark/run.py
@@ -104,8 +104,8 @@
],
cov_versions=[
NoCoverage("nocov"),
- Coverage("753", "coverage==7.5.3"),
- Coverage("sysmon", "coverage==7.5.3", env_vars={"COVERAGE_CORE": "sysmon"}),
+ CoverageSource(slug="ctrace", env_vars={"COVERAGE_CORE": "ctrace"}),
+ CoverageSource(slug="sysmon", env_vars={"COVERAGE_CORE": "sysmon"}),
],
projects=[
# ProjectSphinx(), # Works, slow
@@ -115,14 +115,14 @@
# ProjectDulwich(), # Works
# ProjectBlack(), # Works, slow
# ProjectMpmath(), # Works, slow
- # ProjectMypy(), # Works, slow
+ ProjectMypy(), # Works, slow
# ProjectHtml5lib(), # Works
# ProjectUrllib3(), # Works
],
rows=["pyver", "proj"],
column="cov",
ratios=[
- (f"753%", "753", "nocov"),
+ (f"ctrace%", "ctrace", "nocov"),
(f"sysmon%", "sysmon", "nocov"),
],
load=True,
From 627227a60c0565d6fd2d886860fdafbf51112c46 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 24 Jun 2024 11:45:08 -0400
Subject: [PATCH 07/37] build(deps): bump github/codeql-action (#1800)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 412ab5c4176178930892df540237c587c71786c9 to 23acc5c183826b7a8a97bce3cecc52db901f8251.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/412ab5c4176178930892df540237c587c71786c9...23acc5c183826b7a8a97bce3cecc52db901f8251)
---
updated-dependencies:
- dependency-name: github/codeql-action
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/codeql-analysis.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 742ea7af7..a9eb4b915 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -49,7 +49,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@412ab5c4176178930892df540237c587c71786c9 # v3
+ uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -60,7 +60,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@412ab5c4176178930892df540237c587c71786c9 # v3
+ uses: github/codeql-action/autobuild@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3
# âšī¸ Command-line programs to run using the OS shell.
# đ https://git.io/JvXDl
@@ -74,4 +74,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@412ab5c4176178930892df540237c587c71786c9 # v3
+ uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3
From 4cee9052e58bf992b14b48fab71887a9649788c1 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 24 Jun 2024 16:00:47 +0000
Subject: [PATCH 08/37] build(deps): bump re-actors/alls-green (#1802)
---
.github/workflows/testsuite.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml
index 0f9ab8296..6bccad215 100644
--- a/.github/workflows/testsuite.yml
+++ b/.github/workflows/testsuite.yml
@@ -123,6 +123,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
- uses: re-actors/alls-green@afee1c1eac2a506084c274e9c02c8e0687b48d9e # v1.2.2
+ uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
From fa36ebddb92853c0b154f415277fe79566b8fc2b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 24 Jun 2024 16:17:20 +0000
Subject: [PATCH 09/37] build(deps): bump pypa/gh-action-pypi-publish (#1801)
---
.github/workflows/publish.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 090adeef2..360978688 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -70,7 +70,7 @@ jobs:
ls -1 dist | wc -l
- name: "Publish dists to Test PyPI"
- uses: pypa/gh-action-pypi-publish@4bb033805d9e19112d8c697528791ff53f6c2f74 # v1.9.0
+ uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0
with:
repository-url: https://test.pypi.org/legacy/
@@ -103,4 +103,4 @@ jobs:
ls -1 dist | wc -l
- name: "Publish dists to PyPI"
- uses: pypa/gh-action-pypi-publish@4bb033805d9e19112d8c697528791ff53f6c2f74 # v1.9.0
+ uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0
From 40c5a4a2313410b8631ca4e420f04290dfd72438 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Mon, 24 Jun 2024 15:49:51 -0400
Subject: [PATCH 10/37] build: make dependabot talk like we do
---
.github/dependabot.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 1cdec3b21..3408e4455 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -9,3 +9,5 @@ updates:
schedule:
# Check for updates to GitHub Actions every weekday
interval: "daily"
+ commit-message:
+ prefix: "chore"
From 69a034f59c9812a0ed6b2831981b5417f5ecd9d5 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Mon, 24 Jun 2024 21:52:58 -0400
Subject: [PATCH 11/37] build: generate attestations
---
.github/workflows/publish.yml | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 360978688..386b7284f 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -45,7 +45,8 @@ jobs:
name: "Publish to Test PyPI"
if: ${{ github.event.action == 'publish-testpypi' }}
permissions:
- id-token: write # Needed for trusted publishing to PyPI.
+ id-token: write
+ attestations: write
runs-on: "ubuntu-latest"
environment:
name: "testpypi"
@@ -69,6 +70,11 @@ jobs:
echo "Number of dists:"
ls -1 dist | wc -l
+ - name: "Generate attestations"
+ uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2
+ with:
+ subject-path: "dist/*"
+
- name: "Publish dists to Test PyPI"
uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0
with:
@@ -78,7 +84,8 @@ jobs:
name: "Publish to PyPI"
if: ${{ github.event.action == 'publish-pypi' }}
permissions:
- id-token: write # Needed for trusted publishing to PyPI.
+ id-token: write
+ attestations: write
runs-on: "ubuntu-latest"
environment:
name: "pypi"
@@ -102,5 +109,10 @@ jobs:
echo "Number of dists:"
ls -1 dist | wc -l
+ - name: "Generate attestations"
+ uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2
+ with:
+ subject-path: "dist/*"
+
- name: "Publish dists to PyPI"
uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0
From c654ec06e05467cfa72e7f763ab2bf2a833d5c01 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Tue, 25 Jun 2024 06:36:52 -0400
Subject: [PATCH 12/37] build: kit_upload -> pypi_upload
---
.github/workflows/publish.yml | 5 +++--
Makefile | 4 ++--
howto.txt | 2 +-
3 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 386b7284f..c76e43e3f 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -5,9 +5,10 @@ name: "Publish"
on:
repository_dispatch:
+ # Triggered with `make` targets:
types:
- - publish-testpypi
- - publish-pypi
+ - publish-testpypi # `make test_upload`
+ - publish-pypi # `make pypi_upload`
defaults:
run:
diff --git a/Makefile b/Makefile
index ac2433fdc..9a58205a9 100644
--- a/Makefile
+++ b/Makefile
@@ -177,7 +177,7 @@ sample_html_beta: _sample_cog_html ## Generate sample HTML report for a beta rel
##@ Kitting: making releases
.PHONY: edit_for_release cheats relbranch relcommit1 relcommit2
-.PHONY: kit kit_upload test_upload kit_local build_kits
+.PHONY: kit pypi_upload test_upload kit_local build_kits
.PHONY: tag bump_version
REPO_OWNER = nedbat/coveragepy
@@ -201,7 +201,7 @@ relcommit2: #: Commit the latest sample HTML report (see howto.txt).
kit: ## Make the source distribution.
python -m build
-kit_upload: ## Upload the built distributions to PyPI.
+pypi_upload: ## Upload the built distributions to PyPI.
python ci/trigger_action.py $(REPO_OWNER) publish-pypi
test_upload: ## Upload the distributions to PyPI's testing server.
diff --git a/howto.txt b/howto.txt
index e540f6355..6cba5ba98 100644
--- a/howto.txt
+++ b/howto.txt
@@ -57,7 +57,7 @@
$ make test_upload
- you'll need to approve the action
- upload kits:
- $ make kit_upload
+ $ make pypi_upload
- you'll need to approve the action
- Tag the tree
$ make tag
From da5f4c994261f5ed6180c1a5244ce46516f1ac77 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Sun, 30 Jun 2024 17:01:11 -0400
Subject: [PATCH 13/37] docs: a small tweak to how we label the tabs for sample
configs
---
doc/cmd.rst | 4 ++--
doc/cog_helpers.py | 4 ++--
doc/config.rst | 8 ++++----
doc/contexts.rst | 4 ++--
doc/excluding.rst | 8 ++++----
doc/plugins.rst | 8 ++++----
doc/source.rst | 4 ++--
7 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/doc/cmd.rst b/doc/cmd.rst
index 439927d9c..e2f717882 100644
--- a/doc/cmd.rst
+++ b/doc/cmd.rst
@@ -310,12 +310,12 @@ collected," add this to your configuration file:
disable_warnings = ["no-data-collected"]
.. code-tab:: ini
- :caption: setup.cfg, tox.ini
+ :caption: setup.cfg or tox.ini
[coverage:run]
disable_warnings = no-data-collected
-.. [[[end]]] (checksum: 66c0c28e863c2a44218190a8a6a3f707)
+.. [[[end]]] (checksum: 489285bcfa173b69a286f03fe13e4554)
.. _cmd_datafile:
diff --git a/doc/cog_helpers.py b/doc/cog_helpers.py
index 56e4630a9..8cdb65f09 100644
--- a/doc/cog_helpers.py
+++ b/doc/cog_helpers.py
@@ -73,7 +73,7 @@ def show_configs(ini, toml):
`ini` is the ini-file syntax, `toml` is the equivalent TOML syntax.
The equivalence is checked for accuracy, and the process fails if there's
- a mismtach.
+ a mismatch.
A three-tabbed box will be produced.
"""
@@ -89,7 +89,7 @@ def show_configs(ini, toml):
for name, syntax, text in [
(".coveragerc", "ini", ini),
("pyproject.toml", "toml", toml),
- ("setup.cfg, tox.ini", "ini", ini2),
+ ("setup.cfg or tox.ini", "ini", ini2),
]:
print(f" .. code-tab:: {syntax}")
print(f" :caption: {name}")
diff --git a/doc/config.rst b/doc/config.rst
index 858f4bba0..62c051c5d 100644
--- a/doc/config.rst
+++ b/doc/config.rst
@@ -229,7 +229,7 @@ Here's a sample configuration file, in each syntax:
directory = "coverage_html_report"
.. code-tab:: ini
- :caption: setup.cfg, tox.ini
+ :caption: setup.cfg or tox.ini
[coverage:run]
branch = True
@@ -257,7 +257,7 @@ Here's a sample configuration file, in each syntax:
[coverage:html]
directory = coverage_html_report
-.. [[[end]]] (checksum: 75c6c0c2ee170424cc1c18710e2b4919)
+.. [[[end]]] (checksum: 1d4d59eb69af44aacb77c9ebad869b65)
The specific configuration settings are described below. Many sections and
@@ -535,7 +535,7 @@ equivalent when combining data from different machines:
]
.. code-tab:: ini
- :caption: setup.cfg, tox.ini
+ :caption: setup.cfg or tox.ini
[coverage:paths]
source =
@@ -543,7 +543,7 @@ equivalent when combining data from different machines:
/jenkins/build/*/src
c:\myproj\src
-.. [[[end]]] (checksum: cf06ac36436db0c87be15a85223900d0)
+.. [[[end]]] (checksum: a074a5f121a23135dcb6733bca3e20bd)
The names of the entries ("source" in this example) are ignored, you may choose
diff --git a/doc/contexts.rst b/doc/contexts.rst
index a277dd8e8..75080f0cb 100644
--- a/doc/contexts.rst
+++ b/doc/contexts.rst
@@ -103,12 +103,12 @@ The ``[run] dynamic_context`` setting has only one option now. Set it to
dynamic_context = "test_function"
.. code-tab:: ini
- :caption: setup.cfg, tox.ini
+ :caption: setup.cfg or tox.ini
[coverage:run]
dynamic_context = test_function
-.. [[[end]]] (checksum: 5c5d120ee876e5fe26e573e1a5e8551d)
+.. [[[end]]] (checksum: 7594c36231f0ef52b554aad8c835ccf4)
Each test function you run will be considered a separate dynamic context, and
coverage data will be segregated for each. A test function is any function
diff --git a/doc/excluding.rst b/doc/excluding.rst
index a5766676e..80f0c79dd 100644
--- a/doc/excluding.rst
+++ b/doc/excluding.rst
@@ -123,13 +123,13 @@ all of them by adding a regex to the exclusion list:
]
.. code-tab:: ini
- :caption: setup.cfg, tox.ini
+ :caption: setup.cfg or tox.ini
[coverage:report]
exclude_also =
def __repr__
-.. [[[end]]] (checksum: adc6406467518c89a5a6fe2c4b999416)
+.. [[[end]]] (checksum: e3194120285bcbac38a92b109edaa20c)
For example, here's a list of exclusions I've used:
@@ -203,7 +203,7 @@ For example, here's a list of exclusions I've used:
]
.. code-tab:: ini
- :caption: setup.cfg, tox.ini
+ :caption: setup.cfg or tox.ini
[coverage:report]
exclude_also =
@@ -218,7 +218,7 @@ For example, here's a list of exclusions I've used:
class .*\bProtocol\):
@(abc\.)?abstractmethod
-.. [[[end]]] (checksum: ef1947821b8224c4f02d27f9514e5c5e)
+.. [[[end]]] (checksum: 91f09828a1e6d0e92543e14a8ea3ba39)
The :ref:`config_report_exclude_also` option adds regexes to the built-in
default list so that you can add your own exclusions. The older
diff --git a/doc/plugins.rst b/doc/plugins.rst
index 7be4e4b6b..147fb1db4 100644
--- a/doc/plugins.rst
+++ b/doc/plugins.rst
@@ -78,13 +78,13 @@ a coverage.py plug-in called ``something.plugin``.
plugins = [ "something.plugin" ]
.. code-tab:: ini
- :caption: setup.cfg, tox.ini
+ :caption: setup.cfg or tox.ini
[coverage:run]
plugins =
something.plugin
- .. [[[end]]] (checksum: 788b15abb3c53370ccae3d9348e65385)
+ .. [[[end]]] (checksum: 6e866323d4bc319d42e3199b08615111)
#. If the plug-in needs its own configuration, you can add those settings in
the .coveragerc file in a section named for the plug-in:
@@ -121,13 +121,13 @@ a coverage.py plug-in called ``something.plugin``.
option2 = "abc.foo"
.. code-tab:: ini
- :caption: setup.cfg, tox.ini
+ :caption: setup.cfg or tox.ini
[coverage:something.plugin]
option1 = True
option2 = abc.foo
- .. [[[end]]] (checksum: 71aa2ad856e03d228758fd5026fd3a52)
+ .. [[[end]]] (checksum: b690115dbe7f6c7806567e009b5715c4)
Check the documentation for the plug-in for details on the options it takes.
diff --git a/doc/source.rst b/doc/source.rst
index 3357ae73d..bcf7a8af9 100644
--- a/doc/source.rst
+++ b/doc/source.rst
@@ -128,7 +128,7 @@ current directory:
]
.. code-tab:: ini
- :caption: setup.cfg, tox.ini
+ :caption: setup.cfg or tox.ini
[coverage:run]
omit =
@@ -139,7 +139,7 @@ current directory:
# omit this single file
utils/tirefire.py
-.. [[[end]]] (checksum: 9fa764509b4c484ea613298a20d4b577)
+.. [[[end]]] (checksum: 84ad2743cc0c7a077770e50fcedab29d)
The ``source``, ``include``, and ``omit`` values all work together to determine
the source that will be measured.
From 7272390e892f44b0eb68c13a6c2ff04e681b1716 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Sun, 30 Jun 2024 17:03:36 -0400
Subject: [PATCH 14/37] docs: Python 3.13.0b3
---
CHANGES.rst | 2 +-
README.rst | 2 +-
doc/index.rst | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index c2e2b1215..070098aa5 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -23,7 +23,7 @@ upgrading your version of coverage.py.
Unreleased
----------
-Nothing yet.
+- Python 3.13.0b3 is supported.
.. scriv-start-here
diff --git a/README.rst b/README.rst
index a48d35133..a9c8eb6a7 100644
--- a/README.rst
+++ b/README.rst
@@ -25,7 +25,7 @@ Coverage.py runs on these versions of Python:
.. PYVERSIONS
-* Python 3.8 through 3.12, and 3.13.0b2.
+* Python 3.8 through 3.12, and 3.13.0b3.
* PyPy3 versions 3.8 through 3.10.
Documentation is on `Read the Docs`_. Code repository and issue tracker are on
diff --git a/doc/index.rst b/doc/index.rst
index 9dd939690..748b963af 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -18,7 +18,7 @@ supported on:
.. PYVERSIONS
-* Python 3.8 through 3.12, and 3.13.0b2.
+* Python 3.8 through 3.12, and 3.13.0b3.
* PyPy3 versions 3.8 through 3.10.
.. ifconfig:: prerelease
From c97272ed5ab059bea8f449c15bac0c0ca1aac0a1 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 2 Jul 2024 08:35:09 -0400
Subject: [PATCH 15/37] chore: bump github/codeql-action from 3.25.10 to
3.25.11 (#1805)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.10 to 3.25.11.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/23acc5c183826b7a8a97bce3cecc52db901f8251...b611370bb5703a7efb587f9d136a52ea24c5c38c)
---
updated-dependencies:
- dependency-name: github/codeql-action
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/codeql-analysis.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index a9eb4b915..0934c2528 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -49,7 +49,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3
+ uses: github/codeql-action/init@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -60,7 +60,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3
+ uses: github/codeql-action/autobuild@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3
# âšī¸ Command-line programs to run using the OS shell.
# đ https://git.io/JvXDl
@@ -74,4 +74,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3
+ uses: github/codeql-action/analyze@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3
From de3b92dbfa7b9eb0f5b43b480732201ab7a95ea6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 3 Jul 2024 14:07:21 -0400
Subject: [PATCH 16/37] chore: bump docker/setup-qemu-action from 3.0.0 to
3.1.0 (#1808)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/68827325e0b33c7199eb31dd4e31fbe9023e06e3...5927c834f5b4fdf503fca6f4c7eccda82949e1ee)
---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/kit.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/kit.yml b/.github/workflows/kit.yml
index 93e744625..0351c89be 100644
--- a/.github/workflows/kit.yml
+++ b/.github/workflows/kit.yml
@@ -143,7 +143,7 @@ jobs:
steps:
- name: "Setup QEMU"
if: matrix.os == 'ubuntu'
- uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
+ uses: docker/setup-qemu-action@5927c834f5b4fdf503fca6f4c7eccda82949e1ee # v3.1.0
with:
platforms: arm64
From 21d276abeaceea899edfdfb92484f83a7b9a0579 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Wed, 3 Jul 2024 18:58:44 -0400
Subject: [PATCH 17/37] test(coverage): self.stop() will never be covered;
nested coverage
---
coverage/control.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/coverage/control.py b/coverage/control.py
index 4e1d359ed..583fed946 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -675,7 +675,7 @@ def collect(self) -> Iterator[None]:
try:
yield
finally:
- self.stop()
+ self.stop() # pragma: nested
def _atexit(self, event: str = "atexit") -> None:
"""Clean up on process shutdown."""
From fd4f83c31420876ecebe5b212484333a4fd049c2 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Wed, 3 Jul 2024 19:07:25 -0400
Subject: [PATCH 18/37] test(coverage): this plugin is used ad-hoc during
debugging, it won't be covered
---
tests/select_plugin.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/select_plugin.py b/tests/select_plugin.py
index 9ca48ff35..4239608fc 100644
--- a/tests/select_plugin.py
+++ b/tests/select_plugin.py
@@ -25,7 +25,7 @@ def pytest_addoption(parser):
)
-def pytest_collection_modifyitems(config, items):
+def pytest_collection_modifyitems(config, items): # pragma: debugging
"""Run an external command to get a list of tests to run."""
select_cmd = config.getoption("--select-cmd")
if select_cmd:
From 1fe897d740ebc67bd1f6493bcc889846f16abf10 Mon Sep 17 00:00:00 2001
From: devdanzin <74280297+devdanzin@users.noreply.github.com>
Date: Thu, 4 Jul 2024 17:40:49 -0300
Subject: [PATCH 19/37] feat: multiline exclusion regexes (fix #996) (#1807)
* Support excluding multiline regexes, O(N) algorithm by nedbat.
* Fix test_parser.py.
* Actually fix tests and mypy check.
* Format multiline exclusion tests.
* Apply suggestions from code review by @nedbat.
Co-authored-by: Ned Batchelder
* Improve and add new tests to test_parser.py.
* Skip test_multiline_exclusion_block2 if Python version < 3.10.
* Add tests for exclusion of a whole module or from a marker until the end of the file.
* tweak the regexes in the tests
* test: add one more test for the specific #996 use
* docs: explain multi-line exclusion regexes
* build: the next version will be 7.6.0
* better: no whitespace in regexes
---------
Co-authored-by: Ned Batchelder
---
CHANGES.rst | 14 ++++
coverage/parser.py | 20 +++--
coverage/version.py | 2 +-
doc/excluding.rst | 96 ++++++++++++++++++++-
tests/test_parser.py | 195 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 317 insertions(+), 10 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index 070098aa5..f6fb6f5b2 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -23,8 +23,22 @@ upgrading your version of coverage.py.
Unreleased
----------
+- Exclusion patterns can now be multi-line, thanks to `Daniel Diniz `_. This enables many interesting exclusion use-cases, including those
+ requested in issues `118 `_ (entire files), `996
+ `_ (multiple lines only when appearing together), `1741
+ `_ (remainder of a function), and `1803 `_
+ (arbitrary sequence of marked lines). See the :ref:`multi_line_exclude`
+ section of the docs for more details and examples.
+
- Python 3.13.0b3 is supported.
+.. _issue 118: https://github.com/nedbat/coveragepy/issues/118
+.. _issue 996: https://github.com/nedbat/coveragepy/issues/996
+.. _issue 1741: https://github.com/nedbat/coveragepy/issues/1741
+.. _issue 1803: https://github.com/nedbat/coveragepy/issues/1803
+
+.. _pull 1807: https://github.com/nedbat/coveragepy/pull/1807
.. scriv-start-here
diff --git a/coverage/parser.py b/coverage/parser.py
index 00ccbf10a..19267a718 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -105,14 +105,22 @@ def lines_matching(self, regex: str) -> set[TLineNo]:
"""Find the lines matching a regex.
Returns a set of line numbers, the lines that contain a match for
- `regex`. The entire line needn't match, just a part of it.
+ `regex`. The entire line needn't match, just a part of it.
+ Handles multiline regex patterns.
"""
- regex_c = re.compile(regex)
- matches = set()
- for i, ltext in enumerate(self.text.split("\n"), start=1):
- if regex_c.search(ltext):
- matches.add(self._multiline.get(i, i))
+ regex_c = re.compile(regex, re.MULTILINE)
+ matches: set[TLineNo] = set()
+
+ last_start = 0
+ last_start_line = 0
+ for match in regex_c.finditer(self.text):
+ start, end = match.span()
+ start_line = last_start_line + self.text.count('\n', last_start, start)
+ end_line = last_start_line + self.text.count('\n', last_start, end)
+ matches.update(self._multiline.get(i, i) for i in range(start_line + 1, end_line + 2))
+ last_start = start
+ last_start_line = start_line
return matches
def _raw_parse(self) -> None:
diff --git a/coverage/version.py b/coverage/version.py
index 15be60ad2..fae238809 100644
--- a/coverage/version.py
+++ b/coverage/version.py
@@ -8,7 +8,7 @@
# version_info: same semantics as sys.version_info.
# _dev: the .devN suffix if any.
-version_info = (7, 5, 5, "alpha", 0)
+version_info = (7, 6, 0, "alpha", 0)
_dev = 1
diff --git a/doc/excluding.rst b/doc/excluding.rst
index 80f0c79dd..3ade282fc 100644
--- a/doc/excluding.rst
+++ b/doc/excluding.rst
@@ -73,13 +73,17 @@ line, so it isn't considered a branch at all.
Advanced exclusion
------------------
-Coverage.py identifies exclusions by matching lines against a list of regular
-expressions. Using :ref:`configuration files ` or the coverage
+Coverage.py identifies exclusions by matching source code against a list of
+regular expressions. Using :ref:`configuration files ` or the coverage
:ref:`API `, you can add to that list. This is useful if you have
often-used constructs to exclude that can be matched with a regex. You can
exclude them all at once without littering your code with exclusion pragmas.
-If the matched line introduces a block, the entire block is excluded from
+Before coverage.py 7.6.0, the regexes were matched against single lines of your
+source code. Now they can be multi-line regexes that find matches across
+lines. See :ref:`multi_line_exclude`.
+
+If a matched line introduces a block, the entire block is excluded from
reporting. Matching a ``def`` line or decorator line will exclude an entire
function.
@@ -232,6 +236,92 @@ A similar pragma, "no branch", can be used to tailor branch coverage
measurement. See :ref:`branch` for details.
+.. _multi_line_exclude:
+
+Multi-line exclusion regexes
+----------------------------
+
+.. versionadded:: 7.6.0
+
+Exclusion regexes can match multi-line regions. All of the lines in a matched
+region will be excluded. If part of the region introduces a block, the entire
+block is excluded even if part of it is outside the matched region.
+
+When writing regexes to match multiple lines, remember that ``"."`` won't match
+a newline character, but ``"\n"`` or ``"(?s:.)"`` will. Using the ``"(?s)"``
+flag in your regex will also make dot match a newline.
+
+Here are some examples:
+
+.. [[[cog
+ show_configs(
+ ini=r"""
+ [report]
+ exclude_also =
+ ; Exclude an except clause of a specific form:
+ except ValueError:\n\s*assume\(False\)
+ ; A pragma comment that excludes an entire file:
+ (?s)\A.*# pragma: exclude file.*\Z
+ """,
+ toml=r"""
+ [tool.coverage.report]
+ exclude_also = [
+ # Exclude an except clause of a specific form:
+ "except ValueError:\\n\\s*assume\\(False\\)",
+ # A pragma comment that excludes an entire file:
+ "(?s)\\A.*# pragma: exclude file.*\\Z",
+ ]
+ """,
+ )
+.. ]]]
+
+.. tabs::
+
+ .. code-tab:: ini
+ :caption: .coveragerc
+
+ [report]
+ exclude_also =
+ ; Exclude an except clause of a specific form:
+ except ValueError:\n\s*assume\(False\)
+ ; A pragma comment that excludes an entire file:
+ (?s)\A.*# pragma: exclude file.*\Z
+
+ .. code-tab:: toml
+ :caption: pyproject.toml
+
+ [tool.coverage.report]
+ exclude_also = [
+ # Exclude an except clause of a specific form:
+ "except ValueError:\\n\\s*assume\\(False\\)",
+ # A pragma comment that excludes an entire file:
+ "(?s)\\A.*# pragma: exclude file.*\\Z",
+ ]
+
+ .. code-tab:: ini
+ :caption: setup.cfg or tox.ini
+
+ [coverage:report]
+ exclude_also =
+ ; Exclude an except clause of a specific form:
+ except ValueError:\n\s*assume\(False\)
+ ; A pragma comment that excludes an entire file:
+ (?s)\A.*# pragma: exclude file.*\Z
+
+.. [[[end]]] (checksum: 8892a4efef9da67fb0080d15811e1c19)
+
+The first regex matches a specific except line followed by a specific function
+call. Both lines must be present for the exclusion to take effect. Note that
+the regex uses ``"\n\s*"`` to match the newline and the indentation of the
+second line. Without these, the regex won't match.
+
+The second regex matches the entire text of a file containing the comment ``#
+pragma: exclude file``. This lets you exclude files from coverage measurement
+with an internal comment instead of naming them in a settings file. This regex
+uses the ``"(?s)"`` regex flag to let a dot match any character including a
+newline.
+
+
Excluding source files
----------------------
diff --git a/tests/test_parser.py b/tests/test_parser.py
index e9ad91a05..f5891e127 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -781,6 +781,201 @@ def function() -> int:
assert parser.raw_statements == {1, 3, 4, 5, 6, 8, 9}
assert parser.statements == {1, 8, 9}
+ def test_multiline_exclusion_single_line(self) -> None:
+ regex = r"print\('.*'\)"
+ parser = self.parse_text("""\
+ def foo():
+ print('Hello, world!')
+ """, regex)
+ assert parser.lines_matching(regex) == {2}
+ assert parser.raw_statements == {1, 2}
+ assert parser.statements == {1}
+
+ def test_multiline_exclusion_suite(self) -> None:
+ # A multi-line exclusion that matches a colon line still excludes the entire block.
+ regex = r"if T:\n\s+print\('Hello, world!'\)"
+ parser = self.parse_text("""\
+ def foo():
+ if T:
+ print('Hello, world!')
+ print('This is a multiline regex test.')
+ a = 5
+ """, regex)
+ assert parser.lines_matching(regex) == {2, 3}
+ assert parser.raw_statements == {1, 2, 3, 4, 5}
+ assert parser.statements == {1, 5}
+
+ def test_multiline_exclusion_no_match(self) -> None:
+ regex = r"nonexistent"
+ parser = self.parse_text("""\
+ def foo():
+ print('Hello, world!')
+ """, regex)
+ assert parser.lines_matching(regex) == set()
+ assert parser.raw_statements == {1, 2}
+ assert parser.statements == {1, 2}
+
+ def test_multiline_exclusion_no_source(self) -> None:
+ regex = r"anything"
+ parser = PythonParser(text="", filename="dummy.py", exclude=regex)
+ assert parser.lines_matching(regex) == set()
+ assert parser.raw_statements == set()
+ assert parser.statements == set()
+
+ def test_multiline_exclusion_all_lines_must_match(self) -> None:
+ # https://github.com/nedbat/coveragepy/issues/996
+ regex = r"except ValueError:\n\s*print\('false'\)"
+ parser = self.parse_text("""\
+ try:
+ a = 2
+ print('false')
+ except ValueError:
+ print('false')
+ except ValueError:
+ print('something else')
+ except IndexError:
+ print('false')
+ """, regex)
+ assert parser.lines_matching(regex) == {4, 5}
+ assert parser.raw_statements == {1, 2, 3, 4, 5, 6, 7, 8, 9}
+ assert parser.statements == {1, 2, 3, 6, 7, 8, 9}
+
+ def test_multiline_exclusion_multiple_matches(self) -> None:
+ regex = r"print\('.*'\)\n\s+. = \d"
+ parser = self.parse_text("""\
+ def foo():
+ print('Hello, world!')
+ a = 5
+ def bar():
+ print('Hello again!')
+ b = 6
+ """, regex)
+ assert parser.lines_matching(regex) == {2, 3, 5, 6}
+ assert parser.raw_statements == {1, 2, 3, 4, 5, 6}
+ assert parser.statements == {1, 4}
+
+ def test_multiline_exclusion_suite2(self) -> None:
+ regex = r"print\('Hello, world!'\)\n\s+if T:"
+ parser = self.parse_text("""\
+ def foo():
+ print('Hello, world!')
+ if T:
+ print('This is a test.')
+ """, regex)
+ assert parser.lines_matching(regex) == {2, 3}
+ assert parser.raw_statements == {1, 2, 3, 4}
+ assert parser.statements == {1}
+
+ def test_multiline_exclusion_match_all(self) -> None:
+ regex = (
+ r"def foo\(\):\n\s+print\('Hello, world!'\)\n"
+ + r"\s+if T:\n\s+print\('This is a test\.'\)"
+ )
+ parser = self.parse_text("""\
+ def foo():
+ print('Hello, world!')
+ if T:
+ print('This is a test.')
+ """, regex)
+ assert parser.lines_matching(regex) == {1, 2, 3, 4}
+ assert parser.raw_statements == {1, 2, 3, 4}
+ assert parser.statements == set()
+
+ def test_multiline_exclusion_block(self) -> None:
+ # https://github.com/nedbat/coveragepy/issues/1803
+ regex = "# no cover: start(?s:.)*# no cover: stop"
+ parser = self.parse_text("""\
+ a = my_function1()
+ if debug:
+ msg = "blah blah"
+ # no cover: start
+ log_message(msg, a)
+ b = my_function2()
+ # no cover: stop
+ """, regex)
+ assert parser.lines_matching(regex) == {4, 5, 6, 7}
+ assert parser.raw_statements == {1, 2, 3, 5, 6}
+ assert parser.statements == {1, 2, 3}
+
+ @pytest.mark.skipif(not env.PYBEHAVIOR.match_case, reason="Match-case is new in 3.10")
+ def test_multiline_exclusion_block2(self) -> None:
+ # https://github.com/nedbat/coveragepy/issues/1797
+ regex = r"case _:\n\s+assert_never\("
+ parser = self.parse_text("""\
+ match something:
+ case type_1():
+ logic_1()
+ case type_2():
+ logic_2()
+ case _:
+ assert_never(something)
+ match something:
+ case type_1():
+ logic_1()
+ case type_2():
+ logic_2()
+ case _:
+ print("Default case")
+ """, regex)
+ assert parser.lines_matching(regex) == {6, 7}
+ assert parser.raw_statements == {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}
+ assert parser.statements == {1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14}
+
+ def test_multiline_exclusion_block3(self) -> None:
+ # https://github.com/nedbat/coveragepy/issues/1741
+ # This will only work if there's exactly one return statement in the rest of the function
+ regex = r"# no cover: to return(?s:.)*return"
+ parser = self.parse_text("""\
+ def my_function(args, j):
+ if args.command == Commands.CMD.value:
+ return cmd_handler(j, args)
+ # no cover: to return
+ print(f"Command '{args.command}' was not handled.", file=sys.stderr)
+ parser.print_help(file=sys.stderr)
+
+ return os.EX_USAGE
+ print("not excluded")
+ """, regex)
+ assert parser.lines_matching(regex) == {4, 5, 6, 7, 8}
+ assert parser.raw_statements == {1, 2, 3, 5, 6, 8, 9}
+ assert parser.statements == {1, 2, 3, 9}
+
+ def test_multiline_exclusion_whole_source(self) -> None:
+ # https://github.com/nedbat/coveragepy/issues/118
+ regex = r"(?s)\A.*# pragma: exclude file.*\Z"
+ parser = self.parse_text("""\
+ import coverage
+ # pragma: exclude file
+ def the_void() -> None:
+ if "py" not in __file__:
+ print("Not a Python file.")
+ print("Everything here is excluded.")
+
+ return
+ print("Excluded too")
+ """, regex)
+ assert parser.lines_matching(regex) == {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+ assert parser.raw_statements == {1, 3, 4, 5, 6, 8, 9}
+ assert parser.statements == set()
+
+ def test_multiline_exclusion_from_marker(self) -> None:
+ # https://github.com/nedbat/coveragepy/issues/118
+ regex = r"# pragma: rest of file(?s:.)*\Z"
+ parser = self.parse_text("""\
+ import coverage
+ # pragma: rest of file
+ def the_void() -> None:
+ if "py" not in __file__:
+ print("Not a Python file.")
+ print("Everything here is excluded.")
+
+ return
+ print("Excluded too")
+ """, regex)
+ assert parser.lines_matching(regex) == {2, 3, 4, 5, 6, 7, 8, 9, 10}
+ assert parser.raw_statements == {1, 3, 4, 5, 6, 8, 9}
+ assert parser.statements == {1}
+
class ParserMissingArcDescriptionTest(PythonParserTestBase):
"""Tests for PythonParser.missing_arc_description."""
From 7ea6c39365d719ce2049f902975a79b9925264d5 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Thu, 4 Jul 2024 17:11:45 -0400
Subject: [PATCH 20/37] docs: explain the need for non-greedy wildcards
---
doc/cog_helpers.py | 2 +-
doc/excluding.rst | 42 ++++++++++++++++++++++++++++++------------
tests/test_parser.py | 4 ++--
3 files changed, 33 insertions(+), 15 deletions(-)
diff --git a/doc/cog_helpers.py b/doc/cog_helpers.py
index 8cdb65f09..d30030875 100644
--- a/doc/cog_helpers.py
+++ b/doc/cog_helpers.py
@@ -81,7 +81,7 @@ def show_configs(ini, toml):
toml, toml_vals = _read_config(toml, "covrc.toml")
for key, val in ini_vals.items():
if val != toml_vals[key]:
- cog.error(f"Mismatch! {key}: {val!r} vs {toml_vals[key]!r}")
+ cog.error(f"Mismatch! {key}:\nini: {val!r}\ntoml: {toml_vals[key]!r}")
ini2 = re.sub(r"(?m)^\[", "[coverage:", ini)
print()
diff --git a/doc/excluding.rst b/doc/excluding.rst
index 3ade282fc..7831f7d9f 100644
--- a/doc/excluding.rst
+++ b/doc/excluding.rst
@@ -258,17 +258,21 @@ Here are some examples:
ini=r"""
[report]
exclude_also =
- ; Exclude an except clause of a specific form:
+ ; 1. Exclude an except clause of a specific form:
except ValueError:\n\s*assume\(False\)
- ; A pragma comment that excludes an entire file:
+ ; 2. Comments to turn coverage on and off:
+ no cover: start(?s:.)*?no cover: stop
+ ; 3. A pragma comment that excludes an entire file:
(?s)\A.*# pragma: exclude file.*\Z
""",
toml=r"""
[tool.coverage.report]
exclude_also = [
- # Exclude an except clause of a specific form:
+ # 1. Exclude an except clause of a specific form:
"except ValueError:\\n\\s*assume\\(False\\)",
- # A pragma comment that excludes an entire file:
+ # 2. Comments to turn coverage on and off:
+ "no cover: start(?s:.)*?no cover: stop",
+ # 3. A pragma comment that excludes an entire file:
"(?s)\\A.*# pragma: exclude file.*\\Z",
]
""",
@@ -282,9 +286,11 @@ Here are some examples:
[report]
exclude_also =
- ; Exclude an except clause of a specific form:
+ ; 1. Exclude an except clause of a specific form:
except ValueError:\n\s*assume\(False\)
- ; A pragma comment that excludes an entire file:
+ ; 2. Comments to turn coverage on and off:
+ no cover: start(?s:.)*?no cover: stop
+ ; 3. A pragma comment that excludes an entire file:
(?s)\A.*# pragma: exclude file.*\Z
.. code-tab:: toml
@@ -292,9 +298,11 @@ Here are some examples:
[tool.coverage.report]
exclude_also = [
- # Exclude an except clause of a specific form:
+ # 1. Exclude an except clause of a specific form:
"except ValueError:\\n\\s*assume\\(False\\)",
- # A pragma comment that excludes an entire file:
+ # 2. Comments to turn coverage on and off:
+ "no cover: start(?s:.)*?no cover: stop",
+ # 3. A pragma comment that excludes an entire file:
"(?s)\\A.*# pragma: exclude file.*\\Z",
]
@@ -303,19 +311,29 @@ Here are some examples:
[coverage:report]
exclude_also =
- ; Exclude an except clause of a specific form:
+ ; 1. Exclude an except clause of a specific form:
except ValueError:\n\s*assume\(False\)
- ; A pragma comment that excludes an entire file:
+ ; 2. Comments to turn coverage on and off:
+ no cover: start(?s:.)*?no cover: stop
+ ; 3. A pragma comment that excludes an entire file:
(?s)\A.*# pragma: exclude file.*\Z
-.. [[[end]]] (checksum: 8892a4efef9da67fb0080d15811e1c19)
+.. [[[end]]] (checksum: 22ff0a1433f00d3b4d13544623aaf884)
The first regex matches a specific except line followed by a specific function
call. Both lines must be present for the exclusion to take effect. Note that
the regex uses ``"\n\s*"`` to match the newline and the indentation of the
second line. Without these, the regex won't match.
-The second regex matches the entire text of a file containing the comment ``#
+The second regex creates a pair of comments that can be used to exclude
+statements between them. All lines between ``# no cover: start`` and ``# no
+cover: stop`` will be excluded. The regex doesn't start with ``#`` because
+that's a comment in a .coveragerc file. Be careful with wildcards: we've used
+the non-greedy ``*?`` to match the fewest possible characters between the
+comments. If you used the greedy ``*`` instead, the star would match as many
+as possible, and you could accidentally exclude large swaths of code.
+
+The third regex matches the entire text of a file containing the comment ``#
pragma: exclude file``. This lets you exclude files from coverage measurement
with an internal comment instead of naming them in a settings file. This regex
uses the ``"(?s)"`` regex flag to let a dot match any character including a
diff --git a/tests/test_parser.py b/tests/test_parser.py
index f5891e127..c19e378cb 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -883,7 +883,7 @@ def foo():
def test_multiline_exclusion_block(self) -> None:
# https://github.com/nedbat/coveragepy/issues/1803
- regex = "# no cover: start(?s:.)*# no cover: stop"
+ regex = "# no cover: start(?s:.)*?# no cover: stop"
parser = self.parse_text("""\
a = my_function1()
if debug:
@@ -924,7 +924,7 @@ def test_multiline_exclusion_block2(self) -> None:
def test_multiline_exclusion_block3(self) -> None:
# https://github.com/nedbat/coveragepy/issues/1741
# This will only work if there's exactly one return statement in the rest of the function
- regex = r"# no cover: to return(?s:.)*return"
+ regex = r"# no cover: to return(?s:.)*?return"
parser = self.parse_text("""\
def my_function(args, j):
if args.command == Commands.CMD.value:
From b113eee8858206df07b6fc9568b5dca125ed85fc Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 8 Jul 2024 16:00:25 -0400
Subject: [PATCH 21/37] chore: bump actions/download-artifact from 4.1.7 to
4.1.8 (#1810)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.7 to 4.1.8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/65a9edc5881444af0b9093a5e628f2fe47ea3b2e...fa0a91b85d4f404e444e00e005971372dc801d16)
---
updated-dependencies:
- dependency-name: actions/download-artifact
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/coverage.yml | 4 ++--
.github/workflows/kit.yml | 2 +-
.github/workflows/publish.yml | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index cfcae0a69..7707b3ed2 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -167,7 +167,7 @@ jobs:
python igor.py zip_mods
- name: "Download coverage data"
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
pattern: metacov-*
merge-multiple: true
@@ -234,7 +234,7 @@ jobs:
- name: "Download coverage HTML report"
if: ${{ github.ref == 'refs/heads/master' }}
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: html_report
path: reports_repo/${{ env.report_dir }}
diff --git a/.github/workflows/kit.yml b/.github/workflows/kit.yml
index 0351c89be..0dbf69f91 100644
--- a/.github/workflows/kit.yml
+++ b/.github/workflows/kit.yml
@@ -280,7 +280,7 @@ jobs:
id-token: write
steps:
- name: "Download artifacts"
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
pattern: dist-*
merge-multiple: true
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index c76e43e3f..183c8f702 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -56,7 +56,7 @@ jobs:
steps:
- name: "Download dists"
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
repository: "nedbat/coveragepy"
run-id: ${{needs.find-run.outputs.run-id}}
@@ -95,7 +95,7 @@ jobs:
steps:
- name: "Download dists"
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
repository: "nedbat/coveragepy"
run-id: ${{needs.find-run.outputs.run-id}}
From c1d17734521f618185466dcaedde322909e5be13 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 8 Jul 2024 16:23:00 -0400
Subject: [PATCH 22/37] chore: bump actions/upload-artifact from 4.3.3 to 4.3.4
(#1811)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.3 to 4.3.4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/65462800fd760344b1a7b4382951275a0abb4808...0b2256b8c012f0828dc542b3febcab082c67f72b)
---
updated-dependencies:
- dependency-name: actions/upload-artifact
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ned Batchelder
---
.github/workflows/coverage.yml | 4 ++--
.github/workflows/kit.yml | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 7707b3ed2..b9c251b91 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -125,7 +125,7 @@ jobs:
mv .metacov .metacov.$MATRIX_ID
- name: "Upload coverage data"
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: metacov-${{ env.MATRIX_ID }}
path: .metacov.*
@@ -181,7 +181,7 @@ jobs:
python igor.py combine_html
- name: "Upload HTML report"
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: html_report
path: htmlcov
diff --git a/.github/workflows/kit.yml b/.github/workflows/kit.yml
index 0dbf69f91..c8b99b2e4 100644
--- a/.github/workflows/kit.yml
+++ b/.github/workflows/kit.yml
@@ -181,7 +181,7 @@ jobs:
python -m twine check wheelhouse/*
- name: "Upload wheels"
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: dist-${{ env.MATRIX_ID }}
path: wheelhouse/*.whl
@@ -219,7 +219,7 @@ jobs:
python -m twine check dist/*
- name: "Upload sdist"
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: dist-sdist
path: dist/*.tar.gz
@@ -261,7 +261,7 @@ jobs:
python -m twine check dist/*
- name: "Upload wheels"
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: dist-pypy
path: dist/*.whl
@@ -295,7 +295,7 @@ jobs:
ls -alR
- name: "Upload signatures"
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: signatures
path: |
From 41ae7bd309ee44a75a5300dfa0088281145f2df1 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 10 Jul 2024 16:25:43 -0400
Subject: [PATCH 23/37] chore: bump actions/attest-build-provenance from 1.3.2
to 1.3.3 (#1813)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 1.3.2 to 1.3.3.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/bdd51370e0416ac948727f861e03c2f05d32d78e...5e9cb68e95676991667494a6a4e59b8a2f13e1d0)
---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/publish.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 183c8f702..c4d5746d1 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -72,7 +72,7 @@ jobs:
ls -1 dist | wc -l
- name: "Generate attestations"
- uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2
+ uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3
with:
subject-path: "dist/*"
@@ -111,7 +111,7 @@ jobs:
ls -1 dist | wc -l
- name: "Generate attestations"
- uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2
+ uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3
with:
subject-path: "dist/*"
From 56212288274ed6383f02854d2c8774f1dbb8584d Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Wed, 10 Jul 2024 14:27:03 -0400
Subject: [PATCH 24/37] build(test): gold files now have cache hashes in them
---
coverage/html.py | 29 ++++++++++++------
igor.py | 8 +++++
tests/gold/README.rst | 2 +-
tests/gold/html/Makefile | 6 +++-
tests/gold/html/a/a_py.html | 10 +++---
tests/gold/html/a/class_index.html | 12 ++++----
tests/gold/html/a/function_index.html | 12 ++++----
tests/gold/html/a/index.html | 12 ++++----
tests/gold/html/b_branch/b_py.html | 8 ++---
tests/gold/html/b_branch/class_index.html | 12 ++++----
tests/gold/html/b_branch/function_index.html | 12 ++++----
tests/gold/html/b_branch/index.html | 12 ++++----
tests/gold/html/bom/bom_py.html | 10 +++---
tests/gold/html/bom/class_index.html | 12 ++++----
tests/gold/html/bom/function_index.html | 12 ++++----
tests/gold/html/bom/index.html | 12 ++++----
tests/gold/html/isolatin1/class_index.html | 12 ++++----
tests/gold/html/isolatin1/function_index.html | 12 ++++----
tests/gold/html/isolatin1/index.html | 12 ++++----
tests/gold/html/isolatin1/isolatin1_py.html | 10 +++---
tests/gold/html/omit_1/class_index.html | 12 ++++----
tests/gold/html/omit_1/function_index.html | 12 ++++----
tests/gold/html/omit_1/index.html | 12 ++++----
tests/gold/html/omit_1/m1_py.html | 10 +++---
tests/gold/html/omit_1/m2_py.html | 10 +++---
tests/gold/html/omit_1/m3_py.html | 10 +++---
tests/gold/html/omit_1/main_py.html | 10 +++---
tests/gold/html/omit_2/class_index.html | 12 ++++----
tests/gold/html/omit_2/function_index.html | 12 ++++----
tests/gold/html/omit_2/index.html | 12 ++++----
tests/gold/html/omit_2/m2_py.html | 10 +++---
tests/gold/html/omit_2/m3_py.html | 10 +++---
tests/gold/html/omit_2/main_py.html | 10 +++---
tests/gold/html/omit_3/class_index.html | 12 ++++----
tests/gold/html/omit_3/function_index.html | 12 ++++----
tests/gold/html/omit_3/index.html | 12 ++++----
tests/gold/html/omit_3/m3_py.html | 10 +++---
tests/gold/html/omit_3/main_py.html | 10 +++---
tests/gold/html/omit_4/class_index.html | 12 ++++----
tests/gold/html/omit_4/function_index.html | 12 ++++----
tests/gold/html/omit_4/index.html | 12 ++++----
tests/gold/html/omit_4/m1_py.html | 10 +++---
tests/gold/html/omit_4/m3_py.html | 10 +++---
tests/gold/html/omit_4/main_py.html | 10 +++---
tests/gold/html/omit_5/class_index.html | 12 ++++----
tests/gold/html/omit_5/function_index.html | 12 ++++----
tests/gold/html/omit_5/index.html | 12 ++++----
tests/gold/html/omit_5/m1_py.html | 10 +++---
tests/gold/html/omit_5/main_py.html | 10 +++---
tests/gold/html/other/blah_blah_other_py.html | 20 ++++++------
tests/gold/html/other/class_index.html | 16 +++++-----
tests/gold/html/other/function_index.html | 16 +++++-----
tests/gold/html/other/here_py.html | 20 ++++++------
tests/gold/html/other/index.html | 16 +++++-----
tests/gold/html/partial_626/class_index.html | 12 ++++----
.../gold/html/partial_626/function_index.html | 12 ++++----
tests/gold/html/partial_626/index.html | 12 ++++----
tests/gold/html/partial_626/partial_py.html | 8 ++---
tests/gold/html/styled/a_py.html | 10 +++---
tests/gold/html/styled/class_index.html | 12 ++++----
tests/gold/html/styled/function_index.html | 12 ++++----
tests/gold/html/styled/index.html | 12 ++++----
...e_html.js => coverage_html_cb_6fb7b396.js} | 0
...icon_32.png => favicon_32_cb_58284776.png} | Bin
...losed.png => keybd_closed_cb_ce680311.png} | Bin
.../{style.css => style_cb_8e611ae1.css} | 0
tests/gold/html/unicode/class_index.html | 12 ++++----
tests/gold/html/unicode/function_index.html | 12 ++++----
tests/gold/html/unicode/index.html | 12 ++++----
tests/gold/html/unicode/unicode_py.html | 10 +++---
tests/goldtest.py | 2 ++
71 files changed, 398 insertions(+), 375 deletions(-)
rename tests/gold/html/support/{coverage_html.js => coverage_html_cb_6fb7b396.js} (100%)
rename tests/gold/html/support/{favicon_32.png => favicon_32_cb_58284776.png} (100%)
rename tests/gold/html/support/{keybd_closed.png => keybd_closed_cb_ce680311.png} (100%)
rename tests/gold/html/support/{style.css => style_cb_8e611ae1.css} (100%)
diff --git a/coverage/html.py b/coverage/html.py
index 8669076d3..860327b64 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -214,6 +214,23 @@ def encode_int(n: int) -> str:
return "".join(r)
+def copy_with_cache_bust(src: str, dest_dir: str) -> str:
+ """Copy `src` to `dest_dir`, adding a hash to the name.
+
+ Returns the updated destination file name with hash.
+ """
+ with open(src, "rb") as f:
+ text = f.read()
+ h = Hasher()
+ h.update(text)
+ cache_bust = h.hexdigest()[:8]
+ src_base = os.path.basename(src)
+ dest = src_base.replace(".", f"_cb_{cache_bust}.")
+ with open(os.path.join(dest_dir, dest), "wb") as f:
+ f.write(text)
+ return dest
+
+
class HtmlReporter:
"""HTML reporting."""
@@ -362,18 +379,10 @@ def make_directory(self) -> None:
def copy_static_file(self, src: str, slug: str = "") -> None:
"""Copy a static file into the output directory with cache busting."""
- with open(src, "rb") as f:
- text = f.read()
- h = Hasher()
- h.update(text)
- cache_bust = h.hexdigest()[:8]
- src_base = os.path.basename(src)
- dest = src_base.replace(".", f"_cb_{cache_bust}.")
+ dest = copy_with_cache_bust(src, self.directory)
if not slug:
- slug = src_base.replace(".", "_")
+ slug = os.path.basename(src).replace(".", "_")
self.template_globals["statics"][slug] = dest # type: ignore
- with open(os.path.join(self.directory, dest), "wb") as f:
- f.write(text)
def make_local_static_report_files(self) -> None:
"""Make local instances of static files for HTML report."""
diff --git a/igor.py b/igor.py
index f02ffe68c..5037cb503 100644
--- a/igor.py
+++ b/igor.py
@@ -475,6 +475,14 @@ def do_cheats():
)
+def do_copy_with_hash(*args):
+ """Copy files with a cache-busting hash. Used in tests/gold/html/Makefile."""
+ from coverage.html import copy_with_cache_bust
+ *srcs, dest_dir = args
+ for src in srcs:
+ copy_with_cache_bust(src, dest_dir)
+
+
def do_help():
"""List the available commands"""
items = list(globals().items())
diff --git a/tests/gold/README.rst b/tests/gold/README.rst
index c90c9eece..00b43ff28 100644
--- a/tests/gold/README.rst
+++ b/tests/gold/README.rst
@@ -19,7 +19,7 @@ directories, and then add the supporting files so they can be viewed as
complete output. For example::
cp tests/actual/html/contexts/* tests/gold/html/contexts
- cd tests/actual/html
+ cd tests/gold/html
make complete
If the new actual output is correct, you can use "make update-gold" to copy the
diff --git a/tests/gold/html/Makefile b/tests/gold/html/Makefile
index 1b75f73d5..5ae08b44e 100644
--- a/tests/gold/html/Makefile
+++ b/tests/gold/html/Makefile
@@ -24,4 +24,8 @@ update-gold: ## Copy actual output files from latest tests to gold files.
true
update-support: ## Copy latest support files here for posterity.
- cp ../../../coverage/htmlfiles/*.{css,js,png} support
+ python -m pip install -e ../../..
+ git rm --ignore-unmatch support/*.{css,js,png}
+ mkdir -p support
+ python ../../../igor.py copy_with_hash ../../../coverage/htmlfiles/*.{css,js,png} support
+ git add support
diff --git a/tests/gold/html/a/a_py.html b/tests/gold/html/a/a_py.html
index 911e44771..1b294759e 100644
--- a/tests/gold/html/a/a_py.html
+++ b/tests/gold/html/a/a_py.html
@@ -5,7 +5,7 @@
Coverage for a.py: 67%
-
+