diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5c63d1d4a..7fd60783c 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,6 +69,13 @@ jobs: python-version: "pypy-3.9" - os: windows python-version: "pypy-3.10" + # If we need to tweak the os version we can do it with an include like + # this: + # include: + # - python-version: "3.8" + # 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..c2b38953d 100644 --- a/.github/workflows/python-nightly.yml +++ b/.github/workflows/python-nightly.yml @@ -31,32 +31,41 @@ concurrency: jobs: tests: - name: "${{ matrix.python-version }}" - # Choose a recent Ubuntu that deadsnakes still builds all the versions for. - # For example, deadsnakes doesn't provide 3.10 nightly for 22.04 (jammy) - # 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 + name: "${{ matrix.python-version }} on ${{ matrix.os-short }}" + runs-on: "${{ matrix.os }}" # If it doesn't finish in an hour, it's not going to. Don't spin for six # hours needlessly. timeout-minutes: 60 strategy: matrix: + os: + # Choose a recent Ubuntu that deadsnakes still builds all the versions for. + # For example, deadsnakes doesn't provide 3.10 nightly for 22.04 (jammy) + # 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 + # See https://github.com/deadsnakes/nightly for the source of the nightly + # builds. + # bionic: 18, focal: 20, jammy: 22, noble: 24 + - "ubuntu-22.04" + os-short: + - "ubuntu" python-version: # When changing this list, be sure to check the [gh] list in # 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 - "pypy-3.8-nightly" - "pypy-3.9-nightly" - "pypy-3.10-nightly" + include: + - python-version: "pypy-3.10-nightly" + os: "windows-latest" + os-short: "windows" fail-fast: false steps: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 3376aafd1..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" diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml index e6742753e..9a0f8a74a 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: @@ -57,11 +57,19 @@ jobs: exclude: # Windows pypy 3.9 and 3.10 get stuck with PyPy 7.3.15. I hope to # unstick them, but I don't want that to block all other progress, so - # skip them for now. + # skip them for now. These excludes can be removed once GitHub uses + # PyPy 7.3.16 on Windows. https://github.com/pypy/pypy/issues/4876 - os: windows python-version: "pypy-3.9" - os: windows python-version: "pypy-3.10" + # If we need to tweak the os version we can do it with an include like + # this: + # include: + # - python-version: "3.8" + # os: "macos" + # os-version: "13" + fail-fast: false steps: diff --git a/CHANGES.rst b/CHANGES.rst index 15d24ceec..63f26baa8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -25,15 +25,65 @@ Unreleased - Fix: the PYTHONSAFEPATH environment variable new in Python 3.11 is properly supported, closing `issue 1696`_. Thanks, `Philipp A. `_. +- Fix: a pragma comment on the continuation lines of a multi-line statement + now excludes the statement and its body, the same as if the pragma is + on the first line. This closes `issue 754`_. The fix was contributed by + `Daniel Diniz `_. + +- HTML report improvements: + + - Support files (JavaScript and CSS) referenced by the HTML report now have + hashes added to their names to ensure updated files are used instead of + stale cached copies. + + - Missing branch coverage explanations that said "the condition was never + false" now read "the condition was always true" because it's easier to + understand. + + - Column sort order is remembered better as you move between the index pages, + fixing `issue 1766`_. Thanks, `Daniel Diniz `_. + +.. _issue 754: https://github.com/nedbat/coveragepy/issues/754 +.. _issue 1766: https://github.com/nedbat/coveragepy/issues/1766 +.. _pull 1768: https://github.com/nedbat/coveragepy/pull/1768 +.. _pull 1773: https://github.com/nedbat/coveragepy/pull/1773 + + +.. 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! + Finishes `issue 780`_. + +- 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.0a5 is supported. +- Python 3.13.0a6 is supported. +.. _issue 780: https://github.com/nedbat/coveragepy/issues/780 +.. _issue 1384: https://github.com/nedbat/coveragepy/issues/1384 +.. _issue 1765: https://github.com/nedbat/coveragepy/issues/1765 -.. scriv-start-here .. _changes_7-4-4: diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 9c063c20f..809ad01a7 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -63,6 +63,7 @@ Dan Hemberger Dan Riti Dan Wandschneider Danek Duvall +Daniel Diniz Daniel Hahler Danny Allen David Christian @@ -102,6 +103,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 12f8f0e96..c7512ea0c 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ ##@ Utilities -.PHONY: help clean_platform clean sterile +.PHONY: help clean_platform clean sterile install help: ## Show this help. @# Adapted from https://www.thapaliya.com/en/writings/well-documented-makefiles/ @@ -50,6 +50,9 @@ sterile: clean ## Remove all non-controlled content, even if expensive. rm -rf .tox rm -f cheats.txt +install: ## Install the developer tools + python3 -m pip install -r requirements/dev.pip + ##@ Tests and quality checks diff --git a/README.rst b/README.rst index 7ae6fb409..dabdc84fc 100644 --- a/README.rst +++ b/README.rst @@ -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/control.py b/coverage/control.py index b983f4037..dbca2013d 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -48,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, @@ -931,23 +931,15 @@ 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: @@ -977,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 @@ -996,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.""" diff --git a/coverage/files.py b/coverage/files.py index 5fb704350..b2f69a20b 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -87,8 +87,6 @@ def canonical_filename(filename: str) -> str: return CANONICAL_FILENAME_CACHE[filename] -MAX_FLAT = 100 - def flat_rootname(filename: str) -> str: """A base for a flat file name to correspond to this file. diff --git a/coverage/html.py b/coverage/html.py index 8f8ca674b..fcb5ab5ed 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -12,11 +12,10 @@ import json import os import re -import shutil import string -from dataclasses import dataclass -from typing import Any, Iterable, TYPE_CHECKING, TypedDict, cast +from dataclasses import dataclass, field +from typing import Any, Iterable, TYPE_CHECKING import coverage from coverage.data import CoverageData, add_data_to_hash @@ -41,21 +40,6 @@ os = isolate_module(os) -class IndexInfoDict(TypedDict): - """Information for each file, to render the index file.""" - # For in-memory use, we have Numbers. For serialization, we write a list - # of ints. Two fields keeps the type-checker happier. - nums: Numbers | None - numlist: list[int] - html_filename: str - relative_filename: str - -class FileInfoDict(TypedDict): - """Summary of the information from last rendering, to avoid duplicate work.""" - hash: str - index: IndexInfoDict - - def data_filename(fname: str) -> str: """Return the path to an "htmlfiles" data file of ours. """ @@ -83,7 +67,6 @@ class LineData: tokens: list[tuple[str, str]] number: TLineNo category: str - statement: bool contexts: list[str] contexts_label: str context_list: list[str] @@ -104,6 +87,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.""" @@ -112,21 +116,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 = [] @@ -166,7 +170,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, @@ -217,7 +220,6 @@ class HtmlReporter: "style.css", "coverage_html.js", "keybd_closed.png", - "keybd_open.png", "favicon_32.png", ] @@ -232,25 +234,19 @@ 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 - self.extra_css: str | None - if self.config.extra_css: - self.extra_css = os.path.basename(self.config.extra_css) - else: - self.extra_css = None + self.extra_css = bool(self.config.extra_css) 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 @@ -269,6 +265,7 @@ def __init__(self, cov: Coverage) -> None: "extra_css": self.extra_css, "has_arcs": self.has_arcs, "show_contexts": self.config.show_contexts, + "statics": {}, # Constants for all reports. # These css classes determine which lines are highlighted by default. @@ -279,9 +276,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`. @@ -297,13 +307,21 @@ 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) - if self.should_report_file(ftr): + 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)) + if not have_data: + raise NoDataError("No data to report.") + + self.make_directory() + self.make_local_static_report_files() + if files_to_report: for ftr1, ftr2 in zip(files_to_report[:-1], files_to_report[1:]): ftr1.next_html = ftr2.html_filename @@ -311,24 +329,27 @@ def report(self, morfs: Iterable[TMorf] | None) -> float: 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_file(ftr) - - if not self.all_files_nums: - raise NoDataError("No data to report.") - - self.totals = cast(Numbers, sum(self.all_files_nums)) + 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 file. + # 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) - self.make_local_static_report_files() - return self.totals.n_statements and self.totals.pc_covered + # Write function and class index pages. + self.write_region_index_pages(files_to_report) + + 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.""" @@ -336,56 +357,76 @@ def make_directory(self) -> None: if not os.listdir(self.directory): self.directory_was_empty = True + def copy_static_file(self, src: str, slug: str = "") -> None: + """Copy a static file into the output directory with cache busting.""" + with open(src, "rb") as f: + text = f.read() + h = Hasher() + h.update(text) + cache_bust = h.hexdigest()[:8] + src_base = os.path.basename(src) + dest = src_base.replace(".", f"_cb_{cache_bust}.") + if not slug: + slug = src_base.replace(".", "_") + self.template_globals["statics"][slug] = dest # type: ignore + with open(os.path.join(self.directory, dest), "wb") as f: + f.write(text) + def make_local_static_report_files(self) -> None: """Make local instances of static files for HTML report.""" + # The files we provide must always be copied. for static in self.STATIC_FILES: - shutil.copyfile(data_filename(static), os.path.join(self.directory, static)) + self.copy_static_file(data_filename(static)) + + # The user may have extra CSS they want copied. + if self.extra_css: + assert self.config.extra_css is not None + self.copy_static_file(self.config.extra_css, slug="extra_css") # Only write the .gitignore file if the directory was originally empty. - # .gitignore can't be copied from the source tree because it would - # prevent the static files from being checked in. + # .gitignore can't be copied from the source tree because if it was in + # the source tree, it would stop the static files from being checked in. if self.directory_was_empty: with open(os.path.join(self.directory, ".gitignore"), "w") as fgi: fgi.write("# Created by coverage.py\n*\n") - # The user may have extra CSS they want copied. - if self.extra_css: - 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: - 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) -> None: - """Generate an HTML file for one source file.""" - self.make_directory() + 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. - # 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) @@ -461,148 +502,248 @@ def write_html_file(self, ftr: FileToReport) -> None: }) write_html(html_path, html) - # Save this file's information for the index file. - index_info: IndexInfoDict = { - "nums": ftr.analysis.numbers, - "numlist": [], - "html_filename": ftr.html_filename, - "relative_filename": ftr.fr.relative_filename(), - } - 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.""" - self.make_directory() - index_tmpl = Templite(read_data("index.html"), self.template_globals) + def write_file_index_page(self, first_html: str, final_html: str) -> None: + """Write the file index page for this report.""" + 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 n := self.skipped_covered_count: - skipped_covered_msg = f"{n} file{plural(n)} skipped due to complete coverage." - if 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: - # - # { - # "note": "This file is an internal implementation detail ...", - # "format": 2, - # "version": "4.0a1", - # "globals": "540ee119c15d52a68a53fe6f0897346d", - # "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"]["numlist"]) + 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"] - assert index["nums"] is not None - index["numlist"] = list(dataclasses.astuple(index["nums"])) - index["nums"] = None - 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: @@ -610,36 +751,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..0a859a537 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,62 @@ 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. + if (th.id !== "region") { + let th_id = "file"; // Sort by file if we don't have a column id + let current_direction = direction; + const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); + if (stored_list) { + ({th_id, direction} = JSON.parse(stored_list)) + } + localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({ + "th_id": th.id, + "direction": current_direction + })); + if (th.id !== th_id || document.getElementById("region")) { + // Sort column has changed, unset sorting by function or class. + localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({ + "by_region": false, + "region_direction": current_direction + })); + } + } + else { + // Sort column has changed to by function or class, remember that. + localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({ + "by_region": true, + "region_direction": direction + })); + } } // Find all the elements with data-shortcut attribute, and use them to assign a shortcut key. @@ -96,15 +131,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 +174,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 +206,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 +225,68 @@ 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: + let th_id = "file", 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() + ({th_id, direction} = JSON.parse(stored_list)); + } + let by_region = false, region_direction = "ascending"; + const sorted_by_region = localStorage.getItem(coverage.SORTED_BY_REGION); + if (sorted_by_region) { + ({ + by_region, + region_direction + } = JSON.parse(sorted_by_region)); } - // 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 region_id = "region"; + if (by_region && document.getElementById(region_id)) { + direction = region_direction; + } + // If we are in a page that has a column with id of "region", sort on + // it if the last sort was by function or class. + let th; + if (document.getElementById(region_id)) { + th = document.getElementById(by_region ? region_id : th_id); + } + else { + th = document.getElementById(th_id); + } + th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); + th.click() +}; + +coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; +coverage.SORTED_BY_REGION = "COVERAGE_SORT_REGION"; + +// 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 +304,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 +529,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 +549,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 +652,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 +681,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 +710,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..f75d18b43 100644 --- a/coverage/htmlfiles/index.html +++ b/coverage/htmlfiles/index.html @@ -2,16 +2,16 @@ {# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #} - + {{ title|escape }} - - + + {% 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/keybd_open.png b/coverage/htmlfiles/keybd_open.png deleted file mode 100644 index a8bac6c9d..000000000 Binary files a/coverage/htmlfiles/keybd_open.png and /dev/null differ diff --git a/coverage/htmlfiles/pyfile.html b/coverage/htmlfiles/pyfile.html index bc8fa697d..5a6ea43d5 100644 --- a/coverage/htmlfiles/pyfile.html +++ b/coverage/htmlfiles/pyfile.html @@ -2,14 +2,14 @@ {# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #} - + Coverage for {{relative_filename|escape}}: {{nums.pc_covered_str}}% - - + + {% if extra_css %} - + {% endif %} {% if contexts_json %} @@ -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 6ed6852af..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.5a0.dev1, - created at 2024-04-06 12:54 -0400 + coverage.py v7.5.0, + created at 2024-04-23 13:00 -0400

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

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

