From 3f75d80d22c01c8fb90bc0a5ad0468add281e0f2 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 14 Mar 2024 15:21:03 -0400 Subject: [PATCH 01/44] build: bump version --- CHANGES.rst | 6 ++++++ coverage/version.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7c7667d39..effcd288a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,6 +20,12 @@ upgrading your version of coverage.py. .. Version 9.8.1 — 2027-07-27 .. -------------------------- +Unreleased +---------- + +Nothing yet. + + .. scriv-start-here .. _changes_7-4-4: diff --git a/coverage/version.py b/coverage/version.py index 10f4115ef..0ef2375cb 100644 --- a/coverage/version.py +++ b/coverage/version.py @@ -8,8 +8,8 @@ # version_info: same semantics as sys.version_info. # _dev: the .devN suffix if any. -version_info = (7, 4, 4, "final", 0) -_dev = 0 +version_info = (7, 4, 5, "alpha", 0) +_dev = 1 def _make_version( From fb36364ae8d2a9989abfe1e4ce174455c6488f91 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 14 Mar 2024 15:21:20 -0400 Subject: [PATCH 02/44] build: keep twine output quiet --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 109221d53..12f8f0e96 100644 --- a/Makefile +++ b/Makefile @@ -198,10 +198,10 @@ kit: ## Make the source distribution. python -m build kit_upload: ## Upload the built distributions to PyPI. - twine upload --verbose dist/* + twine upload dist/* test_upload: ## Upload the distributions to PyPI's testing server. - twine upload --verbose --repository testpypi --password $$TWINE_TEST_PASSWORD dist/* + twine upload --repository testpypi --password $$TWINE_TEST_PASSWORD dist/* kit_local: # pip.conf looks like this: From 6a269a6d23c3ef47c8fc3bdca5bd38eb7a48ca45 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 15 Mar 2024 14:04:47 -0400 Subject: [PATCH 03/44] refactor(test): make a helper more error-proof --- tests/coveragetest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 35734f30f..1856df89d 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -262,6 +262,7 @@ def check_coverage( def make_data_file( self, basename: str | None = None, + *, suffix: str | None = None, lines: Mapping[str, Collection[TLineNo]] | None = None, arcs: Mapping[str, Collection[TArc]] | None = None, From fb23aa6c821f554d2e7db6db103c696f0766205d Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 15 Mar 2024 14:07:41 -0400 Subject: [PATCH 04/44] build: 3.13.0a5 is available --- .github/workflows/coverage.yml | 7 ------- .github/workflows/testsuite.yml | 7 ------- CHANGES.rst | 2 +- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5c9a8814f..5c63d1d4a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -69,13 +69,6 @@ jobs: python-version: "pypy-3.9" - os: windows python-version: "pypy-3.10" - # Skip 3.13.0a4 and pin to 3.13.0a3 for Windows due to build error. - # Undo when 3.13.0a5 is released. - - os: windows - python-version: "3.13" - include: - - os: windows - python-version: "3.13.0-alpha.3" # If one job fails, stop the whole thing. fail-fast: true diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml index ee94b1ea0..e6742753e 100644 --- a/.github/workflows/testsuite.yml +++ b/.github/workflows/testsuite.yml @@ -62,13 +62,6 @@ jobs: python-version: "pypy-3.9" - os: windows python-version: "pypy-3.10" - # Skip 3.13.0a4 and pin to 3.13.0a3 for Windows due to build error. - # Undo when 3.13.0a5 is released. - - os: windows - python-version: "3.13" - include: - - os: windows - python-version: "3.13.0-alpha.3" fail-fast: false steps: diff --git a/CHANGES.rst b/CHANGES.rst index effcd288a..66231feaf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -23,7 +23,7 @@ upgrading your version of coverage.py. Unreleased ---------- -Nothing yet. +- Python 3.13.0a5 is supported. .. scriv-start-here From 45cbdd221a99791b3b50951b4a245dde3299aeb7 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 2 Mar 2024 16:01:02 -0500 Subject: [PATCH 05/44] refactor: TypedDict is now supported, no need for if TYPE_CHECKING --- coverage/html.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/coverage/html.py b/coverage/html.py index e2bae1d6b..06fa79b7c 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -15,7 +15,7 @@ import string from dataclasses import dataclass -from typing import Any, Iterable, TYPE_CHECKING, cast +from typing import Any, Iterable, TYPE_CHECKING, TypedDict, cast import coverage from coverage.data import CoverageData, add_data_to_hash @@ -31,26 +31,23 @@ if TYPE_CHECKING: - # To avoid circular imports: from coverage import Coverage from coverage.plugins import FileReporter - # To be able to use 3.8 typing features, and still run on 3.7: - from typing import TypedDict - class IndexInfoDict(TypedDict): - """Information for each file, to render the index file.""" - nums: Numbers - html_filename: str - relative_filename: str +os = isolate_module(os) - class FileInfoDict(TypedDict): - """Summary of the information from last rendering, to avoid duplicate work.""" - hash: str - index: IndexInfoDict +class IndexInfoDict(TypedDict): + """Information for each file, to render the index file.""" + nums: Numbers + html_filename: str + relative_filename: str -os = isolate_module(os) +class FileInfoDict(TypedDict): + """Summary of the information from last rendering, to avoid duplicate work.""" + hash: str + index: IndexInfoDict def data_filename(fname: str) -> str: From 49b3288008399a67a2b83905dc4e4fc1527b2645 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 3 Mar 2024 07:59:22 -0500 Subject: [PATCH 06/44] docs(build): remove an obsolete comment --- .github/workflows/quality.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index d1c1f311a..3376aafd1 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -69,8 +69,6 @@ jobs: - name: "Install dependencies" run: | - # We run on 3.8, but the pins were made on 3.7, so don't insist on - # hashes, which won't match. python -m pip install -r requirements/tox.pip - name: "Tox mypy" From 73cba7e453036f23314723dd0a719012333ddec9 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 3 Mar 2024 07:59:49 -0500 Subject: [PATCH 07/44] docs(comment): tweak the description of the status data format --- coverage/html.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coverage/html.py b/coverage/html.py index 06fa79b7c..ead647112 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -512,9 +512,10 @@ class IncrementalChecker: # The data looks like: # # { + # "note": "This file is an internal implementation detail ...", # "format": 2, - # "globals": "540ee119c15d52a68a53fe6f0897346d", # "version": "4.0a1", + # "globals": "540ee119c15d52a68a53fe6f0897346d", # "files": { # "cogapp___init__": { # "hash": "e45581a5b48f879f301c0f30bf77a50c", From 7877fccb91561107f47ff5935d3df4e49ee154bd Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 28 Feb 2024 15:34:12 -0500 Subject: [PATCH 08/44] refactor: use dataclass for Numbers --- coverage/results.py | 79 +++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 50 deletions(-) diff --git a/coverage/results.py b/coverage/results.py index 45cc4f198..b5289eb45 100644 --- a/coverage/results.py +++ b/coverage/results.py @@ -6,10 +6,10 @@ from __future__ import annotations import collections +import dataclasses from typing import Callable, Iterable, TYPE_CHECKING -from coverage.debug import auto_repr from coverage.exceptions import ConfigError from coverage.misc import nice_pair from coverage.types import TArc, TLineNo @@ -178,6 +178,7 @@ def branch_stats(self) -> dict[TLineNo, tuple[int, int]]: return stats +@dataclasses.dataclass class Numbers: """The numerical results of measuring coverage. @@ -186,38 +187,18 @@ class Numbers: """ - def __init__( - self, - precision: int = 0, - n_files: int = 0, - n_statements: int = 0, - n_excluded: int = 0, - n_missing: int = 0, - n_branches: int = 0, - n_partial_branches: int = 0, - n_missing_branches: int = 0, - ) -> None: - assert 0 <= precision < 10 - self._precision = precision - self._near0 = 1.0 / 10**precision - self._near100 = 100.0 - self._near0 - self.n_files = n_files - self.n_statements = n_statements - self.n_excluded = n_excluded - self.n_missing = n_missing - self.n_branches = n_branches - self.n_partial_branches = n_partial_branches - self.n_missing_branches = n_missing_branches - - __repr__ = auto_repr + precision: int = 0 + n_files: int = 0 + n_statements: int = 0 + n_excluded: int = 0 + n_missing: int = 0 + n_branches: int = 0 + n_partial_branches: int = 0 + n_missing_branches: int = 0 def init_args(self) -> list[int]: """Return a list for __init__(*args) to recreate this object.""" - return [ - self._precision, - self.n_files, self.n_statements, self.n_excluded, self.n_missing, - self.n_branches, self.n_partial_branches, self.n_missing_branches, - ] + return list(dataclasses.astuple(self)) @property def n_executed(self) -> int: @@ -258,19 +239,20 @@ def display_covered(self, pc: float) -> str: result in either "0" or "100". """ - if 0 < pc < self._near0: - pc = self._near0 - elif self._near100 < pc < 100: - pc = self._near100 + near0 = 1.0 / 10 ** self.precision + if 0 < pc < near0: + pc = near0 + elif (100.0 - near0) < pc < 100: + pc = 100.0 - near0 else: - pc = round(pc, self._precision) - return "%.*f" % (self._precision, pc) + pc = round(pc, self.precision) + return "%.*f" % (self.precision, pc) def pc_str_width(self) -> int: """How many characters wide can pc_covered_str be?""" width = 3 # "100" - if self._precision > 0: - width += 1 + self._precision + if self.precision > 0: + width += 1 + self.precision return width @property @@ -281,19 +263,16 @@ def ratio_covered(self) -> tuple[int, int]: return numerator, denominator def __add__(self, other: Numbers) -> Numbers: - nums = Numbers(precision=self._precision) - nums.n_files = self.n_files + other.n_files - nums.n_statements = self.n_statements + other.n_statements - nums.n_excluded = self.n_excluded + other.n_excluded - nums.n_missing = self.n_missing + other.n_missing - nums.n_branches = self.n_branches + other.n_branches - nums.n_partial_branches = ( - self.n_partial_branches + other.n_partial_branches - ) - nums.n_missing_branches = ( - self.n_missing_branches + other.n_missing_branches + return Numbers( + self.precision, + self.n_files + other.n_files, + self.n_statements + other.n_statements, + self.n_excluded + other.n_excluded, + self.n_missing + other.n_missing, + self.n_branches + other.n_branches, + self.n_partial_branches + other.n_partial_branches, + self.n_missing_branches + other.n_missing_branches, ) - return nums def __radd__(self, other: int) -> Numbers: # Implementing 0+Numbers allows us to sum() a list of Numbers. From 121703bb25b3619f28241c0328be13904fb0ca1b Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 1 Mar 2024 16:37:16 -0500 Subject: [PATCH 09/44] perf: more functools.lru_cache for reducing work Got rid of some custom one-element caching, including one that didn't help because the duplicated requests were not sequential. Used functools.lru_cache instead of my own code. Caching file_reporters now means a method might be called more than once, which @expensive was trying to prevent. But the results are cached in the parser, so no worries. --- coverage/control.py | 2 ++ coverage/misc.py | 26 +------------------------- coverage/parser.py | 4 ++++ coverage/phystokens.py | 32 +++++++++----------------------- coverage/python.py | 5 +---- 5 files changed, 17 insertions(+), 52 deletions(-) diff --git a/coverage/control.py b/coverage/control.py index 6f7f9a311..58e64c030 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -8,6 +8,7 @@ import atexit import collections import contextlib +import functools import os import os.path import platform @@ -948,6 +949,7 @@ def _analyze(self, it: FileReporter | TMorf) -> Analysis: return Analysis(data, self.config.precision, fr, self._file_mapper) + @functools.lru_cache(maxsize=1) def _get_file_reporter(self, morf: TMorf) -> FileReporter: """Get a FileReporter for a module or file name.""" assert self._data is not None diff --git a/coverage/misc.py b/coverage/misc.py index 2b27efc99..d5d96fb13 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -21,10 +21,9 @@ from types import ModuleType from typing import ( - Any, Callable, IO, Iterable, Iterator, Mapping, NoReturn, Sequence, TypeVar, + Any, IO, Iterable, Iterator, Mapping, NoReturn, Sequence, TypeVar, ) -from coverage import env from coverage.exceptions import CoverageException from coverage.types import TArc @@ -116,29 +115,6 @@ def nice_pair(pair: TArc) -> str: return "%d-%d" % (start, end) -TSelf = TypeVar("TSelf") -TRetVal = TypeVar("TRetVal") - -def expensive(fn: Callable[[TSelf], TRetVal]) -> Callable[[TSelf], TRetVal]: - """A decorator to indicate that a method shouldn't be called more than once. - - Normally, this does nothing. During testing, this raises an exception if - called more than once. - - """ - if env.TESTING: - attr = "_once_" + fn.__name__ - - def _wrapper(self: TSelf) -> TRetVal: - if hasattr(self, attr): - raise AssertionError(f"Shouldn't have called {fn.__name__} more than once") - setattr(self, attr, True) - return fn(self) - return _wrapper - else: - return fn # pragma: not testing - - def bool_or_none(b: Any) -> bool | None: """Return bool(b), but preserve None.""" if b is None: diff --git a/coverage/parser.py b/coverage/parser.py index 959174c36..45ea397bb 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -6,6 +6,7 @@ from __future__ import annotations import ast +import functools import collections import os import re @@ -100,6 +101,7 @@ def __init__( self._all_arcs: set[TArc] | None = None self._missing_arc_fragments: TArcFragments | None = None + @functools.lru_cache() def lines_matching(self, *regexes: str) -> set[TLineNo]: """Find the lines matching one of a list of regexes. @@ -232,6 +234,7 @@ def _raw_parse(self) -> None: if env.PYBEHAVIOR.module_firstline_1 and self._multiline: self._multiline[1] = min(self.raw_statements) + @functools.lru_cache(maxsize=1000) def first_line(self, lineno: TLineNo) -> TLineNo: """Return the first line number of the statement including `lineno`.""" if lineno < 0: @@ -312,6 +315,7 @@ def _analyze_ast(self) -> None: self._missing_arc_fragments = aaa.missing_arc_fragments + @functools.lru_cache() def exit_counts(self) -> dict[TLineNo, int]: """Get a count of exits from that each line. diff --git a/coverage/phystokens.py b/coverage/phystokens.py index 7d8b30c8a..626e9967e 100644 --- a/coverage/phystokens.py +++ b/coverage/phystokens.py @@ -6,6 +6,7 @@ from __future__ import annotations import ast +import functools import io import keyword import re @@ -170,35 +171,20 @@ def source_token_lines(source: str) -> TSourceTokenLines: yield line -class CachedTokenizer: - """A one-element cache around tokenize.generate_tokens. +@functools.lru_cache(maxsize=100) +def generate_tokens(text: str) -> TokenInfos: + """A cached version of `tokenize.generate_tokens`. When reporting, coverage.py tokenizes files twice, once to find the structure of the file, and once to syntax-color it. Tokenizing is expensive, and easily cached. - This is a one-element cache so that our twice-in-a-row tokenizing doesn't - actually tokenize twice. - + Unfortunately, the HTML report code tokenizes all the files the first time + before then tokenizing them a second time, so we cache many. Ideally we'd + rearrange the code to tokenize each file twice before moving onto the next. """ - def __init__(self) -> None: - self.last_text: str | None = None - self.last_tokens: list[tokenize.TokenInfo] = [] - - def generate_tokens(self, text: str) -> TokenInfos: - """A stand-in for `tokenize.generate_tokens`.""" - if text != self.last_text: - self.last_text = text - readline = io.StringIO(text).readline - try: - self.last_tokens = list(tokenize.generate_tokens(readline)) - except: - self.last_text = None - raise - return self.last_tokens - -# Create our generate_tokens cache as a callable replacement function. -generate_tokens = CachedTokenizer().generate_tokens + readline = io.StringIO(text).readline + return list(tokenize.generate_tokens(readline)) def source_encoding(source: bytes) -> str: diff --git a/coverage/python.py b/coverage/python.py index 0a522d6b9..28344fe0e 100644 --- a/coverage/python.py +++ b/coverage/python.py @@ -14,7 +14,7 @@ from coverage import env from coverage.exceptions import CoverageException, NoSource from coverage.files import canonical_filename, relative_filename, zip_location -from coverage.misc import expensive, isolate_module, join_regex +from coverage.misc import isolate_module, join_regex from coverage.parser import PythonParser from coverage.phystokens import source_token_lines, source_encoding from coverage.plugin import FileReporter @@ -202,7 +202,6 @@ def translate_lines(self, lines: Iterable[TLineNo]) -> set[TLineNo]: def translate_arcs(self, arcs: Iterable[TArc]) -> set[TArc]: return self.parser.translate_arcs(arcs) - @expensive def no_branch_lines(self) -> set[TLineNo]: assert self.coverage is not None no_branch = self.parser.lines_matching( @@ -211,11 +210,9 @@ def no_branch_lines(self) -> set[TLineNo]: ) return no_branch - @expensive def arcs(self) -> set[TArc]: return self.parser.arcs() - @expensive def exit_counts(self) -> dict[TLineNo, int]: return self.parser.exit_counts() From c02c96792f43466d881c8c91071a6dd953970400 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 3 Mar 2024 17:10:51 -0500 Subject: [PATCH 10/44] refactor: a slightly different way to deal with unserializable Numbers --- coverage/html.py | 13 ++++++++++--- coverage/results.py | 4 ---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/coverage/html.py b/coverage/html.py index ead647112..9810e166e 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -6,6 +6,7 @@ from __future__ import annotations import collections +import dataclasses import datetime import functools import json @@ -40,7 +41,10 @@ class IndexInfoDict(TypedDict): """Information for each file, to render the index file.""" - nums: Numbers + # For in-memory use, we have Numbers. For serialization, we write a list + # of ints. Two fields keeps the type-checker happier. + nums: Numbers | None + numlist: list[int] html_filename: str relative_filename: str @@ -460,6 +464,7 @@ def write_html_file(self, ftr: FileToReport, prev_html: str, next_html: str) -> # Save this file's information for the index file. index_info: IndexInfoDict = { "nums": ftr.analysis.numbers, + "numlist": [], "html_filename": ftr.html_filename, "relative_filename": ftr.fr.relative_filename(), } @@ -565,7 +570,7 @@ def read(self) -> None: if usable: self.files = {} for filename, fileinfo in status["files"].items(): - fileinfo["index"]["nums"] = Numbers(*fileinfo["index"]["nums"]) + fileinfo["index"]["nums"] = Numbers(*fileinfo["index"]["numlist"]) self.files[filename] = fileinfo self.globals = status["globals"] else: @@ -577,7 +582,9 @@ def write(self) -> None: files = {} for filename, fileinfo in self.files.items(): index = fileinfo["index"] - index["nums"] = index["nums"].init_args() # type: ignore[typeddict-item] + assert index["nums"] is not None + index["numlist"] = list(dataclasses.astuple(index["nums"])) + index["nums"] = None files[filename] = fileinfo status = { diff --git a/coverage/results.py b/coverage/results.py index b5289eb45..524bbb95c 100644 --- a/coverage/results.py +++ b/coverage/results.py @@ -196,10 +196,6 @@ class Numbers: n_partial_branches: int = 0 n_missing_branches: int = 0 - def init_args(self) -> list[int]: - """Return a list for __init__(*args) to recreate this object.""" - return list(dataclasses.astuple(self)) - @property def n_executed(self) -> int: """Returns the number of executed statements.""" From 52db90eab6d0a0e3c0416ab4ba61cc4268e6f0dd Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 22 Mar 2024 16:29:47 -0400 Subject: [PATCH 11/44] docs: clarify how multiple config files are consulted --- doc/config.rst | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/config.rst b/doc/config.rst index 4485145c0..48bd74ee3 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -37,11 +37,10 @@ A different location for the configuration file can be specified with the ``--rcfile=FILE`` command line option or with the ``COVERAGE_RCFILE`` environment variable. -Coverage.py will read settings from other usual configuration files if no other -configuration file is used. It will automatically read from "setup.cfg" or -"tox.ini" if they exist. In this case, the section names have "coverage:" -prefixed, so the ``[run]`` options described below will be found in the -``[coverage:run]`` section of the file. +If ``.coveragerc`` doesn't exist and another file hasn't been specified, then +coverage.py will look for settings in other common configuration files, in this +order: setup.cfg, tox.ini, or pyproject.toml. The first file found with +coverage.py settings will be used and other files won't be consulted. Coverage.py will read from "pyproject.toml" if TOML support is available, either because you are running on Python 3.11 or later, or because you @@ -68,6 +67,10 @@ values on multiple lines. Boolean values can be specified as ``on``, ``off``, ``true``, ``false``, ``1``, or ``0`` and are case-insensitive. +In setup.cfg or tox.ini, the section names have "coverage:" prefixed, so the +``[run]`` options described below will be found in the ``[coverage:run]`` +section of the file. + TOML Syntax ........... From 9c7857dd94ba678bd851bba63e7b2f066a738311 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 17 Mar 2024 21:55:47 -0400 Subject: [PATCH 12/44] docs: polish up the contributing docs --- doc/contributing.rst | 155 +++++++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 73 deletions(-) diff --git a/doc/contributing.rst b/doc/contributing.rst index 359d49fc1..2f3d3e2fb 100644 --- a/doc/contributing.rst +++ b/doc/contributing.rst @@ -66,14 +66,19 @@ these steps: Running the tests ----------------- +.. To get the test output: + # Resize terminal width to 95 + % make sterile + +.. with COVERAGE_ONE_CORE= + The tests are written mostly as standard unittest-style tests, and are run with pytest running under `tox`_:: $ python3 -m tox -e py38 - ROOT: tox-gh won't override envlist because tox is not running in GitHub Actions - py38: wheel-0.40.0-py3-none-any.whl already present in /Users/nedbatchelder/Library/Application Support/virtualenv/wheel/3.8/embed/3/wheel.json - py38: pip-23.1.2-py3-none-any.whl already present in /Users/nedbatchelder/Library/Application Support/virtualenv/wheel/3.8/embed/3/pip.json - py38: setuptools-67.8.0-py3-none-any.whl already present in /Users/nedbatchelder/Library/Application Support/virtualenv/wheel/3.8/embed/3/setuptools.json + py38: wheel-0.43.0-py3-none-any.whl already present in /Users/ned/Library/Application Support/virtualenv/wheel/3.8/embed/3/wheel.json + py38: pip-24.0-py3-none-any.whl already present in /Users/ned/Library/Application Support/virtualenv/wheel/3.8/embed/3/pip.json + py38: setuptools-69.2.0-py3-none-any.whl already present in /Users/ned/Library/Application Support/virtualenv/wheel/3.8/embed/3/setuptools.json py38: install_deps> python -m pip install -U -r requirements/pip.pip -r requirements/pytest.pip -r requirements/light-threads.pip .pkg: install_requires> python -I -m pip install setuptools .pkg: _optional_hooks> python /usr/local/virtualenvs/coverage/lib/python3.8/site-packages/pyproject_api/_backend.py True setuptools.build_meta @@ -81,56 +86,60 @@ pytest running under `tox`_:: .pkg: install_requires_for_build_editable> python -I -m pip install wheel .pkg: build_editable> python /usr/local/virtualenvs/coverage/lib/python3.8/site-packages/pyproject_api/_backend.py True setuptools.build_meta py38: install_package_deps> python -m pip install -U 'tomli; python_full_version <= "3.11.0a6"' - py38: install_package> python -m pip install -U --force-reinstall --no-deps .tox/.tmp/package/1/coverage-7.2.8a0.dev1-0.editable-cp38-cp38-macosx_13_0_x86_64.whl + py38: install_package> python -m pip install -U --force-reinstall --no-deps .tox/.tmp/package/1/coverage-7.4.5a0.dev1-0.editable-cp38-cp38-macosx_14_0_arm64.whl py38: commands[0]> python igor.py zip_mods py38: commands[1]> python setup.py --quiet build_ext --inplace + ld: warning: duplicate -rpath '/usr/local/pyenv/pyenv/versions/3.8.18/lib' ignored + ld: warning: duplicate -rpath '/opt/homebrew/lib' ignored py38: commands[2]> python -m pip install -q -e . - py38: commands[3]> python igor.py test_with_tracer c - === CPython 3.8.17 with C tracer (.tox/py38/bin/python) === + py38: commands[3]> python igor.py test_with_core ctrace + === CPython 3.8.18 with C tracer (.tox/py38/bin/python) === bringing up nodes... - ............................................................................................ [ 6%] - ...................................x.....x...............s..s..s.s.......................... [ 13%] - ............................................................................................ [ 20%] - ............................................................................................ [ 27%] - ............................................................................................ [ 34%] - ............................................................................................ [ 41%] - ............................................................................................ [ 47%] - ............................................................................................ [ 54%] - ........................................................s...........s....................... [ 61%] - ............................................................................................ [ 68%] - ..........s...........................s...........s......................................... [ 75%] - ..................s...................s..............................................s...... [ 82%] - ...............................s............................................................ [ 88%] - ............................................................................................ [ 95%] - .............................s............................. [100%] - 1332 passed, 14 skipped, 2 xfailed in 60.54s (0:01:00) + ....................................................................................... [ 6%] + .....................................................x...x............s......s.s....s.. [ 12%] + ....................................................................................... [ 18%] + ....................................................................................... [ 25%] + ....................................................................................... [ 31%] + ....................................................................................... [ 37%] + ....................................................................................... [ 44%] + ....................................................................................... [ 50%] + ....................................................................................... [ 56%] + ........................s...........s.................................................. [ 63%] + ...........................................................................s........... [ 69%] + .................................s............s.s.................s.................... [ 75%] + ...........................................s........................................s.. [ 81%] + ................................s...................................................... [ 88%] + ....................................................................................... [ 94%] + ............................................................s................... [100%] + 1368 passed, 15 skipped, 2 xfailed in 13.10s py38: commands[4]> python igor.py remove_extension - py38: commands[5]> python igor.py test_with_tracer py - === CPython 3.8.17 with Python tracer (.tox/py38/bin/python) === + py38: commands[5]> python igor.py test_with_core pytrace + === CPython 3.8.18 with Python tracer (.tox/py38/bin/python) === bringing up nodes... - ............................................................................................ [ 6%] - .............................x.............................................................. [ 13%] - ..ss...................................x.....................................ss............. [ 20%] - ..........s.....................................ss...................s.................sss.. [ 27%] - .ss.....s................................................................................... [ 34%] - ............................................................................................ [ 41%] - ....................................................................s....................... [ 47%] - .....................................................................s..s.ss................ [ 54%] - ...ss.sss.......................................................s........s........sss....... [ 61%] - .ss...............s.s..................s.................s.s................................ [ 68%] - ...........................................................................................s [ 75%] - ........................................................s.......................s........... [ 82%] - ....................ss.s........................ssss........................................ [ 88%] - ............................................................................................ [ 95%] - ................................s...............ss......... [100%] - 1297 passed, 49 skipped, 2 xfailed in 44.59s - .pkg: _exit> python /usr/local/virtualenvs/coverage/lib/python3.8/site-packages/pyproject_api/_backend.py True setuptools.build_meta - py38: OK (143.82=setup[23.23]+cmd[0.29,1.60,8.43,61.64,0.34,48.28] seconds) - congratulations :) (144.93 seconds) - -Tox runs the complete test suite twice for each version of Python you have -installed. The first run uses the C implementation of the trace function, -the second uses the Python implementation. + ....................................................................................... [ 6%] + ....................x..x.............................................s.ss...s.......... [ 12%] + ..........................................................................s.ss.s..s.... [ 18%] + s........s........s..s...s............................................................. [ 25%] + ................s...................................................................... [ 31%] + ...................s......ss..........................ssss...........................s. [ 37%] + ....................................................................................... [ 43%] + ....................................................................................... [ 50%] + .................................................................s..................... [ 56%] + ........s..s.........sss.s............................................................. [ 62%] + ...................................................................ss.................. [ 69%] + ..............................................ss...........s.s......................... [ 75%] + ................................ssssss................................................. [ 81%] + ......s...ss........ss................................................................. [ 88%] + .............................................s......................................... [ 94%] + .......................................................................ss....... [100%] + 1333 passed, 50 skipped, 2 xfailed in 11.17s + py38: OK (37.60=setup[9.10]+cmd[0.11,0.49,2.83,13.59,0.11,11.39] seconds) + congratulations :) (37.91 seconds) + +Tox runs the complete test suite a few times for each version of Python you +have installed. The first run uses the C implementation of the trace function, +the second uses the Python implementation. If `sys.monitoring`_ is available, +the suite will be run again with that core. To limit tox to just a few versions of Python, use the ``-e`` switch:: @@ -143,40 +152,40 @@ just a few tests, you can use `pytest test selectors`_:: $ python3 -m tox -- tests/test_misc.py::HasherTest $ python3 -m tox -- tests/test_misc.py::HasherTest::test_string_hashing +.. with COVERAGE_ONE_CORE=1 + These commands run the tests in one file, one class, and just one test, respectively. The pytest ``-k`` option selects tests based on a word in their name, which can be very convenient for ad-hoc test selection. Of course you can combine tox and pytest options:: $ python3 -m tox -q -e py310 -- -n 0 -vv -k hash - === CPython 3.10.12 with C tracer (.tox/py310/bin/python) === - ======================================= test session starts ======================================== - platform darwin -- Python 3.10.12, pytest-7.3.2, pluggy-1.0.0 -- /Users/nedbatchelder/coverage/trunk/.tox/py310/bin/python + ================================== test session starts =================================== + platform darwin -- Python 3.10.13, pytest-8.1.1, pluggy-1.4.0 -- /Users/ned/coverage/trunk/.tox/py310/bin/python cachedir: .tox/py310/.pytest_cache - hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/Users/nedbatchelder/coverage/trunk/.hypothesis/examples') - rootdir: /Users/nedbatchelder/coverage/trunk + hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase(PosixPath('/Users/ned/coverage/trunk/.hypothesis/examples')) + rootdir: /Users/ned/coverage/trunk configfile: pyproject.toml - plugins: hypothesis-6.78.3, flaky-3.7.0, xdist-3.3.1 - collected 1348 items / 1338 deselected / 10 selected + plugins: flaky-3.8.1, xdist-3.5.0, hypothesis-6.99.6 + collected 1385 items / 1375 deselected / 10 selected run-last-failure: no previously failed tests, not deselecting items. - tests/test_data.py::CoverageDataTest::test_add_to_hash_with_lines PASSED [ 10%] - tests/test_data.py::CoverageDataTest::test_add_to_hash_with_arcs PASSED [ 20%] - tests/test_data.py::CoverageDataTest::test_add_to_lines_hash_with_missing_file PASSED [ 30%] - tests/test_data.py::CoverageDataTest::test_add_to_arcs_hash_with_missing_file PASSED [ 40%] - tests/test_execfile.py::RunPycFileTest::test_running_hashed_pyc PASSED [ 50%] - tests/test_misc.py::HasherTest::test_string_hashing PASSED [ 60%] - tests/test_misc.py::HasherTest::test_bytes_hashing PASSED [ 70%] - tests/test_misc.py::HasherTest::test_unicode_hashing PASSED [ 80%] - tests/test_misc.py::HasherTest::test_dict_hashing PASSED [ 90%] - tests/test_misc.py::HasherTest::test_dict_collision PASSED [100%] + tests/test_data.py::CoverageDataTest::test_add_to_hash_with_lines PASSED [ 10%] + tests/test_data.py::CoverageDataTest::test_add_to_hash_with_arcs PASSED [ 20%] + tests/test_data.py::CoverageDataTest::test_add_to_lines_hash_with_missing_file PASSED [ 30%] + tests/test_data.py::CoverageDataTest::test_add_to_arcs_hash_with_missing_file PASSED [ 40%] + tests/test_execfile.py::RunPycFileTest::test_running_hashed_pyc PASSED [ 50%] + tests/test_misc.py::HasherTest::test_string_hashing PASSED [ 60%] + tests/test_misc.py::HasherTest::test_bytes_hashing PASSED [ 70%] + tests/test_misc.py::HasherTest::test_unicode_hashing PASSED [ 80%] + tests/test_misc.py::HasherTest::test_dict_hashing PASSED [ 90%] + tests/test_misc.py::HasherTest::test_dict_collision PASSED [100%] - =============================== 10 passed, 1338 deselected in 2.24s ================================ - Skipping tests with Python tracer: Only one tracer: no Python tracer for CPython - py310: OK (17.99 seconds) - congratulations :) (19.09 seconds) + ========================== 10 passed, 1375 deselected in 0.60s =========================== + Skipping tests with Python tracer: Only one core: not running pytrace + py310: OK (6.41 seconds) + congratulations :) (6.72 seconds) -TODO: Update this for CORE instead of TRACER You can also affect the test runs with environment variables: @@ -190,7 +199,7 @@ You can also affect the test runs with environment variables: - ``ctrace`` is a sys.settrace function implemented in C. - ``pytrace`` is a sys.settrace function implemented in Python. - - ``sysmon`` is a sys.monitoring implementation. + - ``sysmon`` is a `sys.monitoring`_ implementation. - ``COVERAGE_AST_DUMP=1`` will dump the AST tree as it is being used during code parsing. @@ -201,8 +210,6 @@ as a simple terminal interface to see and set them. Of course, run all the tests on every version of Python you have before submitting a change. -.. _pytest test selectors: https://doc.pytest.org/en/stable/usage.html#specifying-which-tests-to-run - Lint, etc --------- @@ -287,3 +294,5 @@ fixes. If you need help writing tests, please ask. .. _tox: https://tox.readthedocs.io/ .. _black: https://pypi.org/project/black/ .. _set_env.py: https://nedbatchelder.com/blog/201907/set_envpy.html +.. _pytest test selectors: https://doc.pytest.org/en/stable/usage.html#specifying-which-tests-to-run +.. _sys.monitoring: https://docs.python.org/3/library/sys.monitoring.html From a1e6eed986171710a7d8911a02b812165050acb5 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 6 Apr 2024 12:55:15 -0400 Subject: [PATCH 13/44] refactor: html report files start with z_ When I download an HTML report and unzip it, I need to open the directory and find the index.html file. With a d_ prefix, index.html is almost the last file in the directory. Change the prefix to z_ so that now index.html is nearly the first. More convenient for me. --- coverage/files.py | 4 +-- doc/sample_html/index.html | 30 +++++++++---------- doc/sample_html/status.json | 2 +- ...ml => z_7b071bdc2a35fa80___init___py.html} | 12 ++++---- ...ml => z_7b071bdc2a35fa80___main___py.html} | 16 +++++----- ...html => z_7b071bdc2a35fa80_cogapp_py.html} | 16 +++++----- ...l => z_7b071bdc2a35fa80_makefiles_py.html} | 16 +++++----- ...=> z_7b071bdc2a35fa80_test_cogapp_py.html} | 16 +++++----- ...z_7b071bdc2a35fa80_test_makefiles_py.html} | 16 +++++----- ..._7b071bdc2a35fa80_test_whiteutils_py.html} | 16 +++++----- ....html => z_7b071bdc2a35fa80_utils_py.html} | 16 +++++----- ... => z_7b071bdc2a35fa80_whiteutils_py.html} | 12 ++++---- ...r => z_80084bf2fba02475___init__.py,cover} | 0 ...py,cover => z_80084bf2fba02475_a.py,cover} | 0 ...r => z_b039179a8a4ce2c2___init__.py,cover} | 0 ...py,cover => z_b039179a8a4ce2c2_b.py,cover} | 0 tests/gold/html/other/here_py.html | 4 +-- tests/gold/html/other/index.html | 4 +-- tests/test_files.py | 16 +++++----- tests/test_html.py | 6 ++-- 20 files changed, 101 insertions(+), 101 deletions(-) rename doc/sample_html/{d_7b071bdc2a35fa80___init___py.html => z_7b071bdc2a35fa80___init___py.html} (94%) rename doc/sample_html/{d_7b071bdc2a35fa80___main___py.html => z_7b071bdc2a35fa80___main___py.html} (92%) rename doc/sample_html/{d_7b071bdc2a35fa80_cogapp_py.html => z_7b071bdc2a35fa80_cogapp_py.html} (99%) rename doc/sample_html/{d_7b071bdc2a35fa80_makefiles_py.html => z_7b071bdc2a35fa80_makefiles_py.html} (97%) rename doc/sample_html/{d_7b071bdc2a35fa80_test_cogapp_py.html => z_7b071bdc2a35fa80_test_cogapp_py.html} (99%) rename doc/sample_html/{d_7b071bdc2a35fa80_test_makefiles_py.html => z_7b071bdc2a35fa80_test_makefiles_py.html} (98%) rename doc/sample_html/{d_7b071bdc2a35fa80_test_whiteutils_py.html => z_7b071bdc2a35fa80_test_whiteutils_py.html} (98%) rename doc/sample_html/{d_7b071bdc2a35fa80_utils_py.html => z_7b071bdc2a35fa80_utils_py.html} (97%) rename doc/sample_html/{d_7b071bdc2a35fa80_whiteutils_py.html => z_7b071bdc2a35fa80_whiteutils_py.html} (98%) rename tests/gold/annotate/anno_dir/{d_80084bf2fba02475___init__.py,cover => z_80084bf2fba02475___init__.py,cover} (100%) rename tests/gold/annotate/anno_dir/{d_80084bf2fba02475_a.py,cover => z_80084bf2fba02475_a.py,cover} (100%) rename tests/gold/annotate/anno_dir/{d_b039179a8a4ce2c2___init__.py,cover => z_b039179a8a4ce2c2___init__.py,cover} (100%) rename tests/gold/annotate/anno_dir/{d_b039179a8a4ce2c2_b.py,cover => z_b039179a8a4ce2c2_b.py,cover} (100%) diff --git a/coverage/files.py b/coverage/files.py index 0dd3c4e01..5fb704350 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -96,13 +96,13 @@ def flat_rootname(filename: str) -> str: the same directory, but need to differentiate same-named files from different directories. - For example, the file a/b/c.py will return 'd_86bbcbe134d28fd2_c_py' + For example, the file a/b/c.py will return 'z_86bbcbe134d28fd2_c_py' """ dirname, basename = ntpath.split(filename) if dirname: fp = hashlib.new("sha3_256", dirname.encode("UTF-8")).hexdigest()[:16] - prefix = f"d_{fp}_" + prefix = f"z_{fp}_" else: prefix = "" return prefix + basename.replace(".", "_") diff --git a/doc/sample_html/index.html b/doc/sample_html/index.html index 900d30f98..6ed6852af 100644 --- a/doc/sample_html/index.html +++ b/doc/sample_html/index.html @@ -46,8 +46,8 @@

