diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5c9a8814f..cec49d559 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -30,7 +30,7 @@ concurrency: jobs: coverage: name: "${{ matrix.python-version }} on ${{ matrix.os }}" - runs-on: "${{ matrix.os }}-latest" + runs-on: "${{ matrix.os }}-${{ matrix.os-version || 'latest' }}" env: MATRIX_ID: "${{ matrix.python-version }}.${{ matrix.os }}" @@ -69,13 +69,15 @@ 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" + # GitHub is rolling out macos 14, but it doesn't have Python 3.8 or 3.9. + # https://mastodon.social/@hugovk/112320493602782374 include: - - os: windows - python-version: "3.13.0-alpha.3" + - python-version: "3.8" + os: "macos" + os-version: "13" + - python-version: "3.9" + os: "macos" + os-version: "13" # If one job fails, stop the whole thing. fail-fast: true diff --git a/.github/workflows/kit.yml b/.github/workflows/kit.yml index 9d78b430e..36a1d42bd 100644 --- a/.github/workflows/kit.yml +++ b/.github/workflows/kit.yml @@ -48,7 +48,7 @@ concurrency: jobs: wheels: name: "${{ matrix.py }} ${{ matrix.os }} ${{ matrix.arch }} wheels" - runs-on: ${{ matrix.os }}-latest + runs-on: "${{ matrix.os }}-${{ matrix.os-version || 'latest' }}" env: MATRIX_ID: "${{ matrix.py }}-${{ matrix.os }}-${{ matrix.arch }}" strategy: @@ -84,7 +84,7 @@ jobs: # # # Some OS/arch combinations need overrides for the Python versions: # os_arch_pys = { - # ("macos", "arm64"): ["cp38", "cp39", "cp310", "cp311", "cp312"], + # # ("macos", "arm64"): ["cp38", "cp39", "cp310", "cp311", "cp312"], # } # # #----- ^^^ ---------------------- ^^^ ----- @@ -98,6 +98,8 @@ jobs: # "py": the_py, # "arch": the_arch, # } + # if the_os == "macos": + # them["os-version"] = "13" # print(f"- {json.dumps(them)}") # ]]] - {"os": "ubuntu", "py": "cp38", "arch": "x86_64"} @@ -115,16 +117,16 @@ jobs: - {"os": "ubuntu", "py": "cp310", "arch": "aarch64"} - {"os": "ubuntu", "py": "cp311", "arch": "aarch64"} - {"os": "ubuntu", "py": "cp312", "arch": "aarch64"} - - {"os": "macos", "py": "cp38", "arch": "arm64"} - - {"os": "macos", "py": "cp39", "arch": "arm64"} - - {"os": "macos", "py": "cp310", "arch": "arm64"} - - {"os": "macos", "py": "cp311", "arch": "arm64"} - - {"os": "macos", "py": "cp312", "arch": "arm64"} - - {"os": "macos", "py": "cp38", "arch": "x86_64"} - - {"os": "macos", "py": "cp39", "arch": "x86_64"} - - {"os": "macos", "py": "cp310", "arch": "x86_64"} - - {"os": "macos", "py": "cp311", "arch": "x86_64"} - - {"os": "macos", "py": "cp312", "arch": "x86_64"} + - {"os": "macos", "py": "cp38", "arch": "arm64", "os-version": "13"} + - {"os": "macos", "py": "cp39", "arch": "arm64", "os-version": "13"} + - {"os": "macos", "py": "cp310", "arch": "arm64", "os-version": "13"} + - {"os": "macos", "py": "cp311", "arch": "arm64", "os-version": "13"} + - {"os": "macos", "py": "cp312", "arch": "arm64", "os-version": "13"} + - {"os": "macos", "py": "cp38", "arch": "x86_64", "os-version": "13"} + - {"os": "macos", "py": "cp39", "arch": "x86_64", "os-version": "13"} + - {"os": "macos", "py": "cp310", "arch": "x86_64", "os-version": "13"} + - {"os": "macos", "py": "cp311", "arch": "x86_64", "os-version": "13"} + - {"os": "macos", "py": "cp312", "arch": "x86_64", "os-version": "13"} - {"os": "windows", "py": "cp38", "arch": "x86"} - {"os": "windows", "py": "cp39", "arch": "x86"} - {"os": "windows", "py": "cp310", "arch": "x86"} @@ -135,7 +137,7 @@ jobs: - {"os": "windows", "py": "cp310", "arch": "AMD64"} - {"os": "windows", "py": "cp311", "arch": "AMD64"} - {"os": "windows", "py": "cp312", "arch": "AMD64"} - # [[[end]]] (checksum: a6ca53e9c620c9e5ca85e7322122056c) + # [[[end]]] (checksum: 16ed28c185d540b2d9972a0217864472) fail-fast: false steps: diff --git a/.github/workflows/python-nightly.yml b/.github/workflows/python-nightly.yml index 4a3cc0432..13c347350 100644 --- a/.github/workflows/python-nightly.yml +++ b/.github/workflows/python-nightly.yml @@ -37,8 +37,10 @@ jobs: # because jammy ships 3.10, and deadsnakes doesn't want to clobber it. # https://launchpad.net/~deadsnakes/+archive/ubuntu/nightly/+packages # https://github.com/deadsnakes/issues/issues/234 - # bionic: 18, focal: 20, jammy: 22 - runs-on: ubuntu-20.04 + # See https://github.com/deadsnakes/nightly for the source of the nightly + # builds. + # bionic: 18, focal: 20, jammy: 22, noble: 24 + runs-on: ubuntu-22.04 # If it doesn't finish in an hour, it's not going to. Don't spin for six # hours needlessly. timeout-minutes: 60 @@ -50,7 +52,6 @@ jobs: # tox.ini so that tox will run properly. PYVERSIONS # Available versions: # https://launchpad.net/~deadsnakes/+archive/ubuntu/nightly/+packages - - "3.11-dev" - "3.12-dev" - "3.13-dev" # https://github.com/actions/setup-python#available-versions-of-pypy diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index d1c1f311a..c9718bf11 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -31,7 +31,9 @@ jobs: # Because pylint can report different things on different OS's (!) # (https://github.com/PyCQA/pylint/issues/3489), run this on Mac where local # pylint gets run. - runs-on: macos-latest + # GitHub is rolling out macos 14, but it doesn't have Python 3.8 or 3.9. + # https://mastodon.social/@hugovk/112320493602782374 + runs-on: macos-13 steps: - name: "Check out the repo" @@ -69,8 +71,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" diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml index ee94b1ea0..e11b3d74e 100644 --- a/.github/workflows/testsuite.yml +++ b/.github/workflows/testsuite.yml @@ -30,7 +30,7 @@ concurrency: jobs: tests: name: "${{ matrix.python-version }} on ${{ matrix.os }}" - runs-on: "${{ matrix.os }}-latest" + runs-on: "${{ matrix.os }}-${{ matrix.os-version || 'latest' }}" # Don't run tests if the branch name includes "-notests" if: "!contains(github.ref, '-notests')" strategy: @@ -62,13 +62,16 @@ 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" + # GitHub is rolling out macos 14, but it doesn't have Python 3.8 or 3.9. + # https://mastodon.social/@hugovk/112320493602782374 include: - - os: windows - python-version: "3.13.0-alpha.3" + - python-version: "3.8" + os: "macos" + os-version: "13" + - python-version: "3.9" + os: "macos" + os-version: "13" + fail-fast: false steps: diff --git a/CHANGES.rst b/CHANGES.rst index 7c7667d39..9aad1decf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -22,6 +22,38 @@ upgrading your version of coverage.py. .. scriv-start-here +.. _changes_7-5-0: + +Version 7.5.0 — 2024-04-23 +-------------------------- + +- Added initial support for function and class reporting in the HTML report. + There are now three index pages which link to each other: files, functions, + and classes. Other reports don't yet have this information, but it will be + added in the future where it makes sense. Feedback gladly accepted! + +- Other HTML report improvements: + + - There is now a "hide covered" checkbox to filter out 100% files, finishing + `issue 1384`_. + + - The index page is always sorted by one of its columns, with clearer + indications of the sorting. + + - The "previous file" shortcut key didn't work on the index page, but now it + does, fixing `issue 1765`_. + +- The debug output showing which configuration files were tried now shows + absolute paths to help diagnose problems where settings aren't taking effect, + and is renamed from "attempted_config_files" to the more logical + "config_files_attempted." + +- Python 3.13.0a6 is supported. + +.. _issue 1384: https://github.com/nedbat/coveragepy/issues/1384 +.. _issue 1765: https://github.com/nedbat/coveragepy/issues/1765 + + .. _changes_7-4-4: Version 7.4.4 — 2024-03-14 diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 9c063c20f..1a671fac6 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -102,6 +102,7 @@ J. M. F. Tsang JT Olds Jacqueline Lee Jakub Wilk +James Valleroy Jan Rusak Janakarajan Natarajan Jerin Peter George 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: diff --git a/README.rst b/README.rst index 8ceedbf08..dabdc84fc 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ Coverage.py =========== -Code coverage testing for Python. +Code coverage measurement for Python. .. image:: https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg :target: https://vshymanskyy.github.io/StandWithUkraine @@ -25,7 +25,7 @@ Coverage.py runs on these versions of Python: .. PYVERSIONS -* Python 3.8 through 3.12, and 3.13.0a3 and up. +* Python 3.8 through 3.12, and 3.13.0a6 and up. * PyPy3 versions 3.8 through 3.10. Documentation is on `Read the Docs`_. Code repository and issue tracker are on @@ -35,6 +35,7 @@ Documentation is on `Read the Docs`_. Code repository and issue tracker are on .. _GitHub: https://github.com/nedbat/coveragepy **New in 7.x:** +initial function/class reporting; experimental support for sys.monitoring; dropped support for Python 3.7; added ``Coverage.collect()`` context manager; diff --git a/coverage/__init__.py b/coverage/__init__.py index c3403d444..1bda8921d 100644 --- a/coverage/__init__.py +++ b/coverage/__init__.py @@ -28,6 +28,7 @@ from coverage.data import CoverageData as CoverageData from coverage.exceptions import CoverageException as CoverageException from coverage.plugin import ( + CodeRegion as CodeRegion, CoveragePlugin as CoveragePlugin, FileReporter as FileReporter, FileTracer as FileTracer, @@ -35,7 +36,3 @@ # Backward compatibility. coverage = Coverage - -# On Windows, we encode and decode deep enough that something goes wrong and -# the encodings.utf_8 module is loaded and then unloaded, I don't know why. -# Adding a reference here prevents it from being unloaded. Yuk. diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 463ea8fde..9f9c06559 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -26,7 +26,7 @@ from coverage.debug import info_header, short_stack, write_formatted_info from coverage.exceptions import _BaseCoverageException, _ExceptionDuringRun, NoSource from coverage.execfile import PyRunner -from coverage.results import Numbers, should_fail_under +from coverage.results import display_covered, should_fail_under from coverage.version import __url__ # When adding to this file, alphabetization is important. Look for @@ -760,7 +760,7 @@ def command_line(self, argv: list[str]) -> int: precision = cast(int, self.coverage.get_option("report:precision")) if should_fail_under(total, fail_under, precision): msg = "total of {total} is less than fail-under={fail_under:.{p}f}".format( - total=Numbers(precision=precision).display_covered(total), + total=display_covered(total, precision), fail_under=fail_under, p=precision, ) diff --git a/coverage/config.py b/coverage/config.py index 7a7cd540e..7aa2471bd 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -180,7 +180,7 @@ def __init__(self) -> None: """Initialize the configuration attributes to their defaults.""" # Metadata about the config. # We tried to read these config files. - self.attempted_config_files: list[str] = [] + self.config_files_attempted: list[str] = [] # We did read these config files, but maybe didn't find any content for us. self.config_files_read: list[str] = [] # The file that gave us our configuration. @@ -291,7 +291,7 @@ def from_file(self, filename: str, warn: Callable[[str], None], our_file: bool) else: cp = HandyConfigParser(our_file) - self.attempted_config_files.append(filename) + self.config_files_attempted.append(os.path.abspath(filename)) try: files_read = cp.read(filename) diff --git a/coverage/control.py b/coverage/control.py index 6f7f9a311..dbca2013d 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 @@ -47,7 +48,7 @@ from coverage.python import PythonFileReporter from coverage.report import SummaryReporter from coverage.report_core import render_report -from coverage.results import Analysis +from coverage.results import Analysis, analysis_from_file_reporter from coverage.types import ( FilePath, TConfigurable, TConfigSectionIn, TConfigValueIn, TConfigValueOut, TFileDisposition, TLineNo, TMorf, @@ -930,24 +931,17 @@ def analysis2( analysis.missing_formatted(), ) - def _analyze(self, it: FileReporter | TMorf) -> Analysis: - """Analyze a single morf or code unit. - - Returns an `Analysis` object. - - """ - # All reporting comes through here, so do reporting initialization. + def _analyze(self, morf: TMorf) -> Analysis: + """Analyze a module or file. Private for now.""" self._init() self._post_init() data = self.get_data() - if isinstance(it, FileReporter): - fr = it - else: - fr = self._get_file_reporter(it) - - return Analysis(data, self.config.precision, fr, self._file_mapper) + file_reporter = self._get_file_reporter(morf) + filename = self._file_mapper(file_reporter.filename) + return analysis_from_file_reporter(data, self.config.precision, file_reporter, filename) + @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 @@ -975,11 +969,14 @@ def _get_file_reporter(self, morf: TMorf) -> FileReporter: assert isinstance(file_reporter, FileReporter) return file_reporter - def _get_file_reporters(self, morfs: Iterable[TMorf] | None = None) -> list[FileReporter]: - """Get a list of FileReporters for a list of modules or file names. + def _get_file_reporters( + self, + morfs: Iterable[TMorf] | None = None, + ) -> list[tuple[FileReporter, TMorf]]: + """Get FileReporters for a list of modules or file names. For each module or file name in `morfs`, find a FileReporter. Return - the list of FileReporters. + a list pairing FileReporters with the morfs. If `morfs` is a single module or file name, this returns a list of one FileReporter. If `morfs` is empty or None, then the list of all files @@ -994,8 +991,7 @@ def _get_file_reporters(self, morfs: Iterable[TMorf] | None = None) -> list[File if not isinstance(morfs, (list, tuple, set)): morfs = [morfs] # type: ignore[list-item] - file_reporters = [self._get_file_reporter(morf) for morf in morfs] - return file_reporters + return [(self._get_file_reporter(morf), morf) for morf in morfs] def _prepare_data_for_reporting(self) -> None: """Re-map data before reporting, to get implicit "combine" behavior.""" @@ -1302,7 +1298,7 @@ def plugin_info(plugins: list[Any]) -> list[str]: ("plugins.file_tracers", plugin_info(self._plugins.file_tracers)), ("plugins.configurers", plugin_info(self._plugins.configurers)), ("plugins.context_switchers", plugin_info(self._plugins.context_switchers)), - ("configs_attempted", self.config.attempted_config_files), + ("configs_attempted", self.config.config_files_attempted), ("configs_read", self.config.config_files_read), ("config_file", self.config.config_file), ("config_contents", 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/coverage/html.py b/coverage/html.py index e2bae1d6b..f32ca0a29 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 @@ -14,15 +15,17 @@ import shutil import string -from dataclasses import dataclass -from typing import Any, Iterable, TYPE_CHECKING, cast +from dataclasses import dataclass, field +from typing import Any, Iterable, TYPE_CHECKING import coverage from coverage.data import CoverageData, add_data_to_hash from coverage.exceptions import NoDataError from coverage.files import flat_rootname -from coverage.misc import ensure_dir, file_be_gone, Hasher, isolate_module, format_local_datetime -from coverage.misc import human_sorted, plural, stdout_link +from coverage.misc import ( + ensure_dir, file_be_gone, Hasher, isolate_module, format_local_datetime, + human_sorted, plural, stdout_link, +) from coverage.report_core import get_analysis_to_report from coverage.results import Analysis, Numbers from coverage.templite import Templite @@ -31,24 +34,9 @@ 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 - - class FileInfoDict(TypedDict): - """Summary of the information from last rendering, to avoid duplicate work.""" - hash: str - index: IndexInfoDict - os = isolate_module(os) @@ -80,7 +68,6 @@ class LineData: tokens: list[tuple[str, str]] number: TLineNo category: str - statement: bool contexts: list[str] contexts_label: str context_list: list[str] @@ -101,6 +88,27 @@ class FileData: lines: list[LineData] +@dataclass +class IndexItem: + """Information for each index entry, to render an index page.""" + url: str = "" + file: str = "" + description: str = "" + nums: Numbers = field(default_factory=Numbers) + + +@dataclass +class IndexPage: + """Data for each index page.""" + noun: str + plural: str + filename: str + summaries: list[IndexItem] + totals: Numbers + skipped_covered_count: int + skipped_empty_count: int + + class HtmlDataGeneration: """Generate structured data to be turned into HTML reports.""" @@ -109,21 +117,21 @@ class HtmlDataGeneration: def __init__(self, cov: Coverage) -> None: self.coverage = cov self.config = self.coverage.config - data = self.coverage.get_data() - self.has_arcs = data.has_arcs() + self.data = self.coverage.get_data() + self.has_arcs = self.data.has_arcs() if self.config.show_contexts: - if data.measured_contexts() == {""}: + if self.data.measured_contexts() == {""}: self.coverage._warn("No contexts were measured") - data.set_query_contexts(self.config.report_contexts) + self.data.set_query_contexts(self.config.report_contexts) def data_for_file(self, fr: FileReporter, analysis: Analysis) -> FileData: """Produce the data needed for one file's report.""" if self.has_arcs: missing_branch_arcs = analysis.missing_branch_arcs() - arcs_executed = analysis.arcs_executed() + arcs_executed = analysis.arcs_executed if self.config.show_contexts: - contexts_by_lineno = analysis.data.contexts_by_lineno(analysis.filename) + contexts_by_lineno = self.data.contexts_by_lineno(analysis.filename) lines = [] @@ -163,7 +171,6 @@ def data_for_file(self, fr: FileReporter, analysis: Analysis) -> FileData: tokens=tokens, number=lineno, category=category, - statement=(lineno in analysis.statements), contexts=contexts, contexts_label=contexts_label, context_list=context_list, @@ -187,6 +194,7 @@ def __init__(self, fr: FileReporter, analysis: Analysis) -> None: self.analysis = analysis self.rootname = flat_rootname(fr.relative_filename()) self.html_filename = self.rootname + ".html" + self.prev_html = self.next_html = "" HTML_SAFE = string.ascii_letters + string.digits + "!#$%'()*+,-./:;=?@[]^_`{|}~" @@ -228,8 +236,6 @@ def __init__(self, cov: Coverage) -> None: self.skip_empty = self.config.html_skip_empty if self.skip_empty is None: self.skip_empty = self.config.skip_empty - self.skipped_covered_count = 0 - self.skipped_empty_count = 0 title = self.config.html_title @@ -242,11 +248,11 @@ def __init__(self, cov: Coverage) -> None: self.data = self.coverage.get_data() self.has_arcs = self.data.has_arcs() - self.file_summaries: list[IndexInfoDict] = [] - self.all_files_nums: list[Numbers] = [] + self.index_pages: dict[str, IndexPage] = { + "file": self.new_index_page("file", "files"), + } self.incr = IncrementalChecker(self.directory) self.datagen = HtmlDataGeneration(self.coverage) - self.totals = Numbers(precision=self.config.precision) self.directory_was_empty = False self.first_fr = None self.final_fr = None @@ -275,9 +281,22 @@ def __init__(self, cov: Coverage) -> None: "run": "run", }, } + self.index_tmpl = Templite(read_data("index.html"), self.template_globals) self.pyfile_html_source = read_data("pyfile.html") self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals) + def new_index_page(self, noun: str, plural_noun: str) -> IndexPage: + """Create an IndexPage for a kind of region.""" + return IndexPage( + noun=noun, + plural=plural_noun, + filename="index.html" if noun == "file" else f"{noun}_index.html", + summaries=[], + totals=Numbers(precision=self.config.precision), + skipped_covered_count=0, + skipped_empty_count=0, + ) + def report(self, morfs: Iterable[TMorf] | None) -> float: """Generate an HTML report for `morfs`. @@ -293,40 +312,47 @@ def report(self, morfs: Iterable[TMorf] | None) -> float: # to the next and previous page. files_to_report = [] + have_data = False for fr, analysis in get_analysis_to_report(self.coverage, morfs): + have_data = True ftr = FileToReport(fr, analysis) - should = self.should_report_file(ftr) - if should: + if self.should_report(analysis, self.index_pages["file"]): files_to_report.append(ftr) else: file_be_gone(os.path.join(self.directory, ftr.html_filename)) - for i, ftr in enumerate(files_to_report): - if i == 0: - prev_html = "index.html" - else: - prev_html = files_to_report[i - 1].html_filename - if i == len(files_to_report) - 1: - next_html = "index.html" - else: - next_html = files_to_report[i + 1].html_filename - self.write_html_file(ftr, prev_html, next_html) - - if not self.all_files_nums: + if not have_data: raise NoDataError("No data to report.") - self.totals = cast(Numbers, sum(self.all_files_nums)) - - # Write the index file. + if files_to_report: + for ftr1, ftr2 in zip(files_to_report[:-1], files_to_report[1:]): + ftr1.next_html = ftr2.html_filename + ftr2.prev_html = ftr1.html_filename + files_to_report[0].prev_html = "index.html" + files_to_report[-1].next_html = "index.html" + + for ftr in files_to_report: + self.write_html_page(ftr) + for noun, plural_noun in ftr.fr.code_region_kinds(): + if noun not in self.index_pages: + self.index_pages[noun] = self.new_index_page(noun, plural_noun) + + # Write the index page. if files_to_report: first_html = files_to_report[0].html_filename final_html = files_to_report[-1].html_filename else: first_html = final_html = "index.html" - self.index_file(first_html, final_html) + self.write_file_index_page(first_html, final_html) + + # Write function and class index pages. + self.write_region_index_pages(files_to_report) self.make_local_static_report_files() - return self.totals.n_statements and self.totals.pc_covered + return ( + self.index_pages["file"].totals.n_statements + and self.index_pages["file"].totals.pc_covered + ) def make_directory(self) -> None: """Make sure our htmlcov directory exists.""" @@ -352,39 +378,44 @@ def make_local_static_report_files(self) -> None: assert self.config.extra_css is not None shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css)) - def should_report_file(self, ftr: FileToReport) -> bool: - """Determine if we'll report this file.""" + def should_report(self, analysis: Analysis, index_page: IndexPage) -> bool: + """Determine if we'll report this file or region.""" # Get the numbers for this file. - nums = ftr.analysis.numbers - self.all_files_nums.append(nums) + nums = analysis.numbers + index_page.totals += nums if self.skip_covered: # Don't report on 100% files. no_missing_lines = (nums.n_missing == 0) no_missing_branches = (nums.n_partial_branches == 0) if no_missing_lines and no_missing_branches: - # If there's an existing file, remove it. - self.skipped_covered_count += 1 + index_page.skipped_covered_count += 1 return False if self.skip_empty: # Don't report on empty files. if nums.n_statements == 0: - self.skipped_empty_count += 1 + index_page.skipped_empty_count += 1 return False return True - def write_html_file(self, ftr: FileToReport, prev_html: str, next_html: str) -> None: - """Generate an HTML file for one source file.""" + def write_html_page(self, ftr: FileToReport) -> None: + """Generate an HTML page for one source file. + + If the page on disk is already correct based on our incremental status + checking, then the page doesn't have to be generated, and this function + only does page summary bookkeeping. + + """ self.make_directory() - # Find out if the file on disk is already correct. + # Find out if the page on disk is already correct. if self.incr.can_skip_file(self.data, ftr.fr, ftr.rootname): - self.file_summaries.append(self.incr.index_info(ftr.rootname)) + self.index_pages["file"].summaries.append(self.incr.index_info(ftr.rootname)) return - # Write the HTML page for this file. + # Write the HTML page for this source file. file_data = self.datagen.data_for_file(ftr.fr, ftr.analysis) contexts = collections.Counter(c for cline in file_data.lines for c in cline.contexts) @@ -455,151 +486,255 @@ def write_html_file(self, ftr: FileToReport, prev_html: str, next_html: str) -> html = self.source_tmpl.render({ **file_data.__dict__, "contexts_json": contexts_json, - "prev_html": prev_html, - "next_html": next_html, + "prev_html": ftr.prev_html, + "next_html": ftr.next_html, }) write_html(html_path, html) - # Save this file's information for the index file. - index_info: IndexInfoDict = { - "nums": ftr.analysis.numbers, - "html_filename": ftr.html_filename, - "relative_filename": ftr.fr.relative_filename(), - } - self.file_summaries.append(index_info) + # Save this file's information for the index page. + index_info = IndexItem( + url = ftr.html_filename, + file = escape(ftr.fr.relative_filename()), + nums = ftr.analysis.numbers, + ) + self.index_pages["file"].summaries.append(index_info) self.incr.set_index_info(ftr.rootname, index_info) - def index_file(self, first_html: str, final_html: str) -> None: - """Write the index.html file for this report.""" + def write_file_index_page(self, first_html: str, final_html: str) -> None: + """Write the file index page for this report.""" self.make_directory() - index_tmpl = Templite(read_data("index.html"), self.template_globals) + index_file = self.write_index_page( + self.index_pages["file"], + first_html=first_html, + final_html=final_html, + ) + + print_href = stdout_link(index_file, f"file://{os.path.abspath(index_file)}") + self.coverage._message(f"Wrote HTML report to {print_href}") + + # Write the latest hashes for next time. + self.incr.write() + + def write_region_index_pages(self, files_to_report: Iterable[FileToReport]) -> None: + """Write the other index pages for this report.""" + for ftr in files_to_report: + region_nouns = [pair[0] for pair in ftr.fr.code_region_kinds()] + num_lines = len(ftr.fr.source().splitlines()) + outside_lines = set(range(1, num_lines + 1)) + regions = ftr.fr.code_regions() + + for noun in region_nouns: + page_data = self.index_pages[noun] + + for region in regions: + if region.kind != noun: + continue + outside_lines -= region.lines + analysis = ftr.analysis.narrow(region.lines) + if not self.should_report(analysis, page_data): + continue + sorting_name = region.name.rpartition(".")[-1].lstrip("_") + page_data.summaries.append(IndexItem( + url=f"{ftr.html_filename}#t{region.start}", + file=escape(ftr.fr.relative_filename()), + description=( + f"" + + escape(region.name) + + "" + ), + nums=analysis.numbers, + )) + + analysis = ftr.analysis.narrow(outside_lines) + if self.should_report(analysis, page_data): + page_data.summaries.append(IndexItem( + url=ftr.html_filename, + file=escape(ftr.fr.relative_filename()), + description=( + "" + + f"(no {escape(noun)})" + + "" + ), + nums=analysis.numbers, + )) + + for noun, index_page in self.index_pages.items(): + if noun != "file": + self.write_index_page(index_page) + + def write_index_page(self, index_page: IndexPage, **kwargs: str) -> str: + """Write an index page specified by `index_page`. + + Returns the filename created. + """ skipped_covered_msg = skipped_empty_msg = "" - if self.skipped_covered_count: - n = self.skipped_covered_count - skipped_covered_msg = f"{n} file{plural(n)} skipped due to complete coverage." - if self.skipped_empty_count: - n = self.skipped_empty_count - skipped_empty_msg = f"{n} empty file{plural(n)} skipped." - - html = index_tmpl.render({ - "files": self.file_summaries, - "totals": self.totals, + if n := index_page.skipped_covered_count: + word = plural(n, index_page.noun, index_page.plural) + skipped_covered_msg = f"{n} {word} skipped due to complete coverage." + if n := index_page.skipped_empty_count: + word = plural(n, index_page.noun, index_page.plural) + skipped_empty_msg = f"{n} empty {word} skipped." + + index_buttons = [ + { + "label": ip.plural.title(), + "url": ip.filename if ip.noun != index_page.noun else "", + "current": ip.noun == index_page.noun, + } + for ip in self.index_pages.values() + ] + render_data = { + "regions": index_page.summaries, + "totals": index_page.totals, + "noun": index_page.noun, + "column2": index_page.noun if index_page.noun != "file" else "", + "skip_covered": self.skip_covered, "skipped_covered_msg": skipped_covered_msg, "skipped_empty_msg": skipped_empty_msg, - "first_html": first_html, - "final_html": final_html, - }) + "first_html": "", + "final_html": "", + "index_buttons": index_buttons, + } + render_data.update(kwargs) + html = self.index_tmpl.render(render_data) - index_file = os.path.join(self.directory, "index.html") + index_file = os.path.join(self.directory, index_page.filename) write_html(index_file, html) + return index_file - print_href = stdout_link(index_file, f"file://{os.path.abspath(index_file)}") - self.coverage._message(f"Wrote HTML report to {print_href}") - # Write the latest hashes for next time. - self.incr.write() +@dataclass +class FileInfo: + """Summary of the information from last rendering, to avoid duplicate work.""" + hash: str = "" + index: IndexItem = field(default_factory=IndexItem) class IncrementalChecker: - """Logic and data to support incremental reporting.""" + """Logic and data to support incremental reporting. + + When generating an HTML report, often only a few of the source files have + changed since the last time we made the HTML report. This means previously + created HTML pages can be reused without generating them again, speeding + the command. + + This class manages a JSON data file that captures enough information to + know whether an HTML page for a .py file needs to be regenerated or not. + The data file also needs to store all the information needed to create the + entry for the file on the index page so that if the HTML page is reused, + the index page can still be created to refer to it. + + The data looks like:: + + { + "note": "This file is an internal implementation detail ...", + // A fixed number indicating the data format. STATUS_FORMAT + "format": 5, + // The version of coverage.py + "version": "7.4.4", + // A hash of a number of global things, including the configuration + // settings and the pyfile.html template itself. + "globals": "540ee119c15d52a68a53fe6f0897346d", + "files": { + // An entry for each source file keyed by the flat_rootname(). + "z_7b071bdc2a35fa80___init___py": { + // Hash of the source, the text of the .py file. + "hash": "e45581a5b48f879f301c0f30bf77a50c", + // Information for the index.html file. + "index": { + "url": "z_7b071bdc2a35fa80___init___py.html", + "file": "cogapp/__init__.py", + "description": "", + // The Numbers for this file. + "nums": { "precision": 2, "n_files": 1, "n_statements": 43, ... } + } + }, + ... + } + } + + """ STATUS_FILE = "status.json" - STATUS_FORMAT = 2 + STATUS_FORMAT = 5 NOTE = ( "This file is an internal implementation detail to speed up HTML report" + " generation. Its format can change at any time. You might be looking" + " for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json" ) - # The data looks like: - # - # { - # "format": 2, - # "globals": "540ee119c15d52a68a53fe6f0897346d", - # "version": "4.0a1", - # "files": { - # "cogapp___init__": { - # "hash": "e45581a5b48f879f301c0f30bf77a50c", - # "index": { - # "html_filename": "cogapp___init__.html", - # "relative_filename": "cogapp/__init__", - # "nums": [ 1, 14, 0, 0, 0, 0, 0 ] - # } - # }, - # ... - # "cogapp_whiteutils": { - # "hash": "8504bb427fc488c4176809ded0277d51", - # "index": { - # "html_filename": "cogapp_whiteutils.html", - # "relative_filename": "cogapp/whiteutils", - # "nums": [ 1, 59, 0, 1, 28, 2, 2 ] - # } - # } - # } - # } - def __init__(self, directory: str) -> None: self.directory = directory - self.reset() + self._reset() - def reset(self) -> None: + def _reset(self) -> None: """Initialize to empty. Causes all files to be reported.""" self.globals = "" - self.files: dict[str, FileInfoDict] = {} + self.files: dict[str, FileInfo] = {} def read(self) -> None: """Read the information we stored last time.""" - usable = False try: status_file = os.path.join(self.directory, self.STATUS_FILE) with open(status_file) as fstatus: status = json.load(fstatus) except (OSError, ValueError): + # Status file is missing or malformed. usable = False else: - usable = True if status["format"] != self.STATUS_FORMAT: usable = False elif status["version"] != coverage.__version__: usable = False + else: + usable = True if usable: self.files = {} - for filename, fileinfo in status["files"].items(): - fileinfo["index"]["nums"] = Numbers(*fileinfo["index"]["nums"]) + for filename, filedict in status["files"].items(): + indexdict = filedict["index"] + index_item = IndexItem(**indexdict) + index_item.nums = Numbers(**indexdict["nums"]) + fileinfo = FileInfo( + hash=filedict["hash"], + index=index_item, + ) self.files[filename] = fileinfo self.globals = status["globals"] else: - self.reset() + self._reset() def write(self) -> None: """Write the current status.""" status_file = os.path.join(self.directory, self.STATUS_FILE) - files = {} - for filename, fileinfo in self.files.items(): - index = fileinfo["index"] - index["nums"] = index["nums"].init_args() # type: ignore[typeddict-item] - files[filename] = fileinfo - - status = { + status_data = { "note": self.NOTE, "format": self.STATUS_FORMAT, "version": coverage.__version__, "globals": self.globals, - "files": files, + "files": { + fname: dataclasses.asdict(finfo) + for fname, finfo in self.files.items() + }, } with open(status_file, "w") as fout: - json.dump(status, fout, separators=(",", ":")) + json.dump(status_data, fout, separators=(",", ":")) def check_global_data(self, *data: Any) -> None: - """Check the global data that can affect incremental reporting.""" + """Check the global data that can affect incremental reporting. + + Pass in whatever global information could affect the content of the + HTML pages. If the global data has changed since last time, this will + clear the data so that all files are regenerated. + + """ m = Hasher() for d in data: m.update(d) these_globals = m.hexdigest() if self.globals != these_globals: - self.reset() + self._reset() self.globals = these_globals def can_skip_file(self, data: CoverageData, fr: FileReporter, rootname: str) -> bool: @@ -607,36 +742,33 @@ def can_skip_file(self, data: CoverageData, fr: FileReporter, rootname: str) -> `data` is a CoverageData object, `fr` is a `FileReporter`, and `rootname` is the name being used for the file. + + Returns True if the HTML page is fine as-is, False if we need to recreate + the HTML page. + """ m = Hasher() m.update(fr.source().encode("utf-8")) add_data_to_hash(data, fr.filename, m) this_hash = m.hexdigest() - that_hash = self.file_hash(rootname) + file_info = self.files.setdefault(rootname, FileInfo()) - if this_hash == that_hash: + if this_hash == file_info.hash: # Nothing has changed to require the file to be reported again. return True else: - self.set_file_hash(rootname, this_hash) + # File has changed, record the latest hash and force regeneration. + file_info.hash = this_hash return False - def file_hash(self, fname: str) -> str: - """Get the hash of `fname`'s contents.""" - return self.files.get(fname, {}).get("hash", "") # type: ignore[call-overload] - - def set_file_hash(self, fname: str, val: str) -> None: - """Set the hash of `fname`'s contents.""" - self.files.setdefault(fname, {})["hash"] = val # type: ignore[typeddict-item] - - def index_info(self, fname: str) -> IndexInfoDict: + def index_info(self, fname: str) -> IndexItem: """Get the information for index.html for `fname`.""" - return self.files.get(fname, {}).get("index", {}) # type: ignore + return self.files.get(fname, FileInfo()).index - def set_index_info(self, fname: str, info: IndexInfoDict) -> None: + def set_index_info(self, fname: str, info: IndexItem) -> None: """Set the information for index.html for `fname`.""" - self.files.setdefault(fname, {})["index"] = info # type: ignore[typeddict-item] + self.files.setdefault(fname, FileInfo()).index = info # Helpers for templates and generating HTML diff --git a/coverage/htmlfiles/coverage_html.js b/coverage/htmlfiles/coverage_html.js index 593488286..a28c1bef8 100644 --- a/coverage/htmlfiles/coverage_html.js +++ b/coverage/htmlfiles/coverage_html.js @@ -36,11 +36,12 @@ function on_click(sel, fn) { function getCellValue(row, column = 0) { const cell = row.cells[column] // nosemgrep: eslint.detect-object-injection if (cell.childElementCount == 1) { - const child = cell.firstElementChild - if (child instanceof HTMLTimeElement && child.dateTime) { - return child.dateTime - } else if (child instanceof HTMLDataElement && child.value) { - return child.value + var child = cell.firstElementChild; + if (child.tagName === "A") { + child = child.firstElementChild; + } + if (child instanceof HTMLDataElement && child.value) { + return child.value; } } return cell.innerText || cell.textContent; @@ -50,28 +51,37 @@ function rowComparator(rowA, rowB, column = 0) { let valueA = getCellValue(rowA, column); let valueB = getCellValue(rowB, column); if (!isNaN(valueA) && !isNaN(valueB)) { - return valueA - valueB + return valueA - valueB; } return valueA.localeCompare(valueB, undefined, {numeric: true}); } function sortColumn(th) { // Get the current sorting direction of the selected header, - // clear state on other headers and then set the new sorting direction + // clear state on other headers and then set the new sorting direction. const currentSortOrder = th.getAttribute("aria-sort"); [...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none")); + var direction; if (currentSortOrder === "none") { - th.setAttribute("aria-sort", th.dataset.defaultSortOrder || "ascending"); - } else { - th.setAttribute("aria-sort", currentSortOrder === "ascending" ? "descending" : "ascending"); + direction = th.dataset.defaultSortOrder || "ascending"; + } + else if (currentSortOrder === "ascending") { + direction = "descending"; + } + else { + direction = "ascending"; } + th.setAttribute("aria-sort", direction); const column = [...th.parentElement.cells].indexOf(th) - // Sort all rows and afterwards append them in order to move them in the DOM + // Sort all rows and afterwards append them in order to move them in the DOM. Array.from(th.closest("table").querySelectorAll("tbody tr")) - .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (th.getAttribute("aria-sort") === "ascending" ? 1 : -1)) - .forEach(tr => tr.parentElement.appendChild(tr) ); + .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (direction === "ascending" ? 1 : -1)) + .forEach(tr => tr.parentElement.appendChild(tr)); + + // Save the sort order for next time. + localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({column, direction})); } // Find all the elements with data-shortcut attribute, and use them to assign a shortcut key. @@ -96,15 +106,40 @@ coverage.wire_up_filter = function () { const no_rows = document.getElementById("no_rows"); // Observe filter keyevents. - document.getElementById("filter").addEventListener("input", debounce(event => { + const filter_handler = (event => { // Keep running total of each metric, first index contains number of shown rows const totals = new Array(table.rows[0].cells.length).fill(0); // Accumulate the percentage as fraction totals[totals.length - 1] = { "numer": 0, "denom": 0 }; // nosemgrep: eslint.detect-object-injection + var text = document.getElementById("filter").value; + const casefold = (text === text.toLowerCase()); + const hide100 = document.getElementById("hide100").checked; + // Hide / show elements. table_body_rows.forEach(row => { - if (!row.cells[0].textContent.includes(event.target.value)) { + var show = false; + // Check the text filter. + for (let column = 0; column < totals.length; column++) { + cell = row.cells[column]; + if (cell.classList.contains("name")) { + var celltext = cell.textContent; + if (casefold) { + celltext = celltext.toLowerCase(); + } + if (celltext.includes(text)) { + show = true; + } + } + } + + // Check the "hide covered" filter. + if (show && hide100) { + const [numer, denom] = row.cells[row.cells.length - 1].dataset.ratio.split(" "); + show = (numer !== denom); + } + + if (!show) { // hide row.classList.add("hidden"); return; @@ -114,15 +149,19 @@ coverage.wire_up_filter = function () { row.classList.remove("hidden"); totals[0]++; - for (let column = 1; column < totals.length; column++) { + for (let column = 0; column < totals.length; column++) { // Accumulate dynamic totals cell = row.cells[column] // nosemgrep: eslint.detect-object-injection + if (cell.classList.contains("name")) { + continue; + } if (column === totals.length - 1) { // Last column contains percentage const [numer, denom] = cell.dataset.ratio.split(" "); totals[column]["numer"] += parseInt(numer, 10); // nosemgrep: eslint.detect-object-injection totals[column]["denom"] += parseInt(denom, 10); // nosemgrep: eslint.detect-object-injection - } else { + } + else { totals[column] += parseInt(cell.textContent, 10); // nosemgrep: eslint.detect-object-injection } } @@ -142,9 +181,12 @@ coverage.wire_up_filter = function () { const footer = table.tFoot.rows[0]; // Calculate new dynamic sum values based on visible rows. - for (let column = 1; column < totals.length; column++) { + for (let column = 0; column < totals.length; column++) { // Get footer cell element. const cell = footer.cells[column]; // nosemgrep: eslint.detect-object-injection + if (cell.classList.contains("name")) { + continue; + } // Set value into dynamic footer cell element. if (column === totals.length - 1) { @@ -158,48 +200,47 @@ coverage.wire_up_filter = function () { cell.textContent = denom ? `${(numer * 100 / denom).toFixed(places)}%` : `${(100).toFixed(places)}%`; - } else { + } + else { cell.textContent = totals[column]; // nosemgrep: eslint.detect-object-injection } } - })); + }); + + document.getElementById("filter").addEventListener("input", debounce(filter_handler)); + document.getElementById("hide100").addEventListener("input", debounce(filter_handler)); // Trigger change event on setup, to force filter on page refresh // (filter value may still be present). document.getElementById("filter").dispatchEvent(new Event("input")); + document.getElementById("hide100").dispatchEvent(new Event("input")); }; -coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; - -// Loaded on index.html -coverage.index_ready = function () { - coverage.assign_shortkeys(); - coverage.wire_up_filter(); +// Set up the click-to-sort columns. +coverage.wire_up_sorting = function () { document.querySelectorAll("[data-sortable] th[aria-sort]").forEach( th => th.addEventListener("click", e => sortColumn(e.target)) ); // Look for a localStorage item containing previous sort settings: + var column = 0, direction = "ascending"; const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); - if (stored_list) { - const {column, direction} = JSON.parse(stored_list); - const th = document.querySelector("[data-sortable]").tHead.rows[0].cells[column]; // nosemgrep: eslint.detect-object-injection - th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); - th.click() + ({column, direction} = JSON.parse(stored_list)); } - // Watch for page unload events so we can save the final sort settings: - window.addEventListener("unload", function () { - const th = document.querySelector('[data-sortable] th[aria-sort="ascending"], [data-sortable] [aria-sort="descending"]'); - if (!th) { - return; - } - localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({ - column: [...th.parentElement.cells].indexOf(th), - direction: th.getAttribute("aria-sort"), - })); - }); + const th = document.querySelector("[data-sortable]").tHead.rows[0].cells[column]; // nosemgrep: eslint.detect-object-injection + th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); + th.click() +}; + +coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; + +// Loaded on index.html +coverage.index_ready = function () { + coverage.assign_shortkeys(); + coverage.wire_up_filter(); + coverage.wire_up_sorting(); on_click(".button_prev_file", coverage.to_prev_file); on_click(".button_next_file", coverage.to_next_file); @@ -217,7 +258,8 @@ coverage.pyfile_ready = function () { if (frag.length > 2 && frag[1] === "t") { document.querySelector(frag).closest(".n").classList.add("highlight"); coverage.set_sel(parseInt(frag.substr(2), 10)); - } else { + } + else { coverage.set_sel(0); } @@ -441,7 +483,8 @@ coverage.to_next_chunk_nicely = function () { if (line.parentElement !== document.getElementById("source")) { // The element is not a source line but the header or similar coverage.select_line_or_chunk(1); - } else { + } + else { // We extract the line number from the id coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); } @@ -460,7 +503,8 @@ coverage.to_prev_chunk_nicely = function () { if (line.parentElement !== document.getElementById("source")) { // The element is not a source line but the header or similar coverage.select_line_or_chunk(coverage.lines_len); - } else { + } + else { // We extract the line number from the id coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); } @@ -562,7 +606,8 @@ coverage.build_scroll_markers = function () { if (line_number === previous_line + 1) { // If this solid missed block just make previous mark higher. last_mark.style.height = `${line_top + line_height - last_top}px`; - } else { + } + else { // Add colored line in scroll_marker block. last_mark = document.createElement("div"); last_mark.id = `m${line_number}`; @@ -590,7 +635,8 @@ coverage.wire_up_sticky_header = function () { function updateHeader() { if (window.scrollY > header_bottom) { header.classList.add("sticky"); - } else { + } + else { header.classList.remove("sticky"); } } @@ -618,7 +664,8 @@ coverage.expand_contexts = function (e) { document.addEventListener("DOMContentLoaded", () => { if (document.body.classList.contains("indexfile")) { coverage.index_ready(); - } else { + } + else { coverage.pyfile_ready(); } }); diff --git a/coverage/htmlfiles/index.html b/coverage/htmlfiles/index.html index bde46eafe..69d4b19ed 100644 --- a/coverage/htmlfiles/index.html +++ b/coverage/htmlfiles/index.html @@ -2,7 +2,7 @@ {# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #} - + {{ title|escape }} @@ -11,7 +11,7 @@ {% if extra_css %} {% endif %} - + @@ -24,13 +24,16 @@

