diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 5e67fe3cd..c02eed986 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -39,7 +39,7 @@ jobs:
- "3.8"
- "3.9"
- "3.10"
- - "3.11.0-beta.1"
+ - "3.11.0-beta.3"
- "pypy-3.7"
exclude:
# Windows PyPy doesn't seem to work?
diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml
index 16d518a5e..cfb32db31 100644
--- a/.github/workflows/testsuite.yml
+++ b/.github/workflows/testsuite.yml
@@ -42,7 +42,7 @@ jobs:
- "3.8"
- "3.9"
- "3.10"
- - "3.11.0-beta.1"
+ - "3.11.0-beta.3"
- "pypy-3.7"
exclude:
# Windows PyPy doesn't seem to work?
diff --git a/CHANGES.rst b/CHANGES.rst
index 86d5b11eb..44afbd2a9 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -12,11 +12,31 @@ development at the same time, such as 4.5.x and 5.0.
.. When updating the "Unreleased" header to a specific version, use this
.. format. Don't forget the jump target:
..
- .. .. _changes_981:
+ .. .. _changes_9-8-1:
..
.. Version 9.8.1 — 2027-07-27
.. --------------------------
+.. _changes_6-4-1:
+
+Version 6.4.1 — 2022-06-02
+--------------------------
+
+- Greatly improved performance on PyPy, and other environments that need the
+ pure Python trace function. Thanks, Carl Friedrich Bolz-Tereick (`pull
+ 1381`_ and `pull 1388`_). Slightly improved performance when using the C
+ trace function, as most environments do. Closes `issue 1339`_.
+
+- The conditions for using tomllib from the standard library have been made
+ more precise, so that 3.11 alphas will continue to work. Closes `issue
+ 1390`_.
+
+.. _issue 1339: https://github.com/nedbat/coveragepy/issues/1339
+.. _pull 1381: https://github.com/nedbat/coveragepy/pull/1381
+.. _pull 1388: https://github.com/nedbat/coveragepy/pull/1388
+.. _issue 1390: https://github.com/nedbat/coveragepy/issues/1390
+
+
.. _changes_64:
Version 6.4 — 2022-05-22
@@ -79,8 +99,8 @@ Version 6.3.2 — 2022-02-20
decorators like CPython 3.8: both the @-line and the def-line are traced.
Fixes `issue 1326`_.
-- Debug: added ``pybehave`` to the list of :ref:`cmd_debug` and
- :ref:`cmd_run_debug` options.
+- Debug: added ``pybehave`` to the list of :ref:`coverage debug `
+ and :ref:`cmd_run_debug` options.
- Fix: show an intelligible error message if ``--concurrency=multiprocessing``
is used without a configuration file. Closes `issue 1320`_.
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index d3dc538f2..ef1505742 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -31,6 +31,7 @@ Brett Cannon
Bruno P. Kinoshita
Buck Evan
Calen Pennington
+Carl Friedrich Bolz-Tereick
Carl Gieringer
Catherine Proulx
Chris Adams
diff --git a/Makefile b/Makefile
index e2f5e3047..79a1348ae 100644
--- a/Makefile
+++ b/Makefile
@@ -118,7 +118,7 @@ kit_upload: ## Upload the built distributions to PyPI.
twine upload --verbose dist/*
test_upload: ## Upload the distributions to PyPI's testing server.
- twine upload --verbose --repository testpypi dist/*
+ twine upload --verbose --repository testpypi --password $$TWINE_TEST_PASSWORD dist/*
kit_local:
# pip.conf looks like this:
@@ -144,7 +144,7 @@ update_stable: ## Set the stable branch to the latest release.
git push origin stable
comment_on_fixes: ## Add a comment to issues that were fixed.
- python ci/commend_on_fixes.py
+ python ci/comment_on_fixes.py
##@ Documentation
diff --git a/README.rst b/README.rst
index d5195db9b..052bca7d2 100644
--- a/README.rst
+++ b/README.rst
@@ -27,7 +27,7 @@ Coverage.py runs on these versions of Python:
.. PYVERSIONS
-* CPython 3.7 through 3.11.0b1.
+* CPython 3.7 through 3.11.0b3.
* PyPy3 7.3.8.
Documentation is on `Read the Docs`_. Code repository and issue tracker are on
diff --git a/ci/parse_relnotes.py b/ci/parse_relnotes.py
index d19e6d60c..ba5089ebf 100644
--- a/ci/parse_relnotes.py
+++ b/ci/parse_relnotes.py
@@ -84,6 +84,14 @@ def refind(regex, text):
else:
return None
+
+def fix_ref_links(text, version):
+ """Find links to .rst files, and make them full RTFD links."""
+ def new_link(m):
+ return f"](https://coverage.readthedocs.io/en/{version}/{m[1]}.html{m[2]})"
+ return re.sub(r"\]\((\w+)\.rst(#.*?)\)", new_link, text)
+
+
def relnotes(mdlines):
r"""Yield (version, text) pairs from markdown lines.
@@ -97,6 +105,7 @@ def relnotes(mdlines):
if version:
prerelease = any(c in version for c in "abc")
when = refind(r"\d+-\d+-\d+", htext)
+ text = fix_ref_links(text, version)
yield {
"version": version,
"text": text,
diff --git a/coverage/ctracer/tracer.c b/coverage/ctracer/tracer.c
index 442ea514f..570e8d8d1 100644
--- a/coverage/ctracer/tracer.c
+++ b/coverage/ctracer/tracer.c
@@ -514,6 +514,7 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame)
Py_XDECREF(self->pcur_entry->file_data);
self->pcur_entry->file_data = NULL;
self->pcur_entry->file_tracer = Py_None;
+ frame->f_trace_lines = 0;
SHOWLOG(PyFrame_GetLineNumber(frame), filename, "skipped");
}
diff --git a/coverage/pytracer.py b/coverage/pytracer.py
index f0319491d..4f138074b 100644
--- a/coverage/pytracer.py
+++ b/coverage/pytracer.py
@@ -67,6 +67,10 @@ def __init__(self):
# On exit, self.in_atexit = True
atexit.register(setattr, self, 'in_atexit', True)
+ # Cache a bound method on the instance, so that we don't have to
+ # re-create a bound method object all the time.
+ self._cached_bound_method_trace = self._trace
+
def __repr__(self):
return "".format(
id(self),
@@ -105,7 +109,7 @@ def _trace(self, frame, event, arg_unused):
#self.log(":", frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + "()", event)
- if (self.stopped and sys.gettrace() == self._trace): # pylint: disable=comparison-with-callable
+ if (self.stopped and sys.gettrace() == self._cached_bound_method_trace): # pylint: disable=comparison-with-callable
# The PyTrace.stop() method has been called, possibly by another
# thread, let's deactivate ourselves now.
if 0:
@@ -129,12 +133,13 @@ def _trace(self, frame, event, arg_unused):
context_maybe = self.should_start_context(frame)
if context_maybe is not None:
self.context = context_maybe
- self.started_context = True
+ started_context = True
self.switch_context(self.context)
else:
- self.started_context = False
+ started_context = False
else:
- self.started_context = False
+ started_context = False
+ self.started_context = started_context
# Entering a new frame. Decide if we should trace in this file.
self._activity = True
@@ -143,22 +148,35 @@ def _trace(self, frame, event, arg_unused):
self.cur_file_data,
self.cur_file_name,
self.last_line,
- self.started_context,
+ started_context,
)
)
+
+ # Improve tracing performance: when calling a function, both caller
+ # and callee are often within the same file. if that's the case, we
+ # don't have to re-check whether to trace the corresponding
+ # function (which is a little bit espensive since it involves
+ # dictionary lookups). This optimization is only correct if we
+ # didn't start a context.
filename = frame.f_code.co_filename
- self.cur_file_name = filename
- disp = self.should_trace_cache.get(filename)
- if disp is None:
- disp = self.should_trace(filename, frame)
- self.should_trace_cache[filename] = disp
-
- self.cur_file_data = None
- if disp.trace:
- tracename = disp.source_filename
- if tracename not in self.data:
- self.data[tracename] = set()
- self.cur_file_data = self.data[tracename]
+ if filename != self.cur_file_name or started_context:
+ self.cur_file_name = filename
+ disp = self.should_trace_cache.get(filename)
+ if disp is None:
+ disp = self.should_trace(filename, frame)
+ self.should_trace_cache[filename] = disp
+
+ self.cur_file_data = None
+ if disp.trace:
+ tracename = disp.source_filename
+ if tracename not in self.data:
+ self.data[tracename] = set()
+ self.cur_file_data = self.data[tracename]
+ else:
+ frame.f_trace_lines = False
+ elif not self.cur_file_data:
+ frame.f_trace_lines = False
+
# The call event is really a "start frame" event, and happens for
# function calls and re-entering generators. The f_lasti field is
# -1 for calls, and a real offset for generators. Use <0 as the
@@ -174,6 +192,7 @@ def _trace(self, frame, event, arg_unused):
self.last_line = -frame.f_code.co_firstlineno
else:
self.last_line = frame.f_lineno
+
elif event == 'line':
# Record an executed line.
if self.cur_file_data is not None:
@@ -184,6 +203,7 @@ def _trace(self, frame, event, arg_unused):
else:
self.cur_file_data.add(lineno)
self.last_line = lineno
+
elif event == 'return':
if self.trace_arcs and self.cur_file_data:
# Record an arc leaving the function, but beware that a
@@ -211,6 +231,7 @@ def _trace(self, frame, event, arg_unused):
if real_return:
first = frame.f_code.co_firstlineno
self.cur_file_data.add((self.last_line, -first))
+
# Leaving this function, pop the filename stack.
self.cur_file_data, self.cur_file_name, self.last_line, self.started_context = (
self.data_stack.pop()
@@ -219,7 +240,7 @@ def _trace(self, frame, event, arg_unused):
if self.started_context:
self.context = None
self.switch_context(None)
- return self._trace
+ return self._cached_bound_method_trace
def start(self):
"""Start this Tracer.
@@ -237,10 +258,10 @@ def start(self):
# function, but we are marked as running again, so maybe it
# will be ok?
#self.log("~", "starting on different threads")
- return self._trace
+ return self._cached_bound_method_trace
- sys.settrace(self._trace)
- return self._trace
+ sys.settrace(self._cached_bound_method_trace)
+ return self._cached_bound_method_trace
def stop(self):
"""Stop this Tracer."""
@@ -265,9 +286,10 @@ def stop(self):
# so don't warn if we are in atexit on PyPy and the trace function
# has changed to None.
dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None)
- if (not dont_warn) and tf != self._trace: # pylint: disable=comparison-with-callable
+ if (not dont_warn) and tf != self._cached_bound_method_trace: # pylint: disable=comparison-with-callable
self.warn(
- f"Trace function changed, data is likely wrong: {tf!r} != {self._trace!r}",
+ "Trace function changed, data is likely wrong: " +
+ f"{tf!r} != {self._cached_bound_method_trace!r}",
slug="trace-changed",
)
diff --git a/coverage/tomlconfig.py b/coverage/tomlconfig.py
index b04d66e24..148c34f89 100644
--- a/coverage/tomlconfig.py
+++ b/coverage/tomlconfig.py
@@ -12,7 +12,7 @@
from coverage.misc import import_third_party, substitute_variables
-if env.PYVERSION >= (3, 11):
+if env.PYVERSION >= (3, 11, 0, "alpha", 7):
import tomllib # pylint: disable=import-error
else:
# TOML support on Python 3.10 and below is an install-time extra option.
diff --git a/coverage/version.py b/coverage/version.py
index b3714288a..a38726b86 100644
--- a/coverage/version.py
+++ b/coverage/version.py
@@ -5,15 +5,13 @@
# This file is exec'ed in setup.py, don't import anything!
# Same semantics as sys.version_info.
-version_info = (6, 4, 0, "final", 0)
+version_info = (6, 4, 1, "final", 0)
def _make_version(major, minor, micro, releaselevel, serial):
"""Create a readable version string from version_info tuple components."""
assert releaselevel in ['alpha', 'beta', 'candidate', 'final']
- version = "%d.%d" % (major, minor)
- if micro:
- version += ".%d" % (micro,)
+ version = "%d.%d.%d" % (major, minor, micro)
if releaselevel != 'final':
short = {'alpha': 'a', 'beta': 'b', 'candidate': 'rc'}[releaselevel]
version += f"{short}{serial}"
diff --git a/doc/conf.py b/doc/conf.py
index 07e6f3024..87aa6f346 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -63,12 +63,12 @@
# |version| and |release|, also used in various other places throughout the
# built documents.
#
-# The short X.Y version.
-version = "6.4" # CHANGEME
-# The full version, including alpha/beta/rc tags.
-release = "6.4" # CHANGEME
-# The date of release, in "monthname day, year" format.
-release_date = "May 22, 2022" # CHANGEME
+# The short X.Y.Z version. # CHANGEME
+version = "6.4.1"
+# The full version, including alpha/beta/rc tags. # CHANGEME
+release = "6.4.1"
+# The date of release, in "monthname day, year" format. # CHANGEME
+release_date = "June 2, 2022"
rst_epilog = """
.. |release_date| replace:: {release_date}
diff --git a/doc/index.rst b/doc/index.rst
index d96089543..ad4f6f513 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -18,7 +18,7 @@ supported on:
.. PYVERSIONS
-* Python versions 3.7 through 3.11.0b1.
+* Python versions 3.7 through 3.11.0b3.
* PyPy3 7.3.8.
diff --git a/doc/requirements.pip b/doc/requirements.pip
index 25913ed9d..46678fca8 100644
--- a/doc/requirements.pip
+++ b/doc/requirements.pip
@@ -8,7 +8,7 @@ alabaster==0.7.12
# via sphinx
babel==2.10.1
# via sphinx
-certifi==2021.10.8
+certifi==2022.5.18.1
# via requests
charset-normalizer==2.0.12
# via requests
@@ -16,7 +16,7 @@ cogapp==3.3.0
# via -r doc/requirements.in
colorama==0.4.4
# via sphinx-autobuild
-doc8==0.11.1
+doc8==0.11.2
# via -r doc/requirements.in
docutils==0.17.1
# via
@@ -30,7 +30,7 @@ idna==3.3
# via requests
imagesize==1.3.0
# via sphinx
-importlib-metadata==4.11.3
+importlib-metadata==4.11.4
# via
# sphinx
# sphinxcontrib-spelling
@@ -94,7 +94,7 @@ sphinxcontrib-restbuilder==0.3
# via -r doc/requirements.in
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
-sphinxcontrib-spelling==7.3.3
+sphinxcontrib-spelling==7.5.0
# via -r doc/requirements.in
stevedore==3.5.0
# via doc8
diff --git a/doc/sample_html/d_7b071bdc2a35fa80___init___py.html b/doc/sample_html/d_7b071bdc2a35fa80___init___py.html
index be249c4cc..b1aa1a863 100644
--- a/doc/sample_html/d_7b071bdc2a35fa80___init___py.html
+++ b/doc/sample_html/d_7b071bdc2a35fa80___init___py.html
@@ -66,8 +66,8 @@