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 @@

^ index     » next       - coverage.py v6.4, - created at 2022-05-22 18:22 -0400 + coverage.py v6.4.1, + created at 2022-06-02 06:32 -0400