summaryrefslogtreecommitdiff
path: root/coverage
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2019-11-17 07:52:24 -0500
committerNed Batchelder <ned@nedbatchelder.com>2019-12-01 08:35:04 -0500
commit013e9de08c0747fddf92cc1fdac1d1f39185aa3b (patch)
treeada87dee565d5b167e424df6d9143ec49de349fa /coverage
parent10e41ab0dc165b3fd010345ab0fb1ed319d2f230 (diff)
downloadpython-coveragepy-git-013e9de08c0747fddf92cc1fdac1d1f39185aa3b.tar.gz
Experimental: relative_files to support relative file names.
Diffstat (limited to 'coverage')
-rw-r--r--coverage/collector.py30
-rw-r--r--coverage/config.py4
-rw-r--r--coverage/control.py14
-rw-r--r--coverage/results.py4
4 files changed, 31 insertions, 21 deletions
diff --git a/coverage/collector.py b/coverage/collector.py
index 703f65b8..01acb78d 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -10,7 +10,6 @@ from coverage import env
from coverage.backward import litems, range # pylint: disable=redefined-builtin
from coverage.debug import short_stack
from coverage.disposition import FileDisposition
-from coverage.files import abs_file
from coverage.misc import CoverageException, isolate_module
from coverage.pytracer import PyTracer
@@ -59,7 +58,7 @@ class Collector(object):
SUPPORTED_CONCURRENCIES = set(["greenlet", "eventlet", "gevent", "thread"])
def __init__(
- self, should_trace, check_include, should_start_context,
+ self, should_trace, check_include, should_start_context, file_mapper,
timid, branch, warn, concurrency,
):
"""Create a collector.
@@ -75,6 +74,10 @@ class Collector(object):
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
@@ -97,6 +100,7 @@ class Collector(object):
self.should_trace = should_trace
self.check_include = check_include
self.should_start_context = should_start_context
+ self.file_mapper = file_mapper
self.warn = warn
self.branch = branch
self.threading = None
@@ -107,7 +111,7 @@ class Collector(object):
self.origin = short_stack()
self.concur_id_func = None
- self.abs_file_cache = {}
+ self.mapped_file_cache = {}
# We can handle a few concurrency options here, but only one at a time.
these_concurrencies = self.SUPPORTED_CONCURRENCIES.intersection(concurrency)
@@ -381,16 +385,16 @@ class Collector(object):
context = new_context
self.covdata.set_context(context)
- def cached_abs_file(self, filename):
- """A locally cached version of `abs_file`."""
+ def cached_mapped_file(self, filename):
+ """A locally cached version of file names mapped through file_mapper."""
key = (type(filename), filename)
try:
- return self.abs_file_cache[key]
+ return self.mapped_file_cache[key]
except KeyError:
- return self.abs_file_cache.setdefault(key, abs_file(filename))
+ return self.mapped_file_cache.setdefault(key, self.file_mapper(filename))
- def abs_file_dict(self, d):
- """Return a dict like d, but with keys modified by `abs_file`."""
+ def mapped_file_dict(self, d):
+ """Return a dict like d, but with keys modified by file_mapper."""
# The call to litems() 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
@@ -406,7 +410,7 @@ class Collector(object):
else:
raise runtime_err
- return dict((self.cached_abs_file(k), v) for k, v in items if v)
+ return dict((self.cached_mapped_file(k), v) for k, v in items if v)
def flush_data(self):
"""Save the collected data to our associated `CoverageData`.
@@ -420,10 +424,10 @@ class Collector(object):
return False
if self.branch:
- self.covdata.add_arcs(self.abs_file_dict(self.data))
+ self.covdata.add_arcs(self.mapped_file_dict(self.data))
else:
- self.covdata.add_lines(self.abs_file_dict(self.data))
- self.covdata.add_file_tracers(self.abs_file_dict(self.file_tracers))
+ self.covdata.add_lines(self.mapped_file_dict(self.data))
+ self.covdata.add_file_tracers(self.mapped_file_dict(self.file_tracers))
self._clear_data()
return True
diff --git a/coverage/config.py b/coverage/config.py
index b8789fbf..997fc036 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -190,9 +190,10 @@ class CoverageConfig(object):
self.note = None
self.parallel = False
self.plugins = []
- self.source = None
+ self.relative_files = False
self.run_include = None
self.run_omit = None
+ self.source = None
self.timid = False
# Defaults for [report]
@@ -353,6 +354,7 @@ class CoverageConfig(object):
('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'),
('source', 'run:source', 'list'),
diff --git a/coverage/control.py b/coverage/control.py
index 0f306aa2..3a19695a 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -20,7 +20,7 @@ from coverage.context import should_start_context_test_function, combine_context
from coverage.data import CoverageData, combine_parallel_data
from coverage.debug import DebugControl, write_formatted_info
from coverage.disposition import disposition_debug_msg
-from coverage.files import PathAliases, set_relative_directory, abs_file
+from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory
from coverage.html import HtmlReporter
from coverage.inorout import InOrOut
from coverage.jsonreport import JsonReporter
@@ -198,6 +198,7 @@ class Coverage(object):
self._data_suffix = self._run_suffix = None
self._exclude_re = None
self._debug = None
+ self._file_mapper = None
# State machine variables:
# Have we initialized everything?
@@ -238,6 +239,7 @@ class Coverage(object):
self._exclude_re = {}
set_relative_directory()
+ self._file_mapper = relative_filename if self.config.relative_files else abs_file
# Load plugins
self._plugins = Plugins.load_plugins(self.config.plugins, self.config, self._debug)
@@ -405,6 +407,7 @@ class Coverage(object):
should_trace=self._should_trace,
check_include=self._check_include_omit_etc,
should_start_context=should_start_context,
+ file_mapper=self._file_mapper,
timid=self.config.timid,
branch=self.config.branch,
warn=self._warn,
@@ -671,6 +674,7 @@ class Coverage(object):
# mark completely unexecuted files as 0% covered.
if self._data:
for file_path, plugin_name in self._inorout.find_possibly_unexecuted_files():
+ file_path = self._file_mapper(file_path)
self._data.touch_file(file_path, plugin_name)
if self.config.note:
@@ -723,7 +727,7 @@ class Coverage(object):
if not isinstance(it, FileReporter):
it = self._get_file_reporter(it)
- return Analysis(data, it)
+ return Analysis(data, it, self._file_mapper)
def _get_file_reporter(self, morf):
"""Get a FileReporter for a module or file name."""
@@ -731,13 +735,13 @@ class Coverage(object):
file_reporter = "python"
if isinstance(morf, string_class):
- abs_morf = abs_file(morf)
- plugin_name = self._data.file_tracer(abs_morf)
+ mapped_morf = self._file_mapper(morf)
+ plugin_name = self._data.file_tracer(mapped_morf)
if plugin_name:
plugin = self._plugins.get(plugin_name)
if plugin:
- file_reporter = plugin.file_reporter(abs_morf)
+ file_reporter = plugin.file_reporter(mapped_morf)
if file_reporter is None:
raise CoverageException(
"Plugin %r did not provide a file reporter for %r." % (
diff --git a/coverage/results.py b/coverage/results.py
index c88da919..ae8366bf 100644
--- a/coverage/results.py
+++ b/coverage/results.py
@@ -13,10 +13,10 @@ from coverage.misc import contract, CoverageException, nice_pair
class Analysis(object):
"""The results of analyzing a FileReporter."""
- def __init__(self, data, file_reporter):
+ def __init__(self, data, file_reporter, file_mapper):
self.data = data
self.file_reporter = file_reporter
- self.filename = self.file_reporter.filename
+ self.filename = file_mapper(self.file_reporter.filename)
self.statements = self.file_reporter.lines()
self.excluded = self.file_reporter.excluded_lines()