Skip to content

Commit

Permalink
feat!: remove fullcoverage, it doesn't work in 3.13
Browse files Browse the repository at this point in the history
CPython stopped using it in python/cpython#88054
  • Loading branch information
nedbat committed Oct 1, 2023
1 parent 1040bce commit 7b8dec9
Show file tree
Hide file tree
Showing 8 changed files with 11 additions and 125 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Expand Up @@ -34,9 +34,14 @@ Unreleased

- The new 3.12 soft keyword ``type`` is properly bolded in HTML reports.

- Removed the "fullcoverage" feature used by CPython to measure the coverage of
early-imported standard library modules. CPython `stopped using it
<88054_>`_ in 2021, and it stopped working completely in Python 3.13.

.. _issue 1605: https://github.com/nedbat/coveragepy/pull/1605
.. _issue 1684: https://github.com/nedbat/coveragepy/issues/1684
.. _pull 1685: https://github.com/nedbat/coveragepy/pull/1685
.. _88054: https://github.com/python/cpython/issues/88054


.. scriv-start-here
Expand Down
1 change: 0 additions & 1 deletion MANIFEST.in
Expand Up @@ -31,7 +31,6 @@ recursive-include lab *
recursive-include .github *

recursive-include coverage *.pyi
recursive-include coverage/fullcoverage *.py
recursive-include coverage/ctracer *.c *.h

recursive-include doc *.py *.in *.pip *.rst *.txt *.png
Expand Down
22 changes: 3 additions & 19 deletions coverage/collector.py
Expand Up @@ -11,7 +11,7 @@

from types import FrameType
from typing import (
cast, Any, Callable, Dict, List, Mapping, Optional, Set, Tuple, Type, TypeVar,
cast, Any, Callable, Dict, List, Mapping, Optional, Set, Type, TypeVar,
)

from coverage import env
Expand All @@ -24,7 +24,7 @@
from coverage.plugin import CoveragePlugin
from coverage.pytracer import PyTracer
from coverage.types import (
TArc, TFileDisposition, TLineNo, TTraceData, TTraceFn, TTracer, TWarnFn,
TArc, TFileDisposition, TTraceData, TTraceFn, TTracer, TWarnFn,
)

os = isolate_module(os)
Expand Down Expand Up @@ -330,18 +330,9 @@ def start(self) -> None:

self.tracers = []

# Check to see whether we had a fullcoverage tracer installed. If so,
# get the stack frames it stashed away for us.
traces0: List[Tuple[Tuple[FrameType, str, Any], TLineNo]] = []
fn0 = sys.gettrace()
if fn0:
tracer0 = getattr(fn0, '__self__', None)
if tracer0:
traces0 = getattr(tracer0, 'traces', [])

try:
# Install the tracer on this thread.
fn = self._start_tracer()
self._start_tracer()
except:
if self._collectors:
self._collectors[-1].resume()
Expand All @@ -351,13 +342,6 @@ def start(self) -> None:
# stack of collectors.
self._collectors.append(self)

# Replay all the events from fullcoverage into the new trace function.
for (frame, event, arg), lineno in traces0:
try:
fn(frame, event, arg, lineno=lineno)
except TypeError as ex:
raise RuntimeError("fullcoverage must be run with the C trace function.") from ex

# Install our installation tracer in threading, to jump-start other
# threads.
if self.threading:
Expand Down
23 changes: 2 additions & 21 deletions coverage/ctracer/tracer.c
Expand Up @@ -857,24 +857,14 @@ CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unuse
* means it must be callable to be used in sys.settrace().
*
* So we make ourself callable, equivalent to invoking our trace function.
*
* To help with the process of replaying stored frames, this function has an
* optional keyword argument:
*
* def CTracer_call(frame, event, arg, lineno=0)
*
* If provided, the lineno argument is used as the line number, and the
* frame's f_lineno member is ignored.
*/
static PyObject *
CTracer_call(CTracer *self, PyObject *args, PyObject *kwds)
{
PyFrameObject *frame;
PyObject *what_str;
PyObject *arg;
int lineno = 0;
int what;
int orig_lineno;
PyObject *ret = NULL;
PyObject * ascii = NULL;

Expand All @@ -888,10 +878,10 @@ CTracer_call(CTracer *self, PyObject *args, PyObject *kwds)
NULL
};