Cog coverage:

- coverage.py v7.4.4, - created at 2024-03-14 14:39 -0400 + coverage.py v7.4.5a0.dev1, + created at 2024-04-06 12:54 -0400

@@ -66,7 +66,7 @@

Cog coverage: - cogapp/__init__.py + cogapp/__init__.py 1 0 0 @@ -75,7 +75,7 @@

Cog coverage: 100.00% - cogapp/__main__.py + cogapp/__main__.py 3 3 0 @@ -84,7 +84,7 @@

Cog coverage: 0.00% - cogapp/cogapp.py + cogapp/cogapp.py 483 228 1 @@ -93,7 +93,7 @@

Cog coverage: 46.74% - cogapp/makefiles.py + cogapp/makefiles.py 22 18 0 @@ -102,7 +102,7 @@

Cog coverage: 11.11% - cogapp/test_cogapp.py + cogapp/test_cogapp.py 854 598 2 @@ -111,7 +111,7 @@

Cog coverage: 29.50% - cogapp/test_makefiles.py + cogapp/test_makefiles.py 68 51 0 @@ -120,7 +120,7 @@

Cog coverage: 22.97% - cogapp/test_whiteutils.py + cogapp/test_whiteutils.py 68 50 0 @@ -129,7 +129,7 @@

Cog coverage: 26.47% - cogapp/utils.py + cogapp/utils.py 37 8 0 @@ -138,7 +138,7 @@

Cog coverage: 76.74% - cogapp/whiteutils.py + cogapp/whiteutils.py 43 5 0 @@ -166,13 +166,13 @@

Cog coverage: diff --git a/doc/sample_html/d_7b071bdc2a35fa80___main___py.html b/doc/sample_html/z_7b071bdc2a35fa80___main___py.html similarity index 92% rename from doc/sample_html/d_7b071bdc2a35fa80___main___py.html rename to doc/sample_html/z_7b071bdc2a35fa80___main___py.html index b1ecf249a..810f8305e 100644 --- a/doc/sample_html/d_7b071bdc2a35fa80___main___py.html +++ b/doc/sample_html/z_7b071bdc2a35fa80___main___py.html @@ -62,12 +62,12 @@

- « prev     + « prev     ^ index     - » next + » next       - coverage.py v7.4.4, - created at 2024-03-14 14:39 -0400 + coverage.py v7.4.5a0.dev1, + created at 2024-04-06 12:54 -0400