Cog coverage:

- + @@ -83,7 +92,7 @@

Cog coverage:

- + @@ -92,7 +101,7 @@

Cog coverage:

- + @@ -101,7 +110,7 @@

Cog coverage:

- + @@ -110,7 +119,7 @@

Cog coverage:

- + @@ -119,7 +128,7 @@

Cog coverage:

- + @@ -128,7 +137,7 @@

Cog coverage:

- + @@ -137,7 +146,7 @@

Cog coverage:

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

Cog coverage: diff --git a/doc/sample_html/keybd_open.png b/doc/sample_html/keybd_open.png deleted file mode 100644 index a8bac6c9d..000000000 Binary files a/doc/sample_html/keybd_open.png and /dev/null differ diff --git a/doc/sample_html/status.json b/doc/sample_html/status.json index f5e378359..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.5a0.dev1","globals":"93b51f0c694fe02b0939ee4dd75fe224","files":{"z_7b071bdc2a35fa80___init___py":{"hash":"669207c1fb29be3e8be6ce0639506cab","index":{"nums":null,"numlist":[2,1,1,0,0,0,0,0],"html_filename":"z_7b071bdc2a35fa80___init___py.html","relative_filename":"cogapp/__init__.py"}},"z_7b071bdc2a35fa80___main___py":{"hash":"6d9d0d551879aa3e73791f40c5739845","index":{"nums":null,"numlist":[2,1,3,0,3,0,0,0],"html_filename":"z_7b071bdc2a35fa80___main___py.html","relative_filename":"cogapp/__main__.py"}},"z_7b071bdc2a35fa80_cogapp_py":{"hash":"3b620625b0506d140beda5c04a03a961","index":{"nums":null,"numlist":[2,1,483,1,228,208,28,140],"html_filename":"z_7b071bdc2a35fa80_cogapp_py.html","relative_filename":"cogapp/cogapp.py"}},"z_7b071bdc2a35fa80_makefiles_py":{"hash":"e73ea90ac9a2e7af9d1fdb188ea22dfe","index":{"nums":null,"numlist":[2,1,22,0,18,14,0,14],"html_filename":"z_7b071bdc2a35fa80_makefiles_py.html","relative_filename":"cogapp/makefiles.py"}},"z_7b071bdc2a35fa80_test_cogapp_py":{"hash":"f7e93bafb25b733e134d8c75e2d3cc24","index":{"nums":null,"numlist":[2,1,854,2,598,24,1,21],"html_filename":"z_7b071bdc2a35fa80_test_cogapp_py.html","relative_filename":"cogapp/test_cogapp.py"}},"z_7b071bdc2a35fa80_test_makefiles_py":{"hash":"b2e0cdd09c01a8b0e4bdae02e1cfa856","index":{"nums":null,"numlist":[2,1,68,0,51,6,0,6],"html_filename":"z_7b071bdc2a35fa80_test_makefiles_py.html","relative_filename":"cogapp/test_makefiles.py"}},"z_7b071bdc2a35fa80_test_whiteutils_py":{"hash":"4511a89ca54b865d6397d6b7d315c35c","index":{"nums":null,"numlist":[2,1,68,0,50,0,0,0],"html_filename":"z_7b071bdc2a35fa80_test_whiteutils_py.html","relative_filename":"cogapp/test_whiteutils.py"}},"z_7b071bdc2a35fa80_utils_py":{"hash":"f8df213bfbf38327877cdb9699b0ec41","index":{"nums":null,"numlist":[2,1,37,0,8,6,2,2],"html_filename":"z_7b071bdc2a35fa80_utils_py.html","relative_filename":"cogapp/utils.py"}},"z_7b071bdc2a35fa80_whiteutils_py":{"hash":"c68cfd051fc76896fdb23127cf2ca0ea","index":{"nums":null,"numlist":[2,1,43,0,5,34,4,4],"html_filename":"z_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/z_7b071bdc2a35fa80___init___py.html b/doc/sample_html/z_7b071bdc2a35fa80___init___py.html index 678733dcc..4a59bb133 100644 --- a/doc/sample_html/z_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/z_7b071bdc2a35fa80___main___py.html b/doc/sample_html/z_7b071bdc2a35fa80___main___py.html index 810f8305e..4b20354a2 100644 --- a/doc/sample_html/z_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/z_7b071bdc2a35fa80_cogapp_py.html b/doc/sample_html/z_7b071bdc2a35fa80_cogapp_py.html index dcbf1ae6f..fbef027b8 100644 --- a/doc/sample_html/z_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/z_7b071bdc2a35fa80_makefiles_py.html b/doc/sample_html/z_7b071bdc2a35fa80_makefiles_py.html index 5e43565e6..4efacbdf7 100644 --- a/doc/sample_html/z_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/z_7b071bdc2a35fa80_test_cogapp_py.html b/doc/sample_html/z_7b071bdc2a35fa80_test_cogapp_py.html index 7be2a4f70..b28701909 100644 --- a/doc/sample_html/z_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/z_7b071bdc2a35fa80_test_makefiles_py.html b/doc/sample_html/z_7b071bdc2a35fa80_test_makefiles_py.html index 5e92e3102..f1a0d8d8a 100644 --- a/doc/sample_html/z_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/z_7b071bdc2a35fa80_test_whiteutils_py.html b/doc/sample_html/z_7b071bdc2a35fa80_test_whiteutils_py.html index 660043b4b..94899ce53 100644 --- a/doc/sample_html/z_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/z_7b071bdc2a35fa80_utils_py.html b/doc/sample_html/z_7b071bdc2a35fa80_utils_py.html index b8a0c974d..36a49afc0 100644 --- a/doc/sample_html/z_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/z_7b071bdc2a35fa80_whiteutils_py.html b/doc/sample_html/z_7b071bdc2a35fa80_whiteutils_py.html index d0994d144..d53030223 100644 --- a/doc/sample_html/z_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/metacov.ini b/metacov.ini index 78b80f6da..8e00747ba 100644 --- a/metacov.ini +++ b/metacov.ini @@ -73,6 +73,9 @@ exclude_lines = # longer tested. pragma: obscure + # Lines that will never be called, but satisfy the type checker + pragma: never called + partial_branches = pragma: part covered # A for-loop that always hits its break statement 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..d6478144e 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.1 + # 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.2 # 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.2.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,16 +95,16 @@ packaging==24.0 # pyproject-api # pytest # tox -parso==0.8.3 +parso==0.8.4 # via jedi pkginfo==1.10.0 # via twine -platformdirs==4.2.0 +platformdirs==4.2.1 # via # pylint # tox # virtualenv -pluggy==1.4.0 +pluggy==1.5.0 # via # pytest # tox @@ -114,7 +122,7 @@ pyproject-api==1.6.1 # via tox pyproject-hooks==1.0.0 # via build -pytest==8.1.1 +pytest==8.2.0 # via # -r requirements/pytest.in # pytest-xdist @@ -150,7 +158,7 @@ tomli==2.0.1 # tox tomlkit==0.12.4 # via pylint -tox==4.14.1 +tox==4.15.0 # 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.11 # via # pudb # urwid-readline urwid-readline==0.14 # via pudb -virtualenv==20.25.1 +virtualenv==20.26.0 # 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..9d99b85d2 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,16 +18,16 @@ 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 # auditwheel # build # cibuildwheel -platformdirs==4.2.0 +platformdirs==4.2.1 # via cibuildwheel pyelftools==0.31 # via auditwheel @@ -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..848ea8d1e 100644 --- a/requirements/mypy.pip +++ b/requirements/mypy.pip @@ -8,29 +8,29 @@ 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.2 # via -r requirements/pytest.in iniconfig==2.0.0 # via pytest -mypy==1.9.0 +mypy==1.10.0 # via -r requirements/mypy.in 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 -pytest==8.1.1 +pytest==8.2.0 # via # -r requirements/pytest.in # pytest-xdist @@ -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..c680f1967 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 +platformdirs==4.2.1 # via virtualenv -virtualenv==20.25.1 +virtualenv==20.26.0 # 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..3718cc5af 100644 --- a/requirements/pytest.pip +++ b/requirements/pytest.pip @@ -8,25 +8,25 @@ 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.2 # 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 -pytest==8.1.1 +pytest==8.2.0 # via # -r requirements/pytest.in # pytest-xdist diff --git a/requirements/tox.pip b/requirements/tox.pip index b1098c25c..b2712861d 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 @@ -22,11 +22,11 @@ packaging==24.0 # via # pyproject-api # tox -platformdirs==4.2.0 +platformdirs==4.2.1 # 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.15.0 # via # -r requirements/tox.in # tox-gh tox-gh==1.3.1 # via -r requirements/tox.in -virtualenv==20.25.1 +virtualenv==20.26.0 # via tox diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 1856df89d..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") ) @@ -513,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/html/a/a_py.html b/tests/gold/html/a/a_py.html index 740327aec..911e44771 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..54f6e6c44 --- /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.1a0.dev1, + created at 2024-04-28 13:13 -0300 +

