Add python venv
This commit is contained in:
36
utils/python-venv/Lib/site-packages/coverage/__init__.py
Normal file
36
utils/python-venv/Lib/site-packages/coverage/__init__.py
Normal file
@ -0,0 +1,36 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Code coverage measurement for Python.
|
||||
|
||||
Ned Batchelder
|
||||
https://nedbatchelder.com/code/coverage
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from coverage.version import __version__, __url__, version_info
|
||||
|
||||
from coverage.control import Coverage, process_startup
|
||||
from coverage.data import CoverageData
|
||||
from coverage.exceptions import CoverageException
|
||||
from coverage.plugin import CoveragePlugin, FileTracer, FileReporter
|
||||
from coverage.pytracer import PyTracer
|
||||
|
||||
# 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.
|
||||
import encodings.utf_8 # pylint: disable=wrong-import-position, wrong-import-order
|
||||
|
||||
# Because of the "from coverage.control import fooey" lines at the top of the
|
||||
# file, there's an entry for coverage.coverage in sys.modules, mapped to None.
|
||||
# This makes some inspection tools (like pydoc) unable to find the class
|
||||
# coverage.coverage. So remove that entry.
|
||||
try:
|
||||
del sys.modules['coverage.coverage']
|
||||
except KeyError:
|
||||
pass
|
8
utils/python-venv/Lib/site-packages/coverage/__main__.py
Normal file
8
utils/python-venv/Lib/site-packages/coverage/__main__.py
Normal file
@ -0,0 +1,8 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Coverage.py's main entry point."""
|
||||
|
||||
import sys
|
||||
from coverage.cmdline import main
|
||||
sys.exit(main())
|
104
utils/python-venv/Lib/site-packages/coverage/annotate.py
Normal file
104
utils/python-venv/Lib/site-packages/coverage/annotate.py
Normal file
@ -0,0 +1,104 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Source file annotation for coverage.py."""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from coverage.files import flat_rootname
|
||||
from coverage.misc import ensure_dir, isolate_module
|
||||
from coverage.report import get_analysis_to_report
|
||||
|
||||
os = isolate_module(os)
|
||||
|
||||
|
||||
class AnnotateReporter:
|
||||
"""Generate annotated source files showing line coverage.
|
||||
|
||||
This reporter creates annotated copies of the measured source files. Each
|
||||
.py file is copied as a .py,cover file, with a left-hand margin annotating
|
||||
each line::
|
||||
|
||||
> def h(x):
|
||||
- if 0: #pragma: no cover
|
||||
- pass
|
||||
> if x == 1:
|
||||
! a = 1
|
||||
> else:
|
||||
> a = 2
|
||||
|
||||
> h(2)
|
||||
|
||||
Executed lines use '>', lines not executed use '!', lines excluded from
|
||||
consideration use '-'.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, coverage):
|
||||
self.coverage = coverage
|
||||
self.config = self.coverage.config
|
||||
self.directory = None
|
||||
|
||||
blank_re = re.compile(r"\s*(#|$)")
|
||||
else_re = re.compile(r"\s*else\s*:\s*(#|$)")
|
||||
|
||||
def report(self, morfs, directory=None):
|
||||
"""Run the report.
|
||||
|
||||
See `coverage.report()` for arguments.
|
||||
|
||||
"""
|
||||
self.directory = directory
|
||||
self.coverage.get_data()
|
||||
for fr, analysis in get_analysis_to_report(self.coverage, morfs):
|
||||
self.annotate_file(fr, analysis)
|
||||
|
||||
def annotate_file(self, fr, analysis):
|
||||
"""Annotate a single file.
|
||||
|
||||
`fr` is the FileReporter for the file to annotate.
|
||||
|
||||
"""
|
||||
statements = sorted(analysis.statements)
|
||||
missing = sorted(analysis.missing)
|
||||
excluded = sorted(analysis.excluded)
|
||||
|
||||
if self.directory:
|
||||
ensure_dir(self.directory)
|
||||
dest_file = os.path.join(self.directory, flat_rootname(fr.relative_filename()))
|
||||
if dest_file.endswith("_py"):
|
||||
dest_file = dest_file[:-3] + ".py"
|
||||
dest_file += ",cover"
|
||||
else:
|
||||
dest_file = fr.filename + ",cover"
|
||||
|
||||
with open(dest_file, 'w', encoding='utf-8') as dest:
|
||||
i = j = 0
|
||||
covered = True
|
||||
source = fr.source()
|
||||
for lineno, line in enumerate(source.splitlines(True), start=1):
|
||||
while i < len(statements) and statements[i] < lineno:
|
||||
i += 1
|
||||
while j < len(missing) and missing[j] < lineno:
|
||||
j += 1
|
||||
if i < len(statements) and statements[i] == lineno:
|
||||
covered = j >= len(missing) or missing[j] > lineno
|
||||
if self.blank_re.match(line):
|
||||
dest.write(' ')
|
||||
elif self.else_re.match(line):
|
||||
# Special logic for lines containing only 'else:'.
|
||||
if j >= len(missing):
|
||||
dest.write('> ')
|
||||
elif statements[i] == missing[j]:
|
||||
dest.write('! ')
|
||||
else:
|
||||
dest.write('> ')
|
||||
elif lineno in excluded:
|
||||
dest.write('- ')
|
||||
elif covered:
|
||||
dest.write('> ')
|
||||
else:
|
||||
dest.write('! ')
|
||||
|
||||
dest.write(line)
|
19
utils/python-venv/Lib/site-packages/coverage/bytecode.py
Normal file
19
utils/python-venv/Lib/site-packages/coverage/bytecode.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Bytecode manipulation for coverage.py"""
|
||||
|
||||
import types
|
||||
|
||||
|
||||
def code_objects(code):
|
||||
"""Iterate over all the code objects in `code`."""
|
||||
stack = [code]
|
||||
while stack:
|
||||
# We're going to return the code object on the stack, but first
|
||||
# push its children for later returning.
|
||||
code = stack.pop()
|
||||
for c in code.co_consts:
|
||||
if isinstance(c, types.CodeType):
|
||||
stack.append(c)
|
||||
yield code
|
980
utils/python-venv/Lib/site-packages/coverage/cmdline.py
Normal file
980
utils/python-venv/Lib/site-packages/coverage/cmdline.py
Normal file
@ -0,0 +1,980 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Command-line support for coverage.py."""
|
||||
|
||||
import glob
|
||||
import optparse # pylint: disable=deprecated-module
|
||||
import os
|
||||
import os.path
|
||||
import shlex
|
||||
import sys
|
||||
import textwrap
|
||||
import traceback
|
||||
|
||||
import coverage
|
||||
from coverage import Coverage
|
||||
from coverage import env
|
||||
from coverage.collector import CTracer
|
||||
from coverage.config import CoverageConfig
|
||||
from coverage.control import DEFAULT_DATAFILE
|
||||
from coverage.data import combinable_files, debug_data_file
|
||||
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
|
||||
|
||||
# When adding to this file, alphabetization is important. Look for
|
||||
# "alphabetize" comments throughout.
|
||||
|
||||
class Opts:
|
||||
"""A namespace class for individual options we'll build parsers from."""
|
||||
|
||||
# Keep these entries alphabetized (roughly) by the option name as it
|
||||
# appears on the command line.
|
||||
|
||||
append = optparse.make_option(
|
||||
'-a', '--append', action='store_true',
|
||||
help="Append coverage data to .coverage, otherwise it starts clean each time.",
|
||||
)
|
||||
keep = optparse.make_option(
|
||||
'', '--keep', action='store_true',
|
||||
help="Keep original coverage files, otherwise they are deleted.",
|
||||
)
|
||||
branch = optparse.make_option(
|
||||
'', '--branch', action='store_true',
|
||||
help="Measure branch coverage in addition to statement coverage.",
|
||||
)
|
||||
concurrency = optparse.make_option(
|
||||
'', '--concurrency', action='store', metavar="LIBS",
|
||||
help=(
|
||||
"Properly measure code using a concurrency library. " +
|
||||
"Valid values are: {}, or a comma-list of them."
|
||||
).format(", ".join(sorted(CoverageConfig.CONCURRENCY_CHOICES))),
|
||||
)
|
||||
context = optparse.make_option(
|
||||
'', '--context', action='store', metavar="LABEL",
|
||||
help="The context label to record for this coverage run.",
|
||||
)
|
||||
contexts = optparse.make_option(
|
||||
'', '--contexts', action='store', metavar="REGEX1,REGEX2,...",
|
||||
help=(
|
||||
"Only display data from lines covered in the given contexts. " +
|
||||
"Accepts Python regexes, which must be quoted."
|
||||
),
|
||||
)
|
||||
combine_datafile = optparse.make_option(
|
||||
'', '--data-file', action='store', metavar="DATAFILE",
|
||||
help=(
|
||||
"Base name of the data files to operate on. " +
|
||||
"Defaults to '.coverage'. [env: COVERAGE_FILE]"
|
||||
),
|
||||
)
|
||||
input_datafile = optparse.make_option(
|
||||
'', '--data-file', action='store', metavar="INFILE",
|
||||
help=(
|
||||
"Read coverage data for report generation from this file. " +
|
||||
"Defaults to '.coverage'. [env: COVERAGE_FILE]"
|
||||
),
|
||||
)
|
||||
output_datafile = optparse.make_option(
|
||||
'', '--data-file', action='store', metavar="OUTFILE",
|
||||
help=(
|
||||
"Write the recorded coverage data to this file. " +
|
||||
"Defaults to '.coverage'. [env: COVERAGE_FILE]"
|
||||
),
|
||||
)
|
||||
debug = optparse.make_option(
|
||||
'', '--debug', action='store', metavar="OPTS",
|
||||
help="Debug options, separated by commas. [env: COVERAGE_DEBUG]",
|
||||
)
|
||||
directory = optparse.make_option(
|
||||
'-d', '--directory', action='store', metavar="DIR",
|
||||
help="Write the output files to DIR.",
|
||||
)
|
||||
fail_under = optparse.make_option(
|
||||
'', '--fail-under', action='store', metavar="MIN", type="float",
|
||||
help="Exit with a status of 2 if the total coverage is less than MIN.",
|
||||
)
|
||||
help = optparse.make_option(
|
||||
'-h', '--help', action='store_true',
|
||||
help="Get help on this command.",
|
||||
)
|
||||
ignore_errors = optparse.make_option(
|
||||
'-i', '--ignore-errors', action='store_true',
|
||||
help="Ignore errors while reading source files.",
|
||||
)
|
||||
include = optparse.make_option(
|
||||
'', '--include', action='store', metavar="PAT1,PAT2,...",
|
||||
help=(
|
||||
"Include only files whose paths match one of these patterns. " +
|
||||
"Accepts shell-style wildcards, which must be quoted."
|
||||
),
|
||||
)
|
||||
pylib = optparse.make_option(
|
||||
'-L', '--pylib', action='store_true',
|
||||
help=(
|
||||
"Measure coverage even inside the Python installed library, " +
|
||||
"which isn't done by default."
|
||||
),
|
||||
)
|
||||
show_missing = optparse.make_option(
|
||||
'-m', '--show-missing', action='store_true',
|
||||
help="Show line numbers of statements in each module that weren't executed.",
|
||||
)
|
||||
module = optparse.make_option(
|
||||
'-m', '--module', action='store_true',
|
||||
help=(
|
||||
"<pyfile> is an importable Python module, not a script path, " +
|
||||
"to be run as 'python -m' would run it."
|
||||
),
|
||||
)
|
||||
omit = optparse.make_option(
|
||||
'', '--omit', action='store', metavar="PAT1,PAT2,...",
|
||||
help=(
|
||||
"Omit files whose paths match one of these patterns. " +
|
||||
"Accepts shell-style wildcards, which must be quoted."
|
||||
),
|
||||
)
|
||||
output_xml = optparse.make_option(
|
||||
'-o', '', action='store', dest="outfile", metavar="OUTFILE",
|
||||
help="Write the XML report to this file. Defaults to 'coverage.xml'",
|
||||
)
|
||||
output_json = optparse.make_option(
|
||||
'-o', '', action='store', dest="outfile", metavar="OUTFILE",
|
||||
help="Write the JSON report to this file. Defaults to 'coverage.json'",
|
||||
)
|
||||
output_lcov = optparse.make_option(
|
||||
'-o', '', action='store', dest='outfile', metavar="OUTFILE",
|
||||
help="Write the LCOV report to this file. Defaults to 'coverage.lcov'",
|
||||
)
|
||||
json_pretty_print = optparse.make_option(
|
||||
'', '--pretty-print', action='store_true',
|
||||
help="Format the JSON for human readers.",
|
||||
)
|
||||
parallel_mode = optparse.make_option(
|
||||
'-p', '--parallel-mode', action='store_true',
|
||||
help=(
|
||||
"Append the machine name, process id and random number to the " +
|
||||
"data file name to simplify collecting data from " +
|
||||
"many processes."
|
||||
),
|
||||
)
|
||||
precision = optparse.make_option(
|
||||
'', '--precision', action='store', metavar='N', type=int,
|
||||
help=(
|
||||
"Number of digits after the decimal point to display for " +
|
||||
"reported coverage percentages."
|
||||
),
|
||||
)
|
||||
quiet = optparse.make_option(
|
||||
'-q', '--quiet', action='store_true',
|
||||
help="Don't print messages about what is happening.",
|
||||
)
|
||||
rcfile = optparse.make_option(
|
||||
'', '--rcfile', action='store',
|
||||
help=(
|
||||
"Specify configuration file. " +
|
||||
"By default '.coveragerc', 'setup.cfg', 'tox.ini', and " +
|
||||
"'pyproject.toml' are tried. [env: COVERAGE_RCFILE]"
|
||||
),
|
||||
)
|
||||
show_contexts = optparse.make_option(
|
||||
'--show-contexts', action='store_true',
|
||||
help="Show contexts for covered lines.",
|
||||
)
|
||||
skip_covered = optparse.make_option(
|
||||
'--skip-covered', action='store_true',
|
||||
help="Skip files with 100% coverage.",
|
||||
)
|
||||
no_skip_covered = optparse.make_option(
|
||||
'--no-skip-covered', action='store_false', dest='skip_covered',
|
||||
help="Disable --skip-covered.",
|
||||
)
|
||||
skip_empty = optparse.make_option(
|
||||
'--skip-empty', action='store_true',
|
||||
help="Skip files with no code.",
|
||||
)
|
||||
sort = optparse.make_option(
|
||||
'--sort', action='store', metavar='COLUMN',
|
||||
help=(
|
||||
"Sort the report by the named column: name, stmts, miss, branch, brpart, or cover. " +
|
||||
"Default is name."
|
||||
),
|
||||
)
|
||||
source = optparse.make_option(
|
||||
'', '--source', action='store', metavar="SRC1,SRC2,...",
|
||||
help="A list of directories or importable names of code to measure.",
|
||||
)
|
||||
timid = optparse.make_option(
|
||||
'', '--timid', action='store_true',
|
||||
help=(
|
||||
"Use a simpler but slower trace method. Try this if you get " +
|
||||
"seemingly impossible results!"
|
||||
),
|
||||
)
|
||||
title = optparse.make_option(
|
||||
'', '--title', action='store', metavar="TITLE",
|
||||
help="A text string to use as the title on the HTML.",
|
||||
)
|
||||
version = optparse.make_option(
|
||||
'', '--version', action='store_true',
|
||||
help="Display version information and exit.",
|
||||
)
|
||||
|
||||
|
||||
class CoverageOptionParser(optparse.OptionParser):
|
||||
"""Base OptionParser for coverage.py.
|
||||
|
||||
Problems don't exit the program.
|
||||
Defaults are initialized for all options.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(add_help_option=False, *args, **kwargs)
|
||||
self.set_defaults(
|
||||
# Keep these arguments alphabetized by their names.
|
||||
action=None,
|
||||
append=None,
|
||||
branch=None,
|
||||
concurrency=None,
|
||||
context=None,
|
||||
contexts=None,
|
||||
data_file=None,
|
||||
debug=None,
|
||||
directory=None,
|
||||
fail_under=None,
|
||||
help=None,
|
||||
ignore_errors=None,
|
||||
include=None,
|
||||
keep=None,
|
||||
module=None,
|
||||
omit=None,
|
||||
parallel_mode=None,
|
||||
precision=None,
|
||||
pylib=None,
|
||||
quiet=None,
|
||||
rcfile=True,
|
||||
show_contexts=None,
|
||||
show_missing=None,
|
||||
skip_covered=None,
|
||||
skip_empty=None,
|
||||
sort=None,
|
||||
source=None,
|
||||
timid=None,
|
||||
title=None,
|
||||
version=None,
|
||||
)
|
||||
|
||||
self.disable_interspersed_args()
|
||||
|
||||
class OptionParserError(Exception):
|
||||
"""Used to stop the optparse error handler ending the process."""
|
||||
pass
|
||||
|
||||
def parse_args_ok(self, args=None, options=None):
|
||||
"""Call optparse.parse_args, but return a triple:
|
||||
|
||||
(ok, options, args)
|
||||
|
||||
"""
|
||||
try:
|
||||
options, args = super().parse_args(args, options)
|
||||
except self.OptionParserError:
|
||||
return False, None, None
|
||||
return True, options, args
|
||||
|
||||
def error(self, msg):
|
||||
"""Override optparse.error so sys.exit doesn't get called."""
|
||||
show_help(msg)
|
||||
raise self.OptionParserError
|
||||
|
||||
|
||||
class GlobalOptionParser(CoverageOptionParser):
|
||||
"""Command-line parser for coverage.py global option arguments."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.add_options([
|
||||
Opts.help,
|
||||
Opts.version,
|
||||
])
|
||||
|
||||
|
||||
class CmdOptionParser(CoverageOptionParser):
|
||||
"""Parse one of the new-style commands for coverage.py."""
|
||||
|
||||
def __init__(self, action, options, defaults=None, usage=None, description=None):
|
||||
"""Create an OptionParser for a coverage.py command.
|
||||
|
||||
`action` is the slug to put into `options.action`.
|
||||
`options` is a list of Option's for the command.
|
||||
`defaults` is a dict of default value for options.
|
||||
`usage` is the usage string to display in help.
|
||||
`description` is the description of the command, for the help text.
|
||||
|
||||
"""
|
||||
if usage:
|
||||
usage = "%prog " + usage
|
||||
super().__init__(
|
||||
usage=usage,
|
||||
description=description,
|
||||
)
|
||||
self.set_defaults(action=action, **(defaults or {}))
|
||||
self.add_options(options)
|
||||
self.cmd = action
|
||||
|
||||
def __eq__(self, other):
|
||||
# A convenience equality, so that I can put strings in unit test
|
||||
# results, and they will compare equal to objects.
|
||||
return (other == f"<CmdOptionParser:{self.cmd}>")
|
||||
|
||||
__hash__ = None # This object doesn't need to be hashed.
|
||||
|
||||
def get_prog_name(self):
|
||||
"""Override of an undocumented function in optparse.OptionParser."""
|
||||
program_name = super().get_prog_name()
|
||||
|
||||
# Include the sub-command for this parser as part of the command.
|
||||
return f"{program_name} {self.cmd}"
|
||||
|
||||
# In lists of Opts, keep them alphabetized by the option names as they appear
|
||||
# on the command line, since these lists determine the order of the options in
|
||||
# the help output.
|
||||
#
|
||||
# In COMMANDS, keep the keys (command names) alphabetized.
|
||||
|
||||
GLOBAL_ARGS = [
|
||||
Opts.debug,
|
||||
Opts.help,
|
||||
Opts.rcfile,
|
||||
]
|
||||
|
||||
COMMANDS = {
|
||||
'annotate': CmdOptionParser(
|
||||
"annotate",
|
||||
[
|
||||
Opts.directory,
|
||||
Opts.input_datafile,
|
||||
Opts.ignore_errors,
|
||||
Opts.include,
|
||||
Opts.omit,
|
||||
] + GLOBAL_ARGS,
|
||||
usage="[options] [modules]",
|
||||
description=(
|
||||
"Make annotated copies of the given files, marking statements that are executed " +
|
||||
"with > and statements that are missed with !."
|
||||
),
|
||||
),
|
||||
|
||||
'combine': CmdOptionParser(
|
||||
"combine",
|
||||
[
|
||||
Opts.append,
|
||||
Opts.combine_datafile,
|
||||
Opts.keep,
|
||||
Opts.quiet,
|
||||
] + GLOBAL_ARGS,
|
||||
usage="[options] <path1> <path2> ... <pathN>",
|
||||
description=(
|
||||
"Combine data from multiple coverage files collected " +
|
||||
"with 'run -p'. The combined results are written to a single " +
|
||||
"file representing the union of the data. The positional " +
|
||||
"arguments are data files or directories containing data files. " +
|
||||
"If no paths are provided, data files in the default data file's " +
|
||||
"directory are combined."
|
||||
),
|
||||
),
|
||||
|
||||
'debug': CmdOptionParser(
|
||||
"debug", GLOBAL_ARGS,
|
||||
usage="<topic>",
|
||||
description=(
|
||||
"Display information about the internals of coverage.py, " +
|
||||
"for diagnosing problems. " +
|
||||
"Topics are: " +
|
||||
"'data' to show a summary of the collected data; " +
|
||||
"'sys' to show installation information; " +
|
||||
"'config' to show the configuration; " +
|
||||
"'premain' to show what is calling coverage; " +
|
||||
"'pybehave' to show internal flags describing Python behavior."
|
||||
),
|
||||
),
|
||||
|
||||
'erase': CmdOptionParser(
|
||||
"erase",
|
||||
[
|
||||
Opts.combine_datafile
|
||||
] + GLOBAL_ARGS,
|
||||
description="Erase previously collected coverage data.",
|
||||
),
|
||||
|
||||
'help': CmdOptionParser(
|
||||
"help", GLOBAL_ARGS,
|
||||
usage="[command]",
|
||||
description="Describe how to use coverage.py",
|
||||
),
|
||||
|
||||
'html': CmdOptionParser(
|
||||
"html",
|
||||
[
|
||||
Opts.contexts,
|
||||
Opts.directory,
|
||||
Opts.input_datafile,
|
||||
Opts.fail_under,
|
||||
Opts.ignore_errors,
|
||||
Opts.include,
|
||||
Opts.omit,
|
||||
Opts.precision,
|
||||
Opts.quiet,
|
||||
Opts.show_contexts,
|
||||
Opts.skip_covered,
|
||||
Opts.no_skip_covered,
|
||||
Opts.skip_empty,
|
||||
Opts.title,
|
||||
] + GLOBAL_ARGS,
|
||||
usage="[options] [modules]",
|
||||
description=(
|
||||
"Create an HTML report of the coverage of the files. " +
|
||||
"Each file gets its own page, with the source decorated to show " +
|
||||
"executed, excluded, and missed lines."
|
||||
),
|
||||
),
|
||||
|
||||
'json': CmdOptionParser(
|
||||
"json",
|
||||
[
|
||||
Opts.contexts,
|
||||
Opts.input_datafile,
|
||||
Opts.fail_under,
|
||||
Opts.ignore_errors,
|
||||
Opts.include,
|
||||
Opts.omit,
|
||||
Opts.output_json,
|
||||
Opts.json_pretty_print,
|
||||
Opts.quiet,
|
||||
Opts.show_contexts,
|
||||
] + GLOBAL_ARGS,
|
||||
usage="[options] [modules]",
|
||||
description="Generate a JSON report of coverage results.",
|
||||
),
|
||||
|
||||
'lcov': CmdOptionParser(
|
||||
"lcov",
|
||||
[
|
||||
Opts.input_datafile,
|
||||
Opts.fail_under,
|
||||
Opts.ignore_errors,
|
||||
Opts.include,
|
||||
Opts.output_lcov,
|
||||
Opts.omit,
|
||||
Opts.quiet,
|
||||
] + GLOBAL_ARGS,
|
||||
usage="[options] [modules]",
|
||||
description="Generate an LCOV report of coverage results.",
|
||||
),
|
||||
|
||||
'report': CmdOptionParser(
|
||||
"report",
|
||||
[
|
||||
Opts.contexts,
|
||||
Opts.input_datafile,
|
||||
Opts.fail_under,
|
||||
Opts.ignore_errors,
|
||||
Opts.include,
|
||||
Opts.omit,
|
||||
Opts.precision,
|
||||
Opts.sort,
|
||||
Opts.show_missing,
|
||||
Opts.skip_covered,
|
||||
Opts.no_skip_covered,
|
||||
Opts.skip_empty,
|
||||
] + GLOBAL_ARGS,
|
||||
usage="[options] [modules]",
|
||||
description="Report coverage statistics on modules.",
|
||||
),
|
||||
|
||||
'run': CmdOptionParser(
|
||||
"run",
|
||||
[
|
||||
Opts.append,
|
||||
Opts.branch,
|
||||
Opts.concurrency,
|
||||
Opts.context,
|
||||
Opts.output_datafile,
|
||||
Opts.include,
|
||||
Opts.module,
|
||||
Opts.omit,
|
||||
Opts.pylib,
|
||||
Opts.parallel_mode,
|
||||
Opts.source,
|
||||
Opts.timid,
|
||||
] + GLOBAL_ARGS,
|
||||
usage="[options] <pyfile> [program options]",
|
||||
description="Run a Python program, measuring code execution.",
|
||||
),
|
||||
|
||||
'xml': CmdOptionParser(
|
||||
"xml",
|
||||
[
|
||||
Opts.input_datafile,
|
||||
Opts.fail_under,
|
||||
Opts.ignore_errors,
|
||||
Opts.include,
|
||||
Opts.omit,
|
||||
Opts.output_xml,
|
||||
Opts.quiet,
|
||||
Opts.skip_empty,
|
||||
] + GLOBAL_ARGS,
|
||||
usage="[options] [modules]",
|
||||
description="Generate an XML report of coverage results.",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def show_help(error=None, topic=None, parser=None):
|
||||
"""Display an error message, or the named topic."""
|
||||
assert error or topic or parser
|
||||
|
||||
program_path = sys.argv[0]
|
||||
if program_path.endswith(os.path.sep + '__main__.py'):
|
||||
# The path is the main module of a package; get that path instead.
|
||||
program_path = os.path.dirname(program_path)
|
||||
program_name = os.path.basename(program_path)
|
||||
if env.WINDOWS:
|
||||
# entry_points={'console_scripts':...} on Windows makes files
|
||||
# called coverage.exe, coverage3.exe, and coverage-3.5.exe. These
|
||||
# invoke coverage-script.py, coverage3-script.py, and
|
||||
# coverage-3.5-script.py. argv[0] is the .py file, but we want to
|
||||
# get back to the original form.
|
||||
auto_suffix = "-script.py"
|
||||
if program_name.endswith(auto_suffix):
|
||||
program_name = program_name[:-len(auto_suffix)]
|
||||
|
||||
help_params = dict(coverage.__dict__)
|
||||
help_params['program_name'] = program_name
|
||||
if CTracer is not None:
|
||||
help_params['extension_modifier'] = 'with C extension'
|
||||
else:
|
||||
help_params['extension_modifier'] = 'without C extension'
|
||||
|
||||
if error:
|
||||
print(error, file=sys.stderr)
|
||||
print(f"Use '{program_name} help' for help.", file=sys.stderr)
|
||||
elif parser:
|
||||
print(parser.format_help().strip())
|
||||
print()
|
||||
else:
|
||||
help_msg = textwrap.dedent(HELP_TOPICS.get(topic, '')).strip()
|
||||
if help_msg:
|
||||
print(help_msg.format(**help_params))
|
||||
else:
|
||||
print(f"Don't know topic {topic!r}")
|
||||
print("Full documentation is at {__url__}".format(**help_params))
|
||||
|
||||
|
||||
OK, ERR, FAIL_UNDER = 0, 1, 2
|
||||
|
||||
|
||||
class CoverageScript:
|
||||
"""The command-line interface to coverage.py."""
|
||||
|
||||
def __init__(self):
|
||||
self.global_option = False
|
||||
self.coverage = None
|
||||
|
||||
def command_line(self, argv):
|
||||
"""The bulk of the command line interface to coverage.py.
|
||||
|
||||
`argv` is the argument list to process.
|
||||
|
||||
Returns 0 if all is well, 1 if something went wrong.
|
||||
|
||||
"""
|
||||
# Collect the command-line options.
|
||||
if not argv:
|
||||
show_help(topic='minimum_help')
|
||||
return OK
|
||||
|
||||
# The command syntax we parse depends on the first argument. Global
|
||||
# switch syntax always starts with an option.
|
||||
self.global_option = argv[0].startswith('-')
|
||||
if self.global_option:
|
||||
parser = GlobalOptionParser()
|
||||
else:
|
||||
parser = COMMANDS.get(argv[0])
|
||||
if not parser:
|
||||
show_help(f"Unknown command: {argv[0]!r}")
|
||||
return ERR
|
||||
argv = argv[1:]
|
||||
|
||||
ok, options, args = parser.parse_args_ok(argv)
|
||||
if not ok:
|
||||
return ERR
|
||||
|
||||
# Handle help and version.
|
||||
if self.do_help(options, args, parser):
|
||||
return OK
|
||||
|
||||
# Listify the list options.
|
||||
source = unshell_list(options.source)
|
||||
omit = unshell_list(options.omit)
|
||||
include = unshell_list(options.include)
|
||||
debug = unshell_list(options.debug)
|
||||
contexts = unshell_list(options.contexts)
|
||||
|
||||
if options.concurrency is not None:
|
||||
concurrency = options.concurrency.split(",")
|
||||
else:
|
||||
concurrency = None
|
||||
|
||||
# Do something.
|
||||
self.coverage = Coverage(
|
||||
data_file=options.data_file or DEFAULT_DATAFILE,
|
||||
data_suffix=options.parallel_mode,
|
||||
cover_pylib=options.pylib,
|
||||
timid=options.timid,
|
||||
branch=options.branch,
|
||||
config_file=options.rcfile,
|
||||
source=source,
|
||||
omit=omit,
|
||||
include=include,
|
||||
debug=debug,
|
||||
concurrency=concurrency,
|
||||
check_preimported=True,
|
||||
context=options.context,
|
||||
messages=not options.quiet,
|
||||
)
|
||||
|
||||
if options.action == "debug":
|
||||
return self.do_debug(args)
|
||||
|
||||
elif options.action == "erase":
|
||||
self.coverage.erase()
|
||||
return OK
|
||||
|
||||
elif options.action == "run":
|
||||
return self.do_run(options, args)
|
||||
|
||||
elif options.action == "combine":
|
||||
if options.append:
|
||||
self.coverage.load()
|
||||
data_paths = args or None
|
||||
self.coverage.combine(data_paths, strict=True, keep=bool(options.keep))
|
||||
self.coverage.save()
|
||||
return OK
|
||||
|
||||
# Remaining actions are reporting, with some common options.
|
||||
report_args = dict(
|
||||
morfs=unglob_args(args),
|
||||
ignore_errors=options.ignore_errors,
|
||||
omit=omit,
|
||||
include=include,
|
||||
contexts=contexts,
|
||||
)
|
||||
|
||||
# We need to be able to import from the current directory, because
|
||||
# plugins may try to, for example, to read Django settings.
|
||||
sys.path.insert(0, '')
|
||||
|
||||
self.coverage.load()
|
||||
|
||||
total = None
|
||||
if options.action == "report":
|
||||
total = self.coverage.report(
|
||||
precision=options.precision,
|
||||
show_missing=options.show_missing,
|
||||
skip_covered=options.skip_covered,
|
||||
skip_empty=options.skip_empty,
|
||||
sort=options.sort,
|
||||
**report_args
|
||||
)
|
||||
elif options.action == "annotate":
|
||||
self.coverage.annotate(directory=options.directory, **report_args)
|
||||
elif options.action == "html":
|
||||
total = self.coverage.html_report(
|
||||
directory=options.directory,
|
||||
precision=options.precision,
|
||||
skip_covered=options.skip_covered,
|
||||
skip_empty=options.skip_empty,
|
||||
show_contexts=options.show_contexts,
|
||||
title=options.title,
|
||||
**report_args
|
||||
)
|
||||
elif options.action == "xml":
|
||||
total = self.coverage.xml_report(
|
||||
outfile=options.outfile,
|
||||
skip_empty=options.skip_empty,
|
||||
**report_args
|
||||
)
|
||||
elif options.action == "json":
|
||||
total = self.coverage.json_report(
|
||||
outfile=options.outfile,
|
||||
pretty_print=options.pretty_print,
|
||||
show_contexts=options.show_contexts,
|
||||
**report_args
|
||||
)
|
||||
elif options.action == "lcov":
|
||||
total = self.coverage.lcov_report(
|
||||
outfile=options.outfile,
|
||||
**report_args
|
||||
)
|
||||
else:
|
||||
# There are no other possible actions.
|
||||
raise AssertionError
|
||||
|
||||
if total is not None:
|
||||
# Apply the command line fail-under options, and then use the config
|
||||
# value, so we can get fail_under from the config file.
|
||||
if options.fail_under is not None:
|
||||
self.coverage.set_option("report:fail_under", options.fail_under)
|
||||
if options.precision is not None:
|
||||
self.coverage.set_option("report:precision", options.precision)
|
||||
|
||||
fail_under = self.coverage.get_option("report:fail_under")
|
||||
precision = 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),
|
||||
fail_under=fail_under,
|
||||
p=precision,
|
||||
)
|
||||
print("Coverage failure:", msg)
|
||||
return FAIL_UNDER
|
||||
|
||||
return OK
|
||||
|
||||
def do_help(self, options, args, parser):
|
||||
"""Deal with help requests.
|
||||
|
||||
Return True if it handled the request, False if not.
|
||||
|
||||
"""
|
||||
# Handle help.
|
||||
if options.help:
|
||||
if self.global_option:
|
||||
show_help(topic='help')
|
||||
else:
|
||||
show_help(parser=parser)
|
||||
return True
|
||||
|
||||
if options.action == "help":
|
||||
if args:
|
||||
for a in args:
|
||||
parser = COMMANDS.get(a)
|
||||
if parser:
|
||||
show_help(parser=parser)
|
||||
else:
|
||||
show_help(topic=a)
|
||||
else:
|
||||
show_help(topic='help')
|
||||
return True
|
||||
|
||||
# Handle version.
|
||||
if options.version:
|
||||
show_help(topic='version')
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def do_run(self, options, args):
|
||||
"""Implementation of 'coverage run'."""
|
||||
|
||||
if not args:
|
||||
if options.module:
|
||||
# Specified -m with nothing else.
|
||||
show_help("No module specified for -m")
|
||||
return ERR
|
||||
command_line = self.coverage.get_option("run:command_line")
|
||||
if command_line is not None:
|
||||
args = shlex.split(command_line)
|
||||
if args and args[0] in {"-m", "--module"}:
|
||||
options.module = True
|
||||
args = args[1:]
|
||||
if not args:
|
||||
show_help("Nothing to do.")
|
||||
return ERR
|
||||
|
||||
if options.append and self.coverage.get_option("run:parallel"):
|
||||
show_help("Can't append to data files in parallel mode.")
|
||||
return ERR
|
||||
|
||||
if options.concurrency == "multiprocessing":
|
||||
# Can't set other run-affecting command line options with
|
||||
# multiprocessing.
|
||||
for opt_name in ['branch', 'include', 'omit', 'pylib', 'source', 'timid']:
|
||||
# As it happens, all of these options have no default, meaning
|
||||
# they will be None if they have not been specified.
|
||||
if getattr(options, opt_name) is not None:
|
||||
show_help(
|
||||
"Options affecting multiprocessing must only be specified " +
|
||||
"in a configuration file.\n" +
|
||||
f"Remove --{opt_name} from the command line."
|
||||
)
|
||||
return ERR
|
||||
|
||||
os.environ["COVERAGE_RUN"] = "true"
|
||||
|
||||
runner = PyRunner(args, as_module=bool(options.module))
|
||||
runner.prepare()
|
||||
|
||||
if options.append:
|
||||
self.coverage.load()
|
||||
|
||||
# Run the script.
|
||||
self.coverage.start()
|
||||
code_ran = True
|
||||
try:
|
||||
runner.run()
|
||||
except NoSource:
|
||||
code_ran = False
|
||||
raise
|
||||
finally:
|
||||
self.coverage.stop()
|
||||
if code_ran:
|
||||
self.coverage.save()
|
||||
|
||||
return OK
|
||||
|
||||
def do_debug(self, args):
|
||||
"""Implementation of 'coverage debug'."""
|
||||
|
||||
if not args:
|
||||
show_help("What information would you like: config, data, sys, premain, pybehave?")
|
||||
return ERR
|
||||
if args[1:]:
|
||||
show_help("Only one topic at a time, please")
|
||||
return ERR
|
||||
|
||||
if args[0] == "sys":
|
||||
write_formatted_info(print, "sys", self.coverage.sys_info())
|
||||
elif args[0] == "data":
|
||||
print(info_header("data"))
|
||||
data_file = self.coverage.config.data_file
|
||||
debug_data_file(data_file)
|
||||
for filename in combinable_files(data_file):
|
||||
print("-----")
|
||||
debug_data_file(filename)
|
||||
elif args[0] == "config":
|
||||
write_formatted_info(print, "config", self.coverage.config.debug_info())
|
||||
elif args[0] == "premain":
|
||||
print(info_header("premain"))
|
||||
print(short_stack())
|
||||
elif args[0] == "pybehave":
|
||||
write_formatted_info(print, "pybehave", env.debug_info())
|
||||
else:
|
||||
show_help(f"Don't know what you mean by {args[0]!r}")
|
||||
return ERR
|
||||
|
||||
return OK
|
||||
|
||||
|
||||
def unshell_list(s):
|
||||
"""Turn a command-line argument into a list."""
|
||||
if not s:
|
||||
return None
|
||||
if env.WINDOWS:
|
||||
# When running coverage.py as coverage.exe, some of the behavior
|
||||
# of the shell is emulated: wildcards are expanded into a list of
|
||||
# file names. So you have to single-quote patterns on the command
|
||||
# line, but (not) helpfully, the single quotes are included in the
|
||||
# argument, so we have to strip them off here.
|
||||
s = s.strip("'")
|
||||
return s.split(',')
|
||||
|
||||
|
||||
def unglob_args(args):
|
||||
"""Interpret shell wildcards for platforms that need it."""
|
||||
if env.WINDOWS:
|
||||
globbed = []
|
||||
for arg in args:
|
||||
if '?' in arg or '*' in arg:
|
||||
globbed.extend(glob.glob(arg))
|
||||
else:
|
||||
globbed.append(arg)
|
||||
args = globbed
|
||||
return args
|
||||
|
||||
|
||||
HELP_TOPICS = {
|
||||
'help': """\
|
||||
Coverage.py, version {__version__} {extension_modifier}
|
||||
Measure, collect, and report on code coverage in Python programs.
|
||||
|
||||
usage: {program_name} <command> [options] [args]
|
||||
|
||||
Commands:
|
||||
annotate Annotate source files with execution information.
|
||||
combine Combine a number of data files.
|
||||
debug Display information about the internals of coverage.py
|
||||
erase Erase previously collected coverage data.
|
||||
help Get help on using coverage.py.
|
||||
html Create an HTML report.
|
||||
json Create a JSON report of coverage results.
|
||||
lcov Create an LCOV report of coverage results.
|
||||
report Report coverage stats on modules.
|
||||
run Run a Python program and measure code execution.
|
||||
xml Create an XML report of coverage results.
|
||||
|
||||
Use "{program_name} help <command>" for detailed help on any command.
|
||||
""",
|
||||
|
||||
'minimum_help': """\
|
||||
Code coverage for Python, version {__version__} {extension_modifier}. Use '{program_name} help' for help.
|
||||
""",
|
||||
|
||||
'version': """\
|
||||
Coverage.py, version {__version__} {extension_modifier}
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
"""The main entry point to coverage.py.
|
||||
|
||||
This is installed as the script entry point.
|
||||
|
||||
"""
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
try:
|
||||
status = CoverageScript().command_line(argv)
|
||||
except _ExceptionDuringRun as err:
|
||||
# An exception was caught while running the product code. The
|
||||
# sys.exc_info() return tuple is packed into an _ExceptionDuringRun
|
||||
# exception.
|
||||
traceback.print_exception(*err.args) # pylint: disable=no-value-for-parameter
|
||||
status = ERR
|
||||
except _BaseCoverageException as err:
|
||||
# A controlled error inside coverage.py: print the message to the user.
|
||||
msg = err.args[0]
|
||||
print(msg)
|
||||
status = ERR
|
||||
except SystemExit as err:
|
||||
# The user called `sys.exit()`. Exit with their argument, if any.
|
||||
if err.args:
|
||||
status = err.args[0]
|
||||
else:
|
||||
status = None
|
||||
return status
|
||||
|
||||
# Profiling using ox_profile. Install it from GitHub:
|
||||
# pip install git+https://github.com/emin63/ox_profile.git
|
||||
#
|
||||
# $set_env.py: COVERAGE_PROFILE - Set to use ox_profile.
|
||||
_profile = os.environ.get("COVERAGE_PROFILE", "")
|
||||
if _profile: # pragma: debugging
|
||||
from ox_profile.core.launchers import SimpleLauncher # pylint: disable=import-error
|
||||
original_main = main
|
||||
|
||||
def main(argv=None): # pylint: disable=function-redefined
|
||||
"""A wrapper around main that profiles."""
|
||||
profiler = SimpleLauncher.launch()
|
||||
try:
|
||||
return original_main(argv)
|
||||
finally:
|
||||
data, _ = profiler.query(re_filter='coverage', max_records=100)
|
||||
print(profiler.show(query=data, limit=100, sep='', col=''))
|
||||
profiler.cancel()
|
484
utils/python-venv/Lib/site-packages/coverage/collector.py
Normal file
484
utils/python-venv/Lib/site-packages/coverage/collector.py
Normal file
@ -0,0 +1,484 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Raw data collector for coverage.py."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from coverage import env
|
||||
from coverage.config import CoverageConfig
|
||||
from coverage.debug import short_stack
|
||||
from coverage.disposition import FileDisposition
|
||||
from coverage.exceptions import ConfigError
|
||||
from coverage.misc import human_sorted, isolate_module
|
||||
from coverage.pytracer import PyTracer
|
||||
|
||||
os = isolate_module(os)
|
||||
|
||||
|
||||
try:
|
||||
# Use the C extension code when we can, for speed.
|
||||
from coverage.tracer import CTracer, CFileDisposition
|
||||
except ImportError:
|
||||
# Couldn't import the C extension, maybe it isn't built.
|
||||
if os.getenv('COVERAGE_TEST_TRACER') == 'c': # pragma: part covered
|
||||
# During testing, we use the COVERAGE_TEST_TRACER environment variable
|
||||
# to indicate that we've fiddled with the environment to test this
|
||||
# fallback code. If we thought we had a C tracer, but couldn't import
|
||||
# it, then exit quickly and clearly instead of dribbling confusing
|
||||
# errors. I'm using sys.exit here instead of an exception because an
|
||||
# exception here causes all sorts of other noise in unittest.
|
||||
sys.stderr.write("*** COVERAGE_TEST_TRACER is 'c' but can't import CTracer!\n")
|
||||
sys.exit(1)
|
||||
CTracer = None
|
||||
|
||||
|
||||
class Collector:
|
||||
"""Collects trace data.
|
||||
|
||||
Creates a Tracer object for each thread, since they track stack
|
||||
information. Each Tracer points to the same shared data, contributing
|
||||
traced data points.
|
||||
|
||||
When the Collector is started, it creates a Tracer for the current thread,
|
||||
and installs a function to create Tracers for each new thread started.
|
||||
When the Collector is stopped, all active Tracers are stopped.
|
||||
|
||||
Threads started while the Collector is stopped will never have Tracers
|
||||
associated with them.
|
||||
|
||||
"""
|
||||
|
||||
# The stack of active Collectors. Collectors are added here when started,
|
||||
# and popped when stopped. Collectors on the stack are paused when not
|
||||
# the top, and resumed when they become the top again.
|
||||
_collectors = []
|
||||
|
||||
# The concurrency settings we support here.
|
||||
LIGHT_THREADS = {"greenlet", "eventlet", "gevent"}
|
||||
|
||||
def __init__(
|
||||
self, should_trace, check_include, should_start_context, file_mapper,
|
||||
timid, branch, warn, concurrency,
|
||||
):
|
||||
"""Create a collector.
|
||||
|
||||
`should_trace` is a function, taking a file name and a frame, and
|
||||
returning a `coverage.FileDisposition object`.
|
||||
|
||||
`check_include` is a function taking a file name and a frame. It returns
|
||||
a boolean: True if the file should be traced, False if not.
|
||||
|
||||
`should_start_context` is a function taking a frame, and returning a
|
||||
string. If the frame should be the start of a new context, the string
|
||||
is the new context. If the frame should not be the start of a new
|
||||
context, return None.
|
||||
|
||||
`file_mapper` is a function taking a filename, and returning a Unicode
|
||||
filename. The result is the name that will be recorded in the data
|
||||
file.
|
||||
|
||||
If `timid` is true, then a slower simpler trace function will be
|
||||
used. This is important for some environments where manipulation of
|
||||
tracing functions make the faster more sophisticated trace function not
|
||||
operate properly.
|
||||
|
||||
If `branch` is true, then branches will be measured. This involves
|
||||
collecting data on which statements followed each other (arcs). Use
|
||||
`get_arc_data` to get the arc data.
|
||||
|
||||
`warn` is a warning function, taking a single string message argument
|
||||
and an optional slug argument which will be a string or None, to be
|
||||
used if a warning needs to be issued.
|
||||
|
||||
`concurrency` is a list of strings indicating the concurrency libraries
|
||||
in use. Valid values are "greenlet", "eventlet", "gevent", or "thread"
|
||||
(the default). "thread" can be combined with one of the other three.
|
||||
Other values are ignored.
|
||||
|
||||
"""
|
||||
self.should_trace = should_trace
|
||||
self.check_include = check_include
|
||||
self.should_start_context = should_start_context
|
||||
self.file_mapper = file_mapper
|
||||
self.branch = branch
|
||||
self.warn = warn
|
||||
self.concurrency = concurrency
|
||||
assert isinstance(self.concurrency, list), f"Expected a list: {self.concurrency!r}"
|
||||
|
||||
self.threading = None
|
||||
self.covdata = None
|
||||
self.static_context = None
|
||||
|
||||
self.origin = short_stack()
|
||||
|
||||
self.concur_id_func = None
|
||||
self.mapped_file_cache = {}
|
||||
|
||||
if timid:
|
||||
# Being timid: use the simple Python trace function.
|
||||
self._trace_class = PyTracer
|
||||
else:
|
||||
# Being fast: use the C Tracer if it is available, else the Python
|
||||
# trace function.
|
||||
self._trace_class = CTracer or PyTracer
|
||||
|
||||
if self._trace_class is CTracer:
|
||||
self.file_disposition_class = CFileDisposition
|
||||
self.supports_plugins = True
|
||||
self.packed_arcs = True
|
||||
else:
|
||||
self.file_disposition_class = FileDisposition
|
||||
self.supports_plugins = False
|
||||
self.packed_arcs = False
|
||||
|
||||
# We can handle a few concurrency options here, but only one at a time.
|
||||
concurrencies = set(self.concurrency)
|
||||
unknown = concurrencies - CoverageConfig.CONCURRENCY_CHOICES
|
||||
if unknown:
|
||||
show = ", ".join(sorted(unknown))
|
||||
raise ConfigError(f"Unknown concurrency choices: {show}")
|
||||
light_threads = concurrencies & self.LIGHT_THREADS
|
||||
if len(light_threads) > 1:
|
||||
show = ", ".join(sorted(light_threads))
|
||||
raise ConfigError(f"Conflicting concurrency settings: {show}")
|
||||
do_threading = False
|
||||
|
||||
tried = "nothing" # to satisfy pylint
|
||||
try:
|
||||
if "greenlet" in concurrencies:
|
||||
tried = "greenlet"
|
||||
import greenlet
|
||||
self.concur_id_func = greenlet.getcurrent
|
||||
elif "eventlet" in concurrencies:
|
||||
tried = "eventlet"
|
||||
import eventlet.greenthread # pylint: disable=import-error,useless-suppression
|
||||
self.concur_id_func = eventlet.greenthread.getcurrent
|
||||
elif "gevent" in concurrencies:
|
||||
tried = "gevent"
|
||||
import gevent # pylint: disable=import-error,useless-suppression
|
||||
self.concur_id_func = gevent.getcurrent
|
||||
|
||||
if "thread" in concurrencies:
|
||||
do_threading = True
|
||||
except ImportError as ex:
|
||||
msg = f"Couldn't trace with concurrency={tried}, the module isn't installed."
|
||||
raise ConfigError(msg) from ex
|
||||
|
||||
if self.concur_id_func and not hasattr(self._trace_class, "concur_id_func"):
|
||||
raise ConfigError(
|
||||
"Can't support concurrency={} with {}, only threads are supported.".format(
|
||||
tried, self.tracer_name(),
|
||||
)
|
||||
)
|
||||
|
||||
if do_threading or not concurrencies:
|
||||
# It's important to import threading only if we need it. If
|
||||
# it's imported early, and the program being measured uses
|
||||
# gevent, then gevent's monkey-patching won't work properly.
|
||||
import threading
|
||||
self.threading = threading
|
||||
|
||||
self.reset()
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Collector at 0x{id(self):x}: {self.tracer_name()}>"
|
||||
|
||||
def use_data(self, covdata, context):
|
||||
"""Use `covdata` for recording data."""
|
||||
self.covdata = covdata
|
||||
self.static_context = context
|
||||
self.covdata.set_context(self.static_context)
|
||||
|
||||
def tracer_name(self):
|
||||
"""Return the class name of the tracer we're using."""
|
||||
return self._trace_class.__name__
|
||||
|
||||
def _clear_data(self):
|
||||
"""Clear out existing data, but stay ready for more collection."""
|
||||
# We used to used self.data.clear(), but that would remove filename
|
||||
# keys and data values that were still in use higher up the stack
|
||||
# when we are called as part of switch_context.
|
||||
for d in self.data.values():
|
||||
d.clear()
|
||||
|
||||
for tracer in self.tracers:
|
||||
tracer.reset_activity()
|
||||
|
||||
def reset(self):
|
||||
"""Clear collected data, and prepare to collect more."""
|
||||
# A dictionary mapping file names to dicts with line number keys (if not
|
||||
# branch coverage), or mapping file names to dicts with line number
|
||||
# pairs as keys (if branch coverage).
|
||||
self.data = {}
|
||||
|
||||
# A dictionary mapping file names to file tracer plugin names that will
|
||||
# handle them.
|
||||
self.file_tracers = {}
|
||||
|
||||
self.disabled_plugins = set()
|
||||
|
||||
# The .should_trace_cache attribute is a cache from file names to
|
||||
# coverage.FileDisposition objects, or None. When a file is first
|
||||
# considered for tracing, a FileDisposition is obtained from
|
||||
# Coverage.should_trace. Its .trace attribute indicates whether the
|
||||
# file should be traced or not. If it should be, a plugin with dynamic
|
||||
# file names can decide not to trace it based on the dynamic file name
|
||||
# being excluded by the inclusion rules, in which case the
|
||||
# FileDisposition will be replaced by None in the cache.
|
||||
if env.PYPY:
|
||||
import __pypy__ # pylint: disable=import-error
|
||||
# Alex Gaynor said:
|
||||
# should_trace_cache is a strictly growing key: once a key is in
|
||||
# it, it never changes. Further, the keys used to access it are
|
||||
# generally constant, given sufficient context. That is to say, at
|
||||
# any given point _trace() is called, pypy is able to know the key.
|
||||
# This is because the key is determined by the physical source code
|
||||
# line, and that's invariant with the call site.
|
||||
#
|
||||
# This property of a dict with immutable keys, combined with
|
||||
# call-site-constant keys is a match for PyPy's module dict,
|
||||
# which is optimized for such workloads.
|
||||
#
|
||||
# This gives a 20% benefit on the workload described at
|
||||
# https://bitbucket.org/pypy/pypy/issue/1871/10x-slower-than-cpython-under-coverage
|
||||
self.should_trace_cache = __pypy__.newdict("module")
|
||||
else:
|
||||
self.should_trace_cache = {}
|
||||
|
||||
# Our active Tracers.
|
||||
self.tracers = []
|
||||
|
||||
self._clear_data()
|
||||
|
||||
def _start_tracer(self):
|
||||
"""Start a new Tracer object, and store it in self.tracers."""
|
||||
tracer = self._trace_class()
|
||||
tracer.data = self.data
|
||||
tracer.trace_arcs = self.branch
|
||||
tracer.should_trace = self.should_trace
|
||||
tracer.should_trace_cache = self.should_trace_cache
|
||||
tracer.warn = self.warn
|
||||
|
||||
if hasattr(tracer, 'concur_id_func'):
|
||||
tracer.concur_id_func = self.concur_id_func
|
||||
if hasattr(tracer, 'file_tracers'):
|
||||
tracer.file_tracers = self.file_tracers
|
||||
if hasattr(tracer, 'threading'):
|
||||
tracer.threading = self.threading
|
||||
if hasattr(tracer, 'check_include'):
|
||||
tracer.check_include = self.check_include
|
||||
if hasattr(tracer, 'should_start_context'):
|
||||
tracer.should_start_context = self.should_start_context
|
||||
tracer.switch_context = self.switch_context
|
||||
if hasattr(tracer, 'disable_plugin'):
|
||||
tracer.disable_plugin = self.disable_plugin
|
||||
|
||||
fn = tracer.start()
|
||||
self.tracers.append(tracer)
|
||||
|
||||
return fn
|
||||
|
||||
# The trace function has to be set individually on each thread before
|
||||
# execution begins. Ironically, the only support the threading module has
|
||||
# for running code before the thread main is the tracing function. So we
|
||||
# install this as a trace function, and the first time it's called, it does
|
||||
# the real trace installation.
|
||||
#
|
||||
# New in 3.12: threading.settrace_all_threads: https://github.com/python/cpython/pull/96681
|
||||
|
||||
def _installation_trace(self, frame, event, arg):
|
||||
"""Called on new threads, installs the real tracer."""
|
||||
# Remove ourselves as the trace function.
|
||||
sys.settrace(None)
|
||||
# Install the real tracer.
|
||||
fn = self._start_tracer()
|
||||
# Invoke the real trace function with the current event, to be sure
|
||||
# not to lose an event.
|
||||
if fn:
|
||||
fn = fn(frame, event, arg)
|
||||
# Return the new trace function to continue tracing in this scope.
|
||||
return fn
|
||||
|
||||
def start(self):
|
||||
"""Start collecting trace information."""
|
||||
if self._collectors:
|
||||
self._collectors[-1].pause()
|
||||
|
||||
self.tracers = []
|
||||
|
||||
# Check to see whether we had a fullcoverage tracer installed. If so,
|
||||
# get the stack frames it stashed away for us.
|
||||
traces0 = []
|
||||
fn0 = sys.gettrace()
|
||||
if fn0:
|
||||
tracer0 = getattr(fn0, '__self__', None)
|
||||
if tracer0:
|
||||
traces0 = getattr(tracer0, 'traces', [])
|
||||
|
||||
try:
|
||||
# Install the tracer on this thread.
|
||||
fn = self._start_tracer()
|
||||
except:
|
||||
if self._collectors:
|
||||
self._collectors[-1].resume()
|
||||
raise
|
||||
|
||||
# If _start_tracer succeeded, then we add ourselves to the global
|
||||
# stack of collectors.
|
||||
self._collectors.append(self)
|
||||
|
||||
# Replay all the events from fullcoverage into the new trace function.
|
||||
for (frame, event, arg), lineno in traces0:
|
||||
try:
|
||||
fn(frame, event, arg, lineno=lineno)
|
||||
except TypeError as ex:
|
||||
raise Exception("fullcoverage must be run with the C trace function.") from ex
|
||||
|
||||
# Install our installation tracer in threading, to jump-start other
|
||||
# threads.
|
||||
if self.threading:
|
||||
self.threading.settrace(self._installation_trace)
|
||||
|
||||
def stop(self):
|
||||
"""Stop collecting trace information."""
|
||||
assert self._collectors
|
||||
if self._collectors[-1] is not self:
|
||||
print("self._collectors:")
|
||||
for c in self._collectors:
|
||||
print(f" {c!r}\n{c.origin}")
|
||||
assert self._collectors[-1] is self, (
|
||||
f"Expected current collector to be {self!r}, but it's {self._collectors[-1]!r}"
|
||||
)
|
||||
|
||||
self.pause()
|
||||
|
||||
# Remove this Collector from the stack, and resume the one underneath
|
||||
# (if any).
|
||||
self._collectors.pop()
|
||||
if self._collectors:
|
||||
self._collectors[-1].resume()
|
||||
|
||||
def pause(self):
|
||||
"""Pause tracing, but be prepared to `resume`."""
|
||||
for tracer in self.tracers:
|
||||
tracer.stop()
|
||||
stats = tracer.get_stats()
|
||||
if stats:
|
||||
print("\nCoverage.py tracer stats:")
|
||||
for k in human_sorted(stats.keys()):
|
||||
print(f"{k:>20}: {stats[k]}")
|
||||
if self.threading:
|
||||
self.threading.settrace(None)
|
||||
|
||||
def resume(self):
|
||||
"""Resume tracing after a `pause`."""
|
||||
for tracer in self.tracers:
|
||||
tracer.start()
|
||||
if self.threading:
|
||||
self.threading.settrace(self._installation_trace)
|
||||
else:
|
||||
self._start_tracer()
|
||||
|
||||
def _activity(self):
|
||||
"""Has any activity been traced?
|
||||
|
||||
Returns a boolean, True if any trace function was invoked.
|
||||
|
||||
"""
|
||||
return any(tracer.activity() for tracer in self.tracers)
|
||||
|
||||
def switch_context(self, new_context):
|
||||
"""Switch to a new dynamic context."""
|
||||
self.flush_data()
|
||||
if self.static_context:
|
||||
context = self.static_context
|
||||
if new_context:
|
||||
context += "|" + new_context
|
||||
else:
|
||||
context = new_context
|
||||
self.covdata.set_context(context)
|
||||
|
||||
def disable_plugin(self, disposition):
|
||||
"""Disable the plugin mentioned in `disposition`."""
|
||||
file_tracer = disposition.file_tracer
|
||||
plugin = file_tracer._coverage_plugin
|
||||
plugin_name = plugin._coverage_plugin_name
|
||||
self.warn(f"Disabling plug-in {plugin_name!r} due to previous exception")
|
||||
plugin._coverage_enabled = False
|
||||
disposition.trace = False
|
||||
|
||||
def cached_mapped_file(self, filename):
|
||||
"""A locally cached version of file names mapped through file_mapper."""
|
||||
key = (type(filename), filename)
|
||||
try:
|
||||
return self.mapped_file_cache[key]
|
||||
except KeyError:
|
||||
return self.mapped_file_cache.setdefault(key, self.file_mapper(filename))
|
||||
|
||||
def mapped_file_dict(self, d):
|
||||
"""Return a dict like d, but with keys modified by file_mapper."""
|
||||
# The call to list(items()) ensures that the GIL protects the dictionary
|
||||
# iterator against concurrent modifications by tracers running
|
||||
# in other threads. We try three times in case of concurrent
|
||||
# access, hoping to get a clean copy.
|
||||
runtime_err = None
|
||||
for _ in range(3): # pragma: part covered
|
||||
try:
|
||||
items = list(d.items())
|
||||
except RuntimeError as ex: # pragma: cant happen
|
||||
runtime_err = ex
|
||||
else:
|
||||
break
|
||||
else:
|
||||
raise runtime_err # pragma: cant happen
|
||||
|
||||
return {self.cached_mapped_file(k): v for k, v in items}
|
||||
|
||||
def plugin_was_disabled(self, plugin):
|
||||
"""Record that `plugin` was disabled during the run."""
|
||||
self.disabled_plugins.add(plugin._coverage_plugin_name)
|
||||
|
||||
def flush_data(self):
|
||||
"""Save the collected data to our associated `CoverageData`.
|
||||
|
||||
Data may have also been saved along the way. This forces the
|
||||
last of the data to be saved.
|
||||
|
||||
Returns True if there was data to save, False if not.
|
||||
"""
|
||||
if not self._activity():
|
||||
return False
|
||||
|
||||
if self.branch:
|
||||
if self.packed_arcs:
|
||||
# Unpack the line number pairs packed into integers. See
|
||||
# tracer.c:CTracer_record_pair for the C code that creates
|
||||
# these packed ints.
|
||||
data = {}
|
||||
for fname, packeds in self.data.items():
|
||||
tuples = []
|
||||
for packed in packeds:
|
||||
l1 = packed & 0xFFFFF
|
||||
l2 = (packed & (0xFFFFF << 20)) >> 20
|
||||
if packed & (1 << 40):
|
||||
l1 *= -1
|
||||
if packed & (1 << 41):
|
||||
l2 *= -1
|
||||
tuples.append((l1, l2))
|
||||
data[fname] = tuples
|
||||
else:
|
||||
data = self.data
|
||||
self.covdata.add_arcs(self.mapped_file_dict(data))
|
||||
else:
|
||||
self.covdata.add_lines(self.mapped_file_dict(self.data))
|
||||
|
||||
file_tracers = {
|
||||
k: v for k, v in self.file_tracers.items()
|
||||
if v not in self.disabled_plugins
|
||||
}
|
||||
self.covdata.add_file_tracers(self.mapped_file_dict(file_tracers))
|
||||
|
||||
self._clear_data()
|
||||
return True
|
583
utils/python-venv/Lib/site-packages/coverage/config.py
Normal file
583
utils/python-venv/Lib/site-packages/coverage/config.py
Normal file
@ -0,0 +1,583 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Config file for coverage.py"""
|
||||
|
||||
import collections
|
||||
import configparser
|
||||
import copy
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
|
||||
from coverage.exceptions import ConfigError
|
||||
from coverage.misc import contract, isolate_module, human_sorted_items, substitute_variables
|
||||
|
||||
from coverage.tomlconfig import TomlConfigParser, TomlDecodeError
|
||||
|
||||
os = isolate_module(os)
|
||||
|
||||
|
||||
class HandyConfigParser(configparser.RawConfigParser):
|
||||
"""Our specialization of ConfigParser."""
|
||||
|
||||
def __init__(self, our_file):
|
||||
"""Create the HandyConfigParser.
|
||||
|
||||
`our_file` is True if this config file is specifically for coverage,
|
||||
False if we are examining another config file (tox.ini, setup.cfg)
|
||||
for possible settings.
|
||||
"""
|
||||
|
||||
configparser.RawConfigParser.__init__(self)
|
||||
self.section_prefixes = ["coverage:"]
|
||||
if our_file:
|
||||
self.section_prefixes.append("")
|
||||
|
||||
def read(self, filenames, encoding_unused=None):
|
||||
"""Read a file name as UTF-8 configuration data."""
|
||||
return configparser.RawConfigParser.read(self, filenames, encoding="utf-8")
|
||||
|
||||
def has_option(self, section, option):
|
||||
for section_prefix in self.section_prefixes:
|
||||
real_section = section_prefix + section
|
||||
has = configparser.RawConfigParser.has_option(self, real_section, option)
|
||||
if has:
|
||||
return has
|
||||
return False
|
||||
|
||||
def has_section(self, section):
|
||||
for section_prefix in self.section_prefixes:
|
||||
real_section = section_prefix + section
|
||||
has = configparser.RawConfigParser.has_section(self, real_section)
|
||||
if has:
|
||||
return real_section
|
||||
return False
|
||||
|
||||
def options(self, section):
|
||||
for section_prefix in self.section_prefixes:
|
||||
real_section = section_prefix + section
|
||||
if configparser.RawConfigParser.has_section(self, real_section):
|
||||
return configparser.RawConfigParser.options(self, real_section)
|
||||
raise ConfigError(f"No section: {section!r}")
|
||||
|
||||
def get_section(self, section):
|
||||
"""Get the contents of a section, as a dictionary."""
|
||||
d = {}
|
||||
for opt in self.options(section):
|
||||
d[opt] = self.get(section, opt)
|
||||
return d
|
||||
|
||||
def get(self, section, option, *args, **kwargs):
|
||||
"""Get a value, replacing environment variables also.
|
||||
|
||||
The arguments are the same as `RawConfigParser.get`, but in the found
|
||||
value, ``$WORD`` or ``${WORD}`` are replaced by the value of the
|
||||
environment variable ``WORD``.
|
||||
|
||||
Returns the finished value.
|
||||
|
||||
"""
|
||||
for section_prefix in self.section_prefixes:
|
||||
real_section = section_prefix + section
|
||||
if configparser.RawConfigParser.has_option(self, real_section, option):
|
||||
break
|
||||
else:
|
||||
raise ConfigError(f"No option {option!r} in section: {section!r}")
|
||||
|
||||
v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs)
|
||||
v = substitute_variables(v, os.environ)
|
||||
return v
|
||||
|
||||
def getlist(self, section, option):
|
||||
"""Read a list of strings.
|
||||
|
||||
The value of `section` and `option` is treated as a comma- and newline-
|
||||
separated list of strings. Each value is stripped of whitespace.
|
||||
|
||||
Returns the list of strings.
|
||||
|
||||
"""
|
||||
value_list = self.get(section, option)
|
||||
values = []
|
||||
for value_line in value_list.split('\n'):
|
||||
for value in value_line.split(','):
|
||||
value = value.strip()
|
||||
if value:
|
||||
values.append(value)
|
||||
return values
|
||||
|
||||
def getregexlist(self, section, option):
|
||||
"""Read a list of full-line regexes.
|
||||
|
||||
The value of `section` and `option` is treated as a newline-separated
|
||||
list of regexes. Each value is stripped of whitespace.
|
||||
|
||||
Returns the list of strings.
|
||||
|
||||
"""
|
||||
line_list = self.get(section, option)
|
||||
value_list = []
|
||||
for value in line_list.splitlines():
|
||||
value = value.strip()
|
||||
try:
|
||||
re.compile(value)
|
||||
except re.error as e:
|
||||
raise ConfigError(
|
||||
f"Invalid [{section}].{option} value {value!r}: {e}"
|
||||
) from e
|
||||
if value:
|
||||
value_list.append(value)
|
||||
return value_list
|
||||
|
||||
|
||||
# The default line exclusion regexes.
|
||||
DEFAULT_EXCLUDE = [
|
||||
r'#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(cover|COVER)',
|
||||
]
|
||||
|
||||
# The default partial branch regexes, to be modified by the user.
|
||||
DEFAULT_PARTIAL = [
|
||||
r'#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(branch|BRANCH)',
|
||||
]
|
||||
|
||||
# The default partial branch regexes, based on Python semantics.
|
||||
# These are any Python branching constructs that can't actually execute all
|
||||
# their branches.
|
||||
DEFAULT_PARTIAL_ALWAYS = [
|
||||
'while (True|1|False|0):',
|
||||
'if (True|1|False|0):',
|
||||
]
|
||||
|
||||
|
||||
class CoverageConfig:
|
||||
"""Coverage.py configuration.
|
||||
|
||||
The attributes of this class are the various settings that control the
|
||||
operation of coverage.py.
|
||||
|
||||
"""
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the configuration attributes to their defaults."""
|
||||
# Metadata about the config.
|
||||
# We tried to read these config files.
|
||||
self.attempted_config_files = []
|
||||
# We did read these config files, but maybe didn't find any content for us.
|
||||
self.config_files_read = []
|
||||
# The file that gave us our configuration.
|
||||
self.config_file = None
|
||||
self._config_contents = None
|
||||
|
||||
# Defaults for [run] and [report]
|
||||
self._include = None
|
||||
self._omit = None
|
||||
|
||||
# Defaults for [run]
|
||||
self.branch = False
|
||||
self.command_line = None
|
||||
self.concurrency = None
|
||||
self.context = None
|
||||
self.cover_pylib = False
|
||||
self.data_file = ".coverage"
|
||||
self.debug = []
|
||||
self.disable_warnings = []
|
||||
self.dynamic_context = None
|
||||
self.note = None
|
||||
self.parallel = False
|
||||
self.plugins = []
|
||||
self.relative_files = False
|
||||
self.run_include = None
|
||||
self.run_omit = None
|
||||
self.sigterm = False
|
||||
self.source = None
|
||||
self.source_pkgs = []
|
||||
self.timid = False
|
||||
self._crash = None
|
||||
|
||||
# Defaults for [report]
|
||||
self.exclude_list = DEFAULT_EXCLUDE[:]
|
||||
self.fail_under = 0.0
|
||||
self.ignore_errors = False
|
||||
self.report_include = None
|
||||
self.report_omit = None
|
||||
self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
|
||||
self.partial_list = DEFAULT_PARTIAL[:]
|
||||
self.precision = 0
|
||||
self.report_contexts = None
|
||||
self.show_missing = False
|
||||
self.skip_covered = False
|
||||
self.skip_empty = False
|
||||
self.sort = None
|
||||
|
||||
# Defaults for [html]
|
||||
self.extra_css = None
|
||||
self.html_dir = "htmlcov"
|
||||
self.html_skip_covered = None
|
||||
self.html_skip_empty = None
|
||||
self.html_title = "Coverage report"
|
||||
self.show_contexts = False
|
||||
|
||||
# Defaults for [xml]
|
||||
self.xml_output = "coverage.xml"
|
||||
self.xml_package_depth = 99
|
||||
|
||||
# Defaults for [json]
|
||||
self.json_output = "coverage.json"
|
||||
self.json_pretty_print = False
|
||||
self.json_show_contexts = False
|
||||
|
||||
# Defaults for [lcov]
|
||||
self.lcov_output = "coverage.lcov"
|
||||
|
||||
# Defaults for [paths]
|
||||
self.paths = collections.OrderedDict()
|
||||
|
||||
# Options for plugins
|
||||
self.plugin_options = {}
|
||||
|
||||
MUST_BE_LIST = {
|
||||
"debug", "concurrency", "plugins",
|
||||
"report_omit", "report_include",
|
||||
"run_omit", "run_include",
|
||||
}
|
||||
|
||||
def from_args(self, **kwargs):
|
||||
"""Read config values from `kwargs`."""
|
||||
for k, v in kwargs.items():
|
||||
if v is not None:
|
||||
if k in self.MUST_BE_LIST and isinstance(v, str):
|
||||
v = [v]
|
||||
setattr(self, k, v)
|
||||
|
||||
@contract(filename=str)
|
||||
def from_file(self, filename, warn, our_file):
|
||||
"""Read configuration from a .rc file.
|
||||
|
||||
`filename` is a file name to read.
|
||||
|
||||
`our_file` is True if this config file is specifically for coverage,
|
||||
False if we are examining another config file (tox.ini, setup.cfg)
|
||||
for possible settings.
|
||||
|
||||
Returns True or False, whether the file could be read, and it had some
|
||||
coverage.py settings in it.
|
||||
|
||||
"""
|
||||
_, ext = os.path.splitext(filename)
|
||||
if ext == '.toml':
|
||||
cp = TomlConfigParser(our_file)
|
||||
else:
|
||||
cp = HandyConfigParser(our_file)
|
||||
|
||||
self.attempted_config_files.append(filename)
|
||||
|
||||
try:
|
||||
files_read = cp.read(filename)
|
||||
except (configparser.Error, TomlDecodeError) as err:
|
||||
raise ConfigError(f"Couldn't read config file {filename}: {err}") from err
|
||||
if not files_read:
|
||||
return False
|
||||
|
||||
self.config_files_read.extend(map(os.path.abspath, files_read))
|
||||
|
||||
any_set = False
|
||||
try:
|
||||
for option_spec in self.CONFIG_FILE_OPTIONS:
|
||||
was_set = self._set_attr_from_config_option(cp, *option_spec)
|
||||
if was_set:
|
||||
any_set = True
|
||||
except ValueError as err:
|
||||
raise ConfigError(f"Couldn't read config file {filename}: {err}") from err
|
||||
|
||||
# Check that there are no unrecognized options.
|
||||
all_options = collections.defaultdict(set)
|
||||
for option_spec in self.CONFIG_FILE_OPTIONS:
|
||||
section, option = option_spec[1].split(":")
|
||||
all_options[section].add(option)
|
||||
|
||||
for section, options in all_options.items():
|
||||
real_section = cp.has_section(section)
|
||||
if real_section:
|
||||
for unknown in set(cp.options(section)) - options:
|
||||
warn(
|
||||
"Unrecognized option '[{}] {}=' in config file {}".format(
|
||||
real_section, unknown, filename
|
||||
)
|
||||
)
|
||||
|
||||
# [paths] is special
|
||||
if cp.has_section('paths'):
|
||||
for option in cp.options('paths'):
|
||||
self.paths[option] = cp.getlist('paths', option)
|
||||
any_set = True
|
||||
|
||||
# plugins can have options
|
||||
for plugin in self.plugins:
|
||||
if cp.has_section(plugin):
|
||||
self.plugin_options[plugin] = cp.get_section(plugin)
|
||||
any_set = True
|
||||
|
||||
# Was this file used as a config file? If it's specifically our file,
|
||||
# then it was used. If we're piggybacking on someone else's file,
|
||||
# then it was only used if we found some settings in it.
|
||||
if our_file:
|
||||
used = True
|
||||
else:
|
||||
used = any_set
|
||||
|
||||
if used:
|
||||
self.config_file = os.path.abspath(filename)
|
||||
with open(filename, "rb") as f:
|
||||
self._config_contents = f.read()
|
||||
|
||||
return used
|
||||
|
||||
def copy(self):
|
||||
"""Return a copy of the configuration."""
|
||||
return copy.deepcopy(self)
|
||||
|
||||
CONCURRENCY_CHOICES = {"thread", "gevent", "greenlet", "eventlet", "multiprocessing"}
|
||||
|
||||
CONFIG_FILE_OPTIONS = [
|
||||
# These are *args for _set_attr_from_config_option:
|
||||
# (attr, where, type_="")
|
||||
#
|
||||
# attr is the attribute to set on the CoverageConfig object.
|
||||
# where is the section:name to read from the configuration file.
|
||||
# type_ is the optional type to apply, by using .getTYPE to read the
|
||||
# configuration value from the file.
|
||||
|
||||
# [run]
|
||||
('branch', 'run:branch', 'boolean'),
|
||||
('command_line', 'run:command_line'),
|
||||
('concurrency', 'run:concurrency', 'list'),
|
||||
('context', 'run:context'),
|
||||
('cover_pylib', 'run:cover_pylib', 'boolean'),
|
||||
('data_file', 'run:data_file'),
|
||||
('debug', 'run:debug', 'list'),
|
||||
('disable_warnings', 'run:disable_warnings', 'list'),
|
||||
('dynamic_context', 'run:dynamic_context'),
|
||||
('note', 'run:note'),
|
||||
('parallel', 'run:parallel', 'boolean'),
|
||||
('plugins', 'run:plugins', 'list'),
|
||||
('relative_files', 'run:relative_files', 'boolean'),
|
||||
('run_include', 'run:include', 'list'),
|
||||
('run_omit', 'run:omit', 'list'),
|
||||
('sigterm', 'run:sigterm', 'boolean'),
|
||||
('source', 'run:source', 'list'),
|
||||
('source_pkgs', 'run:source_pkgs', 'list'),
|
||||
('timid', 'run:timid', 'boolean'),
|
||||
('_crash', 'run:_crash'),
|
||||
|
||||
# [report]
|
||||
('exclude_list', 'report:exclude_lines', 'regexlist'),
|
||||
('fail_under', 'report:fail_under', 'float'),
|
||||
('ignore_errors', 'report:ignore_errors', 'boolean'),
|
||||
('partial_always_list', 'report:partial_branches_always', 'regexlist'),
|
||||
('partial_list', 'report:partial_branches', 'regexlist'),
|
||||
('precision', 'report:precision', 'int'),
|
||||
('report_contexts', 'report:contexts', 'list'),
|
||||
('report_include', 'report:include', 'list'),
|
||||
('report_omit', 'report:omit', 'list'),
|
||||
('show_missing', 'report:show_missing', 'boolean'),
|
||||
('skip_covered', 'report:skip_covered', 'boolean'),
|
||||
('skip_empty', 'report:skip_empty', 'boolean'),
|
||||
('sort', 'report:sort'),
|
||||
|
||||
# [html]
|
||||
('extra_css', 'html:extra_css'),
|
||||
('html_dir', 'html:directory'),
|
||||
('html_skip_covered', 'html:skip_covered', 'boolean'),
|
||||
('html_skip_empty', 'html:skip_empty', 'boolean'),
|
||||
('html_title', 'html:title'),
|
||||
('show_contexts', 'html:show_contexts', 'boolean'),
|
||||
|
||||
# [xml]
|
||||
('xml_output', 'xml:output'),
|
||||
('xml_package_depth', 'xml:package_depth', 'int'),
|
||||
|
||||
# [json]
|
||||
('json_output', 'json:output'),
|
||||
('json_pretty_print', 'json:pretty_print', 'boolean'),
|
||||
('json_show_contexts', 'json:show_contexts', 'boolean'),
|
||||
|
||||
# [lcov]
|
||||
('lcov_output', 'lcov:output'),
|
||||
]
|
||||
|
||||
def _set_attr_from_config_option(self, cp, attr, where, type_=''):
|
||||
"""Set an attribute on self if it exists in the ConfigParser.
|
||||
|
||||
Returns True if the attribute was set.
|
||||
|
||||
"""
|
||||
section, option = where.split(":")
|
||||
if cp.has_option(section, option):
|
||||
method = getattr(cp, 'get' + type_)
|
||||
setattr(self, attr, method(section, option))
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_plugin_options(self, plugin):
|
||||
"""Get a dictionary of options for the plugin named `plugin`."""
|
||||
return self.plugin_options.get(plugin, {})
|
||||
|
||||
def set_option(self, option_name, value):
|
||||
"""Set an option in the configuration.
|
||||
|
||||
`option_name` is a colon-separated string indicating the section and
|
||||
option name. For example, the ``branch`` option in the ``[run]``
|
||||
section of the config file would be indicated with `"run:branch"`.
|
||||
|
||||
`value` is the new value for the option.
|
||||
|
||||
"""
|
||||
# Special-cased options.
|
||||
if option_name == "paths":
|
||||
self.paths = value
|
||||
return
|
||||
|
||||
# Check all the hard-coded options.
|
||||
for option_spec in self.CONFIG_FILE_OPTIONS:
|
||||
attr, where = option_spec[:2]
|
||||
if where == option_name:
|
||||
setattr(self, attr, value)
|
||||
return
|
||||
|
||||
# See if it's a plugin option.
|
||||
plugin_name, _, key = option_name.partition(":")
|
||||
if key and plugin_name in self.plugins:
|
||||
self.plugin_options.setdefault(plugin_name, {})[key] = value
|
||||
return
|
||||
|
||||
# If we get here, we didn't find the option.
|
||||
raise ConfigError(f"No such option: {option_name!r}")
|
||||
|
||||
def get_option(self, option_name):
|
||||
"""Get an option from the configuration.
|
||||
|
||||
`option_name` is a colon-separated string indicating the section and
|
||||
option name. For example, the ``branch`` option in the ``[run]``
|
||||
section of the config file would be indicated with `"run:branch"`.
|
||||
|
||||
Returns the value of the option.
|
||||
|
||||
"""
|
||||
# Special-cased options.
|
||||
if option_name == "paths":
|
||||
return self.paths
|
||||
|
||||
# Check all the hard-coded options.
|
||||
for option_spec in self.CONFIG_FILE_OPTIONS:
|
||||
attr, where = option_spec[:2]
|
||||
if where == option_name:
|
||||
return getattr(self, attr)
|
||||
|
||||
# See if it's a plugin option.
|
||||
plugin_name, _, key = option_name.partition(":")
|
||||
if key and plugin_name in self.plugins:
|
||||
return self.plugin_options.get(plugin_name, {}).get(key)
|
||||
|
||||
# If we get here, we didn't find the option.
|
||||
raise ConfigError(f"No such option: {option_name!r}")
|
||||
|
||||
def post_process_file(self, path):
|
||||
"""Make final adjustments to a file path to make it usable."""
|
||||
return os.path.expanduser(path)
|
||||
|
||||
def post_process(self):
|
||||
"""Make final adjustments to settings to make them usable."""
|
||||
self.data_file = self.post_process_file(self.data_file)
|
||||
self.html_dir = self.post_process_file(self.html_dir)
|
||||
self.xml_output = self.post_process_file(self.xml_output)
|
||||
self.paths = collections.OrderedDict(
|
||||
(k, [self.post_process_file(f) for f in v])
|
||||
for k, v in self.paths.items()
|
||||
)
|
||||
|
||||
def debug_info(self):
|
||||
"""Make a list of (name, value) pairs for writing debug info."""
|
||||
return human_sorted_items(
|
||||
(k, v) for k, v in self.__dict__.items() if not k.startswith("_")
|
||||
)
|
||||
|
||||
|
||||
def config_files_to_try(config_file):
|
||||
"""What config files should we try to read?
|
||||
|
||||
Returns a list of tuples:
|
||||
(filename, is_our_file, was_file_specified)
|
||||
"""
|
||||
|
||||
# Some API users were specifying ".coveragerc" to mean the same as
|
||||
# True, so make it so.
|
||||
if config_file == ".coveragerc":
|
||||
config_file = True
|
||||
specified_file = (config_file is not True)
|
||||
if not specified_file:
|
||||
# No file was specified. Check COVERAGE_RCFILE.
|
||||
config_file = os.environ.get('COVERAGE_RCFILE')
|
||||
if config_file:
|
||||
specified_file = True
|
||||
if not specified_file:
|
||||
# Still no file specified. Default to .coveragerc
|
||||
config_file = ".coveragerc"
|
||||
files_to_try = [
|
||||
(config_file, True, specified_file),
|
||||
("setup.cfg", False, False),
|
||||
("tox.ini", False, False),
|
||||
("pyproject.toml", False, False),
|
||||
]
|
||||
return files_to_try
|
||||
|
||||
|
||||
def read_coverage_config(config_file, warn, **kwargs):
|
||||
"""Read the coverage.py configuration.
|
||||
|
||||
Arguments:
|
||||
config_file: a boolean or string, see the `Coverage` class for the
|
||||
tricky details.
|
||||
warn: a function to issue warnings.
|
||||
all others: keyword arguments from the `Coverage` class, used for
|
||||
setting values in the configuration.
|
||||
|
||||
Returns:
|
||||
config:
|
||||
config is a CoverageConfig object read from the appropriate
|
||||
configuration file.
|
||||
|
||||
"""
|
||||
# Build the configuration from a number of sources:
|
||||
# 1) defaults:
|
||||
config = CoverageConfig()
|
||||
|
||||
# 2) from a file:
|
||||
if config_file:
|
||||
files_to_try = config_files_to_try(config_file)
|
||||
|
||||
for fname, our_file, specified_file in files_to_try:
|
||||
config_read = config.from_file(fname, warn, our_file=our_file)
|
||||
if config_read:
|
||||
break
|
||||
if specified_file:
|
||||
raise ConfigError(f"Couldn't read {fname!r} as a config file")
|
||||
|
||||
# $set_env.py: COVERAGE_DEBUG - Options for --debug.
|
||||
# 3) from environment variables:
|
||||
env_data_file = os.environ.get('COVERAGE_FILE')
|
||||
if env_data_file:
|
||||
config.data_file = env_data_file
|
||||
debugs = os.environ.get('COVERAGE_DEBUG')
|
||||
if debugs:
|
||||
config.debug.extend(d.strip() for d in debugs.split(","))
|
||||
|
||||
# 4) from constructor arguments:
|
||||
config.from_args(**kwargs)
|
||||
|
||||
# Once all the config has been collected, there's a little post-processing
|
||||
# to do.
|
||||
config.post_process()
|
||||
|
||||
return config
|
65
utils/python-venv/Lib/site-packages/coverage/context.py
Normal file
65
utils/python-venv/Lib/site-packages/coverage/context.py
Normal file
@ -0,0 +1,65 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Determine contexts for coverage.py"""
|
||||
|
||||
|
||||
def combine_context_switchers(context_switchers):
|
||||
"""Create a single context switcher from multiple switchers.
|
||||
|
||||
`context_switchers` is a list of functions that take a frame as an
|
||||
argument and return a string to use as the new context label.
|
||||
|
||||
Returns a function that composites `context_switchers` functions, or None
|
||||
if `context_switchers` is an empty list.
|
||||
|
||||
When invoked, the combined switcher calls `context_switchers` one-by-one
|
||||
until a string is returned. The combined switcher returns None if all
|
||||
`context_switchers` return None.
|
||||
"""
|
||||
if not context_switchers:
|
||||
return None
|
||||
|
||||
if len(context_switchers) == 1:
|
||||
return context_switchers[0]
|
||||
|
||||
def should_start_context(frame):
|
||||
"""The combiner for multiple context switchers."""
|
||||
for switcher in context_switchers:
|
||||
new_context = switcher(frame)
|
||||
if new_context is not None:
|
||||
return new_context
|
||||
return None
|
||||
|
||||
return should_start_context
|
||||
|
||||
|
||||
def should_start_context_test_function(frame):
|
||||
"""Is this frame calling a test_* function?"""
|
||||
co_name = frame.f_code.co_name
|
||||
if co_name.startswith("test") or co_name == "runTest":
|
||||
return qualname_from_frame(frame)
|
||||
return None
|
||||
|
||||
|
||||
def qualname_from_frame(frame):
|
||||
"""Get a qualified name for the code running in `frame`."""
|
||||
co = frame.f_code
|
||||
fname = co.co_name
|
||||
method = None
|
||||
if co.co_argcount and co.co_varnames[0] == "self":
|
||||
self = frame.f_locals.get("self", None)
|
||||
method = getattr(self, fname, None)
|
||||
|
||||
if method is None:
|
||||
func = frame.f_globals.get(fname)
|
||||
if func is None:
|
||||
return None
|
||||
return func.__module__ + "." + fname
|
||||
|
||||
func = getattr(method, "__func__", None)
|
||||
if func is None:
|
||||
cls = self.__class__
|
||||
return cls.__module__ + "." + cls.__name__ + "." + fname
|
||||
|
||||
return func.__module__ + "." + func.__qualname__
|
1232
utils/python-venv/Lib/site-packages/coverage/control.py
Normal file
1232
utils/python-venv/Lib/site-packages/coverage/control.py
Normal file
File diff suppressed because it is too large
Load Diff
171
utils/python-venv/Lib/site-packages/coverage/data.py
Normal file
171
utils/python-venv/Lib/site-packages/coverage/data.py
Normal file
@ -0,0 +1,171 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Coverage data for coverage.py.
|
||||
|
||||
This file had the 4.x JSON data support, which is now gone. This file still
|
||||
has storage-agnostic helpers, and is kept to avoid changing too many imports.
|
||||
CoverageData is now defined in sqldata.py, and imported here to keep the
|
||||
imports working.
|
||||
|
||||
"""
|
||||
|
||||
import glob
|
||||
import os.path
|
||||
|
||||
from coverage.exceptions import CoverageException, NoDataError
|
||||
from coverage.misc import file_be_gone, human_sorted, plural
|
||||
from coverage.sqldata import CoverageData
|
||||
|
||||
|
||||
def line_counts(data, fullpath=False):
|
||||
"""Return a dict summarizing the line coverage data.
|
||||
|
||||
Keys are based on the file names, and values are the number of executed
|
||||
lines. If `fullpath` is true, then the keys are the full pathnames of
|
||||
the files, otherwise they are the basenames of the files.
|
||||
|
||||
Returns a dict mapping file names to counts of lines.
|
||||
|
||||
"""
|
||||
summ = {}
|
||||
if fullpath:
|
||||
# pylint: disable=unnecessary-lambda-assignment
|
||||
filename_fn = lambda f: f
|
||||
else:
|
||||
filename_fn = os.path.basename
|
||||
for filename in data.measured_files():
|
||||
summ[filename_fn(filename)] = len(data.lines(filename))
|
||||
return summ
|
||||
|
||||
|
||||
def add_data_to_hash(data, filename, hasher):
|
||||
"""Contribute `filename`'s data to the `hasher`.
|
||||
|
||||
`hasher` is a `coverage.misc.Hasher` instance to be updated with
|
||||
the file's data. It should only get the results data, not the run
|
||||
data.
|
||||
|
||||
"""
|
||||
if data.has_arcs():
|
||||
hasher.update(sorted(data.arcs(filename) or []))
|
||||
else:
|
||||
hasher.update(sorted(data.lines(filename) or []))
|
||||
hasher.update(data.file_tracer(filename))
|
||||
|
||||
|
||||
def combinable_files(data_file, data_paths=None):
|
||||
"""Make a list of data files to be combined.
|
||||
|
||||
`data_file` is a path to a data file. `data_paths` is a list of files or
|
||||
directories of files.
|
||||
|
||||
Returns a list of absolute file paths.
|
||||
"""
|
||||
data_dir, local = os.path.split(os.path.abspath(data_file))
|
||||
|
||||
data_paths = data_paths or [data_dir]
|
||||
files_to_combine = []
|
||||
for p in data_paths:
|
||||
if os.path.isfile(p):
|
||||
files_to_combine.append(os.path.abspath(p))
|
||||
elif os.path.isdir(p):
|
||||
pattern = glob.escape(os.path.join(os.path.abspath(p), local)) +".*"
|
||||
files_to_combine.extend(glob.glob(pattern))
|
||||
else:
|
||||
raise NoDataError(f"Couldn't combine from non-existent path '{p}'")
|
||||
return files_to_combine
|
||||
|
||||
|
||||
def combine_parallel_data(
|
||||
data, aliases=None, data_paths=None, strict=False, keep=False, message=None,
|
||||
):
|
||||
"""Combine a number of data files together.
|
||||
|
||||
`data` is a CoverageData.
|
||||
|
||||
Treat `data.filename` as a file prefix, and combine the data from all
|
||||
of the data files starting with that prefix plus a dot.
|
||||
|
||||
If `aliases` is provided, it's a `PathAliases` object that is used to
|
||||
re-map paths to match the local machine's.
|
||||
|
||||
If `data_paths` is provided, it is a list of directories or files to
|
||||
combine. Directories are searched for files that start with
|
||||
`data.filename` plus dot as a prefix, and those files are combined.
|
||||
|
||||
If `data_paths` is not provided, then the directory portion of
|
||||
`data.filename` is used as the directory to search for data files.
|
||||
|
||||
Unless `keep` is True every data file found and combined is then deleted from disk. If a file
|
||||
cannot be read, a warning will be issued, and the file will not be
|
||||
deleted.
|
||||
|
||||
If `strict` is true, and no files are found to combine, an error is
|
||||
raised.
|
||||
|
||||
"""
|
||||
files_to_combine = combinable_files(data.base_filename(), data_paths)
|
||||
|
||||
if strict and not files_to_combine:
|
||||
raise NoDataError("No data to combine")
|
||||
|
||||
files_combined = 0
|
||||
for f in files_to_combine:
|
||||
if f == data.data_filename():
|
||||
# Sometimes we are combining into a file which is one of the
|
||||
# parallel files. Skip that file.
|
||||
if data._debug.should('dataio'):
|
||||
data._debug.write(f"Skipping combining ourself: {f!r}")
|
||||
continue
|
||||
if data._debug.should('dataio'):
|
||||
data._debug.write(f"Combining data file {f!r}")
|
||||
try:
|
||||
new_data = CoverageData(f, debug=data._debug)
|
||||
new_data.read()
|
||||
except CoverageException as exc:
|
||||
if data._warn:
|
||||
# The CoverageException has the file name in it, so just
|
||||
# use the message as the warning.
|
||||
data._warn(str(exc))
|
||||
else:
|
||||
data.update(new_data, aliases=aliases)
|
||||
files_combined += 1
|
||||
if message:
|
||||
try:
|
||||
file_name = os.path.relpath(f)
|
||||
except ValueError:
|
||||
# ValueError can be raised under Windows when os.getcwd() returns a
|
||||
# folder from a different drive than the drive of f, in which case
|
||||
# we print the original value of f instead of its relative path
|
||||
file_name = f
|
||||
message(f"Combined data file {file_name}")
|
||||
if not keep:
|
||||
if data._debug.should('dataio'):
|
||||
data._debug.write(f"Deleting combined data file {f!r}")
|
||||
file_be_gone(f)
|
||||
|
||||
if strict and not files_combined:
|
||||
raise NoDataError("No usable data files")
|
||||
|
||||
|
||||
def debug_data_file(filename):
|
||||
"""Implementation of 'coverage debug data'."""
|
||||
data = CoverageData(filename)
|
||||
filename = data.data_filename()
|
||||
print(f"path: {filename}")
|
||||
if not os.path.exists(filename):
|
||||
print("No data collected: file doesn't exist")
|
||||
return
|
||||
data.read()
|
||||
print(f"has_arcs: {data.has_arcs()!r}")
|
||||
summary = line_counts(data, fullpath=True)
|
||||
filenames = human_sorted(summary.keys())
|
||||
nfiles = len(filenames)
|
||||
print(f"{nfiles} file{plural(nfiles)}:")
|
||||
for f in filenames:
|
||||
line = f"{f}: {summary[f]} line{plural(summary[f])}"
|
||||
plugin = data.file_tracer(f)
|
||||
if plugin:
|
||||
line += f" [{plugin}]"
|
||||
print(line)
|
421
utils/python-venv/Lib/site-packages/coverage/debug.py
Normal file
421
utils/python-venv/Lib/site-packages/coverage/debug.py
Normal file
@ -0,0 +1,421 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Control of and utilities for debugging."""
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import inspect
|
||||
import io
|
||||
import itertools
|
||||
import os
|
||||
import pprint
|
||||
import reprlib
|
||||
import sys
|
||||
import types
|
||||
import _thread
|
||||
|
||||
from coverage.misc import isolate_module
|
||||
|
||||
os = isolate_module(os)
|
||||
|
||||
|
||||
# When debugging, it can be helpful to force some options, especially when
|
||||
# debugging the configuration mechanisms you usually use to control debugging!
|
||||
# This is a list of forced debugging options.
|
||||
FORCED_DEBUG = []
|
||||
FORCED_DEBUG_FILE = None
|
||||
|
||||
|
||||
class DebugControl:
|
||||
"""Control and output for debugging."""
|
||||
|
||||
show_repr_attr = False # For SimpleReprMixin
|
||||
|
||||
def __init__(self, options, output):
|
||||
"""Configure the options and output file for debugging."""
|
||||
self.options = list(options) + FORCED_DEBUG
|
||||
self.suppress_callers = False
|
||||
|
||||
filters = []
|
||||
if self.should('pid'):
|
||||
filters.append(add_pid_and_tid)
|
||||
self.output = DebugOutputFile.get_one(
|
||||
output,
|
||||
show_process=self.should('process'),
|
||||
filters=filters,
|
||||
)
|
||||
self.raw_output = self.output.outfile
|
||||
|
||||
def __repr__(self):
|
||||
return f"<DebugControl options={self.options!r} raw_output={self.raw_output!r}>"
|
||||
|
||||
def should(self, option):
|
||||
"""Decide whether to output debug information in category `option`."""
|
||||
if option == "callers" and self.suppress_callers:
|
||||
return False
|
||||
return (option in self.options)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def without_callers(self):
|
||||
"""A context manager to prevent call stacks from being logged."""
|
||||
old = self.suppress_callers
|
||||
self.suppress_callers = True
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.suppress_callers = old
|
||||
|
||||
def write(self, msg):
|
||||
"""Write a line of debug output.
|
||||
|
||||
`msg` is the line to write. A newline will be appended.
|
||||
|
||||
"""
|
||||
self.output.write(msg+"\n")
|
||||
if self.should('self'):
|
||||
caller_self = inspect.stack()[1][0].f_locals.get('self')
|
||||
if caller_self is not None:
|
||||
self.output.write(f"self: {caller_self!r}\n")
|
||||
if self.should('callers'):
|
||||
dump_stack_frames(out=self.output, skip=1)
|
||||
self.output.flush()
|
||||
|
||||
|
||||
class DebugControlString(DebugControl):
|
||||
"""A `DebugControl` that writes to a StringIO, for testing."""
|
||||
def __init__(self, options):
|
||||
super().__init__(options, io.StringIO())
|
||||
|
||||
def get_output(self):
|
||||
"""Get the output text from the `DebugControl`."""
|
||||
return self.raw_output.getvalue()
|
||||
|
||||
|
||||
class NoDebugging:
|
||||
"""A replacement for DebugControl that will never try to do anything."""
|
||||
def should(self, option): # pylint: disable=unused-argument
|
||||
"""Should we write debug messages? Never."""
|
||||
return False
|
||||
|
||||
|
||||
def info_header(label):
|
||||
"""Make a nice header string."""
|
||||
return "--{:-<60s}".format(" "+label+" ")
|
||||
|
||||
|
||||
def info_formatter(info):
|
||||
"""Produce a sequence of formatted lines from info.
|
||||
|
||||
`info` is a sequence of pairs (label, data). The produced lines are
|
||||
nicely formatted, ready to print.
|
||||
|
||||
"""
|
||||
info = list(info)
|
||||
if not info:
|
||||
return
|
||||
label_len = 30
|
||||
assert all(len(l) < label_len for l, _ in info)
|
||||
for label, data in info:
|
||||
if data == []:
|
||||
data = "-none-"
|
||||
if isinstance(data, tuple) and len(repr(tuple(data))) < 30:
|
||||
# Convert to tuple to scrub namedtuples.
|
||||
yield "%*s: %r" % (label_len, label, tuple(data))
|
||||
elif isinstance(data, (list, set, tuple)):
|
||||
prefix = "%*s:" % (label_len, label)
|
||||
for e in data:
|
||||
yield "%*s %s" % (label_len+1, prefix, e)
|
||||
prefix = ""
|
||||
else:
|
||||
yield "%*s: %s" % (label_len, label, data)
|
||||
|
||||
|
||||
def write_formatted_info(write, header, info):
|
||||
"""Write a sequence of (label,data) pairs nicely.
|
||||
|
||||
`write` is a function write(str) that accepts each line of output.
|
||||
`header` is a string to start the section. `info` is a sequence of
|
||||
(label, data) pairs, where label is a str, and data can be a single
|
||||
value, or a list/set/tuple.
|
||||
|
||||
"""
|
||||
write(info_header(header))
|
||||
for line in info_formatter(info):
|
||||
write(f" {line}")
|
||||
|
||||
|
||||
def short_stack(limit=None, skip=0):
|
||||
"""Return a string summarizing the call stack.
|
||||
|
||||
The string is multi-line, with one line per stack frame. Each line shows
|
||||
the function name, the file name, and the line number:
|
||||
|
||||
...
|
||||
start_import_stop : /Users/ned/coverage/trunk/tests/coveragetest.py @95
|
||||
import_local_file : /Users/ned/coverage/trunk/tests/coveragetest.py @81
|
||||
import_local_file : /Users/ned/coverage/trunk/coverage/backward.py @159
|
||||
...
|
||||
|
||||
`limit` is the number of frames to include, defaulting to all of them.
|
||||
|
||||
`skip` is the number of frames to skip, so that debugging functions can
|
||||
call this and not be included in the result.
|
||||
|
||||
"""
|
||||
stack = inspect.stack()[limit:skip:-1]
|
||||
return "\n".join("%30s : %s:%d" % (t[3], t[1], t[2]) for t in stack)
|
||||
|
||||
|
||||
def dump_stack_frames(limit=None, out=None, skip=0):
|
||||
"""Print a summary of the stack to stdout, or someplace else."""
|
||||
out = out or sys.stdout
|
||||
out.write(short_stack(limit=limit, skip=skip+1))
|
||||
out.write("\n")
|
||||
|
||||
|
||||
def clipped_repr(text, numchars=50):
|
||||
"""`repr(text)`, but limited to `numchars`."""
|
||||
r = reprlib.Repr()
|
||||
r.maxstring = numchars
|
||||
return r.repr(text)
|
||||
|
||||
|
||||
def short_id(id64):
|
||||
"""Given a 64-bit id, make a shorter 16-bit one."""
|
||||
id16 = 0
|
||||
for offset in range(0, 64, 16):
|
||||
id16 ^= id64 >> offset
|
||||
return id16 & 0xFFFF
|
||||
|
||||
|
||||
def add_pid_and_tid(text):
|
||||
"""A filter to add pid and tid to debug messages."""
|
||||
# Thread ids are useful, but too long. Make a shorter one.
|
||||
tid = f"{short_id(_thread.get_ident()):04x}"
|
||||
text = f"{os.getpid():5d}.{tid}: {text}"
|
||||
return text
|
||||
|
||||
|
||||
class SimpleReprMixin:
|
||||
"""A mixin implementing a simple __repr__."""
|
||||
simple_repr_ignore = ['simple_repr_ignore', '$coverage.object_id']
|
||||
|
||||
def __repr__(self):
|
||||
show_attrs = (
|
||||
(k, v) for k, v in self.__dict__.items()
|
||||
if getattr(v, "show_repr_attr", True)
|
||||
and not callable(v)
|
||||
and k not in self.simple_repr_ignore
|
||||
)
|
||||
return "<{klass} @0x{id:x} {attrs}>".format(
|
||||
klass=self.__class__.__name__,
|
||||
id=id(self),
|
||||
attrs=" ".join(f"{k}={v!r}" for k, v in show_attrs),
|
||||
)
|
||||
|
||||
|
||||
def simplify(v): # pragma: debugging
|
||||
"""Turn things which are nearly dict/list/etc into dict/list/etc."""
|
||||
if isinstance(v, dict):
|
||||
return {k:simplify(vv) for k, vv in v.items()}
|
||||
elif isinstance(v, (list, tuple)):
|
||||
return type(v)(simplify(vv) for vv in v)
|
||||
elif hasattr(v, "__dict__"):
|
||||
return simplify({'.'+k: v for k, v in v.__dict__.items()})
|
||||
else:
|
||||
return v
|
||||
|
||||
|
||||
def pp(v): # pragma: debugging
|
||||
"""Debug helper to pretty-print data, including SimpleNamespace objects."""
|
||||
# Might not be needed in 3.9+
|
||||
pprint.pprint(simplify(v))
|
||||
|
||||
|
||||
def filter_text(text, filters):
|
||||
"""Run `text` through a series of filters.
|
||||
|
||||
`filters` is a list of functions. Each takes a string and returns a
|
||||
string. Each is run in turn.
|
||||
|
||||
Returns: the final string that results after all of the filters have
|
||||
run.
|
||||
|
||||
"""
|
||||
clean_text = text.rstrip()
|
||||
ending = text[len(clean_text):]
|
||||
text = clean_text
|
||||
for fn in filters:
|
||||
lines = []
|
||||
for line in text.splitlines():
|
||||
lines.extend(fn(line).splitlines())
|
||||
text = "\n".join(lines)
|
||||
return text + ending
|
||||
|
||||
|
||||
class CwdTracker: # pragma: debugging
|
||||
"""A class to add cwd info to debug messages."""
|
||||
def __init__(self):
|
||||
self.cwd = None
|
||||
|
||||
def filter(self, text):
|
||||
"""Add a cwd message for each new cwd."""
|
||||
cwd = os.getcwd()
|
||||
if cwd != self.cwd:
|
||||
text = f"cwd is now {cwd!r}\n" + text
|
||||
self.cwd = cwd
|
||||
return text
|
||||
|
||||
|
||||
class DebugOutputFile: # pragma: debugging
|
||||
"""A file-like object that includes pid and cwd information."""
|
||||
def __init__(self, outfile, show_process, filters):
|
||||
self.outfile = outfile
|
||||
self.show_process = show_process
|
||||
self.filters = list(filters)
|
||||
|
||||
if self.show_process:
|
||||
self.filters.insert(0, CwdTracker().filter)
|
||||
self.write(f"New process: executable: {sys.executable!r}\n")
|
||||
self.write("New process: cmd: {!r}\n".format(getattr(sys, 'argv', None)))
|
||||
if hasattr(os, 'getppid'):
|
||||
self.write(f"New process: pid: {os.getpid()!r}, parent pid: {os.getppid()!r}\n")
|
||||
|
||||
SYS_MOD_NAME = '$coverage.debug.DebugOutputFile.the_one'
|
||||
SINGLETON_ATTR = 'the_one_and_is_interim'
|
||||
|
||||
@classmethod
|
||||
def get_one(cls, fileobj=None, show_process=True, filters=(), interim=False):
|
||||
"""Get a DebugOutputFile.
|
||||
|
||||
If `fileobj` is provided, then a new DebugOutputFile is made with it.
|
||||
|
||||
If `fileobj` isn't provided, then a file is chosen
|
||||
(COVERAGE_DEBUG_FILE, or stderr), and a process-wide singleton
|
||||
DebugOutputFile is made.
|
||||
|
||||
`show_process` controls whether the debug file adds process-level
|
||||
information, and filters is a list of other message filters to apply.
|
||||
|
||||
`filters` are the text filters to apply to the stream to annotate with
|
||||
pids, etc.
|
||||
|
||||
If `interim` is true, then a future `get_one` can replace this one.
|
||||
|
||||
"""
|
||||
if fileobj is not None:
|
||||
# Make DebugOutputFile around the fileobj passed.
|
||||
return cls(fileobj, show_process, filters)
|
||||
|
||||
# Because of the way igor.py deletes and re-imports modules,
|
||||
# this class can be defined more than once. But we really want
|
||||
# a process-wide singleton. So stash it in sys.modules instead of
|
||||
# on a class attribute. Yes, this is aggressively gross.
|
||||
singleton_module = sys.modules.get(cls.SYS_MOD_NAME)
|
||||
the_one, is_interim = getattr(singleton_module, cls.SINGLETON_ATTR, (None, True))
|
||||
if the_one is None or is_interim:
|
||||
if fileobj is None:
|
||||
debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE)
|
||||
if debug_file_name in ("stdout", "stderr"):
|
||||
fileobj = getattr(sys, debug_file_name)
|
||||
elif debug_file_name:
|
||||
fileobj = open(debug_file_name, "a")
|
||||
else:
|
||||
fileobj = sys.stderr
|
||||
the_one = cls(fileobj, show_process, filters)
|
||||
singleton_module = types.ModuleType(cls.SYS_MOD_NAME)
|
||||
setattr(singleton_module, cls.SINGLETON_ATTR, (the_one, interim))
|
||||
sys.modules[cls.SYS_MOD_NAME] = singleton_module
|
||||
return the_one
|
||||
|
||||
def write(self, text):
|
||||
"""Just like file.write, but filter through all our filters."""
|
||||
self.outfile.write(filter_text(text, self.filters))
|
||||
self.outfile.flush()
|
||||
|
||||
def flush(self):
|
||||
"""Flush our file."""
|
||||
self.outfile.flush()
|
||||
|
||||
|
||||
def log(msg, stack=False): # pragma: debugging
|
||||
"""Write a log message as forcefully as possible."""
|
||||
out = DebugOutputFile.get_one(interim=True)
|
||||
out.write(msg+"\n")
|
||||
if stack:
|
||||
dump_stack_frames(out=out, skip=1)
|
||||
|
||||
|
||||
def decorate_methods(decorator, butnot=(), private=False): # pragma: debugging
|
||||
"""A class decorator to apply a decorator to methods."""
|
||||
def _decorator(cls):
|
||||
for name, meth in inspect.getmembers(cls, inspect.isroutine):
|
||||
if name not in cls.__dict__:
|
||||
continue
|
||||
if name != "__init__":
|
||||
if not private and name.startswith("_"):
|
||||
continue
|
||||
if name in butnot:
|
||||
continue
|
||||
setattr(cls, name, decorator(meth))
|
||||
return cls
|
||||
return _decorator
|
||||
|
||||
|
||||
def break_in_pudb(func): # pragma: debugging
|
||||
"""A function decorator to stop in the debugger for each call."""
|
||||
@functools.wraps(func)
|
||||
def _wrapper(*args, **kwargs):
|
||||
import pudb
|
||||
sys.stdout = sys.__stdout__
|
||||
pudb.set_trace()
|
||||
return func(*args, **kwargs)
|
||||
return _wrapper
|
||||
|
||||
|
||||
OBJ_IDS = itertools.count()
|
||||
CALLS = itertools.count()
|
||||
OBJ_ID_ATTR = "$coverage.object_id"
|
||||
|
||||
def show_calls(show_args=True, show_stack=False, show_return=False): # pragma: debugging
|
||||
"""A method decorator to debug-log each call to the function."""
|
||||
def _decorator(func):
|
||||
@functools.wraps(func)
|
||||
def _wrapper(self, *args, **kwargs):
|
||||
oid = getattr(self, OBJ_ID_ATTR, None)
|
||||
if oid is None:
|
||||
oid = f"{os.getpid():08d} {next(OBJ_IDS):04d}"
|
||||
setattr(self, OBJ_ID_ATTR, oid)
|
||||
extra = ""
|
||||
if show_args:
|
||||
eargs = ", ".join(map(repr, args))
|
||||
ekwargs = ", ".join("{}={!r}".format(*item) for item in kwargs.items())
|
||||
extra += "("
|
||||
extra += eargs
|
||||
if eargs and ekwargs:
|
||||
extra += ", "
|
||||
extra += ekwargs
|
||||
extra += ")"
|
||||
if show_stack:
|
||||
extra += " @ "
|
||||
extra += "; ".join(_clean_stack_line(l) for l in short_stack().splitlines())
|
||||
callid = next(CALLS)
|
||||
msg = f"{oid} {callid:04d} {func.__name__}{extra}\n"
|
||||
DebugOutputFile.get_one(interim=True).write(msg)
|
||||
ret = func(self, *args, **kwargs)
|
||||
if show_return:
|
||||
msg = f"{oid} {callid:04d} {func.__name__} return {ret!r}\n"
|
||||
DebugOutputFile.get_one(interim=True).write(msg)
|
||||
return ret
|
||||
return _wrapper
|
||||
return _decorator
|
||||
|
||||
|
||||
def _clean_stack_line(s): # pragma: debugging
|
||||
"""Simplify some paths in a stack trace, for compactness."""
|
||||
s = s.strip()
|
||||
s = s.replace(os.path.dirname(__file__) + '/', '')
|
||||
s = s.replace(os.path.dirname(os.__file__) + '/', '')
|
||||
s = s.replace(sys.prefix + '/', '')
|
||||
return s
|
41
utils/python-venv/Lib/site-packages/coverage/disposition.py
Normal file
41
utils/python-venv/Lib/site-packages/coverage/disposition.py
Normal file
@ -0,0 +1,41 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Simple value objects for tracking what to do with files."""
|
||||
|
||||
|
||||
class FileDisposition:
|
||||
"""A simple value type for recording what to do with a file."""
|
||||
|
||||
def __repr__(self):
|
||||
return f"<FileDisposition {self.canonical_filename!r}: trace={self.trace}>"
|
||||
|
||||
|
||||
# FileDisposition "methods": FileDisposition is a pure value object, so it can
|
||||
# be implemented in either C or Python. Acting on them is done with these
|
||||
# functions.
|
||||
|
||||
def disposition_init(cls, original_filename):
|
||||
"""Construct and initialize a new FileDisposition object."""
|
||||
disp = cls()
|
||||
disp.original_filename = original_filename
|
||||
disp.canonical_filename = original_filename
|
||||
disp.source_filename = None
|
||||
disp.trace = False
|
||||
disp.reason = ""
|
||||
disp.file_tracer = None
|
||||
disp.has_dynamic_filename = False
|
||||
return disp
|
||||
|
||||
|
||||
def disposition_debug_msg(disp):
|
||||
"""Make a nice debug message of what the FileDisposition is doing."""
|
||||
if disp.trace:
|
||||
msg = f"Tracing {disp.original_filename!r}"
|
||||
if disp.original_filename != disp.source_filename:
|
||||
msg += f" as {disp.source_filename!r}"
|
||||
if disp.file_tracer:
|
||||
msg += f": will be traced by {disp.file_tracer!r}"
|
||||
else:
|
||||
msg = f"Not tracing {disp.original_filename!r}: {disp.reason}"
|
||||
return msg
|
151
utils/python-venv/Lib/site-packages/coverage/env.py
Normal file
151
utils/python-venv/Lib/site-packages/coverage/env.py
Normal file
@ -0,0 +1,151 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Determine facts about the environment."""
|
||||
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
|
||||
# Operating systems.
|
||||
WINDOWS = sys.platform == "win32"
|
||||
LINUX = sys.platform.startswith("linux")
|
||||
OSX = sys.platform == "darwin"
|
||||
|
||||
# Python implementations.
|
||||
CPYTHON = (platform.python_implementation() == "CPython")
|
||||
PYPY = (platform.python_implementation() == "PyPy")
|
||||
JYTHON = (platform.python_implementation() == "Jython")
|
||||
IRONPYTHON = (platform.python_implementation() == "IronPython")
|
||||
|
||||
# Python versions. We amend version_info with one more value, a zero if an
|
||||
# official version, or 1 if built from source beyond an official version.
|
||||
PYVERSION = sys.version_info + (int(platform.python_version()[-1] == "+"),)
|
||||
|
||||
if PYPY:
|
||||
PYPYVERSION = sys.pypy_version_info
|
||||
|
||||
# Python behavior.
|
||||
class PYBEHAVIOR:
|
||||
"""Flags indicating this Python's behavior."""
|
||||
|
||||
# Does Python conform to PEP626, Precise line numbers for debugging and other tools.
|
||||
# https://www.python.org/dev/peps/pep-0626
|
||||
pep626 = CPYTHON and (PYVERSION > (3, 10, 0, 'alpha', 4))
|
||||
|
||||
# Is "if __debug__" optimized away?
|
||||
if PYPY:
|
||||
optimize_if_debug = True
|
||||
else:
|
||||
optimize_if_debug = not pep626
|
||||
|
||||
# Is "if not __debug__" optimized away? The exact details have changed
|
||||
# across versions.
|
||||
if pep626:
|
||||
optimize_if_not_debug = 1
|
||||
elif PYPY:
|
||||
if PYVERSION >= (3, 9):
|
||||
optimize_if_not_debug = 2
|
||||
elif PYVERSION[:2] == (3, 8):
|
||||
optimize_if_not_debug = 3
|
||||
else:
|
||||
optimize_if_not_debug = 1
|
||||
else:
|
||||
if PYVERSION >= (3, 8, 0, 'beta', 1):
|
||||
optimize_if_not_debug = 2
|
||||
else:
|
||||
optimize_if_not_debug = 1
|
||||
|
||||
# Can co_lnotab have negative deltas?
|
||||
negative_lnotab = not (PYPY and PYPYVERSION < (7, 2))
|
||||
|
||||
# 3.7 changed how functions with only docstrings are numbered.
|
||||
docstring_only_function = (not PYPY) and ((3, 7, 0, 'beta', 5) <= PYVERSION <= (3, 10))
|
||||
|
||||
# When a break/continue/return statement in a try block jumps to a finally
|
||||
# block, does the finally block do the break/continue/return (pre-3.8), or
|
||||
# does the finally jump back to the break/continue/return (3.8) to do the
|
||||
# work?
|
||||
finally_jumps_back = ((3, 8) <= PYVERSION < (3, 10))
|
||||
|
||||
# When a function is decorated, does the trace function get called for the
|
||||
# @-line and also the def-line (new behavior in 3.8)? Or just the @-line
|
||||
# (old behavior)?
|
||||
trace_decorated_def = (CPYTHON and PYVERSION >= (3, 8)) or (PYPY and PYVERSION >= (3, 9))
|
||||
|
||||
# Functions are no longer claimed to start at their earliest decorator even though
|
||||
# the decorators are traced?
|
||||
def_ast_no_decorator = (PYPY and PYVERSION >= (3, 9))
|
||||
|
||||
# CPython 3.11 now jumps to the decorator line again while executing
|
||||
# the decorator.
|
||||
trace_decorator_line_again = (CPYTHON and PYVERSION > (3, 11, 0, 'alpha', 3, 0))
|
||||
|
||||
# Are while-true loops optimized into absolute jumps with no loop setup?
|
||||
nix_while_true = (PYVERSION >= (3, 8))
|
||||
|
||||
# CPython 3.9a1 made sys.argv[0] and other reported files absolute paths.
|
||||
report_absolute_files = ((CPYTHON or (PYPYVERSION >= (7, 3, 10))) and PYVERSION >= (3, 9))
|
||||
|
||||
# Lines after break/continue/return/raise are no longer compiled into the
|
||||
# bytecode. They used to be marked as missing, now they aren't executable.
|
||||
omit_after_jump = pep626
|
||||
|
||||
# PyPy has always omitted statements after return.
|
||||
omit_after_return = omit_after_jump or PYPY
|
||||
|
||||
# Modules used to have firstlineno equal to the line number of the first
|
||||
# real line of code. Now they always start at 1.
|
||||
module_firstline_1 = pep626
|
||||
|
||||
# Are "if 0:" lines (and similar) kept in the compiled code?
|
||||
keep_constant_test = pep626
|
||||
|
||||
# When leaving a with-block, do we visit the with-line again for the exit?
|
||||
exit_through_with = (PYVERSION >= (3, 10, 0, 'beta'))
|
||||
|
||||
# Match-case construct.
|
||||
match_case = (PYVERSION >= (3, 10))
|
||||
|
||||
# Some words are keywords in some places, identifiers in other places.
|
||||
soft_keywords = (PYVERSION >= (3, 10))
|
||||
|
||||
# Modules start with a line numbered zero. This means empty modules have
|
||||
# only a 0-number line, which is ignored, giving a truly empty module.
|
||||
empty_is_empty = (PYVERSION >= (3, 11, 0, 'beta', 4))
|
||||
|
||||
# Coverage.py specifics.
|
||||
|
||||
# Are we using the C-implemented trace function?
|
||||
C_TRACER = os.getenv('COVERAGE_TEST_TRACER', 'c') == 'c'
|
||||
|
||||
# Are we coverage-measuring ourselves?
|
||||
METACOV = os.getenv('COVERAGE_COVERAGE', '') != ''
|
||||
|
||||
# Are we running our test suite?
|
||||
# Even when running tests, you can use COVERAGE_TESTING=0 to disable the
|
||||
# test-specific behavior like contracts.
|
||||
TESTING = os.getenv('COVERAGE_TESTING', '') == 'True'
|
||||
|
||||
# Environment COVERAGE_NO_CONTRACTS=1 can turn off contracts while debugging
|
||||
# tests to remove noise from stack traces.
|
||||
# $set_env.py: COVERAGE_NO_CONTRACTS - Disable PyContracts to simplify stack traces.
|
||||
USE_CONTRACTS = (
|
||||
TESTING
|
||||
and not bool(int(os.environ.get("COVERAGE_NO_CONTRACTS", 0)))
|
||||
and (PYVERSION < (3, 11))
|
||||
)
|
||||
|
||||
def debug_info():
|
||||
"""Return a list of (name, value) pairs for printing debug information."""
|
||||
info = [
|
||||
(name, value) for name, value in globals().items()
|
||||
if not name.startswith("_") and
|
||||
name not in {"PYBEHAVIOR", "debug_info"} and
|
||||
not isinstance(value, type(os))
|
||||
]
|
||||
info += [
|
||||
(name, value) for name, value in PYBEHAVIOR.__dict__.items()
|
||||
if not name.startswith("_")
|
||||
]
|
||||
return sorted(info)
|
72
utils/python-venv/Lib/site-packages/coverage/exceptions.py
Normal file
72
utils/python-venv/Lib/site-packages/coverage/exceptions.py
Normal file
@ -0,0 +1,72 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Exceptions coverage.py can raise."""
|
||||
|
||||
|
||||
class _BaseCoverageException(Exception):
|
||||
"""The base-base of all Coverage exceptions."""
|
||||
pass
|
||||
|
||||
|
||||
class CoverageException(_BaseCoverageException):
|
||||
"""The base class of all exceptions raised by Coverage.py."""
|
||||
pass
|
||||
|
||||
|
||||
class ConfigError(_BaseCoverageException):
|
||||
"""A problem with a config file, or a value in one."""
|
||||
pass
|
||||
|
||||
|
||||
class DataError(CoverageException):
|
||||
"""An error in using a data file."""
|
||||
pass
|
||||
|
||||
class NoDataError(CoverageException):
|
||||
"""We didn't have data to work with."""
|
||||
pass
|
||||
|
||||
|
||||
class NoSource(CoverageException):
|
||||
"""We couldn't find the source for a module."""
|
||||
pass
|
||||
|
||||
|
||||
class NoCode(NoSource):
|
||||
"""We couldn't find any code at all."""
|
||||
pass
|
||||
|
||||
|
||||
class NotPython(CoverageException):
|
||||
"""A source file turned out not to be parsable Python."""
|
||||
pass
|
||||
|
||||
|
||||
class PluginError(CoverageException):
|
||||
"""A plugin misbehaved."""
|
||||
pass
|
||||
|
||||
|
||||
class _ExceptionDuringRun(CoverageException):
|
||||
"""An exception happened while running customer code.
|
||||
|
||||
Construct it with three arguments, the values from `sys.exc_info`.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class _StopEverything(_BaseCoverageException):
|
||||
"""An exception that means everything should stop.
|
||||
|
||||
The CoverageTest class converts these to SkipTest, so that when running
|
||||
tests, raising this exception will automatically skip the test.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CoverageWarning(Warning):
|
||||
"""A warning from Coverage.py."""
|
||||
pass
|
307
utils/python-venv/Lib/site-packages/coverage/execfile.py
Normal file
307
utils/python-venv/Lib/site-packages/coverage/execfile.py
Normal file
@ -0,0 +1,307 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Execute files of Python code."""
|
||||
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
import inspect
|
||||
import marshal
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import types
|
||||
|
||||
from coverage import env
|
||||
from coverage.exceptions import CoverageException, _ExceptionDuringRun, NoCode, NoSource
|
||||
from coverage.files import canonical_filename, python_reported_file
|
||||
from coverage.misc import isolate_module
|
||||
from coverage.phystokens import compile_unicode
|
||||
from coverage.python import get_python_source
|
||||
|
||||
os = isolate_module(os)
|
||||
|
||||
|
||||
PYC_MAGIC_NUMBER = importlib.util.MAGIC_NUMBER
|
||||
|
||||
class DummyLoader:
|
||||
"""A shim for the pep302 __loader__, emulating pkgutil.ImpLoader.
|
||||
|
||||
Currently only implements the .fullname attribute
|
||||
"""
|
||||
def __init__(self, fullname, *_args):
|
||||
self.fullname = fullname
|
||||
|
||||
|
||||
def find_module(modulename):
|
||||
"""Find the module named `modulename`.
|
||||
|
||||
Returns the file path of the module, the name of the enclosing
|
||||
package, and the spec.
|
||||
"""
|
||||
try:
|
||||
spec = importlib.util.find_spec(modulename)
|
||||
except ImportError as err:
|
||||
raise NoSource(str(err)) from err
|
||||
if not spec:
|
||||
raise NoSource(f"No module named {modulename!r}")
|
||||
pathname = spec.origin
|
||||
packagename = spec.name
|
||||
if spec.submodule_search_locations:
|
||||
mod_main = modulename + ".__main__"
|
||||
spec = importlib.util.find_spec(mod_main)
|
||||
if not spec:
|
||||
raise NoSource(
|
||||
f"No module named {mod_main}; " +
|
||||
f"{modulename!r} is a package and cannot be directly executed"
|
||||
)
|
||||
pathname = spec.origin
|
||||
packagename = spec.name
|
||||
packagename = packagename.rpartition(".")[0]
|
||||
return pathname, packagename, spec
|
||||
|
||||
|
||||
class PyRunner:
|
||||
"""Multi-stage execution of Python code.
|
||||
|
||||
This is meant to emulate real Python execution as closely as possible.
|
||||
|
||||
"""
|
||||
def __init__(self, args, as_module=False):
|
||||
self.args = args
|
||||
self.as_module = as_module
|
||||
|
||||
self.arg0 = args[0]
|
||||
self.package = self.modulename = self.pathname = self.loader = self.spec = None
|
||||
|
||||
def prepare(self):
|
||||
"""Set sys.path properly.
|
||||
|
||||
This needs to happen before any importing, and without importing anything.
|
||||
"""
|
||||
if self.as_module:
|
||||
path0 = os.getcwd()
|
||||
elif os.path.isdir(self.arg0):
|
||||
# Running a directory means running the __main__.py file in that
|
||||
# directory.
|
||||
path0 = self.arg0
|
||||
else:
|
||||
path0 = os.path.abspath(os.path.dirname(self.arg0))
|
||||
|
||||
if os.path.isdir(sys.path[0]):
|
||||
# sys.path fakery. If we are being run as a command, then sys.path[0]
|
||||
# is the directory of the "coverage" script. If this is so, replace
|
||||
# sys.path[0] with the directory of the file we're running, or the
|
||||
# current directory when running modules. If it isn't so, then we
|
||||
# don't know what's going on, and just leave it alone.
|
||||
top_file = inspect.stack()[-1][0].f_code.co_filename
|
||||
sys_path_0_abs = os.path.abspath(sys.path[0])
|
||||
top_file_dir_abs = os.path.abspath(os.path.dirname(top_file))
|
||||
sys_path_0_abs = canonical_filename(sys_path_0_abs)
|
||||
top_file_dir_abs = canonical_filename(top_file_dir_abs)
|
||||
if sys_path_0_abs != top_file_dir_abs:
|
||||
path0 = None
|
||||
|
||||
else:
|
||||
# sys.path[0] is a file. Is the next entry the directory containing
|
||||
# that file?
|
||||
if sys.path[1] == os.path.dirname(sys.path[0]):
|
||||
# Can it be right to always remove that?
|
||||
del sys.path[1]
|
||||
|
||||
if path0 is not None:
|
||||
sys.path[0] = python_reported_file(path0)
|
||||
|
||||
def _prepare2(self):
|
||||
"""Do more preparation to run Python code.
|
||||
|
||||
Includes finding the module to run and adjusting sys.argv[0].
|
||||
This method is allowed to import code.
|
||||
|
||||
"""
|
||||
if self.as_module:
|
||||
self.modulename = self.arg0
|
||||
pathname, self.package, self.spec = find_module(self.modulename)
|
||||
if self.spec is not None:
|
||||
self.modulename = self.spec.name
|
||||
self.loader = DummyLoader(self.modulename)
|
||||
self.pathname = os.path.abspath(pathname)
|
||||
self.args[0] = self.arg0 = self.pathname
|
||||
elif os.path.isdir(self.arg0):
|
||||
# Running a directory means running the __main__.py file in that
|
||||
# directory.
|
||||
for ext in [".py", ".pyc", ".pyo"]:
|
||||
try_filename = os.path.join(self.arg0, "__main__" + ext)
|
||||
# 3.8.10 changed how files are reported when running a
|
||||
# directory. But I'm not sure how far this change is going to
|
||||
# spread, so I'll just hard-code it here for now.
|
||||
if env.PYVERSION >= (3, 8, 10):
|
||||
try_filename = os.path.abspath(try_filename)
|
||||
if os.path.exists(try_filename):
|
||||
self.arg0 = try_filename
|
||||
break
|
||||
else:
|
||||
raise NoSource(f"Can't find '__main__' module in '{self.arg0}'")
|
||||
|
||||
# Make a spec. I don't know if this is the right way to do it.
|
||||
try_filename = python_reported_file(try_filename)
|
||||
self.spec = importlib.machinery.ModuleSpec("__main__", None, origin=try_filename)
|
||||
self.spec.has_location = True
|
||||
self.package = ""
|
||||
self.loader = DummyLoader("__main__")
|
||||
else:
|
||||
self.loader = DummyLoader("__main__")
|
||||
|
||||
self.arg0 = python_reported_file(self.arg0)
|
||||
|
||||
def run(self):
|
||||
"""Run the Python code!"""
|
||||
|
||||
self._prepare2()
|
||||
|
||||
# Create a module to serve as __main__
|
||||
main_mod = types.ModuleType('__main__')
|
||||
|
||||
from_pyc = self.arg0.endswith((".pyc", ".pyo"))
|
||||
main_mod.__file__ = self.arg0
|
||||
if from_pyc:
|
||||
main_mod.__file__ = main_mod.__file__[:-1]
|
||||
if self.package is not None:
|
||||
main_mod.__package__ = self.package
|
||||
main_mod.__loader__ = self.loader
|
||||
if self.spec is not None:
|
||||
main_mod.__spec__ = self.spec
|
||||
|
||||
main_mod.__builtins__ = sys.modules['builtins']
|
||||
|
||||
sys.modules['__main__'] = main_mod
|
||||
|
||||
# Set sys.argv properly.
|
||||
sys.argv = self.args
|
||||
|
||||
try:
|
||||
# Make a code object somehow.
|
||||
if from_pyc:
|
||||
code = make_code_from_pyc(self.arg0)
|
||||
else:
|
||||
code = make_code_from_py(self.arg0)
|
||||
except CoverageException:
|
||||
raise
|
||||
except Exception as exc:
|
||||
msg = f"Couldn't run '{self.arg0}' as Python code: {exc.__class__.__name__}: {exc}"
|
||||
raise CoverageException(msg) from exc
|
||||
|
||||
# Execute the code object.
|
||||
# Return to the original directory in case the test code exits in
|
||||
# a non-existent directory.
|
||||
cwd = os.getcwd()
|
||||
try:
|
||||
exec(code, main_mod.__dict__)
|
||||
except SystemExit: # pylint: disable=try-except-raise
|
||||
# The user called sys.exit(). Just pass it along to the upper
|
||||
# layers, where it will be handled.
|
||||
raise
|
||||
except Exception:
|
||||
# Something went wrong while executing the user code.
|
||||
# Get the exc_info, and pack them into an exception that we can
|
||||
# throw up to the outer loop. We peel one layer off the traceback
|
||||
# so that the coverage.py code doesn't appear in the final printed
|
||||
# traceback.
|
||||
typ, err, tb = sys.exc_info()
|
||||
|
||||
# PyPy3 weirdness. If I don't access __context__, then somehow it
|
||||
# is non-None when the exception is reported at the upper layer,
|
||||
# and a nested exception is shown to the user. This getattr fixes
|
||||
# it somehow? https://bitbucket.org/pypy/pypy/issue/1903
|
||||
getattr(err, '__context__', None)
|
||||
|
||||
# Call the excepthook.
|
||||
try:
|
||||
err.__traceback__ = err.__traceback__.tb_next
|
||||
sys.excepthook(typ, err, tb.tb_next)
|
||||
except SystemExit: # pylint: disable=try-except-raise
|
||||
raise
|
||||
except Exception as exc:
|
||||
# Getting the output right in the case of excepthook
|
||||
# shenanigans is kind of involved.
|
||||
sys.stderr.write("Error in sys.excepthook:\n")
|
||||
typ2, err2, tb2 = sys.exc_info()
|
||||
err2.__suppress_context__ = True
|
||||
err2.__traceback__ = err2.__traceback__.tb_next
|
||||
sys.__excepthook__(typ2, err2, tb2.tb_next)
|
||||
sys.stderr.write("\nOriginal exception was:\n")
|
||||
raise _ExceptionDuringRun(typ, err, tb.tb_next) from exc
|
||||
else:
|
||||
sys.exit(1)
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
|
||||
|
||||
def run_python_module(args):
|
||||
"""Run a Python module, as though with ``python -m name args...``.
|
||||
|
||||
`args` is the argument array to present as sys.argv, including the first
|
||||
element naming the module being executed.
|
||||
|
||||
This is a helper for tests, to encapsulate how to use PyRunner.
|
||||
|
||||
"""
|
||||
runner = PyRunner(args, as_module=True)
|
||||
runner.prepare()
|
||||
runner.run()
|
||||
|
||||
|
||||
def run_python_file(args):
|
||||
"""Run a Python file as if it were the main program on the command line.
|
||||
|
||||
`args` is the argument array to present as sys.argv, including the first
|
||||
element naming the file being executed. `package` is the name of the
|
||||
enclosing package, if any.
|
||||
|
||||
This is a helper for tests, to encapsulate how to use PyRunner.
|
||||
|
||||
"""
|
||||
runner = PyRunner(args, as_module=False)
|
||||
runner.prepare()
|
||||
runner.run()
|
||||
|
||||
|
||||
def make_code_from_py(filename):
|
||||
"""Get source from `filename` and make a code object of it."""
|
||||
# Open the source file.
|
||||
try:
|
||||
source = get_python_source(filename)
|
||||
except (OSError, NoSource) as exc:
|
||||
raise NoSource(f"No file to run: '{filename}'") from exc
|
||||
|
||||
code = compile_unicode(source, filename, "exec")
|
||||
return code
|
||||
|
||||
|
||||
def make_code_from_pyc(filename):
|
||||
"""Get a code object from a .pyc file."""
|
||||
try:
|
||||
fpyc = open(filename, "rb")
|
||||
except OSError as exc:
|
||||
raise NoCode(f"No file to run: '{filename}'") from exc
|
||||
|
||||
with fpyc:
|
||||
# First four bytes are a version-specific magic number. It has to
|
||||
# match or we won't run the file.
|
||||
magic = fpyc.read(4)
|
||||
if magic != PYC_MAGIC_NUMBER:
|
||||
raise NoCode(f"Bad magic number in .pyc file: {magic!r} != {PYC_MAGIC_NUMBER!r}")
|
||||
|
||||
flags = struct.unpack('<L', fpyc.read(4))[0]
|
||||
hash_based = flags & 0x01
|
||||
if hash_based:
|
||||
fpyc.read(8) # Skip the hash.
|
||||
else:
|
||||
# Skip the junk in the header that we don't need.
|
||||
fpyc.read(4) # Skip the moddate.
|
||||
fpyc.read(4) # Skip the size.
|
||||
|
||||
# The rest of the file is the code object we want.
|
||||
code = marshal.load(fpyc)
|
||||
|
||||
return code
|
436
utils/python-venv/Lib/site-packages/coverage/files.py
Normal file
436
utils/python-venv/Lib/site-packages/coverage/files.py
Normal file
@ -0,0 +1,436 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""File wrangling."""
|
||||
|
||||
import fnmatch
|
||||
import hashlib
|
||||
import ntpath
|
||||
import os
|
||||
import os.path
|
||||
import posixpath
|
||||
import re
|
||||
import sys
|
||||
|
||||
from coverage import env
|
||||
from coverage.exceptions import ConfigError
|
||||
from coverage.misc import contract, human_sorted, isolate_module, join_regex
|
||||
|
||||
|
||||
os = isolate_module(os)
|
||||
|
||||
|
||||
def set_relative_directory():
|
||||
"""Set the directory that `relative_filename` will be relative to."""
|
||||
global RELATIVE_DIR, CANONICAL_FILENAME_CACHE
|
||||
|
||||
# The current directory
|
||||
abs_curdir = abs_file(os.curdir)
|
||||
if not abs_curdir.endswith(os.sep):
|
||||
# Suffix with separator only if not at the system root
|
||||
abs_curdir = abs_curdir + os.sep
|
||||
|
||||
# The absolute path to our current directory.
|
||||
RELATIVE_DIR = os.path.normcase(abs_curdir)
|
||||
|
||||
# Cache of results of calling the canonical_filename() method, to
|
||||
# avoid duplicating work.
|
||||
CANONICAL_FILENAME_CACHE = {}
|
||||
|
||||
|
||||
def relative_directory():
|
||||
"""Return the directory that `relative_filename` is relative to."""
|
||||
return RELATIVE_DIR
|
||||
|
||||
|
||||
@contract(returns='unicode')
|
||||
def relative_filename(filename):
|
||||
"""Return the relative form of `filename`.
|
||||
|
||||
The file name will be relative to the current directory when the
|
||||
`set_relative_directory` was called.
|
||||
|
||||
"""
|
||||
fnorm = os.path.normcase(filename)
|
||||
if fnorm.startswith(RELATIVE_DIR):
|
||||
filename = filename[len(RELATIVE_DIR):]
|
||||
return filename
|
||||
|
||||
|
||||
@contract(returns='unicode')
|
||||
def canonical_filename(filename):
|
||||
"""Return a canonical file name for `filename`.
|
||||
|
||||
An absolute path with no redundant components and normalized case.
|
||||
|
||||
"""
|
||||
if filename not in CANONICAL_FILENAME_CACHE:
|
||||
cf = filename
|
||||
if not os.path.isabs(filename):
|
||||
for path in [os.curdir] + sys.path:
|
||||
if path is None:
|
||||
continue
|
||||
f = os.path.join(path, filename)
|
||||
try:
|
||||
exists = os.path.exists(f)
|
||||
except UnicodeError:
|
||||
exists = False
|
||||
if exists:
|
||||
cf = f
|
||||
break
|
||||
cf = abs_file(cf)
|
||||
CANONICAL_FILENAME_CACHE[filename] = cf
|
||||
return CANONICAL_FILENAME_CACHE[filename]
|
||||
|
||||
|
||||
MAX_FLAT = 100
|
||||
|
||||
@contract(filename='unicode', returns='unicode')
|
||||
def flat_rootname(filename):
|
||||
"""A base for a flat file name to correspond to this file.
|
||||
|
||||
Useful for writing files about the code where you want all the files in
|
||||
the same directory, but need to differentiate same-named files from
|
||||
different directories.
|
||||
|
||||
For example, the file a/b/c.py will return 'd_86bbcbe134d28fd2_c_py'
|
||||
|
||||
"""
|
||||
dirname, basename = ntpath.split(filename)
|
||||
if dirname:
|
||||
fp = hashlib.new("sha3_256", dirname.encode("UTF-8")).hexdigest()[:16]
|
||||
prefix = f"d_{fp}_"
|
||||
else:
|
||||
prefix = ""
|
||||
return prefix + basename.replace(".", "_")
|
||||
|
||||
|
||||
if env.WINDOWS:
|
||||
|
||||
_ACTUAL_PATH_CACHE = {}
|
||||
_ACTUAL_PATH_LIST_CACHE = {}
|
||||
|
||||
def actual_path(path):
|
||||
"""Get the actual path of `path`, including the correct case."""
|
||||
if path in _ACTUAL_PATH_CACHE:
|
||||
return _ACTUAL_PATH_CACHE[path]
|
||||
|
||||
head, tail = os.path.split(path)
|
||||
if not tail:
|
||||
# This means head is the drive spec: normalize it.
|
||||
actpath = head.upper()
|
||||
elif not head:
|
||||
actpath = tail
|
||||
else:
|
||||
head = actual_path(head)
|
||||
if head in _ACTUAL_PATH_LIST_CACHE:
|
||||
files = _ACTUAL_PATH_LIST_CACHE[head]
|
||||
else:
|
||||
try:
|
||||
files = os.listdir(head)
|
||||
except Exception:
|
||||
# This will raise OSError, or this bizarre TypeError:
|
||||
# https://bugs.python.org/issue1776160
|
||||
files = []
|
||||
_ACTUAL_PATH_LIST_CACHE[head] = files
|
||||
normtail = os.path.normcase(tail)
|
||||
for f in files:
|
||||
if os.path.normcase(f) == normtail:
|
||||
tail = f
|
||||
break
|
||||
actpath = os.path.join(head, tail)
|
||||
_ACTUAL_PATH_CACHE[path] = actpath
|
||||
return actpath
|
||||
|
||||
else:
|
||||
def actual_path(path):
|
||||
"""The actual path for non-Windows platforms."""
|
||||
return path
|
||||
|
||||
|
||||
@contract(returns='unicode')
|
||||
def abs_file(path):
|
||||
"""Return the absolute normalized form of `path`."""
|
||||
return actual_path(os.path.abspath(os.path.realpath(path)))
|
||||
|
||||
|
||||
def python_reported_file(filename):
|
||||
"""Return the string as Python would describe this file name."""
|
||||
if env.PYBEHAVIOR.report_absolute_files:
|
||||
filename = os.path.abspath(filename)
|
||||
return filename
|
||||
|
||||
|
||||
RELATIVE_DIR = None
|
||||
CANONICAL_FILENAME_CACHE = None
|
||||
set_relative_directory()
|
||||
|
||||
|
||||
def isabs_anywhere(filename):
|
||||
"""Is `filename` an absolute path on any OS?"""
|
||||
return ntpath.isabs(filename) or posixpath.isabs(filename)
|
||||
|
||||
|
||||
def prep_patterns(patterns):
|
||||
"""Prepare the file patterns for use in a `FnmatchMatcher`.
|
||||
|
||||
If a pattern starts with a wildcard, it is used as a pattern
|
||||
as-is. If it does not start with a wildcard, then it is made
|
||||
absolute with the current directory.
|
||||
|
||||
If `patterns` is None, an empty list is returned.
|
||||
|
||||
"""
|
||||
prepped = []
|
||||
for p in patterns or []:
|
||||
if p.startswith(("*", "?")):
|
||||
prepped.append(p)
|
||||
else:
|
||||
prepped.append(abs_file(p))
|
||||
return prepped
|
||||
|
||||
|
||||
class TreeMatcher:
|
||||
"""A matcher for files in a tree.
|
||||
|
||||
Construct with a list of paths, either files or directories. Paths match
|
||||
with the `match` method if they are one of the files, or if they are
|
||||
somewhere in a subtree rooted at one of the directories.
|
||||
|
||||
"""
|
||||
def __init__(self, paths, name="unknown"):
|
||||
self.original_paths = human_sorted(paths)
|
||||
self.paths = list(map(os.path.normcase, paths))
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return f"<TreeMatcher {self.name} {self.original_paths!r}>"
|
||||
|
||||
def info(self):
|
||||
"""A list of strings for displaying when dumping state."""
|
||||
return self.original_paths
|
||||
|
||||
def match(self, fpath):
|
||||
"""Does `fpath` indicate a file in one of our trees?"""
|
||||
fpath = os.path.normcase(fpath)
|
||||
for p in self.paths:
|
||||
if fpath.startswith(p):
|
||||
if fpath == p:
|
||||
# This is the same file!
|
||||
return True
|
||||
if fpath[len(p)] == os.sep:
|
||||
# This is a file in the directory
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ModuleMatcher:
|
||||
"""A matcher for modules in a tree."""
|
||||
def __init__(self, module_names, name="unknown"):
|
||||
self.modules = list(module_names)
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ModuleMatcher {self.name} {self.modules!r}>"
|
||||
|
||||
def info(self):
|
||||
"""A list of strings for displaying when dumping state."""
|
||||
return self.modules
|
||||
|
||||
def match(self, module_name):
|
||||
"""Does `module_name` indicate a module in one of our packages?"""
|
||||
if not module_name:
|
||||
return False
|
||||
|
||||
for m in self.modules:
|
||||
if module_name.startswith(m):
|
||||
if module_name == m:
|
||||
return True
|
||||
if module_name[len(m)] == '.':
|
||||
# This is a module in the package
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class FnmatchMatcher:
|
||||
"""A matcher for files by file name pattern."""
|
||||
def __init__(self, pats, name="unknown"):
|
||||
self.pats = list(pats)
|
||||
self.re = fnmatches_to_regex(self.pats, case_insensitive=env.WINDOWS)
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return f"<FnmatchMatcher {self.name} {self.pats!r}>"
|
||||
|
||||
def info(self):
|
||||
"""A list of strings for displaying when dumping state."""
|
||||
return self.pats
|
||||
|
||||
def match(self, fpath):
|
||||
"""Does `fpath` match one of our file name patterns?"""
|
||||
return self.re.match(fpath) is not None
|
||||
|
||||
|
||||
def sep(s):
|
||||
"""Find the path separator used in this string, or os.sep if none."""
|
||||
sep_match = re.search(r"[\\/]", s)
|
||||
if sep_match:
|
||||
the_sep = sep_match[0]
|
||||
else:
|
||||
the_sep = os.sep
|
||||
return the_sep
|
||||
|
||||
|
||||
def fnmatches_to_regex(patterns, case_insensitive=False, partial=False):
|
||||
"""Convert fnmatch patterns to a compiled regex that matches any of them.
|
||||
|
||||
Slashes are always converted to match either slash or backslash, for
|
||||
Windows support, even when running elsewhere.
|
||||
|
||||
If `partial` is true, then the pattern will match if the target string
|
||||
starts with the pattern. Otherwise, it must match the entire string.
|
||||
|
||||
Returns: a compiled regex object. Use the .match method to compare target
|
||||
strings.
|
||||
|
||||
"""
|
||||
regexes = (fnmatch.translate(pattern) for pattern in patterns)
|
||||
# Python3.7 fnmatch translates "/" as "/". Before that, it translates as "\/",
|
||||
# so we have to deal with maybe a backslash.
|
||||
regexes = (re.sub(r"\\?/", r"[\\\\/]", regex) for regex in regexes)
|
||||
|
||||
if partial:
|
||||
# fnmatch always adds a \Z to match the whole string, which we don't
|
||||
# want, so we remove the \Z. While removing it, we only replace \Z if
|
||||
# followed by paren (introducing flags), or at end, to keep from
|
||||
# destroying a literal \Z in the pattern.
|
||||
regexes = (re.sub(r'\\Z(\(\?|$)', r'\1', regex) for regex in regexes)
|
||||
|
||||
flags = 0
|
||||
if case_insensitive:
|
||||
flags |= re.IGNORECASE
|
||||
compiled = re.compile(join_regex(regexes), flags=flags)
|
||||
|
||||
return compiled
|
||||
|
||||
|
||||
class PathAliases:
|
||||
"""A collection of aliases for paths.
|
||||
|
||||
When combining data files from remote machines, often the paths to source
|
||||
code are different, for example, due to OS differences, or because of
|
||||
serialized checkouts on continuous integration machines.
|
||||
|
||||
A `PathAliases` object tracks a list of pattern/result pairs, and can
|
||||
map a path through those aliases to produce a unified path.
|
||||
|
||||
"""
|
||||
def __init__(self, debugfn=None, relative=False):
|
||||
self.aliases = [] # A list of (original_pattern, regex, result)
|
||||
self.debugfn = debugfn or (lambda msg: 0)
|
||||
self.relative = relative
|
||||
self.pprinted = False
|
||||
|
||||
def pprint(self):
|
||||
"""Dump the important parts of the PathAliases, for debugging."""
|
||||
self.debugfn(f"Aliases (relative={self.relative}):")
|
||||
for original_pattern, regex, result in self.aliases:
|
||||
self.debugfn(f" Rule: {original_pattern!r} -> {result!r} using regex {regex.pattern!r}")
|
||||
|
||||
def add(self, pattern, result):
|
||||
"""Add the `pattern`/`result` pair to the list of aliases.
|
||||
|
||||
`pattern` is an `fnmatch`-style pattern. `result` is a simple
|
||||
string. When mapping paths, if a path starts with a match against
|
||||
`pattern`, then that match is replaced with `result`. This models
|
||||
isomorphic source trees being rooted at different places on two
|
||||
different machines.
|
||||
|
||||
`pattern` can't end with a wildcard component, since that would
|
||||
match an entire tree, and not just its root.
|
||||
|
||||
"""
|
||||
original_pattern = pattern
|
||||
pattern_sep = sep(pattern)
|
||||
|
||||
if len(pattern) > 1:
|
||||
pattern = pattern.rstrip(r"\/")
|
||||
|
||||
# The pattern can't end with a wildcard component.
|
||||
if pattern.endswith("*"):
|
||||
raise ConfigError("Pattern must not end with wildcards.")
|
||||
|
||||
# The pattern is meant to match a filepath. Let's make it absolute
|
||||
# unless it already is, or is meant to match any prefix.
|
||||
if not pattern.startswith('*') and not isabs_anywhere(pattern + pattern_sep):
|
||||
pattern = abs_file(pattern)
|
||||
if not pattern.endswith(pattern_sep):
|
||||
pattern += pattern_sep
|
||||
|
||||
# Make a regex from the pattern.
|
||||
regex = fnmatches_to_regex([pattern], case_insensitive=True, partial=True)
|
||||
|
||||
# Normalize the result: it must end with a path separator.
|
||||
result_sep = sep(result)
|
||||
result = result.rstrip(r"\/") + result_sep
|
||||
self.aliases.append((original_pattern, regex, result))
|
||||
|
||||
def map(self, path):
|
||||
"""Map `path` through the aliases.
|
||||
|
||||
`path` is checked against all of the patterns. The first pattern to
|
||||
match is used to replace the root of the path with the result root.
|
||||
Only one pattern is ever used. If no patterns match, `path` is
|
||||
returned unchanged.
|
||||
|
||||
The separator style in the result is made to match that of the result
|
||||
in the alias.
|
||||
|
||||
Returns the mapped path. If a mapping has happened, this is a
|
||||
canonical path. If no mapping has happened, it is the original value
|
||||
of `path` unchanged.
|
||||
|
||||
"""
|
||||
if not self.pprinted:
|
||||
self.pprint()
|
||||
self.pprinted = True
|
||||
|
||||
for original_pattern, regex, result in self.aliases:
|
||||
m = regex.match(path)
|
||||
if m:
|
||||
new = path.replace(m[0], result)
|
||||
new = new.replace(sep(path), sep(result))
|
||||
if not self.relative:
|
||||
new = canonical_filename(new)
|
||||
self.debugfn(
|
||||
f"Matched path {path!r} to rule {original_pattern!r} -> {result!r}, " +
|
||||
f"producing {new!r}"
|
||||
)
|
||||
return new
|
||||
self.debugfn(f"No rules match, path {path!r} is unchanged")
|
||||
return path
|
||||
|
||||
|
||||
def find_python_files(dirname):
|
||||
"""Yield all of the importable Python files in `dirname`, recursively.
|
||||
|
||||
To be importable, the files have to be in a directory with a __init__.py,
|
||||
except for `dirname` itself, which isn't required to have one. The
|
||||
assumption is that `dirname` was specified directly, so the user knows
|
||||
best, but sub-directories are checked for a __init__.py to be sure we only
|
||||
find the importable files.
|
||||
|
||||
"""
|
||||
for i, (dirpath, dirnames, filenames) in enumerate(os.walk(dirname)):
|
||||
if i > 0 and '__init__.py' not in filenames:
|
||||
# If a directory doesn't have __init__.py, then it isn't
|
||||
# importable and neither are its files
|
||||
del dirnames[:]
|
||||
continue
|
||||
for filename in filenames:
|
||||
# We're only interested in files that look like reasonable Python
|
||||
# files: Must end with .py or .pyw, and must not have certain funny
|
||||
# characters that probably mean they are editor junk.
|
||||
if re.match(r"^[^.#~!$@%^&*()+=,]+\.pyw?$", filename):
|
||||
yield os.path.join(dirpath, filename)
|
@ -0,0 +1,54 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Imposter encodings module that installs a coverage-style tracer.
|
||||
|
||||
This is NOT the encodings module; it is an imposter that sets up tracing
|
||||
instrumentation and then replaces itself with the real encodings module.
|
||||
|
||||
If the directory that holds this file is placed first in the PYTHONPATH when
|
||||
using "coverage" to run Python's tests, then this file will become the very
|
||||
first module imported by the internals of Python 3. It installs a
|
||||
coverage.py-compatible trace function that can watch Standard Library modules
|
||||
execute from the very earliest stages of Python's own boot process. This fixes
|
||||
a problem with coverage.py - that it starts too late to trace the coverage of
|
||||
many of the most fundamental modules in the Standard Library.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
class FullCoverageTracer:
|
||||
def __init__(self):
|
||||
# `traces` is a list of trace events. Frames are tricky: the same
|
||||
# frame object is used for a whole scope, with new line numbers
|
||||
# written into it. So in one scope, all the frame objects are the
|
||||
# same object, and will eventually all will point to the last line
|
||||
# executed. So we keep the line numbers alongside the frames.
|
||||
# The list looks like:
|
||||
#
|
||||
# traces = [
|
||||
# ((frame, event, arg), lineno), ...
|
||||
# ]
|
||||
#
|
||||
self.traces = []
|
||||
|
||||
def fullcoverage_trace(self, *args):
|
||||
frame, event, arg = args
|
||||
if frame.f_lineno is not None:
|
||||
# https://bugs.python.org/issue46911
|
||||
self.traces.append((args, frame.f_lineno))
|
||||
return self.fullcoverage_trace
|
||||
|
||||
sys.settrace(FullCoverageTracer().fullcoverage_trace)
|
||||
|
||||
# Remove our own directory from sys.path; remove ourselves from
|
||||
# sys.modules; and re-import "encodings", which will be the real package
|
||||
# this time. Note that the delete from sys.modules dictionary has to
|
||||
# happen last, since all of the symbols in this module will become None
|
||||
# at that exact moment, including "sys".
|
||||
|
||||
parentdir = max(filter(__file__.startswith, sys.path), key=len)
|
||||
sys.path.remove(parentdir)
|
||||
del sys.modules['encodings']
|
||||
import encodings
|
550
utils/python-venv/Lib/site-packages/coverage/html.py
Normal file
550
utils/python-venv/Lib/site-packages/coverage/html.py
Normal file
@ -0,0 +1,550 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""HTML reporting for coverage.py."""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import types
|
||||
|
||||
import coverage
|
||||
from coverage.data import add_data_to_hash
|
||||
from coverage.exceptions import NoDataError
|
||||
from coverage.files import flat_rootname
|
||||
from coverage.misc import ensure_dir, file_be_gone, Hasher, isolate_module, format_local_datetime
|
||||
from coverage.misc import human_sorted, plural
|
||||
from coverage.report import get_analysis_to_report
|
||||
from coverage.results import Numbers
|
||||
from coverage.templite import Templite
|
||||
|
||||
os = isolate_module(os)
|
||||
|
||||
|
||||
def data_filename(fname):
|
||||
"""Return the path to an "htmlfiles" data file of ours.
|
||||
"""
|
||||
static_dir = os.path.join(os.path.dirname(__file__), "htmlfiles")
|
||||
static_filename = os.path.join(static_dir, fname)
|
||||
return static_filename
|
||||
|
||||
|
||||
def read_data(fname):
|
||||
"""Return the contents of a data file of ours."""
|
||||
with open(data_filename(fname)) as data_file:
|
||||
return data_file.read()
|
||||
|
||||
|
||||
def write_html(fname, html):
|
||||
"""Write `html` to `fname`, properly encoded."""
|
||||
html = re.sub(r"(\A\s+)|(\s+$)", "", html, flags=re.MULTILINE) + "\n"
|
||||
with open(fname, "wb") as fout:
|
||||
fout.write(html.encode('ascii', 'xmlcharrefreplace'))
|
||||
|
||||
|
||||
class HtmlDataGeneration:
|
||||
"""Generate structured data to be turned into HTML reports."""
|
||||
|
||||
EMPTY = "(empty)"
|
||||
|
||||
def __init__(self, cov):
|
||||
self.coverage = cov
|
||||
self.config = self.coverage.config
|
||||
data = self.coverage.get_data()
|
||||
self.has_arcs = data.has_arcs()
|
||||
if self.config.show_contexts:
|
||||
if data.measured_contexts() == {""}:
|
||||
self.coverage._warn("No contexts were measured")
|
||||
data.set_query_contexts(self.config.report_contexts)
|
||||
|
||||
def data_for_file(self, fr, analysis):
|
||||
"""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()
|
||||
|
||||
if self.config.show_contexts:
|
||||
contexts_by_lineno = analysis.data.contexts_by_lineno(analysis.filename)
|
||||
|
||||
lines = []
|
||||
|
||||
for lineno, tokens in enumerate(fr.source_token_lines(), start=1):
|
||||
# Figure out how to mark this line.
|
||||
category = None
|
||||
short_annotations = []
|
||||
long_annotations = []
|
||||
|
||||
if lineno in analysis.excluded:
|
||||
category = 'exc'
|
||||
elif lineno in analysis.missing:
|
||||
category = 'mis'
|
||||
elif self.has_arcs and lineno in missing_branch_arcs:
|
||||
category = 'par'
|
||||
for b in missing_branch_arcs[lineno]:
|
||||
if b < 0:
|
||||
short_annotations.append("exit")
|
||||
else:
|
||||
short_annotations.append(b)
|
||||
long_annotations.append(fr.missing_arc_description(lineno, b, arcs_executed))
|
||||
elif lineno in analysis.statements:
|
||||
category = 'run'
|
||||
|
||||
contexts = contexts_label = None
|
||||
context_list = None
|
||||
if category and self.config.show_contexts:
|
||||
contexts = human_sorted(c or self.EMPTY for c in contexts_by_lineno.get(lineno, ()))
|
||||
if contexts == [self.EMPTY]:
|
||||
contexts_label = self.EMPTY
|
||||
else:
|
||||
contexts_label = f"{len(contexts)} ctx"
|
||||
context_list = contexts
|
||||
|
||||
lines.append(types.SimpleNamespace(
|
||||
tokens=tokens,
|
||||
number=lineno,
|
||||
category=category,
|
||||
statement=(lineno in analysis.statements),
|
||||
contexts=contexts,
|
||||
contexts_label=contexts_label,
|
||||
context_list=context_list,
|
||||
short_annotations=short_annotations,
|
||||
long_annotations=long_annotations,
|
||||
))
|
||||
|
||||
file_data = types.SimpleNamespace(
|
||||
relative_filename=fr.relative_filename(),
|
||||
nums=analysis.numbers,
|
||||
lines=lines,
|
||||
)
|
||||
|
||||
return file_data
|
||||
|
||||
|
||||
class FileToReport:
|
||||
"""A file we're considering reporting."""
|
||||
def __init__(self, fr, analysis):
|
||||
self.fr = fr
|
||||
self.analysis = analysis
|
||||
self.rootname = flat_rootname(fr.relative_filename())
|
||||
self.html_filename = self.rootname + ".html"
|
||||
|
||||
|
||||
class HtmlReporter:
|
||||
"""HTML reporting."""
|
||||
|
||||
# These files will be copied from the htmlfiles directory to the output
|
||||
# directory.
|
||||
STATIC_FILES = [
|
||||
"style.css",
|
||||
"coverage_html.js",
|
||||
"keybd_closed.png",
|
||||
"keybd_open.png",
|
||||
"favicon_32.png",
|
||||
]
|
||||
|
||||
def __init__(self, cov):
|
||||
self.coverage = cov
|
||||
self.config = self.coverage.config
|
||||
self.directory = self.config.html_dir
|
||||
|
||||
self.skip_covered = self.config.html_skip_covered
|
||||
if self.skip_covered is None:
|
||||
self.skip_covered = self.config.skip_covered
|
||||
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
|
||||
|
||||
if self.config.extra_css:
|
||||
self.extra_css = os.path.basename(self.config.extra_css)
|
||||
else:
|
||||
self.extra_css = None
|
||||
|
||||
self.data = self.coverage.get_data()
|
||||
self.has_arcs = self.data.has_arcs()
|
||||
|
||||
self.file_summaries = []
|
||||
self.all_files_nums = []
|
||||
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
|
||||
|
||||
self.template_globals = {
|
||||
# Functions available in the templates.
|
||||
'escape': escape,
|
||||
'pair': pair,
|
||||
'len': len,
|
||||
|
||||
# Constants for this report.
|
||||
'__url__': coverage.__url__,
|
||||
'__version__': coverage.__version__,
|
||||
'title': title,
|
||||
'time_stamp': format_local_datetime(datetime.datetime.now()),
|
||||
'extra_css': self.extra_css,
|
||||
'has_arcs': self.has_arcs,
|
||||
'show_contexts': self.config.show_contexts,
|
||||
|
||||
# Constants for all reports.
|
||||
# These css classes determine which lines are highlighted by default.
|
||||
'category': {
|
||||
'exc': 'exc show_exc',
|
||||
'mis': 'mis show_mis',
|
||||
'par': 'par run show_par',
|
||||
'run': 'run',
|
||||
},
|
||||
}
|
||||
self.pyfile_html_source = read_data("pyfile.html")
|
||||
self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals)
|
||||
|
||||
def report(self, morfs):
|
||||
"""Generate an HTML report for `morfs`.
|
||||
|
||||
`morfs` is a list of modules or file names.
|
||||
|
||||
"""
|
||||
# Read the status data and check that this run used the same
|
||||
# global data as the last run.
|
||||
self.incr.read()
|
||||
self.incr.check_global_data(self.config, self.pyfile_html_source)
|
||||
|
||||
# Process all the files. For each page we need to supply a link
|
||||
# to the next and previous page.
|
||||
files_to_report = []
|
||||
|
||||
for fr, analysis in get_analysis_to_report(self.coverage, morfs):
|
||||
ftr = FileToReport(fr, analysis)
|
||||
should = self.should_report_file(ftr)
|
||||
if should:
|
||||
files_to_report.append(ftr)
|
||||
else:
|
||||
file_be_gone(os.path.join(self.directory, ftr.html_filename))
|
||||
|
||||
for i, ftr in enumerate(files_to_report):
|
||||
if i == 0:
|
||||
prev_html = "index.html"
|
||||
else:
|
||||
prev_html = files_to_report[i - 1].html_filename
|
||||
if i == len(files_to_report) - 1:
|
||||
next_html = "index.html"
|
||||
else:
|
||||
next_html = files_to_report[i + 1].html_filename
|
||||
self.write_html_file(ftr, prev_html, next_html)
|
||||
|
||||
if not self.all_files_nums:
|
||||
raise NoDataError("No data to report.")
|
||||
|
||||
self.totals = sum(self.all_files_nums)
|
||||
|
||||
# Write the index file.
|
||||
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.make_local_static_report_files()
|
||||
return self.totals.n_statements and self.totals.pc_covered
|
||||
|
||||
def make_directory(self):
|
||||
"""Make sure our htmlcov directory exists."""
|
||||
ensure_dir(self.directory)
|
||||
if not os.listdir(self.directory):
|
||||
self.directory_was_empty = True
|
||||
|
||||
def make_local_static_report_files(self):
|
||||
"""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))
|
||||
|
||||
# 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.
|
||||
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:
|
||||
shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css))
|
||||
|
||||
def should_report_file(self, ftr):
|
||||
"""Determine if we'll report this file."""
|
||||
# Get the numbers for this file.
|
||||
nums = ftr.analysis.numbers
|
||||
self.all_files_nums.append(nums)
|
||||
|
||||
if self.skip_covered:
|
||||
# Don't report on 100% files.
|
||||
no_missing_lines = (nums.n_missing == 0)
|
||||
no_missing_branches = (nums.n_partial_branches == 0)
|
||||
if no_missing_lines and no_missing_branches:
|
||||
# If there's an existing file, remove it.
|
||||
self.skipped_covered_count += 1
|
||||
return False
|
||||
|
||||
if self.skip_empty:
|
||||
# Don't report on empty files.
|
||||
if nums.n_statements == 0:
|
||||
self.skipped_empty_count += 1
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def write_html_file(self, ftr, prev_html, next_html):
|
||||
"""Generate an HTML file for one source file."""
|
||||
self.make_directory()
|
||||
|
||||
# Find out if the file 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))
|
||||
return
|
||||
|
||||
# Write the HTML page for this file.
|
||||
file_data = self.datagen.data_for_file(ftr.fr, ftr.analysis)
|
||||
for ldata in file_data.lines:
|
||||
# Build the HTML for the line.
|
||||
html = []
|
||||
for tok_type, tok_text in ldata.tokens:
|
||||
if tok_type == "ws":
|
||||
html.append(escape(tok_text))
|
||||
else:
|
||||
tok_html = escape(tok_text) or ' '
|
||||
html.append(
|
||||
f'<span class="{tok_type}">{tok_html}</span>'
|
||||
)
|
||||
ldata.html = ''.join(html)
|
||||
|
||||
if ldata.short_annotations:
|
||||
# 202F is NARROW NO-BREAK SPACE.
|
||||
# 219B is RIGHTWARDS ARROW WITH STROKE.
|
||||
ldata.annotate = ", ".join(
|
||||
f"{ldata.number} ↛ {d}"
|
||||
for d in ldata.short_annotations
|
||||
)
|
||||
else:
|
||||
ldata.annotate = None
|
||||
|
||||
if ldata.long_annotations:
|
||||
longs = ldata.long_annotations
|
||||
if len(longs) == 1:
|
||||
ldata.annotate_long = longs[0]
|
||||
else:
|
||||
ldata.annotate_long = "{:d} missed branches: {}".format(
|
||||
len(longs),
|
||||
", ".join(
|
||||
f"{num:d}) {ann_long}"
|
||||
for num, ann_long in enumerate(longs, start=1)
|
||||
),
|
||||
)
|
||||
else:
|
||||
ldata.annotate_long = None
|
||||
|
||||
css_classes = []
|
||||
if ldata.category:
|
||||
css_classes.append(self.template_globals['category'][ldata.category])
|
||||
ldata.css_class = ' '.join(css_classes) or "pln"
|
||||
|
||||
html_path = os.path.join(self.directory, ftr.html_filename)
|
||||
html = self.source_tmpl.render({
|
||||
**file_data.__dict__,
|
||||
'prev_html': prev_html,
|
||||
'next_html': next_html,
|
||||
})
|
||||
write_html(html_path, html)
|
||||
|
||||
# Save this file's information for the index file.
|
||||
index_info = {
|
||||
'nums': ftr.analysis.numbers,
|
||||
'html_filename': ftr.html_filename,
|
||||
'relative_filename': ftr.fr.relative_filename(),
|
||||
}
|
||||
self.file_summaries.append(index_info)
|
||||
self.incr.set_index_info(ftr.rootname, index_info)
|
||||
|
||||
def index_file(self, first_html, final_html):
|
||||
"""Write the index.html file for this report."""
|
||||
self.make_directory()
|
||||
index_tmpl = Templite(read_data("index.html"), self.template_globals)
|
||||
|
||||
skipped_covered_msg = skipped_empty_msg = ""
|
||||
if self.skipped_covered_count:
|
||||
n = self.skipped_covered_count
|
||||
skipped_covered_msg = f"{n} file{plural(n)} skipped due to complete coverage."
|
||||
if self.skipped_empty_count:
|
||||
n = self.skipped_empty_count
|
||||
skipped_empty_msg = f"{n} empty file{plural(n)} skipped."
|
||||
|
||||
html = index_tmpl.render({
|
||||
'files': self.file_summaries,
|
||||
'totals': self.totals,
|
||||
'skipped_covered_msg': skipped_covered_msg,
|
||||
'skipped_empty_msg': skipped_empty_msg,
|
||||
'first_html': first_html,
|
||||
'final_html': final_html,
|
||||
})
|
||||
|
||||
index_file = os.path.join(self.directory, "index.html")
|
||||
write_html(index_file, html)
|
||||
self.coverage._message(f"Wrote HTML report to {index_file}")
|
||||
|
||||
# Write the latest hashes for next time.
|
||||
self.incr.write()
|
||||
|
||||
|
||||
class IncrementalChecker:
|
||||
"""Logic and data to support incremental reporting."""
|
||||
|
||||
STATUS_FILE = "status.json"
|
||||
STATUS_FORMAT = 2
|
||||
|
||||
# pylint: disable=wrong-spelling-in-comment,useless-suppression
|
||||
# The data looks like:
|
||||
#
|
||||
# {
|
||||
# "format": 2,
|
||||
# "globals": "540ee119c15d52a68a53fe6f0897346d",
|
||||
# "version": "4.0a1",
|
||||
# "files": {
|
||||
# "cogapp___init__": {
|
||||
# "hash": "e45581a5b48f879f301c0f30bf77a50c",
|
||||
# "index": {
|
||||
# "html_filename": "cogapp___init__.html",
|
||||
# "relative_filename": "cogapp/__init__",
|
||||
# "nums": [ 1, 14, 0, 0, 0, 0, 0 ]
|
||||
# }
|
||||
# },
|
||||
# ...
|
||||
# "cogapp_whiteutils": {
|
||||
# "hash": "8504bb427fc488c4176809ded0277d51",
|
||||
# "index": {
|
||||
# "html_filename": "cogapp_whiteutils.html",
|
||||
# "relative_filename": "cogapp/whiteutils",
|
||||
# "nums": [ 1, 59, 0, 1, 28, 2, 2 ]
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
def __init__(self, directory):
|
||||
self.directory = directory
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""Initialize to empty. Causes all files to be reported."""
|
||||
self.globals = ''
|
||||
self.files = {}
|
||||
|
||||
def read(self):
|
||||
"""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):
|
||||
usable = False
|
||||
else:
|
||||
usable = True
|
||||
if status['format'] != self.STATUS_FORMAT:
|
||||
usable = False
|
||||
elif status['version'] != coverage.__version__:
|
||||
usable = False
|
||||
|
||||
if usable:
|
||||
self.files = {}
|
||||
for filename, fileinfo in status['files'].items():
|
||||
fileinfo['index']['nums'] = Numbers(*fileinfo['index']['nums'])
|
||||
self.files[filename] = fileinfo
|
||||
self.globals = status['globals']
|
||||
else:
|
||||
self.reset()
|
||||
|
||||
def write(self):
|
||||
"""Write the current status."""
|
||||
status_file = os.path.join(self.directory, self.STATUS_FILE)
|
||||
files = {}
|
||||
for filename, fileinfo in self.files.items():
|
||||
fileinfo['index']['nums'] = fileinfo['index']['nums'].init_args()
|
||||
files[filename] = fileinfo
|
||||
|
||||
status = {
|
||||
'format': self.STATUS_FORMAT,
|
||||
'version': coverage.__version__,
|
||||
'globals': self.globals,
|
||||
'files': files,
|
||||
}
|
||||
with open(status_file, "w") as fout:
|
||||
json.dump(status, fout, separators=(',', ':'))
|
||||
|
||||
def check_global_data(self, *data):
|
||||
"""Check the global data that can affect incremental reporting."""
|
||||
m = Hasher()
|
||||
for d in data:
|
||||
m.update(d)
|
||||
these_globals = m.hexdigest()
|
||||
if self.globals != these_globals:
|
||||
self.reset()
|
||||
self.globals = these_globals
|
||||
|
||||
def can_skip_file(self, data, fr, rootname):
|
||||
"""Can we skip reporting this file?
|
||||
|
||||
`data` is a CoverageData object, `fr` is a `FileReporter`, and
|
||||
`rootname` is the name being used for the file.
|
||||
"""
|
||||
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)
|
||||
|
||||
if this_hash == that_hash:
|
||||
# Nothing has changed to require the file to be reported again.
|
||||
return True
|
||||
else:
|
||||
self.set_file_hash(rootname, this_hash)
|
||||
return False
|
||||
|
||||
def file_hash(self, fname):
|
||||
"""Get the hash of `fname`'s contents."""
|
||||
return self.files.get(fname, {}).get('hash', '')
|
||||
|
||||
def set_file_hash(self, fname, val):
|
||||
"""Set the hash of `fname`'s contents."""
|
||||
self.files.setdefault(fname, {})['hash'] = val
|
||||
|
||||
def index_info(self, fname):
|
||||
"""Get the information for index.html for `fname`."""
|
||||
return self.files.get(fname, {}).get('index', {})
|
||||
|
||||
def set_index_info(self, fname, info):
|
||||
"""Set the information for index.html for `fname`."""
|
||||
self.files.setdefault(fname, {})['index'] = info
|
||||
|
||||
|
||||
# Helpers for templates and generating HTML
|
||||
|
||||
def escape(t):
|
||||
"""HTML-escape the text in `t`.
|
||||
|
||||
This is only suitable for HTML text, not attributes.
|
||||
|
||||
"""
|
||||
# Convert HTML special chars into HTML entities.
|
||||
return t.replace("&", "&").replace("<", "<")
|
||||
|
||||
|
||||
def pair(ratio):
|
||||
"""Format a pair of numbers so JavaScript can read them in an attribute."""
|
||||
return "%s %s" % ratio
|
@ -0,0 +1,604 @@
|
||||
// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
// For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
// Coverage.py HTML report browser code.
|
||||
/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */
|
||||
/*global coverage: true, document, window, $ */
|
||||
|
||||
coverage = {};
|
||||
|
||||
// General helpers
|
||||
function debounce(callback, wait) {
|
||||
let timeoutId = null;
|
||||
return function(...args) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
callback.apply(this, args);
|
||||
}, wait);
|
||||
};
|
||||
};
|
||||
|
||||
function checkVisible(element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const viewBottom = Math.max(document.documentElement.clientHeight, window.innerHeight);
|
||||
const viewTop = 30;
|
||||
return !(rect.bottom < viewTop || rect.top >= viewBottom);
|
||||
}
|
||||
|
||||
function on_click(sel, fn) {
|
||||
const elt = document.querySelector(sel);
|
||||
if (elt) {
|
||||
elt.addEventListener("click", fn);
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers for table sorting
|
||||
function getCellValue(row, column = 0) {
|
||||
const cell = row.cells[column]
|
||||
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
|
||||
}
|
||||
}
|
||||
return cell.innerText || cell.textContent;
|
||||
}
|
||||
|
||||
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.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
|
||||
const currentSortOrder = th.getAttribute("aria-sort");
|
||||
[...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none"));
|
||||
if (currentSortOrder === "none") {
|
||||
th.setAttribute("aria-sort", th.dataset.defaultSortOrder || "ascending");
|
||||
} else {
|
||||
th.setAttribute("aria-sort", currentSortOrder === "ascending" ? "descending" : "ascending");
|
||||
}
|
||||
|
||||
const column = [...th.parentElement.cells].indexOf(th)
|
||||
|
||||
// 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) );
|
||||
}
|
||||
|
||||
// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key.
|
||||
coverage.assign_shortkeys = function () {
|
||||
document.querySelectorAll("[data-shortcut]").forEach(element => {
|
||||
document.addEventListener("keypress", event => {
|
||||
if (event.target.tagName.toLowerCase() === "input") {
|
||||
return; // ignore keypress from search filter
|
||||
}
|
||||
if (event.key === element.dataset.shortcut) {
|
||||
element.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Create the events for the filter box.
|
||||
coverage.wire_up_filter = function () {
|
||||
// Cache elements.
|
||||
const table = document.querySelector("table.index");
|
||||
const table_body_rows = table.querySelectorAll("tbody tr");
|
||||
const no_rows = document.getElementById("no_rows");
|
||||
|
||||
// Observe filter keyevents.
|
||||
document.getElementById("filter").addEventListener("input", debounce(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 };
|
||||
|
||||
// Hide / show elements.
|
||||
table_body_rows.forEach(row => {
|
||||
if (!row.cells[0].textContent.includes(event.target.value)) {
|
||||
// hide
|
||||
row.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
// show
|
||||
row.classList.remove("hidden");
|
||||
totals[0]++;
|
||||
|
||||
for (let column = 1; column < totals.length; column++) {
|
||||
// Accumulate dynamic totals
|
||||
cell = row.cells[column]
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Show placeholder if no rows will be displayed.
|
||||
if (!totals[0]) {
|
||||
// Show placeholder, hide table.
|
||||
no_rows.style.display = "block";
|
||||
table.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide placeholder, show table.
|
||||
no_rows.style.display = null;
|
||||
table.style.display = null;
|
||||
|
||||
const footer = table.tFoot.rows[0];
|
||||
// Calculate new dynamic sum values based on visible rows.
|
||||
for (let column = 1; column < totals.length; column++) {
|
||||
// Get footer cell element.
|
||||
const cell = footer.cells[column];
|
||||
|
||||
// Set value into dynamic footer cell element.
|
||||
if (column === totals.length - 1) {
|
||||
// Percentage column uses the numerator and denominator,
|
||||
// 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];
|
||||
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];
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Trigger change event on setup, to force filter on page refresh
|
||||
// (filter value may still be present).
|
||||
document.getElementById("filter").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();
|
||||
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:
|
||||
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()
|
||||
}
|
||||
|
||||
// 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"),
|
||||
}));
|
||||
});
|
||||
|
||||
on_click(".button_prev_file", coverage.to_prev_file);
|
||||
on_click(".button_next_file", coverage.to_next_file);
|
||||
|
||||
on_click(".button_show_hide_help", coverage.show_hide_help);
|
||||
};
|
||||
|
||||
// -- pyfile stuff --
|
||||
|
||||
coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS";
|
||||
|
||||
coverage.pyfile_ready = function () {
|
||||
// If we're directed to a particular line number, highlight the line.
|
||||
var frag = location.hash;
|
||||
if (frag.length > 2 && frag[1] === 't') {
|
||||
document.querySelector(frag).closest(".n").classList.add("highlight");
|
||||
coverage.set_sel(parseInt(frag.substr(2), 10));
|
||||
} else {
|
||||
coverage.set_sel(0);
|
||||
}
|
||||
|
||||
on_click(".button_toggle_run", coverage.toggle_lines);
|
||||
on_click(".button_toggle_mis", coverage.toggle_lines);
|
||||
on_click(".button_toggle_exc", coverage.toggle_lines);
|
||||
on_click(".button_toggle_par", coverage.toggle_lines);
|
||||
|
||||
on_click(".button_next_chunk", coverage.to_next_chunk_nicely);
|
||||
on_click(".button_prev_chunk", coverage.to_prev_chunk_nicely);
|
||||
on_click(".button_top_of_page", coverage.to_top);
|
||||
on_click(".button_first_chunk", coverage.to_first_chunk);
|
||||
|
||||
on_click(".button_prev_file", coverage.to_prev_file);
|
||||
on_click(".button_next_file", coverage.to_next_file);
|
||||
on_click(".button_to_index", coverage.to_index);
|
||||
|
||||
on_click(".button_show_hide_help", coverage.show_hide_help);
|
||||
|
||||
coverage.filters = undefined;
|
||||
try {
|
||||
coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE);
|
||||
} catch(err) {}
|
||||
|
||||
if (coverage.filters) {
|
||||
coverage.filters = JSON.parse(coverage.filters);
|
||||
}
|
||||
else {
|
||||
coverage.filters = {run: false, exc: true, mis: true, par: true};
|
||||
}
|
||||
|
||||
for (cls in coverage.filters) {
|
||||
coverage.set_line_visibilty(cls, coverage.filters[cls]);
|
||||
}
|
||||
|
||||
coverage.assign_shortkeys();
|
||||
coverage.init_scroll_markers();
|
||||
coverage.wire_up_sticky_header();
|
||||
|
||||
// Rebuild scroll markers when the window height changes.
|
||||
window.addEventListener("resize", coverage.build_scroll_markers);
|
||||
};
|
||||
|
||||
coverage.toggle_lines = function (event) {
|
||||
const btn = event.target.closest("button");
|
||||
const category = btn.value
|
||||
const show = !btn.classList.contains("show_" + category);
|
||||
coverage.set_line_visibilty(category, show);
|
||||
coverage.build_scroll_markers();
|
||||
coverage.filters[category] = show;
|
||||
try {
|
||||
localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters));
|
||||
} catch(err) {}
|
||||
};
|
||||
|
||||
coverage.set_line_visibilty = function (category, should_show) {
|
||||
const cls = "show_" + category;
|
||||
const btn = document.querySelector(".button_toggle_" + category);
|
||||
if (btn) {
|
||||
if (should_show) {
|
||||
document.querySelectorAll("#source ." + category).forEach(e => e.classList.add(cls));
|
||||
btn.classList.add(cls);
|
||||
}
|
||||
else {
|
||||
document.querySelectorAll("#source ." + category).forEach(e => e.classList.remove(cls));
|
||||
btn.classList.remove(cls);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Return the nth line div.
|
||||
coverage.line_elt = function (n) {
|
||||
return document.getElementById("t" + n)?.closest("p");
|
||||
};
|
||||
|
||||
// Set the selection. b and e are line numbers.
|
||||
coverage.set_sel = function (b, e) {
|
||||
// The first line selected.
|
||||
coverage.sel_begin = b;
|
||||
// The next line not selected.
|
||||
coverage.sel_end = (e === undefined) ? b+1 : e;
|
||||
};
|
||||
|
||||
coverage.to_top = function () {
|
||||
coverage.set_sel(0, 1);
|
||||
coverage.scroll_window(0);
|
||||
};
|
||||
|
||||
coverage.to_first_chunk = function () {
|
||||
coverage.set_sel(0, 1);
|
||||
coverage.to_next_chunk();
|
||||
};
|
||||
|
||||
coverage.to_prev_file = function () {
|
||||
window.location = document.getElementById("prevFileLink").href;
|
||||
}
|
||||
|
||||
coverage.to_next_file = function () {
|
||||
window.location = document.getElementById("nextFileLink").href;
|
||||
}
|
||||
|
||||
coverage.to_index = function () {
|
||||
location.href = document.getElementById("indexLink").href;
|
||||
}
|
||||
|
||||
coverage.show_hide_help = function () {
|
||||
const helpCheck = document.getElementById("help_panel_state")
|
||||
helpCheck.checked = !helpCheck.checked;
|
||||
}
|
||||
|
||||
// Return a string indicating what kind of chunk this line belongs to,
|
||||
// or null if not a chunk.
|
||||
coverage.chunk_indicator = function (line_elt) {
|
||||
const classes = line_elt?.className;
|
||||
if (!classes) {
|
||||
return null;
|
||||
}
|
||||
const match = classes.match(/\bshow_\w+\b/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return match[0];
|
||||
};
|
||||
|
||||
coverage.to_next_chunk = function () {
|
||||
const c = coverage;
|
||||
|
||||
// Find the start of the next colored chunk.
|
||||
var probe = c.sel_end;
|
||||
var chunk_indicator, probe_line;
|
||||
while (true) {
|
||||
probe_line = c.line_elt(probe);
|
||||
if (!probe_line) {
|
||||
return;
|
||||
}
|
||||
chunk_indicator = c.chunk_indicator(probe_line);
|
||||
if (chunk_indicator) {
|
||||
break;
|
||||
}
|
||||
probe++;
|
||||
}
|
||||
|
||||
// There's a next chunk, `probe` points to it.
|
||||
var begin = probe;
|
||||
|
||||
// Find the end of this chunk.
|
||||
var next_indicator = chunk_indicator;
|
||||
while (next_indicator === chunk_indicator) {
|
||||
probe++;
|
||||
probe_line = c.line_elt(probe);
|
||||
next_indicator = c.chunk_indicator(probe_line);
|
||||
}
|
||||
c.set_sel(begin, probe);
|
||||
c.show_selection();
|
||||
};
|
||||
|
||||
coverage.to_prev_chunk = function () {
|
||||
const c = coverage;
|
||||
|
||||
// Find the end of the prev colored chunk.
|
||||
var probe = c.sel_begin-1;
|
||||
var probe_line = c.line_elt(probe);
|
||||
if (!probe_line) {
|
||||
return;
|
||||
}
|
||||
var chunk_indicator = c.chunk_indicator(probe_line);
|
||||
while (probe > 1 && !chunk_indicator) {
|
||||
probe--;
|
||||
probe_line = c.line_elt(probe);
|
||||
if (!probe_line) {
|
||||
return;
|
||||
}
|
||||
chunk_indicator = c.chunk_indicator(probe_line);
|
||||
}
|
||||
|
||||
// There's a prev chunk, `probe` points to its last line.
|
||||
var end = probe+1;
|
||||
|
||||
// Find the beginning of this chunk.
|
||||
var prev_indicator = chunk_indicator;
|
||||
while (prev_indicator === chunk_indicator) {
|
||||
probe--;
|
||||
if (probe <= 0) {
|
||||
return;
|
||||
}
|
||||
probe_line = c.line_elt(probe);
|
||||
prev_indicator = c.chunk_indicator(probe_line);
|
||||
}
|
||||
c.set_sel(probe+1, end);
|
||||
c.show_selection();
|
||||
};
|
||||
|
||||
// Returns 0, 1, or 2: how many of the two ends of the selection are on
|
||||
// the screen right now?
|
||||
coverage.selection_ends_on_screen = function () {
|
||||
if (coverage.sel_begin === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const begin = coverage.line_elt(coverage.sel_begin);
|
||||
const end = coverage.line_elt(coverage.sel_end-1);
|
||||
|
||||
return (
|
||||
(checkVisible(begin) ? 1 : 0)
|
||||
+ (checkVisible(end) ? 1 : 0)
|
||||
);
|
||||
};
|
||||
|
||||
coverage.to_next_chunk_nicely = function () {
|
||||
if (coverage.selection_ends_on_screen() === 0) {
|
||||
// The selection is entirely off the screen:
|
||||
// Set the top line on the screen as selection.
|
||||
|
||||
// This will select the top-left of the viewport
|
||||
// As this is most likely the span with the line number we take the parent
|
||||
const line = document.elementFromPoint(0, 0).parentElement;
|
||||
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 {
|
||||
// We extract the line number from the id
|
||||
coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10));
|
||||
}
|
||||
}
|
||||
coverage.to_next_chunk();
|
||||
};
|
||||
|
||||
coverage.to_prev_chunk_nicely = function () {
|
||||
if (coverage.selection_ends_on_screen() === 0) {
|
||||
// The selection is entirely off the screen:
|
||||
// Set the lowest line on the screen as selection.
|
||||
|
||||
// This will select the bottom-left of the viewport
|
||||
// As this is most likely the span with the line number we take the parent
|
||||
const line = document.elementFromPoint(document.documentElement.clientHeight-1, 0).parentElement;
|
||||
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 {
|
||||
// We extract the line number from the id
|
||||
coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10));
|
||||
}
|
||||
}
|
||||
coverage.to_prev_chunk();
|
||||
};
|
||||
|
||||
// Select line number lineno, or if it is in a colored chunk, select the
|
||||
// entire chunk
|
||||
coverage.select_line_or_chunk = function (lineno) {
|
||||
var c = coverage;
|
||||
var probe_line = c.line_elt(lineno);
|
||||
if (!probe_line) {
|
||||
return;
|
||||
}
|
||||
var the_indicator = c.chunk_indicator(probe_line);
|
||||
if (the_indicator) {
|
||||
// The line is in a highlighted chunk.
|
||||
// Search backward for the first line.
|
||||
var probe = lineno;
|
||||
var indicator = the_indicator;
|
||||
while (probe > 0 && indicator === the_indicator) {
|
||||
probe--;
|
||||
probe_line = c.line_elt(probe);
|
||||
if (!probe_line) {
|
||||
break;
|
||||
}
|
||||
indicator = c.chunk_indicator(probe_line);
|
||||
}
|
||||
var begin = probe + 1;
|
||||
|
||||
// Search forward for the last line.
|
||||
probe = lineno;
|
||||
indicator = the_indicator;
|
||||
while (indicator === the_indicator) {
|
||||
probe++;
|
||||
probe_line = c.line_elt(probe);
|
||||
indicator = c.chunk_indicator(probe_line);
|
||||
}
|
||||
|
||||
coverage.set_sel(begin, probe);
|
||||
}
|
||||
else {
|
||||
coverage.set_sel(lineno);
|
||||
}
|
||||
};
|
||||
|
||||
coverage.show_selection = function () {
|
||||
// Highlight the lines in the chunk
|
||||
document.querySelectorAll("#source .highlight").forEach(e => e.classList.remove("highlight"));
|
||||
for (let probe = coverage.sel_begin; probe < coverage.sel_end; probe++) {
|
||||
coverage.line_elt(probe).querySelector(".n").classList.add("highlight");
|
||||
}
|
||||
|
||||
coverage.scroll_to_selection();
|
||||
};
|
||||
|
||||
coverage.scroll_to_selection = function () {
|
||||
// Scroll the page if the chunk isn't fully visible.
|
||||
if (coverage.selection_ends_on_screen() < 2) {
|
||||
const element = coverage.line_elt(coverage.sel_begin);
|
||||
coverage.scroll_window(element.offsetTop - 60);
|
||||
}
|
||||
};
|
||||
|
||||
coverage.scroll_window = function (to_pos) {
|
||||
window.scroll({top: to_pos, behavior: "smooth"});
|
||||
};
|
||||
|
||||
coverage.init_scroll_markers = function () {
|
||||
// Init some variables
|
||||
coverage.lines_len = document.querySelectorAll('#source > p').length;
|
||||
|
||||
// Build html
|
||||
coverage.build_scroll_markers();
|
||||
};
|
||||
|
||||
coverage.build_scroll_markers = function () {
|
||||
const temp_scroll_marker = document.getElementById('scroll_marker')
|
||||
if (temp_scroll_marker) temp_scroll_marker.remove();
|
||||
// Don't build markers if the window has no scroll bar.
|
||||
if (document.body.scrollHeight <= window.innerHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const marker_scale = window.innerHeight / document.body.scrollHeight;
|
||||
const line_height = Math.min(Math.max(3, window.innerHeight / coverage.lines_len), 10);
|
||||
|
||||
let previous_line = -99, last_mark, last_top;
|
||||
|
||||
const scroll_marker = document.createElement("div");
|
||||
scroll_marker.id = "scroll_marker";
|
||||
document.getElementById('source').querySelectorAll(
|
||||
'p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par'
|
||||
).forEach(element => {
|
||||
const line_top = Math.floor(element.offsetTop * marker_scale);
|
||||
const line_number = parseInt(element.querySelector(".n a").id.substr(1));
|
||||
|
||||
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 {
|
||||
// Add colored line in scroll_marker block.
|
||||
last_mark = document.createElement("div");
|
||||
last_mark.id = `m${line_number}`;
|
||||
last_mark.classList.add("marker");
|
||||
last_mark.style.height = `${line_height}px`;
|
||||
last_mark.style.top = `${line_top}px`;
|
||||
scroll_marker.append(last_mark);
|
||||
last_top = line_top;
|
||||
}
|
||||
|
||||
previous_line = line_number;
|
||||
});
|
||||
|
||||
// Append last to prevent layout calculation
|
||||
document.body.append(scroll_marker);
|
||||
};
|
||||
|
||||
coverage.wire_up_sticky_header = function () {
|
||||
const header = document.querySelector('header');
|
||||
const header_bottom = (
|
||||
header.querySelector('.content h2').getBoundingClientRect().top -
|
||||
header.getBoundingClientRect().top
|
||||
);
|
||||
|
||||
function updateHeader() {
|
||||
if (window.scrollY > header_bottom) {
|
||||
header.classList.add('sticky');
|
||||
} else {
|
||||
header.classList.remove('sticky');
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', updateHeader);
|
||||
updateHeader();
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (document.body.classList.contains("indexfile")) {
|
||||
coverage.index_ready();
|
||||
} else {
|
||||
coverage.pyfile_ready();
|
||||
}
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,142 @@
|
||||
{# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 #}
|
||||
{# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>{{ title|escape }}</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32.png">
|
||||
<link rel="stylesheet" href="style.css" type="text/css">
|
||||
{% if extra_css %}
|
||||
<link rel="stylesheet" href="{{ extra_css }}" type="text/css">
|
||||
{% endif %}
|
||||
<script type="text/javascript" src="coverage_html.js" defer></script>
|
||||
</head>
|
||||
<body class="indexfile">
|
||||
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>{{ title|escape }}:
|
||||
<span class="pc_cov">{{totals.pc_covered_str}}%</span>
|
||||
</h1>
|
||||
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed.png" alt="Show/hide keyboard shortcuts" />
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>n</kbd>
|
||||
<kbd>s</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
{% if has_arcs %}
|
||||
<kbd>b</kbd>
|
||||
<kbd>p</kbd>
|
||||
{% endif %}
|
||||
<kbd>c</kbd>
|
||||
change column sorting
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<form id="filter_container">
|
||||
<input id="filter" type="text" value="" placeholder="filter..." />
|
||||
</form>
|
||||
|
||||
<p class="text">
|
||||
<a class="nav" href="{{__url__}}">coverage.py v{{__version__}}</a>,
|
||||
created at {{ time_stamp }}
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="index">
|
||||
<table class="index" data-sortable>
|
||||
<thead>
|
||||
{# The title="" attr doesn"t work in Safari. #}
|
||||
<tr class="tablehead" title="Click to sort">
|
||||
<th class="name left" aria-sort="none" data-shortcut="n">Module</th>
|
||||
<th aria-sort="none" data-default-sort-order="descending" data-shortcut="s">statements</th>
|
||||
<th aria-sort="none" data-default-sort-order="descending" data-shortcut="m">missing</th>
|
||||
<th aria-sort="none" data-default-sort-order="descending" data-shortcut="x">excluded</th>
|
||||
{% if has_arcs %}
|
||||
<th aria-sort="none" data-default-sort-order="descending" data-shortcut="b">branches</th>
|
||||
<th aria-sort="none" data-default-sort-order="descending" data-shortcut="p">partial</th>
|
||||
{% endif %}
|
||||
<th class="right" aria-sort="none" data-shortcut="c">coverage</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for file in files %}
|
||||
<tr class="file">
|
||||
<td class="name left"><a href="{{file.html_filename}}">{{file.relative_filename}}</a></td>
|
||||
<td>{{file.nums.n_statements}}</td>
|
||||
<td>{{file.nums.n_missing}}</td>
|
||||
<td>{{file.nums.n_excluded}}</td>
|
||||
{% if has_arcs %}
|
||||
<td>{{file.nums.n_branches}}</td>
|
||||
<td>{{file.nums.n_partial_branches}}</td>
|
||||
{% endif %}
|
||||
<td class="right" data-ratio="{{file.nums.ratio_covered|pair}}">{{file.nums.pc_covered_str}}%</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="total">
|
||||
<td class="name left">Total</td>
|
||||
<td>{{totals.n_statements}}</td>
|
||||
<td>{{totals.n_missing}}</td>
|
||||
<td>{{totals.n_excluded}}</td>
|
||||
{% if has_arcs %}
|
||||
<td>{{totals.n_branches}}</td>
|
||||
<td>{{totals.n_partial_branches}}</td>
|
||||
{% endif %}
|
||||
<td class="right" data-ratio="{{totals.ratio_covered|pair}}">{{totals.pc_covered_str}}%</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<p id="no_rows">
|
||||
No items found using the specified filter.
|
||||
</p>
|
||||
|
||||
{% if skipped_covered_msg %}
|
||||
<p>{{ skipped_covered_msg }}</p>
|
||||
{% endif %}
|
||||
{% if skipped_empty_msg %}
|
||||
<p>{{ skipped_empty_msg }}</p>
|
||||
{% endif %}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="{{__url__}}">coverage.py v{{__version__}}</a>,
|
||||
created at {{ time_stamp }}
|
||||
</p>
|
||||
</div>
|
||||
<aside class="hidden">
|
||||
<a id="prevFileLink" class="nav" href="{{ final_html }}"/>
|
||||
<a id="nextFileLink" class="nav" href="{{ first_html }}"/>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["/>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"/>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"/>
|
||||
</aside>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
@ -0,0 +1,146 @@
|
||||
{# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 #}
|
||||
{# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage for {{relative_filename|escape}}: {{nums.pc_covered_str}}%</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32.png">
|
||||
<link rel="stylesheet" href="style.css" type="text/css">
|
||||
{% if extra_css %}
|
||||
<link rel="stylesheet" href="{{ extra_css }}" type="text/css">
|
||||
{% endif %}
|
||||
<script type="text/javascript" src="coverage_html.js" defer></script>
|
||||
</head>
|
||||
<body class="pyfile">
|
||||
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>
|
||||
<span class="text">Coverage for </span><b>{{relative_filename|escape}}</b>:
|
||||
<span class="pc_cov">{{nums.pc_covered_str}}%</span>
|
||||
</h1>
|
||||
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed.png" alt="Show/hide keyboard shortcuts" />
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>r</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
{% if has_arcs %}
|
||||
<kbd>p</kbd>
|
||||
{% endif %}
|
||||
toggle line displays
|
||||
</p>
|
||||
<p>
|
||||
<kbd>j</kbd>
|
||||
<kbd>k</kbd>
|
||||
next/prev highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>0</kbd> (zero) top of page
|
||||
</p>
|
||||
<p>
|
||||
<kbd>1</kbd> (one) first highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>u</kbd> up to the index
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<h2>
|
||||
<span class="text">{{nums.n_statements}} statements </span>
|
||||
<button type="button" class="{{category.run}} button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">{{nums.n_executed}}<span class="text"> run</span></button>
|
||||
<button type="button" class="{{category.mis}} button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">{{nums.n_missing}}<span class="text"> missing</span></button>
|
||||
<button type="button" class="{{category.exc}} button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">{{nums.n_excluded}}<span class="text"> excluded</span></button>
|
||||
{% if has_arcs %}
|
||||
<button type="button" class="{{category.par}} button_toggle_par" value="par" data-shortcut="p" title="Toggle lines partially run">{{nums.n_partial_branches}}<span class="text"> partial</span></button>
|
||||
{% endif %}
|
||||
</h2>
|
||||
|
||||
<p class="text">
|
||||
<a id="prevFileLink" class="nav" href="{{ prev_html }}">« prev</a>
|
||||
<a id="indexLink" class="nav" href="index.html">^ index</a>
|
||||
<a id="nextFileLink" class="nav" href="{{ next_html }}">» next</a>
|
||||
|
||||
<a class="nav" href="{{__url__}}">coverage.py v{{__version__}}</a>,
|
||||
created at {{ time_stamp }}
|
||||
</p>
|
||||
|
||||
<aside class="hidden">
|
||||
<button type="button" class="button_next_chunk" data-shortcut="j"/>
|
||||
<button type="button" class="button_prev_chunk" data-shortcut="k"/>
|
||||
<button type="button" class="button_top_of_page" data-shortcut="0"/>
|
||||
<button type="button" class="button_first_chunk" data-shortcut="1"/>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["/>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"/>
|
||||
<button type="button" class="button_to_index" data-shortcut="u"/>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"/>
|
||||
</aside>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="source">
|
||||
{% for line in lines -%}
|
||||
{% joined %}
|
||||
<p class="{{line.css_class}}">
|
||||
<span class="n"><a id="t{{line.number}}" href="#t{{line.number}}">{{line.number}}</a></span>
|
||||
<span class="t">{{line.html}} </span>
|
||||
{% if line.context_list %}
|
||||
<input type="checkbox" id="ctxs{{line.number}}" />
|
||||
{% endif %}
|
||||
{# Things that should float right in the line. #}
|
||||
<span class="r">
|
||||
{% if line.annotate %}
|
||||
<span class="annotate short">{{line.annotate}}</span>
|
||||
<span class="annotate long">{{line.annotate_long}}</span>
|
||||
{% endif %}
|
||||
{% if line.contexts %}
|
||||
<label for="ctxs{{line.number}}" class="ctx">{{ line.contexts_label }}</label>
|
||||
{% endif %}
|
||||
</span>
|
||||
{# Things that should appear below the line. #}
|
||||
{% if line.context_list %}
|
||||
<span class="ctxs">
|
||||
{% for context in line.context_list %}
|
||||
<span>{{context}}</span>
|
||||
{% endfor %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endjoined %}
|
||||
{% endfor %}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a id="prevFileLink" class="nav" href="{{ prev_html }}">« prev</a>
|
||||
<a id="indexLink" class="nav" href="index.html">^ index</a>
|
||||
<a id="nextFileLink" class="nav" href="{{ next_html }}">» next</a>
|
||||
|
||||
<a class="nav" href="{{__url__}}">coverage.py v{{__version__}}</a>,
|
||||
created at {{ time_stamp }}
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
311
utils/python-venv/Lib/site-packages/coverage/htmlfiles/style.css
Normal file
311
utils/python-venv/Lib/site-packages/coverage/htmlfiles/style.css
Normal file
@ -0,0 +1,311 @@
|
||||
@charset "UTF-8";
|
||||
/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
|
||||
/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
|
||||
/* Don't edit this .css file. Edit the .scss file instead! */
|
||||
html, body, h1, h2, h3, p, table, td, th { margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; }
|
||||
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 1em; background: #fff; color: #000; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { body { background: #1e1e1e; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { body { color: #eee; } }
|
||||
|
||||
html > body { font-size: 16px; }
|
||||
|
||||
a:active, a:focus { outline: 2px dashed #007acc; }
|
||||
|
||||
p { font-size: .875em; line-height: 1.4em; }
|
||||
|
||||
table { border-collapse: collapse; }
|
||||
|
||||
td { vertical-align: top; }
|
||||
|
||||
table tr.hidden { display: none !important; }
|
||||
|
||||
p#no_rows { display: none; font-size: 1.2em; }
|
||||
|
||||
a.nav { text-decoration: none; color: inherit; }
|
||||
|
||||
a.nav:hover { text-decoration: underline; color: inherit; }
|
||||
|
||||
.hidden { display: none; }
|
||||
|
||||
header { background: #f8f8f8; width: 100%; z-index: 2; border-bottom: 1px solid #ccc; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header { background: black; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header { border-color: #333; } }
|
||||
|
||||
header .content { padding: 1rem 3.5rem; }
|
||||
|
||||
header h2 { margin-top: .5em; font-size: 1em; }
|
||||
|
||||
header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header p.text { color: #aaa; } }
|
||||
|
||||
header.sticky { position: fixed; left: 0; right: 0; height: 2.5em; }
|
||||
|
||||
header.sticky .text { display: none; }
|
||||
|
||||
header.sticky h1, header.sticky h2 { font-size: 1em; margin-top: 0; display: inline-block; }
|
||||
|
||||
header.sticky .content { padding: 0.5rem 3.5rem; }
|
||||
|
||||
header.sticky .content p { font-size: 1em; }
|
||||
|
||||
header.sticky ~ #source { padding-top: 6.5em; }
|
||||
|
||||
main { position: relative; z-index: 1; }
|
||||
|
||||
footer { margin: 1rem 3.5rem; }
|
||||
|
||||
footer .content { padding: 0; color: #666; font-style: italic; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { footer .content { color: #aaa; } }
|
||||
|
||||
#index { margin: 1rem 0 0 3.5rem; }
|
||||
|
||||
h1 { font-size: 1.25em; display: inline-block; }
|
||||
|
||||
#filter_container { float: right; margin: 0 2em 0 0; }
|
||||
|
||||
#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 input { border-color: #444; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #filter_container input { background: #1e1e1e; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #filter_container input { color: #eee; } }
|
||||
|
||||
#filter_container input:focus { border-color: #007acc; }
|
||||
|
||||
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) { header button { border-color: #444; } }
|
||||
|
||||
header button:active, header button:focus { outline: 2px dashed #007acc; }
|
||||
|
||||
header button.run { background: #eeffee; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.run { background: #373d29; } }
|
||||
|
||||
header button.run.show_run { background: #dfd; border: 2px solid #00dd00; margin: 0 .1em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.run.show_run { background: #373d29; } }
|
||||
|
||||
header button.mis { background: #ffeeee; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.mis { background: #4b1818; } }
|
||||
|
||||
header button.mis.show_mis { background: #fdd; border: 2px solid #ff0000; margin: 0 .1em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.mis.show_mis { background: #4b1818; } }
|
||||
|
||||
header button.exc { background: #f7f7f7; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.exc { background: #333; } }
|
||||
|
||||
header button.exc.show_exc { background: #eee; border: 2px solid #808080; margin: 0 .1em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.exc.show_exc { background: #333; } }
|
||||
|
||||
header button.par { background: #ffffd5; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.par { background: #650; } }
|
||||
|
||||
header button.par.show_par { background: #ffa; border: 2px solid #bbbb00; margin: 0 .1em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.par.show_par { background: #650; } }
|
||||
|
||||
#help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; }
|
||||
|
||||
#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; }
|
||||
|
||||
#help_panel_wrapper { float: right; position: relative; }
|
||||
|
||||
#keyboard_icon { margin: 5px; }
|
||||
|
||||
#help_panel_state { display: none; }
|
||||
|
||||
#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; color: #333; }
|
||||
|
||||
#help_panel .keyhelp p { margin-top: .75em; }
|
||||
|
||||
#help_panel .legend { font-style: italic; margin-bottom: 1em; }
|
||||
|
||||
.indexfile #help_panel { width: 25em; }
|
||||
|
||||
.pyfile #help_panel { width: 18em; }
|
||||
|
||||
#help_panel_state:checked ~ #help_panel { display: block; }
|
||||
|
||||
kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; border-radius: 3px; }
|
||||
|
||||
#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
|
||||
|
||||
#source p { position: relative; white-space: pre; }
|
||||
|
||||
#source p * { box-sizing: border-box; }
|
||||
|
||||
#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .n { color: #777; } }
|
||||
|
||||
#source p .n.highlight { background: #ffdd00; }
|
||||
|
||||
#source p .n a { margin-top: -4em; padding-top: 4em; text-decoration: none; color: #999; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .n a { color: #777; } }
|
||||
|
||||
#source p .n a:hover { text-decoration: underline; color: #999; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .n a:hover { color: #777; } }
|
||||
|
||||
#source p .t { display: inline-block; width: 100%; box-sizing: border-box; margin-left: -.5em; padding-left: 0.3em; border-left: 0.2em solid #fff; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .t { border-color: #1e1e1e; } }
|
||||
|
||||
#source p .t:hover { background: #f2f2f2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .t:hover { background: #282828; } }
|
||||
|
||||
#source p .t:hover ~ .r .annotate.long { display: block; }
|
||||
|
||||
#source p .t .com { color: #008000; font-style: italic; line-height: 1px; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .t .com { color: #6a9955; } }
|
||||
|
||||
#source p .t .key { font-weight: bold; line-height: 1px; }
|
||||
|
||||
#source p .t .str { color: #0451a5; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .t .str { color: #9cdcfe; } }
|
||||
|
||||
#source p.mis .t { border-left: 0.2em solid #ff0000; }
|
||||
|
||||
#source p.mis.show_mis .t { background: #fdd; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t { background: #4b1818; } }
|
||||
|
||||
#source p.mis.show_mis .t:hover { background: #f2d2d2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } }
|
||||
|
||||
#source p.run .t { border-left: 0.2em solid #00dd00; }
|
||||
|
||||
#source p.run.show_run .t { background: #dfd; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.run.show_run .t { background: #373d29; } }
|
||||
|
||||
#source p.run.show_run .t:hover { background: #d2f2d2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } }
|
||||
|
||||
#source p.exc .t { border-left: 0.2em solid #808080; }
|
||||
|
||||
#source p.exc.show_exc .t { background: #eee; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t { background: #333; } }
|
||||
|
||||
#source p.exc.show_exc .t:hover { background: #e2e2e2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } }
|
||||
|
||||
#source p.par .t { border-left: 0.2em solid #bbbb00; }
|
||||
|
||||
#source p.par.show_par .t { background: #ffa; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.par.show_par .t { background: #650; } }
|
||||
|
||||
#source p.par.show_par .t:hover { background: #f2f2a2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } }
|
||||
|
||||
#source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; }
|
||||
|
||||
#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .annotate { color: #ddd; } }
|
||||
|
||||
#source p .annotate.short:hover ~ .long { display: block; }
|
||||
|
||||
#source p .annotate.long { width: 30em; right: 2.5em; }
|
||||
|
||||
#source p input { display: none; }
|
||||
|
||||
#source p input ~ .r label.ctx { cursor: pointer; border-radius: .25em; }
|
||||
|
||||
#source p input ~ .r label.ctx::before { content: "▶ "; }
|
||||
|
||||
#source p input ~ .r label.ctx:hover { background: #e8f4ff; color: #666; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { background: #0f3a42; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { color: #aaa; } }
|
||||
|
||||
#source p input:checked ~ .r label.ctx { background: #d0e8ff; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { background: #056; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { color: #aaa; } }
|
||||
|
||||
#source p input:checked ~ .r label.ctx::before { content: "▼ "; }
|
||||
|
||||
#source p input:checked ~ .ctxs { padding: .25em .5em; overflow-y: scroll; max-height: 10.5em; }
|
||||
|
||||
#source p label.ctx { color: #999; display: inline-block; padding: 0 .5em; font-size: .8333em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p label.ctx { color: #777; } }
|
||||
|
||||
#source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; white-space: nowrap; background: #d0e8ff; border-radius: .25em; margin-right: 1.75em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .ctxs { background: #056; } }
|
||||
|
||||
#source p .ctxs span { display: block; text-align: right; }
|
||||
|
||||
#index { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.875em; }
|
||||
|
||||
#index table.index { margin-left: -.5em; }
|
||||
|
||||
#index td, #index th { text-align: right; width: 5em; 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 th { font-style: italic; color: #333; cursor: pointer; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #index th { color: #ddd; } }
|
||||
|
||||
#index th:hover { background: #eee; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #index th:hover { background: #333; } }
|
||||
|
||||
#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="descending"]::after { font-family: sans-serif; content: " ↓"; }
|
||||
|
||||
#index td.name a { text-decoration: none; color: inherit; }
|
||||
|
||||
#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; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #index tr.file:hover { background: #333; } }
|
||||
|
||||
#index tr.file: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; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #scroll_marker { background: #1e1e1e; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #scroll_marker { border-color: #333; } }
|
||||
|
||||
#scroll_marker .marker { background: #ccc; position: absolute; min-height: 3px; width: 100%; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #scroll_marker .marker { background: #444; } }
|
@ -0,0 +1,719 @@
|
||||
/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
|
||||
/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
|
||||
|
||||
// CSS styles for coverage.py HTML reports.
|
||||
|
||||
// When you edit this file, you need to run "make css" to get the CSS file
|
||||
// generated, and then check in both the .scss and the .css files.
|
||||
|
||||
// When working on the file, this command is useful:
|
||||
// sass --watch --style=compact --sourcemap=none --no-cache coverage/htmlfiles/style.scss:htmlcov/style.css
|
||||
//
|
||||
// OR you can process sass purely in python with `pip install pysass`, then:
|
||||
// pysassc --style=compact coverage/htmlfiles/style.scss coverage/htmlfiles/style.css
|
||||
|
||||
// Ignore this comment, it's for the CSS output file:
|
||||
/* Don't edit this .css file. Edit the .scss file instead! */
|
||||
|
||||
// Dimensions
|
||||
$left-gutter: 3.5rem;
|
||||
|
||||
//
|
||||
// Declare colors and variables
|
||||
//
|
||||
|
||||
$font-normal: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||
$font-code: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
|
||||
$off-button-lighten: 50%;
|
||||
$hover-dark-amt: 95%;
|
||||
|
||||
$focus-color: #007acc;
|
||||
|
||||
$mis-color: #ff0000;
|
||||
$run-color: #00dd00;
|
||||
$exc-color: #808080;
|
||||
$par-color: #bbbb00;
|
||||
|
||||
$light-bg: #fff;
|
||||
$light-fg: #000;
|
||||
$light-gray1: #f8f8f8;
|
||||
$light-gray2: #eee;
|
||||
$light-gray3: #ccc;
|
||||
$light-gray4: #999;
|
||||
$light-gray5: #666;
|
||||
$light-gray6: #333;
|
||||
$light-pln-bg: $light-bg;
|
||||
$light-mis-bg: #fdd;
|
||||
$light-run-bg: #dfd;
|
||||
$light-exc-bg: $light-gray2;
|
||||
$light-par-bg: #ffa;
|
||||
$light-token-com: #008000;
|
||||
$light-token-str: #0451a5;
|
||||
$light-context-bg-color: #d0e8ff;
|
||||
|
||||
$dark-bg: #1e1e1e;
|
||||
$dark-fg: #eee;
|
||||
$dark-gray1: #222;
|
||||
$dark-gray2: #333;
|
||||
$dark-gray3: #444;
|
||||
$dark-gray4: #777;
|
||||
$dark-gray5: #aaa;
|
||||
$dark-gray6: #ddd;
|
||||
$dark-pln-bg: $dark-bg;
|
||||
$dark-mis-bg: #4b1818;
|
||||
$dark-run-bg: #373d29;
|
||||
$dark-exc-bg: $dark-gray2;
|
||||
$dark-par-bg: #650;
|
||||
$dark-token-com: #6a9955;
|
||||
$dark-token-str: #9cdcfe;
|
||||
$dark-context-bg-color: #056;
|
||||
|
||||
//
|
||||
// Mixins and utilities
|
||||
//
|
||||
|
||||
@mixin background-dark($color) {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background: $color;
|
||||
}
|
||||
}
|
||||
@mixin color-dark($color) {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: $color;
|
||||
}
|
||||
}
|
||||
@mixin border-color-dark($color) {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
border-color: $color;
|
||||
}
|
||||
}
|
||||
|
||||
// Add visual outline to navigable elements on focus improve accessibility.
|
||||
@mixin focus-border {
|
||||
&:active, &:focus {
|
||||
outline: 2px dashed $focus-color;
|
||||
}
|
||||
}
|
||||
|
||||
// Page-wide styles
|
||||
html, body, h1, h2, h3, p, table, td, th {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-weight: inherit;
|
||||
font-style: inherit;
|
||||
font-size: 100%;
|
||||
font-family: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
// Set baseline grid to 16 pt.
|
||||
body {
|
||||
font-family: $font-normal;
|
||||
font-size: 1em;
|
||||
background: $light-bg;
|
||||
color: $light-fg;
|
||||
@include background-dark($dark-bg);
|
||||
@include color-dark($dark-fg);
|
||||
}
|
||||
|
||||
html>body {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
@include focus-border;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: .875em;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
table tr.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
p#no_rows {
|
||||
display: none;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
a.nav {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Page structure
|
||||
header {
|
||||
background: $light-gray1;
|
||||
@include background-dark(black);
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
border-bottom: 1px solid $light-gray3;
|
||||
@include border-color-dark($dark-gray2);
|
||||
|
||||
.content {
|
||||
padding: 1rem $left-gutter;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: .5em;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
p.text {
|
||||
margin: .5em 0 -.5em;
|
||||
color: $light-gray5;
|
||||
@include color-dark($dark-gray5);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
&.sticky {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2.5em;
|
||||
|
||||
.text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
font-size: 1em;
|
||||
margin-top: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: .5rem $left-gutter;
|
||||
p {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
& ~ #source {
|
||||
padding-top: 6.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin: 1rem $left-gutter;
|
||||
|
||||
.content {
|
||||
padding: 0;
|
||||
color: $light-gray5;
|
||||
@include color-dark($dark-gray5);
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
#index {
|
||||
margin: 1rem 0 0 $left-gutter;
|
||||
}
|
||||
|
||||
// Header styles
|
||||
|
||||
h1 {
|
||||
font-size: 1.25em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#filter_container {
|
||||
float: right;
|
||||
margin: 0 2em 0 0;
|
||||
|
||||
input {
|
||||
width: 10em;
|
||||
padding: 0.2em 0.5em;
|
||||
border: 2px solid $light-gray3;
|
||||
background: $light-bg;
|
||||
color: $light-fg;
|
||||
@include border-color-dark($dark-gray3);
|
||||
@include background-dark($dark-bg);
|
||||
@include color-dark($dark-fg);
|
||||
&:focus {
|
||||
border-color: $focus-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: $light-gray3;
|
||||
@include border-color-dark($dark-gray3);
|
||||
@include focus-border;
|
||||
|
||||
&.run {
|
||||
background: mix($light-run-bg, $light-bg, $off-button-lighten);
|
||||
@include background-dark($dark-run-bg);
|
||||
&.show_run {
|
||||
background: $light-run-bg;
|
||||
@include background-dark($dark-run-bg);
|
||||
border: 2px solid $run-color;
|
||||
margin: 0 .1em;
|
||||
}
|
||||
}
|
||||
&.mis {
|
||||
background: mix($light-mis-bg, $light-bg, $off-button-lighten);
|
||||
@include background-dark($dark-mis-bg);
|
||||
&.show_mis {
|
||||
background: $light-mis-bg;
|
||||
@include background-dark($dark-mis-bg);
|
||||
border: 2px solid $mis-color;
|
||||
margin: 0 .1em;
|
||||
}
|
||||
}
|
||||
&.exc {
|
||||
background: mix($light-exc-bg, $light-bg, $off-button-lighten);
|
||||
@include background-dark($dark-exc-bg);
|
||||
&.show_exc {
|
||||
background: $light-exc-bg;
|
||||
@include background-dark($dark-exc-bg);
|
||||
border: 2px solid $exc-color;
|
||||
margin: 0 .1em;
|
||||
}
|
||||
}
|
||||
&.par {
|
||||
background: mix($light-par-bg, $light-bg, $off-button-lighten);
|
||||
@include background-dark($dark-par-bg);
|
||||
&.show_par {
|
||||
background: $light-par-bg;
|
||||
@include background-dark($dark-par-bg);
|
||||
border: 2px solid $par-color;
|
||||
margin: 0 .1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Yellow post-it things.
|
||||
%popup {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
background: #ffffcc;
|
||||
border: 1px solid #888;
|
||||
border-radius: .2em;
|
||||
color: #333;
|
||||
padding: .25em .5em;
|
||||
}
|
||||
|
||||
// Yellow post-it's in the text listings.
|
||||
%in-text-popup {
|
||||
@extend %popup;
|
||||
white-space: normal;
|
||||
float: right;
|
||||
top: 1.75em;
|
||||
right: 1em;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
// Help panel
|
||||
#help_panel_wrapper {
|
||||
float: right;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#keyboard_icon {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
#help_panel_state {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#help_panel {
|
||||
@extend %popup;
|
||||
top: 25px;
|
||||
right: 0;
|
||||
padding: .75em;
|
||||
border: 1px solid #883;
|
||||
|
||||
color: #333;
|
||||
|
||||
.keyhelp p {
|
||||
margin-top: .75em;
|
||||
}
|
||||
|
||||
.legend {
|
||||
font-style: italic;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.indexfile & {
|
||||
width: 25em;
|
||||
}
|
||||
|
||||
.pyfile & {
|
||||
width: 18em;
|
||||
}
|
||||
|
||||
#help_panel_state:checked ~ & {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
kbd {
|
||||
border: 1px solid black;
|
||||
border-color: #888 #333 #333 #888;
|
||||
padding: .1em .35em;
|
||||
font-family: $font-code;
|
||||
font-weight: bold;
|
||||
background: #eee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
// Source file styles
|
||||
|
||||
// The slim bar at the left edge of the source lines, colored by coverage.
|
||||
$border-indicator-width: .2em;
|
||||
|
||||
#source {
|
||||
padding: 1em 0 1em $left-gutter;
|
||||
font-family: $font-code;
|
||||
|
||||
p {
|
||||
// position relative makes position:absolute pop-ups appear in the right place.
|
||||
position: relative;
|
||||
white-space: pre;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.n {
|
||||
float: left;
|
||||
text-align: right;
|
||||
width: $left-gutter;
|
||||
box-sizing: border-box;
|
||||
margin-left: -$left-gutter;
|
||||
padding-right: 1em;
|
||||
color: $light-gray4;
|
||||
@include color-dark($dark-gray4);
|
||||
|
||||
&.highlight {
|
||||
background: #ffdd00;
|
||||
}
|
||||
|
||||
a {
|
||||
// These two lines make anchors to the line scroll the line to be
|
||||
// visible beneath the fixed-position header.
|
||||
margin-top: -4em;
|
||||
padding-top: 4em;
|
||||
|
||||
text-decoration: none;
|
||||
color: $light-gray4;
|
||||
@include color-dark($dark-gray4);
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: $light-gray4;
|
||||
@include color-dark($dark-gray4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.t {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-left: -.5em;
|
||||
padding-left: .5em - $border-indicator-width;
|
||||
border-left: $border-indicator-width solid $light-bg;
|
||||
@include border-color-dark($dark-bg);
|
||||
|
||||
&:hover {
|
||||
background: mix($light-pln-bg, $light-fg, $hover-dark-amt);
|
||||
@include background-dark(mix($dark-pln-bg, $dark-fg, $hover-dark-amt));
|
||||
|
||||
& ~ .r .annotate.long {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// Syntax coloring
|
||||
.com {
|
||||
color: $light-token-com;
|
||||
@include color-dark($dark-token-com);
|
||||
font-style: italic;
|
||||
line-height: 1px;
|
||||
}
|
||||
.key {
|
||||
font-weight: bold;
|
||||
line-height: 1px;
|
||||
}
|
||||
.str {
|
||||
color: $light-token-str;
|
||||
@include color-dark($dark-token-str);
|
||||
}
|
||||
}
|
||||
|
||||
&.mis {
|
||||
.t {
|
||||
border-left: $border-indicator-width solid $mis-color;
|
||||
}
|
||||
|
||||
&.show_mis .t {
|
||||
background: $light-mis-bg;
|
||||
@include background-dark($dark-mis-bg);
|
||||
|
||||
&:hover {
|
||||
background: mix($light-mis-bg, $light-fg, $hover-dark-amt);
|
||||
@include background-dark(mix($dark-mis-bg, $dark-fg, $hover-dark-amt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.run {
|
||||
.t {
|
||||
border-left: $border-indicator-width solid $run-color;
|
||||
}
|
||||
|
||||
&.show_run .t {
|
||||
background: $light-run-bg;
|
||||
@include background-dark($dark-run-bg);
|
||||
|
||||
&:hover {
|
||||
background: mix($light-run-bg, $light-fg, $hover-dark-amt);
|
||||
@include background-dark(mix($dark-run-bg, $dark-fg, $hover-dark-amt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.exc {
|
||||
.t {
|
||||
border-left: $border-indicator-width solid $exc-color;
|
||||
}
|
||||
|
||||
&.show_exc .t {
|
||||
background: $light-exc-bg;
|
||||
@include background-dark($dark-exc-bg);
|
||||
|
||||
&:hover {
|
||||
background: mix($light-exc-bg, $light-fg, $hover-dark-amt);
|
||||
@include background-dark(mix($dark-exc-bg, $dark-fg, $hover-dark-amt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.par {
|
||||
.t {
|
||||
border-left: $border-indicator-width solid $par-color;
|
||||
}
|
||||
|
||||
&.show_par .t {
|
||||
background: $light-par-bg;
|
||||
@include background-dark($dark-par-bg);
|
||||
|
||||
&:hover {
|
||||
background: mix($light-par-bg, $light-fg, $hover-dark-amt);
|
||||
@include background-dark(mix($dark-par-bg, $dark-fg, $hover-dark-amt));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.r {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 2.5em;
|
||||
font-family: $font-normal;
|
||||
}
|
||||
|
||||
.annotate {
|
||||
font-family: $font-normal;
|
||||
color: $light-gray5;
|
||||
@include color-dark($dark-gray6);
|
||||
padding-right: .5em;
|
||||
|
||||
&.short:hover ~ .long {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.long {
|
||||
@extend %in-text-popup;
|
||||
width: 30em;
|
||||
right: 2.5em;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
|
||||
& ~ .r label.ctx {
|
||||
cursor: pointer;
|
||||
border-radius: .25em;
|
||||
&::before {
|
||||
content: "▶ ";
|
||||
}
|
||||
&:hover {
|
||||
background: mix($light-context-bg-color, $light-bg, $off-button-lighten);
|
||||
@include background-dark(mix($dark-context-bg-color, $dark-bg, $off-button-lighten));
|
||||
color: $light-gray5;
|
||||
@include color-dark($dark-gray5);
|
||||
}
|
||||
}
|
||||
|
||||
&:checked ~ .r label.ctx {
|
||||
background: $light-context-bg-color;
|
||||
@include background-dark($dark-context-bg-color);
|
||||
color: $light-gray5;
|
||||
@include color-dark($dark-gray5);
|
||||
border-radius: .75em .75em 0 0;
|
||||
padding: 0 .5em;
|
||||
margin: -.25em 0;
|
||||
&::before {
|
||||
content: "▼ ";
|
||||
}
|
||||
}
|
||||
|
||||
&:checked ~ .ctxs {
|
||||
padding: .25em .5em;
|
||||
overflow-y: scroll;
|
||||
max-height: 10.5em;
|
||||
}
|
||||
}
|
||||
|
||||
label.ctx {
|
||||
color: $light-gray4;
|
||||
@include color-dark($dark-gray4);
|
||||
display: inline-block;
|
||||
padding: 0 .5em;
|
||||
font-size: .8333em; // 10/12
|
||||
}
|
||||
|
||||
.ctxs {
|
||||
display: block;
|
||||
max-height: 0;
|
||||
overflow-y: hidden;
|
||||
transition: all .2s;
|
||||
padding: 0 .5em;
|
||||
font-family: $font-normal;
|
||||
white-space: nowrap;
|
||||
background: $light-context-bg-color;
|
||||
@include background-dark($dark-context-bg-color);
|
||||
border-radius: .25em;
|
||||
margin-right: 1.75em;
|
||||
span {
|
||||
display: block;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// index styles
|
||||
#index {
|
||||
font-family: $font-code;
|
||||
font-size: 0.875em;
|
||||
|
||||
table.index {
|
||||
margin-left: -.5em;
|
||||
}
|
||||
td, th {
|
||||
text-align: right;
|
||||
width: 5em;
|
||||
padding: .25em .5em;
|
||||
border-bottom: 1px solid $light-gray2;
|
||||
@include border-color-dark($dark-gray2);
|
||||
&.name {
|
||||
text-align: left;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
th {
|
||||
font-style: italic;
|
||||
color: $light-gray6;
|
||||
@include color-dark($dark-gray6);
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: $light-gray2;
|
||||
@include background-dark($dark-gray2);
|
||||
}
|
||||
&[aria-sort="ascending"], &[aria-sort="descending"] {
|
||||
white-space: nowrap;
|
||||
background: $light-gray2;
|
||||
@include background-dark($dark-gray2);
|
||||
padding-left: .5em;
|
||||
}
|
||||
&[aria-sort="ascending"]::after {
|
||||
font-family: sans-serif;
|
||||
content: " ↑";
|
||||
}
|
||||
&[aria-sort="descending"]::after {
|
||||
font-family: sans-serif;
|
||||
content: " ↓";
|
||||
}
|
||||
}
|
||||
td.name a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
tr.total td,
|
||||
tr.total_dynamic td {
|
||||
font-weight: bold;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: none;
|
||||
}
|
||||
tr.file:hover {
|
||||
background: $light-gray2;
|
||||
@include background-dark($dark-gray2);
|
||||
td.name {
|
||||
text-decoration: underline;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scroll marker styles
|
||||
#scroll_marker {
|
||||
position: fixed;
|
||||
z-index: 3;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
background: $light-bg;
|
||||
border-left: 1px solid $light-gray2;
|
||||
@include background-dark($dark-bg);
|
||||
@include border-color-dark($dark-gray2);
|
||||
will-change: transform; // for faster scrolling of fixed element in Chrome
|
||||
|
||||
.marker {
|
||||
background: $light-gray3;
|
||||
@include background-dark($dark-gray3);
|
||||
position: absolute;
|
||||
min-height: 3px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
604
utils/python-venv/Lib/site-packages/coverage/inorout.py
Normal file
604
utils/python-venv/Lib/site-packages/coverage/inorout.py
Normal file
@ -0,0 +1,604 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Determining whether files are being measured/reported or not."""
|
||||
|
||||
import importlib.util
|
||||
import inspect
|
||||
import itertools
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import sysconfig
|
||||
import traceback
|
||||
|
||||
from coverage import env
|
||||
from coverage.disposition import FileDisposition, disposition_init
|
||||
from coverage.exceptions import CoverageException, PluginError
|
||||
from coverage.files import TreeMatcher, FnmatchMatcher, ModuleMatcher
|
||||
from coverage.files import prep_patterns, find_python_files, canonical_filename
|
||||
from coverage.misc import sys_modules_saved
|
||||
from coverage.python import source_for_file, source_for_morf
|
||||
|
||||
|
||||
# Pypy has some unusual stuff in the "stdlib". Consider those locations
|
||||
# when deciding where the stdlib is. These modules are not used for anything,
|
||||
# they are modules importable from the pypy lib directories, so that we can
|
||||
# find those directories.
|
||||
_structseq = _pypy_irc_topic = None
|
||||
if env.PYPY:
|
||||
try:
|
||||
import _structseq
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import _pypy_irc_topic
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def canonical_path(morf, directory=False):
|
||||
"""Return the canonical path of the module or file `morf`.
|
||||
|
||||
If the module is a package, then return its directory. If it is a
|
||||
module, then return its file, unless `directory` is True, in which
|
||||
case return its enclosing directory.
|
||||
|
||||
"""
|
||||
morf_path = canonical_filename(source_for_morf(morf))
|
||||
if morf_path.endswith("__init__.py") or directory:
|
||||
morf_path = os.path.split(morf_path)[0]
|
||||
return morf_path
|
||||
|
||||
|
||||
def name_for_module(filename, frame):
|
||||
"""Get the name of the module for a filename and frame.
|
||||
|
||||
For configurability's sake, we allow __main__ modules to be matched by
|
||||
their importable name.
|
||||
|
||||
If loaded via runpy (aka -m), we can usually recover the "original"
|
||||
full dotted module name, otherwise, we resort to interpreting the
|
||||
file name to get the module's name. In the case that the module name
|
||||
can't be determined, None is returned.
|
||||
|
||||
"""
|
||||
module_globals = frame.f_globals if frame is not None else {}
|
||||
if module_globals is None: # pragma: only ironpython
|
||||
# IronPython doesn't provide globals: https://github.com/IronLanguages/main/issues/1296
|
||||
module_globals = {}
|
||||
|
||||
dunder_name = module_globals.get('__name__', None)
|
||||
|
||||
if isinstance(dunder_name, str) and dunder_name != '__main__':
|
||||
# This is the usual case: an imported module.
|
||||
return dunder_name
|
||||
|
||||
loader = module_globals.get('__loader__', None)
|
||||
for attrname in ('fullname', 'name'): # attribute renamed in py3.2
|
||||
if hasattr(loader, attrname):
|
||||
fullname = getattr(loader, attrname)
|
||||
else:
|
||||
continue
|
||||
|
||||
if isinstance(fullname, str) and fullname != '__main__':
|
||||
# Module loaded via: runpy -m
|
||||
return fullname
|
||||
|
||||
# Script as first argument to Python command line.
|
||||
inspectedname = inspect.getmodulename(filename)
|
||||
if inspectedname is not None:
|
||||
return inspectedname
|
||||
else:
|
||||
return dunder_name
|
||||
|
||||
|
||||
def module_is_namespace(mod):
|
||||
"""Is the module object `mod` a PEP420 namespace module?"""
|
||||
return hasattr(mod, '__path__') and getattr(mod, '__file__', None) is None
|
||||
|
||||
|
||||
def module_has_file(mod):
|
||||
"""Does the module object `mod` have an existing __file__ ?"""
|
||||
mod__file__ = getattr(mod, '__file__', None)
|
||||
if mod__file__ is None:
|
||||
return False
|
||||
return os.path.exists(mod__file__)
|
||||
|
||||
|
||||
def file_and_path_for_module(modulename):
|
||||
"""Find the file and search path for `modulename`.
|
||||
|
||||
Returns:
|
||||
filename: The filename of the module, or None.
|
||||
path: A list (possibly empty) of directories to find submodules in.
|
||||
|
||||
"""
|
||||
filename = None
|
||||
path = []
|
||||
try:
|
||||
spec = importlib.util.find_spec(modulename)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
if spec is not None:
|
||||
filename = spec.origin
|
||||
path = list(spec.submodule_search_locations or ())
|
||||
return filename, path
|
||||
|
||||
|
||||
def add_stdlib_paths(paths):
|
||||
"""Add paths where the stdlib can be found to the set `paths`."""
|
||||
# Look at where some standard modules are located. That's the
|
||||
# indication for "installed with the interpreter". In some
|
||||
# environments (virtualenv, for example), these modules may be
|
||||
# spread across a few locations. Look at all the candidate modules
|
||||
# we've imported, and take all the different ones.
|
||||
modules_we_happen_to_have = [
|
||||
inspect, itertools, os, platform, re, sysconfig, traceback,
|
||||
_pypy_irc_topic, _structseq,
|
||||
]
|
||||
for m in modules_we_happen_to_have:
|
||||
if m is not None and hasattr(m, "__file__"):
|
||||
paths.add(canonical_path(m, directory=True))
|
||||
|
||||
if _structseq and not hasattr(_structseq, '__file__'):
|
||||
# PyPy 2.4 has no __file__ in the builtin modules, but the code
|
||||
# objects still have the file names. So dig into one to find
|
||||
# the path to exclude. The "filename" might be synthetic,
|
||||
# don't be fooled by those.
|
||||
structseq_file = _structseq.structseq_new.__code__.co_filename
|
||||
if not structseq_file.startswith("<"):
|
||||
paths.add(canonical_path(structseq_file))
|
||||
|
||||
|
||||
def add_third_party_paths(paths):
|
||||
"""Add locations for third-party packages to the set `paths`."""
|
||||
# Get the paths that sysconfig knows about.
|
||||
scheme_names = set(sysconfig.get_scheme_names())
|
||||
|
||||
for scheme in scheme_names:
|
||||
# https://foss.heptapod.net/pypy/pypy/-/issues/3433
|
||||
better_scheme = "pypy_posix" if scheme == "pypy" else scheme
|
||||
if os.name in better_scheme.split("_"):
|
||||
config_paths = sysconfig.get_paths(scheme)
|
||||
for path_name in ["platlib", "purelib", "scripts"]:
|
||||
paths.add(config_paths[path_name])
|
||||
|
||||
|
||||
def add_coverage_paths(paths):
|
||||
"""Add paths where coverage.py code can be found to the set `paths`."""
|
||||
cover_path = canonical_path(__file__, directory=True)
|
||||
paths.add(cover_path)
|
||||
if env.TESTING:
|
||||
# Don't include our own test code.
|
||||
paths.add(os.path.join(cover_path, "tests"))
|
||||
|
||||
# When testing, we use PyContracts, which should be considered
|
||||
# part of coverage.py, and it uses six. Exclude those directories
|
||||
# just as we exclude ourselves.
|
||||
if env.USE_CONTRACTS:
|
||||
import contracts
|
||||
import six
|
||||
for mod in [contracts, six]:
|
||||
paths.add(canonical_path(mod))
|
||||
|
||||
|
||||
class InOrOut:
|
||||
"""Machinery for determining what files to measure."""
|
||||
|
||||
def __init__(self, warn, debug):
|
||||
self.warn = warn
|
||||
self.debug = debug
|
||||
|
||||
# The matchers for should_trace.
|
||||
self.source_match = None
|
||||
self.source_pkgs_match = None
|
||||
self.pylib_paths = self.cover_paths = self.third_paths = None
|
||||
self.pylib_match = self.cover_match = self.third_match = None
|
||||
self.include_match = self.omit_match = None
|
||||
self.plugins = []
|
||||
self.disp_class = FileDisposition
|
||||
|
||||
# The source argument can be directories or package names.
|
||||
self.source = []
|
||||
self.source_pkgs = []
|
||||
self.source_pkgs_unmatched = []
|
||||
self.omit = self.include = None
|
||||
|
||||
# Is the source inside a third-party area?
|
||||
self.source_in_third = False
|
||||
|
||||
def configure(self, config):
|
||||
"""Apply the configuration to get ready for decision-time."""
|
||||
self.source_pkgs.extend(config.source_pkgs)
|
||||
for src in config.source or []:
|
||||
if os.path.isdir(src):
|
||||
self.source.append(canonical_filename(src))
|
||||
else:
|
||||
self.source_pkgs.append(src)
|
||||
self.source_pkgs_unmatched = self.source_pkgs[:]
|
||||
|
||||
self.omit = prep_patterns(config.run_omit)
|
||||
self.include = prep_patterns(config.run_include)
|
||||
|
||||
# The directories for files considered "installed with the interpreter".
|
||||
self.pylib_paths = set()
|
||||
if not config.cover_pylib:
|
||||
add_stdlib_paths(self.pylib_paths)
|
||||
|
||||
# To avoid tracing the coverage.py code itself, we skip anything
|
||||
# located where we are.
|
||||
self.cover_paths = set()
|
||||
add_coverage_paths(self.cover_paths)
|
||||
|
||||
# Find where third-party packages are installed.
|
||||
self.third_paths = set()
|
||||
add_third_party_paths(self.third_paths)
|
||||
|
||||
def debug(msg):
|
||||
if self.debug:
|
||||
self.debug.write(msg)
|
||||
|
||||
# Generally useful information
|
||||
debug("sys.path:" + "".join(f"\n {p}" for p in sys.path))
|
||||
|
||||
# Create the matchers we need for should_trace
|
||||
if self.source or self.source_pkgs:
|
||||
against = []
|
||||
if self.source:
|
||||
self.source_match = TreeMatcher(self.source, "source")
|
||||
against.append(f"trees {self.source_match!r}")
|
||||
if self.source_pkgs:
|
||||
self.source_pkgs_match = ModuleMatcher(self.source_pkgs, "source_pkgs")
|
||||
against.append(f"modules {self.source_pkgs_match!r}")
|
||||
debug("Source matching against " + " and ".join(against))
|
||||
else:
|
||||
if self.pylib_paths:
|
||||
self.pylib_match = TreeMatcher(self.pylib_paths, "pylib")
|
||||
debug(f"Python stdlib matching: {self.pylib_match!r}")
|
||||
if self.include:
|
||||
self.include_match = FnmatchMatcher(self.include, "include")
|
||||
debug(f"Include matching: {self.include_match!r}")
|
||||
if self.omit:
|
||||
self.omit_match = FnmatchMatcher(self.omit, "omit")
|
||||
debug(f"Omit matching: {self.omit_match!r}")
|
||||
|
||||
self.cover_match = TreeMatcher(self.cover_paths, "coverage")
|
||||
debug(f"Coverage code matching: {self.cover_match!r}")
|
||||
|
||||
self.third_match = TreeMatcher(self.third_paths, "third")
|
||||
debug(f"Third-party lib matching: {self.third_match!r}")
|
||||
|
||||
# Check if the source we want to measure has been installed as a
|
||||
# third-party package.
|
||||
with sys_modules_saved():
|
||||
for pkg in self.source_pkgs:
|
||||
try:
|
||||
modfile, path = file_and_path_for_module(pkg)
|
||||
debug(f"Imported source package {pkg!r} as {modfile!r}")
|
||||
except CoverageException as exc:
|
||||
debug(f"Couldn't import source package {pkg!r}: {exc}")
|
||||
continue
|
||||
if modfile:
|
||||
if self.third_match.match(modfile):
|
||||
debug(
|
||||
f"Source is in third-party because of source_pkg {pkg!r} at {modfile!r}"
|
||||
)
|
||||
self.source_in_third = True
|
||||
else:
|
||||
for pathdir in path:
|
||||
if self.third_match.match(pathdir):
|
||||
debug(
|
||||
f"Source is in third-party because of {pkg!r} path directory " +
|
||||
f"at {pathdir!r}"
|
||||
)
|
||||
self.source_in_third = True
|
||||
|
||||
for src in self.source:
|
||||
if self.third_match.match(src):
|
||||
debug(f"Source is in third-party because of source directory {src!r}")
|
||||
self.source_in_third = True
|
||||
|
||||
def should_trace(self, filename, frame=None):
|
||||
"""Decide whether to trace execution in `filename`, with a reason.
|
||||
|
||||
This function is called from the trace function. As each new file name
|
||||
is encountered, this function determines whether it is traced or not.
|
||||
|
||||
Returns a FileDisposition object.
|
||||
|
||||
"""
|
||||
original_filename = filename
|
||||
disp = disposition_init(self.disp_class, filename)
|
||||
|
||||
def nope(disp, reason):
|
||||
"""Simple helper to make it easy to return NO."""
|
||||
disp.trace = False
|
||||
disp.reason = reason
|
||||
return disp
|
||||
|
||||
if original_filename.startswith('<'):
|
||||
return nope(disp, "not a real original file name")
|
||||
|
||||
if frame is not None:
|
||||
# Compiled Python files have two file names: frame.f_code.co_filename is
|
||||
# the file name at the time the .pyc was compiled. The second name is
|
||||
# __file__, which is where the .pyc was actually loaded from. Since
|
||||
# .pyc files can be moved after compilation (for example, by being
|
||||
# installed), we look for __file__ in the frame and prefer it to the
|
||||
# co_filename value.
|
||||
dunder_file = frame.f_globals and frame.f_globals.get('__file__')
|
||||
if dunder_file:
|
||||
filename = source_for_file(dunder_file)
|
||||
if original_filename and not original_filename.startswith('<'):
|
||||
orig = os.path.basename(original_filename)
|
||||
if orig != os.path.basename(filename):
|
||||
# Files shouldn't be renamed when moved. This happens when
|
||||
# exec'ing code. If it seems like something is wrong with
|
||||
# the frame's file name, then just use the original.
|
||||
filename = original_filename
|
||||
|
||||
if not filename:
|
||||
# Empty string is pretty useless.
|
||||
return nope(disp, "empty string isn't a file name")
|
||||
|
||||
if filename.startswith('memory:'):
|
||||
return nope(disp, "memory isn't traceable")
|
||||
|
||||
if filename.startswith('<'):
|
||||
# Lots of non-file execution is represented with artificial
|
||||
# file names like "<string>", "<doctest readme.txt[0]>", or
|
||||
# "<exec_function>". Don't ever trace these executions, since we
|
||||
# can't do anything with the data later anyway.
|
||||
return nope(disp, "not a real file name")
|
||||
|
||||
# Jython reports the .class file to the tracer, use the source file.
|
||||
if filename.endswith("$py.class"):
|
||||
filename = filename[:-9] + ".py"
|
||||
|
||||
canonical = canonical_filename(filename)
|
||||
disp.canonical_filename = canonical
|
||||
|
||||
# Try the plugins, see if they have an opinion about the file.
|
||||
plugin = None
|
||||
for plugin in self.plugins.file_tracers:
|
||||
if not plugin._coverage_enabled:
|
||||
continue
|
||||
|
||||
try:
|
||||
file_tracer = plugin.file_tracer(canonical)
|
||||
if file_tracer is not None:
|
||||
file_tracer._coverage_plugin = plugin
|
||||
disp.trace = True
|
||||
disp.file_tracer = file_tracer
|
||||
if file_tracer.has_dynamic_source_filename():
|
||||
disp.has_dynamic_filename = True
|
||||
else:
|
||||
disp.source_filename = canonical_filename(
|
||||
file_tracer.source_filename()
|
||||
)
|
||||
break
|
||||
except Exception:
|
||||
plugin_name = plugin._coverage_plugin_name
|
||||
tb = traceback.format_exc()
|
||||
self.warn(f"Disabling plug-in {plugin_name!r} due to an exception:\n{tb}")
|
||||
plugin._coverage_enabled = False
|
||||
continue
|
||||
else:
|
||||
# No plugin wanted it: it's Python.
|
||||
disp.trace = True
|
||||
disp.source_filename = canonical
|
||||
|
||||
if not disp.has_dynamic_filename:
|
||||
if not disp.source_filename:
|
||||
raise PluginError(
|
||||
f"Plugin {plugin!r} didn't set source_filename for '{disp.original_filename}'"
|
||||
)
|
||||
reason = self.check_include_omit_etc(disp.source_filename, frame)
|
||||
if reason:
|
||||
nope(disp, reason)
|
||||
|
||||
return disp
|
||||
|
||||
def check_include_omit_etc(self, filename, frame):
|
||||
"""Check a file name against the include, omit, etc, rules.
|
||||
|
||||
Returns a string or None. String means, don't trace, and is the reason
|
||||
why. None means no reason found to not trace.
|
||||
|
||||
"""
|
||||
modulename = name_for_module(filename, frame)
|
||||
|
||||
# If the user specified source or include, then that's authoritative
|
||||
# about the outer bound of what to measure and we don't have to apply
|
||||
# any canned exclusions. If they didn't, then we have to exclude the
|
||||
# stdlib and coverage.py directories.
|
||||
if self.source_match or self.source_pkgs_match:
|
||||
extra = ""
|
||||
ok = False
|
||||
if self.source_pkgs_match:
|
||||
if self.source_pkgs_match.match(modulename):
|
||||
ok = True
|
||||
if modulename in self.source_pkgs_unmatched:
|
||||
self.source_pkgs_unmatched.remove(modulename)
|
||||
else:
|
||||
extra = f"module {modulename!r} "
|
||||
if not ok and self.source_match:
|
||||
if self.source_match.match(filename):
|
||||
ok = True
|
||||
if not ok:
|
||||
return extra + "falls outside the --source spec"
|
||||
if not self.source_in_third:
|
||||
if self.third_match.match(filename):
|
||||
return "inside --source, but is third-party"
|
||||
elif self.include_match:
|
||||
if not self.include_match.match(filename):
|
||||
return "falls outside the --include trees"
|
||||
else:
|
||||
# We exclude the coverage.py code itself, since a little of it
|
||||
# will be measured otherwise.
|
||||
if self.cover_match.match(filename):
|
||||
return "is part of coverage.py"
|
||||
|
||||
# If we aren't supposed to trace installed code, then check if this
|
||||
# is near the Python standard library and skip it if so.
|
||||
if self.pylib_match and self.pylib_match.match(filename):
|
||||
return "is in the stdlib"
|
||||
|
||||
# Exclude anything in the third-party installation areas.
|
||||
if self.third_match.match(filename):
|
||||
return "is a third-party module"
|
||||
|
||||
# Check the file against the omit pattern.
|
||||
if self.omit_match and self.omit_match.match(filename):
|
||||
return "is inside an --omit pattern"
|
||||
|
||||
# No point tracing a file we can't later write to SQLite.
|
||||
try:
|
||||
filename.encode("utf-8")
|
||||
except UnicodeEncodeError:
|
||||
return "non-encodable filename"
|
||||
|
||||
# No reason found to skip this file.
|
||||
return None
|
||||
|
||||
def warn_conflicting_settings(self):
|
||||
"""Warn if there are settings that conflict."""
|
||||
if self.include:
|
||||
if self.source or self.source_pkgs:
|
||||
self.warn("--include is ignored because --source is set", slug="include-ignored")
|
||||
|
||||
def warn_already_imported_files(self):
|
||||
"""Warn if files have already been imported that we will be measuring."""
|
||||
if self.include or self.source or self.source_pkgs:
|
||||
warned = set()
|
||||
for mod in list(sys.modules.values()):
|
||||
filename = getattr(mod, "__file__", None)
|
||||
if filename is None:
|
||||
continue
|
||||
if filename in warned:
|
||||
continue
|
||||
|
||||
if len(getattr(mod, "__path__", ())) > 1:
|
||||
# A namespace package, which confuses this code, so ignore it.
|
||||
continue
|
||||
|
||||
disp = self.should_trace(filename)
|
||||
if disp.has_dynamic_filename:
|
||||
# A plugin with dynamic filenames: the Python file
|
||||
# shouldn't cause a warning, since it won't be the subject
|
||||
# of tracing anyway.
|
||||
continue
|
||||
if disp.trace:
|
||||
msg = f"Already imported a file that will be measured: {filename}"
|
||||
self.warn(msg, slug="already-imported")
|
||||
warned.add(filename)
|
||||
elif self.debug and self.debug.should('trace'):
|
||||
self.debug.write(
|
||||
"Didn't trace already imported file {!r}: {}".format(
|
||||
disp.original_filename, disp.reason
|
||||
)
|
||||
)
|
||||
|
||||
def warn_unimported_source(self):
|
||||
"""Warn about source packages that were of interest, but never traced."""
|
||||
for pkg in self.source_pkgs_unmatched:
|
||||
self._warn_about_unmeasured_code(pkg)
|
||||
|
||||
def _warn_about_unmeasured_code(self, pkg):
|
||||
"""Warn about a package or module that we never traced.
|
||||
|
||||
`pkg` is a string, the name of the package or module.
|
||||
|
||||
"""
|
||||
mod = sys.modules.get(pkg)
|
||||
if mod is None:
|
||||
self.warn(f"Module {pkg} was never imported.", slug="module-not-imported")
|
||||
return
|
||||
|
||||
if module_is_namespace(mod):
|
||||
# A namespace package. It's OK for this not to have been traced,
|
||||
# since there is no code directly in it.
|
||||
return
|
||||
|
||||
if not module_has_file(mod):
|
||||
self.warn(f"Module {pkg} has no Python source.", slug="module-not-python")
|
||||
return
|
||||
|
||||
# The module was in sys.modules, and seems like a module with code, but
|
||||
# we never measured it. I guess that means it was imported before
|
||||
# coverage even started.
|
||||
msg = f"Module {pkg} was previously imported, but not measured"
|
||||
self.warn(msg, slug="module-not-measured")
|
||||
|
||||
def find_possibly_unexecuted_files(self):
|
||||
"""Find files in the areas of interest that might be untraced.
|
||||
|
||||
Yields pairs: file path, and responsible plug-in name.
|
||||
"""
|
||||
for pkg in self.source_pkgs:
|
||||
if (not pkg in sys.modules or
|
||||
not module_has_file(sys.modules[pkg])):
|
||||
continue
|
||||
pkg_file = source_for_file(sys.modules[pkg].__file__)
|
||||
yield from self._find_executable_files(canonical_path(pkg_file))
|
||||
|
||||
for src in self.source:
|
||||
yield from self._find_executable_files(src)
|
||||
|
||||
def _find_plugin_files(self, src_dir):
|
||||
"""Get executable files from the plugins."""
|
||||
for plugin in self.plugins.file_tracers:
|
||||
for x_file in plugin.find_executable_files(src_dir):
|
||||
yield x_file, plugin._coverage_plugin_name
|
||||
|
||||
def _find_executable_files(self, src_dir):
|
||||
"""Find executable files in `src_dir`.
|
||||
|
||||
Search for files in `src_dir` that can be executed because they
|
||||
are probably importable. Don't include ones that have been omitted
|
||||
by the configuration.
|
||||
|
||||
Yield the file path, and the plugin name that handles the file.
|
||||
|
||||
"""
|
||||
py_files = ((py_file, None) for py_file in find_python_files(src_dir))
|
||||
plugin_files = self._find_plugin_files(src_dir)
|
||||
|
||||
for file_path, plugin_name in itertools.chain(py_files, plugin_files):
|
||||
file_path = canonical_filename(file_path)
|
||||
if self.omit_match and self.omit_match.match(file_path):
|
||||
# Turns out this file was omitted, so don't pull it back
|
||||
# in as unexecuted.
|
||||
continue
|
||||
yield file_path, plugin_name
|
||||
|
||||
def sys_info(self):
|
||||
"""Our information for Coverage.sys_info.
|
||||
|
||||
Returns a list of (key, value) pairs.
|
||||
"""
|
||||
info = [
|
||||
("coverage_paths", self.cover_paths),
|
||||
("stdlib_paths", self.pylib_paths),
|
||||
("third_party_paths", self.third_paths),
|
||||
]
|
||||
|
||||
matcher_names = [
|
||||
'source_match', 'source_pkgs_match',
|
||||
'include_match', 'omit_match',
|
||||
'cover_match', 'pylib_match', 'third_match',
|
||||
]
|
||||
|
||||
for matcher_name in matcher_names:
|
||||
matcher = getattr(self, matcher_name)
|
||||
if matcher:
|
||||
matcher_info = matcher.info()
|
||||
else:
|
||||
matcher_info = '-none-'
|
||||
info.append((matcher_name, matcher_info))
|
||||
|
||||
return info
|
118
utils/python-venv/Lib/site-packages/coverage/jsonreport.py
Normal file
118
utils/python-venv/Lib/site-packages/coverage/jsonreport.py
Normal file
@ -0,0 +1,118 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Json reporting for coverage.py"""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import sys
|
||||
|
||||
from coverage import __version__
|
||||
from coverage.report import get_analysis_to_report
|
||||
from coverage.results import Numbers
|
||||
|
||||
|
||||
class JsonReporter:
|
||||
"""A reporter for writing JSON coverage results."""
|
||||
|
||||
report_type = "JSON report"
|
||||
|
||||
def __init__(self, coverage):
|
||||
self.coverage = coverage
|
||||
self.config = self.coverage.config
|
||||
self.total = Numbers(self.config.precision)
|
||||
self.report_data = {}
|
||||
|
||||
def report(self, morfs, outfile=None):
|
||||
"""Generate a json report for `morfs`.
|
||||
|
||||
`morfs` is a list of modules or file names.
|
||||
|
||||
`outfile` is a file object to write the json to.
|
||||
|
||||
"""
|
||||
outfile = outfile or sys.stdout
|
||||
coverage_data = self.coverage.get_data()
|
||||
coverage_data.set_query_contexts(self.config.report_contexts)
|
||||
self.report_data["meta"] = {
|
||||
"version": __version__,
|
||||
"timestamp": datetime.datetime.now().isoformat(),
|
||||
"branch_coverage": coverage_data.has_arcs(),
|
||||
"show_contexts": self.config.json_show_contexts,
|
||||
}
|
||||
|
||||
measured_files = {}
|
||||
for file_reporter, analysis in get_analysis_to_report(self.coverage, morfs):
|
||||
measured_files[file_reporter.relative_filename()] = self.report_one_file(
|
||||
coverage_data,
|
||||
analysis
|
||||
)
|
||||
|
||||
self.report_data["files"] = measured_files
|
||||
|
||||
self.report_data["totals"] = {
|
||||
'covered_lines': self.total.n_executed,
|
||||
'num_statements': self.total.n_statements,
|
||||
'percent_covered': self.total.pc_covered,
|
||||
'percent_covered_display': self.total.pc_covered_str,
|
||||
'missing_lines': self.total.n_missing,
|
||||
'excluded_lines': self.total.n_excluded,
|
||||
}
|
||||
|
||||
if coverage_data.has_arcs():
|
||||
self.report_data["totals"].update({
|
||||
'num_branches': self.total.n_branches,
|
||||
'num_partial_branches': self.total.n_partial_branches,
|
||||
'covered_branches': self.total.n_executed_branches,
|
||||
'missing_branches': self.total.n_missing_branches,
|
||||
})
|
||||
|
||||
json.dump(
|
||||
self.report_data,
|
||||
outfile,
|
||||
indent=(4 if self.config.json_pretty_print else None),
|
||||
)
|
||||
|
||||
return self.total.n_statements and self.total.pc_covered
|
||||
|
||||
def report_one_file(self, coverage_data, analysis):
|
||||
"""Extract the relevant report data for a single file."""
|
||||
nums = analysis.numbers
|
||||
self.total += nums
|
||||
summary = {
|
||||
'covered_lines': nums.n_executed,
|
||||
'num_statements': nums.n_statements,
|
||||
'percent_covered': nums.pc_covered,
|
||||
'percent_covered_display': nums.pc_covered_str,
|
||||
'missing_lines': nums.n_missing,
|
||||
'excluded_lines': nums.n_excluded,
|
||||
}
|
||||
reported_file = {
|
||||
'executed_lines': sorted(analysis.executed),
|
||||
'summary': summary,
|
||||
'missing_lines': sorted(analysis.missing),
|
||||
'excluded_lines': sorted(analysis.excluded),
|
||||
}
|
||||
if self.config.json_show_contexts:
|
||||
reported_file['contexts'] = analysis.data.contexts_by_lineno(analysis.filename)
|
||||
if coverage_data.has_arcs():
|
||||
reported_file['summary'].update({
|
||||
'num_branches': nums.n_branches,
|
||||
'num_partial_branches': nums.n_partial_branches,
|
||||
'covered_branches': nums.n_executed_branches,
|
||||
'missing_branches': nums.n_missing_branches,
|
||||
})
|
||||
reported_file['executed_branches'] = list(
|
||||
_convert_branch_arcs(analysis.executed_branch_arcs())
|
||||
)
|
||||
reported_file['missing_branches'] = list(
|
||||
_convert_branch_arcs(analysis.missing_branch_arcs())
|
||||
)
|
||||
return reported_file
|
||||
|
||||
|
||||
def _convert_branch_arcs(branch_arcs):
|
||||
"""Convert branch arcs to a list of two-element tuples."""
|
||||
for source, targets in branch_arcs.items():
|
||||
for target in targets:
|
||||
yield source, target
|
106
utils/python-venv/Lib/site-packages/coverage/lcovreport.py
Normal file
106
utils/python-venv/Lib/site-packages/coverage/lcovreport.py
Normal file
@ -0,0 +1,106 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""LCOV reporting for coverage.py."""
|
||||
|
||||
import sys
|
||||
import base64
|
||||
from hashlib import md5
|
||||
|
||||
from coverage.report import get_analysis_to_report
|
||||
|
||||
|
||||
class LcovReporter:
|
||||
"""A reporter for writing LCOV coverage reports."""
|
||||
|
||||
report_type = "LCOV report"
|
||||
|
||||
def __init__(self, coverage):
|
||||
self.coverage = coverage
|
||||
self.config = self.coverage.config
|
||||
|
||||
def report(self, morfs, outfile=None):
|
||||
"""Renders the full lcov report.
|
||||
|
||||
'morfs' is a list of modules or filenames
|
||||
|
||||
outfile is the file object to write the file into.
|
||||
"""
|
||||
|
||||
self.coverage.get_data()
|
||||
outfile = outfile or sys.stdout
|
||||
|
||||
for fr, analysis in get_analysis_to_report(self.coverage, morfs):
|
||||
self.get_lcov(fr, analysis, outfile)
|
||||
|
||||
def get_lcov(self, fr, analysis, outfile=None):
|
||||
"""Produces the lcov data for a single file.
|
||||
|
||||
This currently supports both line and branch coverage,
|
||||
however function coverage is not supported.
|
||||
"""
|
||||
outfile.write("TN:\n")
|
||||
outfile.write(f"SF:{fr.relative_filename()}\n")
|
||||
source_lines = fr.source().splitlines()
|
||||
|
||||
for covered in sorted(analysis.executed):
|
||||
# Note: Coverage.py currently only supports checking *if* a line
|
||||
# has been executed, not how many times, so we set this to 1 for
|
||||
# nice output even if it's technically incorrect.
|
||||
|
||||
# The lines below calculate a 64-bit encoded md5 hash of the line
|
||||
# corresponding to the DA lines in the lcov file, for either case
|
||||
# of the line being covered or missed in coverage.py. The final two
|
||||
# characters of the encoding ("==") are removed from the hash to
|
||||
# allow genhtml to run on the resulting lcov file.
|
||||
if source_lines:
|
||||
line = source_lines[covered-1].encode("utf-8")
|
||||
else:
|
||||
line = b""
|
||||
hashed = base64.b64encode(md5(line).digest()).decode().rstrip("=")
|
||||
outfile.write(f"DA:{covered},1,{hashed}\n")
|
||||
|
||||
for missed in sorted(analysis.missing):
|
||||
assert source_lines
|
||||
line = source_lines[missed-1].encode("utf-8")
|
||||
hashed = base64.b64encode(md5(line).digest()).decode().rstrip("=")
|
||||
outfile.write(f"DA:{missed},0,{hashed}\n")
|
||||
|
||||
outfile.write(f"LF:{len(analysis.statements)}\n")
|
||||
outfile.write(f"LH:{len(analysis.executed)}\n")
|
||||
|
||||
# More information dense branch coverage data.
|
||||
missing_arcs = analysis.missing_branch_arcs()
|
||||
executed_arcs = analysis.executed_branch_arcs()
|
||||
for block_number, block_line_number in enumerate(
|
||||
sorted(analysis.branch_stats().keys())
|
||||
):
|
||||
for branch_number, line_number in enumerate(
|
||||
sorted(missing_arcs[block_line_number])
|
||||
):
|
||||
# The exit branches have a negative line number,
|
||||
# this will not produce valid lcov. Setting
|
||||
# the line number of the exit branch to 0 will allow
|
||||
# for valid lcov, while preserving the data.
|
||||
line_number = max(line_number, 0)
|
||||
outfile.write(f"BRDA:{line_number},{block_number},{branch_number},-\n")
|
||||
|
||||
# The start value below allows for the block number to be
|
||||
# preserved between these two for loops (stopping the loop from
|
||||
# resetting the value of the block number to 0).
|
||||
for branch_number, line_number in enumerate(
|
||||
sorted(executed_arcs[block_line_number]),
|
||||
start=len(missing_arcs[block_line_number]),
|
||||
):
|
||||
line_number = max(line_number, 0)
|
||||
outfile.write(f"BRDA:{line_number},{block_number},{branch_number},1\n")
|
||||
|
||||
# Summary of the branch coverage.
|
||||
if analysis.has_arcs():
|
||||
branch_stats = analysis.branch_stats()
|
||||
brf = sum(t for t, k in branch_stats.values())
|
||||
brh = brf - sum(t - k for t, k in branch_stats.values())
|
||||
outfile.write(f"BRF:{brf}\n")
|
||||
outfile.write(f"BRH:{brh}\n")
|
||||
|
||||
outfile.write("end_of_record\n")
|
406
utils/python-venv/Lib/site-packages/coverage/misc.py
Normal file
406
utils/python-venv/Lib/site-packages/coverage/misc.py
Normal file
@ -0,0 +1,406 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Miscellaneous stuff for coverage.py."""
|
||||
|
||||
import contextlib
|
||||
import errno
|
||||
import hashlib
|
||||
import importlib
|
||||
import importlib.util
|
||||
import inspect
|
||||
import locale
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
|
||||
from coverage import env
|
||||
from coverage.exceptions import CoverageException
|
||||
|
||||
# In 6.0, the exceptions moved from misc.py to exceptions.py. But a number of
|
||||
# other packages were importing the exceptions from misc, so import them here.
|
||||
# pylint: disable=unused-wildcard-import
|
||||
from coverage.exceptions import * # pylint: disable=wildcard-import
|
||||
|
||||
ISOLATED_MODULES = {}
|
||||
|
||||
|
||||
def isolate_module(mod):
|
||||
"""Copy a module so that we are isolated from aggressive mocking.
|
||||
|
||||
If a test suite mocks os.path.exists (for example), and then we need to use
|
||||
it during the test, everything will get tangled up if we use their mock.
|
||||
Making a copy of the module when we import it will isolate coverage.py from
|
||||
those complications.
|
||||
"""
|
||||
if mod not in ISOLATED_MODULES:
|
||||
new_mod = types.ModuleType(mod.__name__)
|
||||
ISOLATED_MODULES[mod] = new_mod
|
||||
for name in dir(mod):
|
||||
value = getattr(mod, name)
|
||||
if isinstance(value, types.ModuleType):
|
||||
value = isolate_module(value)
|
||||
setattr(new_mod, name, value)
|
||||
return ISOLATED_MODULES[mod]
|
||||
|
||||
os = isolate_module(os)
|
||||
|
||||
|
||||
class SysModuleSaver:
|
||||
"""Saves the contents of sys.modules, and removes new modules later."""
|
||||
def __init__(self):
|
||||
self.old_modules = set(sys.modules)
|
||||
|
||||
def restore(self):
|
||||
"""Remove any modules imported since this object started."""
|
||||
new_modules = set(sys.modules) - self.old_modules
|
||||
for m in new_modules:
|
||||
del sys.modules[m]
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def sys_modules_saved():
|
||||
"""A context manager to remove any modules imported during a block."""
|
||||
saver = SysModuleSaver()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
saver.restore()
|
||||
|
||||
|
||||
def import_third_party(modname):
|
||||
"""Import a third-party module we need, but might not be installed.
|
||||
|
||||
This also cleans out the module after the import, so that coverage won't
|
||||
appear to have imported it. This lets the third party use coverage for
|
||||
their own tests.
|
||||
|
||||
Arguments:
|
||||
modname (str): the name of the module to import.
|
||||
|
||||
Returns:
|
||||
The imported module, or None if the module couldn't be imported.
|
||||
|
||||
"""
|
||||
with sys_modules_saved():
|
||||
try:
|
||||
return importlib.import_module(modname)
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
|
||||
def dummy_decorator_with_args(*args_unused, **kwargs_unused):
|
||||
"""Dummy no-op implementation of a decorator with arguments."""
|
||||
def _decorator(func):
|
||||
return func
|
||||
return _decorator
|
||||
|
||||
|
||||
# Use PyContracts for assertion testing on parameters and returns, but only if
|
||||
# we are running our own test suite.
|
||||
if env.USE_CONTRACTS:
|
||||
from contracts import contract # pylint: disable=unused-import
|
||||
from contracts import new_contract as raw_new_contract
|
||||
|
||||
def new_contract(*args, **kwargs):
|
||||
"""A proxy for contracts.new_contract that doesn't mind happening twice."""
|
||||
try:
|
||||
raw_new_contract(*args, **kwargs)
|
||||
except ValueError:
|
||||
# During meta-coverage, this module is imported twice, and
|
||||
# PyContracts doesn't like redefining contracts. It's OK.
|
||||
pass
|
||||
|
||||
# Define contract words that PyContract doesn't have.
|
||||
new_contract('bytes', lambda v: isinstance(v, bytes))
|
||||
new_contract('unicode', lambda v: isinstance(v, str))
|
||||
|
||||
def one_of(argnames):
|
||||
"""Ensure that only one of the argnames is non-None."""
|
||||
def _decorator(func):
|
||||
argnameset = {name.strip() for name in argnames.split(",")}
|
||||
def _wrapper(*args, **kwargs):
|
||||
vals = [kwargs.get(name) for name in argnameset]
|
||||
assert sum(val is not None for val in vals) == 1
|
||||
return func(*args, **kwargs)
|
||||
return _wrapper
|
||||
return _decorator
|
||||
else: # pragma: not testing
|
||||
# We aren't using real PyContracts, so just define our decorators as
|
||||
# stunt-double no-ops.
|
||||
contract = dummy_decorator_with_args
|
||||
one_of = dummy_decorator_with_args
|
||||
|
||||
def new_contract(*args_unused, **kwargs_unused):
|
||||
"""Dummy no-op implementation of `new_contract`."""
|
||||
pass
|
||||
|
||||
|
||||
def nice_pair(pair):
|
||||
"""Make a nice string representation of a pair of numbers.
|
||||
|
||||
If the numbers are equal, just return the number, otherwise return the pair
|
||||
with a dash between them, indicating the range.
|
||||
|
||||
"""
|
||||
start, end = pair
|
||||
if start == end:
|
||||
return "%d" % start
|
||||
else:
|
||||
return "%d-%d" % (start, end)
|
||||
|
||||
|
||||
def expensive(fn):
|
||||
"""A decorator to indicate that a method shouldn't be called more than once.
|
||||
|
||||
Normally, this does nothing. During testing, this raises an exception if
|
||||
called more than once.
|
||||
|
||||
"""
|
||||
if env.TESTING:
|
||||
attr = "_once_" + fn.__name__
|
||||
|
||||
def _wrapper(self):
|
||||
if hasattr(self, attr):
|
||||
raise AssertionError(f"Shouldn't have called {fn.__name__} more than once")
|
||||
setattr(self, attr, True)
|
||||
return fn(self)
|
||||
return _wrapper
|
||||
else:
|
||||
return fn # pragma: not testing
|
||||
|
||||
|
||||
def bool_or_none(b):
|
||||
"""Return bool(b), but preserve None."""
|
||||
if b is None:
|
||||
return None
|
||||
else:
|
||||
return bool(b)
|
||||
|
||||
|
||||
def join_regex(regexes):
|
||||
"""Combine a list of regexes into one that matches any of them."""
|
||||
return "|".join(f"(?:{r})" for r in regexes)
|
||||
|
||||
|
||||
def file_be_gone(path):
|
||||
"""Remove a file, and don't get annoyed if it doesn't exist."""
|
||||
try:
|
||||
os.remove(path)
|
||||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
|
||||
|
||||
def ensure_dir(directory):
|
||||
"""Make sure the directory exists.
|
||||
|
||||
If `directory` is None or empty, do nothing.
|
||||
"""
|
||||
if directory:
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
|
||||
|
||||
def ensure_dir_for_file(path):
|
||||
"""Make sure the directory for the path exists."""
|
||||
ensure_dir(os.path.dirname(path))
|
||||
|
||||
|
||||
def output_encoding(outfile=None):
|
||||
"""Determine the encoding to use for output written to `outfile` or stdout."""
|
||||
if outfile is None:
|
||||
outfile = sys.stdout
|
||||
encoding = (
|
||||
getattr(outfile, "encoding", None) or
|
||||
getattr(sys.__stdout__, "encoding", None) or
|
||||
locale.getpreferredencoding()
|
||||
)
|
||||
return encoding
|
||||
|
||||
|
||||
class Hasher:
|
||||
"""Hashes Python data for fingerprinting."""
|
||||
def __init__(self):
|
||||
self.hash = hashlib.new("sha3_256")
|
||||
|
||||
def update(self, v):
|
||||
"""Add `v` to the hash, recursively if needed."""
|
||||
self.hash.update(str(type(v)).encode("utf-8"))
|
||||
if isinstance(v, str):
|
||||
self.hash.update(v.encode("utf-8"))
|
||||
elif isinstance(v, bytes):
|
||||
self.hash.update(v)
|
||||
elif v is None:
|
||||
pass
|
||||
elif isinstance(v, (int, float)):
|
||||
self.hash.update(str(v).encode("utf-8"))
|
||||
elif isinstance(v, (tuple, list)):
|
||||
for e in v:
|
||||
self.update(e)
|
||||
elif isinstance(v, dict):
|
||||
keys = v.keys()
|
||||
for k in sorted(keys):
|
||||
self.update(k)
|
||||
self.update(v[k])
|
||||
else:
|
||||
for k in dir(v):
|
||||
if k.startswith('__'):
|
||||
continue
|
||||
a = getattr(v, k)
|
||||
if inspect.isroutine(a):
|
||||
continue
|
||||
self.update(k)
|
||||
self.update(a)
|
||||
self.hash.update(b'.')
|
||||
|
||||
def hexdigest(self):
|
||||
"""Retrieve the hex digest of the hash."""
|
||||
return self.hash.hexdigest()[:32]
|
||||
|
||||
|
||||
def _needs_to_implement(that, func_name):
|
||||
"""Helper to raise NotImplementedError in interface stubs."""
|
||||
if hasattr(that, "_coverage_plugin_name"):
|
||||
thing = "Plugin"
|
||||
name = that._coverage_plugin_name
|
||||
else:
|
||||
thing = "Class"
|
||||
klass = that.__class__
|
||||
name = f"{klass.__module__}.{klass.__name__}"
|
||||
|
||||
raise NotImplementedError(
|
||||
f"{thing} {name!r} needs to implement {func_name}()"
|
||||
)
|
||||
|
||||
|
||||
class DefaultValue:
|
||||
"""A sentinel object to use for unusual default-value needs.
|
||||
|
||||
Construct with a string that will be used as the repr, for display in help
|
||||
and Sphinx output.
|
||||
|
||||
"""
|
||||
def __init__(self, display_as):
|
||||
self.display_as = display_as
|
||||
|
||||
def __repr__(self):
|
||||
return self.display_as
|
||||
|
||||
|
||||
def substitute_variables(text, variables):
|
||||
"""Substitute ``${VAR}`` variables in `text` with their values.
|
||||
|
||||
Variables in the text can take a number of shell-inspired forms::
|
||||
|
||||
$VAR
|
||||
${VAR}
|
||||
${VAR?} strict: an error if VAR isn't defined.
|
||||
${VAR-missing} defaulted: "missing" if VAR isn't defined.
|
||||
$$ just a dollar sign.
|
||||
|
||||
`variables` is a dictionary of variable values.
|
||||
|
||||
Returns the resulting text with values substituted.
|
||||
|
||||
"""
|
||||
dollar_pattern = r"""(?x) # Use extended regex syntax
|
||||
\$ # A dollar sign,
|
||||
(?: # then
|
||||
(?P<dollar>\$) | # a dollar sign, or
|
||||
(?P<word1>\w+) | # a plain word, or
|
||||
{ # a {-wrapped
|
||||
(?P<word2>\w+) # word,
|
||||
(?:
|
||||
(?P<strict>\?) | # with a strict marker
|
||||
-(?P<defval>[^}]*) # or a default value
|
||||
)? # maybe.
|
||||
}
|
||||
)
|
||||
"""
|
||||
|
||||
dollar_groups = ('dollar', 'word1', 'word2')
|
||||
|
||||
def dollar_replace(match):
|
||||
"""Called for each $replacement."""
|
||||
# Only one of the dollar_groups will have matched, just get its text.
|
||||
word = next(g for g in match.group(*dollar_groups) if g) # pragma: always breaks
|
||||
if word == "$":
|
||||
return "$"
|
||||
elif word in variables:
|
||||
return variables[word]
|
||||
elif match['strict']:
|
||||
msg = f"Variable {word} is undefined: {text!r}"
|
||||
raise CoverageException(msg)
|
||||
else:
|
||||
return match['defval']
|
||||
|
||||
text = re.sub(dollar_pattern, dollar_replace, text)
|
||||
return text
|
||||
|
||||
|
||||
def format_local_datetime(dt):
|
||||
"""Return a string with local timezone representing the date.
|
||||
"""
|
||||
return dt.astimezone().strftime('%Y-%m-%d %H:%M %z')
|
||||
|
||||
|
||||
def import_local_file(modname, modfile=None):
|
||||
"""Import a local file as a module.
|
||||
|
||||
Opens a file in the current directory named `modname`.py, imports it
|
||||
as `modname`, and returns the module object. `modfile` is the file to
|
||||
import if it isn't in the current directory.
|
||||
|
||||
"""
|
||||
if modfile is None:
|
||||
modfile = modname + '.py'
|
||||
spec = importlib.util.spec_from_file_location(modname, modfile)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
sys.modules[modname] = mod
|
||||
spec.loader.exec_module(mod)
|
||||
|
||||
return mod
|
||||
|
||||
|
||||
def human_key(s):
|
||||
"""Turn a string into a list of string and number chunks.
|
||||
"z23a" -> ["z", 23, "a"]
|
||||
"""
|
||||
def tryint(s):
|
||||
"""If `s` is a number, return an int, else `s` unchanged."""
|
||||
try:
|
||||
return int(s)
|
||||
except ValueError:
|
||||
return s
|
||||
|
||||
return [tryint(c) for c in re.split(r"(\d+)", s)]
|
||||
|
||||
def human_sorted(strings):
|
||||
"""Sort the given iterable of strings the way that humans expect.
|
||||
|
||||
Numeric components in the strings are sorted as numbers.
|
||||
|
||||
Returns the sorted list.
|
||||
|
||||
"""
|
||||
return sorted(strings, key=human_key)
|
||||
|
||||
def human_sorted_items(items, reverse=False):
|
||||
"""Sort the (string, value) items the way humans expect.
|
||||
|
||||
Returns the sorted list of items.
|
||||
"""
|
||||
return sorted(items, key=lambda pair: (human_key(pair[0]), pair[1]), reverse=reverse)
|
||||
|
||||
|
||||
def plural(n, thing="", things=""):
|
||||
"""Pluralize a word.
|
||||
|
||||
If n is 1, return thing. Otherwise return things, or thing+s.
|
||||
"""
|
||||
if n == 1:
|
||||
return thing
|
||||
else:
|
||||
return things or (thing + "s")
|
103
utils/python-venv/Lib/site-packages/coverage/multiproc.py
Normal file
103
utils/python-venv/Lib/site-packages/coverage/multiproc.py
Normal file
@ -0,0 +1,103 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Monkey-patching to add multiprocessing support for coverage.py"""
|
||||
|
||||
import multiprocessing
|
||||
import multiprocessing.process
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from coverage.misc import contract
|
||||
|
||||
# An attribute that will be set on the module to indicate that it has been
|
||||
# monkey-patched.
|
||||
PATCHED_MARKER = "_coverage$patched"
|
||||
|
||||
|
||||
OriginalProcess = multiprocessing.process.BaseProcess
|
||||
original_bootstrap = OriginalProcess._bootstrap
|
||||
|
||||
class ProcessWithCoverage(OriginalProcess): # pylint: disable=abstract-method
|
||||
"""A replacement for multiprocess.Process that starts coverage."""
|
||||
|
||||
def _bootstrap(self, *args, **kwargs):
|
||||
"""Wrapper around _bootstrap to start coverage."""
|
||||
try:
|
||||
from coverage import Coverage # avoid circular import
|
||||
cov = Coverage(data_suffix=True, auto_data=True)
|
||||
cov._warn_preimported_source = False
|
||||
cov.start()
|
||||
debug = cov._debug
|
||||
if debug.should("multiproc"):
|
||||
debug.write("Calling multiprocessing bootstrap")
|
||||
except Exception:
|
||||
print("Exception during multiprocessing bootstrap init:")
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
sys.stdout.flush()
|
||||
raise
|
||||
try:
|
||||
return original_bootstrap(self, *args, **kwargs)
|
||||
finally:
|
||||
if debug.should("multiproc"):
|
||||
debug.write("Finished multiprocessing bootstrap")
|
||||
cov.stop()
|
||||
cov.save()
|
||||
if debug.should("multiproc"):
|
||||
debug.write("Saved multiprocessing data")
|
||||
|
||||
class Stowaway:
|
||||
"""An object to pickle, so when it is unpickled, it can apply the monkey-patch."""
|
||||
def __init__(self, rcfile):
|
||||
self.rcfile = rcfile
|
||||
|
||||
def __getstate__(self):
|
||||
return {'rcfile': self.rcfile}
|
||||
|
||||
def __setstate__(self, state):
|
||||
patch_multiprocessing(state['rcfile'])
|
||||
|
||||
|
||||
@contract(rcfile=str)
|
||||
def patch_multiprocessing(rcfile):
|
||||
"""Monkey-patch the multiprocessing module.
|
||||
|
||||
This enables coverage measurement of processes started by multiprocessing.
|
||||
This involves aggressive monkey-patching.
|
||||
|
||||
`rcfile` is the path to the rcfile being used.
|
||||
|
||||
"""
|
||||
|
||||
if hasattr(multiprocessing, PATCHED_MARKER):
|
||||
return
|
||||
|
||||
OriginalProcess._bootstrap = ProcessWithCoverage._bootstrap
|
||||
|
||||
# Set the value in ProcessWithCoverage that will be pickled into the child
|
||||
# process.
|
||||
os.environ["COVERAGE_RCFILE"] = os.path.abspath(rcfile)
|
||||
|
||||
# When spawning processes rather than forking them, we have no state in the
|
||||
# new process. We sneak in there with a Stowaway: we stuff one of our own
|
||||
# objects into the data that gets pickled and sent to the sub-process. When
|
||||
# the Stowaway is unpickled, it's __setstate__ method is called, which
|
||||
# re-applies the monkey-patch.
|
||||
# Windows only spawns, so this is needed to keep Windows working.
|
||||
try:
|
||||
from multiprocessing import spawn
|
||||
original_get_preparation_data = spawn.get_preparation_data
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
else:
|
||||
def get_preparation_data_with_stowaway(name):
|
||||
"""Get the original preparation data, and also insert our stowaway."""
|
||||
d = original_get_preparation_data(name)
|
||||
d['stowaway'] = Stowaway(rcfile)
|
||||
return d
|
||||
|
||||
spawn.get_preparation_data = get_preparation_data_with_stowaway
|
||||
|
||||
setattr(multiprocessing, PATCHED_MARKER, True)
|
156
utils/python-venv/Lib/site-packages/coverage/numbits.py
Normal file
156
utils/python-venv/Lib/site-packages/coverage/numbits.py
Normal file
@ -0,0 +1,156 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""
|
||||
Functions to manipulate packed binary representations of number sets.
|
||||
|
||||
To save space, coverage stores sets of line numbers in SQLite using a packed
|
||||
binary representation called a numbits. A numbits is a set of positive
|
||||
integers.
|
||||
|
||||
A numbits is stored as a blob in the database. The exact meaning of the bytes
|
||||
in the blobs should be considered an implementation detail that might change in
|
||||
the future. Use these functions to work with those binary blobs of data.
|
||||
|
||||
"""
|
||||
import json
|
||||
|
||||
from itertools import zip_longest
|
||||
|
||||
from coverage.misc import contract, new_contract
|
||||
|
||||
def _to_blob(b):
|
||||
"""Convert a bytestring into a type SQLite will accept for a blob."""
|
||||
return b
|
||||
|
||||
new_contract('blob', lambda v: isinstance(v, bytes))
|
||||
|
||||
|
||||
@contract(nums='Iterable', returns='blob')
|
||||
def nums_to_numbits(nums):
|
||||
"""Convert `nums` into a numbits.
|
||||
|
||||
Arguments:
|
||||
nums: a reusable iterable of integers, the line numbers to store.
|
||||
|
||||
Returns:
|
||||
A binary blob.
|
||||
"""
|
||||
try:
|
||||
nbytes = max(nums) // 8 + 1
|
||||
except ValueError:
|
||||
# nums was empty.
|
||||
return _to_blob(b'')
|
||||
b = bytearray(nbytes)
|
||||
for num in nums:
|
||||
b[num//8] |= 1 << num % 8
|
||||
return _to_blob(bytes(b))
|
||||
|
||||
|
||||
@contract(numbits='blob', returns='list[int]')
|
||||
def numbits_to_nums(numbits):
|
||||
"""Convert a numbits into a list of numbers.
|
||||
|
||||
Arguments:
|
||||
numbits: a binary blob, the packed number set.
|
||||
|
||||
Returns:
|
||||
A list of ints.
|
||||
|
||||
When registered as a SQLite function by :func:`register_sqlite_functions`,
|
||||
this returns a string, a JSON-encoded list of ints.
|
||||
|
||||
"""
|
||||
nums = []
|
||||
for byte_i, byte in enumerate(numbits):
|
||||
for bit_i in range(8):
|
||||
if (byte & (1 << bit_i)):
|
||||
nums.append(byte_i * 8 + bit_i)
|
||||
return nums
|
||||
|
||||
|
||||
@contract(numbits1='blob', numbits2='blob', returns='blob')
|
||||
def numbits_union(numbits1, numbits2):
|
||||
"""Compute the union of two numbits.
|
||||
|
||||
Returns:
|
||||
A new numbits, the union of `numbits1` and `numbits2`.
|
||||
"""
|
||||
byte_pairs = zip_longest(numbits1, numbits2, fillvalue=0)
|
||||
return _to_blob(bytes(b1 | b2 for b1, b2 in byte_pairs))
|
||||
|
||||
|
||||
@contract(numbits1='blob', numbits2='blob', returns='blob')
|
||||
def numbits_intersection(numbits1, numbits2):
|
||||
"""Compute the intersection of two numbits.
|
||||
|
||||
Returns:
|
||||
A new numbits, the intersection `numbits1` and `numbits2`.
|
||||
"""
|
||||
byte_pairs = zip_longest(numbits1, numbits2, fillvalue=0)
|
||||
intersection_bytes = bytes(b1 & b2 for b1, b2 in byte_pairs)
|
||||
return _to_blob(intersection_bytes.rstrip(b'\0'))
|
||||
|
||||
|
||||
@contract(numbits1='blob', numbits2='blob', returns='bool')
|
||||
def numbits_any_intersection(numbits1, numbits2):
|
||||
"""Is there any number that appears in both numbits?
|
||||
|
||||
Determine whether two number sets have a non-empty intersection. This is
|
||||
faster than computing the intersection.
|
||||
|
||||
Returns:
|
||||
A bool, True if there is any number in both `numbits1` and `numbits2`.
|
||||
"""
|
||||
byte_pairs = zip_longest(numbits1, numbits2, fillvalue=0)
|
||||
return any(b1 & b2 for b1, b2 in byte_pairs)
|
||||
|
||||
|
||||
@contract(num='int', numbits='blob', returns='bool')
|
||||
def num_in_numbits(num, numbits):
|
||||
"""Does the integer `num` appear in `numbits`?
|
||||
|
||||
Returns:
|
||||
A bool, True if `num` is a member of `numbits`.
|
||||
"""
|
||||
nbyte, nbit = divmod(num, 8)
|
||||
if nbyte >= len(numbits):
|
||||
return False
|
||||
return bool(numbits[nbyte] & (1 << nbit))
|
||||
|
||||
|
||||
def register_sqlite_functions(connection):
|
||||
"""
|
||||
Define numbits functions in a SQLite connection.
|
||||
|
||||
This defines these functions for use in SQLite statements:
|
||||
|
||||
* :func:`numbits_union`
|
||||
* :func:`numbits_intersection`
|
||||
* :func:`numbits_any_intersection`
|
||||
* :func:`num_in_numbits`
|
||||
* :func:`numbits_to_nums`
|
||||
|
||||
`connection` is a :class:`sqlite3.Connection <python:sqlite3.Connection>`
|
||||
object. After creating the connection, pass it to this function to
|
||||
register the numbits functions. Then you can use numbits functions in your
|
||||
queries::
|
||||
|
||||
import sqlite3
|
||||
from coverage.numbits import register_sqlite_functions
|
||||
|
||||
conn = sqlite3.connect('example.db')
|
||||
register_sqlite_functions(conn)
|
||||
c = conn.cursor()
|
||||
# Kind of a nonsense query:
|
||||
# Find all the files and contexts that executed line 47 in any file:
|
||||
c.execute(
|
||||
"select file_id, context_id from line_bits where num_in_numbits(?, numbits)",
|
||||
(47,)
|
||||
)
|
||||
"""
|
||||
connection.create_function("numbits_union", 2, numbits_union)
|
||||
connection.create_function("numbits_intersection", 2, numbits_intersection)
|
||||
connection.create_function("numbits_any_intersection", 2, numbits_any_intersection)
|
||||
connection.create_function("num_in_numbits", 2, num_in_numbits)
|
||||
connection.create_function("numbits_to_nums", 1, lambda b: json.dumps(numbits_to_nums(b)))
|
1375
utils/python-venv/Lib/site-packages/coverage/parser.py
Normal file
1375
utils/python-venv/Lib/site-packages/coverage/parser.py
Normal file
File diff suppressed because it is too large
Load Diff
227
utils/python-venv/Lib/site-packages/coverage/phystokens.py
Normal file
227
utils/python-venv/Lib/site-packages/coverage/phystokens.py
Normal file
@ -0,0 +1,227 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Better tokenizing for coverage.py."""
|
||||
|
||||
import ast
|
||||
import keyword
|
||||
import re
|
||||
import token
|
||||
import tokenize
|
||||
|
||||
from coverage import env
|
||||
from coverage.misc import contract
|
||||
|
||||
|
||||
def phys_tokens(toks):
|
||||
"""Return all physical tokens, even line continuations.
|
||||
|
||||
tokenize.generate_tokens() doesn't return a token for the backslash that
|
||||
continues lines. This wrapper provides those tokens so that we can
|
||||
re-create a faithful representation of the original source.
|
||||
|
||||
Returns the same values as generate_tokens()
|
||||
|
||||
"""
|
||||
last_line = None
|
||||
last_lineno = -1
|
||||
last_ttext = None
|
||||
for ttype, ttext, (slineno, scol), (elineno, ecol), ltext in toks:
|
||||
if last_lineno != elineno:
|
||||
if last_line and last_line.endswith("\\\n"):
|
||||
# We are at the beginning of a new line, and the last line
|
||||
# ended with a backslash. We probably have to inject a
|
||||
# backslash token into the stream. Unfortunately, there's more
|
||||
# to figure out. This code::
|
||||
#
|
||||
# usage = """\
|
||||
# HEY THERE
|
||||
# """
|
||||
#
|
||||
# triggers this condition, but the token text is::
|
||||
#
|
||||
# '"""\\\nHEY THERE\n"""'
|
||||
#
|
||||
# so we need to figure out if the backslash is already in the
|
||||
# string token or not.
|
||||
inject_backslash = True
|
||||
if last_ttext.endswith("\\"):
|
||||
inject_backslash = False
|
||||
elif ttype == token.STRING:
|
||||
if "\n" in ttext and ttext.split('\n', 1)[0][-1] == '\\':
|
||||
# It's a multi-line string and the first line ends with
|
||||
# a backslash, so we don't need to inject another.
|
||||
inject_backslash = False
|
||||
if inject_backslash:
|
||||
# Figure out what column the backslash is in.
|
||||
ccol = len(last_line.split("\n")[-2]) - 1
|
||||
# Yield the token, with a fake token type.
|
||||
yield (
|
||||
99999, "\\\n",
|
||||
(slineno, ccol), (slineno, ccol+2),
|
||||
last_line
|
||||
)
|
||||
last_line = ltext
|
||||
if ttype not in (tokenize.NEWLINE, tokenize.NL):
|
||||
last_ttext = ttext
|
||||
yield ttype, ttext, (slineno, scol), (elineno, ecol), ltext
|
||||
last_lineno = elineno
|
||||
|
||||
|
||||
class MatchCaseFinder(ast.NodeVisitor):
|
||||
"""Helper for finding match/case lines."""
|
||||
def __init__(self, source):
|
||||
# This will be the set of line numbers that start match or case statements.
|
||||
self.match_case_lines = set()
|
||||
self.visit(ast.parse(source))
|
||||
|
||||
def visit_Match(self, node):
|
||||
"""Invoked by ast.NodeVisitor.visit"""
|
||||
self.match_case_lines.add(node.lineno)
|
||||
for case in node.cases:
|
||||
self.match_case_lines.add(case.pattern.lineno)
|
||||
self.generic_visit(node)
|
||||
|
||||
|
||||
@contract(source='unicode')
|
||||
def source_token_lines(source):
|
||||
"""Generate a series of lines, one for each line in `source`.
|
||||
|
||||
Each line is a list of pairs, each pair is a token::
|
||||
|
||||
[('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('), ... ]
|
||||
|
||||
Each pair has a token class, and the token text.
|
||||
|
||||
If you concatenate all the token texts, and then join them with newlines,
|
||||
you should have your original `source` back, with two differences:
|
||||
trailing whitespace is not preserved, and a final line with no newline
|
||||
is indistinguishable from a final line with a newline.
|
||||
|
||||
"""
|
||||
|
||||
ws_tokens = {token.INDENT, token.DEDENT, token.NEWLINE, tokenize.NL}
|
||||
line = []
|
||||
col = 0
|
||||
|
||||
source = source.expandtabs(8).replace('\r\n', '\n')
|
||||
tokgen = generate_tokens(source)
|
||||
|
||||
if env.PYBEHAVIOR.soft_keywords:
|
||||
match_case_lines = MatchCaseFinder(source).match_case_lines
|
||||
|
||||
for ttype, ttext, (sline, scol), (_, ecol), _ in phys_tokens(tokgen):
|
||||
mark_start = True
|
||||
for part in re.split('(\n)', ttext):
|
||||
if part == '\n':
|
||||
yield line
|
||||
line = []
|
||||
col = 0
|
||||
mark_end = False
|
||||
elif part == '':
|
||||
mark_end = False
|
||||
elif ttype in ws_tokens:
|
||||
mark_end = False
|
||||
else:
|
||||
if mark_start and scol > col:
|
||||
line.append(("ws", " " * (scol - col)))
|
||||
mark_start = False
|
||||
tok_class = tokenize.tok_name.get(ttype, 'xx').lower()[:3]
|
||||
if ttype == token.NAME:
|
||||
if keyword.iskeyword(ttext):
|
||||
# Hard keywords are always keywords.
|
||||
tok_class = "key"
|
||||
elif env.PYBEHAVIOR.soft_keywords and keyword.issoftkeyword(ttext):
|
||||
# Soft keywords appear at the start of the line, on lines that start
|
||||
# match or case statements.
|
||||
if len(line) == 0:
|
||||
is_start_of_line = True
|
||||
elif (len(line) == 1) and line[0][0] == "ws":
|
||||
is_start_of_line = True
|
||||
else:
|
||||
is_start_of_line = False
|
||||
if is_start_of_line and sline in match_case_lines:
|
||||
tok_class = "key"
|
||||
line.append((tok_class, part))
|
||||
mark_end = True
|
||||
scol = 0
|
||||
if mark_end:
|
||||
col = ecol
|
||||
|
||||
if line:
|
||||
yield line
|
||||
|
||||
|
||||
class CachedTokenizer:
|
||||
"""A one-element cache around tokenize.generate_tokens.
|
||||
|
||||
When reporting, coverage.py tokenizes files twice, once to find the
|
||||
structure of the file, and once to syntax-color it. Tokenizing is
|
||||
expensive, and easily cached.
|
||||
|
||||
This is a one-element cache so that our twice-in-a-row tokenizing doesn't
|
||||
actually tokenize twice.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.last_text = None
|
||||
self.last_tokens = None
|
||||
|
||||
@contract(text='unicode')
|
||||
def generate_tokens(self, text):
|
||||
"""A stand-in for `tokenize.generate_tokens`."""
|
||||
if text != self.last_text:
|
||||
self.last_text = text
|
||||
readline = iter(text.splitlines(True)).__next__
|
||||
try:
|
||||
self.last_tokens = list(tokenize.generate_tokens(readline))
|
||||
except:
|
||||
self.last_text = None
|
||||
raise
|
||||
return self.last_tokens
|
||||
|
||||
# Create our generate_tokens cache as a callable replacement function.
|
||||
generate_tokens = CachedTokenizer().generate_tokens
|
||||
|
||||
|
||||
COOKIE_RE = re.compile(r"^[ \t]*#.*coding[:=][ \t]*([-\w.]+)", flags=re.MULTILINE)
|
||||
|
||||
@contract(source='bytes')
|
||||
def source_encoding(source):
|
||||
"""Determine the encoding for `source`, according to PEP 263.
|
||||
|
||||
`source` is a byte string: the text of the program.
|
||||
|
||||
Returns a string, the name of the encoding.
|
||||
|
||||
"""
|
||||
readline = iter(source.splitlines(True)).__next__
|
||||
return tokenize.detect_encoding(readline)[0]
|
||||
|
||||
|
||||
@contract(source='unicode')
|
||||
def compile_unicode(source, filename, mode):
|
||||
"""Just like the `compile` builtin, but works on any Unicode string.
|
||||
|
||||
Python 2's compile() builtin has a stupid restriction: if the source string
|
||||
is Unicode, then it may not have a encoding declaration in it. Why not?
|
||||
Who knows! It also decodes to utf-8, and then tries to interpret those
|
||||
utf-8 bytes according to the encoding declaration. Why? Who knows!
|
||||
|
||||
This function neuters the coding declaration, and compiles it.
|
||||
|
||||
"""
|
||||
source = neuter_encoding_declaration(source)
|
||||
code = compile(source, filename, mode)
|
||||
return code
|
||||
|
||||
|
||||
@contract(source='unicode', returns='unicode')
|
||||
def neuter_encoding_declaration(source):
|
||||
"""Return `source`, with any encoding declaration neutered."""
|
||||
if COOKIE_RE.search(source):
|
||||
source_lines = source.splitlines(True)
|
||||
for lineno in range(min(2, len(source_lines))):
|
||||
source_lines[lineno] = COOKIE_RE.sub("# (deleted declaration)", source_lines[lineno])
|
||||
source = "".join(source_lines)
|
||||
return source
|
521
utils/python-venv/Lib/site-packages/coverage/plugin.py
Normal file
521
utils/python-venv/Lib/site-packages/coverage/plugin.py
Normal file
@ -0,0 +1,521 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""
|
||||
.. versionadded:: 4.0
|
||||
|
||||
Plug-in interfaces for coverage.py.
|
||||
|
||||
Coverage.py supports a few different kinds of plug-ins that change its
|
||||
behavior:
|
||||
|
||||
* File tracers implement tracing of non-Python file types.
|
||||
|
||||
* Configurers add custom configuration, using Python code to change the
|
||||
configuration.
|
||||
|
||||
* Dynamic context switchers decide when the dynamic context has changed, for
|
||||
example, to record what test function produced the coverage.
|
||||
|
||||
To write a coverage.py plug-in, create a module with a subclass of
|
||||
:class:`~coverage.CoveragePlugin`. You will override methods in your class to
|
||||
participate in various aspects of coverage.py's processing.
|
||||
Different types of plug-ins have to override different methods.
|
||||
|
||||
Any plug-in can optionally implement :meth:`~coverage.CoveragePlugin.sys_info`
|
||||
to provide debugging information about their operation.
|
||||
|
||||
Your module must also contain a ``coverage_init`` function that registers an
|
||||
instance of your plug-in class::
|
||||
|
||||
import coverage
|
||||
|
||||
class MyPlugin(coverage.CoveragePlugin):
|
||||
...
|
||||
|
||||
def coverage_init(reg, options):
|
||||
reg.add_file_tracer(MyPlugin())
|
||||
|
||||
You use the `reg` parameter passed to your ``coverage_init`` function to
|
||||
register your plug-in object. The registration method you call depends on
|
||||
what kind of plug-in it is.
|
||||
|
||||
If your plug-in takes options, the `options` parameter is a dictionary of your
|
||||
plug-in's options from the coverage.py configuration file. Use them however
|
||||
you want to configure your object before registering it.
|
||||
|
||||
Coverage.py will store its own information on your plug-in object, using
|
||||
attributes whose names start with ``_coverage_``. Don't be startled.
|
||||
|
||||
.. warning::
|
||||
Plug-ins are imported by coverage.py before it begins measuring code.
|
||||
If you write a plugin in your own project, it might import your product
|
||||
code before coverage.py can start measuring. This can result in your
|
||||
own code being reported as missing.
|
||||
|
||||
One solution is to put your plugins in your project tree, but not in
|
||||
your importable Python package.
|
||||
|
||||
|
||||
.. _file_tracer_plugins:
|
||||
|
||||
File Tracers
|
||||
============
|
||||
|
||||
File tracers implement measurement support for non-Python files. File tracers
|
||||
implement the :meth:`~coverage.CoveragePlugin.file_tracer` method to claim
|
||||
files and the :meth:`~coverage.CoveragePlugin.file_reporter` method to report
|
||||
on those files.
|
||||
|
||||
In your ``coverage_init`` function, use the ``add_file_tracer`` method to
|
||||
register your file tracer.
|
||||
|
||||
|
||||
.. _configurer_plugins:
|
||||
|
||||
Configurers
|
||||
===========
|
||||
|
||||
.. versionadded:: 4.5
|
||||
|
||||
Configurers modify the configuration of coverage.py during start-up.
|
||||
Configurers implement the :meth:`~coverage.CoveragePlugin.configure` method to
|
||||
change the configuration.
|
||||
|
||||
In your ``coverage_init`` function, use the ``add_configurer`` method to
|
||||
register your configurer.
|
||||
|
||||
|
||||
.. _dynamic_context_plugins:
|
||||
|
||||
Dynamic Context Switchers
|
||||
=========================
|
||||
|
||||
.. versionadded:: 5.0
|
||||
|
||||
Dynamic context switcher plugins implement the
|
||||
:meth:`~coverage.CoveragePlugin.dynamic_context` method to dynamically compute
|
||||
the context label for each measured frame.
|
||||
|
||||
Computed context labels are useful when you want to group measured data without
|
||||
modifying the source code.
|
||||
|
||||
For example, you could write a plugin that checks `frame.f_code` to inspect
|
||||
the currently executed method, and set the context label to a fully qualified
|
||||
method name if it's an instance method of `unittest.TestCase` and the method
|
||||
name starts with 'test'. Such a plugin would provide basic coverage grouping
|
||||
by test and could be used with test runners that have no built-in coveragepy
|
||||
support.
|
||||
|
||||
In your ``coverage_init`` function, use the ``add_dynamic_context`` method to
|
||||
register your dynamic context switcher.
|
||||
|
||||
"""
|
||||
|
||||
import functools
|
||||
|
||||
from coverage import files
|
||||
from coverage.misc import contract, _needs_to_implement
|
||||
|
||||
|
||||
class CoveragePlugin:
|
||||
"""Base class for coverage.py plug-ins."""
|
||||
|
||||
def file_tracer(self, filename): # pylint: disable=unused-argument
|
||||
"""Get a :class:`FileTracer` object for a file.
|
||||
|
||||
Plug-in type: file tracer.
|
||||
|
||||
Every Python source file is offered to your plug-in to give it a chance
|
||||
to take responsibility for tracing the file. If your plug-in can
|
||||
handle the file, it should return a :class:`FileTracer` object.
|
||||
Otherwise return None.
|
||||
|
||||
There is no way to register your plug-in for particular files.
|
||||
Instead, this method is invoked for all files as they are executed,
|
||||
and the plug-in decides whether it can trace the file or not.
|
||||
Be prepared for `filename` to refer to all kinds of files that have
|
||||
nothing to do with your plug-in.
|
||||
|
||||
The file name will be a Python file being executed. There are two
|
||||
broad categories of behavior for a plug-in, depending on the kind of
|
||||
files your plug-in supports:
|
||||
|
||||
* Static file names: each of your original source files has been
|
||||
converted into a distinct Python file. Your plug-in is invoked with
|
||||
the Python file name, and it maps it back to its original source
|
||||
file.
|
||||
|
||||
* Dynamic file names: all of your source files are executed by the same
|
||||
Python file. In this case, your plug-in implements
|
||||
:meth:`FileTracer.dynamic_source_filename` to provide the actual
|
||||
source file for each execution frame.
|
||||
|
||||
`filename` is a string, the path to the file being considered. This is
|
||||
the absolute real path to the file. If you are comparing to other
|
||||
paths, be sure to take this into account.
|
||||
|
||||
Returns a :class:`FileTracer` object to use to trace `filename`, or
|
||||
None if this plug-in cannot trace this file.
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
def file_reporter(self, filename): # pylint: disable=unused-argument
|
||||
"""Get the :class:`FileReporter` class to use for a file.
|
||||
|
||||
Plug-in type: file tracer.
|
||||
|
||||
This will only be invoked if `filename` returns non-None from
|
||||
:meth:`file_tracer`. It's an error to return None from this method.
|
||||
|
||||
Returns a :class:`FileReporter` object to use to report on `filename`,
|
||||
or the string `"python"` to have coverage.py treat the file as Python.
|
||||
|
||||
"""
|
||||
_needs_to_implement(self, "file_reporter")
|
||||
|
||||
def dynamic_context(self, frame): # pylint: disable=unused-argument
|
||||
"""Get the dynamically computed context label for `frame`.
|
||||
|
||||
Plug-in type: dynamic context.
|
||||
|
||||
This method is invoked for each frame when outside of a dynamic
|
||||
context, to see if a new dynamic context should be started. If it
|
||||
returns a string, a new context label is set for this and deeper
|
||||
frames. The dynamic context ends when this frame returns.
|
||||
|
||||
Returns a string to start a new dynamic context, or None if no new
|
||||
context should be started.
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
def find_executable_files(self, src_dir): # pylint: disable=unused-argument
|
||||
"""Yield all of the executable files in `src_dir`, recursively.
|
||||
|
||||
Plug-in type: file tracer.
|
||||
|
||||
Executability is a plug-in-specific property, but generally means files
|
||||
which would have been considered for coverage analysis, had they been
|
||||
included automatically.
|
||||
|
||||
Returns or yields a sequence of strings, the paths to files that could
|
||||
have been executed, including files that had been executed.
|
||||
|
||||
"""
|
||||
return []
|
||||
|
||||
def configure(self, config):
|
||||
"""Modify the configuration of coverage.py.
|
||||
|
||||
Plug-in type: configurer.
|
||||
|
||||
This method is called during coverage.py start-up, to give your plug-in
|
||||
a chance to change the configuration. The `config` parameter is an
|
||||
object with :meth:`~coverage.Coverage.get_option` and
|
||||
:meth:`~coverage.Coverage.set_option` methods. Do not call any other
|
||||
methods on the `config` object.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def sys_info(self):
|
||||
"""Get a list of information useful for debugging.
|
||||
|
||||
Plug-in type: any.
|
||||
|
||||
This method will be invoked for ``--debug=sys``. Your
|
||||
plug-in can return any information it wants to be displayed.
|
||||
|
||||
Returns a list of pairs: `[(name, value), ...]`.
|
||||
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
class FileTracer:
|
||||
"""Support needed for files during the execution phase.
|
||||
|
||||
File tracer plug-ins implement subclasses of FileTracer to return from
|
||||
their :meth:`~CoveragePlugin.file_tracer` method.
|
||||
|
||||
You may construct this object from :meth:`CoveragePlugin.file_tracer` any
|
||||
way you like. A natural choice would be to pass the file name given to
|
||||
`file_tracer`.
|
||||
|
||||
`FileTracer` objects should only be created in the
|
||||
:meth:`CoveragePlugin.file_tracer` method.
|
||||
|
||||
See :ref:`howitworks` for details of the different coverage.py phases.
|
||||
|
||||
"""
|
||||
|
||||
def source_filename(self):
|
||||
"""The source file name for this file.
|
||||
|
||||
This may be any file name you like. A key responsibility of a plug-in
|
||||
is to own the mapping from Python execution back to whatever source
|
||||
file name was originally the source of the code.
|
||||
|
||||
See :meth:`CoveragePlugin.file_tracer` for details about static and
|
||||
dynamic file names.
|
||||
|
||||
Returns the file name to credit with this execution.
|
||||
|
||||
"""
|
||||
_needs_to_implement(self, "source_filename")
|
||||
|
||||
def has_dynamic_source_filename(self):
|
||||
"""Does this FileTracer have dynamic source file names?
|
||||
|
||||
FileTracers can provide dynamically determined file names by
|
||||
implementing :meth:`dynamic_source_filename`. Invoking that function
|
||||
is expensive. To determine whether to invoke it, coverage.py uses the
|
||||
result of this function to know if it needs to bother invoking
|
||||
:meth:`dynamic_source_filename`.
|
||||
|
||||
See :meth:`CoveragePlugin.file_tracer` for details about static and
|
||||
dynamic file names.
|
||||
|
||||
Returns True if :meth:`dynamic_source_filename` should be called to get
|
||||
dynamic source file names.
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def dynamic_source_filename(self, filename, frame): # pylint: disable=unused-argument
|
||||
"""Get a dynamically computed source file name.
|
||||
|
||||
Some plug-ins need to compute the source file name dynamically for each
|
||||
frame.
|
||||
|
||||
This function will not be invoked if
|
||||
:meth:`has_dynamic_source_filename` returns False.
|
||||
|
||||
Returns the source file name for this frame, or None if this frame
|
||||
shouldn't be measured.
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
def line_number_range(self, frame):
|
||||
"""Get the range of source line numbers for a given a call frame.
|
||||
|
||||
The call frame is examined, and the source line number in the original
|
||||
file is returned. The return value is a pair of numbers, the starting
|
||||
line number and the ending line number, both inclusive. For example,
|
||||
returning (5, 7) means that lines 5, 6, and 7 should be considered
|
||||
executed.
|
||||
|
||||
This function might decide that the frame doesn't indicate any lines
|
||||
from the source file were executed. Return (-1, -1) in this case to
|
||||
tell coverage.py that no lines should be recorded for this frame.
|
||||
|
||||
"""
|
||||
lineno = frame.f_lineno
|
||||
return lineno, lineno
|
||||
|
||||
|
||||
@functools.total_ordering
|
||||
class FileReporter:
|
||||
"""Support needed for files during the analysis and reporting phases.
|
||||
|
||||
File tracer plug-ins implement a subclass of `FileReporter`, and return
|
||||
instances from their :meth:`CoveragePlugin.file_reporter` method.
|
||||
|
||||
There are many methods here, but only :meth:`lines` is required, to provide
|
||||
the set of executable lines in the file.
|
||||
|
||||
See :ref:`howitworks` for details of the different coverage.py phases.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, filename):
|
||||
"""Simple initialization of a `FileReporter`.
|
||||
|
||||
The `filename` argument is the path to the file being reported. This
|
||||
will be available as the `.filename` attribute on the object. Other
|
||||
method implementations on this base class rely on this attribute.
|
||||
|
||||
"""
|
||||
self.filename = filename
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0.__class__.__name__} filename={0.filename!r}>".format(self)
|
||||
|
||||
def relative_filename(self):
|
||||
"""Get the relative file name for this file.
|
||||
|
||||
This file path will be displayed in reports. The default
|
||||
implementation will supply the actual project-relative file path. You
|
||||
only need to supply this method if you have an unusual syntax for file
|
||||
paths.
|
||||
|
||||
"""
|
||||
return files.relative_filename(self.filename)
|
||||
|
||||
@contract(returns='unicode')
|
||||
def source(self):
|
||||
"""Get the source for the file.
|
||||
|
||||
Returns a Unicode string.
|
||||
|
||||
The base implementation simply reads the `self.filename` file and
|
||||
decodes it as UTF-8. Override this method if your file isn't readable
|
||||
as a text file, or if you need other encoding support.
|
||||
|
||||
"""
|
||||
with open(self.filename, "rb") as f:
|
||||
return f.read().decode("utf-8")
|
||||
|
||||
def lines(self):
|
||||
"""Get the executable lines in this file.
|
||||
|
||||
Your plug-in must determine which lines in the file were possibly
|
||||
executable. This method returns a set of those line numbers.
|
||||
|
||||
Returns a set of line numbers.
|
||||
|
||||
"""
|
||||
_needs_to_implement(self, "lines")
|
||||
|
||||
def excluded_lines(self):
|
||||
"""Get the excluded executable lines in this file.
|
||||
|
||||
Your plug-in can use any method it likes to allow the user to exclude
|
||||
executable lines from consideration.
|
||||
|
||||
Returns a set of line numbers.
|
||||
|
||||
The base implementation returns the empty set.
|
||||
|
||||
"""
|
||||
return set()
|
||||
|
||||
def translate_lines(self, lines):
|
||||
"""Translate recorded lines into reported lines.
|
||||
|
||||
Some file formats will want to report lines slightly differently than
|
||||
they are recorded. For example, Python records the last line of a
|
||||
multi-line statement, but reports are nicer if they mention the first
|
||||
line.
|
||||
|
||||
Your plug-in can optionally define this method to perform these kinds
|
||||
of adjustment.
|
||||
|
||||
`lines` is a sequence of integers, the recorded line numbers.
|
||||
|
||||
Returns a set of integers, the adjusted line numbers.
|
||||
|
||||
The base implementation returns the numbers unchanged.
|
||||
|
||||
"""
|
||||
return set(lines)
|
||||
|
||||
def arcs(self):
|
||||
"""Get the executable arcs in this file.
|
||||
|
||||
To support branch coverage, your plug-in needs to be able to indicate
|
||||
possible execution paths, as a set of line number pairs. Each pair is
|
||||
a `(prev, next)` pair indicating that execution can transition from the
|
||||
`prev` line number to the `next` line number.
|
||||
|
||||
Returns a set of pairs of line numbers. The default implementation
|
||||
returns an empty set.
|
||||
|
||||
"""
|
||||
return set()
|
||||
|
||||
def no_branch_lines(self):
|
||||
"""Get the lines excused from branch coverage in this file.
|
||||
|
||||
Your plug-in can use any method it likes to allow the user to exclude
|
||||
lines from consideration of branch coverage.
|
||||
|
||||
Returns a set of line numbers.
|
||||
|
||||
The base implementation returns the empty set.
|
||||
|
||||
"""
|
||||
return set()
|
||||
|
||||
def translate_arcs(self, arcs):
|
||||
"""Translate recorded arcs into reported arcs.
|
||||
|
||||
Similar to :meth:`translate_lines`, but for arcs. `arcs` is a set of
|
||||
line number pairs.
|
||||
|
||||
Returns a set of line number pairs.
|
||||
|
||||
The default implementation returns `arcs` unchanged.
|
||||
|
||||
"""
|
||||
return arcs
|
||||
|
||||
def exit_counts(self):
|
||||
"""Get a count of exits from that each line.
|
||||
|
||||
To determine which lines are branches, coverage.py looks for lines that
|
||||
have more than one exit. This function creates a dict mapping each
|
||||
executable line number to a count of how many exits it has.
|
||||
|
||||
To be honest, this feels wrong, and should be refactored. Let me know
|
||||
if you attempt to implement this method in your plug-in...
|
||||
|
||||
"""
|
||||
return {}
|
||||
|
||||
def missing_arc_description(self, start, end, executed_arcs=None): # pylint: disable=unused-argument
|
||||
"""Provide an English sentence describing a missing arc.
|
||||
|
||||
The `start` and `end` arguments are the line numbers of the missing
|
||||
arc. Negative numbers indicate entering or exiting code objects.
|
||||
|
||||
The `executed_arcs` argument is a set of line number pairs, the arcs
|
||||
that were executed in this file.
|
||||
|
||||
By default, this simply returns the string "Line {start} didn't jump
|
||||
to {end}".
|
||||
|
||||
"""
|
||||
return f"Line {start} didn't jump to line {end}"
|
||||
|
||||
def source_token_lines(self):
|
||||
"""Generate a series of tokenized lines, one for each line in `source`.
|
||||
|
||||
These tokens are used for syntax-colored reports.
|
||||
|
||||
Each line is a list of pairs, each pair is a token::
|
||||
|
||||
[('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('), ... ]
|
||||
|
||||
Each pair has a token class, and the token text. The token classes
|
||||
are:
|
||||
|
||||
* ``'com'``: a comment
|
||||
* ``'key'``: a keyword
|
||||
* ``'nam'``: a name, or identifier
|
||||
* ``'num'``: a number
|
||||
* ``'op'``: an operator
|
||||
* ``'str'``: a string literal
|
||||
* ``'ws'``: some white space
|
||||
* ``'txt'``: some other kind of text
|
||||
|
||||
If you concatenate all the token texts, and then join them with
|
||||
newlines, you should have your original source back.
|
||||
|
||||
The default implementation simply returns each line tagged as
|
||||
``'txt'``.
|
||||
|
||||
"""
|
||||
for line in self.source().splitlines():
|
||||
yield [('txt', line)]
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, FileReporter) and self.filename == other.filename
|
||||
|
||||
def __lt__(self, other):
|
||||
return isinstance(other, FileReporter) and self.filename < other.filename
|
||||
|
||||
__hash__ = None # This object doesn't need to be hashed.
|
280
utils/python-venv/Lib/site-packages/coverage/plugin_support.py
Normal file
280
utils/python-venv/Lib/site-packages/coverage/plugin_support.py
Normal file
@ -0,0 +1,280 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Support for plugins."""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
from coverage.exceptions import PluginError
|
||||
from coverage.misc import isolate_module
|
||||
from coverage.plugin import CoveragePlugin, FileTracer, FileReporter
|
||||
|
||||
os = isolate_module(os)
|
||||
|
||||
|
||||
class Plugins:
|
||||
"""The currently loaded collection of coverage.py plugins."""
|
||||
|
||||
def __init__(self):
|
||||
self.order = []
|
||||
self.names = {}
|
||||
self.file_tracers = []
|
||||
self.configurers = []
|
||||
self.context_switchers = []
|
||||
|
||||
self.current_module = None
|
||||
self.debug = None
|
||||
|
||||
@classmethod
|
||||
def load_plugins(cls, modules, config, debug=None):
|
||||
"""Load plugins from `modules`.
|
||||
|
||||
Returns a Plugins object with the loaded and configured plugins.
|
||||
|
||||
"""
|
||||
plugins = cls()
|
||||
plugins.debug = debug
|
||||
|
||||
for module in modules:
|
||||
plugins.current_module = module
|
||||
__import__(module)
|
||||
mod = sys.modules[module]
|
||||
|
||||
coverage_init = getattr(mod, "coverage_init", None)
|
||||
if not coverage_init:
|
||||
raise PluginError(
|
||||
f"Plugin module {module!r} didn't define a coverage_init function"
|
||||
)
|
||||
|
||||
options = config.get_plugin_options(module)
|
||||
coverage_init(plugins, options)
|
||||
|
||||
plugins.current_module = None
|
||||
return plugins
|
||||
|
||||
def add_file_tracer(self, plugin):
|
||||
"""Add a file tracer plugin.
|
||||
|
||||
`plugin` is an instance of a third-party plugin class. It must
|
||||
implement the :meth:`CoveragePlugin.file_tracer` method.
|
||||
|
||||
"""
|
||||
self._add_plugin(plugin, self.file_tracers)
|
||||
|
||||
def add_configurer(self, plugin):
|
||||
"""Add a configuring plugin.
|
||||
|
||||
`plugin` is an instance of a third-party plugin class. It must
|
||||
implement the :meth:`CoveragePlugin.configure` method.
|
||||
|
||||
"""
|
||||
self._add_plugin(plugin, self.configurers)
|
||||
|
||||
def add_dynamic_context(self, plugin):
|
||||
"""Add a dynamic context plugin.
|
||||
|
||||
`plugin` is an instance of a third-party plugin class. It must
|
||||
implement the :meth:`CoveragePlugin.dynamic_context` method.
|
||||
|
||||
"""
|
||||
self._add_plugin(plugin, self.context_switchers)
|
||||
|
||||
def add_noop(self, plugin):
|
||||
"""Add a plugin that does nothing.
|
||||
|
||||
This is only useful for testing the plugin support.
|
||||
|
||||
"""
|
||||
self._add_plugin(plugin, None)
|
||||
|
||||
def _add_plugin(self, plugin, specialized):
|
||||
"""Add a plugin object.
|
||||
|
||||
`plugin` is a :class:`CoveragePlugin` instance to add. `specialized`
|
||||
is a list to append the plugin to.
|
||||
|
||||
"""
|
||||
plugin_name = f"{self.current_module}.{plugin.__class__.__name__}"
|
||||
if self.debug and self.debug.should('plugin'):
|
||||
self.debug.write(f"Loaded plugin {self.current_module!r}: {plugin!r}")
|
||||
labelled = LabelledDebug(f"plugin {self.current_module!r}", self.debug)
|
||||
plugin = DebugPluginWrapper(plugin, labelled)
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
plugin._coverage_plugin_name = plugin_name
|
||||
plugin._coverage_enabled = True
|
||||
self.order.append(plugin)
|
||||
self.names[plugin_name] = plugin
|
||||
if specialized is not None:
|
||||
specialized.append(plugin)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.order)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.order)
|
||||
|
||||
def get(self, plugin_name):
|
||||
"""Return a plugin by name."""
|
||||
return self.names[plugin_name]
|
||||
|
||||
|
||||
class LabelledDebug:
|
||||
"""A Debug writer, but with labels for prepending to the messages."""
|
||||
|
||||
def __init__(self, label, debug, prev_labels=()):
|
||||
self.labels = list(prev_labels) + [label]
|
||||
self.debug = debug
|
||||
|
||||
def add_label(self, label):
|
||||
"""Add a label to the writer, and return a new `LabelledDebug`."""
|
||||
return LabelledDebug(label, self.debug, self.labels)
|
||||
|
||||
def message_prefix(self):
|
||||
"""The prefix to use on messages, combining the labels."""
|
||||
prefixes = self.labels + ['']
|
||||
return ":\n".join(" "*i+label for i, label in enumerate(prefixes))
|
||||
|
||||
def write(self, message):
|
||||
"""Write `message`, but with the labels prepended."""
|
||||
self.debug.write(f"{self.message_prefix()}{message}")
|
||||
|
||||
|
||||
class DebugPluginWrapper(CoveragePlugin):
|
||||
"""Wrap a plugin, and use debug to report on what it's doing."""
|
||||
|
||||
def __init__(self, plugin, debug):
|
||||
super().__init__()
|
||||
self.plugin = plugin
|
||||
self.debug = debug
|
||||
|
||||
def file_tracer(self, filename):
|
||||
tracer = self.plugin.file_tracer(filename)
|
||||
self.debug.write(f"file_tracer({filename!r}) --> {tracer!r}")
|
||||
if tracer:
|
||||
debug = self.debug.add_label(f"file {filename!r}")
|
||||
tracer = DebugFileTracerWrapper(tracer, debug)
|
||||
return tracer
|
||||
|
||||
def file_reporter(self, filename):
|
||||
reporter = self.plugin.file_reporter(filename)
|
||||
self.debug.write(f"file_reporter({filename!r}) --> {reporter!r}")
|
||||
if reporter:
|
||||
debug = self.debug.add_label(f"file {filename!r}")
|
||||
reporter = DebugFileReporterWrapper(filename, reporter, debug)
|
||||
return reporter
|
||||
|
||||
def dynamic_context(self, frame):
|
||||
context = self.plugin.dynamic_context(frame)
|
||||
self.debug.write(f"dynamic_context({frame!r}) --> {context!r}")
|
||||
return context
|
||||
|
||||
def find_executable_files(self, src_dir):
|
||||
executable_files = self.plugin.find_executable_files(src_dir)
|
||||
self.debug.write(f"find_executable_files({src_dir!r}) --> {executable_files!r}")
|
||||
return executable_files
|
||||
|
||||
def configure(self, config):
|
||||
self.debug.write(f"configure({config!r})")
|
||||
self.plugin.configure(config)
|
||||
|
||||
def sys_info(self):
|
||||
return self.plugin.sys_info()
|
||||
|
||||
|
||||
class DebugFileTracerWrapper(FileTracer):
|
||||
"""A debugging `FileTracer`."""
|
||||
|
||||
def __init__(self, tracer, debug):
|
||||
self.tracer = tracer
|
||||
self.debug = debug
|
||||
|
||||
def _show_frame(self, frame):
|
||||
"""A short string identifying a frame, for debug messages."""
|
||||
return "%s@%d" % (
|
||||
os.path.basename(frame.f_code.co_filename),
|
||||
frame.f_lineno,
|
||||
)
|
||||
|
||||
def source_filename(self):
|
||||
sfilename = self.tracer.source_filename()
|
||||
self.debug.write(f"source_filename() --> {sfilename!r}")
|
||||
return sfilename
|
||||
|
||||
def has_dynamic_source_filename(self):
|
||||
has = self.tracer.has_dynamic_source_filename()
|
||||
self.debug.write(f"has_dynamic_source_filename() --> {has!r}")
|
||||
return has
|
||||
|
||||
def dynamic_source_filename(self, filename, frame):
|
||||
dyn = self.tracer.dynamic_source_filename(filename, frame)
|
||||
self.debug.write("dynamic_source_filename({!r}, {}) --> {!r}".format(
|
||||
filename, self._show_frame(frame), dyn,
|
||||
))
|
||||
return dyn
|
||||
|
||||
def line_number_range(self, frame):
|
||||
pair = self.tracer.line_number_range(frame)
|
||||
self.debug.write(f"line_number_range({self._show_frame(frame)}) --> {pair!r}")
|
||||
return pair
|
||||
|
||||
|
||||
class DebugFileReporterWrapper(FileReporter):
|
||||
"""A debugging `FileReporter`."""
|
||||
|
||||
def __init__(self, filename, reporter, debug):
|
||||
super().__init__(filename)
|
||||
self.reporter = reporter
|
||||
self.debug = debug
|
||||
|
||||
def relative_filename(self):
|
||||
ret = self.reporter.relative_filename()
|
||||
self.debug.write(f"relative_filename() --> {ret!r}")
|
||||
return ret
|
||||
|
||||
def lines(self):
|
||||
ret = self.reporter.lines()
|
||||
self.debug.write(f"lines() --> {ret!r}")
|
||||
return ret
|
||||
|
||||
def excluded_lines(self):
|
||||
ret = self.reporter.excluded_lines()
|
||||
self.debug.write(f"excluded_lines() --> {ret!r}")
|
||||
return ret
|
||||
|
||||
def translate_lines(self, lines):
|
||||
ret = self.reporter.translate_lines(lines)
|
||||
self.debug.write(f"translate_lines({lines!r}) --> {ret!r}")
|
||||
return ret
|
||||
|
||||
def translate_arcs(self, arcs):
|
||||
ret = self.reporter.translate_arcs(arcs)
|
||||
self.debug.write(f"translate_arcs({arcs!r}) --> {ret!r}")
|
||||
return ret
|
||||
|
||||
def no_branch_lines(self):
|
||||
ret = self.reporter.no_branch_lines()
|
||||
self.debug.write(f"no_branch_lines() --> {ret!r}")
|
||||
return ret
|
||||
|
||||
def exit_counts(self):
|
||||
ret = self.reporter.exit_counts()
|
||||
self.debug.write(f"exit_counts() --> {ret!r}")
|
||||
return ret
|
||||
|
||||
def arcs(self):
|
||||
ret = self.reporter.arcs()
|
||||
self.debug.write(f"arcs() --> {ret!r}")
|
||||
return ret
|
||||
|
||||
def source(self):
|
||||
ret = self.reporter.source()
|
||||
self.debug.write("source() --> %d chars" % (len(ret),))
|
||||
return ret
|
||||
|
||||
def source_token_lines(self):
|
||||
ret = list(self.reporter.source_token_lines())
|
||||
self.debug.write("source_token_lines() --> %d tokens" % (len(ret),))
|
||||
return ret
|
247
utils/python-venv/Lib/site-packages/coverage/python.py
Normal file
247
utils/python-venv/Lib/site-packages/coverage/python.py
Normal file
@ -0,0 +1,247 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Python source expertise for coverage.py"""
|
||||
|
||||
import os.path
|
||||
import types
|
||||
import zipimport
|
||||
|
||||
from coverage import env
|
||||
from coverage.exceptions import CoverageException, NoSource
|
||||
from coverage.files import canonical_filename, relative_filename
|
||||
from coverage.misc import contract, expensive, isolate_module, join_regex
|
||||
from coverage.parser import PythonParser
|
||||
from coverage.phystokens import source_token_lines, source_encoding
|
||||
from coverage.plugin import FileReporter
|
||||
|
||||
os = isolate_module(os)
|
||||
|
||||
|
||||
@contract(returns='bytes')
|
||||
def read_python_source(filename):
|
||||
"""Read the Python source text from `filename`.
|
||||
|
||||
Returns bytes.
|
||||
|
||||
"""
|
||||
with open(filename, "rb") as f:
|
||||
source = f.read()
|
||||
|
||||
if env.IRONPYTHON:
|
||||
# IronPython reads Unicode strings even for "rb" files.
|
||||
source = bytes(source)
|
||||
|
||||
return source.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
|
||||
|
||||
|
||||
@contract(returns='unicode')
|
||||
def get_python_source(filename):
|
||||
"""Return the source code, as unicode."""
|
||||
base, ext = os.path.splitext(filename)
|
||||
if ext == ".py" and env.WINDOWS:
|
||||
exts = [".py", ".pyw"]
|
||||
else:
|
||||
exts = [ext]
|
||||
|
||||
for ext in exts:
|
||||
try_filename = base + ext
|
||||
if os.path.exists(try_filename):
|
||||
# A regular text file: open it.
|
||||
source = read_python_source(try_filename)
|
||||
break
|
||||
|
||||
# Maybe it's in a zip file?
|
||||
source = get_zip_bytes(try_filename)
|
||||
if source is not None:
|
||||
break
|
||||
else:
|
||||
# Couldn't find source.
|
||||
raise NoSource(f"No source for code: '{filename}'.")
|
||||
|
||||
# Replace \f because of http://bugs.python.org/issue19035
|
||||
source = source.replace(b'\f', b' ')
|
||||
source = source.decode(source_encoding(source), "replace")
|
||||
|
||||
# Python code should always end with a line with a newline.
|
||||
if source and source[-1] != '\n':
|
||||
source += '\n'
|
||||
|
||||
return source
|
||||
|
||||
|
||||
@contract(returns='bytes|None')
|
||||
def get_zip_bytes(filename):
|
||||
"""Get data from `filename` if it is a zip file path.
|
||||
|
||||
Returns the bytestring data read from the zip file, or None if no zip file
|
||||
could be found or `filename` isn't in it. The data returned will be
|
||||
an empty string if the file is empty.
|
||||
|
||||
"""
|
||||
markers = ['.zip'+os.sep, '.egg'+os.sep, '.pex'+os.sep]
|
||||
for marker in markers:
|
||||
if marker in filename:
|
||||
parts = filename.split(marker)
|
||||
try:
|
||||
zi = zipimport.zipimporter(parts[0]+marker[:-1])
|
||||
except zipimport.ZipImportError:
|
||||
continue
|
||||
try:
|
||||
data = zi.get_data(parts[1])
|
||||
except OSError:
|
||||
continue
|
||||
return data
|
||||
return None
|
||||
|
||||
|
||||
def source_for_file(filename):
|
||||
"""Return the source filename for `filename`.
|
||||
|
||||
Given a file name being traced, return the best guess as to the source
|
||||
file to attribute it to.
|
||||
|
||||
"""
|
||||
if filename.endswith(".py"):
|
||||
# .py files are themselves source files.
|
||||
return filename
|
||||
|
||||
elif filename.endswith((".pyc", ".pyo")):
|
||||
# Bytecode files probably have source files near them.
|
||||
py_filename = filename[:-1]
|
||||
if os.path.exists(py_filename):
|
||||
# Found a .py file, use that.
|
||||
return py_filename
|
||||
if env.WINDOWS:
|
||||
# On Windows, it could be a .pyw file.
|
||||
pyw_filename = py_filename + "w"
|
||||
if os.path.exists(pyw_filename):
|
||||
return pyw_filename
|
||||
# Didn't find source, but it's probably the .py file we want.
|
||||
return py_filename
|
||||
|
||||
elif filename.endswith("$py.class"):
|
||||
# Jython is easy to guess.
|
||||
return filename[:-9] + ".py"
|
||||
|
||||
# No idea, just use the file name as-is.
|
||||
return filename
|
||||
|
||||
|
||||
def source_for_morf(morf):
|
||||
"""Get the source filename for the module-or-file `morf`."""
|
||||
if hasattr(morf, '__file__') and morf.__file__:
|
||||
filename = morf.__file__
|
||||
elif isinstance(morf, types.ModuleType):
|
||||
# A module should have had .__file__, otherwise we can't use it.
|
||||
# This could be a PEP-420 namespace package.
|
||||
raise CoverageException(f"Module {morf} has no file")
|
||||
else:
|
||||
filename = morf
|
||||
|
||||
filename = source_for_file(filename)
|
||||
return filename
|
||||
|
||||
|
||||
class PythonFileReporter(FileReporter):
|
||||
"""Report support for a Python file."""
|
||||
|
||||
def __init__(self, morf, coverage=None):
|
||||
self.coverage = coverage
|
||||
|
||||
filename = source_for_morf(morf)
|
||||
|
||||
super().__init__(canonical_filename(filename))
|
||||
|
||||
if hasattr(morf, '__name__'):
|
||||
name = morf.__name__.replace(".", os.sep)
|
||||
if os.path.basename(filename).startswith('__init__.'):
|
||||
name += os.sep + "__init__"
|
||||
name += ".py"
|
||||
else:
|
||||
name = relative_filename(filename)
|
||||
self.relname = name
|
||||
|
||||
self._source = None
|
||||
self._parser = None
|
||||
self._excluded = None
|
||||
|
||||
def __repr__(self):
|
||||
return f"<PythonFileReporter {self.filename!r}>"
|
||||
|
||||
@contract(returns='unicode')
|
||||
def relative_filename(self):
|
||||
return self.relname
|
||||
|
||||
@property
|
||||
def parser(self):
|
||||
"""Lazily create a :class:`PythonParser`."""
|
||||
if self._parser is None:
|
||||
self._parser = PythonParser(
|
||||
filename=self.filename,
|
||||
exclude=self.coverage._exclude_regex('exclude'),
|
||||
)
|
||||
self._parser.parse_source()
|
||||
return self._parser
|
||||
|
||||
def lines(self):
|
||||
"""Return the line numbers of statements in the file."""
|
||||
return self.parser.statements
|
||||
|
||||
def excluded_lines(self):
|
||||
"""Return the line numbers of statements in the file."""
|
||||
return self.parser.excluded
|
||||
|
||||
def translate_lines(self, lines):
|
||||
return self.parser.translate_lines(lines)
|
||||
|
||||
def translate_arcs(self, arcs):
|
||||
return self.parser.translate_arcs(arcs)
|
||||
|
||||
@expensive
|
||||
def no_branch_lines(self):
|
||||
no_branch = self.parser.lines_matching(
|
||||
join_regex(self.coverage.config.partial_list),
|
||||
join_regex(self.coverage.config.partial_always_list),
|
||||
)
|
||||
return no_branch
|
||||
|
||||
@expensive
|
||||
def arcs(self):
|
||||
return self.parser.arcs()
|
||||
|
||||
@expensive
|
||||
def exit_counts(self):
|
||||
return self.parser.exit_counts()
|
||||
|
||||
def missing_arc_description(self, start, end, executed_arcs=None):
|
||||
return self.parser.missing_arc_description(start, end, executed_arcs)
|
||||
|
||||
@contract(returns='unicode')
|
||||
def source(self):
|
||||
if self._source is None:
|
||||
self._source = get_python_source(self.filename)
|
||||
return self._source
|
||||
|
||||
def should_be_python(self):
|
||||
"""Does it seem like this file should contain Python?
|
||||
|
||||
This is used to decide if a file reported as part of the execution of
|
||||
a program was really likely to have contained Python in the first
|
||||
place.
|
||||
|
||||
"""
|
||||
# Get the file extension.
|
||||
_, ext = os.path.splitext(self.filename)
|
||||
|
||||
# Anything named *.py* should be Python.
|
||||
if ext.startswith('.py'):
|
||||
return True
|
||||
# A file with no extension should be Python.
|
||||
if not ext:
|
||||
return True
|
||||
# Everything else is probably not Python.
|
||||
return False
|
||||
|
||||
def source_token_lines(self):
|
||||
return source_token_lines(self.source())
|
306
utils/python-venv/Lib/site-packages/coverage/pytracer.py
Normal file
306
utils/python-venv/Lib/site-packages/coverage/pytracer.py
Normal file
@ -0,0 +1,306 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Raw data collector for coverage.py."""
|
||||
|
||||
import atexit
|
||||
import dis
|
||||
import sys
|
||||
|
||||
from coverage import env
|
||||
|
||||
# We need the YIELD_VALUE opcode below, in a comparison-friendly form.
|
||||
RESUME = dis.opmap.get('RESUME')
|
||||
RETURN_VALUE = dis.opmap['RETURN_VALUE']
|
||||
if RESUME is None:
|
||||
YIELD_VALUE = dis.opmap['YIELD_VALUE']
|
||||
YIELD_FROM = dis.opmap['YIELD_FROM']
|
||||
YIELD_FROM_OFFSET = 0 if env.PYPY else 2
|
||||
|
||||
# When running meta-coverage, this file can try to trace itself, which confuses
|
||||
# everything. Don't trace ourselves.
|
||||
|
||||
THIS_FILE = __file__.rstrip("co")
|
||||
|
||||
class PyTracer:
|
||||
"""Python implementation of the raw data tracer."""
|
||||
|
||||
# Because of poor implementations of trace-function-manipulating tools,
|
||||
# the Python trace function must be kept very simple. In particular, there
|
||||
# must be only one function ever set as the trace function, both through
|
||||
# sys.settrace, and as the return value from the trace function. Put
|
||||
# another way, the trace function must always return itself. It cannot
|
||||
# swap in other functions, or return None to avoid tracing a particular
|
||||
# frame.
|
||||
#
|
||||
# The trace manipulator that introduced this restriction is DecoratorTools,
|
||||
# which sets a trace function, and then later restores the pre-existing one
|
||||
# by calling sys.settrace with a function it found in the current frame.
|
||||
#
|
||||
# Systems that use DecoratorTools (or similar trace manipulations) must use
|
||||
# PyTracer to get accurate results. The command-line --timid argument is
|
||||
# used to force the use of this tracer.
|
||||
|
||||
def __init__(self):
|
||||
# Attributes set from the collector:
|
||||
self.data = None
|
||||
self.trace_arcs = False
|
||||
self.should_trace = None
|
||||
self.should_trace_cache = None
|
||||
self.should_start_context = None
|
||||
self.warn = None
|
||||
# The threading module to use, if any.
|
||||
self.threading = None
|
||||
|
||||
self.cur_file_data = None
|
||||
self.last_line = 0 # int, but uninitialized.
|
||||
self.cur_file_name = None
|
||||
self.context = None
|
||||
self.started_context = False
|
||||
|
||||
self.data_stack = []
|
||||
self.thread = None
|
||||
self.stopped = False
|
||||
self._activity = False
|
||||
|
||||
self.in_atexit = False
|
||||
# On exit, self.in_atexit = True
|
||||
atexit.register(setattr, self, 'in_atexit', True)
|
||||
|
||||
# Cache a bound method on the instance, so that we don't have to
|
||||
# re-create a bound method object all the time.
|
||||
self._cached_bound_method_trace = self._trace
|
||||
|
||||
def __repr__(self):
|
||||
return "<PyTracer at 0x{:x}: {} lines in {} files>".format(
|
||||
id(self),
|
||||
sum(len(v) for v in self.data.values()),
|
||||
len(self.data),
|
||||
)
|
||||
|
||||
def log(self, marker, *args):
|
||||
"""For hard-core logging of what this tracer is doing."""
|
||||
with open("/tmp/debug_trace.txt", "a") as f:
|
||||
f.write("{} {}[{}]".format(
|
||||
marker,
|
||||
id(self),
|
||||
len(self.data_stack),
|
||||
))
|
||||
if 0: # if you want thread ids..
|
||||
f.write(".{:x}.{:x}".format(
|
||||
self.thread.ident,
|
||||
self.threading.current_thread().ident,
|
||||
))
|
||||
f.write(" {}".format(" ".join(map(str, args))))
|
||||
if 0: # if you want callers..
|
||||
f.write(" | ")
|
||||
stack = " / ".join(
|
||||
(fname or "???").rpartition("/")[-1]
|
||||
for _, fname, _, _ in self.data_stack
|
||||
)
|
||||
f.write(stack)
|
||||
f.write("\n")
|
||||
|
||||
def _trace(self, frame, event, arg_unused):
|
||||
"""The trace function passed to sys.settrace."""
|
||||
|
||||
if THIS_FILE in frame.f_code.co_filename:
|
||||
return None
|
||||
|
||||
#self.log(":", frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + "()", event)
|
||||
|
||||
if (self.stopped and sys.gettrace() == self._cached_bound_method_trace): # pylint: disable=comparison-with-callable
|
||||
# The PyTrace.stop() method has been called, possibly by another
|
||||
# thread, let's deactivate ourselves now.
|
||||
if 0:
|
||||
self.log("---\nX", frame.f_code.co_filename, frame.f_lineno)
|
||||
f = frame
|
||||
while f:
|
||||
self.log(">", f.f_code.co_filename, f.f_lineno, f.f_code.co_name, f.f_trace)
|
||||
f = f.f_back
|
||||
sys.settrace(None)
|
||||
self.cur_file_data, self.cur_file_name, self.last_line, self.started_context = (
|
||||
self.data_stack.pop()
|
||||
)
|
||||
return None
|
||||
|
||||
# if event != 'call' and frame.f_code.co_filename != self.cur_file_name:
|
||||
# self.log("---\n*", frame.f_code.co_filename, self.cur_file_name, frame.f_lineno)
|
||||
|
||||
if event == 'call':
|
||||
# Should we start a new context?
|
||||
if self.should_start_context and self.context is None:
|
||||
context_maybe = self.should_start_context(frame)
|
||||
if context_maybe is not None:
|
||||
self.context = context_maybe
|
||||
started_context = True
|
||||
self.switch_context(self.context)
|
||||
else:
|
||||
started_context = False
|
||||
else:
|
||||
started_context = False
|
||||
self.started_context = started_context
|
||||
|
||||
# Entering a new frame. Decide if we should trace in this file.
|
||||
self._activity = True
|
||||
self.data_stack.append(
|
||||
(
|
||||
self.cur_file_data,
|
||||
self.cur_file_name,
|
||||
self.last_line,
|
||||
started_context,
|
||||
)
|
||||
)
|
||||
|
||||
# Improve tracing performance: when calling a function, both caller
|
||||
# and callee are often within the same file. if that's the case, we
|
||||
# don't have to re-check whether to trace the corresponding
|
||||
# function (which is a little bit espensive since it involves
|
||||
# dictionary lookups). This optimization is only correct if we
|
||||
# didn't start a context.
|
||||
filename = frame.f_code.co_filename
|
||||
if filename != self.cur_file_name or started_context:
|
||||
self.cur_file_name = filename
|
||||
disp = self.should_trace_cache.get(filename)
|
||||
if disp is None:
|
||||
disp = self.should_trace(filename, frame)
|
||||
self.should_trace_cache[filename] = disp
|
||||
|
||||
self.cur_file_data = None
|
||||
if disp.trace:
|
||||
tracename = disp.source_filename
|
||||
if tracename not in self.data:
|
||||
self.data[tracename] = set()
|
||||
self.cur_file_data = self.data[tracename]
|
||||
else:
|
||||
frame.f_trace_lines = False
|
||||
elif not self.cur_file_data:
|
||||
frame.f_trace_lines = False
|
||||
|
||||
# The call event is really a "start frame" event, and happens for
|
||||
# function calls and re-entering generators. The f_lasti field is
|
||||
# -1 for calls, and a real offset for generators. Use <0 as the
|
||||
# line number for calls, and the real line number for generators.
|
||||
if RESUME is not None:
|
||||
# The current opcode is guaranteed to be RESUME. The argument
|
||||
# determines what kind of resume it is.
|
||||
oparg = frame.f_code.co_code[frame.f_lasti + 1]
|
||||
real_call = (oparg == 0)
|
||||
else:
|
||||
real_call = (getattr(frame, 'f_lasti', -1) < 0)
|
||||
if real_call:
|
||||
self.last_line = -frame.f_code.co_firstlineno
|
||||
else:
|
||||
self.last_line = frame.f_lineno
|
||||
|
||||
elif event == 'line':
|
||||
# Record an executed line.
|
||||
if self.cur_file_data is not None:
|
||||
lineno = frame.f_lineno
|
||||
|
||||
if self.trace_arcs:
|
||||
self.cur_file_data.add((self.last_line, lineno))
|
||||
else:
|
||||
self.cur_file_data.add(lineno)
|
||||
self.last_line = lineno
|
||||
|
||||
elif event == 'return':
|
||||
if self.trace_arcs and self.cur_file_data:
|
||||
# Record an arc leaving the function, but beware that a
|
||||
# "return" event might just mean yielding from a generator.
|
||||
code = frame.f_code.co_code
|
||||
lasti = frame.f_lasti
|
||||
if RESUME is not None:
|
||||
if len(code) == lasti + 2:
|
||||
# A return from the end of a code object is a real return.
|
||||
real_return = True
|
||||
else:
|
||||
# it's a real return.
|
||||
real_return = (code[lasti + 2] != RESUME)
|
||||
else:
|
||||
if code[lasti] == RETURN_VALUE:
|
||||
real_return = True
|
||||
elif code[lasti] == YIELD_VALUE:
|
||||
real_return = False
|
||||
elif len(code) <= lasti + YIELD_FROM_OFFSET:
|
||||
real_return = True
|
||||
elif code[lasti + YIELD_FROM_OFFSET] == YIELD_FROM:
|
||||
real_return = False
|
||||
else:
|
||||
real_return = True
|
||||
if real_return:
|
||||
first = frame.f_code.co_firstlineno
|
||||
self.cur_file_data.add((self.last_line, -first))
|
||||
|
||||
# Leaving this function, pop the filename stack.
|
||||
self.cur_file_data, self.cur_file_name, self.last_line, self.started_context = (
|
||||
self.data_stack.pop()
|
||||
)
|
||||
# Leaving a context?
|
||||
if self.started_context:
|
||||
self.context = None
|
||||
self.switch_context(None)
|
||||
return self._cached_bound_method_trace
|
||||
|
||||
def start(self):
|
||||
"""Start this Tracer.
|
||||
|
||||
Return a Python function suitable for use with sys.settrace().
|
||||
|
||||
"""
|
||||
self.stopped = False
|
||||
if self.threading:
|
||||
if self.thread is None:
|
||||
self.thread = self.threading.current_thread()
|
||||
else:
|
||||
if self.thread.ident != self.threading.current_thread().ident:
|
||||
# Re-starting from a different thread!? Don't set the trace
|
||||
# function, but we are marked as running again, so maybe it
|
||||
# will be ok?
|
||||
#self.log("~", "starting on different threads")
|
||||
return self._cached_bound_method_trace
|
||||
|
||||
sys.settrace(self._cached_bound_method_trace)
|
||||
return self._cached_bound_method_trace
|
||||
|
||||
def stop(self):
|
||||
"""Stop this Tracer."""
|
||||
# Get the active tracer callback before setting the stop flag to be
|
||||
# able to detect if the tracer was changed prior to stopping it.
|
||||
tf = sys.gettrace()
|
||||
|
||||
# Set the stop flag. The actual call to sys.settrace(None) will happen
|
||||
# in the self._trace callback itself to make sure to call it from the
|
||||
# right thread.
|
||||
self.stopped = True
|
||||
|
||||
if self.threading and self.thread.ident != self.threading.current_thread().ident:
|
||||
# Called on a different thread than started us: we can't unhook
|
||||
# ourselves, but we've set the flag that we should stop, so we
|
||||
# won't do any more tracing.
|
||||
#self.log("~", "stopping on different threads")
|
||||
return
|
||||
|
||||
if self.warn:
|
||||
# PyPy clears the trace function before running atexit functions,
|
||||
# so don't warn if we are in atexit on PyPy and the trace function
|
||||
# has changed to None.
|
||||
dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None)
|
||||
if (not dont_warn) and tf != self._cached_bound_method_trace: # pylint: disable=comparison-with-callable
|
||||
self.warn(
|
||||
"Trace function changed, data is likely wrong: " +
|
||||
f"{tf!r} != {self._cached_bound_method_trace!r}",
|
||||
slug="trace-changed",
|
||||
)
|
||||
|
||||
def activity(self):
|
||||
"""Has there been any activity?"""
|
||||
return self._activity
|
||||
|
||||
def reset_activity(self):
|
||||
"""Reset the activity() flag."""
|
||||
self._activity = False
|
||||
|
||||
def get_stats(self):
|
||||
"""Return a dictionary of statistics, or None."""
|
||||
return None
|
91
utils/python-venv/Lib/site-packages/coverage/report.py
Normal file
91
utils/python-venv/Lib/site-packages/coverage/report.py
Normal file
@ -0,0 +1,91 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Reporter foundation for coverage.py."""
|
||||
|
||||
import sys
|
||||
|
||||
from coverage.exceptions import CoverageException, NoDataError, NotPython
|
||||
from coverage.files import prep_patterns, FnmatchMatcher
|
||||
from coverage.misc import ensure_dir_for_file, file_be_gone
|
||||
|
||||
|
||||
def render_report(output_path, reporter, morfs, msgfn):
|
||||
"""Run a one-file report generator, managing the output file.
|
||||
|
||||
This function ensures the output file is ready to be written to. Then writes
|
||||
the report to it. Then closes the file and cleans up.
|
||||
|
||||
"""
|
||||
file_to_close = None
|
||||
delete_file = False
|
||||
|
||||
if output_path == "-":
|
||||
outfile = sys.stdout
|
||||
else:
|
||||
# Ensure that the output directory is created; done here
|
||||
# because this report pre-opens the output file.
|
||||
# HTMLReport does this using the Report plumbing because
|
||||
# its task is more complex, being multiple files.
|
||||
ensure_dir_for_file(output_path)
|
||||
outfile = open(output_path, "w", encoding="utf-8")
|
||||
file_to_close = outfile
|
||||
|
||||
try:
|
||||
return reporter.report(morfs, outfile=outfile)
|
||||
except CoverageException:
|
||||
delete_file = True
|
||||
raise
|
||||
finally:
|
||||
if file_to_close:
|
||||
file_to_close.close()
|
||||
if delete_file:
|
||||
file_be_gone(output_path) # pragma: part covered (doesn't return)
|
||||
else:
|
||||
msgfn(f"Wrote {reporter.report_type} to {output_path}")
|
||||
|
||||
|
||||
def get_analysis_to_report(coverage, morfs):
|
||||
"""Get the files to report on.
|
||||
|
||||
For each morf in `morfs`, if it should be reported on (based on the omit
|
||||
and include configuration options), yield a pair, the `FileReporter` and
|
||||
`Analysis` for the morf.
|
||||
|
||||
"""
|
||||
file_reporters = coverage._get_file_reporters(morfs)
|
||||
config = coverage.config
|
||||
|
||||
if config.report_include:
|
||||
matcher = FnmatchMatcher(prep_patterns(config.report_include), "report_include")
|
||||
file_reporters = [fr for fr in file_reporters if matcher.match(fr.filename)]
|
||||
|
||||
if config.report_omit:
|
||||
matcher = FnmatchMatcher(prep_patterns(config.report_omit), "report_omit")
|
||||
file_reporters = [fr for fr in file_reporters if not matcher.match(fr.filename)]
|
||||
|
||||
if not file_reporters:
|
||||
raise NoDataError("No data to report.")
|
||||
|
||||
for fr in sorted(file_reporters):
|
||||
try:
|
||||
analysis = coverage._analyze(fr)
|
||||
except NotPython:
|
||||
# Only report errors for .py files, and only if we didn't
|
||||
# explicitly suppress those errors.
|
||||
# NotPython is only raised by PythonFileReporter, which has a
|
||||
# should_be_python() method.
|
||||
if fr.should_be_python():
|
||||
if config.ignore_errors:
|
||||
msg = f"Couldn't parse Python file '{fr.filename}'"
|
||||
coverage._warn(msg, slug="couldnt-parse")
|
||||
else:
|
||||
raise
|
||||
except Exception as exc:
|
||||
if config.ignore_errors:
|
||||
msg = f"Couldn't parse '{fr.filename}': {exc}".rstrip()
|
||||
coverage._warn(msg, slug="couldnt-parse")
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
yield (fr, analysis)
|
361
utils/python-venv/Lib/site-packages/coverage/results.py
Normal file
361
utils/python-venv/Lib/site-packages/coverage/results.py
Normal file
@ -0,0 +1,361 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Results of coverage measurement."""
|
||||
|
||||
import collections
|
||||
|
||||
from coverage.debug import SimpleReprMixin
|
||||
from coverage.exceptions import ConfigError
|
||||
from coverage.misc import contract, nice_pair
|
||||
|
||||
|
||||
class Analysis:
|
||||
"""The results of analyzing a FileReporter."""
|
||||
|
||||
def __init__(self, data, precision, file_reporter, file_mapper):
|
||||
self.data = data
|
||||
self.file_reporter = file_reporter
|
||||
self.filename = file_mapper(self.file_reporter.filename)
|
||||
self.statements = self.file_reporter.lines()
|
||||
self.excluded = self.file_reporter.excluded_lines()
|
||||
|
||||
# Identify missing statements.
|
||||
executed = self.data.lines(self.filename) or []
|
||||
executed = self.file_reporter.translate_lines(executed)
|
||||
self.executed = executed
|
||||
self.missing = self.statements - self.executed
|
||||
|
||||
if self.data.has_arcs():
|
||||
self._arc_possibilities = sorted(self.file_reporter.arcs())
|
||||
self.exit_counts = self.file_reporter.exit_counts()
|
||||
self.no_branch = self.file_reporter.no_branch_lines()
|
||||
n_branches = self._total_branches()
|
||||
mba = self.missing_branch_arcs()
|
||||
n_partial_branches = sum(len(v) for k,v in mba.items() if k not in self.missing)
|
||||
n_missing_branches = sum(len(v) for k,v in mba.items())
|
||||
else:
|
||||
self._arc_possibilities = []
|
||||
self.exit_counts = {}
|
||||
self.no_branch = set()
|
||||
n_branches = n_partial_branches = n_missing_branches = 0
|
||||
|
||||
self.numbers = Numbers(
|
||||
precision=precision,
|
||||
n_files=1,
|
||||
n_statements=len(self.statements),
|
||||
n_excluded=len(self.excluded),
|
||||
n_missing=len(self.missing),
|
||||
n_branches=n_branches,
|
||||
n_partial_branches=n_partial_branches,
|
||||
n_missing_branches=n_missing_branches,
|
||||
)
|
||||
|
||||
def missing_formatted(self, branches=False):
|
||||
"""The missing line numbers, formatted nicely.
|
||||
|
||||
Returns a string like "1-2, 5-11, 13-14".
|
||||
|
||||
If `branches` is true, includes the missing branch arcs also.
|
||||
|
||||
"""
|
||||
if branches and self.has_arcs():
|
||||
arcs = self.missing_branch_arcs().items()
|
||||
else:
|
||||
arcs = None
|
||||
|
||||
return format_lines(self.statements, self.missing, arcs=arcs)
|
||||
|
||||
def has_arcs(self):
|
||||
"""Were arcs measured in this result?"""
|
||||
return self.data.has_arcs()
|
||||
|
||||
@contract(returns='list(tuple(int, int))')
|
||||
def arc_possibilities(self):
|
||||
"""Returns a sorted list of the arcs in the code."""
|
||||
return self._arc_possibilities
|
||||
|
||||
@contract(returns='list(tuple(int, int))')
|
||||
def arcs_executed(self):
|
||||
"""Returns a sorted list of the arcs actually executed in the code."""
|
||||
executed = self.data.arcs(self.filename) or []
|
||||
executed = self.file_reporter.translate_arcs(executed)
|
||||
return sorted(executed)
|
||||
|
||||
@contract(returns='list(tuple(int, int))')
|
||||
def arcs_missing(self):
|
||||
"""Returns a sorted list of the unexecuted arcs in the code."""
|
||||
possible = self.arc_possibilities()
|
||||
executed = self.arcs_executed()
|
||||
missing = (
|
||||
p for p in possible
|
||||
if p not in executed
|
||||
and p[0] not in self.no_branch
|
||||
and p[1] not in self.excluded
|
||||
)
|
||||
return sorted(missing)
|
||||
|
||||
@contract(returns='list(tuple(int, int))')
|
||||
def arcs_unpredicted(self):
|
||||
"""Returns a sorted list of the executed arcs missing from the code."""
|
||||
possible = self.arc_possibilities()
|
||||
executed = self.arcs_executed()
|
||||
# Exclude arcs here which connect a line to itself. They can occur
|
||||
# in executed data in some cases. This is where they can cause
|
||||
# trouble, and here is where it's the least burden to remove them.
|
||||
# Also, generators can somehow cause arcs from "enter" to "exit", so
|
||||
# make sure we have at least one positive value.
|
||||
unpredicted = (
|
||||
e for e in executed
|
||||
if e not in possible
|
||||
and e[0] != e[1]
|
||||
and (e[0] > 0 or e[1] > 0)
|
||||
)
|
||||
return sorted(unpredicted)
|
||||
|
||||
def _branch_lines(self):
|
||||
"""Returns a list of line numbers that have more than one exit."""
|
||||
return [l1 for l1,count in self.exit_counts.items() if count > 1]
|
||||
|
||||
def _total_branches(self):
|
||||
"""How many total branches are there?"""
|
||||
return sum(count for count in self.exit_counts.values() if count > 1)
|
||||
|
||||
@contract(returns='dict(int: list(int))')
|
||||
def missing_branch_arcs(self):
|
||||
"""Return arcs that weren't executed from branch lines.
|
||||
|
||||
Returns {l1:[l2a,l2b,...], ...}
|
||||
|
||||
"""
|
||||
missing = self.arcs_missing()
|
||||
branch_lines = set(self._branch_lines())
|
||||
mba = collections.defaultdict(list)
|
||||
for l1, l2 in missing:
|
||||
if l1 in branch_lines:
|
||||
mba[l1].append(l2)
|
||||
return mba
|
||||
|
||||
@contract(returns='dict(int: list(int))')
|
||||
def executed_branch_arcs(self):
|
||||
"""Return arcs that were executed from branch lines.
|
||||
|
||||
Returns {l1:[l2a,l2b,...], ...}
|
||||
|
||||
"""
|
||||
executed = self.arcs_executed()
|
||||
branch_lines = set(self._branch_lines())
|
||||
eba = collections.defaultdict(list)
|
||||
for l1, l2 in executed:
|
||||
if l1 in branch_lines:
|
||||
eba[l1].append(l2)
|
||||
return eba
|
||||
|
||||
@contract(returns='dict(int: tuple(int, int))')
|
||||
def branch_stats(self):
|
||||
"""Get stats about branches.
|
||||
|
||||
Returns a dict mapping line numbers to a tuple:
|
||||
(total_exits, taken_exits).
|
||||
"""
|
||||
|
||||
missing_arcs = self.missing_branch_arcs()
|
||||
stats = {}
|
||||
for lnum in self._branch_lines():
|
||||
exits = self.exit_counts[lnum]
|
||||
missing = len(missing_arcs[lnum])
|
||||
stats[lnum] = (exits, exits - missing)
|
||||
return stats
|
||||
|
||||
|
||||
class Numbers(SimpleReprMixin):
|
||||
"""The numerical results of measuring coverage.
|
||||
|
||||
This holds the basic statistics from `Analysis`, and is used to roll
|
||||
up statistics across files.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
precision=0,
|
||||
n_files=0, n_statements=0, n_excluded=0, n_missing=0,
|
||||
n_branches=0, n_partial_branches=0, n_missing_branches=0
|
||||
):
|
||||
assert 0 <= precision < 10
|
||||
self._precision = precision
|
||||
self._near0 = 1.0 / 10**precision
|
||||
self._near100 = 100.0 - self._near0
|
||||
self.n_files = n_files
|
||||
self.n_statements = n_statements
|
||||
self.n_excluded = n_excluded
|
||||
self.n_missing = n_missing
|
||||
self.n_branches = n_branches
|
||||
self.n_partial_branches = n_partial_branches
|
||||
self.n_missing_branches = n_missing_branches
|
||||
|
||||
def init_args(self):
|
||||
"""Return a list for __init__(*args) to recreate this object."""
|
||||
return [
|
||||
self._precision,
|
||||
self.n_files, self.n_statements, self.n_excluded, self.n_missing,
|
||||
self.n_branches, self.n_partial_branches, self.n_missing_branches,
|
||||
]
|
||||
|
||||
@property
|
||||
def n_executed(self):
|
||||
"""Returns the number of executed statements."""
|
||||
return self.n_statements - self.n_missing
|
||||
|
||||
@property
|
||||
def n_executed_branches(self):
|
||||
"""Returns the number of executed branches."""
|
||||
return self.n_branches - self.n_missing_branches
|
||||
|
||||
@property
|
||||
def pc_covered(self):
|
||||
"""Returns a single percentage value for coverage."""
|
||||
if self.n_statements > 0:
|
||||
numerator, denominator = self.ratio_covered
|
||||
pc_cov = (100.0 * numerator) / denominator
|
||||
else:
|
||||
pc_cov = 100.0
|
||||
return pc_cov
|
||||
|
||||
@property
|
||||
def pc_covered_str(self):
|
||||
"""Returns the percent covered, as a string, without a percent sign.
|
||||
|
||||
Note that "0" is only returned when the value is truly zero, and "100"
|
||||
is only returned when the value is truly 100. Rounding can never
|
||||
result in either "0" or "100".
|
||||
|
||||
"""
|
||||
return self.display_covered(self.pc_covered)
|
||||
|
||||
def display_covered(self, pc):
|
||||
"""Return a displayable total percentage, as a string.
|
||||
|
||||
Note that "0" is only returned when the value is truly zero, and "100"
|
||||
is only returned when the value is truly 100. Rounding can never
|
||||
result in either "0" or "100".
|
||||
|
||||
"""
|
||||
if 0 < pc < self._near0:
|
||||
pc = self._near0
|
||||
elif self._near100 < pc < 100:
|
||||
pc = self._near100
|
||||
else:
|
||||
pc = round(pc, self._precision)
|
||||
return "%.*f" % (self._precision, pc)
|
||||
|
||||
def pc_str_width(self):
|
||||
"""How many characters wide can pc_covered_str be?"""
|
||||
width = 3 # "100"
|
||||
if self._precision > 0:
|
||||
width += 1 + self._precision
|
||||
return width
|
||||
|
||||
@property
|
||||
def ratio_covered(self):
|
||||
"""Return a numerator and denominator for the coverage ratio."""
|
||||
numerator = self.n_executed + self.n_executed_branches
|
||||
denominator = self.n_statements + self.n_branches
|
||||
return numerator, denominator
|
||||
|
||||
def __add__(self, other):
|
||||
nums = Numbers(precision=self._precision)
|
||||
nums.n_files = self.n_files + other.n_files
|
||||
nums.n_statements = self.n_statements + other.n_statements
|
||||
nums.n_excluded = self.n_excluded + other.n_excluded
|
||||
nums.n_missing = self.n_missing + other.n_missing
|
||||
nums.n_branches = self.n_branches + other.n_branches
|
||||
nums.n_partial_branches = (
|
||||
self.n_partial_branches + other.n_partial_branches
|
||||
)
|
||||
nums.n_missing_branches = (
|
||||
self.n_missing_branches + other.n_missing_branches
|
||||
)
|
||||
return nums
|
||||
|
||||
def __radd__(self, other):
|
||||
# Implementing 0+Numbers allows us to sum() a list of Numbers.
|
||||
assert other == 0 # we only ever call it this way.
|
||||
return self
|
||||
|
||||
|
||||
def _line_ranges(statements, lines):
|
||||
"""Produce a list of ranges for `format_lines`."""
|
||||
statements = sorted(statements)
|
||||
lines = sorted(lines)
|
||||
|
||||
pairs = []
|
||||
start = None
|
||||
lidx = 0
|
||||
for stmt in statements:
|
||||
if lidx >= len(lines):
|
||||
break
|
||||
if stmt == lines[lidx]:
|
||||
lidx += 1
|
||||
if not start:
|
||||
start = stmt
|
||||
end = stmt
|
||||
elif start:
|
||||
pairs.append((start, end))
|
||||
start = None
|
||||
if start:
|
||||
pairs.append((start, end))
|
||||
return pairs
|
||||
|
||||
|
||||
def format_lines(statements, lines, arcs=None):
|
||||
"""Nicely format a list of line numbers.
|
||||
|
||||
Format a list of line numbers for printing by coalescing groups of lines as
|
||||
long as the lines represent consecutive statements. This will coalesce
|
||||
even if there are gaps between statements.
|
||||
|
||||
For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and
|
||||
`lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14".
|
||||
|
||||
Both `lines` and `statements` can be any iterable. All of the elements of
|
||||
`lines` must be in `statements`, and all of the values must be positive
|
||||
integers.
|
||||
|
||||
If `arcs` is provided, they are (start,[end,end,end]) pairs that will be
|
||||
included in the output as long as start isn't in `lines`.
|
||||
|
||||
"""
|
||||
line_items = [(pair[0], nice_pair(pair)) for pair in _line_ranges(statements, lines)]
|
||||
if arcs:
|
||||
line_exits = sorted(arcs)
|
||||
for line, exits in line_exits:
|
||||
for ex in sorted(exits):
|
||||
if line not in lines and ex not in lines:
|
||||
dest = (ex if ex > 0 else "exit")
|
||||
line_items.append((line, f"{line}->{dest}"))
|
||||
|
||||
ret = ', '.join(t[-1] for t in sorted(line_items))
|
||||
return ret
|
||||
|
||||
|
||||
@contract(total='number', fail_under='number', precision=int, returns=bool)
|
||||
def should_fail_under(total, fail_under, precision):
|
||||
"""Determine if a total should fail due to fail-under.
|
||||
|
||||
`total` is a float, the coverage measurement total. `fail_under` is the
|
||||
fail_under setting to compare with. `precision` is the number of digits
|
||||
to consider after the decimal point.
|
||||
|
||||
Returns True if the total should fail.
|
||||
|
||||
"""
|
||||
# We can never achieve higher than 100% coverage, or less than zero.
|
||||
if not (0 <= fail_under <= 100.0):
|
||||
msg = f"fail_under={fail_under} is invalid. Must be between 0 and 100."
|
||||
raise ConfigError(msg)
|
||||
|
||||
# Special case for fail_under=100, it must really be 100.
|
||||
if fail_under == 100.0 and total != 100.0:
|
||||
return True
|
||||
|
||||
return round(total, precision) < fail_under
|
1182
utils/python-venv/Lib/site-packages/coverage/sqldata.py
Normal file
1182
utils/python-venv/Lib/site-packages/coverage/sqldata.py
Normal file
File diff suppressed because it is too large
Load Diff
152
utils/python-venv/Lib/site-packages/coverage/summary.py
Normal file
152
utils/python-venv/Lib/site-packages/coverage/summary.py
Normal file
@ -0,0 +1,152 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Summary reporting"""
|
||||
|
||||
import sys
|
||||
|
||||
from coverage.exceptions import ConfigError, NoDataError
|
||||
from coverage.misc import human_sorted_items
|
||||
from coverage.report import get_analysis_to_report
|
||||
from coverage.results import Numbers
|
||||
|
||||
|
||||
class SummaryReporter:
|
||||
"""A reporter for writing the summary report."""
|
||||
|
||||
def __init__(self, coverage):
|
||||
self.coverage = coverage
|
||||
self.config = self.coverage.config
|
||||
self.branches = coverage.get_data().has_arcs()
|
||||
self.outfile = None
|
||||
self.fr_analysis = []
|
||||
self.skipped_count = 0
|
||||
self.empty_count = 0
|
||||
self.total = Numbers(precision=self.config.precision)
|
||||
self.fmt_err = "%s %s: %s"
|
||||
|
||||
def writeout(self, line):
|
||||
"""Write a line to the output, adding a newline."""
|
||||
self.outfile.write(line.rstrip())
|
||||
self.outfile.write("\n")
|
||||
|
||||
def report(self, morfs, outfile=None):
|
||||
"""Writes a report summarizing coverage statistics per module.
|
||||
|
||||
`outfile` is a file object to write the summary to. It must be opened
|
||||
for native strings (bytes on Python 2, Unicode on Python 3).
|
||||
|
||||
"""
|
||||
self.outfile = outfile or sys.stdout
|
||||
|
||||
self.coverage.get_data().set_query_contexts(self.config.report_contexts)
|
||||
for fr, analysis in get_analysis_to_report(self.coverage, morfs):
|
||||
self.report_one_file(fr, analysis)
|
||||
|
||||
# Prepare the formatting strings, header, and column sorting.
|
||||
max_name = max([len(fr.relative_filename()) for (fr, analysis) in self.fr_analysis] + [5])
|
||||
fmt_name = "%%- %ds " % max_name
|
||||
fmt_skip_covered = "\n%s file%s skipped due to complete coverage."
|
||||
fmt_skip_empty = "\n%s empty file%s skipped."
|
||||
|
||||
header = (fmt_name % "Name") + " Stmts Miss"
|
||||
fmt_coverage = fmt_name + "%6d %6d"
|
||||
if self.branches:
|
||||
header += " Branch BrPart"
|
||||
fmt_coverage += " %6d %6d"
|
||||
width100 = Numbers(precision=self.config.precision).pc_str_width()
|
||||
header += "%*s" % (width100+4, "Cover")
|
||||
fmt_coverage += "%%%ds%%%%" % (width100+3,)
|
||||
if self.config.show_missing:
|
||||
header += " Missing"
|
||||
fmt_coverage += " %s"
|
||||
rule = "-" * len(header)
|
||||
|
||||
column_order = dict(name=0, stmts=1, miss=2, cover=-1)
|
||||
if self.branches:
|
||||
column_order.update(dict(branch=3, brpart=4))
|
||||
|
||||
# Write the header
|
||||
self.writeout(header)
|
||||
self.writeout(rule)
|
||||
|
||||
# `lines` is a list of pairs, (line text, line values). The line text
|
||||
# is a string that will be printed, and line values is a tuple of
|
||||
# sortable values.
|
||||
lines = []
|
||||
|
||||
for (fr, analysis) in self.fr_analysis:
|
||||
nums = analysis.numbers
|
||||
|
||||
args = (fr.relative_filename(), nums.n_statements, nums.n_missing)
|
||||
if self.branches:
|
||||
args += (nums.n_branches, nums.n_partial_branches)
|
||||
args += (nums.pc_covered_str,)
|
||||
if self.config.show_missing:
|
||||
args += (analysis.missing_formatted(branches=True),)
|
||||
text = fmt_coverage % args
|
||||
# Add numeric percent coverage so that sorting makes sense.
|
||||
args += (nums.pc_covered,)
|
||||
lines.append((text, args))
|
||||
|
||||
# Sort the lines and write them out.
|
||||
sort_option = (self.config.sort or "name").lower()
|
||||
reverse = False
|
||||
if sort_option[0] == '-':
|
||||
reverse = True
|
||||
sort_option = sort_option[1:]
|
||||
elif sort_option[0] == '+':
|
||||
sort_option = sort_option[1:]
|
||||
|
||||
if sort_option == "name":
|
||||
lines = human_sorted_items(lines, reverse=reverse)
|
||||
else:
|
||||
position = column_order.get(sort_option)
|
||||
if position is None:
|
||||
raise ConfigError(f"Invalid sorting option: {self.config.sort!r}")
|
||||
lines.sort(key=lambda l: (l[1][position], l[0]), reverse=reverse)
|
||||
|
||||
for line in lines:
|
||||
self.writeout(line[0])
|
||||
|
||||
# Write a TOTAL line if we had at least one file.
|
||||
if self.total.n_files > 0:
|
||||
self.writeout(rule)
|
||||
args = ("TOTAL", self.total.n_statements, self.total.n_missing)
|
||||
if self.branches:
|
||||
args += (self.total.n_branches, self.total.n_partial_branches)
|
||||
args += (self.total.pc_covered_str,)
|
||||
if self.config.show_missing:
|
||||
args += ("",)
|
||||
self.writeout(fmt_coverage % args)
|
||||
|
||||
# Write other final lines.
|
||||
if not self.total.n_files and not self.skipped_count:
|
||||
raise NoDataError("No data to report.")
|
||||
|
||||
if self.config.skip_covered and self.skipped_count:
|
||||
self.writeout(
|
||||
fmt_skip_covered % (self.skipped_count, 's' if self.skipped_count > 1 else '')
|
||||
)
|
||||
if self.config.skip_empty and self.empty_count:
|
||||
self.writeout(
|
||||
fmt_skip_empty % (self.empty_count, 's' if self.empty_count > 1 else '')
|
||||
)
|
||||
|
||||
return self.total.n_statements and self.total.pc_covered
|
||||
|
||||
def report_one_file(self, fr, analysis):
|
||||
"""Report on just one file, the callback from report()."""
|
||||
nums = analysis.numbers
|
||||
self.total += nums
|
||||
|
||||
no_missing_lines = (nums.n_missing == 0)
|
||||
no_missing_branches = (nums.n_partial_branches == 0)
|
||||
if self.config.skip_covered and no_missing_lines and no_missing_branches:
|
||||
# Don't report on 100% files.
|
||||
self.skipped_count += 1
|
||||
elif self.config.skip_empty and nums.n_statements == 0:
|
||||
# Don't report on empty files.
|
||||
self.empty_count += 1
|
||||
else:
|
||||
self.fr_analysis.append((fr, analysis))
|
297
utils/python-venv/Lib/site-packages/coverage/templite.py
Normal file
297
utils/python-venv/Lib/site-packages/coverage/templite.py
Normal file
@ -0,0 +1,297 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""A simple Python template renderer, for a nano-subset of Django syntax.
|
||||
|
||||
For a detailed discussion of this code, see this chapter from 500 Lines:
|
||||
http://aosabook.org/en/500L/a-template-engine.html
|
||||
|
||||
"""
|
||||
|
||||
# Coincidentally named the same as http://code.activestate.com/recipes/496702/
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class TempliteSyntaxError(ValueError):
|
||||
"""Raised when a template has a syntax error."""
|
||||
pass
|
||||
|
||||
|
||||
class TempliteValueError(ValueError):
|
||||
"""Raised when an expression won't evaluate in a template."""
|
||||
pass
|
||||
|
||||
|
||||
class CodeBuilder:
|
||||
"""Build source code conveniently."""
|
||||
|
||||
def __init__(self, indent=0):
|
||||
self.code = []
|
||||
self.indent_level = indent
|
||||
|
||||
def __str__(self):
|
||||
return "".join(str(c) for c in self.code)
|
||||
|
||||
def add_line(self, line):
|
||||
"""Add a line of source to the code.
|
||||
|
||||
Indentation and newline will be added for you, don't provide them.
|
||||
|
||||
"""
|
||||
self.code.extend([" " * self.indent_level, line, "\n"])
|
||||
|
||||
def add_section(self):
|
||||
"""Add a section, a sub-CodeBuilder."""
|
||||
section = CodeBuilder(self.indent_level)
|
||||
self.code.append(section)
|
||||
return section
|
||||
|
||||
INDENT_STEP = 4 # PEP8 says so!
|
||||
|
||||
def indent(self):
|
||||
"""Increase the current indent for following lines."""
|
||||
self.indent_level += self.INDENT_STEP
|
||||
|
||||
def dedent(self):
|
||||
"""Decrease the current indent for following lines."""
|
||||
self.indent_level -= self.INDENT_STEP
|
||||
|
||||
def get_globals(self):
|
||||
"""Execute the code, and return a dict of globals it defines."""
|
||||
# A check that the caller really finished all the blocks they started.
|
||||
assert self.indent_level == 0
|
||||
# Get the Python source as a single string.
|
||||
python_source = str(self)
|
||||
# Execute the source, defining globals, and return them.
|
||||
global_namespace = {}
|
||||
exec(python_source, global_namespace)
|
||||
return global_namespace
|
||||
|
||||
|
||||
class Templite:
|
||||
"""A simple template renderer, for a nano-subset of Django syntax.
|
||||
|
||||
Supported constructs are extended variable access::
|
||||
|
||||
{{var.modifier.modifier|filter|filter}}
|
||||
|
||||
loops::
|
||||
|
||||
{% for var in list %}...{% endfor %}
|
||||
|
||||
and ifs::
|
||||
|
||||
{% if var %}...{% endif %}
|
||||
|
||||
Comments are within curly-hash markers::
|
||||
|
||||
{# This will be ignored #}
|
||||
|
||||
Lines between `{% joined %}` and `{% endjoined %}` will have lines stripped
|
||||
and joined. Be careful, this could join words together!
|
||||
|
||||
Any of these constructs can have a hyphen at the end (`-}}`, `-%}`, `-#}`),
|
||||
which will collapse the whitespace following the tag.
|
||||
|
||||
Construct a Templite with the template text, then use `render` against a
|
||||
dictionary context to create a finished string::
|
||||
|
||||
templite = Templite('''
|
||||
<h1>Hello {{name|upper}}!</h1>
|
||||
{% for topic in topics %}
|
||||
<p>You are interested in {{topic}}.</p>
|
||||
{% endif %}
|
||||
''',
|
||||
{'upper': str.upper},
|
||||
)
|
||||
text = templite.render({
|
||||
'name': "Ned",
|
||||
'topics': ['Python', 'Geometry', 'Juggling'],
|
||||
})
|
||||
|
||||
"""
|
||||
def __init__(self, text, *contexts):
|
||||
"""Construct a Templite with the given `text`.
|
||||
|
||||
`contexts` are dictionaries of values to use for future renderings.
|
||||
These are good for filters and global values.
|
||||
|
||||
"""
|
||||
self.context = {}
|
||||
for context in contexts:
|
||||
self.context.update(context)
|
||||
|
||||
self.all_vars = set()
|
||||
self.loop_vars = set()
|
||||
|
||||
# We construct a function in source form, then compile it and hold onto
|
||||
# it, and execute it to render the template.
|
||||
code = CodeBuilder()
|
||||
|
||||
code.add_line("def render_function(context, do_dots):")
|
||||
code.indent()
|
||||
vars_code = code.add_section()
|
||||
code.add_line("result = []")
|
||||
code.add_line("append_result = result.append")
|
||||
code.add_line("extend_result = result.extend")
|
||||
code.add_line("to_str = str")
|
||||
|
||||
buffered = []
|
||||
|
||||
def flush_output():
|
||||
"""Force `buffered` to the code builder."""
|
||||
if len(buffered) == 1:
|
||||
code.add_line("append_result(%s)" % buffered[0])
|
||||
elif len(buffered) > 1:
|
||||
code.add_line("extend_result([%s])" % ", ".join(buffered))
|
||||
del buffered[:]
|
||||
|
||||
ops_stack = []
|
||||
|
||||
# Split the text to form a list of tokens.
|
||||
tokens = re.split(r"(?s)({{.*?}}|{%.*?%}|{#.*?#})", text)
|
||||
|
||||
squash = in_joined = False
|
||||
|
||||
for token in tokens:
|
||||
if token.startswith('{'):
|
||||
start, end = 2, -2
|
||||
squash = (token[-3] == '-')
|
||||
if squash:
|
||||
end = -3
|
||||
|
||||
if token.startswith('{#'):
|
||||
# Comment: ignore it and move on.
|
||||
continue
|
||||
elif token.startswith('{{'):
|
||||
# An expression to evaluate.
|
||||
expr = self._expr_code(token[start:end].strip())
|
||||
buffered.append("to_str(%s)" % expr)
|
||||
else:
|
||||
# token.startswith('{%')
|
||||
# Action tag: split into words and parse further.
|
||||
flush_output()
|
||||
|
||||
words = token[start:end].strip().split()
|
||||
if words[0] == 'if':
|
||||
# An if statement: evaluate the expression to determine if.
|
||||
if len(words) != 2:
|
||||
self._syntax_error("Don't understand if", token)
|
||||
ops_stack.append('if')
|
||||
code.add_line("if %s:" % self._expr_code(words[1]))
|
||||
code.indent()
|
||||
elif words[0] == 'for':
|
||||
# A loop: iterate over expression result.
|
||||
if len(words) != 4 or words[2] != 'in':
|
||||
self._syntax_error("Don't understand for", token)
|
||||
ops_stack.append('for')
|
||||
self._variable(words[1], self.loop_vars)
|
||||
code.add_line(
|
||||
"for c_{} in {}:".format(
|
||||
words[1],
|
||||
self._expr_code(words[3])
|
||||
)
|
||||
)
|
||||
code.indent()
|
||||
elif words[0] == 'joined':
|
||||
ops_stack.append('joined')
|
||||
in_joined = True
|
||||
elif words[0].startswith('end'):
|
||||
# Endsomething. Pop the ops stack.
|
||||
if len(words) != 1:
|
||||
self._syntax_error("Don't understand end", token)
|
||||
end_what = words[0][3:]
|
||||
if not ops_stack:
|
||||
self._syntax_error("Too many ends", token)
|
||||
start_what = ops_stack.pop()
|
||||
if start_what != end_what:
|
||||
self._syntax_error("Mismatched end tag", end_what)
|
||||
if end_what == 'joined':
|
||||
in_joined = False
|
||||
else:
|
||||
code.dedent()
|
||||
else:
|
||||
self._syntax_error("Don't understand tag", words[0])
|
||||
else:
|
||||
# Literal content. If it isn't empty, output it.
|
||||
if in_joined:
|
||||
token = re.sub(r"\s*\n\s*", "", token.strip())
|
||||
elif squash:
|
||||
token = token.lstrip()
|
||||
if token:
|
||||
buffered.append(repr(token))
|
||||
|
||||
if ops_stack:
|
||||
self._syntax_error("Unmatched action tag", ops_stack[-1])
|
||||
|
||||
flush_output()
|
||||
|
||||
for var_name in self.all_vars - self.loop_vars:
|
||||
vars_code.add_line(f"c_{var_name} = context[{var_name!r}]")
|
||||
|
||||
code.add_line('return "".join(result)')
|
||||
code.dedent()
|
||||
self._render_function = code.get_globals()['render_function']
|
||||
|
||||
def _expr_code(self, expr):
|
||||
"""Generate a Python expression for `expr`."""
|
||||
if "|" in expr:
|
||||
pipes = expr.split("|")
|
||||
code = self._expr_code(pipes[0])
|
||||
for func in pipes[1:]:
|
||||
self._variable(func, self.all_vars)
|
||||
code = f"c_{func}({code})"
|
||||
elif "." in expr:
|
||||
dots = expr.split(".")
|
||||
code = self._expr_code(dots[0])
|
||||
args = ", ".join(repr(d) for d in dots[1:])
|
||||
code = f"do_dots({code}, {args})"
|
||||
else:
|
||||
self._variable(expr, self.all_vars)
|
||||
code = "c_%s" % expr
|
||||
return code
|
||||
|
||||
def _syntax_error(self, msg, thing):
|
||||
"""Raise a syntax error using `msg`, and showing `thing`."""
|
||||
raise TempliteSyntaxError(f"{msg}: {thing!r}")
|
||||
|
||||
def _variable(self, name, vars_set):
|
||||
"""Track that `name` is used as a variable.
|
||||
|
||||
Adds the name to `vars_set`, a set of variable names.
|
||||
|
||||
Raises an syntax error if `name` is not a valid name.
|
||||
|
||||
"""
|
||||
if not re.match(r"[_a-zA-Z][_a-zA-Z0-9]*$", name):
|
||||
self._syntax_error("Not a valid name", name)
|
||||
vars_set.add(name)
|
||||
|
||||
def render(self, context=None):
|
||||
"""Render this template by applying it to `context`.
|
||||
|
||||
`context` is a dictionary of values to use in this rendering.
|
||||
|
||||
"""
|
||||
# Make the complete context we'll use.
|
||||
render_context = dict(self.context)
|
||||
if context:
|
||||
render_context.update(context)
|
||||
return self._render_function(render_context, self._do_dots)
|
||||
|
||||
def _do_dots(self, value, *dots):
|
||||
"""Evaluate dotted expressions at run-time."""
|
||||
for dot in dots:
|
||||
try:
|
||||
value = getattr(value, dot)
|
||||
except AttributeError:
|
||||
try:
|
||||
value = value[dot]
|
||||
except (TypeError, KeyError) as exc:
|
||||
raise TempliteValueError(
|
||||
f"Couldn't evaluate {value!r}.{dot}"
|
||||
) from exc
|
||||
if callable(value):
|
||||
value = value()
|
||||
return value
|
170
utils/python-venv/Lib/site-packages/coverage/tomlconfig.py
Normal file
170
utils/python-venv/Lib/site-packages/coverage/tomlconfig.py
Normal file
@ -0,0 +1,170 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""TOML configuration support for coverage.py"""
|
||||
|
||||
import configparser
|
||||
import os
|
||||
import re
|
||||
|
||||
from coverage import env
|
||||
from coverage.exceptions import ConfigError
|
||||
from coverage.misc import import_third_party, substitute_variables
|
||||
|
||||
|
||||
if env.PYVERSION >= (3, 11, 0, "alpha", 7):
|
||||
import tomllib # pylint: disable=import-error
|
||||
else:
|
||||
# TOML support on Python 3.10 and below is an install-time extra option.
|
||||
# (Import typing is here because import_third_party will unload any module
|
||||
# that wasn't already imported. tomli imports typing, and if we unload it,
|
||||
# later it's imported again, and on Python 3.6, this causes infinite
|
||||
# recursion.)
|
||||
import typing # pylint: disable=unused-import
|
||||
tomllib = import_third_party("tomli")
|
||||
|
||||
|
||||
class TomlDecodeError(Exception):
|
||||
"""An exception class that exists even when toml isn't installed."""
|
||||
pass
|
||||
|
||||
|
||||
class TomlConfigParser:
|
||||
"""TOML file reading with the interface of HandyConfigParser."""
|
||||
|
||||
# This class has the same interface as config.HandyConfigParser, no
|
||||
# need for docstrings.
|
||||
# pylint: disable=missing-function-docstring
|
||||
|
||||
def __init__(self, our_file):
|
||||
self.our_file = our_file
|
||||
self.data = None
|
||||
|
||||
def read(self, filenames):
|
||||
# RawConfigParser takes a filename or list of filenames, but we only
|
||||
# ever call this with a single filename.
|
||||
assert isinstance(filenames, (bytes, str, os.PathLike))
|
||||
filename = os.fspath(filenames)
|
||||
|
||||
try:
|
||||
with open(filename, encoding='utf-8') as fp:
|
||||
toml_text = fp.read()
|
||||
except OSError:
|
||||
return []
|
||||
if tomllib is not None:
|
||||
toml_text = substitute_variables(toml_text, os.environ)
|
||||
try:
|
||||
self.data = tomllib.loads(toml_text)
|
||||
except tomllib.TOMLDecodeError as err:
|
||||
raise TomlDecodeError(str(err)) from err
|
||||
return [filename]
|
||||
else:
|
||||
has_toml = re.search(r"^\[tool\.coverage\.", toml_text, flags=re.MULTILINE)
|
||||
if self.our_file or has_toml:
|
||||
# Looks like they meant to read TOML, but we can't read it.
|
||||
msg = "Can't read {!r} without TOML support. Install with [toml] extra"
|
||||
raise ConfigError(msg.format(filename))
|
||||
return []
|
||||
|
||||
def _get_section(self, section):
|
||||
"""Get a section from the data.
|
||||
|
||||
Arguments:
|
||||
section (str): A section name, which can be dotted.
|
||||
|
||||
Returns:
|
||||
name (str): the actual name of the section that was found, if any,
|
||||
or None.
|
||||
data (str): the dict of data in the section, or None if not found.
|
||||
|
||||
"""
|
||||
prefixes = ["tool.coverage."]
|
||||
if self.our_file:
|
||||
prefixes.append("")
|
||||
for prefix in prefixes:
|
||||
real_section = prefix + section
|
||||
parts = real_section.split(".")
|
||||
try:
|
||||
data = self.data[parts[0]]
|
||||
for part in parts[1:]:
|
||||
data = data[part]
|
||||
except KeyError:
|
||||
continue
|
||||
break
|
||||
else:
|
||||
return None, None
|
||||
return real_section, data
|
||||
|
||||
def _get(self, section, option):
|
||||
"""Like .get, but returns the real section name and the value."""
|
||||
name, data = self._get_section(section)
|
||||
if data is None:
|
||||
raise configparser.NoSectionError(section)
|
||||
try:
|
||||
return name, data[option]
|
||||
except KeyError as exc:
|
||||
raise configparser.NoOptionError(option, name) from exc
|
||||
|
||||
def has_option(self, section, option):
|
||||
_, data = self._get_section(section)
|
||||
if data is None:
|
||||
return False
|
||||
return option in data
|
||||
|
||||
def has_section(self, section):
|
||||
name, _ = self._get_section(section)
|
||||
return name
|
||||
|
||||
def options(self, section):
|
||||
_, data = self._get_section(section)
|
||||
if data is None:
|
||||
raise configparser.NoSectionError(section)
|
||||
return list(data.keys())
|
||||
|
||||
def get_section(self, section):
|
||||
_, data = self._get_section(section)
|
||||
return data
|
||||
|
||||
def get(self, section, option):
|
||||
_, value = self._get(section, option)
|
||||
return value
|
||||
|
||||
def _check_type(self, section, option, value, type_, type_desc):
|
||||
if not isinstance(value, type_):
|
||||
raise ValueError(
|
||||
'Option {!r} in section {!r} is not {}: {!r}'
|
||||
.format(option, section, type_desc, value)
|
||||
)
|
||||
|
||||
def getboolean(self, section, option):
|
||||
name, value = self._get(section, option)
|
||||
self._check_type(name, option, value, bool, "a boolean")
|
||||
return value
|
||||
|
||||
def getlist(self, section, option):
|
||||
name, values = self._get(section, option)
|
||||
self._check_type(name, option, values, list, "a list")
|
||||
return values
|
||||
|
||||
def getregexlist(self, section, option):
|
||||
name, values = self._get(section, option)
|
||||
self._check_type(name, option, values, list, "a list")
|
||||
for value in values:
|
||||
value = value.strip()
|
||||
try:
|
||||
re.compile(value)
|
||||
except re.error as e:
|
||||
raise ConfigError(f"Invalid [{name}].{option} value {value!r}: {e}") from e
|
||||
return values
|
||||
|
||||
def getint(self, section, option):
|
||||
name, value = self._get(section, option)
|
||||
self._check_type(name, option, value, int, "an integer")
|
||||
return value
|
||||
|
||||
def getfloat(self, section, option):
|
||||
name, value = self._get(section, option)
|
||||
if isinstance(value, int):
|
||||
value = float(value)
|
||||
self._check_type(name, option, value, float, "a float")
|
||||
return value
|
Binary file not shown.
31
utils/python-venv/Lib/site-packages/coverage/version.py
Normal file
31
utils/python-venv/Lib/site-packages/coverage/version.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""The version and URL for coverage.py"""
|
||||
# This file is exec'ed in setup.py, don't import anything!
|
||||
|
||||
# Same semantics as sys.version_info.
|
||||
version_info = (6, 5, 0, "final", 0)
|
||||
|
||||
|
||||
def _make_version(major, minor, micro, releaselevel, serial):
|
||||
"""Create a readable version string from version_info tuple components."""
|
||||
assert releaselevel in ['alpha', 'beta', 'candidate', 'final']
|
||||
version = "%d.%d.%d" % (major, minor, micro)
|
||||
if releaselevel != 'final':
|
||||
short = {'alpha': 'a', 'beta': 'b', 'candidate': 'rc'}[releaselevel]
|
||||
version += f"{short}{serial}"
|
||||
return version
|
||||
|
||||
|
||||
def _make_url(major, minor, micro, releaselevel, serial):
|
||||
"""Make the URL people should start at for this version of coverage.py."""
|
||||
url = "https://coverage.readthedocs.io"
|
||||
if releaselevel != 'final':
|
||||
# For pre-releases, use a version-specific URL.
|
||||
url += "/en/" + _make_version(major, minor, micro, releaselevel, serial)
|
||||
return url
|
||||
|
||||
|
||||
__version__ = _make_version(*version_info)
|
||||
__url__ = _make_url(*version_info)
|
230
utils/python-venv/Lib/site-packages/coverage/xmlreport.py
Normal file
230
utils/python-venv/Lib/site-packages/coverage/xmlreport.py
Normal file
@ -0,0 +1,230 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""XML reporting for coverage.py"""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import time
|
||||
import xml.dom.minidom
|
||||
|
||||
from coverage import __url__, __version__, files
|
||||
from coverage.misc import isolate_module, human_sorted, human_sorted_items
|
||||
from coverage.report import get_analysis_to_report
|
||||
|
||||
os = isolate_module(os)
|
||||
|
||||
|
||||
DTD_URL = 'https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd'
|
||||
|
||||
|
||||
def rate(hit, num):
|
||||
"""Return the fraction of `hit`/`num`, as a string."""
|
||||
if num == 0:
|
||||
return "1"
|
||||
else:
|
||||
return "%.4g" % (float(hit) / num)
|
||||
|
||||
|
||||
class XmlReporter:
|
||||
"""A reporter for writing Cobertura-style XML coverage results."""
|
||||
|
||||
report_type = "XML report"
|
||||
|
||||
def __init__(self, coverage):
|
||||
self.coverage = coverage
|
||||
self.config = self.coverage.config
|
||||
|
||||
self.source_paths = set()
|
||||
if self.config.source:
|
||||
for src in self.config.source:
|
||||
if os.path.exists(src):
|
||||
if not self.config.relative_files:
|
||||
src = files.canonical_filename(src)
|
||||
self.source_paths.add(src)
|
||||
self.packages = {}
|
||||
self.xml_out = None
|
||||
|
||||
def report(self, morfs, outfile=None):
|
||||
"""Generate a Cobertura-compatible XML report for `morfs`.
|
||||
|
||||
`morfs` is a list of modules or file names.
|
||||
|
||||
`outfile` is a file object to write the XML to.
|
||||
|
||||
"""
|
||||
# Initial setup.
|
||||
outfile = outfile or sys.stdout
|
||||
has_arcs = self.coverage.get_data().has_arcs()
|
||||
|
||||
# Create the DOM that will store the data.
|
||||
impl = xml.dom.minidom.getDOMImplementation()
|
||||
self.xml_out = impl.createDocument(None, "coverage", None)
|
||||
|
||||
# Write header stuff.
|
||||
xcoverage = self.xml_out.documentElement
|
||||
xcoverage.setAttribute("version", __version__)
|
||||
xcoverage.setAttribute("timestamp", str(int(time.time()*1000)))
|
||||
xcoverage.appendChild(self.xml_out.createComment(
|
||||
f" Generated by coverage.py: {__url__} "
|
||||
))
|
||||
xcoverage.appendChild(self.xml_out.createComment(f" Based on {DTD_URL} "))
|
||||
|
||||
# Call xml_file for each file in the data.
|
||||
for fr, analysis in get_analysis_to_report(self.coverage, morfs):
|
||||
self.xml_file(fr, analysis, has_arcs)
|
||||
|
||||
xsources = self.xml_out.createElement("sources")
|
||||
xcoverage.appendChild(xsources)
|
||||
|
||||
# Populate the XML DOM with the source info.
|
||||
for path in human_sorted(self.source_paths):
|
||||
xsource = self.xml_out.createElement("source")
|
||||
xsources.appendChild(xsource)
|
||||
txt = self.xml_out.createTextNode(path)
|
||||
xsource.appendChild(txt)
|
||||
|
||||
lnum_tot, lhits_tot = 0, 0
|
||||
bnum_tot, bhits_tot = 0, 0
|
||||
|
||||
xpackages = self.xml_out.createElement("packages")
|
||||
xcoverage.appendChild(xpackages)
|
||||
|
||||
# Populate the XML DOM with the package info.
|
||||
for pkg_name, pkg_data in human_sorted_items(self.packages.items()):
|
||||
class_elts, lhits, lnum, bhits, bnum = pkg_data
|
||||
xpackage = self.xml_out.createElement("package")
|
||||
xpackages.appendChild(xpackage)
|
||||
xclasses = self.xml_out.createElement("classes")
|
||||
xpackage.appendChild(xclasses)
|
||||
for _, class_elt in human_sorted_items(class_elts.items()):
|
||||
xclasses.appendChild(class_elt)
|
||||
xpackage.setAttribute("name", pkg_name.replace(os.sep, '.'))
|
||||
xpackage.setAttribute("line-rate", rate(lhits, lnum))
|
||||
if has_arcs:
|
||||
branch_rate = rate(bhits, bnum)
|
||||
else:
|
||||
branch_rate = "0"
|
||||
xpackage.setAttribute("branch-rate", branch_rate)
|
||||
xpackage.setAttribute("complexity", "0")
|
||||
|
||||
lnum_tot += lnum
|
||||
lhits_tot += lhits
|
||||
bnum_tot += bnum
|
||||
bhits_tot += bhits
|
||||
|
||||
xcoverage.setAttribute("lines-valid", str(lnum_tot))
|
||||
xcoverage.setAttribute("lines-covered", str(lhits_tot))
|
||||
xcoverage.setAttribute("line-rate", rate(lhits_tot, lnum_tot))
|
||||
if has_arcs:
|
||||
xcoverage.setAttribute("branches-valid", str(bnum_tot))
|
||||
xcoverage.setAttribute("branches-covered", str(bhits_tot))
|
||||
xcoverage.setAttribute("branch-rate", rate(bhits_tot, bnum_tot))
|
||||
else:
|
||||
xcoverage.setAttribute("branches-covered", "0")
|
||||
xcoverage.setAttribute("branches-valid", "0")
|
||||
xcoverage.setAttribute("branch-rate", "0")
|
||||
xcoverage.setAttribute("complexity", "0")
|
||||
|
||||
# Write the output file.
|
||||
outfile.write(serialize_xml(self.xml_out))
|
||||
|
||||
# Return the total percentage.
|
||||
denom = lnum_tot + bnum_tot
|
||||
if denom == 0:
|
||||
pct = 0.0
|
||||
else:
|
||||
pct = 100.0 * (lhits_tot + bhits_tot) / denom
|
||||
return pct
|
||||
|
||||
def xml_file(self, fr, analysis, has_arcs):
|
||||
"""Add to the XML report for a single file."""
|
||||
|
||||
if self.config.skip_empty:
|
||||
if analysis.numbers.n_statements == 0:
|
||||
return
|
||||
|
||||
# Create the 'lines' and 'package' XML elements, which
|
||||
# are populated later. Note that a package == a directory.
|
||||
filename = fr.filename.replace("\\", "/")
|
||||
for source_path in self.source_paths:
|
||||
source_path = files.canonical_filename(source_path)
|
||||
if filename.startswith(source_path.replace("\\", "/") + "/"):
|
||||
rel_name = filename[len(source_path)+1:]
|
||||
break
|
||||
else:
|
||||
rel_name = fr.relative_filename()
|
||||
self.source_paths.add(fr.filename[:-len(rel_name)].rstrip(r"\/"))
|
||||
|
||||
dirname = os.path.dirname(rel_name) or "."
|
||||
dirname = "/".join(dirname.split("/")[:self.config.xml_package_depth])
|
||||
package_name = dirname.replace("/", ".")
|
||||
|
||||
package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0])
|
||||
|
||||
xclass = self.xml_out.createElement("class")
|
||||
|
||||
xclass.appendChild(self.xml_out.createElement("methods"))
|
||||
|
||||
xlines = self.xml_out.createElement("lines")
|
||||
xclass.appendChild(xlines)
|
||||
|
||||
xclass.setAttribute("name", os.path.relpath(rel_name, dirname))
|
||||
xclass.setAttribute("filename", rel_name.replace("\\", "/"))
|
||||
xclass.setAttribute("complexity", "0")
|
||||
|
||||
branch_stats = analysis.branch_stats()
|
||||
missing_branch_arcs = analysis.missing_branch_arcs()
|
||||
|
||||
# For each statement, create an XML 'line' element.
|
||||
for line in sorted(analysis.statements):
|
||||
xline = self.xml_out.createElement("line")
|
||||
xline.setAttribute("number", str(line))
|
||||
|
||||
# Q: can we get info about the number of times a statement is
|
||||
# executed? If so, that should be recorded here.
|
||||
xline.setAttribute("hits", str(int(line not in analysis.missing)))
|
||||
|
||||
if has_arcs:
|
||||
if line in branch_stats:
|
||||
total, taken = branch_stats[line]
|
||||
xline.setAttribute("branch", "true")
|
||||
xline.setAttribute(
|
||||
"condition-coverage",
|
||||
"%d%% (%d/%d)" % (100*taken//total, taken, total)
|
||||
)
|
||||
if line in missing_branch_arcs:
|
||||
annlines = ["exit" if b < 0 else str(b) for b in missing_branch_arcs[line]]
|
||||
xline.setAttribute("missing-branches", ",".join(annlines))
|
||||
xlines.appendChild(xline)
|
||||
|
||||
class_lines = len(analysis.statements)
|
||||
class_hits = class_lines - len(analysis.missing)
|
||||
|
||||
if has_arcs:
|
||||
class_branches = sum(t for t, k in branch_stats.values())
|
||||
missing_branches = sum(t - k for t, k in branch_stats.values())
|
||||
class_br_hits = class_branches - missing_branches
|
||||
else:
|
||||
class_branches = 0.0
|
||||
class_br_hits = 0.0
|
||||
|
||||
# Finalize the statistics that are collected in the XML DOM.
|
||||
xclass.setAttribute("line-rate", rate(class_hits, class_lines))
|
||||
if has_arcs:
|
||||
branch_rate = rate(class_br_hits, class_branches)
|
||||
else:
|
||||
branch_rate = "0"
|
||||
xclass.setAttribute("branch-rate", branch_rate)
|
||||
|
||||
package[0][rel_name] = xclass
|
||||
package[1] += class_hits
|
||||
package[2] += class_lines
|
||||
package[3] += class_br_hits
|
||||
package[4] += class_branches
|
||||
|
||||
|
||||
def serialize_xml(dom):
|
||||
"""Serialize a minidom node to XML."""
|
||||
return dom.toprettyxml()
|
Reference in New Issue
Block a user