Repository navigation
Add Coverage.clear_data() for lightweight data reset - #2235
pctablet505 wants to merge 3 commits into
Conversation
eb5d806 to
f4492d2
Compare
082b00e to
4f796b8
Compare
4f796b8 to
efa67ae
Compare
erase() clears collected data but also forces the next start() to rerun _init_for_start(), rebuilding Core, Collector, and InOrOut and re-registering atexit/sigterm handlers. For code that repeatedly starts/stops measurement on the same Coverage instance and only needs a clean slate of data between cycles, this reinitialization is pure overhead. clear_data() closes and replaces the current CoverageData, keeping the existing collector/tracer setup intact for the next start(). Closes coveragepy#2139.
clear_data() closed the current CoverageData and made a fresh one, but the closed object stayed referenced in _data_to_close for the life of the process. Over many start/stop/clear cycles (e.g. long fuzzer runs) these closed, empty objects piled up, partly defeating the point of the lighter-weight reset. _close_data() now drops the object from _data_to_close when it forgets it. Also fill in the versionadded placeholder for Coverage.clear_data().
efa67ae to
c01dea4
Compare
|
One disk-backed edge case in pctablet505's implementation, beyond cyberthirst's At from pathlib import Path
from tempfile import TemporaryDirectory
from coverage import Coverage
with TemporaryDirectory() as temp:
root = Path(temp)
cov = Coverage(config_file=False, data_file=str(root / ".coverage"),
data_suffix="workerA", include=[str(root / "*.py")])
for n in (1, 2):
if n == 2:
cov.clear_data()
source = root / f"cycle_{n}.py"
source.write_text("answer = 42\n", encoding="utf-8")
with cov.collect():
exec(compile(source.read_text(encoding="utf-8"), str(source), "exec"), {})
cov.save()
print(Path(cov.get_data().data_filename()).name)Output: Reading both files confirms the suffixed file contains cycle 1 and the base file contains cycle 2. The recorded comparison reproduced this three times per configuration on CPython 3.12.14/Linux using CTracer, PyTracer, and SysMonitor. Default unsuffixed and no-disk controls passed. Replacing The new method calls The full suite and true concurrent workers were not tested. |
Summary
erase()clears collected coverage data, but it also sets things up sothe next
start()reruns_init_for_start()— rebuildingCore,Collector, andInOrOut, and re-registering the atexit/sigtermhandlers. For code that starts/stops measurement on the same
Coverageinstance many times in a row and only needs a clean slateof data between cycles (tracing configuration doesn't change), that
reinitialization is pure overhead.
This adds
Coverage.clear_data(): it closes and replaces the currentCoverageData, but leaves the existing collector/tracer setup aloneso the next
start()doesn't redo the expensive part.Closes #2139, which includes a benchmark showing the reinitialization
overhead and the speedup from avoiding it.
Changes
coverage/control.py: newCoverage.clear_data()method, plus asmall
_close_data()helper it shares the "close and forget thecurrent CoverageData" logic with.
tests/test_api.py: tests thatclear_data()gives a fresh data set,that repeated cycles behave like the existing
erase()-based test,and that the collector/core/inorout objects from the first
start()are still the ones in use after a
clear_data()+start()cycle.CHANGES.rst: entry under "Unreleased".Test plan
pytest tests/test_api.py tests/test_data.py tests/test_collector.py— all passingruff format --check,pylint, andmypy --strictall clean on the touched filesclear_data()substituted for the manual workaround; confirms the same order-of-magnitude speedup and no extra atexit-callback accumulation