+
+
+
+

ModulestatementsmissingexcludedbranchespartialcoverageFilestatementsmissingexcludedbranchespartialcoverage
cogapp/__init__.py 1 00 100.00%
cogapp/__main__.py 3 30 0.00%
cogapp/cogapp.py 483 22828 46.74%
cogapp/makefiles.py 22 180 11.11%
cogapp/test_cogapp.py 854 5981 29.50%
cogapp/test_makefiles.py 68 510 22.97%
cogapp/test_whiteutils.py 68 500 26.47%
cogapp/utils.py 37 82 76.74%
cogapp/whiteutils.py 43 5
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..2c07a62aa --- /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.1a0.dev1, + created at 2024-04-28 13:13 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..f2b29d815 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.1a0.dev1, + created at 2024-04-25 23:03 -0300

@@ -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..5bbd60cda 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 @@

1def one(x): 

2 # This will be a branch that misses the else. 

-

3 if x < 2: 3 ↛ 6line 3 didn't jump to line 6, because the condition on line 3 was never false

+

3 if x < 2: 3 ↛ 6line 3 didn't jump to line 6, because the condition on line 3 was always true

4 a = 3 

5 else: 

6 a = 4 

@@ -93,7 +93,7 @@

9 

10def two(x): 

11 # A missed else that branches to "exit" 

