diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 3408e4455..c6841b18a 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -7,7 +7,11 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
- # Check for updates to GitHub Actions every weekday
- interval: "daily"
+ # Check for updates to GitHub Actions once a week
+ interval: "weekly"
+ groups:
+ action-dependencies:
+ patterns:
+ - "*"
commit-message:
prefix: "chore"
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 0934c2528..fbb600a5d 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@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3
+ uses: github/codeql-action/init@2d790406f505036ef40ecba973cc774a50395aac # 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@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3
+ uses: github/codeql-action/autobuild@2d790406f505036ef40ecba973cc774a50395aac # 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@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3
+ uses: github/codeql-action/analyze@2d790406f505036ef40ecba973cc774a50395aac # v3
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index 912e215f5..c578fafec 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: 'Dependency Review'
- uses: actions/dependency-review-action@72eb03d02c7872a771aacd928f3123ac62ad6d3a # v4.3.3
+ uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
with:
base-ref: ${{ github.event.pull_request.base.sha || 'master' }}
head-ref: ${{ github.event.pull_request.head.sha || github.ref }}
diff --git a/.github/workflows/kit.yml b/.github/workflows/kit.yml
index a54bdbc01..d9e485bab 100644
--- a/.github/workflows/kit.yml
+++ b/.github/workflows/kit.yml
@@ -77,10 +77,9 @@ jobs:
# "macos": ["arm64", "x86_64"],
# "windows": ["x86", "AMD64"],
# }
- # # PYVERSIONS. Available versions:
- # # https://github.com/actions/python-versions/blob/main/versions-manifest.json
+ # # PYVERSIONS. Available versions: https://pypi.org/project/cibuildwheel/
# # PyPy versions are handled further below in the "pypy" step.
- # pys = ["cp38", "cp39", "cp310", "cp311", "cp312"]
+ # pys = ["cp38", "cp39", "cp310", "cp311", "cp312", "cp313"]
#
# # Some OS/arch combinations need overrides for the Python versions:
# os_arch_pys = {
@@ -107,43 +106,50 @@ jobs:
- {"os": "ubuntu", "py": "cp310", "arch": "x86_64"}
- {"os": "ubuntu", "py": "cp311", "arch": "x86_64"}
- {"os": "ubuntu", "py": "cp312", "arch": "x86_64"}
+ - {"os": "ubuntu", "py": "cp313", "arch": "x86_64"}
- {"os": "ubuntu", "py": "cp38", "arch": "i686"}
- {"os": "ubuntu", "py": "cp39", "arch": "i686"}
- {"os": "ubuntu", "py": "cp310", "arch": "i686"}
- {"os": "ubuntu", "py": "cp311", "arch": "i686"}
- {"os": "ubuntu", "py": "cp312", "arch": "i686"}
+ - {"os": "ubuntu", "py": "cp313", "arch": "i686"}
- {"os": "ubuntu", "py": "cp38", "arch": "aarch64"}
- {"os": "ubuntu", "py": "cp39", "arch": "aarch64"}
- {"os": "ubuntu", "py": "cp310", "arch": "aarch64"}
- {"os": "ubuntu", "py": "cp311", "arch": "aarch64"}
- {"os": "ubuntu", "py": "cp312", "arch": "aarch64"}
+ - {"os": "ubuntu", "py": "cp313", "arch": "aarch64"}
- {"os": "macos", "py": "cp38", "arch": "arm64", "os-version": "13"}
- {"os": "macos", "py": "cp39", "arch": "arm64", "os-version": "13"}
- {"os": "macos", "py": "cp310", "arch": "arm64", "os-version": "13"}
- {"os": "macos", "py": "cp311", "arch": "arm64", "os-version": "13"}
- {"os": "macos", "py": "cp312", "arch": "arm64", "os-version": "13"}
+ - {"os": "macos", "py": "cp313", "arch": "arm64", "os-version": "13"}
- {"os": "macos", "py": "cp38", "arch": "x86_64", "os-version": "13"}
- {"os": "macos", "py": "cp39", "arch": "x86_64", "os-version": "13"}
- {"os": "macos", "py": "cp310", "arch": "x86_64", "os-version": "13"}
- {"os": "macos", "py": "cp311", "arch": "x86_64", "os-version": "13"}
- {"os": "macos", "py": "cp312", "arch": "x86_64", "os-version": "13"}
+ - {"os": "macos", "py": "cp313", "arch": "x86_64", "os-version": "13"}
- {"os": "windows", "py": "cp38", "arch": "x86"}
- {"os": "windows", "py": "cp39", "arch": "x86"}
- {"os": "windows", "py": "cp310", "arch": "x86"}
- {"os": "windows", "py": "cp311", "arch": "x86"}
- {"os": "windows", "py": "cp312", "arch": "x86"}
+ - {"os": "windows", "py": "cp313", "arch": "x86"}
- {"os": "windows", "py": "cp38", "arch": "AMD64"}
- {"os": "windows", "py": "cp39", "arch": "AMD64"}
- {"os": "windows", "py": "cp310", "arch": "AMD64"}
- {"os": "windows", "py": "cp311", "arch": "AMD64"}
- {"os": "windows", "py": "cp312", "arch": "AMD64"}
- # [[[end]]] (checksum: 16ed28c185d540b2d9972a0217864472)
+ - {"os": "windows", "py": "cp313", "arch": "AMD64"}
+ # [[[end]]] (checksum: e0cd49f4a0028c4fdf1036e9bc843075)
fail-fast: false
steps:
- name: "Setup QEMU"
if: matrix.os == 'ubuntu'
- uses: docker/setup-qemu-action@5927c834f5b4fdf503fca6f4c7eccda82949e1ee # v3.1.0
+ uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
with:
platforms: arm64
@@ -164,10 +170,11 @@ jobs:
- name: "Build wheels"
env:
- CIBW_BUILD: ${{ matrix.py }}-*
+ CIBW_BUILD: ${{ matrix.py }}*-*
CIBW_ARCHS: ${{ matrix.arch }}
CIBW_ENVIRONMENT: PIP_DISABLE_PIP_VERSION_CHECK=1
CIBW_PRERELEASE_PYTHONS: True
+ CIBW_FREE_THREADED_SUPPORT: True
CIBW_TEST_COMMAND: python -c "from coverage.tracer import CTracer; print('CTracer OK!')"
run: |
python -m cibuildwheel --output-dir wheelhouse
@@ -285,8 +292,14 @@ jobs:
pattern: dist-*
merge-multiple: true
+ - name: "List distributions"
+ run: |
+ ls -alR
+ echo "Number of dists, there should be 72:"
+ ls -1 coverage-* | wc -l
+
- name: "Sign artifacts"
- uses: sigstore/gh-action-sigstore-python@61f6a500bbfdd9a2a339cf033e5421951fbc1cd2 # v2.1.1
+ uses: sigstore/gh-action-sigstore-python@f514d46b907ebcd5bedc05145c03b69c1edd8b46 # v3.0.0
with:
inputs: coverage-*.*
@@ -299,7 +312,5 @@ jobs:
with:
name: signatures
path: |
- *.crt
- *.sig
- *.sigstore
+ *.sigstore.json
retention-days: 7
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index c4d5746d1..35ae00e28 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -68,7 +68,7 @@ jobs:
- name: "What did we get?"
run: |
ls -alR
- echo "Number of dists:"
+ echo "Number of dists, should be 72:"
ls -1 dist | wc -l
- name: "Generate attestations"
@@ -107,7 +107,7 @@ jobs:
- name: "What did we get?"
run: |
ls -alR
- echo "Number of dists:"
+ echo "Number of dists, should be 72:"
ls -1 dist | wc -l
- name: "Generate attestations"
diff --git a/.github/workflows/python-nightly.yml b/.github/workflows/python-nightly.yml
index fb4341e27..a91e58c41 100644
--- a/.github/workflows/python-nightly.yml
+++ b/.github/workflows/python-nightly.yml
@@ -104,6 +104,7 @@ jobs:
set -xe
python -VV
python -m site
+ python -m sysconfig
python -c "import sys; print('GIL:', getattr(sys, '_is_gil_enabled', lambda: True)())"
python -m coverage debug sys
python -m coverage debug pybehave
diff --git a/CHANGES.rst b/CHANGES.rst
index 13e3c1eb9..573cbdefe 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -22,6 +22,24 @@ upgrading your version of coverage.py.
.. scriv-start-here
+.. _changes_7-6-1:
+
+Version 7.6.1 — 2024-08-04
+--------------------------
+
+- Fix: coverage used to fail when measuring code using :func:`runpy.run_path
+ ` with a :class:`Path ` argument.
+ This is now fixed, thanks to `Ask Hjorth Larsen `_.
+
+- Fix: backslashes preceding a multi-line backslashed string could confuse the
+ HTML report. This is now fixed, thanks to `LiuYinCarl `_.
+
+- Now we publish wheels for Python 3.13, both regular and free-threaded.
+
+.. _pull 1819: https://github.com/nedbat/coveragepy/pull/1819
+.. _pull 1828: https://github.com/nedbat/coveragepy/pull/1828
+
+
.. _changes_7-6-0:
Version 7.6.0 — 2024-07-11
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 728f2fb1d..9097546ea 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -25,6 +25,7 @@ Artem Dayneko
Arthur Deygin
Arthur Rio
Asher Foa
+Ask Hjorth Larsen
Ben Carlsson
Ben Finney
Benjamin Parzella
@@ -134,6 +135,7 @@ Leonardo Pistone
Lewis Gaul
Lex Berezhny
Liam DeVoe
+LiuYinCarl
Loïc Dachary
Lorenzo Micò
Louis Heredero
diff --git a/README.rst b/README.rst
index 04df255ff..203f8357c 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.0b3.
+* Python 3.8 through 3.12, and 3.13.0rc1, including free-threading.
* PyPy3 versions 3.8 through 3.10.
Documentation is on `Read the Docs`_. Code repository and issue tracker are on
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index 9f9c06559..7c01ccbf0 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -19,9 +19,9 @@
import coverage
from coverage import Coverage
from coverage import env
-from coverage.collector import HAS_CTRACER
from coverage.config import CoverageConfig
from coverage.control import DEFAULT_DATAFILE
+from coverage.core import HAS_CTRACER
from coverage.data import combinable_files, debug_data_file
from coverage.debug import info_header, short_stack, write_formatted_info
from coverage.exceptions import _BaseCoverageException, _ExceptionDuringRun, NoSource
diff --git a/coverage/collector.py b/coverage/collector.py
index 93860e43c..470d3a7e3 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -17,38 +17,27 @@
from coverage import env
from coverage.config import CoverageConfig
+from coverage.core import Core
from coverage.data import CoverageData
from coverage.debug import short_stack
-from coverage.disposition import FileDisposition
from coverage.exceptions import ConfigError
from coverage.misc import human_sorted_items, isolate_module
from coverage.plugin import CoveragePlugin
-from coverage.pytracer import PyTracer
-from coverage.sysmon import SysMonitor
from coverage.types import (
- TArc, TFileDisposition, TTraceData, TTraceFn, TracerCore, TWarnFn,
+ TArc,
+ TCheckIncludeFn,
+ TFileDisposition,
+ TShouldStartContextFn,
+ TShouldTraceFn,
+ TTraceData,
+ TTraceFn,
+ Tracer,
+ TWarnFn,
)
os = isolate_module(os)
-try:
- # Use the C extension code when we can, for speed.
- from coverage.tracer import CTracer, CFileDisposition
- HAS_CTRACER = True
-except ImportError:
- # Couldn't import the C extension, maybe it isn't built.
- if os.getenv("COVERAGE_CORE") == "ctrace": # pragma: part covered
- # During testing, we use the COVERAGE_CORE environment variable
- # to indicate that we've fiddled with the environment to test this
- # fallback code. If we thought we had a C tracer, but couldn't import
- # it, then exit quickly and clearly instead of dribbling confusing
- # errors. I'm using sys.exit here instead of an exception because an
- # exception here causes all sorts of other noise in unittest.
- sys.stderr.write("*** COVERAGE_CORE is 'ctrace' but can't import CTracer!\n")
- sys.exit(1)
- HAS_CTRACER = False
-
T = TypeVar("T")
@@ -78,15 +67,14 @@ class Collector:
def __init__(
self,
- should_trace: Callable[[str, FrameType], TFileDisposition],
- check_include: Callable[[str, FrameType], bool],
- should_start_context: Callable[[FrameType], str | None] | None,
+ core: Core,
+ should_trace: TShouldTraceFn,
+ check_include: TCheckIncludeFn,
+ should_start_context: TShouldStartContextFn | None,
file_mapper: Callable[[str], str],
- timid: bool,
branch: bool,
warn: TWarnFn,
concurrency: list[str],
- metacov: bool,
) -> None:
"""Create a collector.
@@ -105,11 +93,6 @@ def __init__(
filename. The result is the name that will be recorded in the data
file.
- If `timid` is true, then a slower simpler trace function will be
- used. This is important for some environments where manipulation of
- tracing functions make the faster more sophisticated trace function not
- operate properly.
-
If `branch` is true, then branches will be measured. This involves
collecting data on which statements followed each other (arcs). Use
`get_arc_data` to get the arc data.
@@ -124,6 +107,7 @@ def __init__(
Other values are ignored.
"""
+ self.core = core
self.should_trace = should_trace
self.check_include = check_include
self.should_start_context = should_start_context
@@ -143,52 +127,6 @@ def __init__(
self.concur_id_func = None
- self._trace_class: type[TracerCore]
- self.file_disposition_class: type[TFileDisposition]
-
- core: str | None
- if timid:
- core = "pytrace"
- else:
- core = os.getenv("COVERAGE_CORE")
-
- if core == "sysmon" and not env.PYBEHAVIOR.pep669:
- self.warn("sys.monitoring isn't available, using default core", slug="no-sysmon")
- core = None
-
- if not core:
- # Once we're comfortable with sysmon as a default:
- # if env.PYBEHAVIOR.pep669 and self.should_start_context is None:
- # core = "sysmon"
- if HAS_CTRACER:
- core = "ctrace"
- else:
- core = "pytrace"
-
- if core == "sysmon":
- self._trace_class = SysMonitor
- self._core_kwargs = {"tool_id": 3 if metacov else 1}
- self.file_disposition_class = FileDisposition
- self.supports_plugins = False
- self.packed_arcs = False
- self.systrace = False
- elif core == "ctrace":
- self._trace_class = CTracer
- self._core_kwargs = {}
- self.file_disposition_class = CFileDisposition
- self.supports_plugins = True
- self.packed_arcs = True
- self.systrace = True
- elif core == "pytrace":
- self._trace_class = PyTracer
- self._core_kwargs = {}
- self.file_disposition_class = FileDisposition
- self.supports_plugins = False
- self.packed_arcs = False
- self.systrace = True
- else:
- raise ConfigError(f"Unknown core value: {core!r}")
-
# We can handle a few concurrency options here, but only one at a time.
concurrencies = set(self.concurrency)
unknown = concurrencies - CoverageConfig.CONCURRENCY_CHOICES
@@ -222,7 +160,7 @@ def __init__(
msg = f"Couldn't trace with concurrency={tried}, the module isn't installed."
raise ConfigError(msg) from ex
- if self.concur_id_func and not hasattr(self._trace_class, "concur_id_func"):
+ if self.concur_id_func and not hasattr(core.tracer_class, "concur_id_func"):
raise ConfigError(
"Can't support concurrency={} with {}, only threads are supported.".format(
tried, self.tracer_name(),
@@ -249,7 +187,7 @@ def use_data(self, covdata: CoverageData, context: str | None) -> None:
def tracer_name(self) -> str:
"""Return the class name of the tracer we're using."""
- return self._trace_class.__name__
+ return self.core.tracer_class.__name__
def _clear_data(self) -> None:
"""Clear out existing data, but stay ready for more collection."""
@@ -305,7 +243,7 @@ def reset(self) -> None:
self.should_trace_cache = {}
# Our active Tracers.
- self.tracers: list[TracerCore] = []
+ self.tracers: list[Tracer] = []
self._clear_data()
@@ -321,7 +259,7 @@ def unlock_data(self) -> None:
def _start_tracer(self) -> TTraceFn | None:
"""Start a new Tracer object, and store it in self.tracers."""
- tracer = self._trace_class(**self._core_kwargs)
+ tracer = self.core.tracer_class(**self.core.tracer_kwargs)
tracer.data = self.data
tracer.lock_data = self.lock_data
tracer.unlock_data = self.unlock_data
@@ -403,7 +341,7 @@ def start(self) -> None:
# Install our installation tracer in threading, to jump-start other
# threads.
- if self.systrace and self.threading:
+ if self.core.systrace and self.threading:
self.threading.settrace(self._installation_trace)
def stop(self) -> None:
@@ -440,7 +378,7 @@ def resume(self) -> None:
"""Resume tracing after a `pause`."""
for tracer in self.tracers:
tracer.start()
- if self.systrace:
+ if self.core.systrace:
if self.threading:
self.threading.settrace(self._installation_trace)
else:
@@ -523,7 +461,7 @@ def flush_data(self) -> bool:
return False
if self.branch:
- if self.packed_arcs:
+ if self.core.packed_arcs:
# Unpack the line number pairs packed into integers. See
# tracer.c:CTracer_record_pair for the C code that creates
# these packed ints.
diff --git a/coverage/context.py b/coverage/context.py
index c8ee71271..630d3d25a 100644
--- a/coverage/context.py
+++ b/coverage/context.py
@@ -6,12 +6,14 @@
from __future__ import annotations
from types import FrameType
-from typing import cast, Callable, Sequence
+from typing import cast, Sequence
+
+from coverage.types import TShouldStartContextFn
def combine_context_switchers(
- context_switchers: Sequence[Callable[[FrameType], str | None]],
-) -> Callable[[FrameType], str | None] | None:
+ context_switchers: Sequence[TShouldStartContextFn],
+) -> TShouldStartContextFn | None:
"""Create a single context switcher from multiple switchers.
`context_switchers` is a list of functions that take a frame as an
diff --git a/coverage/control.py b/coverage/control.py
index 583fed946..ca757e9e1 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -26,9 +26,10 @@
from coverage import env
from coverage.annotate import AnnotateReporter
-from coverage.collector import Collector, HAS_CTRACER
+from coverage.collector import Collector
from coverage.config import CoverageConfig, read_coverage_config
from coverage.context import should_start_context_test_function, combine_context_switchers
+from coverage.core import Core, HAS_CTRACER
from coverage.data import CoverageData, combine_parallel_data
from coverage.debug import (
DebugControl, NoDebugging, short_stack, write_formatted_info, relevant_environment_display,
@@ -262,6 +263,7 @@ def __init__( # pylint: disable=too-many-arguments
self._inorout: InOrOut | None = None
self._plugins: Plugins = Plugins()
self._data: CoverageData | None = None
+ self._core: Core | None = None
self._collector: Collector | None = None
self._metacov = False
@@ -532,16 +534,20 @@ def _init_for_start(self) -> None:
should_start_context = combine_context_switchers(context_switchers)
+ self._core = Core(
+ warn=self._warn,
+ timid=self.config.timid,
+ metacov=self._metacov,
+ )
self._collector = Collector(
+ core=self._core,
should_trace=self._should_trace,
check_include=self._check_include_omit_etc,
should_start_context=should_start_context,
file_mapper=self._file_mapper,
- timid=self.config.timid,
branch=self.config.branch,
warn=self._warn,
concurrency=concurrency,
- metacov=self._metacov,
)
suffix = self._data_suffix_specified
@@ -563,7 +569,7 @@ def _init_for_start(self) -> None:
self._collector.use_data(self._data, self.config.context)
# Early warning if we aren't going to be able to support plugins.
- if self._plugins.file_tracers and not self._collector.supports_plugins:
+ if self._plugins.file_tracers and not self._core.supports_plugins:
self._warn(
"Plugin file tracers ({}) aren't supported with {}".format(
", ".join(
@@ -584,7 +590,7 @@ def _init_for_start(self) -> None:
include_namespace_packages=self.config.include_namespace_packages,
)
self._inorout.plugins = self._plugins
- self._inorout.disp_class = self._collector.file_disposition_class
+ self._inorout.disp_class = self._core.file_disposition_class
# It's useful to write debug info after initing for start.
self._should_write_debug = True
diff --git a/coverage/core.py b/coverage/core.py
new file mode 100644
index 000000000..89e49c49b
--- /dev/null
+++ b/coverage/core.py
@@ -0,0 +1,90 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Management of core choices."""
+
+from __future__ import annotations
+
+import os
+import sys
+from typing import Any
+
+from coverage import env
+from coverage.disposition import FileDisposition
+from coverage.exceptions import ConfigError
+from coverage.pytracer import PyTracer
+from coverage.sysmon import SysMonitor
+from coverage.types import TFileDisposition, Tracer, TWarnFn
+
+
+try:
+ # Use the C extension code when we can, for speed.
+ from coverage.tracer import CTracer, CFileDisposition
+ HAS_CTRACER = True
+except ImportError:
+ # Couldn't import the C extension, maybe it isn't built.
+ if os.getenv("COVERAGE_CORE") == "ctrace": # pragma: part covered
+ # During testing, we use the COVERAGE_CORE environment variable
+ # to indicate that we've fiddled with the environment to test this
+ # fallback code. If we thought we had a C tracer, but couldn't import
+ # it, then exit quickly and clearly instead of dribbling confusing
+ # errors. I'm using sys.exit here instead of an exception because an
+ # exception here causes all sorts of other noise in unittest.
+ sys.stderr.write("*** COVERAGE_CORE is 'ctrace' but can't import CTracer!\n")
+ sys.exit(1)
+ HAS_CTRACER = False
+
+
+class Core:
+ """Information about the central technology enabling execution measurement."""
+
+ tracer_class: type[Tracer]
+ tracer_kwargs: dict[str, Any]
+ file_disposition_class: type[TFileDisposition]
+ supports_plugins: bool
+ packed_arcs: bool
+ systrace: bool
+
+ def __init__(self, warn: TWarnFn, timid: bool, metacov: bool) -> None:
+ core_name: str | None
+ if timid:
+ core_name = "pytrace"
+ else:
+ core_name = os.getenv("COVERAGE_CORE")
+
+ if core_name == "sysmon" and not env.PYBEHAVIOR.pep669:
+ warn("sys.monitoring isn't available, using default core", slug="no-sysmon")
+ core_name = None
+
+ if not core_name:
+ # Once we're comfortable with sysmon as a default:
+ # if env.PYBEHAVIOR.pep669 and self.should_start_context is None:
+ # core_name = "sysmon"
+ if HAS_CTRACER:
+ core_name = "ctrace"
+ else:
+ core_name = "pytrace"
+
+ if core_name == "sysmon":
+ self.tracer_class = SysMonitor
+ self.tracer_kwargs = {"tool_id": 3 if metacov else 1}
+ self.file_disposition_class = FileDisposition
+ self.supports_plugins = False
+ self.packed_arcs = False
+ self.systrace = False
+ elif core_name == "ctrace":
+ self.tracer_class = CTracer
+ self.tracer_kwargs = {}
+ self.file_disposition_class = CFileDisposition
+ self.supports_plugins = True
+ self.packed_arcs = True
+ self.systrace = True
+ elif core_name == "pytrace":
+ self.tracer_class = PyTracer
+ self.tracer_kwargs = {}
+ self.file_disposition_class = FileDisposition
+ self.supports_plugins = False
+ self.packed_arcs = False
+ self.systrace = True
+ else:
+ raise ConfigError(f"Unknown core value: {core_name!r}")
diff --git a/coverage/inorout.py b/coverage/inorout.py
index 5ea29edf1..4df54b2b4 100644
--- a/coverage/inorout.py
+++ b/coverage/inorout.py
@@ -321,7 +321,8 @@ def nope(disp: TFileDisposition, reason: str) -> TFileDisposition:
# co_filename value.
dunder_file = frame.f_globals and frame.f_globals.get("__file__")
if dunder_file:
- filename = source_for_file(dunder_file)
+ # Danger: __file__ can (rarely?) be of type Path.
+ filename = source_for_file(str(dunder_file))
if original_filename and not original_filename.startswith("<"):
orig = os.path.basename(original_filename)
if orig != os.path.basename(filename):
diff --git a/coverage/parser.py b/coverage/parser.py
index 19267a718..0f7e353a3 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -678,7 +678,7 @@ class NodeList(ast.AST):
"""
def __init__(self, body: Sequence[ast.AST]) -> None:
self.body = body
- self.lineno = body[0].lineno
+ self.lineno = body[0].lineno # type: ignore[attr-defined]
# TODO: Shouldn't the cause messages join with "and" instead of "or"?
@@ -824,7 +824,7 @@ def line_for_node(self, node: ast.AST) -> TLineNo:
if handler is not None:
return handler(node)
else:
- return node.lineno
+ return node.lineno # type: ignore[attr-defined, no-any-return]
# First lines: _line__*
#
@@ -1020,10 +1020,10 @@ def _missing__While(self, node: ast.While) -> ast.AST | None:
if not body_nodes:
return None
# Make a synthetic While-true node.
- new_while = ast.While()
- new_while.lineno = body_nodes.lineno
- new_while.test = ast.Name()
- new_while.test.lineno = body_nodes.lineno
+ new_while = ast.While() # type: ignore[call-arg]
+ new_while.lineno = body_nodes.lineno # type: ignore[attr-defined]
+ new_while.test = ast.Name() # type: ignore[call-arg]
+ new_while.test.lineno = body_nodes.lineno # type: ignore[attr-defined]
new_while.test.id = "True"
assert hasattr(body_nodes, "body")
new_while.body = body_nodes.body
diff --git a/coverage/phystokens.py b/coverage/phystokens.py
index ec8959fb6..9fc36ecda 100644
--- a/coverage/phystokens.py
+++ b/coverage/phystokens.py
@@ -57,7 +57,15 @@ def _phys_tokens(toks: TokenInfos) -> TokenInfos:
if last_ttext.endswith("\\"):
inject_backslash = False
elif ttype == token.STRING:
- if "\n" in ttext and ttext.split("\n", 1)[0][-1] == "\\":
+ if last_line.endswith(last_ttext + "\\\n"):
+ # Deal with special cases like such code::
+ #
+ # a = ["aaa",\
+ # "bbb \
+ # ccc"]
+ #
+ inject_backslash = True
+ elif "\n" in ttext and ttext.split("\n", 1)[0][-1] == "\\":
# It's a multi-line string and the first line ends with
# a backslash, so we don't need to inject another.
inject_backslash = False
diff --git a/coverage/pytracer.py b/coverage/pytracer.py
index 00cacea15..8144e6566 100644
--- a/coverage/pytracer.py
+++ b/coverage/pytracer.py
@@ -16,8 +16,16 @@
from coverage import env
from coverage.types import (
- TArc, TFileDisposition, TLineNo, TTraceData, TTraceFileData, TTraceFn,
- TracerCore, TWarnFn,
+ TArc,
+ TFileDisposition,
+ TLineNo,
+ TShouldStartContextFn,
+ TShouldTraceFn,
+ TTraceData,
+ TTraceFileData,
+ TTraceFn,
+ TWarnFn,
+ Tracer,
)
# We need the YIELD_VALUE opcode below, in a comparison-friendly form.
@@ -36,7 +44,7 @@
THIS_FILE = __file__.rstrip("co")
-class PyTracer(TracerCore):
+class PyTracer(Tracer):
"""Python implementation of the raw data tracer."""
# Because of poor implementations of trace-function-manipulating tools,
@@ -64,9 +72,9 @@ def __init__(self) -> None:
# Attributes set from the collector:
self.data: TTraceData
self.trace_arcs = False
- self.should_trace: Callable[[str, FrameType], TFileDisposition]
+ self.should_trace: TShouldTraceFn
self.should_trace_cache: dict[str, TFileDisposition | None]
- self.should_start_context: Callable[[FrameType], str | None] | None = None
+ self.should_start_context: TShouldStartContextFn | None = None
self.switch_context: Callable[[str | None], None] | None = None
self.lock_data: Callable[[], None]
self.unlock_data: Callable[[], None]
diff --git a/coverage/sysmon.py b/coverage/sysmon.py
index 0c9e795c6..ef54292f3 100644
--- a/coverage/sysmon.py
+++ b/coverage/sysmon.py
@@ -29,9 +29,11 @@
TArc,
TFileDisposition,
TLineNo,
+ TShouldStartContextFn,
+ TShouldTraceFn,
TTraceData,
TTraceFileData,
- TracerCore,
+ Tracer,
TWarnFn,
)
@@ -171,7 +173,7 @@ def bytes_to_lines(code: CodeType) -> dict[int, int]:
return b2l
-class SysMonitor(TracerCore):
+class SysMonitor(Tracer):
"""Python implementation of the raw data tracer for PEP669 implementations."""
# One of these will be used across threads. Be careful.
@@ -180,11 +182,11 @@ def __init__(self, tool_id: int) -> None:
# Attributes set from the collector:
self.data: TTraceData
self.trace_arcs = False
- self.should_trace: Callable[[str, FrameType], TFileDisposition]
+ self.should_trace: TShouldTraceFn
self.should_trace_cache: dict[str, TFileDisposition | None]
# TODO: should_start_context and switch_context are unused!
# Change tests/testenv.py:DYN_CONTEXTS when this is updated.
- self.should_start_context: Callable[[FrameType], str | None] | None = None
+ self.should_start_context: TShouldStartContextFn | None = None
self.switch_context: Callable[[str | None], None] | None = None
self.lock_data: Callable[[], None]
self.unlock_data: Callable[[], None]
diff --git a/coverage/tracer.pyi b/coverage/tracer.pyi
index 58dd69f94..d850493ed 100644
--- a/coverage/tracer.pyi
+++ b/coverage/tracer.pyi
@@ -5,7 +5,7 @@
from typing import Any, Dict
-from coverage.types import TFileDisposition, TTraceData, TTraceFn, TracerCore
+from coverage.types import TFileDisposition, TTraceData, TTraceFn, Tracer
class CFileDisposition(TFileDisposition):
"""CFileDisposition is in ctracer/filedisp.c"""
@@ -18,7 +18,7 @@ class CFileDisposition(TFileDisposition):
trace: Any
def __init__(self) -> None: ...
-class CTracer(TracerCore):
+class CTracer(Tracer):
"""CTracer is in ctracer/tracer.c"""
check_include: Any
concur_id_func: Any
diff --git a/coverage/types.py b/coverage/types.py
index ff5c59cda..bfd2a4d15 100644
--- a/coverage/types.py
+++ b/coverage/types.py
@@ -78,14 +78,19 @@ class TFileDisposition(Protocol):
TTraceData = Dict[str, TTraceFileData]
-class TracerCore(Protocol):
+# Functions passed into collectors.
+TShouldTraceFn = Callable[[str, FrameType], TFileDisposition]
+TCheckIncludeFn = Callable[[str, FrameType], bool]
+TShouldStartContextFn = Callable[[FrameType], Union[str, None]]
+
+class Tracer(Protocol):
"""Anything that can report on Python execution."""
data: TTraceData
trace_arcs: bool
- should_trace: Callable[[str, FrameType], TFileDisposition]
+ should_trace: TShouldTraceFn
should_trace_cache: Mapping[str, TFileDisposition | None]
- should_start_context: Callable[[FrameType], str | None] | None
+ should_start_context: TShouldStartContextFn | None
switch_context: Callable[[str | None], None] | None
lock_data: Callable[[], None]
unlock_data: Callable[[], None]
diff --git a/coverage/version.py b/coverage/version.py
index f1f2993ba..ffefd8e00 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, 6, 0, "final", 0)
+version_info = (7, 6, 1, "final", 0)
_dev = 0
diff --git a/doc/conf.py b/doc/conf.py
index 3886abb7f..030a1a1c1 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -67,11 +67,11 @@
# @@@ editable
copyright = "2009–2024, Ned Batchelder" # pylint: disable=redefined-builtin
# The short X.Y.Z version.
-version = "7.6.0"
+version = "7.6.1"
# The full version, including alpha/beta/rc tags.
-release = "7.6.0"
+release = "7.6.1"
# The date of release, in "monthname day, year" format.
-release_date = "July 11, 2024"
+release_date = "August 4, 2024"
# @@@ end
rst_epilog = f"""
diff --git a/doc/index.rst b/doc/index.rst
index 748b963af..100823ada 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.0b3.
+* Python 3.8 through 3.12, and 3.13.0rc1, including free-threading.
* PyPy3 versions 3.8 through 3.10.
.. ifconfig:: prerelease
diff --git a/doc/other.rst b/doc/other.rst
index d0998b37f..8c8f71003 100644
--- a/doc/other.rst
+++ b/doc/other.rst
@@ -53,9 +53,9 @@ Language plugins
Coverage.py plugins to enable coverage measurement of other languages.
-* `django-coverage`__ measures the coverage of Django templates.
+* `django-coverage-plugin`__ measures the coverage of Django templates.
- __ https://pypi.org/project/django-coverage/
+ __ https://pypi.org/project/django-coverage-plugin/
* `Cython`__ provides a plugin for measuring Cythonized code.
diff --git a/doc/sample_html/class_index.html b/doc/sample_html/class_index.html
index ddcea11fd..244ce8666 100644
--- a/doc/sample_html/class_index.html
+++ b/doc/sample_html/class_index.html
@@ -56,8 +56,8 @@
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
@@ -537,8 +537,8 @@
@@ -97,8 +97,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
diff --git a/doc/sample_html/z_7b071bdc2a35fa80___main___py.html b/doc/sample_html/z_7b071bdc2a35fa80___main___py.html
index 666dc1bdb..0d6ee9ec8 100644
--- a/doc/sample_html/z_7b071bdc2a35fa80___main___py.html
+++ b/doc/sample_html/z_7b071bdc2a35fa80___main___py.html
@@ -66,8 +66,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
@@ -97,8 +97,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
diff --git a/doc/sample_html/z_7b071bdc2a35fa80_cogapp_py.html b/doc/sample_html/z_7b071bdc2a35fa80_cogapp_py.html
index d4c19de42..fe0a6ae66 100644
--- a/doc/sample_html/z_7b071bdc2a35fa80_cogapp_py.html
+++ b/doc/sample_html/z_7b071bdc2a35fa80_cogapp_py.html
@@ -66,8 +66,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
@@ -928,8 +928,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
diff --git a/doc/sample_html/z_7b071bdc2a35fa80_makefiles_py.html b/doc/sample_html/z_7b071bdc2a35fa80_makefiles_py.html
index 8d7d719c1..01df01b73 100644
--- a/doc/sample_html/z_7b071bdc2a35fa80_makefiles_py.html
+++ b/doc/sample_html/z_7b071bdc2a35fa80_makefiles_py.html
@@ -66,8 +66,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
@@ -127,8 +127,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
diff --git a/doc/sample_html/z_7b071bdc2a35fa80_test_cogapp_py.html b/doc/sample_html/z_7b071bdc2a35fa80_test_cogapp_py.html
index cca5f267c..8a8fe6485 100644
--- a/doc/sample_html/z_7b071bdc2a35fa80_test_cogapp_py.html
+++ b/doc/sample_html/z_7b071bdc2a35fa80_test_cogapp_py.html
@@ -66,8 +66,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
@@ -2737,8 +2737,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
diff --git a/doc/sample_html/z_7b071bdc2a35fa80_test_makefiles_py.html b/doc/sample_html/z_7b071bdc2a35fa80_test_makefiles_py.html
index 212d79adc..0d13c5f97 100644
--- a/doc/sample_html/z_7b071bdc2a35fa80_test_makefiles_py.html
+++ b/doc/sample_html/z_7b071bdc2a35fa80_test_makefiles_py.html
@@ -66,8 +66,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
@@ -205,8 +205,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
diff --git a/doc/sample_html/z_7b071bdc2a35fa80_test_whiteutils_py.html b/doc/sample_html/z_7b071bdc2a35fa80_test_whiteutils_py.html
index beb14854f..cb14b7b45 100644
--- a/doc/sample_html/z_7b071bdc2a35fa80_test_whiteutils_py.html
+++ b/doc/sample_html/z_7b071bdc2a35fa80_test_whiteutils_py.html
@@ -66,8 +66,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
@@ -186,8 +186,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
diff --git a/doc/sample_html/z_7b071bdc2a35fa80_utils_py.html b/doc/sample_html/z_7b071bdc2a35fa80_utils_py.html
index 3b4350f43..ee5aef610 100644
--- a/doc/sample_html/z_7b071bdc2a35fa80_utils_py.html
+++ b/doc/sample_html/z_7b071bdc2a35fa80_utils_py.html
@@ -66,8 +66,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
@@ -159,8 +159,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
diff --git a/doc/sample_html/z_7b071bdc2a35fa80_whiteutils_py.html b/doc/sample_html/z_7b071bdc2a35fa80_whiteutils_py.html
index c1d1fbe37..abd237ccf 100644
--- a/doc/sample_html/z_7b071bdc2a35fa80_whiteutils_py.html
+++ b/doc/sample_html/z_7b071bdc2a35fa80_whiteutils_py.html
@@ -66,8 +66,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
@@ -159,8 +159,8 @@
^ index
» next
- coverage.py v7.6.0,
- created at 2024-07-11 12:38 -0400
+ coverage.py v7.6.1,
+ created at 2024-08-04 15:03 -0400
diff --git a/doc/sleepy.rst b/doc/sleepy.rst
index ceee6f392..2bb6b8d6b 100644
--- a/doc/sleepy.rst
+++ b/doc/sleepy.rst
@@ -7,15 +7,10 @@
Sleepy Snake
============
-Coverage.py's mascot is Sleepy Snake, drawn by Ben Batchelder. Ben's art can
-be found on `Instagram`_ and at `artofbatch.com`_. Some details of Sleepy's
+Coverage.py's mascot is Sleepy Snake. Some details of Sleepy's
creation are on `Ned's blog`__.
__ https://nedbatchelder.com/blog/201912/sleepy_snake.html
.. image:: media/sleepy-snake-600.png
:alt: Sleepy Snake, cozy in his snake-shaped bed.
-
-
-.. _Instagram: https://instagram.com/artofbatch
-.. _artofbatch.com: https://artofbatch.com
diff --git a/howto.txt b/howto.txt
index 6cba5ba98..785a6157c 100644
--- a/howto.txt
+++ b/howto.txt
@@ -52,7 +52,6 @@
- Kits:
- Wait for kits to finish:
- https://github.com/nedbat/coveragepy/actions/workflows/kit.yml
- - there should be 52
- test the pypi upload:
$ make test_upload
- you'll need to approve the action
diff --git a/igor.py b/igor.py
index 5037cb503..dc21ae344 100644
--- a/igor.py
+++ b/igor.py
@@ -321,6 +321,9 @@ def print_banner(label):
if rev:
version += f" (rev {rev})"
+ gil = "gil" if getattr(sys, '_is_gil_enabled', lambda: True)() else "nogil"
+ version += f" ({gil})"
+
try:
which_python = os.path.relpath(sys.executable)
except ValueError:
diff --git a/requirements/dev.pip b/requirements/dev.pip
index 2f4634e77..f276cfd8d 100644
--- a/requirements/dev.pip
+++ b/requirements/dev.pip
@@ -4,17 +4,17 @@
#
# make upgrade
#
-astroid==3.2.2
+astroid==3.2.4
# via pylint
-attrs==23.2.0
+attrs==24.1.0
# via hypothesis
backports-tarfile==1.2.0
# via jaraco-context
build==1.2.1
# via check-manifest
-cachetools==5.3.3
+cachetools==5.4.0
# via tox
-certifi==2024.6.2
+certifi==2024.7.4
# via requests
chardet==5.2.0
# via tox
@@ -35,13 +35,13 @@ distlib==0.3.8
# via virtualenv
docutils==0.20.1
# via readme-renderer
-exceptiongroup==1.2.1
+exceptiongroup==1.2.2
# via
# hypothesis
# pytest
execnet==2.1.1
# via pytest-xdist
-filelock==3.15.3
+filelock==3.15.4
# via
# tox
# virtualenv
@@ -49,11 +49,11 @@ flaky==3.8.1
# via -r requirements/pytest.in
greenlet==3.0.3
# via -r requirements/dev.in
-hypothesis==6.103.2
+hypothesis==6.108.7
# via -r requirements/pytest.in
idna==3.7
# via requests
-importlib-metadata==7.2.0
+importlib-metadata==8.2.0
# via
# build
# keyring
@@ -68,11 +68,11 @@ jaraco-classes==3.4.0
# via keyring
jaraco-context==5.3.0
# via keyring
-jaraco-functools==4.0.1
+jaraco-functools==4.0.2
# via keyring
jedi==0.19.1
# via pudb
-keyring==25.2.1
+keyring==25.3.0
# via twine
libsass==0.23.0
# via -r requirements/dev.in
@@ -86,7 +86,7 @@ more-itertools==10.3.0
# via
# jaraco-classes
# jaraco-functools
-nh3==0.2.17
+nh3==0.2.18
# via readme-renderer
packaging==24.1
# via
@@ -97,7 +97,7 @@ packaging==24.1
# tox
parso==0.8.4
# via jedi
-pkginfo==1.11.1
+pkginfo==1.10.0
# via twine
platformdirs==4.2.2
# via
@@ -108,7 +108,7 @@ pluggy==1.5.0
# via
# pytest
# tox
-pudb==2024.1
+pudb==2024.1.2
# via -r requirements/dev.in
pygments==2.18.0
# via
@@ -116,13 +116,13 @@ pygments==2.18.0
# pudb
# readme-renderer
# rich
-pylint==3.2.3
+pylint==3.2.6
# via -r requirements/dev.in
pyproject-api==1.7.1
# via tox
pyproject-hooks==1.1.0
# via build
-pytest==8.2.2
+pytest==8.3.2
# via
# -r requirements/pytest.in
# pytest-xdist
@@ -155,15 +155,15 @@ tomli==2.0.1
# pyproject-api
# pytest
# tox
-tomlkit==0.12.5
+tomlkit==0.13.0
# via pylint
-tox==4.15.1
+tox==4.16.0
# via
# -r requirements/tox.in
# tox-gh
-tox-gh==1.3.1
+tox-gh==1.3.2
# via -r requirements/tox.in
-twine==5.1.0
+twine==5.1.1
# via -r requirements/dev.in
typing-extensions==4.12.2
# via
@@ -175,7 +175,7 @@ urllib3==2.2.2
# via
# requests
# twine
-urwid==2.6.14
+urwid==2.6.15
# via
# pudb
# urwid-readline
@@ -193,9 +193,9 @@ zipp==3.19.2
# importlib-resources
# The following packages are considered to be unsafe in a requirements file:
-pip==24.1
+pip==24.2
# via -r requirements/pip.in
-setuptools==70.1.0
+setuptools==72.1.0
# via
# -r requirements/pip.in
# check-manifest
diff --git a/requirements/kit.pip b/requirements/kit.pip
index 076da7a57..aa1e9a21a 100644
--- a/requirements/kit.pip
+++ b/requirements/kit.pip
@@ -10,27 +10,27 @@ backports-tarfile==1.2.0
# via jaraco-context
bashlex==0.18
# via cibuildwheel
-bracex==2.4
+bracex==2.5
# via cibuildwheel
build==1.2.1
# via -r requirements/kit.in
-certifi==2024.6.2
+certifi==2024.7.4
# via
# cibuildwheel
# requests
charset-normalizer==3.3.2
# via requests
-cibuildwheel==2.19.1
+cibuildwheel==2.20.0
# via -r requirements/kit.in
colorama==0.4.6
# via -r requirements/kit.in
docutils==0.20.1
# via readme-renderer
-filelock==3.15.3
+filelock==3.15.4
# via cibuildwheel
idna==3.7
# via requests
-importlib-metadata==7.2.0
+importlib-metadata==8.2.0
# via
# build
# keyring
@@ -41,9 +41,9 @@ jaraco-classes==3.4.0
# via keyring
jaraco-context==5.3.0
# via keyring
-jaraco-functools==4.0.1
+jaraco-functools==4.0.2
# via keyring
-keyring==25.2.1
+keyring==25.3.0
# via twine
markdown-it-py==3.0.0
# via rich
@@ -53,14 +53,14 @@ more-itertools==10.3.0
# via
# jaraco-classes
# jaraco-functools
-nh3==0.2.17
+nh3==0.2.18
# via readme-renderer
packaging==24.1
# via
# auditwheel
# build
# cibuildwheel
-pkginfo==1.11.1
+pkginfo==1.10.0
# via twine
platformdirs==4.2.2
# via cibuildwheel
@@ -88,7 +88,7 @@ tomli==2.0.1
# via
# build
# cibuildwheel
-twine==5.1.0
+twine==5.1.1
# via -r requirements/kit.in
typing-extensions==4.12.2
# via
@@ -106,5 +106,5 @@ zipp==3.19.2
# importlib-resources
# The following packages are considered to be unsafe in a requirements file:
-setuptools==70.1.0
+setuptools==72.1.0
# via -r requirements/kit.in
diff --git a/requirements/light-threads.pip b/requirements/light-threads.pip
index e371af3c6..68dda2f89 100644
--- a/requirements/light-threads.pip
+++ b/requirements/light-threads.pip
@@ -25,7 +25,7 @@ zope-interface==6.4.post2
# via gevent
# The following packages are considered to be unsafe in a requirements file:
-setuptools==70.1.0
+setuptools==72.1.0
# via
# zope-event
# zope-interface
diff --git a/requirements/mypy.pip b/requirements/mypy.pip
index 4d6477309..42b7f5a66 100644
--- a/requirements/mypy.pip
+++ b/requirements/mypy.pip
@@ -4,11 +4,11 @@
#
# make upgrade
#
-attrs==23.2.0
+attrs==24.1.0
# via hypothesis
colorama==0.4.6
# via -r requirements/pytest.in
-exceptiongroup==1.2.1
+exceptiongroup==1.2.2
# via
# hypothesis
# pytest
@@ -16,11 +16,11 @@ execnet==2.1.1
# via pytest-xdist
flaky==3.8.1
# via -r requirements/pytest.in
-hypothesis==6.103.2
+hypothesis==6.108.7
# via -r requirements/pytest.in
iniconfig==2.0.0
# via pytest
-mypy==1.10.0
+mypy==1.11.1
# via -r requirements/mypy.in
mypy-extensions==1.0.0
# via mypy
@@ -30,7 +30,7 @@ pluggy==1.5.0
# via pytest
pygments==2.18.0
# via -r requirements/pytest.in
-pytest==8.2.2
+pytest==8.3.2
# via
# -r requirements/pytest.in
# pytest-xdist
@@ -42,7 +42,7 @@ tomli==2.0.1
# via
# mypy
# pytest
-types-requests==2.32.0.20240622
+types-requests==2.32.0.20240712
# via -r requirements/mypy.in
types-tabulate==0.9.0.20240106
# via -r requirements/mypy.in
diff --git a/requirements/pip-tools.pip b/requirements/pip-tools.pip
index ba853807c..60fdce03f 100644
--- a/requirements/pip-tools.pip
+++ b/requirements/pip-tools.pip
@@ -8,7 +8,7 @@ build==1.2.1
# via pip-tools
click==8.1.7
# via pip-tools
-importlib-metadata==7.2.0
+importlib-metadata==8.2.0
# via build
packaging==24.1
# via build
@@ -28,7 +28,7 @@ zipp==3.19.2
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
-pip==24.1
+pip==24.2
# via pip-tools
-setuptools==70.1.0
+setuptools==72.1.0
# via pip-tools
diff --git a/requirements/pip.pip b/requirements/pip.pip
index fe6819374..04ccf1cce 100644
--- a/requirements/pip.pip
+++ b/requirements/pip.pip
@@ -6,7 +6,7 @@
#
distlib==0.3.8
# via virtualenv
-filelock==3.15.3
+filelock==3.15.4
# via virtualenv
platformdirs==4.2.2
# via virtualenv
@@ -14,7 +14,7 @@ virtualenv==20.26.3
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
-pip==24.1
+pip==24.2
# via -r requirements/pip.in
-setuptools==70.1.0
+setuptools==72.1.0
# via -r requirements/pip.in
diff --git a/requirements/pytest.pip b/requirements/pytest.pip
index b2da89ddc..af95dfb21 100644
--- a/requirements/pytest.pip
+++ b/requirements/pytest.pip
@@ -4,11 +4,11 @@
#
# make upgrade
#
-attrs==23.2.0
+attrs==24.1.0
# via hypothesis
colorama==0.4.6
# via -r requirements/pytest.in
-exceptiongroup==1.2.1
+exceptiongroup==1.2.2
# via
# hypothesis
# pytest
@@ -16,7 +16,7 @@ execnet==2.1.1
# via pytest-xdist
flaky==3.8.1
# via -r requirements/pytest.in
-hypothesis==6.103.2
+hypothesis==6.108.7
# via -r requirements/pytest.in
iniconfig==2.0.0
# via pytest
@@ -26,7 +26,7 @@ pluggy==1.5.0
# via pytest
pygments==2.18.0
# via -r requirements/pytest.in
-pytest==8.2.2
+pytest==8.3.2
# via
# -r requirements/pytest.in
# pytest-xdist
diff --git a/requirements/tox.pip b/requirements/tox.pip
index 80366621a..7bec3da1c 100644
--- a/requirements/tox.pip
+++ b/requirements/tox.pip
@@ -4,7 +4,7 @@
#
# make upgrade
#
-cachetools==5.3.3
+cachetools==5.4.0
# via tox
chardet==5.2.0
# via tox
@@ -14,7 +14,7 @@ colorama==0.4.6
# tox
distlib==0.3.8
# via virtualenv
-filelock==3.15.3
+filelock==3.15.4
# via
# tox
# virtualenv
@@ -34,11 +34,11 @@ tomli==2.0.1
# via
# pyproject-api
# tox
-tox==4.15.1
+tox==4.16.0
# via
# -r requirements/tox.in
# tox-gh
-tox-gh==1.3.1
+tox-gh==1.3.2
# via -r requirements/tox.in
virtualenv==20.26.3
# via tox
diff --git a/setup.py b/setup.py
index cc5f7ed1a..4ce5d34f0 100644
--- a/setup.py
+++ b/setup.py
@@ -3,17 +3,14 @@
"""Code coverage measurement for Python"""
-# Distutils setup for coverage.py
+# Setuptools setup for coverage.py
# This file is used unchanged under all versions of Python.
import os
import sys
-# Setuptools has to be imported before distutils, or things break.
-from setuptools import setup
-from distutils.core import Extension # pylint: disable=wrong-import-order
+from setuptools import Extension, errors, setup
from setuptools.command.build_ext import build_ext # pylint: disable=wrong-import-order
-from distutils import errors # pylint: disable=wrong-import-order
# Get or massage our metadata. We exec coverage/version.py so we can avoid
# importing the product code into setup.py.
@@ -131,12 +128,11 @@
ext_errors = (
errors.CCompilerError,
- errors.DistutilsExecError,
- errors.DistutilsPlatformError,
+ errors.ExecError,
+ errors.PlatformError,
)
if sys.platform == "win32":
- # distutils.msvc9compiler can raise an IOError when failing to
- # find the compiler
+ # IOError can be raised when failing to find the compiler
ext_errors += (IOError,)
@@ -155,7 +151,7 @@ def run(self):
"""Wrap `run` with `BuildFailed`."""
try:
build_ext.run(self)
- except errors.DistutilsPlatformError as exc:
+ except errors.PlatformError as exc:
raise BuildFailed() from exc
def build_extension(self, ext):
diff --git a/tests/test_debug.py b/tests/test_debug.py
index c818f9308..bdf98a5f7 100644
--- a/tests/test_debug.py
+++ b/tests/test_debug.py
@@ -376,7 +376,7 @@ def test_short_stack_full(self) -> None:
else:
py = "pypy" if env.PYPY else "python"
majv, minv = sys.version_info[:2]
- pylib = f"lib{s}{py}{majv}.{minv}"
+ pylib = f"lib{s}{py}{majv}.{minv}{sys.abiflags}"
assert len(re_lines(fr"{s}{pylib}{s}site-packages{s}_pytest", stack_text)) > 3
assert len(re_lines(fr"{s}{pylib}{s}site-packages{s}pluggy", stack_text)) > 3
assert not re_lines(r" 0x[0-9a-fA-F]+", stack_text) # No frame ids
diff --git a/tests/test_html.py b/tests/test_html.py
index f2d9bcbb3..c7cee1f98 100644
--- a/tests/test_html.py
+++ b/tests/test_html.py
@@ -1131,6 +1131,28 @@ def test_tabbed(self) -> None:
doesnt_contain("out/tabbed_py.html", "\t")
+ def test_bug_1828(self) -> None:
+ # https://github.com/nedbat/coveragepy/pull/1828
+ self.make_file("backslashes.py", """\
+ a = ["aaa",\\
+ "bbb \\
+ ccc"]
+ """)
+
+ cov = coverage.Coverage()
+ backslashes = self.start_import_stop(cov, "backslashes")
+ cov.html_report(backslashes, directory="out")
+
+ contains(
+ "out/backslashes_py.html",
+ # line 2 is `"bbb \`
+ r'2'
+ + r' "bbb \',
+ # line 3 is `ccc"]`
+ r'3'
+ + r' ccc"]',
+ )
+
def test_unicode(self) -> None:
surrogate = "\U000e0100"
diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py
index 063e517d6..0a863ab6e 100644
--- a/tests/test_phystokens.py
+++ b/tests/test_phystokens.py
@@ -98,6 +98,24 @@ def test_tokenize_real_file(self) -> None:
real_file = os.path.join(TESTS_DIR, "test_coverage.py")
self.check_file_tokenization(real_file)
+ def test_1828(self) -> None:
+ # https://github.com/nedbat/coveragepy/pull/1828
+ tokens = list(source_token_lines(textwrap.dedent("""
+ x = \
+ 1
+ a = ["aaa",\\
+ "bbb \\
+ ccc"]
+ """)))
+ assert tokens == [
+ [],
+ [('nam', 'x'), ('ws', ' '), ('op', '='), ('ws', ' '), ('num', '1')],
+ [('nam', 'a'), ('ws', ' '), ('op', '='), ('ws', ' '),
+ ('op', '['), ('str', '"aaa"'), ('op', ','), ('xx', '\\')],
+ [('ws', ' '), ('str', '"bbb \\')],
+ [('str', ' ccc"'), ('op', ']')],
+ ]
+
@pytest.mark.parametrize("fname", [
"stress_phystoken.tok",
"stress_phystoken_dos.tok",
@@ -113,6 +131,7 @@ def test_stress(self, fname: str) -> None:
with open(stress) as fstress:
assert re.search(r"(?m) $", fstress.read()), f"{stress} needs a trailing space."
+
@pytest.mark.skipif(not env.PYBEHAVIOR.soft_keywords, reason="Soft keywords are new in Python 3.10")
class SoftKeywordTest(CoverageTest):
"""Tests the tokenizer handling soft keywords."""
diff --git a/tests/test_python.py b/tests/test_python.py
index ee0268ffc..6a8362919 100644
--- a/tests/test_python.py
+++ b/tests/test_python.py
@@ -63,3 +63,24 @@ def test_source_for_file_windows(tmp_path: pathlib.Path) -> None:
# If both pyw and py exist, py is preferred
a_py.write_text("")
assert source_for_file(src + 'c') == src
+
+
+class RunpyTest(CoverageTest):
+ """Tests using runpy."""
+
+ @pytest.mark.parametrize("convert_to", ["str", "Path"])
+ def test_runpy_path(self, convert_to: str) -> None:
+ # Ensure runpy.run_path(path) works when path is pathlib.Path or str.
+ #
+ # runpy.run_path(pathlib.Path(...)) causes __file__ to be a Path,
+ # which may make source_for_file() stumble (#1819) with:
+ #
+ # AttributeError: 'PosixPath' object has no attribute 'endswith'
+
+ self.check_coverage(f"""\
+ import runpy
+ from pathlib import Path
+ pyfile = Path('script.py')
+ pyfile.write_text('')
+ runpy.run_path({convert_to}(pyfile))
+ """)