{{ title|escape }}:
- + +
+ + +
+

+ {% for ibtn in index_buttons %} + {{ ibtn.label }}{#-#} + {% endfor %} +

+

coverage.py v{{__version__}}, created at {{ time_stamp }} @@ -67,37 +80,46 @@

{{ title|escape }}:
- {# The title="" attr doesn"t work in Safari. #} + {# The title="" attr doesn't work in Safari. #} - - - - + + {% if column2 %} + + {% endif %} + + + {% if has_arcs %} - - + + {% endif %} - + - {% for file in files %} - - - - - + {% for region in regions %} + + + {% if column2 %} + + {% endif %} + + + {% if has_arcs %} - - + + {% endif %} - + {% endfor %} + {% if column2 %} + + {% endif %} @@ -130,11 +152,11 @@

{{ title|escape }}:

diff --git a/coverage/htmlfiles/pyfile.html b/coverage/htmlfiles/pyfile.html index bc8fa697d..b7aa6ba83 100644 --- a/coverage/htmlfiles/pyfile.html +++ b/coverage/htmlfiles/pyfile.html @@ -2,7 +2,7 @@ {# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #} - + Coverage for {{relative_filename|escape}}: {{nums.pc_covered_str}}% @@ -18,7 +18,7 @@ {% endif %} - + @@ -32,7 +32,7 @@

ModulestatementsmissingexcludedFile{{ column2 }}statementsmissingexcludedbranchespartialbranchespartialcoveragecoverage
{{file.relative_filename}}{{file.nums.n_statements}}{{file.nums.n_missing}}{{file.nums.n_excluded}}
{{region.file}}{{region.description}}{{region.nums.n_statements}}{{region.nums.n_missing}}{{region.nums.n_excluded}}{{file.nums.n_branches}}{{file.nums.n_partial_branches}}{{region.nums.n_branches}}{{region.nums.n_partial_branches}}{{file.nums.pc_covered_str}}%{{region.nums.pc_covered_str}}%
Total {{totals.n_statements}} {{totals.n_missing}} {{totals.n_excluded}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedbranchespartialcoverage
cogapp/__init__.py(no class)10000100.00%
cogapp/__main__.py(no class)330000.00%
cogapp/cogapp.pyCogError30020100.00%
cogapp/cogapp.pyCogUsageError00000100.00%
cogapp/cogapp.pyCogInternalError00000100.00%
cogapp/cogapp.pyCogGeneratedError00000100.00%
cogapp/cogapp.pyCogUserException00000100.00%
cogapp/cogapp.pyCogCheckFailed00000100.00%
cogapp/cogapp.pyCogGenerator586030488.64%
cogapp/cogapp.pyCogOptions8058144017.74%
cogapp/cogapp.pyCog25615501242236.05%
cogapp/cogapp.py(no class)710000100.00%
cogapp/makefiles.py(no class)40000100.00%
cogapp/test_cogapp.pyCogTestsInMemory730020100.00%
cogapp/test_cogapp.pyCogOptionsTests31310000.00%
cogapp/test_cogapp.pyFileStructureTests29290000.00%
cogapp/test_cogapp.pyCogErrorTests11110000.00%
cogapp/test_cogapp.pyCogGeneratorGetCodeTests37370000.00%
cogapp/test_cogapp.pyTestCaseWithTempDir19190000.00%
cogapp/test_cogapp.pyArgumentHandlingTests43430000.00%
cogapp/test_cogapp.pyTestMain27270000.00%
cogapp/test_cogapp.pyTestFileHandling73730200.00%
cogapp/test_cogapp.pyCogTestLineEndings12120000.00%
cogapp/test_cogapp.pyCogTestCharacterEncoding12120000.00%
cogapp/test_cogapp.pyTestCaseWithImports660400.00%
cogapp/test_cogapp.pyCogIncludeTests46460000.00%
cogapp/test_cogapp.pyCogTestsInFiles1221222800.00%
cogapp/test_cogapp.pyCheckTests40400600.00%
cogapp/test_cogapp.pyWritabilityTests19190000.00%
cogapp/test_cogapp.pyChecksumTests30300000.00%
cogapp/test_cogapp.pyCustomMarkerTests12120000.00%
cogapp/test_cogapp.pyBlakeTests15150000.00%
cogapp/test_cogapp.pyErrorCallTests12120000.00%
cogapp/test_cogapp.py(no class)185202198.40%
cogapp/test_makefiles.pySimpleTests51510600.00%
cogapp/test_makefiles.py(no class)170000100.00%
cogapp/test_whiteutils.pyWhitePrefixTests17170000.00%
cogapp/test_whiteutils.pyReindentBlockTests21210000.00%
cogapp/test_whiteutils.pyCommonPrefixTests12120000.00%
cogapp/test_whiteutils.py(no class)180000100.00%
cogapp/utils.pyRedirectable8304258.33%
cogapp/utils.pyNumberedFileReader70020100.00%
cogapp/utils.py(no class)170000100.00%
cogapp/whiteutils.py(no class)40000100.00%
Total 150292432362937.34%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/doc/sample_html/coverage_html.js b/doc/sample_html/coverage_html.js index 593488286..a28c1bef8 100644 --- a/doc/sample_html/coverage_html.js +++ b/doc/sample_html/coverage_html.js @@ -36,11 +36,12 @@ function on_click(sel, fn) { function getCellValue(row, column = 0) { const cell = row.cells[column] // nosemgrep: eslint.detect-object-injection if (cell.childElementCount == 1) { - const child = cell.firstElementChild - if (child instanceof HTMLTimeElement && child.dateTime) { - return child.dateTime - } else if (child instanceof HTMLDataElement && child.value) { - return child.value + var child = cell.firstElementChild; + if (child.tagName === "A") { + child = child.firstElementChild; + } + if (child instanceof HTMLDataElement && child.value) { + return child.value; } } return cell.innerText || cell.textContent; @@ -50,28 +51,37 @@ function rowComparator(rowA, rowB, column = 0) { let valueA = getCellValue(rowA, column); let valueB = getCellValue(rowB, column); if (!isNaN(valueA) && !isNaN(valueB)) { - return valueA - valueB + return valueA - valueB; } return valueA.localeCompare(valueB, undefined, {numeric: true}); } function sortColumn(th) { // Get the current sorting direction of the selected header, - // clear state on other headers and then set the new sorting direction + // clear state on other headers and then set the new sorting direction. const currentSortOrder = th.getAttribute("aria-sort"); [...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none")); + var direction; if (currentSortOrder === "none") { - th.setAttribute("aria-sort", th.dataset.defaultSortOrder || "ascending"); - } else { - th.setAttribute("aria-sort", currentSortOrder === "ascending" ? "descending" : "ascending"); + direction = th.dataset.defaultSortOrder || "ascending"; + } + else if (currentSortOrder === "ascending") { + direction = "descending"; + } + else { + direction = "ascending"; } + th.setAttribute("aria-sort", direction); const column = [...th.parentElement.cells].indexOf(th) - // Sort all rows and afterwards append them in order to move them in the DOM + // Sort all rows and afterwards append them in order to move them in the DOM. Array.from(th.closest("table").querySelectorAll("tbody tr")) - .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (th.getAttribute("aria-sort") === "ascending" ? 1 : -1)) - .forEach(tr => tr.parentElement.appendChild(tr) ); + .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (direction === "ascending" ? 1 : -1)) + .forEach(tr => tr.parentElement.appendChild(tr)); + + // Save the sort order for next time. + localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({column, direction})); } // Find all the elements with data-shortcut attribute, and use them to assign a shortcut key. @@ -96,15 +106,40 @@ coverage.wire_up_filter = function () { const no_rows = document.getElementById("no_rows"); // Observe filter keyevents. - document.getElementById("filter").addEventListener("input", debounce(event => { + const filter_handler = (event => { // Keep running total of each metric, first index contains number of shown rows const totals = new Array(table.rows[0].cells.length).fill(0); // Accumulate the percentage as fraction totals[totals.length - 1] = { "numer": 0, "denom": 0 }; // nosemgrep: eslint.detect-object-injection + var text = document.getElementById("filter").value; + const casefold = (text === text.toLowerCase()); + const hide100 = document.getElementById("hide100").checked; + // Hide / show elements. table_body_rows.forEach(row => { - if (!row.cells[0].textContent.includes(event.target.value)) { + var show = false; + // Check the text filter. + for (let column = 0; column < totals.length; column++) { + cell = row.cells[column]; + if (cell.classList.contains("name")) { + var celltext = cell.textContent; + if (casefold) { + celltext = celltext.toLowerCase(); + } + if (celltext.includes(text)) { + show = true; + } + } + } + + // Check the "hide covered" filter. + if (show && hide100) { + const [numer, denom] = row.cells[row.cells.length - 1].dataset.ratio.split(" "); + show = (numer !== denom); + } + + if (!show) { // hide row.classList.add("hidden"); return; @@ -114,15 +149,19 @@ coverage.wire_up_filter = function () { row.classList.remove("hidden"); totals[0]++; - for (let column = 1; column < totals.length; column++) { + for (let column = 0; column < totals.length; column++) { // Accumulate dynamic totals cell = row.cells[column] // nosemgrep: eslint.detect-object-injection + if (cell.classList.contains("name")) { + continue; + } if (column === totals.length - 1) { // Last column contains percentage const [numer, denom] = cell.dataset.ratio.split(" "); totals[column]["numer"] += parseInt(numer, 10); // nosemgrep: eslint.detect-object-injection totals[column]["denom"] += parseInt(denom, 10); // nosemgrep: eslint.detect-object-injection - } else { + } + else { totals[column] += parseInt(cell.textContent, 10); // nosemgrep: eslint.detect-object-injection } } @@ -142,9 +181,12 @@ coverage.wire_up_filter = function () { const footer = table.tFoot.rows[0]; // Calculate new dynamic sum values based on visible rows. - for (let column = 1; column < totals.length; column++) { + for (let column = 0; column < totals.length; column++) { // Get footer cell element. const cell = footer.cells[column]; // nosemgrep: eslint.detect-object-injection + if (cell.classList.contains("name")) { + continue; + } // Set value into dynamic footer cell element. if (column === totals.length - 1) { @@ -158,48 +200,47 @@ coverage.wire_up_filter = function () { cell.textContent = denom ? `${(numer * 100 / denom).toFixed(places)}%` : `${(100).toFixed(places)}%`; - } else { + } + else { cell.textContent = totals[column]; // nosemgrep: eslint.detect-object-injection } } - })); + }); + + document.getElementById("filter").addEventListener("input", debounce(filter_handler)); + document.getElementById("hide100").addEventListener("input", debounce(filter_handler)); // Trigger change event on setup, to force filter on page refresh // (filter value may still be present). document.getElementById("filter").dispatchEvent(new Event("input")); + document.getElementById("hide100").dispatchEvent(new Event("input")); }; -coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; - -// Loaded on index.html -coverage.index_ready = function () { - coverage.assign_shortkeys(); - coverage.wire_up_filter(); +// Set up the click-to-sort columns. +coverage.wire_up_sorting = function () { document.querySelectorAll("[data-sortable] th[aria-sort]").forEach( th => th.addEventListener("click", e => sortColumn(e.target)) ); // Look for a localStorage item containing previous sort settings: + var column = 0, direction = "ascending"; const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); - if (stored_list) { - const {column, direction} = JSON.parse(stored_list); - const th = document.querySelector("[data-sortable]").tHead.rows[0].cells[column]; // nosemgrep: eslint.detect-object-injection - th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); - th.click() + ({column, direction} = JSON.parse(stored_list)); } - // Watch for page unload events so we can save the final sort settings: - window.addEventListener("unload", function () { - const th = document.querySelector('[data-sortable] th[aria-sort="ascending"], [data-sortable] [aria-sort="descending"]'); - if (!th) { - return; - } - localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({ - column: [...th.parentElement.cells].indexOf(th), - direction: th.getAttribute("aria-sort"), - })); - }); + const th = document.querySelector("[data-sortable]").tHead.rows[0].cells[column]; // nosemgrep: eslint.detect-object-injection + th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); + th.click() +}; + +coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; + +// Loaded on index.html +coverage.index_ready = function () { + coverage.assign_shortkeys(); + coverage.wire_up_filter(); + coverage.wire_up_sorting(); on_click(".button_prev_file", coverage.to_prev_file); on_click(".button_next_file", coverage.to_next_file); @@ -217,7 +258,8 @@ coverage.pyfile_ready = function () { if (frag.length > 2 && frag[1] === "t") { document.querySelector(frag).closest(".n").classList.add("highlight"); coverage.set_sel(parseInt(frag.substr(2), 10)); - } else { + } + else { coverage.set_sel(0); } @@ -441,7 +483,8 @@ coverage.to_next_chunk_nicely = function () { if (line.parentElement !== document.getElementById("source")) { // The element is not a source line but the header or similar coverage.select_line_or_chunk(1); - } else { + } + else { // We extract the line number from the id coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); } @@ -460,7 +503,8 @@ coverage.to_prev_chunk_nicely = function () { if (line.parentElement !== document.getElementById("source")) { // The element is not a source line but the header or similar coverage.select_line_or_chunk(coverage.lines_len); - } else { + } + else { // We extract the line number from the id coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); } @@ -562,7 +606,8 @@ coverage.build_scroll_markers = function () { if (line_number === previous_line + 1) { // If this solid missed block just make previous mark higher. last_mark.style.height = `${line_top + line_height - last_top}px`; - } else { + } + else { // Add colored line in scroll_marker block. last_mark = document.createElement("div"); last_mark.id = `m${line_number}`; @@ -590,7 +635,8 @@ coverage.wire_up_sticky_header = function () { function updateHeader() { if (window.scrollY > header_bottom) { header.classList.add("sticky"); - } else { + } + else { header.classList.remove("sticky"); } } @@ -618,7 +664,8 @@ coverage.expand_contexts = function (e) { document.addEventListener("DOMContentLoaded", () => { if (document.body.classList.contains("indexfile")) { coverage.index_ready(); - } else { + } + else { coverage.pyfile_ready(); } }); diff --git a/doc/sample_html/function_index.html b/doc/sample_html/function_index.html new file mode 100644 index 000000000..c24314733 --- /dev/null +++ b/doc/sample_html/function_index.html @@ -0,0 +1,2393 @@ + + + + + Cog coverage + + + + + +
+
+

Cog coverage: + 38.64% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0, + created at 2024-04-23 13:00 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedbranchespartialcoverage
cogapp/__init__.py(no function)10000100.00%
cogapp/__main__.py(no function)330000.00%
cogapp/cogapp.pyCogError.__init__30020100.00%
cogapp/cogapp.pyCogGenerator.__init__40000100.00%
cogapp/cogapp.pyCogGenerator.parseMarker10000100.00%
cogapp/cogapp.pyCogGenerator.parseLine10000100.00%
cogapp/cogapp.pyCogGenerator.getCode50060100.00%
cogapp/cogapp.pyCogGenerator.evaluate334016483.67%
cogapp/cogapp.pyCogGenerator.msg110000.00%
cogapp/cogapp.pyCogGenerator.out100080100.00%
cogapp/cogapp.pyCogGenerator.outl20000100.00%
cogapp/cogapp.pyCogGenerator.error110000.00%
cogapp/cogapp.pyCogOptions.__init__220000100.00%
cogapp/cogapp.pyCogOptions.__eq__110000.00%
cogapp/cogapp.pyCogOptions.clone110000.00%
cogapp/cogapp.pyCogOptions.addToIncludePath220000.00%
cogapp/cogapp.pyCogOptions.parseArgs464614000.00%
cogapp/cogapp.pyCogOptions._parse_markers440000.00%
cogapp/cogapp.pyCogOptions.validate440400.00%
cogapp/cogapp.pyCog.__init__60000100.00%
cogapp/cogapp.pyCog._fixEndOutputPatterns30000100.00%
cogapp/cogapp.pyCog.showWarning110000.00%
cogapp/cogapp.pyCog.isBeginSpecLine10000100.00%
cogapp/cogapp.pyCog.isEndSpecLine10000100.00%
cogapp/cogapp.pyCog.isEndOutputLine10000100.00%
cogapp/cogapp.pyCog.createCogModule20000100.00%
cogapp/cogapp.pyCog.openOutputFile990400.00%
cogapp/cogapp.pyCog.openInputFile330200.00%
cogapp/cogapp.pyCog.processFile104230602170.73%
cogapp/cogapp.pyCog.suffixLines4202150.00%
cogapp/cogapp.pyCog.processString40000100.00%
cogapp/cogapp.pyCog.replaceFile11110600.00%
cogapp/cogapp.pyCog.saveIncludePath220000.00%
cogapp/cogapp.pyCog.restoreIncludePath330000.00%
cogapp/cogapp.pyCog.addToIncludePath220000.00%
cogapp/cogapp.pyCog.processOneFile313101600.00%
cogapp/cogapp.pyCog.processWildcards550400.00%
cogapp/cogapp.pyCog.processFileList11110400.00%
cogapp/cogapp.pyCog.processArguments16160800.00%
cogapp/cogapp.pyCog.callableMain161601000.00%
cogapp/cogapp.pyCog.main20200800.00%
cogapp/cogapp.pyfind_cog_source14808245.45%
cogapp/cogapp.pymain110000.00%
cogapp/cogapp.py(no function)710000100.00%
cogapp/makefiles.pymakeFiles11110800.00%
cogapp/makefiles.pyremoveFiles770600.00%
cogapp/makefiles.py(no function)40000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testNoCog30020100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testSimple30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testEmptyCog30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testMultipleCogs30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testTrimBlankLines30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testTrimEmptyBlankLines30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testTrimBlankLinesWithLastPartial30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testCogOutDedent30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.test22EndOfLine30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testIndentedCode30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testPrefixedCode30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testPrefixedIndentedCode30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testBogusPrefixMatch30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testNoFinalNewline30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testNoOutputAtAll30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testPurelyBlankLine30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testEmptyOutl30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testFirstLineNum30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testCompactOneLineCode40000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testInsideOutCompact30000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testSharingGlobals40000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testAssertInCogCode40000100.00%
cogapp/test_cogapp.pyCogTestsInMemory.testCogPrevious40000100.00%
cogapp/test_cogapp.pyCogOptionsTests.testEquality770000.00%
cogapp/test_cogapp.pyCogOptionsTests.testCloning990000.00%
cogapp/test_cogapp.pyCogOptionsTests.testCombiningFlags550000.00%
cogapp/test_cogapp.pyCogOptionsTests.testMarkers550000.00%
cogapp/test_cogapp.pyCogOptionsTests.testMarkersSwitch550000.00%
cogapp/test_cogapp.pyFileStructureTests.isBad330000.00%
cogapp/test_cogapp.pyFileStructureTests.testBeginNoEnd220000.00%
cogapp/test_cogapp.pyFileStructureTests.testNoEoo440000.00%
cogapp/test_cogapp.pyFileStructureTests.testStartWithEnd440000.00%
cogapp/test_cogapp.pyFileStructureTests.testStartWithEoo440000.00%
cogapp/test_cogapp.pyFileStructureTests.testNoEnd440000.00%
cogapp/test_cogapp.pyFileStructureTests.testTwoBegins440000.00%
cogapp/test_cogapp.pyFileStructureTests.testTwoEnds440000.00%
cogapp/test_cogapp.pyCogErrorTests.testErrorMsg440000.00%
cogapp/test_cogapp.pyCogErrorTests.testErrorNoMsg440000.00%
cogapp/test_cogapp.pyCogErrorTests.testNoErrorIfErrorNotCalled330000.00%
cogapp/test_cogapp.pyCogGeneratorGetCodeTests.setUp330000.00%
cogapp/test_cogapp.pyCogGeneratorGetCodeTests.testEmpty330000.00%
cogapp/test_cogapp.pyCogGeneratorGetCodeTests.testSimple550000.00%
cogapp/test_cogapp.pyCogGeneratorGetCodeTests.testCompressed1550000.00%
cogapp/test_cogapp.pyCogGeneratorGetCodeTests.testCompressed2550000.00%
cogapp/test_cogapp.pyCogGeneratorGetCodeTests.testCompressed3550000.00%
cogapp/test_cogapp.pyCogGeneratorGetCodeTests.testCompressed4550000.00%
cogapp/test_cogapp.pyCogGeneratorGetCodeTests.testNoCommonPrefixForMarkers660000.00%
cogapp/test_cogapp.pyTestCaseWithTempDir.newCog330000.00%
cogapp/test_cogapp.pyTestCaseWithTempDir.setUp550000.00%
cogapp/test_cogapp.pyTestCaseWithTempDir.tearDown220000.00%
cogapp/test_cogapp.pyTestCaseWithTempDir.assertFilesSame550000.00%
cogapp/test_cogapp.pyTestCaseWithTempDir.assertFileContent440000.00%
cogapp/test_cogapp.pyArgumentHandlingTests.testArgumentFailure770000.00%
cogapp/test_cogapp.pyArgumentHandlingTests.testNoDashOAndAtFile330000.00%
cogapp/test_cogapp.pyArgumentHandlingTests.testNoDashOAndAmpFile330000.00%
cogapp/test_cogapp.pyArgumentHandlingTests.testDashV330000.00%
cogapp/test_cogapp.pyArgumentHandlingTests.producesHelp440000.00%
cogapp/test_cogapp.pyArgumentHandlingTests.testDashH440000.00%
cogapp/test_cogapp.pyArgumentHandlingTests.testDashOAndDashR440000.00%
cogapp/test_cogapp.pyArgumentHandlingTests.testDashZ770000.00%
cogapp/test_cogapp.pyArgumentHandlingTests.testBadDashD440000.00%
cogapp/test_cogapp.pyArgumentHandlingTests.testBadMarkers440000.00%
cogapp/test_cogapp.pyTestMain.setUp440000.00%
cogapp/test_cogapp.pyTestMain.tearDown440000.00%
cogapp/test_cogapp.pyTestMain.test_main_function550000.00%
cogapp/test_cogapp.pyTestMain.test_error_report110000.00%
cogapp/test_cogapp.pyTestMain.test_error_report_with_prologue110000.00%
cogapp/test_cogapp.pyTestMain.check_error_report660000.00%
cogapp/test_cogapp.pyTestMain.test_error_in_prologue660000.00%
cogapp/test_cogapp.pyTestFileHandling.testSimple660000.00%
cogapp/test_cogapp.pyTestFileHandling.testPrintOutput660000.00%
cogapp/test_cogapp.pyTestFileHandling.testWildcards880000.00%
cogapp/test_cogapp.pyTestFileHandling.testOutputFile440000.00%
cogapp/test_cogapp.pyTestFileHandling.testAtFile770000.00%
cogapp/test_cogapp.pyTestFileHandling.testNestedAtFile770000.00%
cogapp/test_cogapp.pyTestFileHandling.testAtFileWithArgs550000.00%
cogapp/test_cogapp.pyTestFileHandling.testAtFileWithBadArgCombo440000.00%
cogapp/test_cogapp.pyTestFileHandling.testAtFileWithTrickyFilenames770000.00%
cogapp/test_cogapp.pyTestFileHandling.testAtFileWithTrickyFilenames.fix_backslashes330200.00%
cogapp/test_cogapp.pyTestFileHandling.testAmpFile550000.00%
cogapp/test_cogapp.pyTestFileHandling.run_with_verbosity550000.00%
cogapp/test_cogapp.pyTestFileHandling.test_verbosity0220000.00%
cogapp/test_cogapp.pyTestFileHandling.test_verbosity1220000.00%
cogapp/test_cogapp.pyTestFileHandling.test_verbosity2220000.00%
cogapp/test_cogapp.pyCogTestLineEndings.testOutputNativeEol330000.00%
cogapp/test_cogapp.pyCogTestLineEndings.testOutputLfEol330000.00%
cogapp/test_cogapp.pyCogTestLineEndings.testReplaceNativeEol330000.00%
cogapp/test_cogapp.pyCogTestLineEndings.testReplaceLfEol330000.00%
cogapp/test_cogapp.pyCogTestCharacterEncoding.testSimple660000.00%
cogapp/test_cogapp.pyCogTestCharacterEncoding.testFileEncodingOption660000.00%
cogapp/test_cogapp.pyTestCaseWithImports.setUp220000.00%
cogapp/test_cogapp.pyTestCaseWithImports.tearDown440400.00%
cogapp/test_cogapp.pyCogIncludeTests.testNeedIncludePath440000.00%
cogapp/test_cogapp.pyCogIncludeTests.testIncludePath330000.00%
cogapp/test_cogapp.pyCogIncludeTests.testTwoIncludePaths330000.00%
cogapp/test_cogapp.pyCogIncludeTests.testTwoIncludePaths2330000.00%
cogapp/test_cogapp.pyCogIncludeTests.testUselessIncludePath330000.00%
cogapp/test_cogapp.pyCogIncludeTests.testSysPathIsUnchanged26260000.00%
cogapp/test_cogapp.pyCogIncludeTests.testSubDirectories440000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testWarnIfNoCogCode13130000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testFileNameProps770000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testGlobalsDontCrossFiles770000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testRemoveGeneratedOutput10100000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testMsgCall550000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testErrorMessageHasNoTraceback770000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testDashD19190000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testOutputToStdout990000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testReadFromStdin11110000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testReadFromStdin.restore_stdin110000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testSuffixOutputLines440000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testEmptySuffix440000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testHellishSuffix440000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testPrologue440000.00%
cogapp/test_cogapp.pyCogTestsInFiles.testThreads13130800.00%
cogapp/test_cogapp.pyCogTestsInFiles.testThreads.thread_main442000.00%
cogapp/test_cogapp.pyCheckTests.run_check330000.00%
cogapp/test_cogapp.pyCheckTests.assert_made_files_unchanged550400.00%
cogapp/test_cogapp.pyCheckTests.test_check_no_cog550000.00%
cogapp/test_cogapp.pyCheckTests.test_check_good550000.00%
cogapp/test_cogapp.pyCheckTests.test_check_bad550000.00%
cogapp/test_cogapp.pyCheckTests.test_check_mixed770200.00%
cogapp/test_cogapp.pyCheckTests.test_check_with_good_checksum550000.00%
cogapp/test_cogapp.pyCheckTests.test_check_with_bad_checksum550000.00%
cogapp/test_cogapp.pyWritabilityTests.setUp550000.00%
cogapp/test_cogapp.pyWritabilityTests.tearDown220000.00%
cogapp/test_cogapp.pyWritabilityTests.testReadonlyNoCommand330000.00%
cogapp/test_cogapp.pyWritabilityTests.testReadonlyWithCommand330000.00%
cogapp/test_cogapp.pyWritabilityTests.testReadonlyWithCommandWithNoSlot330000.00%
cogapp/test_cogapp.pyWritabilityTests.testReadonlyWithIneffectualCommand330000.00%
cogapp/test_cogapp.pyChecksumTests.testCreateChecksumOutput440000.00%
cogapp/test_cogapp.pyChecksumTests.testCheckChecksumOutput440000.00%
cogapp/test_cogapp.pyChecksumTests.testRemoveChecksumOutput440000.00%
cogapp/test_cogapp.pyChecksumTests.testTamperedChecksumOutput14140000.00%
cogapp/test_cogapp.pyChecksumTests.testArgvIsntModified440000.00%
cogapp/test_cogapp.pyCustomMarkerTests.testCustomerMarkers440000.00%
cogapp/test_cogapp.pyCustomMarkerTests.testTrulyWackyMarkers440000.00%
cogapp/test_cogapp.pyCustomMarkerTests.testChangeJustOneMarker440000.00%
cogapp/test_cogapp.pyBlakeTests.testDeleteCode440000.00%
cogapp/test_cogapp.pyBlakeTests.testDeleteCodeWithDashRFails440000.00%
cogapp/test_cogapp.pyBlakeTests.testSettingGlobals770000.00%
cogapp/test_cogapp.pyErrorCallTests.testErrorCallHasNoTraceback550000.00%
cogapp/test_cogapp.pyErrorCallTests.testRealErrorHasTraceback770000.00%
cogapp/test_cogapp.py(no function)185202198.40%
cogapp/test_makefiles.pySimpleTests.setUp330000.00%
cogapp/test_makefiles.pySimpleTests.tearDown110000.00%
cogapp/test_makefiles.pySimpleTests.exists110000.00%
cogapp/test_makefiles.pySimpleTests.checkFilesExist440400.00%
cogapp/test_makefiles.pySimpleTests.checkFilesDontExist220200.00%
cogapp/test_makefiles.pySimpleTests.testOneFile11110000.00%
cogapp/test_makefiles.pySimpleTests.testManyFiles660000.00%
cogapp/test_makefiles.pySimpleTests.testOverlapping12120000.00%
cogapp/test_makefiles.pySimpleTests.testContents660000.00%
cogapp/test_makefiles.pySimpleTests.testDedent550000.00%
cogapp/test_makefiles.py(no function)170000100.00%
cogapp/test_whiteutils.pyWhitePrefixTests.testSingleLine770000.00%
cogapp/test_whiteutils.pyWhitePrefixTests.testMultiLine330000.00%
cogapp/test_whiteutils.pyWhitePrefixTests.testBlankLinesAreIgnored440000.00%
cogapp/test_whiteutils.pyWhitePrefixTests.testTabCharacters110000.00%
cogapp/test_whiteutils.pyWhitePrefixTests.testDecreasingLengths220000.00%
cogapp/test_whiteutils.pyReindentBlockTests.testNonTermLine10100000.00%
cogapp/test_whiteutils.pyReindentBlockTests.testSingleLine10100000.00%
cogapp/test_whiteutils.pyReindentBlockTests.testRealBlock110000.00%
cogapp/test_whiteutils.pyCommonPrefixTests.testDegenerateCases440000.00%
cogapp/test_whiteutils.pyCommonPrefixTests.testNoCommonPrefix330000.00%
cogapp/test_whiteutils.pyCommonPrefixTests.testUsualCases330000.00%
cogapp/test_whiteutils.pyCommonPrefixTests.testBlankLine110000.00%
cogapp/test_whiteutils.pyCommonPrefixTests.testDecreasingLengths110000.00%
cogapp/test_whiteutils.py(no function)180000100.00%
cogapp/utils.pyRedirectable.__init__20000100.00%
cogapp/utils.pyRedirectable.setOutput4104262.50%
cogapp/utils.pyRedirectable.prout110000.00%
cogapp/utils.pyRedirectable.prerr110000.00%
cogapp/utils.pyNumberedFileReader.__init__20000100.00%
cogapp/utils.pyNumberedFileReader.readline40020100.00%
cogapp/utils.pyNumberedFileReader.linenumber10000100.00%
cogapp/utils.pychange_dir550000.00%
cogapp/utils.py(no function)170000100.00%
cogapp/whiteutils.pywhitePrefix123012279.17%
cogapp/whiteutils.pyreindentBlock141010191.67%
cogapp/whiteutils.pycommonPrefix131012192.00%
cogapp/whiteutils.py(no function)40000100.00%
Total 157996132923538.64%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/doc/sample_html/index.html b/doc/sample_html/index.html index 900d30f98..5a6c4fcc3 100644 --- a/doc/sample_html/index.html +++ b/doc/sample_html/index.html @@ -1,11 +1,11 @@ - + Cog coverage - +
@@ -16,13 +16,13 @@

Cog coverage:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v7.4.4, - created at 2024-03-14 14:39 -0400 + coverage.py v7.5.0, + created at 2024-04-23 13:00 -0400

@@ -55,18 +64,18 @@

Cog coverage: - - - - - - - + + + + + + + - - + + @@ -74,8 +83,8 @@

Cog coverage:

- - + + @@ -83,8 +92,8 @@

Cog coverage:

- - + + @@ -92,8 +101,8 @@

Cog coverage:

- - + + @@ -101,8 +110,8 @@

Cog coverage:

- - + + @@ -110,8 +119,8 @@

Cog coverage:

- - + + @@ -119,8 +128,8 @@

Cog coverage:

- - + + @@ -128,8 +137,8 @@

Cog coverage:

- - + + @@ -137,8 +146,8 @@

Cog coverage:

- - + + @@ -166,16 +175,16 @@

Cog coverage: diff --git a/doc/sample_html/status.json b/doc/sample_html/status.json index 80fdc4fad..cd72fa04c 100644 --- a/doc/sample_html/status.json +++ b/doc/sample_html/status.json @@ -1 +1 @@ -{"note":"This file is an internal implementation detail to speed up HTML report generation. Its format can change at any time. You might be looking for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json","format":2,"version":"7.4.4","globals":"93b51f0c694fe02b0939ee4dd75fe224","files":{"d_7b071bdc2a35fa80___init___py":{"hash":"669207c1fb29be3e8be6ce0639506cab","index":{"nums":[2,1,1,0,0,0,0,0],"html_filename":"d_7b071bdc2a35fa80___init___py.html","relative_filename":"cogapp/__init__.py"}},"d_7b071bdc2a35fa80___main___py":{"hash":"6d9d0d551879aa3e73791f40c5739845","index":{"nums":[2,1,3,0,3,0,0,0],"html_filename":"d_7b071bdc2a35fa80___main___py.html","relative_filename":"cogapp/__main__.py"}},"d_7b071bdc2a35fa80_cogapp_py":{"hash":"3b620625b0506d140beda5c04a03a961","index":{"nums":[2,1,483,1,228,208,28,140],"html_filename":"d_7b071bdc2a35fa80_cogapp_py.html","relative_filename":"cogapp/cogapp.py"}},"d_7b071bdc2a35fa80_makefiles_py":{"hash":"e73ea90ac9a2e7af9d1fdb188ea22dfe","index":{"nums":[2,1,22,0,18,14,0,14],"html_filename":"d_7b071bdc2a35fa80_makefiles_py.html","relative_filename":"cogapp/makefiles.py"}},"d_7b071bdc2a35fa80_test_cogapp_py":{"hash":"f7e93bafb25b733e134d8c75e2d3cc24","index":{"nums":[2,1,854,2,598,24,1,21],"html_filename":"d_7b071bdc2a35fa80_test_cogapp_py.html","relative_filename":"cogapp/test_cogapp.py"}},"d_7b071bdc2a35fa80_test_makefiles_py":{"hash":"b2e0cdd09c01a8b0e4bdae02e1cfa856","index":{"nums":[2,1,68,0,51,6,0,6],"html_filename":"d_7b071bdc2a35fa80_test_makefiles_py.html","relative_filename":"cogapp/test_makefiles.py"}},"d_7b071bdc2a35fa80_test_whiteutils_py":{"hash":"4511a89ca54b865d6397d6b7d315c35c","index":{"nums":[2,1,68,0,50,0,0,0],"html_filename":"d_7b071bdc2a35fa80_test_whiteutils_py.html","relative_filename":"cogapp/test_whiteutils.py"}},"d_7b071bdc2a35fa80_utils_py":{"hash":"f8df213bfbf38327877cdb9699b0ec41","index":{"nums":[2,1,37,0,8,6,2,2],"html_filename":"d_7b071bdc2a35fa80_utils_py.html","relative_filename":"cogapp/utils.py"}},"d_7b071bdc2a35fa80_whiteutils_py":{"hash":"c68cfd051fc76896fdb23127cf2ca0ea","index":{"nums":[2,1,43,0,5,34,4,4],"html_filename":"d_7b071bdc2a35fa80_whiteutils_py.html","relative_filename":"cogapp/whiteutils.py"}}}} \ No newline at end of file +{"note":"This file is an internal implementation detail to speed up HTML report generation. Its format can change at any time. You might be looking for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json","format":5,"version":"7.5.0","globals":"6209e941d022650f2b874068b895003a","files":{"z_7b071bdc2a35fa80___init___py":{"hash":"669207c1fb29be3e8be6ce0639506cab","index":{"url":"z_7b071bdc2a35fa80___init___py.html","file":"cogapp/__init__.py","description":"","nums":{"precision":2,"n_files":1,"n_statements":1,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_7b071bdc2a35fa80___main___py":{"hash":"6d9d0d551879aa3e73791f40c5739845","index":{"url":"z_7b071bdc2a35fa80___main___py.html","file":"cogapp/__main__.py","description":"","nums":{"precision":2,"n_files":1,"n_statements":3,"n_excluded":0,"n_missing":3,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_7b071bdc2a35fa80_cogapp_py":{"hash":"3b620625b0506d140beda5c04a03a961","index":{"url":"z_7b071bdc2a35fa80_cogapp_py.html","file":"cogapp/cogapp.py","description":"","nums":{"precision":2,"n_files":1,"n_statements":483,"n_excluded":1,"n_missing":228,"n_branches":208,"n_partial_branches":28,"n_missing_branches":140}}},"z_7b071bdc2a35fa80_makefiles_py":{"hash":"e73ea90ac9a2e7af9d1fdb188ea22dfe","index":{"url":"z_7b071bdc2a35fa80_makefiles_py.html","file":"cogapp/makefiles.py","description":"","nums":{"precision":2,"n_files":1,"n_statements":22,"n_excluded":0,"n_missing":18,"n_branches":14,"n_partial_branches":0,"n_missing_branches":14}}},"z_7b071bdc2a35fa80_test_cogapp_py":{"hash":"f7e93bafb25b733e134d8c75e2d3cc24","index":{"url":"z_7b071bdc2a35fa80_test_cogapp_py.html","file":"cogapp/test_cogapp.py","description":"","nums":{"precision":2,"n_files":1,"n_statements":854,"n_excluded":2,"n_missing":598,"n_branches":24,"n_partial_branches":1,"n_missing_branches":21}}},"z_7b071bdc2a35fa80_test_makefiles_py":{"hash":"b2e0cdd09c01a8b0e4bdae02e1cfa856","index":{"url":"z_7b071bdc2a35fa80_test_makefiles_py.html","file":"cogapp/test_makefiles.py","description":"","nums":{"precision":2,"n_files":1,"n_statements":68,"n_excluded":0,"n_missing":51,"n_branches":6,"n_partial_branches":0,"n_missing_branches":6}}},"z_7b071bdc2a35fa80_test_whiteutils_py":{"hash":"4511a89ca54b865d6397d6b7d315c35c","index":{"url":"z_7b071bdc2a35fa80_test_whiteutils_py.html","file":"cogapp/test_whiteutils.py","description":"","nums":{"precision":2,"n_files":1,"n_statements":68,"n_excluded":0,"n_missing":50,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_7b071bdc2a35fa80_utils_py":{"hash":"f8df213bfbf38327877cdb9699b0ec41","index":{"url":"z_7b071bdc2a35fa80_utils_py.html","file":"cogapp/utils.py","description":"","nums":{"precision":2,"n_files":1,"n_statements":37,"n_excluded":0,"n_missing":8,"n_branches":6,"n_partial_branches":2,"n_missing_branches":2}}},"z_7b071bdc2a35fa80_whiteutils_py":{"hash":"c68cfd051fc76896fdb23127cf2ca0ea","index":{"url":"z_7b071bdc2a35fa80_whiteutils_py.html","file":"cogapp/whiteutils.py","description":"","nums":{"precision":2,"n_files":1,"n_statements":43,"n_excluded":0,"n_missing":5,"n_branches":34,"n_partial_branches":4,"n_missing_branches":4}}}}} \ No newline at end of file diff --git a/doc/sample_html/style.css b/doc/sample_html/style.css index aec9cbef2..3cdaf05a3 100644 --- a/doc/sample_html/style.css +++ b/doc/sample_html/style.css @@ -22,7 +22,7 @@ td { vertical-align: top; } table tr.hidden { display: none !important; } -p#no_rows { display: none; font-size: 1.2em; } +p#no_rows { display: none; font-size: 1.15em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } a.nav { text-decoration: none; color: inherit; } @@ -40,6 +40,18 @@ header .content { padding: 1rem 3.5rem; } header h2 { margin-top: .5em; font-size: 1em; } +header h2 a.button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header h2 a.button { background: #333; } } + +@media (prefers-color-scheme: dark) { header h2 a.button { border-color: #444; } } + +header h2 a.button.current { border: 2px solid; background: #fff; border-color: #999; cursor: default; } + +@media (prefers-color-scheme: dark) { header h2 a.button.current { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { header h2 a.button.current { border-color: #777; } } + header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; } @media (prefers-color-scheme: dark) { header p.text { color: #aaa; } } @@ -68,19 +80,29 @@ footer .content { padding: 0; color: #666; font-style: italic; } h1 { font-size: 1.25em; display: inline-block; } -#filter_container { float: right; margin: 0 2em 0 0; } +#filter_container { float: right; margin: 0 2em 0 0; line-height: 1.66em; } + +#filter_container #filter { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } -#filter_container input { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } +@media (prefers-color-scheme: dark) { #filter_container #filter { border-color: #444; } } -@media (prefers-color-scheme: dark) { #filter_container input { border-color: #444; } } +@media (prefers-color-scheme: dark) { #filter_container #filter { background: #1e1e1e; } } -@media (prefers-color-scheme: dark) { #filter_container input { background: #1e1e1e; } } +@media (prefers-color-scheme: dark) { #filter_container #filter { color: #eee; } } -@media (prefers-color-scheme: dark) { #filter_container input { color: #eee; } } +#filter_container #filter:focus { border-color: #007acc; } -#filter_container input:focus { border-color: #007acc; } +#filter_container :disabled ~ label { color: #ccc; } -header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; color: inherit; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } +@media (prefers-color-scheme: dark) { #filter_container :disabled ~ label { color: #444; } } + +#filter_container label { font-size: .875em; color: #666; } + +@media (prefers-color-scheme: dark) { #filter_container label { color: #aaa; } } + +header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header button { background: #333; } } @media (prefers-color-scheme: dark) { header button { border-color: #444; } } @@ -266,13 +288,13 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em #index table.index { margin-left: -.5em; } -#index td, #index th { text-align: right; width: 5em; padding: .25em .5em; border-bottom: 1px solid #eee; } +#index td, #index th { text-align: right; padding: .25em .5em; border-bottom: 1px solid #eee; } @media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } } -#index td.name, #index th.name { text-align: left; width: auto; } +#index td.name, #index th.name { text-align: left; width: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; min-width: 15em; } -#index th { font-style: italic; color: #333; cursor: pointer; } +#index th { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-style: italic; color: #333; cursor: pointer; } @media (prefers-color-scheme: dark) { #index th { color: #ddd; } } @@ -280,23 +302,29 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em @media (prefers-color-scheme: dark) { #index th:hover { background: #333; } } +#index th .arrows { color: #666; font-size: 85%; font-family: sans-serif; font-style: normal; pointer-events: none; } + #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { white-space: nowrap; background: #eee; padding-left: .5em; } @media (prefers-color-scheme: dark) { #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background: #333; } } -#index th[aria-sort="ascending"]::after { font-family: sans-serif; content: " ↑"; } +#index th[aria-sort="ascending"] .arrows::after { content: " ▲"; } + +#index th[aria-sort="descending"] .arrows::after { content: " ▼"; } -#index th[aria-sort="descending"]::after { font-family: sans-serif; content: " ↓"; } +#index td.name { font-size: 1.15em; } #index td.name a { text-decoration: none; color: inherit; } +#index td.name .no-noun { font-style: italic; } + #index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-top: 1px solid #ccc; border-bottom: none; } -#index tr.file:hover { background: #eee; } +#index tr.region:hover { background: #eee; } -@media (prefers-color-scheme: dark) { #index tr.file:hover { background: #333; } } +@media (prefers-color-scheme: dark) { #index tr.region:hover { background: #333; } } -#index tr.file:hover td.name { text-decoration: underline; color: inherit; } +#index tr.region:hover td.name { text-decoration: underline; color: inherit; } #scroll_marker { position: fixed; z-index: 3; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; } diff --git a/doc/sample_html/d_7b071bdc2a35fa80___init___py.html b/doc/sample_html/z_7b071bdc2a35fa80___init___py.html similarity index 87% rename from doc/sample_html/d_7b071bdc2a35fa80___init___py.html rename to doc/sample_html/z_7b071bdc2a35fa80___init___py.html index 5e04da820..4a59bb133 100644 --- a/doc/sample_html/d_7b071bdc2a35fa80___init___py.html +++ b/doc/sample_html/z_7b071bdc2a35fa80___init___py.html @@ -1,11 +1,11 @@ - + Coverage for cogapp/__init__.py: 100.00% - +
@@ -17,7 +17,7 @@

@@ -93,12 +93,12 @@

diff --git a/doc/sample_html/d_7b071bdc2a35fa80___main___py.html b/doc/sample_html/z_7b071bdc2a35fa80___main___py.html similarity index 86% rename from doc/sample_html/d_7b071bdc2a35fa80___main___py.html rename to doc/sample_html/z_7b071bdc2a35fa80___main___py.html index b1ecf249a..4b20354a2 100644 --- a/doc/sample_html/d_7b071bdc2a35fa80___main___py.html +++ b/doc/sample_html/z_7b071bdc2a35fa80___main___py.html @@ -1,11 +1,11 @@ - + Coverage for cogapp/__main__.py: 0.00% - +
@@ -17,7 +17,7 @@

@@ -93,12 +93,12 @@

diff --git a/doc/sample_html/d_7b071bdc2a35fa80_cogapp_py.html b/doc/sample_html/z_7b071bdc2a35fa80_cogapp_py.html similarity index 99% rename from doc/sample_html/d_7b071bdc2a35fa80_cogapp_py.html rename to doc/sample_html/z_7b071bdc2a35fa80_cogapp_py.html index ef2bf64f0..fbef027b8 100644 --- a/doc/sample_html/d_7b071bdc2a35fa80_cogapp_py.html +++ b/doc/sample_html/z_7b071bdc2a35fa80_cogapp_py.html @@ -1,11 +1,11 @@ - + Coverage for cogapp/cogapp.py: 46.74% - +
@@ -17,7 +17,7 @@

@@ -907,12 +907,12 @@

diff --git a/doc/sample_html/d_7b071bdc2a35fa80_makefiles_py.html b/doc/sample_html/z_7b071bdc2a35fa80_makefiles_py.html similarity index 94% rename from doc/sample_html/d_7b071bdc2a35fa80_makefiles_py.html rename to doc/sample_html/z_7b071bdc2a35fa80_makefiles_py.html index 2515bd109..4efacbdf7 100644 --- a/doc/sample_html/d_7b071bdc2a35fa80_makefiles_py.html +++ b/doc/sample_html/z_7b071bdc2a35fa80_makefiles_py.html @@ -1,11 +1,11 @@ - + Coverage for cogapp/makefiles.py: 11.11% - +
@@ -17,7 +17,7 @@

@@ -122,12 +122,12 @@

diff --git a/doc/sample_html/d_7b071bdc2a35fa80_test_cogapp_py.html b/doc/sample_html/z_7b071bdc2a35fa80_test_cogapp_py.html similarity index 99% rename from doc/sample_html/d_7b071bdc2a35fa80_test_cogapp_py.html rename to doc/sample_html/z_7b071bdc2a35fa80_test_cogapp_py.html index 51bf9f244..b28701909 100644 --- a/doc/sample_html/d_7b071bdc2a35fa80_test_cogapp_py.html +++ b/doc/sample_html/z_7b071bdc2a35fa80_test_cogapp_py.html @@ -1,11 +1,11 @@ - + Coverage for cogapp/test_cogapp.py: 29.50% - +
@@ -17,7 +17,7 @@

@@ -2742,12 +2742,12 @@

diff --git a/doc/sample_html/d_7b071bdc2a35fa80_test_makefiles_py.html b/doc/sample_html/z_7b071bdc2a35fa80_test_makefiles_py.html similarity index 97% rename from doc/sample_html/d_7b071bdc2a35fa80_test_makefiles_py.html rename to doc/sample_html/z_7b071bdc2a35fa80_test_makefiles_py.html index 5cdc63122..f1a0d8d8a 100644 --- a/doc/sample_html/d_7b071bdc2a35fa80_test_makefiles_py.html +++ b/doc/sample_html/z_7b071bdc2a35fa80_test_makefiles_py.html @@ -1,11 +1,11 @@ - + Coverage for cogapp/test_makefiles.py: 22.97% - +
@@ -17,7 +17,7 @@

@@ -201,12 +201,12 @@

diff --git a/doc/sample_html/d_7b071bdc2a35fa80_test_whiteutils_py.html b/doc/sample_html/z_7b071bdc2a35fa80_test_whiteutils_py.html similarity index 97% rename from doc/sample_html/d_7b071bdc2a35fa80_test_whiteutils_py.html rename to doc/sample_html/z_7b071bdc2a35fa80_test_whiteutils_py.html index f2d07b840..94899ce53 100644 --- a/doc/sample_html/d_7b071bdc2a35fa80_test_whiteutils_py.html +++ b/doc/sample_html/z_7b071bdc2a35fa80_test_whiteutils_py.html @@ -1,11 +1,11 @@ - + Coverage for cogapp/test_whiteutils.py: 26.47% - +
@@ -17,7 +17,7 @@

@@ -183,12 +183,12 @@

diff --git a/doc/sample_html/d_7b071bdc2a35fa80_utils_py.html b/doc/sample_html/z_7b071bdc2a35fa80_utils_py.html similarity index 96% rename from doc/sample_html/d_7b071bdc2a35fa80_utils_py.html rename to doc/sample_html/z_7b071bdc2a35fa80_utils_py.html index fcd212415..36a49afc0 100644 --- a/doc/sample_html/d_7b071bdc2a35fa80_utils_py.html +++ b/doc/sample_html/z_7b071bdc2a35fa80_utils_py.html @@ -1,11 +1,11 @@ - + Coverage for cogapp/utils.py: 76.74% - +
@@ -17,7 +17,7 @@

@@ -157,12 +157,12 @@

diff --git a/doc/sample_html/d_7b071bdc2a35fa80_whiteutils_py.html b/doc/sample_html/z_7b071bdc2a35fa80_whiteutils_py.html similarity index 96% rename from doc/sample_html/d_7b071bdc2a35fa80_whiteutils_py.html rename to doc/sample_html/z_7b071bdc2a35fa80_whiteutils_py.html index a2047f82c..d53030223 100644 --- a/doc/sample_html/d_7b071bdc2a35fa80_whiteutils_py.html +++ b/doc/sample_html/z_7b071bdc2a35fa80_whiteutils_py.html @@ -1,11 +1,11 @@ - + Coverage for cogapp/whiteutils.py: 88.31% - +
@@ -17,7 +17,7 @@

@@ -153,12 +153,12 @@

diff --git a/pyproject.toml b/pyproject.toml index 1752aa66c..7dc56333f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,8 +38,10 @@ no-docstring-rgx = "__.*__|test[A-Z_].*|setUp|_decorator|_wrapper|_.*__.*" defining-attr-methods = [ "__init__", "__new__", + "__post_init__", "setUp", "reset", + "_reset", ] [tool.pylint.design] @@ -142,7 +144,7 @@ balanced_clumps = [ # We aren't using ruff for real yet... [tool.ruff] -target-version = "py38" # Can't use [project] +target-version = "py38" # Can't use [project] line-length = 100 [tool.ruff.lint] diff --git a/requirements/dev.pip b/requirements/dev.pip index 46f4161c2..7c957f77c 100644 --- a/requirements/dev.pip +++ b/requirements/dev.pip @@ -8,7 +8,9 @@ astroid==3.1.0 # via pylint attrs==23.2.0 # via hypothesis -build==1.1.1 +backports-tarfile==1.1.0 + # via jaraco-context +build==1.2.1 # via check-manifest cachetools==5.3.3 # via tox @@ -33,13 +35,13 @@ distlib==0.3.8 # via virtualenv docutils==0.20.1 # via readme-renderer -exceptiongroup==1.2.0 +exceptiongroup==1.2.1 # via # hypothesis # pytest -execnet==2.0.2 +execnet==2.1.1 # via pytest-xdist -filelock==3.13.1 +filelock==3.13.4 # via # tox # virtualenv @@ -47,26 +49,30 @@ flaky==3.8.1 # via -r requirements/pytest.in greenlet==3.0.3 # via -r requirements/dev.in -hypothesis==6.99.6 +hypothesis==6.100.1 # via -r requirements/pytest.in -idna==3.6 +idna==3.7 # via requests -importlib-metadata==7.0.2 +importlib-metadata==7.1.0 # via # build # keyring # twine -importlib-resources==6.3.0 +importlib-resources==6.4.0 # via keyring iniconfig==2.0.0 # via pytest isort==5.13.2 # via pylint -jaraco-classes==3.3.1 +jaraco-classes==3.4.0 + # via keyring +jaraco-context==5.3.0 + # via keyring +jaraco-functools==4.0.1 # via keyring jedi==0.19.1 # via pudb -keyring==24.3.1 +keyring==25.1.0 # via twine libsass==0.23.0 # via -r requirements/dev.in @@ -77,8 +83,10 @@ mccabe==0.7.0 mdurl==0.1.2 # via markdown-it-py more-itertools==10.2.0 - # via jaraco-classes -nh3==0.2.15 + # via + # jaraco-classes + # jaraco-functools +nh3==0.2.17 # via readme-renderer packaging==24.0 # via @@ -87,7 +95,7 @@ packaging==24.0 # pyproject-api # pytest # tox -parso==0.8.3 +parso==0.8.4 # via jedi pkginfo==1.10.0 # via twine @@ -96,7 +104,7 @@ platformdirs==4.2.0 # pylint # tox # virtualenv -pluggy==1.4.0 +pluggy==1.5.0 # via # pytest # tox @@ -150,7 +158,7 @@ tomli==2.0.1 # tox tomlkit==0.12.4 # via pylint -tox==4.14.1 +tox==4.14.2 # via # -r requirements/tox.in # tox-gh @@ -158,7 +166,7 @@ tox-gh==1.3.1 # via -r requirements/tox.in twine==5.0.0 # via -r requirements/dev.in -typing-extensions==4.10.0 +typing-extensions==4.11.0 # via # astroid # pylint @@ -168,19 +176,19 @@ urllib3==2.2.1 # via # requests # twine -urwid==2.6.9 +urwid==2.6.10 # via # pudb # urwid-readline urwid-readline==0.14 # via pudb -virtualenv==20.25.1 +virtualenv==20.25.3 # via # -r requirements/pip.in # tox wcwidth==0.2.13 # via urwid -zipp==3.18.0 +zipp==3.18.1 # via # importlib-metadata # importlib-resources @@ -188,7 +196,7 @@ zipp==3.18.0 # The following packages are considered to be unsafe in a requirements file: pip==24.0 # via -r requirements/pip.in -setuptools==69.2.0 +setuptools==69.5.1 # via # -r requirements/pip.in # check-manifest diff --git a/requirements/kit.pip b/requirements/kit.pip index 4f9c187eb..b134d3ef2 100644 --- a/requirements/kit.pip +++ b/requirements/kit.pip @@ -10,7 +10,7 @@ bashlex==0.18 # via cibuildwheel bracex==2.4 # via cibuildwheel -build==1.1.1 +build==1.2.1 # via -r requirements/kit.in certifi==2024.2.2 # via cibuildwheel @@ -18,9 +18,9 @@ cibuildwheel==2.17.0 # via -r requirements/kit.in colorama==0.4.6 # via -r requirements/kit.in -filelock==3.13.1 +filelock==3.13.4 # via cibuildwheel -importlib-metadata==7.0.2 +importlib-metadata==7.1.0 # via build packaging==24.0 # via @@ -38,13 +38,13 @@ tomli==2.0.1 # build # cibuildwheel # pyproject-hooks -typing-extensions==4.10.0 +typing-extensions==4.11.0 # via cibuildwheel wheel==0.43.0 # via -r requirements/kit.in -zipp==3.18.0 +zipp==3.18.1 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -setuptools==69.2.0 +setuptools==69.5.1 # via -r requirements/kit.in diff --git a/requirements/light-threads.pip b/requirements/light-threads.pip index a2a3f92dc..54492490c 100644 --- a/requirements/light-threads.pip +++ b/requirements/light-threads.pip @@ -8,7 +8,7 @@ cffi==1.16.0 # via -r requirements/light-threads.in dnspython==2.6.1 # via eventlet -eventlet==0.35.2 +eventlet==0.36.1 # via -r requirements/light-threads.in gevent==24.2.1 # via -r requirements/light-threads.in @@ -17,15 +17,15 @@ greenlet==3.0.3 # -r requirements/light-threads.in # eventlet # gevent -pycparser==2.21 +pycparser==2.22 # via cffi zope-event==5.0 # via gevent -zope-interface==6.2 +zope-interface==6.3 # via gevent # The following packages are considered to be unsafe in a requirements file: -setuptools==69.2.0 +setuptools==69.5.1 # via # zope-event # zope-interface diff --git a/requirements/mypy.pip b/requirements/mypy.pip index 85d794482..4fc939231 100644 --- a/requirements/mypy.pip +++ b/requirements/mypy.pip @@ -8,15 +8,15 @@ attrs==23.2.0 # via hypothesis colorama==0.4.6 # via -r requirements/pytest.in -exceptiongroup==1.2.0 +exceptiongroup==1.2.1 # via # hypothesis # pytest -execnet==2.0.2 +execnet==2.1.1 # via pytest-xdist flaky==3.8.1 # via -r requirements/pytest.in -hypothesis==6.99.6 +hypothesis==6.100.1 # via -r requirements/pytest.in iniconfig==2.0.0 # via pytest @@ -26,7 +26,7 @@ mypy-extensions==1.0.0 # via mypy packaging==24.0 # via pytest -pluggy==1.4.0 +pluggy==1.5.0 # via pytest pygments==2.17.2 # via -r requirements/pytest.in @@ -42,5 +42,5 @@ tomli==2.0.1 # via # mypy # pytest -typing-extensions==4.10.0 +typing-extensions==4.11.0 # via mypy diff --git a/requirements/pip-tools.pip b/requirements/pip-tools.pip index 1b920320b..0d7fd4efc 100644 --- a/requirements/pip-tools.pip +++ b/requirements/pip-tools.pip @@ -4,11 +4,11 @@ # # make upgrade # -build==1.1.1 +build==1.2.1 # via pip-tools click==8.1.7 # via pip-tools -importlib-metadata==7.0.2 +importlib-metadata==7.1.0 # via build packaging==24.0 # via build @@ -25,11 +25,11 @@ tomli==2.0.1 # pyproject-hooks wheel==0.43.0 # via pip-tools -zipp==3.18.0 +zipp==3.18.1 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: pip==24.0 # via pip-tools -setuptools==69.2.0 +setuptools==69.5.1 # via pip-tools diff --git a/requirements/pip.pip b/requirements/pip.pip index 754f62a3f..396a0ee82 100644 --- a/requirements/pip.pip +++ b/requirements/pip.pip @@ -6,15 +6,15 @@ # distlib==0.3.8 # via virtualenv -filelock==3.13.1 +filelock==3.13.4 # via virtualenv platformdirs==4.2.0 # via virtualenv -virtualenv==20.25.1 +virtualenv==20.25.3 # via -r requirements/pip.in # The following packages are considered to be unsafe in a requirements file: pip==24.0 # via -r requirements/pip.in -setuptools==69.2.0 +setuptools==69.5.1 # via -r requirements/pip.in diff --git a/requirements/pytest.pip b/requirements/pytest.pip index 7850ef659..2c0465693 100644 --- a/requirements/pytest.pip +++ b/requirements/pytest.pip @@ -8,21 +8,21 @@ attrs==23.2.0 # via hypothesis colorama==0.4.6 # via -r requirements/pytest.in -exceptiongroup==1.2.0 +exceptiongroup==1.2.1 # via # hypothesis # pytest -execnet==2.0.2 +execnet==2.1.1 # via pytest-xdist flaky==3.8.1 # via -r requirements/pytest.in -hypothesis==6.99.6 +hypothesis==6.100.1 # via -r requirements/pytest.in iniconfig==2.0.0 # via pytest packaging==24.0 # via pytest -pluggy==1.4.0 +pluggy==1.5.0 # via pytest pygments==2.17.2 # via -r requirements/pytest.in diff --git a/requirements/tox.pip b/requirements/tox.pip index b1098c25c..a9beb9d32 100644 --- a/requirements/tox.pip +++ b/requirements/tox.pip @@ -14,7 +14,7 @@ colorama==0.4.6 # tox distlib==0.3.8 # via virtualenv -filelock==3.13.1 +filelock==3.13.4 # via # tox # virtualenv @@ -26,7 +26,7 @@ platformdirs==4.2.0 # via # tox # virtualenv -pluggy==1.4.0 +pluggy==1.5.0 # via tox pyproject-api==1.6.1 # via tox @@ -34,11 +34,11 @@ tomli==2.0.1 # via # pyproject-api # tox -tox==4.14.1 +tox==4.14.2 # via # -r requirements/tox.in # tox-gh tox-gh==1.3.1 # via -r requirements/tox.in -virtualenv==20.25.1 +virtualenv==20.25.3 # via tox diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 35734f30f..8d17860fb 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -239,12 +239,12 @@ def check_coverage( if arcs is not None: # print("Possible arcs:") # print(" expected:", arcs) - # print(" actual:", analysis.arc_possibilities()) + # print(" actual:", analysis.arc_possibilities) # print("Executed:") - # print(" actual:", sorted(set(analysis.arcs_executed()))) + # print(" actual:", sorted(set(analysis.arcs_executed))) # TODO: this would be nicer with pytest-check, once we can run that. msg = ( - self._check_arcs(arcs, analysis.arc_possibilities(), "Possible") + + self._check_arcs(arcs, analysis.arc_possibilities, "Possible") + self._check_arcs(arcs_missing, analysis.arcs_missing(), "Missing") + self._check_arcs(arcs_unpredicted, analysis.arcs_unpredicted(), "Unpredicted") ) @@ -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, @@ -512,7 +513,7 @@ def get_missing_arc_description(self, cov: Coverage, start: TLineNo, end: TLineN assert self.last_module_name is not None filename = self.last_module_name + ".py" fr = cov._get_file_reporter(filename) - arcs_executed = cov._analyze(filename).arcs_executed() + arcs_executed = cov._analyze(filename).arcs_executed return fr.missing_arc_description(start, end, arcs_executed) diff --git a/tests/gold/annotate/anno_dir/d_80084bf2fba02475___init__.py,cover b/tests/gold/annotate/anno_dir/z_80084bf2fba02475___init__.py,cover similarity index 100% rename from tests/gold/annotate/anno_dir/d_80084bf2fba02475___init__.py,cover rename to tests/gold/annotate/anno_dir/z_80084bf2fba02475___init__.py,cover diff --git a/tests/gold/annotate/anno_dir/d_80084bf2fba02475_a.py,cover b/tests/gold/annotate/anno_dir/z_80084bf2fba02475_a.py,cover similarity index 100% rename from tests/gold/annotate/anno_dir/d_80084bf2fba02475_a.py,cover rename to tests/gold/annotate/anno_dir/z_80084bf2fba02475_a.py,cover diff --git a/tests/gold/annotate/anno_dir/d_b039179a8a4ce2c2___init__.py,cover b/tests/gold/annotate/anno_dir/z_b039179a8a4ce2c2___init__.py,cover similarity index 100% rename from tests/gold/annotate/anno_dir/d_b039179a8a4ce2c2___init__.py,cover rename to tests/gold/annotate/anno_dir/z_b039179a8a4ce2c2___init__.py,cover diff --git a/tests/gold/annotate/anno_dir/d_b039179a8a4ce2c2_b.py,cover b/tests/gold/annotate/anno_dir/z_b039179a8a4ce2c2_b.py,cover similarity index 100% rename from tests/gold/annotate/anno_dir/d_b039179a8a4ce2c2_b.py,cover rename to tests/gold/annotate/anno_dir/z_b039179a8a4ce2c2_b.py,cover diff --git a/tests/gold/html/a/a_py.html b/tests/gold/html/a/a_py.html index 740327aec..fcb5ab42f 100644 --- a/tests/gold/html/a/a_py.html +++ b/tests/gold/html/a/a_py.html @@ -1,11 +1,11 @@ - + Coverage for a.py: 67% - +
@@ -17,7 +17,7 @@

@@ -89,12 +89,12 @@

diff --git a/tests/gold/html/a/class_index.html b/tests/gold/html/a/class_index.html new file mode 100644 index 000000000..ccf31848b --- /dev/null +++ b/tests/gold/html/a/class_index.html @@ -0,0 +1,115 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 67% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedbranchespartialcoverageFilestatementsmissingexcludedbranchespartialcoverage
cogapp/__init__.py
cogapp/__init__.py 1 0 00 100.00%
cogapp/__main__.py
cogapp/__main__.py 3 3 00 0.00%
cogapp/cogapp.py
cogapp/cogapp.py 483 228 128 46.74%
cogapp/makefiles.py
cogapp/makefiles.py 22 18 00 11.11%
cogapp/test_cogapp.py
cogapp/test_cogapp.py 854 598 21 29.50%
cogapp/test_makefiles.py
cogapp/test_makefiles.py 68 51 00 22.97%
cogapp/test_whiteutils.py
cogapp/test_whiteutils.py 68 50 00 26.47%
cogapp/utils.py
cogapp/utils.py 37 8 02 76.74%
cogapp/whiteutils.py
cogapp/whiteutils.py 43 5 0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
a.py(no class)31067%
Total 31067%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/a/function_index.html b/tests/gold/html/a/function_index.html new file mode 100644 index 000000000..401081a01 --- /dev/null +++ b/tests/gold/html/a/function_index.html @@ -0,0 +1,115 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 67% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
a.py(no function)31067%
Total 31067%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/a/index.html b/tests/gold/html/a/index.html index d7806cd65..d81014ef2 100644 --- a/tests/gold/html/a/index.html +++ b/tests/gold/html/a/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 16:29 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -53,15 +62,15 @@

Coverage report: - - - - - + + + + + - + @@ -86,16 +95,16 @@

Coverage report: diff --git a/tests/gold/html/b_branch/b_py.html b/tests/gold/html/b_branch/b_py.html index 4b9229d1a..8d78784b7 100644 --- a/tests/gold/html/b_branch/b_py.html +++ b/tests/gold/html/b_branch/b_py.html @@ -1,11 +1,11 @@ - + Coverage for b.py: 70% - +
@@ -17,7 +17,7 @@

@@ -113,12 +113,12 @@

diff --git a/tests/gold/html/b_branch/class_index.html b/tests/gold/html/b_branch/class_index.html new file mode 100644 index 000000000..fc2ea6513 --- /dev/null +++ b/tests/gold/html/b_branch/class_index.html @@ -0,0 +1,123 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
a.py 3 1
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedbranchespartialcoverage
b.py(no class)60000100%
Total 60000100%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/b_branch/function_index.html b/tests/gold/html/b_branch/function_index.html new file mode 100644 index 000000000..2d67b4b04 --- /dev/null +++ b/tests/gold/html/b_branch/function_index.html @@ -0,0 +1,153 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 70% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedbranchespartialcoverage
b.pyone3102160%
b.pytwo2002175%
b.pythree6202250%
b.py(no function)60000100%
Total 17306470%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/b_branch/index.html b/tests/gold/html/b_branch/index.html index 660a15244..cc559c184 100644 --- a/tests/gold/html/b_branch/index.html +++ b/tests/gold/html/b_branch/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 16:29 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -55,17 +64,17 @@

Coverage report: - - - - - - - + + + + + + + - + @@ -94,16 +103,16 @@

Coverage report: diff --git a/tests/gold/html/bom/bom_py.html b/tests/gold/html/bom/bom_py.html index 187fd5235..0c15cc65c 100644 --- a/tests/gold/html/bom/bom_py.html +++ b/tests/gold/html/bom/bom_py.html @@ -1,11 +1,11 @@ - + Coverage for bom.py: 100% - +
@@ -17,7 +17,7 @@

@@ -89,12 +89,12 @@

diff --git a/tests/gold/html/bom/class_index.html b/tests/gold/html/bom/class_index.html new file mode 100644 index 000000000..de80cded8 --- /dev/null +++ b/tests/gold/html/bom/class_index.html @@ -0,0 +1,115 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedbranchespartialcoverageFilestatementsmissingexcludedbranchespartialcoverage
b.py 17 3
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
bom.py(no class)300100%
Total 300100%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/bom/function_index.html b/tests/gold/html/bom/function_index.html new file mode 100644 index 000000000..2e986c4e8 --- /dev/null +++ b/tests/gold/html/bom/function_index.html @@ -0,0 +1,115 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
bom.py(no function)300100%
Total 300100%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/bom/index.html b/tests/gold/html/bom/index.html index faca07f10..0a352c79d 100644 --- a/tests/gold/html/bom/index.html +++ b/tests/gold/html/bom/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v7.2.8a0.dev1, - created at 2023-06-19 21:52 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -53,15 +62,15 @@

Coverage report: - - - - - + + + + + - + @@ -86,16 +95,16 @@

Coverage report: diff --git a/tests/gold/html/contexts/class_index.html b/tests/gold/html/contexts/class_index.html new file mode 100644 index 000000000..1e7915d43 --- /dev/null +++ b/tests/gold/html/contexts/class_index.html @@ -0,0 +1,115 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
bom.py 3 0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
two_tests.py(no class)700100%
Total 700100%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/contexts/function_index.html b/tests/gold/html/contexts/function_index.html new file mode 100644 index 000000000..22f3bb946 --- /dev/null +++ b/tests/gold/html/contexts/function_index.html @@ -0,0 +1,139 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 94% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
two_tests.pyhelper100100%
two_tests.pytest_one200100%
two_tests.pytest_two71086%
two_tests.py(no function)700100%
Total 171094%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/contexts/index.html b/tests/gold/html/contexts/index.html index 05d6a457f..87a44c81f 100644 --- a/tests/gold/html/contexts/index.html +++ b/tests/gold/html/contexts/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v7.2.3a0.dev1, - created at 2023-03-21 08:44 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -53,15 +62,15 @@

Coverage report: - - - - - + + + + + - + @@ -86,16 +95,16 @@

Coverage report: diff --git a/tests/gold/html/contexts/two_tests_py.html b/tests/gold/html/contexts/two_tests_py.html index aadc79767..eefc2cb47 100644 --- a/tests/gold/html/contexts/two_tests_py.html +++ b/tests/gold/html/contexts/two_tests_py.html @@ -1,5 +1,5 @@ - + Coverage for two_tests.py: 94% @@ -12,7 +12,7 @@ "c": "two_tests.test_one" } - +
@@ -24,7 +24,7 @@

1def helper(lineno): 

-

2 x = 2 1acb

+

2 x = 2 1acb

3 

4def test_one(): 

-

5 a = 5 1c

-

6 helper(6) 1c

+

5 a = 5 1c

+

6 helper(6) 1c

7 

8def test_two(): 

-

9 a = 9 1b

-

10 b = 10 1b

-

11 if a > 11: 1b

+

9 a = 9 1b

+

10 b = 10 1b

+

11 if a > 11: 1b

12 b = 12 

-

13 assert a == (13-4) 1b

-

14 assert b == (14-4) 1b

-

15 helper( 1b

+

13 assert a == (13-4) 1b

+

14 assert b == (14-4) 1b

+

15 helper( 1b

16 16 

17 ) 

18 

@@ -113,12 +113,12 @@

diff --git a/tests/gold/html/isolatin1/class_index.html b/tests/gold/html/isolatin1/class_index.html new file mode 100644 index 000000000..4d26befbf --- /dev/null +++ b/tests/gold/html/isolatin1/class_index.html @@ -0,0 +1,115 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
two_tests.py 17 1
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
isolatin1.py(no class)200100%
Total 200100%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/isolatin1/function_index.html b/tests/gold/html/isolatin1/function_index.html new file mode 100644 index 000000000..f4f3c2617 --- /dev/null +++ b/tests/gold/html/isolatin1/function_index.html @@ -0,0 +1,115 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
isolatin1.py(no function)200100%
Total 200100%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/isolatin1/index.html b/tests/gold/html/isolatin1/index.html index bb8bd6ce9..6e6575583 100644 --- a/tests/gold/html/isolatin1/index.html +++ b/tests/gold/html/isolatin1/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 16:29 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -53,15 +62,15 @@

Coverage report: - - - - - + + + + + - + @@ -86,16 +95,16 @@

Coverage report: diff --git a/tests/gold/html/isolatin1/isolatin1_py.html b/tests/gold/html/isolatin1/isolatin1_py.html index 086133239..be73a5296 100644 --- a/tests/gold/html/isolatin1/isolatin1_py.html +++ b/tests/gold/html/isolatin1/isolatin1_py.html @@ -1,11 +1,11 @@ - + Coverage for isolatin1.py: 100% - +
@@ -17,7 +17,7 @@

@@ -89,12 +89,12 @@

diff --git a/tests/gold/html/omit_1/class_index.html b/tests/gold/html/omit_1/class_index.html new file mode 100644 index 000000000..456187154 --- /dev/null +++ b/tests/gold/html/omit_1/class_index.html @@ -0,0 +1,139 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
isolatin1.py 2 0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
m1.py(no class)200100%
m2.py(no class)200100%
m3.py(no class)200100%
main.py(no class)800100%
Total 1400100%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/omit_1/function_index.html b/tests/gold/html/omit_1/function_index.html new file mode 100644 index 000000000..f969c64e7 --- /dev/null +++ b/tests/gold/html/omit_1/function_index.html @@ -0,0 +1,139 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
m1.py(no function)200100%
m2.py(no function)200100%
m3.py(no function)200100%
main.py(no function)800100%
Total 1400100%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/omit_1/index.html b/tests/gold/html/omit_1/index.html index 623ffea43..c54042cd7 100644 --- a/tests/gold/html/omit_1/index.html +++ b/tests/gold/html/omit_1/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 16:29 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -53,36 +62,36 @@

Coverage report: - - - - - + + + + + - + - + - + - + @@ -107,16 +116,16 @@

Coverage report: diff --git a/tests/gold/html/omit_1/m1_py.html b/tests/gold/html/omit_1/m1_py.html index fb31f286d..61c9daafa 100644 --- a/tests/gold/html/omit_1/m1_py.html +++ b/tests/gold/html/omit_1/m1_py.html @@ -1,11 +1,11 @@ - + Coverage for m1.py: 100% - +
@@ -17,7 +17,7 @@

@@ -86,12 +86,12 @@

diff --git a/tests/gold/html/omit_1/m2_py.html b/tests/gold/html/omit_1/m2_py.html index f3e1ac3e7..b223e28fe 100644 --- a/tests/gold/html/omit_1/m2_py.html +++ b/tests/gold/html/omit_1/m2_py.html @@ -1,11 +1,11 @@ - + Coverage for m2.py: 100% - +
@@ -17,7 +17,7 @@

@@ -86,12 +86,12 @@

diff --git a/tests/gold/html/omit_1/m3_py.html b/tests/gold/html/omit_1/m3_py.html index 43fa92dc2..144bd9d91 100644 --- a/tests/gold/html/omit_1/m3_py.html +++ b/tests/gold/html/omit_1/m3_py.html @@ -1,11 +1,11 @@ - + Coverage for m3.py: 100% - +
@@ -17,7 +17,7 @@

@@ -86,12 +86,12 @@

diff --git a/tests/gold/html/omit_1/main_py.html b/tests/gold/html/omit_1/main_py.html index 8323667df..2305a3d10 100644 --- a/tests/gold/html/omit_1/main_py.html +++ b/tests/gold/html/omit_1/main_py.html @@ -1,11 +1,11 @@ - + Coverage for main.py: 100% - +
@@ -17,7 +17,7 @@

@@ -94,12 +94,12 @@

diff --git a/tests/gold/html/omit_2/class_index.html b/tests/gold/html/omit_2/class_index.html new file mode 100644 index 000000000..b6b321fc2 --- /dev/null +++ b/tests/gold/html/omit_2/class_index.html @@ -0,0 +1,131 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
m1.py 2 0 0 100%
m2.py 2 0 0 100%
m3.py 2 0 0 100%
main.py 8 0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
m2.py(no class)200100%
m3.py(no class)200100%
main.py(no class)800100%
Total 1200100%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/omit_2/function_index.html b/tests/gold/html/omit_2/function_index.html new file mode 100644 index 000000000..73a925743 --- /dev/null +++ b/tests/gold/html/omit_2/function_index.html @@ -0,0 +1,131 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
m2.py(no function)200100%
m3.py(no function)200100%
main.py(no function)800100%
Total 1200100%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/omit_2/index.html b/tests/gold/html/omit_2/index.html index 112215f4d..0e8dbaeb7 100644 --- a/tests/gold/html/omit_2/index.html +++ b/tests/gold/html/omit_2/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 16:29 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -53,29 +62,29 @@

Coverage report: - - - - - + + + + + - + - + - + @@ -100,16 +109,16 @@

Coverage report: diff --git a/tests/gold/html/omit_2/m2_py.html b/tests/gold/html/omit_2/m2_py.html index cf7d0cb9b..5d4691152 100644 --- a/tests/gold/html/omit_2/m2_py.html +++ b/tests/gold/html/omit_2/m2_py.html @@ -1,11 +1,11 @@ - + Coverage for m2.py: 100% - +
@@ -17,7 +17,7 @@

@@ -86,12 +86,12 @@

diff --git a/tests/gold/html/omit_2/m3_py.html b/tests/gold/html/omit_2/m3_py.html index 43fa92dc2..144bd9d91 100644 --- a/tests/gold/html/omit_2/m3_py.html +++ b/tests/gold/html/omit_2/m3_py.html @@ -1,11 +1,11 @@ - + Coverage for m3.py: 100% - +
@@ -17,7 +17,7 @@

@@ -86,12 +86,12 @@

diff --git a/tests/gold/html/omit_2/main_py.html b/tests/gold/html/omit_2/main_py.html index 8323667df..2305a3d10 100644 --- a/tests/gold/html/omit_2/main_py.html +++ b/tests/gold/html/omit_2/main_py.html @@ -1,11 +1,11 @@ - + Coverage for main.py: 100% - +
@@ -17,7 +17,7 @@

@@ -94,12 +94,12 @@

diff --git a/tests/gold/html/omit_3/class_index.html b/tests/gold/html/omit_3/class_index.html new file mode 100644 index 000000000..2670b007c --- /dev/null +++ b/tests/gold/html/omit_3/class_index.html @@ -0,0 +1,123 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
m2.py 2 0 0 100%
m3.py 2 0 0 100%
main.py 8 0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
m3.py(no class)200100%
main.py(no class)800100%
Total 1000100%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/omit_3/function_index.html b/tests/gold/html/omit_3/function_index.html new file mode 100644 index 000000000..df17d58ac --- /dev/null +++ b/tests/gold/html/omit_3/function_index.html @@ -0,0 +1,123 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
m3.py(no function)200100%
main.py(no function)800100%
Total 1000100%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/omit_3/index.html b/tests/gold/html/omit_3/index.html index 049f9e793..1290644cb 100644 --- a/tests/gold/html/omit_3/index.html +++ b/tests/gold/html/omit_3/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 16:28 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -53,22 +62,22 @@

Coverage report: - - - - - + + + + + - + - + @@ -93,16 +102,16 @@

Coverage report: diff --git a/tests/gold/html/omit_3/m3_py.html b/tests/gold/html/omit_3/m3_py.html index a9fb1b61d..b436f9c06 100644 --- a/tests/gold/html/omit_3/m3_py.html +++ b/tests/gold/html/omit_3/m3_py.html @@ -1,11 +1,11 @@ - + Coverage for m3.py: 100% - +
@@ -17,7 +17,7 @@

@@ -86,12 +86,12 @@

diff --git a/tests/gold/html/omit_3/main_py.html b/tests/gold/html/omit_3/main_py.html index 36ca955f8..2305a3d10 100644 --- a/tests/gold/html/omit_3/main_py.html +++ b/tests/gold/html/omit_3/main_py.html @@ -1,11 +1,11 @@ - + Coverage for main.py: 100% - +
@@ -17,7 +17,7 @@

@@ -94,12 +94,12 @@

diff --git a/tests/gold/html/omit_4/class_index.html b/tests/gold/html/omit_4/class_index.html new file mode 100644 index 000000000..bfd703ce6 --- /dev/null +++ b/tests/gold/html/omit_4/class_index.html @@ -0,0 +1,131 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
m3.py 2 0 0 100%
main.py 8 0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
m1.py(no class)200100%
m3.py(no class)200100%
main.py(no class)800100%
Total 1200100%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/omit_4/function_index.html b/tests/gold/html/omit_4/function_index.html new file mode 100644 index 000000000..e77284634 --- /dev/null +++ b/tests/gold/html/omit_4/function_index.html @@ -0,0 +1,131 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
m1.py(no function)200100%
m3.py(no function)200100%
main.py(no function)800100%
Total 1200100%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/omit_4/index.html b/tests/gold/html/omit_4/index.html index eb38fcebc..1fc6a295e 100644 --- a/tests/gold/html/omit_4/index.html +++ b/tests/gold/html/omit_4/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 16:29 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -53,29 +62,29 @@

Coverage report: - - - - - + + + + + - + - + - + @@ -100,16 +109,16 @@

Coverage report: diff --git a/tests/gold/html/omit_4/m1_py.html b/tests/gold/html/omit_4/m1_py.html index 661220702..fab4710ea 100644 --- a/tests/gold/html/omit_4/m1_py.html +++ b/tests/gold/html/omit_4/m1_py.html @@ -1,11 +1,11 @@ - + Coverage for m1.py: 100% - +
@@ -17,7 +17,7 @@

@@ -86,12 +86,12 @@

diff --git a/tests/gold/html/omit_4/m3_py.html b/tests/gold/html/omit_4/m3_py.html index adb4e0ab5..f067b59df 100644 --- a/tests/gold/html/omit_4/m3_py.html +++ b/tests/gold/html/omit_4/m3_py.html @@ -1,11 +1,11 @@ - + Coverage for m3.py: 100% - +
@@ -17,7 +17,7 @@

@@ -86,12 +86,12 @@

diff --git a/tests/gold/html/omit_4/main_py.html b/tests/gold/html/omit_4/main_py.html index 8323667df..2305a3d10 100644 --- a/tests/gold/html/omit_4/main_py.html +++ b/tests/gold/html/omit_4/main_py.html @@ -1,11 +1,11 @@ - + Coverage for main.py: 100% - +
@@ -17,7 +17,7 @@

@@ -94,12 +94,12 @@

diff --git a/tests/gold/html/omit_5/class_index.html b/tests/gold/html/omit_5/class_index.html new file mode 100644 index 000000000..bf7a6f8df --- /dev/null +++ b/tests/gold/html/omit_5/class_index.html @@ -0,0 +1,123 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
m1.py 2 0 0 100%
m3.py 2 0 0 100%
main.py 8 0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
m1.py(no class)200100%
main.py(no class)800100%
Total 1000100%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/omit_5/function_index.html b/tests/gold/html/omit_5/function_index.html new file mode 100644 index 000000000..ef49bec8e --- /dev/null +++ b/tests/gold/html/omit_5/function_index.html @@ -0,0 +1,123 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
m1.py(no function)200100%
main.py(no function)800100%
Total 1000100%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/omit_5/index.html b/tests/gold/html/omit_5/index.html index 7e9dacece..8a929a2ff 100644 --- a/tests/gold/html/omit_5/index.html +++ b/tests/gold/html/omit_5/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 16:29 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -53,22 +62,22 @@

Coverage report: - - - - - + + + + + - + - + @@ -93,16 +102,16 @@

Coverage report: diff --git a/tests/gold/html/omit_5/m1_py.html b/tests/gold/html/omit_5/m1_py.html index a628f07e8..99d4e347e 100644 --- a/tests/gold/html/omit_5/m1_py.html +++ b/tests/gold/html/omit_5/m1_py.html @@ -1,11 +1,11 @@ - + Coverage for m1.py: 100% - +
@@ -17,7 +17,7 @@

@@ -86,12 +86,12 @@

diff --git a/tests/gold/html/omit_5/main_py.html b/tests/gold/html/omit_5/main_py.html index 84d7396fb..807d15144 100644 --- a/tests/gold/html/omit_5/main_py.html +++ b/tests/gold/html/omit_5/main_py.html @@ -1,11 +1,11 @@ - + Coverage for main.py: 100% - +
@@ -17,7 +17,7 @@

@@ -94,12 +94,12 @@

diff --git a/tests/gold/html/other/blah_blah_other_py.html b/tests/gold/html/other/blah_blah_other_py.html index 08aa596dc..aa7590e6b 100644 --- a/tests/gold/html/other/blah_blah_other_py.html +++ b/tests/gold/html/other/blah_blah_other_py.html @@ -1,23 +1,23 @@ - + - Coverage for /private/var/folders/10/4sn2sk3j2mg5m116f08_367m0000gq/T/pytest-of-nedbatchelder/pytest-49/popen-gw0/t75/othersrc/other.py: 100% + Coverage for /private/var/folders/6j/khn0mcrj35d1k3yylpl8zl080000gn/T/pytest-of-ned/pytest-259/popen-gw0/t75/othersrc/other.py: 100% - +

- Coverage for /private/var/folders/10/4sn2sk3j2mg5m116f08_367m0000gq/T/pytest-of-nedbatchelder/pytest-49/popen-gw0/t75/othersrc/other.py: + Coverage for /private/var/folders/6j/khn0mcrj35d1k3yylpl8zl080000gn/T/pytest-of-ned/pytest-259/popen-gw0/t75/othersrc/other.py: 100%

@@ -88,12 +88,12 @@

diff --git a/tests/gold/html/other/class_index.html b/tests/gold/html/other/class_index.html new file mode 100644 index 000000000..f03740731 --- /dev/null +++ b/tests/gold/html/other/class_index.html @@ -0,0 +1,123 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 80% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
m1.py 2 0 0 100%
main.py 8 0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
/private/var/folders/6j/khn0mcrj35d1k3yylpl8zl080000gn/T/pytest-of-ned/pytest-259/popen-gw0/t75/othersrc/other.py(no class)100100%
here.py(no class)41075%
Total 51080%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/other/function_index.html b/tests/gold/html/other/function_index.html new file mode 100644 index 000000000..add83b3a1 --- /dev/null +++ b/tests/gold/html/other/function_index.html @@ -0,0 +1,123 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 80% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
/private/var/folders/6j/khn0mcrj35d1k3yylpl8zl080000gn/T/pytest-of-ned/pytest-259/popen-gw0/t75/othersrc/other.py(no function)100100%
here.py(no function)41075%
Total 51080%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/other/here_py.html b/tests/gold/html/other/here_py.html index 9ca80414d..6a9df7634 100644 --- a/tests/gold/html/other/here_py.html +++ b/tests/gold/html/other/here_py.html @@ -1,11 +1,11 @@ - + Coverage for here.py: 75% - +
@@ -17,7 +17,7 @@

@@ -90,12 +90,12 @@

diff --git a/tests/gold/html/other/index.html b/tests/gold/html/other/index.html index 6134c7c3a..8bfa9700b 100644 --- a/tests/gold/html/other/index.html +++ b/tests/gold/html/other/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 16:29 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -53,22 +62,22 @@

Coverage report: - - - - - + + + + + - - + + - + @@ -93,16 +102,16 @@

Coverage report: diff --git a/tests/gold/html/partial/class_index.html b/tests/gold/html/partial/class_index.html new file mode 100644 index 000000000..6ba06b5e8 --- /dev/null +++ b/tests/gold/html/partial/class_index.html @@ -0,0 +1,123 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 91% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
/private/var/folders/10/4sn2sk3j2mg5m116f08_367m0000gq/T/pytest-of-nedbatchelder/pytest-49/popen-gw0/t75/othersrc/other.py
/private/var/folders/6j/khn0mcrj35d1k3yylpl8zl080000gn/T/pytest-of-ned/pytest-259/popen-gw0/t75/othersrc/other.py 1 0 0 100%
here.py 4 1
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedbranchespartialcoverage
partial.py(no class)7014191%
Total 7014191%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/partial/function_index.html b/tests/gold/html/partial/function_index.html new file mode 100644 index 000000000..d9bcbc4a9 --- /dev/null +++ b/tests/gold/html/partial/function_index.html @@ -0,0 +1,123 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 91% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedbranchespartialcoverage
partial.py(no function)7014191%
Total 7014191%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/partial/index.html b/tests/gold/html/partial/index.html index 4c77a2a2a..9ccdd5274 100644 --- a/tests/gold/html/partial/index.html +++ b/tests/gold/html/partial/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 17:08 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -55,17 +64,17 @@

Coverage report: - - - - - - - + + + + + + + - + @@ -94,16 +103,16 @@

Coverage report: diff --git a/tests/gold/html/partial/partial_py.html b/tests/gold/html/partial/partial_py.html index 6ac57a4e9..94c980a7a 100644 --- a/tests/gold/html/partial/partial_py.html +++ b/tests/gold/html/partial/partial_py.html @@ -1,11 +1,11 @@ - + Coverage for partial.py: 91% - +
@@ -17,7 +17,7 @@

@@ -103,12 +103,12 @@

diff --git a/tests/gold/html/partial_626/class_index.html b/tests/gold/html/partial_626/class_index.html new file mode 100644 index 000000000..67792dd0a --- /dev/null +++ b/tests/gold/html/partial_626/class_index.html @@ -0,0 +1,123 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 87% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedbranchespartialcoverageFilestatementsmissingexcludedbranchespartialcoverage
partial.py 7 0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedbranchespartialcoverage
partial.py(no class)9016287%
Total 9016287%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/partial_626/function_index.html b/tests/gold/html/partial_626/function_index.html new file mode 100644 index 000000000..5ba147182 --- /dev/null +++ b/tests/gold/html/partial_626/function_index.html @@ -0,0 +1,123 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 87% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedbranchespartialcoverage
partial.py(no function)9016287%
Total 9016287%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/partial_626/index.html b/tests/gold/html/partial_626/index.html index a34eb2b27..bcadc7cb8 100644 --- a/tests/gold/html/partial_626/index.html +++ b/tests/gold/html/partial_626/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 16:29 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -55,17 +64,17 @@

Coverage report: - - - - - - - + + + + + + + - + @@ -94,16 +103,16 @@

Coverage report: diff --git a/tests/gold/html/partial_626/partial_py.html b/tests/gold/html/partial_626/partial_py.html index ac4e10ede..73e6597fb 100644 --- a/tests/gold/html/partial_626/partial_py.html +++ b/tests/gold/html/partial_626/partial_py.html @@ -1,11 +1,11 @@ - + Coverage for partial.py: 87% - +
@@ -17,7 +17,7 @@

@@ -103,12 +103,12 @@

diff --git a/tests/gold/html/styled/a_py.html b/tests/gold/html/styled/a_py.html index d78c34b2e..5b6585ff0 100644 --- a/tests/gold/html/styled/a_py.html +++ b/tests/gold/html/styled/a_py.html @@ -1,12 +1,12 @@ - + Coverage for a.py: 67% - +
@@ -18,7 +18,7 @@

@@ -90,12 +90,12 @@

diff --git a/tests/gold/html/styled/class_index.html b/tests/gold/html/styled/class_index.html new file mode 100644 index 000000000..ed087f6f1 --- /dev/null +++ b/tests/gold/html/styled/class_index.html @@ -0,0 +1,116 @@ + + + + + Coverage report + + + + + + +
+
+

Coverage report: + 67% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedbranchespartialcoverageFilestatementsmissingexcludedbranchespartialcoverage
partial.py 9 0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
a.py(no class)31067%
Total 31067%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/styled/function_index.html b/tests/gold/html/styled/function_index.html new file mode 100644 index 000000000..fcbeb4724 --- /dev/null +++ b/tests/gold/html/styled/function_index.html @@ -0,0 +1,116 @@ + + + + + Coverage report + + + + + + +
+
+

Coverage report: + 67% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
a.py(no function)31067%
Total 31067%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/styled/index.html b/tests/gold/html/styled/index.html index efa947243..da2d101e7 100644 --- a/tests/gold/html/styled/index.html +++ b/tests/gold/html/styled/index.html @@ -1,12 +1,12 @@ - + Coverage report - +
@@ -17,13 +17,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 16:29 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -54,15 +63,15 @@

Coverage report: - - - - - + + + + + - + @@ -87,16 +96,16 @@

Coverage report: diff --git a/tests/gold/html/styled/style.css b/tests/gold/html/styled/style.css index aec9cbef2..3cdaf05a3 100644 --- a/tests/gold/html/styled/style.css +++ b/tests/gold/html/styled/style.css @@ -22,7 +22,7 @@ td { vertical-align: top; } table tr.hidden { display: none !important; } -p#no_rows { display: none; font-size: 1.2em; } +p#no_rows { display: none; font-size: 1.15em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } a.nav { text-decoration: none; color: inherit; } @@ -40,6 +40,18 @@ header .content { padding: 1rem 3.5rem; } header h2 { margin-top: .5em; font-size: 1em; } +header h2 a.button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header h2 a.button { background: #333; } } + +@media (prefers-color-scheme: dark) { header h2 a.button { border-color: #444; } } + +header h2 a.button.current { border: 2px solid; background: #fff; border-color: #999; cursor: default; } + +@media (prefers-color-scheme: dark) { header h2 a.button.current { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { header h2 a.button.current { border-color: #777; } } + header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; } @media (prefers-color-scheme: dark) { header p.text { color: #aaa; } } @@ -68,19 +80,29 @@ footer .content { padding: 0; color: #666; font-style: italic; } h1 { font-size: 1.25em; display: inline-block; } -#filter_container { float: right; margin: 0 2em 0 0; } +#filter_container { float: right; margin: 0 2em 0 0; line-height: 1.66em; } + +#filter_container #filter { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } -#filter_container input { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } +@media (prefers-color-scheme: dark) { #filter_container #filter { border-color: #444; } } -@media (prefers-color-scheme: dark) { #filter_container input { border-color: #444; } } +@media (prefers-color-scheme: dark) { #filter_container #filter { background: #1e1e1e; } } -@media (prefers-color-scheme: dark) { #filter_container input { background: #1e1e1e; } } +@media (prefers-color-scheme: dark) { #filter_container #filter { color: #eee; } } -@media (prefers-color-scheme: dark) { #filter_container input { color: #eee; } } +#filter_container #filter:focus { border-color: #007acc; } -#filter_container input:focus { border-color: #007acc; } +#filter_container :disabled ~ label { color: #ccc; } -header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; color: inherit; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } +@media (prefers-color-scheme: dark) { #filter_container :disabled ~ label { color: #444; } } + +#filter_container label { font-size: .875em; color: #666; } + +@media (prefers-color-scheme: dark) { #filter_container label { color: #aaa; } } + +header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header button { background: #333; } } @media (prefers-color-scheme: dark) { header button { border-color: #444; } } @@ -266,13 +288,13 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em #index table.index { margin-left: -.5em; } -#index td, #index th { text-align: right; width: 5em; padding: .25em .5em; border-bottom: 1px solid #eee; } +#index td, #index th { text-align: right; padding: .25em .5em; border-bottom: 1px solid #eee; } @media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } } -#index td.name, #index th.name { text-align: left; width: auto; } +#index td.name, #index th.name { text-align: left; width: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; min-width: 15em; } -#index th { font-style: italic; color: #333; cursor: pointer; } +#index th { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-style: italic; color: #333; cursor: pointer; } @media (prefers-color-scheme: dark) { #index th { color: #ddd; } } @@ -280,23 +302,29 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em @media (prefers-color-scheme: dark) { #index th:hover { background: #333; } } +#index th .arrows { color: #666; font-size: 85%; font-family: sans-serif; font-style: normal; pointer-events: none; } + #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { white-space: nowrap; background: #eee; padding-left: .5em; } @media (prefers-color-scheme: dark) { #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background: #333; } } -#index th[aria-sort="ascending"]::after { font-family: sans-serif; content: " ↑"; } +#index th[aria-sort="ascending"] .arrows::after { content: " ▲"; } + +#index th[aria-sort="descending"] .arrows::after { content: " ▼"; } -#index th[aria-sort="descending"]::after { font-family: sans-serif; content: " ↓"; } +#index td.name { font-size: 1.15em; } #index td.name a { text-decoration: none; color: inherit; } +#index td.name .no-noun { font-style: italic; } + #index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-top: 1px solid #ccc; border-bottom: none; } -#index tr.file:hover { background: #eee; } +#index tr.region:hover { background: #eee; } -@media (prefers-color-scheme: dark) { #index tr.file:hover { background: #333; } } +@media (prefers-color-scheme: dark) { #index tr.region:hover { background: #333; } } -#index tr.file:hover td.name { text-decoration: underline; color: inherit; } +#index tr.region:hover td.name { text-decoration: underline; color: inherit; } #scroll_marker { position: fixed; z-index: 3; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; } diff --git a/tests/gold/html/support/coverage_html.js b/tests/gold/html/support/coverage_html.js index 4c321182c..a28c1bef8 100644 --- a/tests/gold/html/support/coverage_html.js +++ b/tests/gold/html/support/coverage_html.js @@ -34,13 +34,14 @@ function on_click(sel, fn) { // Helpers for table sorting function getCellValue(row, column = 0) { - const cell = row.cells[column] + const cell = row.cells[column] // nosemgrep: eslint.detect-object-injection if (cell.childElementCount == 1) { - const child = cell.firstElementChild - if (child instanceof HTMLTimeElement && child.dateTime) { - return child.dateTime - } else if (child instanceof HTMLDataElement && child.value) { - return child.value + var child = cell.firstElementChild; + if (child.tagName === "A") { + child = child.firstElementChild; + } + if (child instanceof HTMLDataElement && child.value) { + return child.value; } } return cell.innerText || cell.textContent; @@ -50,28 +51,37 @@ function rowComparator(rowA, rowB, column = 0) { let valueA = getCellValue(rowA, column); let valueB = getCellValue(rowB, column); if (!isNaN(valueA) && !isNaN(valueB)) { - return valueA - valueB + return valueA - valueB; } return valueA.localeCompare(valueB, undefined, {numeric: true}); } function sortColumn(th) { // Get the current sorting direction of the selected header, - // clear state on other headers and then set the new sorting direction + // clear state on other headers and then set the new sorting direction. const currentSortOrder = th.getAttribute("aria-sort"); [...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none")); + var direction; if (currentSortOrder === "none") { - th.setAttribute("aria-sort", th.dataset.defaultSortOrder || "ascending"); - } else { - th.setAttribute("aria-sort", currentSortOrder === "ascending" ? "descending" : "ascending"); + direction = th.dataset.defaultSortOrder || "ascending"; + } + else if (currentSortOrder === "ascending") { + direction = "descending"; + } + else { + direction = "ascending"; } + th.setAttribute("aria-sort", direction); const column = [...th.parentElement.cells].indexOf(th) - // Sort all rows and afterwards append them in order to move them in the DOM + // Sort all rows and afterwards append them in order to move them in the DOM. Array.from(th.closest("table").querySelectorAll("tbody tr")) - .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (th.getAttribute("aria-sort") === "ascending" ? 1 : -1)) - .forEach(tr => tr.parentElement.appendChild(tr) ); + .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (direction === "ascending" ? 1 : -1)) + .forEach(tr => tr.parentElement.appendChild(tr)); + + // Save the sort order for next time. + localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({column, direction})); } // Find all the elements with data-shortcut attribute, and use them to assign a shortcut key. @@ -96,15 +106,40 @@ coverage.wire_up_filter = function () { const no_rows = document.getElementById("no_rows"); // Observe filter keyevents. - document.getElementById("filter").addEventListener("input", debounce(event => { + const filter_handler = (event => { // Keep running total of each metric, first index contains number of shown rows const totals = new Array(table.rows[0].cells.length).fill(0); // Accumulate the percentage as fraction - totals[totals.length - 1] = { "numer": 0, "denom": 0 }; + totals[totals.length - 1] = { "numer": 0, "denom": 0 }; // nosemgrep: eslint.detect-object-injection + + var text = document.getElementById("filter").value; + const casefold = (text === text.toLowerCase()); + const hide100 = document.getElementById("hide100").checked; // Hide / show elements. table_body_rows.forEach(row => { - if (!row.cells[0].textContent.includes(event.target.value)) { + var show = false; + // Check the text filter. + for (let column = 0; column < totals.length; column++) { + cell = row.cells[column]; + if (cell.classList.contains("name")) { + var celltext = cell.textContent; + if (casefold) { + celltext = celltext.toLowerCase(); + } + if (celltext.includes(text)) { + show = true; + } + } + } + + // Check the "hide covered" filter. + if (show && hide100) { + const [numer, denom] = row.cells[row.cells.length - 1].dataset.ratio.split(" "); + show = (numer !== denom); + } + + if (!show) { // hide row.classList.add("hidden"); return; @@ -114,16 +149,20 @@ coverage.wire_up_filter = function () { row.classList.remove("hidden"); totals[0]++; - for (let column = 1; column < totals.length; column++) { + for (let column = 0; column < totals.length; column++) { // Accumulate dynamic totals - cell = row.cells[column] + cell = row.cells[column] // nosemgrep: eslint.detect-object-injection + if (cell.classList.contains("name")) { + continue; + } if (column === totals.length - 1) { // Last column contains percentage const [numer, denom] = cell.dataset.ratio.split(" "); - totals[column]["numer"] += parseInt(numer, 10); - totals[column]["denom"] += parseInt(denom, 10); - } else { - totals[column] += parseInt(cell.textContent, 10); + totals[column]["numer"] += parseInt(numer, 10); // nosemgrep: eslint.detect-object-injection + totals[column]["denom"] += parseInt(denom, 10); // nosemgrep: eslint.detect-object-injection + } + else { + totals[column] += parseInt(cell.textContent, 10); // nosemgrep: eslint.detect-object-injection } } }); @@ -142,9 +181,12 @@ coverage.wire_up_filter = function () { const footer = table.tFoot.rows[0]; // Calculate new dynamic sum values based on visible rows. - for (let column = 1; column < totals.length; column++) { + for (let column = 0; column < totals.length; column++) { // Get footer cell element. - const cell = footer.cells[column]; + const cell = footer.cells[column]; // nosemgrep: eslint.detect-object-injection + if (cell.classList.contains("name")) { + continue; + } // Set value into dynamic footer cell element. if (column === totals.length - 1) { @@ -152,54 +194,53 @@ coverage.wire_up_filter = function () { // and adapts to the number of decimal places. const match = /\.([0-9]+)/.exec(cell.textContent); const places = match ? match[1].length : 0; - const { numer, denom } = totals[column]; + const { numer, denom } = totals[column]; // nosemgrep: eslint.detect-object-injection cell.dataset.ratio = `${numer} ${denom}`; // Check denom to prevent NaN if filtered files contain no statements cell.textContent = denom ? `${(numer * 100 / denom).toFixed(places)}%` : `${(100).toFixed(places)}%`; - } else { - cell.textContent = totals[column]; + } + else { + cell.textContent = totals[column]; // nosemgrep: eslint.detect-object-injection } } - })); + }); + + document.getElementById("filter").addEventListener("input", debounce(filter_handler)); + document.getElementById("hide100").addEventListener("input", debounce(filter_handler)); // Trigger change event on setup, to force filter on page refresh // (filter value may still be present). document.getElementById("filter").dispatchEvent(new Event("input")); + document.getElementById("hide100").dispatchEvent(new Event("input")); }; -coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; - -// Loaded on index.html -coverage.index_ready = function () { - coverage.assign_shortkeys(); - coverage.wire_up_filter(); +// Set up the click-to-sort columns. +coverage.wire_up_sorting = function () { document.querySelectorAll("[data-sortable] th[aria-sort]").forEach( th => th.addEventListener("click", e => sortColumn(e.target)) ); // Look for a localStorage item containing previous sort settings: + var column = 0, direction = "ascending"; const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); - if (stored_list) { - const {column, direction} = JSON.parse(stored_list); - const th = document.querySelector("[data-sortable]").tHead.rows[0].cells[column]; - th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); - th.click() + ({column, direction} = JSON.parse(stored_list)); } - // Watch for page unload events so we can save the final sort settings: - window.addEventListener("unload", function () { - const th = document.querySelector('[data-sortable] th[aria-sort="ascending"], [data-sortable] [aria-sort="descending"]'); - if (!th) { - return; - } - localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({ - column: [...th.parentElement.cells].indexOf(th), - direction: th.getAttribute("aria-sort"), - })); - }); + const th = document.querySelector("[data-sortable]").tHead.rows[0].cells[column]; // nosemgrep: eslint.detect-object-injection + th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); + th.click() +}; + +coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; + +// Loaded on index.html +coverage.index_ready = function () { + coverage.assign_shortkeys(); + coverage.wire_up_filter(); + coverage.wire_up_sorting(); on_click(".button_prev_file", coverage.to_prev_file); on_click(".button_next_file", coverage.to_next_file); @@ -217,7 +258,8 @@ coverage.pyfile_ready = function () { if (frag.length > 2 && frag[1] === "t") { document.querySelector(frag).closest(".n").classList.add("highlight"); coverage.set_sel(parseInt(frag.substr(2), 10)); - } else { + } + else { coverage.set_sel(0); } @@ -250,7 +292,7 @@ coverage.pyfile_ready = function () { } for (cls in coverage.filters) { - coverage.set_line_visibilty(cls, coverage.filters[cls]); + coverage.set_line_visibilty(cls, coverage.filters[cls]); // nosemgrep: eslint.detect-object-injection } coverage.assign_shortkeys(); @@ -441,7 +483,8 @@ coverage.to_next_chunk_nicely = function () { if (line.parentElement !== document.getElementById("source")) { // The element is not a source line but the header or similar coverage.select_line_or_chunk(1); - } else { + } + else { // We extract the line number from the id coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); } @@ -460,7 +503,8 @@ coverage.to_prev_chunk_nicely = function () { if (line.parentElement !== document.getElementById("source")) { // The element is not a source line but the header or similar coverage.select_line_or_chunk(coverage.lines_len); - } else { + } + else { // We extract the line number from the id coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); } @@ -562,7 +606,8 @@ coverage.build_scroll_markers = function () { if (line_number === previous_line + 1) { // If this solid missed block just make previous mark higher. last_mark.style.height = `${line_top + line_height - last_top}px`; - } else { + } + else { // Add colored line in scroll_marker block. last_mark = document.createElement("div"); last_mark.id = `m${line_number}`; @@ -590,7 +635,8 @@ coverage.wire_up_sticky_header = function () { function updateHeader() { if (window.scrollY > header_bottom) { header.classList.add("sticky"); - } else { + } + else { header.classList.remove("sticky"); } } @@ -618,7 +664,8 @@ coverage.expand_contexts = function (e) { document.addEventListener("DOMContentLoaded", () => { if (document.body.classList.contains("indexfile")) { coverage.index_ready(); - } else { + } + else { coverage.pyfile_ready(); } }); diff --git a/tests/gold/html/support/style.css b/tests/gold/html/support/style.css index 2555fdfee..3cdaf05a3 100644 --- a/tests/gold/html/support/style.css +++ b/tests/gold/html/support/style.css @@ -22,7 +22,7 @@ td { vertical-align: top; } table tr.hidden { display: none !important; } -p#no_rows { display: none; font-size: 1.2em; } +p#no_rows { display: none; font-size: 1.15em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } a.nav { text-decoration: none; color: inherit; } @@ -40,6 +40,18 @@ header .content { padding: 1rem 3.5rem; } header h2 { margin-top: .5em; font-size: 1em; } +header h2 a.button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header h2 a.button { background: #333; } } + +@media (prefers-color-scheme: dark) { header h2 a.button { border-color: #444; } } + +header h2 a.button.current { border: 2px solid; background: #fff; border-color: #999; cursor: default; } + +@media (prefers-color-scheme: dark) { header h2 a.button.current { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { header h2 a.button.current { border-color: #777; } } + header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; } @media (prefers-color-scheme: dark) { header p.text { color: #aaa; } } @@ -68,19 +80,29 @@ footer .content { padding: 0; color: #666; font-style: italic; } h1 { font-size: 1.25em; display: inline-block; } -#filter_container { float: right; margin: 0 2em 0 0; } +#filter_container { float: right; margin: 0 2em 0 0; line-height: 1.66em; } + +#filter_container #filter { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } -#filter_container input { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } +@media (prefers-color-scheme: dark) { #filter_container #filter { border-color: #444; } } -@media (prefers-color-scheme: dark) { #filter_container input { border-color: #444; } } +@media (prefers-color-scheme: dark) { #filter_container #filter { background: #1e1e1e; } } -@media (prefers-color-scheme: dark) { #filter_container input { background: #1e1e1e; } } +@media (prefers-color-scheme: dark) { #filter_container #filter { color: #eee; } } -@media (prefers-color-scheme: dark) { #filter_container input { color: #eee; } } +#filter_container #filter:focus { border-color: #007acc; } -#filter_container input:focus { border-color: #007acc; } +#filter_container :disabled ~ label { color: #ccc; } -header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; color: inherit; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } +@media (prefers-color-scheme: dark) { #filter_container :disabled ~ label { color: #444; } } + +#filter_container label { font-size: .875em; color: #666; } + +@media (prefers-color-scheme: dark) { #filter_container label { color: #aaa; } } + +header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header button { background: #333; } } @media (prefers-color-scheme: dark) { header button { border-color: #444; } } @@ -154,7 +176,7 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em #source p .n.highlight { background: #ffdd00; } -#source p .n a { margin-top: -4em; padding-top: 4em; text-decoration: none; color: #999; } +#source p .n a { scroll-margin-top: 6em; text-decoration: none; color: #999; } @media (prefers-color-scheme: dark) { #source p .n a { color: #777; } } @@ -266,13 +288,13 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em #index table.index { margin-left: -.5em; } -#index td, #index th { text-align: right; width: 5em; padding: .25em .5em; border-bottom: 1px solid #eee; } +#index td, #index th { text-align: right; padding: .25em .5em; border-bottom: 1px solid #eee; } @media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } } -#index td.name, #index th.name { text-align: left; width: auto; } +#index td.name, #index th.name { text-align: left; width: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; min-width: 15em; } -#index th { font-style: italic; color: #333; cursor: pointer; } +#index th { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-style: italic; color: #333; cursor: pointer; } @media (prefers-color-scheme: dark) { #index th { color: #ddd; } } @@ -280,23 +302,29 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em @media (prefers-color-scheme: dark) { #index th:hover { background: #333; } } +#index th .arrows { color: #666; font-size: 85%; font-family: sans-serif; font-style: normal; pointer-events: none; } + #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { white-space: nowrap; background: #eee; padding-left: .5em; } @media (prefers-color-scheme: dark) { #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background: #333; } } -#index th[aria-sort="ascending"]::after { font-family: sans-serif; content: " ↑"; } +#index th[aria-sort="ascending"] .arrows::after { content: " ▲"; } + +#index th[aria-sort="descending"] .arrows::after { content: " ▼"; } -#index th[aria-sort="descending"]::after { font-family: sans-serif; content: " ↓"; } +#index td.name { font-size: 1.15em; } #index td.name a { text-decoration: none; color: inherit; } +#index td.name .no-noun { font-style: italic; } + #index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-top: 1px solid #ccc; border-bottom: none; } -#index tr.file:hover { background: #eee; } +#index tr.region:hover { background: #eee; } -@media (prefers-color-scheme: dark) { #index tr.file:hover { background: #333; } } +@media (prefers-color-scheme: dark) { #index tr.region:hover { background: #333; } } -#index tr.file:hover td.name { text-decoration: underline; color: inherit; } +#index tr.region:hover td.name { text-decoration: underline; color: inherit; } #scroll_marker { position: fixed; z-index: 3; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; } diff --git a/tests/gold/html/unicode/class_index.html b/tests/gold/html/unicode/class_index.html new file mode 100644 index 000000000..e2313d51d --- /dev/null +++ b/tests/gold/html/unicode/class_index.html @@ -0,0 +1,115 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+ +

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
a.py 3 1
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
unicode.py(no class)200100%
Total 200100%
+

+ No items found using the specified filter. +

+ + + + diff --git a/tests/gold/html/unicode/function_index.html b/tests/gold/html/unicode/function_index.html new file mode 100644 index 000000000..669bfde3b --- /dev/null +++ b/tests/gold/html/unicode/function_index.html @@ -0,0 +1,115 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 100% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
unicode.py(no function)200100%
Total 200100%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/gold/html/unicode/index.html b/tests/gold/html/unicode/index.html index 9e98277a0..6dd964250 100644 --- a/tests/gold/html/unicode/index.html +++ b/tests/gold/html/unicode/index.html @@ -1,11 +1,11 @@ - + Coverage report - +
@@ -16,13 +16,13 @@

Coverage report:
- + +
+ + +
+

+ Files + Functions + Classes +

- coverage.py v6.4a0, - created at 2022-05-20 16:28 -0400 + coverage.py v7.5.0a1.dev1, + created at 2024-04-21 10:39 -0400

@@ -53,15 +62,15 @@

Coverage report: - - - - - + + + + + - + @@ -86,16 +95,16 @@

Coverage report: diff --git a/tests/gold/html/unicode/unicode_py.html b/tests/gold/html/unicode/unicode_py.html index 8d2b6cdda..8847845bd 100644 --- a/tests/gold/html/unicode/unicode_py.html +++ b/tests/gold/html/unicode/unicode_py.html @@ -1,11 +1,11 @@ - + Coverage for unicode.py: 100% - +
@@ -17,7 +17,7 @@

@@ -89,12 +89,12 @@

diff --git a/tests/test_debug.py b/tests/test_debug.py index e74a7236a..c818f9308 100644 --- a/tests/test_debug.py +++ b/tests/test_debug.py @@ -179,7 +179,7 @@ def test_debug_config(self) -> None: out_text = self.f1_debug_output(["config"]) labels = """ - attempted_config_files branch config_files_read config_file cover_pylib data_file + branch config_file config_files_attempted config_files_read cover_pylib data_file debug exclude_list extra_css html_dir html_title ignore_errors run_include run_omit parallel partial_always_list partial_list paths precision show_missing source timid xml_output diff --git a/tests/test_files.py b/tests/test_files.py index aac9c4279..ab42fa080 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -109,13 +109,13 @@ def test_source_exists(self, to_make: str, to_check: str, answer: bool) -> None: @pytest.mark.parametrize("original, flat", [ ("abc.py", "abc_py"), ("hellothere", "hellothere"), - ("a/b/c.py", "d_86bbcbe134d28fd2_c_py"), - ("a/b/defghi.py", "d_86bbcbe134d28fd2_defghi_py"), - ("/a/b/c.py", "d_bb25e0ada04227c6_c_py"), - ("/a/b/defghi.py", "d_bb25e0ada04227c6_defghi_py"), - (r"c:\foo\bar.html", "d_e7c107482373f299_bar_html"), - (r"d:\foo\bar.html", "d_584a05dcebc67b46_bar_html"), - ("Montréal/☺/conf.py", "d_c840497a2c647ce0_conf_py"), + ("a/b/c.py", "z_86bbcbe134d28fd2_c_py"), + ("a/b/defghi.py", "z_86bbcbe134d28fd2_defghi_py"), + ("/a/b/c.py", "z_bb25e0ada04227c6_c_py"), + ("/a/b/defghi.py", "z_bb25e0ada04227c6_defghi_py"), + (r"c:\foo\bar.html", "z_e7c107482373f299_bar_html"), + (r"d:\foo\bar.html", "z_584a05dcebc67b46_bar_html"), + ("Montréal/☺/conf.py", "z_c840497a2c647ce0_conf_py"), ( # original: r"c:\lorem\ipsum\quia\dolor\sit\amet\consectetur\adipisci\velit\sed" + r"\quia\non\numquam\eius\modi\tempora\incidunt\ut\labore\et\dolore" + @@ -123,7 +123,7 @@ def test_source_exists(self, to_make: str, to_check: str, answer: bool) -> None: r"\nostrum\exercitationem\ullam\corporis\suscipit\laboriosam" + r"\Montréal\☺\my_program.py", # flat: - "d_e597dfacb73a23d5_my_program_py", + "z_e597dfacb73a23d5_my_program_py", ), ]) def test_flat_rootname(original: str, flat: str) -> None: diff --git a/tests/test_html.py b/tests/test_html.py index cab0bae48..bceaf3efe 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -109,7 +109,7 @@ def assert_correct_timestamp(self, html: str) -> None: ) def assert_valid_hrefs(self) -> None: - """Assert that the hrefs in htmlcov/*.html to see the references are valid. + """Assert that the hrefs in htmlcov/*.html are valid. Doesn't check external links (those with a protocol). """ @@ -125,6 +125,7 @@ def assert_valid_hrefs(self) -> None: continue if "://" in href: continue + href = href.partition("#")[0] # ignore fragment in URLs. hrefs[href].add(fname) for href, sources in hrefs.items(): assert os.path.exists(f"htmlcov/{href}"), ( @@ -179,6 +180,8 @@ def run_coverage( def assert_htmlcov_files_exist(self) -> None: """Assert that all the expected htmlcov files exist.""" self.assert_exists("htmlcov/index.html") + self.assert_exists("htmlcov/function_index.html") + self.assert_exists("htmlcov/class_index.html") self.assert_exists("htmlcov/main_file_py.html") self.assert_exists("htmlcov/helper1_py.html") self.assert_exists("htmlcov/helper2_py.html") @@ -310,7 +313,7 @@ def test_status_format_change(self) -> None: with open("htmlcov/status.json") as status_json: status_data = json.load(status_json) - assert status_data['format'] == 2 + assert status_data['format'] == 5 status_data['format'] = 99 with open("htmlcov/status.json", "w") as status_json: json.dump(status_data, status_json) @@ -616,6 +619,26 @@ def normal(): res = self.run_coverage(covargs=dict(source="."), htmlargs=dict(skip_covered=True)) assert res == 100.0 self.assert_doesnt_exist("htmlcov/main_file_py.html") + # Since there are no files to report, we can't collect any region + # information, so there are no region-based index pages. + self.assert_doesnt_exist("htmlcov/function_index.html") + self.assert_doesnt_exist("htmlcov/class_index.html") + + def test_report_skip_covered_100_functions(self) -> None: + self.make_file("main_file.py", """\ + def normal(): + print("z") + def abnormal(): + print("a") + normal() + """) + res = self.run_coverage(covargs=dict(source="."), htmlargs=dict(skip_covered=True)) + assert res == 80.0 + self.assert_exists("htmlcov/main_file_py.html") + # We have a file to report, so we get function and class index pages, + # even though there are no classes. + self.assert_exists("htmlcov/function_index.html") + self.assert_exists("htmlcov/class_index.html") def make_init_and_main(self) -> None: """Helper to create files for skip_empty scenarios.""" @@ -718,6 +741,10 @@ def test_a(self) -> None: '

', ) + @pytest.mark.skipif( + env.PYPY and env.PYVERSION[:2] == (3, 8), + reason="PyPy 3.8 produces different results!?", + ) def test_b_branch(self) -> None: self.make_file("b.py", """\ def one(x): @@ -937,7 +964,7 @@ def test_other(self) -> None: compare_html( gold_path("html/other"), "out/other", extra_scrubs=[ - (r'href="d_[0-9a-z]{16}_', 'href="_TEST_TMPDIR_othersrc_'), + (r'href="z_[0-9a-z]{16}_', 'href="_TEST_TMPDIR_othersrc_'), ], ) contains( @@ -1129,10 +1156,10 @@ def test_accented_directory(self) -> None: cov = coverage.Coverage() cov.load() cov.html_report() - self.assert_exists("htmlcov/d_5786906b6f0ffeb4_accented_py.html") + self.assert_exists("htmlcov/z_5786906b6f0ffeb4_accented_py.html") with open("htmlcov/index.html") as indexf: index = indexf.read() - expected = 'â%saccented.py' + expected = 'â%saccented.py' assert expected % os.sep in index diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 0fd8cd031..9f0b0cc3f 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -412,8 +412,8 @@ def test_plugin2_with_branch(self) -> None: analysis = cov._analyze("foo_7.html") assert analysis.statements == {1, 2, 3, 4, 5, 6, 7} # Plugins don't do branch coverage yet. - assert analysis.has_arcs() is True - assert analysis.arc_possibilities() == [] + assert analysis.has_arcs is True + assert analysis.arc_possibilities == [] assert analysis.missing == {1, 2, 3, 6, 7} diff --git a/tests/test_regions.py b/tests/test_regions.py new file mode 100644 index 000000000..9bdfa579a --- /dev/null +++ b/tests/test_regions.py @@ -0,0 +1,122 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Tests for coverage/regions.py.""" + +from __future__ import annotations + +import collections +import textwrap +from pathlib import Path + +import pytest + +import coverage +from coverage import env +from coverage.plugin import CodeRegion +from coverage.regions import code_regions + + +skip_pypy38 = pytest.mark.skipif( + env.PYPY and env.PYVERSION < (3, 9), + reason="PyPy 3.8 somehow gets different results from ast?", + # But PyPy 3.8 is almost out of support so meh. +) + +@skip_pypy38 +def test_code_regions() -> None: + regions = code_regions(textwrap.dedent("""\ + # Numbers in this code are the line number. + '''Module docstring''' + + CONST = 4 + class MyClass: + class_attr = 6 + + def __init__(self): + self.x = 9 + + def method_a(self): + self.x = 12 + def inmethod(): + self.x = 14 + class DeepInside: + def method_b(): + self.x = 17 + class Deeper: + def bb(): + self.x = 20 + self.y = 21 + + class InnerClass: + constant = 24 + def method_c(self): + self.x = 26 + + def func(): + x = 29 + y = 30 + def inner(): + z = 32 + def inner_inner(): + w = 34 + + class InsideFunc: + def method_d(self): + self.x = 38 + + return 40 + + async def afunc(): + x = 43 + """)) + + F = "function" + C = "class" + + assert sorted(regions) == sorted([ + CodeRegion(F, "MyClass.__init__", start=8, lines={9}), + CodeRegion(F, "MyClass.method_a", start=11, lines={12, 13, 21}), + CodeRegion(F, "MyClass.method_a.inmethod", start=13, lines={14, 15, 16, 18, 19}), + CodeRegion(F, "MyClass.method_a.inmethod.DeepInside.method_b", start=16, lines={17}), + CodeRegion(F, "MyClass.method_a.inmethod.DeepInside.Deeper.bb", start=19, lines={20}), + CodeRegion(F, "MyClass.InnerClass.method_c", start=25, lines={26}), + CodeRegion(F, "func", start=28, lines={29, 30, 31, 35, 36, 37, 39, 40}), + CodeRegion(F, "func.inner", start=31, lines={32, 33}), + CodeRegion(F, "func.inner.inner_inner", start=33, lines={34}), + CodeRegion(F, "func.InsideFunc.method_d", start=37, lines={38}), + CodeRegion(F, "afunc", start=42, lines={43}), + CodeRegion(C, "MyClass", start=5, lines={9, 12, 13, 14, 15, 16, 18, 19, 21}), + CodeRegion(C, "MyClass.method_a.inmethod.DeepInside", start=15, lines={17}), + CodeRegion(C, "MyClass.method_a.inmethod.DeepInside.Deeper", start=18, lines={20}), + CodeRegion(C, "MyClass.InnerClass", start=23, lines={26}), + CodeRegion(C, "func.InsideFunc", start=36, lines={38}), + ]) + + +@skip_pypy38 +def test_real_code_regions() -> None: + # Run code_regions on most of the coverage source code, checking that it + # succeeds and there are no overlaps. + + cov_dir = Path(coverage.__file__).parent.parent + any_fails = False + # To run against all the files in the tox venvs: + # for source_file in cov_dir.rglob("*.py"): + for sub in [".", "ci", "coverage", "lab", "tests"]: + for source_file in (cov_dir / sub).glob("*.py"): + regions = code_regions(source_file.read_text(encoding="utf-8")) + for kind in ["function", "class"]: + kind_regions = [reg for reg in regions if reg.kind == kind] + line_counts = collections.Counter( + lno for reg in kind_regions for lno in reg.lines + ) + overlaps = [line for line, count in line_counts.items() if count > 1] + if overlaps: # pragma: only failure + print( + f"{kind.title()} overlaps in {source_file.relative_to(Path.cwd())}: " + + f"{overlaps}" + ) + any_fails = True + if any_fails: + pytest.fail("Overlaps were found") # pragma: only failure diff --git a/tests/test_report_common.py b/tests/test_report_common.py index 3828bb6fd..2f0b913b6 100644 --- a/tests/test_report_common.py +++ b/tests/test_report_common.py @@ -222,7 +222,7 @@ def test_html(self) -> None: cov.html_report() contains("htmlcov/index.html", """\ - + diff --git a/tests/test_results.py b/tests/test_results.py index e05824033..ec807c560 100644 --- a/tests/test_results.py +++ b/tests/test_results.py @@ -12,7 +12,7 @@ import pytest from coverage.exceptions import ConfigError -from coverage.results import format_lines, Numbers, should_fail_under +from coverage.results import Numbers, display_covered, format_lines, should_fail_under from coverage.types import TLineNo from tests.coveragetest import CoverageTest @@ -70,15 +70,7 @@ def test_pc_covered_str(self, kwargs: dict[str, int], res: str) -> None: (2, 99.99995, "99.99"), ]) def test_display_covered(self, prec: int, pc: float, res: str) -> None: - assert Numbers(precision=prec).display_covered(pc) == res - - @pytest.mark.parametrize("prec, width", [ - (0, 3), # 100 - (1, 5), # 100.0 - (4, 8), # 100.0000 - ]) - def test_pc_str_width(self, prec: int, width: int) -> None: - assert Numbers(precision=prec).pc_str_width() == width + assert display_covered(pc, prec) == res def test_covered_ratio(self) -> None: n = Numbers(n_files=1, n_statements=200, n_missing=47)
ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
unicode.py 2 067%
good.j2 3 1