-

12 if x: 12 ↛ exitline 12 didn't return from function 'two', because the condition on line 12 was never false

+

12 if x: 12 ↛ exitline 12 didn't return from function 'two', because the condition on line 12 was always true

13 a = 5 

14 

15two(1) 

@@ -101,7 +101,7 @@

17def three(): 

18 try: 

19 # This if has two branches, *neither* one taken. 

-

20 if name_error_this_variable_doesnt_exist: 20 ↛ 21,   20 ↛ 232 missed branches: 1) line 20 didn't jump to line 21, because the condition on line 20 was never true, 2) line 20 didn't jump to line 23, because the condition on line 20 was never false

+

20 if name_error_this_variable_doesnt_exist: 20 ↛ 21,   20 ↛ 232 missed branches: 1) line 20 didn't jump to line 21, because the condition on line 20 was never true, 2) line 20 didn't jump to line 23, because the condition on line 20 was always true

21 a = 1 

22 else: 

23 a = 2 

@@ -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..1c76fd240 --- /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.1a0.dev1, + created at 2024-04-28 13:13 -0300 +

+
+
+
+

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..467f4cc38 --- /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.1a0.dev1, + created at 2024-04-28 13:13 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..488f25eae 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.1a0.dev1, + created at 2024-04-25 23:03 -0300