static char *kwlist[] = {"frame", "event", "arg", "lineno", NULL};
static char *kwlist[] = {"frame", "event", "arg", NULL};

if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!O|i:Tracer_call", kwlist,
&PyFrame_Type, &frame, &PyUnicode_Type, &what_str, &arg, &lineno)) {
&PyFrame_Type, &frame, &PyUnicode_Type, &what_str, &arg)) {
goto done;
}

Expand All @@ -913,21 +903,12 @@ CTracer_call(CTracer *self, PyObject *args, PyObject *kwds)
Py_DECREF(ascii);
#endif

/* Save off the frame's lineno, and use the forced one, if provided. */
orig_lineno = frame->f_lineno;
if (lineno > 0) {
frame->f_lineno = lineno;
}

/* Invoke the C function, and return ourselves. */
if (CTracer_trace(self, frame, what, arg) == RET_OK) {
Py_INCREF(self);
ret = (PyObject *)self;
}

/* Clean up. */
frame->f_lineno = orig_lineno;

/* For better speed, install ourselves the C way so that future calls go
directly to CTracer_trace, without this intermediate function.
Expand Down
57 changes: 0 additions & 57 deletions coverage/fullcoverage/encodings.py

This file was deleted.

3 changes: 1 addition & 2 deletions pyproject.toml
Expand Up @@ -26,8 +26,7 @@ warn_unused_configs = true
warn_unused_ignores = true

exclude = """(?x)(
^coverage/fullcoverage/encodings\\.py$ # can't import things into it.
| ^tests/balance_xdist_plugin\\.py$ # not part of our test suite.
^tests/balance_xdist_plugin\\.py$ # not part of our test suite.
)"""

## PYLINT
Expand Down
1 change: 0 additions & 1 deletion setup.py
Expand Up @@ -87,7 +87,6 @@
package_data={
'coverage': [
'htmlfiles/*.*',
'fullcoverage/*.*',
'py.typed',
]
},
Expand Down
24 changes: 0 additions & 24 deletions tests/test_process.py
Expand Up @@ -556,30 +556,6 @@ def f():
)
assert msg in out

@pytest.mark.expensive
@pytest.mark.skipif(not env.C_TRACER, reason="fullcoverage only works with the C tracer.")
@pytest.mark.skipif(env.METACOV, reason="Can't test fullcoverage when measuring ourselves")
def test_fullcoverage(self) -> None:
# fullcoverage is a trick to get stdlib modules measured from
# the very beginning of the process. Here we import os and
# then check how many lines are measured.
self.make_file("getenv.py", """\
import os
print("FOOEY == %s" % os.getenv("FOOEY"))
""")

fullcov = os.path.join(os.path.dirname(coverage.__file__), "fullcoverage")
self.set_environ("FOOEY", "BOO")
self.set_environ("PYTHONPATH", fullcov)
out = self.run_command("python -X frozen_modules=off -m coverage run -L getenv.py")
assert out == "FOOEY == BOO\n"
data = coverage.CoverageData()
data.read()
# The actual number of executed lines in os.py when it's
# imported is 120 or so. Just running os.getenv executes
# about 5.
assert line_counts(data)['os.py'] > 50

# Pypy passes locally, but fails in CI? Perhaps the version of macOS is
# significant? https://foss.heptapod.net/pypy/pypy/-/issues/3074
@pytest.mark.skipif(env.PYPY, reason="PyPy is unreliable with this test")
Expand Down

0 comments on commit 7b8dec9

Please sign in to comment.