| Index: Tools/Scripts/webkitpy/thirdparty/coverage/control.py
|
| diff --git a/Tools/Scripts/webkitpy/thirdparty/coverage/control.py b/Tools/Scripts/webkitpy/thirdparty/coverage/control.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b617f486abda8071f504a3638b2439cf14d8418a
|
| --- /dev/null
|
| +++ b/Tools/Scripts/webkitpy/thirdparty/coverage/control.py
|
| @@ -0,0 +1,688 @@
|
| +"""Core control stuff for Coverage."""
|
| +
|
| +import atexit, os, random, socket, sys
|
| +
|
| +from coverage.annotate import AnnotateReporter
|
| +from coverage.backward import string_class
|
| +from coverage.codeunit import code_unit_factory, CodeUnit
|
| +from coverage.collector import Collector
|
| +from coverage.config import CoverageConfig
|
| +from coverage.data import CoverageData
|
| +from coverage.files import FileLocator, TreeMatcher, FnmatchMatcher
|
| +from coverage.files import PathAliases, find_python_files
|
| +from coverage.html import HtmlReporter
|
| +from coverage.misc import CoverageException, bool_or_none, join_regex
|
| +from coverage.results import Analysis, Numbers
|
| +from coverage.summary import SummaryReporter
|
| +from coverage.xmlreport import XmlReporter
|
| +
|
| +class coverage(object):
|
| + """Programmatic access to Coverage.
|
| +
|
| + To use::
|
| +
|
| + from coverage import coverage
|
| +
|
| + cov = coverage()
|
| + cov.start()
|
| + #.. blah blah (run your code) blah blah ..
|
| + cov.stop()
|
| + cov.html_report(directory='covhtml')
|
| +
|
| + """
|
| + def __init__(self, data_file=None, data_suffix=None, cover_pylib=None,
|
| + auto_data=False, timid=None, branch=None, config_file=True,
|
| + source=None, omit=None, include=None):
|
| + """
|
| + `data_file` is the base name of the data file to use, defaulting to
|
| + ".coverage". `data_suffix` is appended (with a dot) to `data_file` to
|
| + create the final file name. If `data_suffix` is simply True, then a
|
| + suffix is created with the machine and process identity included.
|
| +
|
| + `cover_pylib` is a boolean determining whether Python code installed
|
| + with the Python interpreter is measured. This includes the Python
|
| + standard library and any packages installed with the interpreter.
|
| +
|
| + If `auto_data` is true, then any existing data file will be read when
|
| + coverage measurement starts, and data will be saved automatically when
|
| + measurement stops.
|
| +
|
| + If `timid` is true, then a slower and simpler trace function will be
|
| + used. This is important for some environments where manipulation of
|
| + tracing functions breaks the faster trace function.
|
| +
|
| + If `branch` is true, then branch coverage will be measured in addition
|
| + to the usual statement coverage.
|
| +
|
| + `config_file` determines what config file to read. If it is a string,
|
| + it is the name of the config file to read. If it is True, then a
|
| + standard file is read (".coveragerc"). If it is False, then no file is
|
| + read.
|
| +
|
| + `source` is a list of file paths or package names. Only code located
|
| + in the trees indicated by the file paths or package names will be
|
| + measured.
|
| +
|
| + `include` and `omit` are lists of filename patterns. Files that match
|
| + `include` will be measured, files that match `omit` will not. Each
|
| + will also accept a single string argument.
|
| +
|
| + """
|
| + from coverage import __version__
|
| +
|
| + # A record of all the warnings that have been issued.
|
| + self._warnings = []
|
| +
|
| + # Build our configuration from a number of sources:
|
| + # 1: defaults:
|
| + self.config = CoverageConfig()
|
| +
|
| + # 2: from the coveragerc file:
|
| + if config_file:
|
| + if config_file is True:
|
| + config_file = ".coveragerc"
|
| + try:
|
| + self.config.from_file(config_file)
|
| + except ValueError:
|
| + _, err, _ = sys.exc_info()
|
| + raise CoverageException(
|
| + "Couldn't read config file %s: %s" % (config_file, err)
|
| + )
|
| +
|
| + # 3: from environment variables:
|
| + self.config.from_environment('COVERAGE_OPTIONS')
|
| + env_data_file = os.environ.get('COVERAGE_FILE')
|
| + if env_data_file:
|
| + self.config.data_file = env_data_file
|
| +
|
| + # 4: from constructor arguments:
|
| + if isinstance(omit, string_class):
|
| + omit = [omit]
|
| + if isinstance(include, string_class):
|
| + include = [include]
|
| + self.config.from_args(
|
| + data_file=data_file, cover_pylib=cover_pylib, timid=timid,
|
| + branch=branch, parallel=bool_or_none(data_suffix),
|
| + source=source, omit=omit, include=include
|
| + )
|
| +
|
| + self.auto_data = auto_data
|
| + self.atexit_registered = False
|
| +
|
| + # _exclude_re is a dict mapping exclusion list names to compiled
|
| + # regexes.
|
| + self._exclude_re = {}
|
| + self._exclude_regex_stale()
|
| +
|
| + self.file_locator = FileLocator()
|
| +
|
| + # The source argument can be directories or package names.
|
| + self.source = []
|
| + self.source_pkgs = []
|
| + for src in self.config.source or []:
|
| + if os.path.exists(src):
|
| + self.source.append(self.file_locator.canonical_filename(src))
|
| + else:
|
| + self.source_pkgs.append(src)
|
| +
|
| + self.omit = self._prep_patterns(self.config.omit)
|
| + self.include = self._prep_patterns(self.config.include)
|
| +
|
| + self.collector = Collector(
|
| + self._should_trace, timid=self.config.timid,
|
| + branch=self.config.branch, warn=self._warn
|
| + )
|
| +
|
| + # Suffixes are a bit tricky. We want to use the data suffix only when
|
| + # collecting data, not when combining data. So we save it as
|
| + # `self.run_suffix` now, and promote it to `self.data_suffix` if we
|
| + # find that we are collecting data later.
|
| + if data_suffix or self.config.parallel:
|
| + if not isinstance(data_suffix, string_class):
|
| + # if data_suffix=True, use .machinename.pid.random
|
| + data_suffix = True
|
| + else:
|
| + data_suffix = None
|
| + self.data_suffix = None
|
| + self.run_suffix = data_suffix
|
| +
|
| + # Create the data file. We do this at construction time so that the
|
| + # data file will be written into the directory where the process
|
| + # started rather than wherever the process eventually chdir'd to.
|
| + self.data = CoverageData(
|
| + basename=self.config.data_file,
|
| + collector="coverage v%s" % __version__
|
| + )
|
| +
|
| + # The dirs for files considered "installed with the interpreter".
|
| + self.pylib_dirs = []
|
| + if not self.config.cover_pylib:
|
| + # 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.
|
| + for m in (atexit, os, random, socket):
|
| + if hasattr(m, "__file__"):
|
| + m_dir = self._canonical_dir(m.__file__)
|
| + if m_dir not in self.pylib_dirs:
|
| + self.pylib_dirs.append(m_dir)
|
| +
|
| + # To avoid tracing the coverage code itself, we skip anything located
|
| + # where we are.
|
| + self.cover_dir = self._canonical_dir(__file__)
|
| +
|
| + # The matchers for _should_trace, created when tracing starts.
|
| + self.source_match = None
|
| + self.pylib_match = self.cover_match = None
|
| + self.include_match = self.omit_match = None
|
| +
|
| + # Only _harvest_data once per measurement cycle.
|
| + self._harvested = False
|
| +
|
| + # Set the reporting precision.
|
| + Numbers.set_precision(self.config.precision)
|
| +
|
| + # When tearing down the coverage object, modules can become None.
|
| + # Saving the modules as object attributes avoids problems, but it is
|
| + # quite ad-hoc which modules need to be saved and which references
|
| + # need to use the object attributes.
|
| + self.socket = socket
|
| + self.os = os
|
| + self.random = random
|
| +
|
| + def _canonical_dir(self, f):
|
| + """Return the canonical directory of the file `f`."""
|
| + return os.path.split(self.file_locator.canonical_filename(f))[0]
|
| +
|
| + def _source_for_file(self, filename):
|
| + """Return the source file for `filename`."""
|
| + if not filename.endswith(".py"):
|
| + if filename[-4:-1] == ".py":
|
| + filename = filename[:-1]
|
| + return filename
|
| +
|
| + def _should_trace(self, filename, frame):
|
| + """Decide whether to trace execution in `filename`
|
| +
|
| + 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 canonicalized filename if it should be traced, False if it
|
| + should not.
|
| +
|
| + """
|
| + if os is None:
|
| + return False
|
| +
|
| + if filename.startswith('<'):
|
| + # Lots of non-file execution is represented with artificial
|
| + # filenames 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 False
|
| +
|
| + if filename.endswith(".html"):
|
| + # Jinja and maybe other templating systems compile templates into
|
| + # Python code, but use the template filename as the filename in
|
| + # the compiled code. Of course, those filenames are useless later
|
| + # so don't bother collecting. TODO: How should we really separate
|
| + # out good file extensions from bad?
|
| + return False
|
| +
|
| + self._check_for_packages()
|
| +
|
| + # Compiled Python files have two filenames: frame.f_code.co_filename is
|
| + # the filename 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.get('__file__')
|
| + if dunder_file:
|
| + filename = self._source_for_file(dunder_file)
|
| +
|
| + # Jython reports the .class file to the tracer, use the source file.
|
| + if filename.endswith("$py.class"):
|
| + filename = filename[:-9] + ".py"
|
| +
|
| + canonical = self.file_locator.canonical_filename(filename)
|
| +
|
| + # 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:
|
| + if not self.source_match.match(canonical):
|
| + return False
|
| + elif self.include_match:
|
| + if not self.include_match.match(canonical):
|
| + return False
|
| + else:
|
| + # 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(canonical):
|
| + return False
|
| +
|
| + # We exclude the coverage code itself, since a little of it will be
|
| + # measured otherwise.
|
| + if self.cover_match and self.cover_match.match(canonical):
|
| + return False
|
| +
|
| + # Check the file against the omit pattern.
|
| + if self.omit_match and self.omit_match.match(canonical):
|
| + return False
|
| +
|
| + return canonical
|
| +
|
| + # To log what should_trace returns, change this to "if 1:"
|
| + if 0:
|
| + _real_should_trace = _should_trace
|
| + def _should_trace(self, filename, frame): # pylint: disable=E0102
|
| + """A logging decorator around the real _should_trace function."""
|
| + ret = self._real_should_trace(filename, frame)
|
| + print("should_trace: %r -> %r" % (filename, ret))
|
| + return ret
|
| +
|
| + def _warn(self, msg):
|
| + """Use `msg` as a warning."""
|
| + self._warnings.append(msg)
|
| + sys.stderr.write("Coverage.py warning: %s\n" % msg)
|
| +
|
| + def _prep_patterns(self, 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.
|
| +
|
| + """
|
| + patterns = patterns or []
|
| + prepped = []
|
| + for p in patterns or []:
|
| + if p.startswith("*") or p.startswith("?"):
|
| + prepped.append(p)
|
| + else:
|
| + prepped.append(self.file_locator.abs_file(p))
|
| + return prepped
|
| +
|
| + def _check_for_packages(self):
|
| + """Update the source_match matcher with latest imported packages."""
|
| + # Our self.source_pkgs attribute is a list of package names we want to
|
| + # measure. Each time through here, we see if we've imported any of
|
| + # them yet. If so, we add its file to source_match, and we don't have
|
| + # to look for that package any more.
|
| + if self.source_pkgs:
|
| + found = []
|
| + for pkg in self.source_pkgs:
|
| + try:
|
| + mod = sys.modules[pkg]
|
| + except KeyError:
|
| + continue
|
| +
|
| + found.append(pkg)
|
| +
|
| + try:
|
| + pkg_file = mod.__file__
|
| + except AttributeError:
|
| + self._warn("Module %s has no Python source." % pkg)
|
| + else:
|
| + d, f = os.path.split(pkg_file)
|
| + if f.startswith('__init__.'):
|
| + # This is actually a package, return the directory.
|
| + pkg_file = d
|
| + else:
|
| + pkg_file = self._source_for_file(pkg_file)
|
| + pkg_file = self.file_locator.canonical_filename(pkg_file)
|
| + self.source.append(pkg_file)
|
| + self.source_match.add(pkg_file)
|
| +
|
| + for pkg in found:
|
| + self.source_pkgs.remove(pkg)
|
| +
|
| + def use_cache(self, usecache):
|
| + """Control the use of a data file (incorrectly called a cache).
|
| +
|
| + `usecache` is true or false, whether to read and write data on disk.
|
| +
|
| + """
|
| + self.data.usefile(usecache)
|
| +
|
| + def load(self):
|
| + """Load previously-collected coverage data from the data file."""
|
| + self.collector.reset()
|
| + self.data.read()
|
| +
|
| + def start(self):
|
| + """Start measuring code coverage."""
|
| + if self.run_suffix:
|
| + # Calling start() means we're running code, so use the run_suffix
|
| + # as the data_suffix when we eventually save the data.
|
| + self.data_suffix = self.run_suffix
|
| + if self.auto_data:
|
| + self.load()
|
| + # Save coverage data when Python exits.
|
| + if not self.atexit_registered:
|
| + atexit.register(self.save)
|
| + self.atexit_registered = True
|
| +
|
| + # Create the matchers we need for _should_trace
|
| + if self.source or self.source_pkgs:
|
| + self.source_match = TreeMatcher(self.source)
|
| + else:
|
| + if self.cover_dir:
|
| + self.cover_match = TreeMatcher([self.cover_dir])
|
| + if self.pylib_dirs:
|
| + self.pylib_match = TreeMatcher(self.pylib_dirs)
|
| + if self.include:
|
| + self.include_match = FnmatchMatcher(self.include)
|
| + if self.omit:
|
| + self.omit_match = FnmatchMatcher(self.omit)
|
| +
|
| + self._harvested = False
|
| + self.collector.start()
|
| +
|
| + def stop(self):
|
| + """Stop measuring code coverage."""
|
| + self.collector.stop()
|
| + self._harvest_data()
|
| +
|
| + def erase(self):
|
| + """Erase previously-collected coverage data.
|
| +
|
| + This removes the in-memory data collected in this session as well as
|
| + discarding the data file.
|
| +
|
| + """
|
| + self.collector.reset()
|
| + self.data.erase()
|
| +
|
| + def clear_exclude(self, which='exclude'):
|
| + """Clear the exclude list."""
|
| + setattr(self.config, which + "_list", [])
|
| + self._exclude_regex_stale()
|
| +
|
| + def exclude(self, regex, which='exclude'):
|
| + """Exclude source lines from execution consideration.
|
| +
|
| + A number of lists of regular expressions are maintained. Each list
|
| + selects lines that are treated differently during reporting.
|
| +
|
| + `which` determines which list is modified. The "exclude" list selects
|
| + lines that are not considered executable at all. The "partial" list
|
| + indicates lines with branches that are not taken.
|
| +
|
| + `regex` is a regular expression. The regex is added to the specified
|
| + list. If any of the regexes in the list is found in a line, the line
|
| + is marked for special treatment during reporting.
|
| +
|
| + """
|
| + excl_list = getattr(self.config, which + "_list")
|
| + excl_list.append(regex)
|
| + self._exclude_regex_stale()
|
| +
|
| + def _exclude_regex_stale(self):
|
| + """Drop all the compiled exclusion regexes, a list was modified."""
|
| + self._exclude_re.clear()
|
| +
|
| + def _exclude_regex(self, which):
|
| + """Return a compiled regex for the given exclusion list."""
|
| + if which not in self._exclude_re:
|
| + excl_list = getattr(self.config, which + "_list")
|
| + self._exclude_re[which] = join_regex(excl_list)
|
| + return self._exclude_re[which]
|
| +
|
| + def get_exclude_list(self, which='exclude'):
|
| + """Return a list of excluded regex patterns.
|
| +
|
| + `which` indicates which list is desired. See `exclude` for the lists
|
| + that are available, and their meaning.
|
| +
|
| + """
|
| + return getattr(self.config, which + "_list")
|
| +
|
| + def save(self):
|
| + """Save the collected coverage data to the data file."""
|
| + data_suffix = self.data_suffix
|
| + if data_suffix is True:
|
| + # If data_suffix was a simple true value, then make a suffix with
|
| + # plenty of distinguishing information. We do this here in
|
| + # `save()` at the last minute so that the pid will be correct even
|
| + # if the process forks.
|
| + data_suffix = "%s.%s.%06d" % (
|
| + self.socket.gethostname(), self.os.getpid(),
|
| + self.random.randint(0, 99999)
|
| + )
|
| +
|
| + self._harvest_data()
|
| + self.data.write(suffix=data_suffix)
|
| +
|
| + def combine(self):
|
| + """Combine together a number of similarly-named coverage data files.
|
| +
|
| + All coverage data files whose name starts with `data_file` (from the
|
| + coverage() constructor) will be read, and combined together into the
|
| + current measurements.
|
| +
|
| + """
|
| + aliases = None
|
| + if self.config.paths:
|
| + aliases = PathAliases(self.file_locator)
|
| + for paths in self.config.paths.values():
|
| + result = paths[0]
|
| + for pattern in paths[1:]:
|
| + aliases.add(pattern, result)
|
| + self.data.combine_parallel_data(aliases=aliases)
|
| +
|
| + def _harvest_data(self):
|
| + """Get the collected data and reset the collector.
|
| +
|
| + Also warn about various problems collecting data.
|
| +
|
| + """
|
| + if not self._harvested:
|
| + self.data.add_line_data(self.collector.get_line_data())
|
| + self.data.add_arc_data(self.collector.get_arc_data())
|
| + self.collector.reset()
|
| +
|
| + # If there are still entries in the source_pkgs list, then we never
|
| + # encountered those packages.
|
| + for pkg in self.source_pkgs:
|
| + self._warn("Module %s was never imported." % pkg)
|
| +
|
| + # Find out if we got any data.
|
| + summary = self.data.summary()
|
| + if not summary:
|
| + self._warn("No data was collected.")
|
| +
|
| + # Find files that were never executed at all.
|
| + for src in self.source:
|
| + for py_file in find_python_files(src):
|
| + self.data.touch_file(py_file)
|
| +
|
| + self._harvested = True
|
| +
|
| + # Backward compatibility with version 1.
|
| + def analysis(self, morf):
|
| + """Like `analysis2` but doesn't return excluded line numbers."""
|
| + f, s, _, m, mf = self.analysis2(morf)
|
| + return f, s, m, mf
|
| +
|
| + def analysis2(self, morf):
|
| + """Analyze a module.
|
| +
|
| + `morf` is a module or a filename. It will be analyzed to determine
|
| + its coverage statistics. The return value is a 5-tuple:
|
| +
|
| + * The filename for the module.
|
| + * A list of line numbers of executable statements.
|
| + * A list of line numbers of excluded statements.
|
| + * A list of line numbers of statements not run (missing from
|
| + execution).
|
| + * A readable formatted string of the missing line numbers.
|
| +
|
| + The analysis uses the source file itself and the current measured
|
| + coverage data.
|
| +
|
| + """
|
| + analysis = self._analyze(morf)
|
| + return (
|
| + analysis.filename, analysis.statements, analysis.excluded,
|
| + analysis.missing, analysis.missing_formatted()
|
| + )
|
| +
|
| + def _analyze(self, it):
|
| + """Analyze a single morf or code unit.
|
| +
|
| + Returns an `Analysis` object.
|
| +
|
| + """
|
| + if not isinstance(it, CodeUnit):
|
| + it = code_unit_factory(it, self.file_locator)[0]
|
| +
|
| + return Analysis(self, it)
|
| +
|
| + def report(self, morfs=None, show_missing=True, ignore_errors=None,
|
| + file=None, # pylint: disable=W0622
|
| + omit=None, include=None
|
| + ):
|
| + """Write a summary report to `file`.
|
| +
|
| + Each module in `morfs` is listed, with counts of statements, executed
|
| + statements, missing statements, and a list of lines missed.
|
| +
|
| + `include` is a list of filename patterns. Modules whose filenames
|
| + match those patterns will be included in the report. Modules matching
|
| + `omit` will not be included in the report.
|
| +
|
| + """
|
| + self.config.from_args(
|
| + ignore_errors=ignore_errors, omit=omit, include=include
|
| + )
|
| + reporter = SummaryReporter(
|
| + self, show_missing, self.config.ignore_errors
|
| + )
|
| + reporter.report(morfs, outfile=file, config=self.config)
|
| +
|
| + def annotate(self, morfs=None, directory=None, ignore_errors=None,
|
| + omit=None, include=None):
|
| + """Annotate a list of modules.
|
| +
|
| + Each module in `morfs` is annotated. The source is written to a new
|
| + file, named with a ",cover" suffix, with each line prefixed with a
|
| + marker to indicate the coverage of the line. Covered lines have ">",
|
| + excluded lines have "-", and missing lines have "!".
|
| +
|
| + See `coverage.report()` for other arguments.
|
| +
|
| + """
|
| + self.config.from_args(
|
| + ignore_errors=ignore_errors, omit=omit, include=include
|
| + )
|
| + reporter = AnnotateReporter(self, self.config.ignore_errors)
|
| + reporter.report(morfs, config=self.config, directory=directory)
|
| +
|
| + def html_report(self, morfs=None, directory=None, ignore_errors=None,
|
| + omit=None, include=None):
|
| + """Generate an HTML report.
|
| +
|
| + See `coverage.report()` for other arguments.
|
| +
|
| + """
|
| + self.config.from_args(
|
| + ignore_errors=ignore_errors, omit=omit, include=include,
|
| + html_dir=directory,
|
| + )
|
| + reporter = HtmlReporter(self, self.config.ignore_errors)
|
| + reporter.report(morfs, config=self.config)
|
| +
|
| + def xml_report(self, morfs=None, outfile=None, ignore_errors=None,
|
| + omit=None, include=None):
|
| + """Generate an XML report of coverage results.
|
| +
|
| + The report is compatible with Cobertura reports.
|
| +
|
| + Each module in `morfs` is included in the report. `outfile` is the
|
| + path to write the file to, "-" will write to stdout.
|
| +
|
| + See `coverage.report()` for other arguments.
|
| +
|
| + """
|
| + self.config.from_args(
|
| + ignore_errors=ignore_errors, omit=omit, include=include,
|
| + xml_output=outfile,
|
| + )
|
| + file_to_close = None
|
| + if self.config.xml_output:
|
| + if self.config.xml_output == '-':
|
| + outfile = sys.stdout
|
| + else:
|
| + outfile = open(self.config.xml_output, "w")
|
| + file_to_close = outfile
|
| + try:
|
| + reporter = XmlReporter(self, self.config.ignore_errors)
|
| + reporter.report(morfs, outfile=outfile, config=self.config)
|
| + finally:
|
| + if file_to_close:
|
| + file_to_close.close()
|
| +
|
| + def sysinfo(self):
|
| + """Return a list of (key, value) pairs showing internal information."""
|
| +
|
| + import coverage as covmod
|
| + import platform, re
|
| +
|
| + try:
|
| + implementation = platform.python_implementation()
|
| + except AttributeError:
|
| + implementation = "unknown"
|
| +
|
| + info = [
|
| + ('version', covmod.__version__),
|
| + ('coverage', covmod.__file__),
|
| + ('cover_dir', self.cover_dir),
|
| + ('pylib_dirs', self.pylib_dirs),
|
| + ('tracer', self.collector.tracer_name()),
|
| + ('data_path', self.data.filename),
|
| + ('python', sys.version.replace('\n', '')),
|
| + ('platform', platform.platform()),
|
| + ('implementation', implementation),
|
| + ('cwd', os.getcwd()),
|
| + ('path', sys.path),
|
| + ('environment', [
|
| + ("%s = %s" % (k, v)) for k, v in os.environ.items()
|
| + if re.search("^COV|^PY", k)
|
| + ]),
|
| + ]
|
| + return info
|
| +
|
| +
|
| +def process_startup():
|
| + """Call this at Python startup to perhaps measure coverage.
|
| +
|
| + If the environment variable COVERAGE_PROCESS_START is defined, coverage
|
| + measurement is started. The value of the variable is the config file
|
| + to use.
|
| +
|
| + There are two ways to configure your Python installation to invoke this
|
| + function when Python starts:
|
| +
|
| + #. Create or append to sitecustomize.py to add these lines::
|
| +
|
| + import coverage
|
| + coverage.process_startup()
|
| +
|
| + #. Create a .pth file in your Python installation containing::
|
| +
|
| + import coverage; coverage.process_startup()
|
| +
|
| + """
|
| + cps = os.environ.get("COVERAGE_PROCESS_START")
|
| + if cps:
|
| + cov = coverage(config_file=cps, auto_data=True)
|
| + if os.environ.get("COVERAGE_COVERAGE"):
|
| + # Measuring coverage within coverage.py takes yet more trickery.
|
| + cov.cover_dir = "Please measure coverage.py!"
|
| + cov.start()
|
|
|