@@ -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..9bc6fa67f 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..d95dcbb27 --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+

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..644766774 --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..7199b32f6 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.1a0.dev1, + created at 2024-04-25 23:03 -0300

@@ -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..15ee3bf05 --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+

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..6b30e63ce --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..f5a791f59 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.1a0.dev1, + created at 2024-04-25 23:02 -0300

@@ -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..68617811a 100644 --- a/tests/gold/html/contexts/two_tests_py.html +++ b/tests/gold/html/contexts/two_tests_py.html @@ -1,10 +1,10 @@ - + Coverage for two_tests.py: 94% - - + + - +
@@ -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..e38567bcd --- /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.1a0.dev1, + created at 2024-04-28 13:13 -0300 +

+
+
+
+

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..5c1585efd --- /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.1a0.dev1, + created at 2024-04-28 13:13 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..89c6d6816 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.1a0.dev1, + created at 2024-04-25 23:02 -0300

@@ -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..22a9a32cb 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..9564621ae --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+

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..5f0191656 --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..8e7b8a0df 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.1a0.dev1, + created at 2024-04-25 23:02 -0300

@@ -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..29335c6f2 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..395739c76 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..7244934c8 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..78d4d9488 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..bd1d6e492 --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+

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..ea47083e9 --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..c1aa13657 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.1a0.dev1, + created at 2024-04-25 23:03 -0300

@@ -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..1fe88a2d1 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..7244934c8 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..78d4d9488 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..5c7acbcf9 --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+

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..f7c6d34e9 --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..1a57ebfd5 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.1a0.dev1, + created at 2024-04-25 23:03 -0300

@@ -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..1dbabb79b 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..78d4d9488 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..f23a9854b --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+

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..225c14190 --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..9b445b809 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.1a0.dev1, + created at 2024-04-25 23:02 -0300

@@ -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..f4a11037d 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..cf002b8a1 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..78d4d9488 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..bf6b1182b --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+

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..2eef10d7c --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..dd2aca1d5 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.1a0.dev1, + created at 2024-04-25 23:02 -0300

@@ -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..48c631f0c 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..f282fa8df 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..8a84c98b8 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-293/t40/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-293/t40/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..7798e9d5e --- /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.1a0.dev1, + created at 2024-04-28 13:17 -0300 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
m1.py 2 0 0 100%
main.py 8 0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
C:\Users\ddini\AppData\Local\Temp\pytest-of-ddini\pytest-9\popen-gw6\t1\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..54dbec6bb --- /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.1a0.dev1, + created at 2024-04-28 13:17 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
C:\Users\ddini\AppData\Local\Temp\pytest-of-ddini\pytest-9\popen-gw6\t1\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 28195c923..bc9669954 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 7bab6a148..26de08aa8 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.1a0.dev1, + created at 2024-04-28 13:17 -0300

@@ -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..e455ca6ba --- /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.1a0.dev1, + created at 2024-04-29 17:40 -0300 +

+
+
+
+

ModulestatementsmissingexcludedcoverageFilestatementsmissingexcludedcoverage
/private/var/folders/10/4sn2sk3j2mg5m116f08_367m0000gq/T/pytest-of-nedbatchelder/pytest-49/popen-gw0/t75/othersrc/other.py
C:\Users\ddini\AppData\Local\Temp\pytest-of-ddini\pytest-9\popen-gw6\t1\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..c2e2d1717 --- /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.1a0.dev1, + created at 2024-04-29 17:40 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..91d5539cf 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.1a0.dev1, + created at 2024-04-29 17:40 -0300

@@ -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..765851e77 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 @@

@@ -85,7 +85,7 @@

1# partial branches and excluded lines 

2a = 2 

3 

-

4while "no peephole".upper(): # t4 4 ↛ 7line 4 didn't jump to line 7, because the condition on line 4 was never false

+

4while "no peephole".upper(): # t4 4 ↛ 7line 4 didn't jump to line 7, because the condition on line 4 was always true

5 break 

6 

7while a: # pragma: no branch 

@@ -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..3fa31cb5c --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+

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..b884070fe --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..f3d921961 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.1a0.dev1, + created at 2024-04-25 23:02 -0300

@@ -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..093456ea9 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 @@

@@ -85,7 +85,7 @@

1# partial branches and excluded lines 

2a = 2 

3 

-

4while "no peephole".upper(): # t4 4 ↛ 7line 4 didn't jump to line 7, because the condition on line 4 was never false

+

4while "no peephole".upper(): # t4 4 ↛ 7line 4 didn't jump to line 7, because the condition on line 4 was always true

5 break 

6 

7while a: # pragma: no branch 

@@ -94,7 +94,7 @@

10if 0: 

11 never_happen() 

12 

-

13if 13: 13 ↛ 16line 13 didn't jump to line 16, because the condition on line 13 was never false

+

13if 13: 13 ↛ 16line 13 didn't jump to line 16, because the condition on line 13 was always true

14 a = 14 

15 

16if a == 16: 

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

diff --git a/tests/gold/html/styled/a_py.html b/tests/gold/html/styled/a_py.html index d78c34b2e..c2f41f7bf 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..f629bad0f --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+

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/extra.css b/tests/gold/html/styled/extra.css deleted file mode 100644 index 46c41fcd3..000000000 --- a/tests/gold/html/styled/extra.css +++ /dev/null @@ -1 +0,0 @@ -/* Doesn't matter what goes in here, it gets copied. */ diff --git a/tests/gold/html/styled/function_index.html b/tests/gold/html/styled/function_index.html new file mode 100644 index 000000000..a8731146f --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..141cebfb6 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.1a0.dev1, + created at 2024-04-25 23:02 -0300

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

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

Coverage report: diff --git a/tests/gold/html/styled/myextra.css b/tests/gold/html/styled/myextra.css new file mode 100644 index 000000000..df7d24353 --- /dev/null +++ b/tests/gold/html/styled/myextra.css @@ -0,0 +1 @@ +/* Doesn't matter what's here, it gets copied. */ 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..0a859a537 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,62 @@ 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. + if (th.id !== "region") { + let th_id = "file"; // Sort by file if we don't have a column id + let current_direction = direction; + const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); + if (stored_list) { + ({th_id, direction} = JSON.parse(stored_list)) + } + localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({ + "th_id": th.id, + "direction": current_direction + })); + if (th.id !== th_id || document.getElementById("region")) { + // Sort column has changed, unset sorting by function or class. + localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({ + "by_region": false, + "region_direction": current_direction + })); + } + } + else { + // Sort column has changed to by function or class, remember that. + localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({ + "by_region": true, + "region_direction": direction + })); + } } // Find all the elements with data-shortcut attribute, and use them to assign a shortcut key. @@ -96,15 +131,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 +174,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 +206,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 +219,74 @@ 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: + let th_id = "file", 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() + ({th_id, direction} = JSON.parse(stored_list)); + } + let by_region = false, region_direction = "ascending"; + const sorted_by_region = localStorage.getItem(coverage.SORTED_BY_REGION); + if (sorted_by_region) { + ({ + by_region, + region_direction + } = JSON.parse(sorted_by_region)); } - // 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 region_id = "region"; + if (by_region && document.getElementById(region_id)) { + direction = region_direction; + } + // If we are in a page that has a column with id of "region", sort on + // it if the last sort was by function or class. + let th; + if (document.getElementById(region_id)) { + th = document.getElementById(by_region ? region_id : th_id); + } + else { + th = document.getElementById(th_id); + } + th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); + th.click() +}; + +coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; +coverage.SORTED_BY_REGION = "COVERAGE_SORT_REGION"; + +// 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 +304,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 +338,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 +529,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 +549,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 +652,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 +681,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 +710,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/keybd_open.png b/tests/gold/html/support/keybd_open.png deleted file mode 100644 index a8bac6c9d..000000000 Binary files a/tests/gold/html/support/keybd_open.png and /dev/null differ 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..c3008963a --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+

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..23ab22d0e --- /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.1a0.dev1, + created at 2024-04-28 13:14 -0300 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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..bba4e6ca9 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.1a0.dev1, + created at 2024-04-25 23:03 -0300

@@ -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..5e28caea1 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_html.py b/tests/test_html.py index bac9ee7ce..1a8c71f95 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -108,13 +108,13 @@ def assert_correct_timestamp(self, html: str) -> None: msg=f"Time stamp is wrong: {timestamp}", ) - def assert_valid_hrefs(self) -> None: - """Assert that the hrefs in htmlcov/*.html to see the references are valid. + def assert_valid_hrefs(self, directory: str = "htmlcov") -> None: + """Assert that the hrefs in htmlcov/*.html are valid. Doesn't check external links (those with a protocol). """ hrefs = collections.defaultdict(set) - for fname in glob.glob("htmlcov/*.html"): + for fname in glob.glob(f"{directory}/*.html"): with open(fname) as fhtml: html = fhtml.read() for href in re.findall(r""" href=['"]([^'"]*)['"]""", html): @@ -125,9 +125,10 @@ 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}"), ( + assert os.path.exists(f"{directory}/{href}"), ( f"These files link to {href!r}, which doesn't exist: {', '.join(sources)}" ) @@ -179,12 +180,21 @@ 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") - self.assert_exists("htmlcov/style.css") - self.assert_exists("htmlcov/coverage_html.js") self.assert_exists("htmlcov/.gitignore") + # Cache-busted files have random data in the name, but they should all + # be there, and there should only be one of each. + statics = ["style.css", "coverage_html.js", "keybd_closed.png", "favicon_32.png"] + files = os.listdir("htmlcov") + for static in statics: + base, ext = os.path.splitext(static) + busted_file_pattern = fr"{base}_cb_\w{{8}}{ext}" + matches = [m for f in files if (m := re.fullmatch(busted_file_pattern, f))] + assert len(matches) == 1, f"Found {len(matches)} files for {static}" def test_html_created(self) -> None: # Test basic HTML generation: files should be created. @@ -310,7 +320,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 +626,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.""" @@ -668,6 +698,8 @@ def compare_html( (r'coverage\.py v[\d.abcdev]+', 'coverage.py vVER'), (r'created at \d\d\d\d-\d\d-\d\d \d\d:\d\d [-+]\d\d\d\d', 'created at DATE'), (r'created at \d\d\d\d-\d\d-\d\d \d\d:\d\d', 'created at DATE'), + # Static files have cache busting. + (r'_cb_\w{8}\.', '_CB.'), # Occasionally an absolute path is in the HTML report. (filepath_to_regex(TESTS_DIR), 'TESTS_DIR'), (filepath_to_regex(flat_rootname(str(TESTS_DIR))), '_TESTS_DIR'), @@ -677,16 +709,28 @@ def compare_html( (filepath_to_regex(abs_file(os.getcwd())), 'TEST_TMPDIR'), (filepath_to_regex(flat_rootname(str(abs_file(os.getcwd())))), '_TEST_TMPDIR'), (r'/private/var/[\w/]+/pytest-of-\w+/pytest-\d+/(popen-gw\d+/)?t\d+', 'TEST_TMPDIR'), + # If the gold files were created on Windows, we need to scrub Windows paths also: + (r'[A-Z]:\\Users\\[\w\\]+\\pytest-of-\w+\\pytest-\d+\\(popen-gw\d+\\)?t\d+', 'TEST_TMPDIR'), ] - if env.WINDOWS: - # For file paths... - scrubs += [(r"\\", "/")] if extra_scrubs: scrubs += extra_scrubs compare(expected, actual, file_pattern="*.html", scrubs=scrubs) -class HtmlGoldTest(CoverageTest): +def unbust(directory: str) -> None: + """Find files with cache busting, and rename them to simple names. + + This makes it possible for us to compare gold files. + """ + with change_dir(directory): + for fname in os.listdir("."): + base, ext = os.path.splitext(fname) + base, _, _ = base.partition("_cb_") + if base != fname: + os.rename(fname, base + ext) + + +class HtmlGoldTest(HtmlTestHelpers, CoverageTest): """Tests of HTML reporting that use gold files.""" def test_a(self) -> None: @@ -718,6 +762,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): @@ -764,17 +812,17 @@ def three(): ('3 ↛ 6' + 'line 3 didn\'t jump to line 6, ' + - 'because the condition on line 3 was never false'), + 'because the condition on line 3 was always true'), ('12 ↛ exit' + 'line 12 didn\'t return from function \'two\', ' + - 'because the condition on line 12 was never false'), + 'because the condition on line 12 was always true'), ('20 ↛ 21,   ' + '20 ↛ 23' + '2 missed branches: ' + '1) line 20 didn\'t jump to line 21, ' + 'because the condition on line 20 was never true, ' + '2) line 20 didn\'t jump to line 23, ' + - 'because the condition on line 20 was never false'), + 'because the condition on line 20 was always true'), ) contains( "out/b_branch/index.html", @@ -937,7 +985,8 @@ def test_other(self) -> None: compare_html( gold_path("html/other"), "out/other", extra_scrubs=[ - (r'href="z_[0-9a-z]{16}_', 'href="_TEST_TMPDIR_othersrc_'), + (r'href="z_[0-9a-z]{16}_other_', 'href="_TEST_TMPDIR_other_othersrc_'), + (r'TEST_TMPDIR\\othersrc\\other.py', 'TEST_TMPDIR/othersrc/other.py'), ], ) contains( @@ -1023,28 +1072,29 @@ def test_styled(self) -> None: a = 4 """) - self.make_file("extra.css", "/* Doesn't matter what goes in here, it gets copied. */\n") + self.make_file("myfile/myextra.css", "/* Doesn't matter what's here, it gets copied. */\n") cov = coverage.Coverage() a = self.start_import_stop(cov, "a") - cov.html_report(a, directory="out/styled", extra_css="extra.css") - + cov.html_report(a, directory="out/styled", extra_css="myfile/myextra.css") + self.assert_valid_hrefs("out/styled") compare_html(gold_path("html/styled"), "out/styled") + unbust("out/styled") compare(gold_path("html/styled"), "out/styled", file_pattern="*.css") - contains( + contains_rx( "out/styled/a_py.html", - '', - ('if1 ' + - '<2'), - (' a= ' + - '3'), - '67%', + r'', + (r'if1 ' + + r'<2'), + (r' a= ' + + r'3'), + r'67%', ) - contains( + contains_rx( "out/styled/index.html", - '', - 'a.py', - '67%', + r'', + r'a.py', + r'67%', ) def test_tabbed(self) -> None: diff --git a/tests/test_parser.py b/tests/test_parser.py index 7f50e55b5..eaaf4ae0a 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -425,6 +425,24 @@ def f(): ) assert parser.statements == {1,7} + def test_multiline_if_no_branch(self) -> None: + # From https://github.com/nedbat/coveragepy/issues/754 + parser = self.parse_text("""\ + if (this_is_a_verylong_boolean_expression == True # pragma: no branch + and another_long_expression and here_another_expression): + do_something() + """, + ) + parser2 = self.parse_text("""\ + if this_is_a_verylong_boolean_expression == True and another_long_expression \\ + and here_another_expression: # pragma: no branch + do_something() + """, + ) + assert parser.statements == parser2.statements == {1, 3} + pragma_re = ".*pragma: no branch.*" + assert parser.lines_matching(pragma_re) == parser2.lines_matching(pragma_re) + def test_excluding_function(self) -> None: parser = self.parse_text("""\ def fn(foo): # nocover @@ -759,7 +777,7 @@ def func10(): """) expected = "line 1 didn't jump to line 2, because the condition on line 1 was never true" assert expected == parser.missing_arc_description(1, 2) - expected = "line 1 didn't jump to line 3, because the condition on line 1 was never false" + expected = "line 1 didn't jump to line 3, because the condition on line 1 was always true" assert expected == parser.missing_arc_description(1, 3) expected = ( "line 6 didn't return from function 'func5', " + @@ -772,7 +790,7 @@ def func10(): assert expected == parser.missing_arc_description(11, 12) expected = ( "line 11 didn't jump to line 13, " + - "because the condition on line 11 was never false" + "because the condition on line 11 was always true" ) assert expected == parser.missing_arc_description(11, 13) diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 0fd8cd031..b3c8cd6f6 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -33,7 +33,7 @@ class NullConfig(TPluginConfig): """A plugin configure thing when we don't really need one.""" def get_plugin_options(self, plugin: str) -> TConfigSectionOut: - return {} + return {} # pragma: never called class FakeConfig(TPluginConfig): @@ -284,7 +284,8 @@ def test_exception_if_plugins_on_pytracer(self) -> None: if testenv.PY_TRACER: core = "PyTracer" - elif testenv.SYS_MON: + else: + assert testenv.SYS_MON core = "SysMonitor" expected_warnings = [ @@ -412,8 +413,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