diff --git a/coverage/control.py b/coverage/control.py index d60db2126..9637b0c27 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -678,15 +678,12 @@ def combine(self, data_paths=None, strict=False): self._post_init() self.get_data() - aliases = None - if self.config.paths: - aliases = PathAliases() - for paths in self.config.paths.values(): - result = paths[0] - for pattern in paths[1:]: - aliases.add(pattern, result) - - combine_parallel_data(self._data, aliases=aliases, data_paths=data_paths, strict=strict) + combine_parallel_data( + self._data, + aliases=PathAliases.configure(self.config), + data_paths=data_paths, + strict=strict, + ) def get_data(self): """Get the collected data. diff --git a/coverage/files.py b/coverage/files.py index 5c2ff1ace..dc54adf98 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -64,12 +64,13 @@ def canonical_filename(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) + f = os.path.join(str_filename(path), str_filename(filename)) except UnicodeError: - exists = False - if exists: + continue + + if os.path.exists(f): cf = f break cf = abs_file(cf) @@ -143,6 +144,21 @@ def actual_path(filename): return filename +if env.PY2: + def str_filename(filename): + return ( + filename + if isinstance(filename, str) + else filename.encode( + sys.getfilesystemencoding() or sys.getdefaultencoding() + ) + ) +else: + @contract(filename='unicode', returns='unicode') + def str_filename(filename): + return filename + + if env.PY2: @contract(returns='unicode') def unicode_filename(filename): @@ -215,7 +231,8 @@ class TreeMatcher(object): somewhere in a subtree rooted at one of the directories. """ - def __init__(self, paths): + def __init__(self, paths, aliases=None): + self.aliases = aliases self.paths = list(paths) def __repr__(self): @@ -227,6 +244,9 @@ def info(self): def match(self, fpath): """Does `fpath` indicate a file in one of our trees?""" + if self.aliases is not None: + fpath = self.aliases.map(fpath) + for p in self.paths: if fpath.startswith(p): if fpath == p: @@ -268,7 +288,8 @@ def match(self, module_name): class FnmatchMatcher(object): """A matcher for files by file name pattern.""" - def __init__(self, pats): + def __init__(self, pats, aliases=None): + self.aliases = aliases self.pats = list(pats) self.re = fnmatches_to_regex(self.pats, case_insensitive=env.WINDOWS) @@ -281,6 +302,8 @@ def info(self): def match(self, fpath): """Does `fpath` match one of our file name patterns?""" + if self.aliases is not None: + fpath = self.aliases.map(fpath) return self.re.match(fpath) is not None @@ -408,6 +431,21 @@ def map(self, path): return path + @classmethod + def configure(cls, config): + paths = config.paths + if not paths: + return None + + aliases = PathAliases() + for paths in paths.values(): + result = paths[0] + for pattern in paths[1:]: + aliases.add(pattern, result) + return aliases + + + def find_python_files(dirname): """Yield all of the importable Python files in `dirname`, recursively. diff --git a/coverage/inorout.py b/coverage/inorout.py index ec5f2c1ac..3ee596bbd 100644 --- a/coverage/inorout.py +++ b/coverage/inorout.py @@ -17,7 +17,7 @@ from coverage.backward import code_object from coverage.disposition import FileDisposition, disposition_init from coverage.files import TreeMatcher, FnmatchMatcher, ModuleMatcher -from coverage.files import prep_patterns, find_python_files, canonical_filename +from coverage.files import prep_patterns, find_python_files, canonical_filename, PathAliases from coverage.misc import CoverageException from coverage.python import source_for_file, source_for_morf @@ -132,6 +132,7 @@ def __init__(self, warn, debug): def configure(self, config): """Apply the configuration to get ready for decision-time.""" + aliases = PathAliases.configure(config) for src in config.source or []: if os.path.isdir(src): self.source.append(canonical_filename(src)) @@ -186,7 +187,7 @@ def debug(msg): if self.source or self.source_pkgs: against = [] if self.source: - self.source_match = TreeMatcher(self.source) + self.source_match = TreeMatcher(self.source, aliases=aliases) against.append("trees {!r}".format(self.source_match)) if self.source_pkgs: self.source_pkgs_match = ModuleMatcher(self.source_pkgs) @@ -194,16 +195,16 @@ def debug(msg): debug("Source matching against " + " and ".join(against)) else: if self.cover_paths: - self.cover_match = TreeMatcher(self.cover_paths) + self.cover_match = TreeMatcher(self.cover_paths, aliases=aliases) debug("Coverage code matching: {!r}".format(self.cover_match)) if self.pylib_paths: - self.pylib_match = TreeMatcher(self.pylib_paths) + self.pylib_match = TreeMatcher(self.pylib_paths, aliases=aliases) debug("Python stdlib matching: {!r}".format(self.pylib_match)) if self.include: - self.include_match = FnmatchMatcher(self.include) + self.include_match = FnmatchMatcher(self.include, aliases=aliases) debug("Include matching: {!r}".format(self.include_match)) if self.omit: - self.omit_match = FnmatchMatcher(self.omit) + self.omit_match = FnmatchMatcher(self.omit, aliases=aliases) debug("Omit matching: {!r}".format(self.omit_match)) def should_trace(self, filename, frame=None): diff --git a/tests/test_process.py b/tests/test_process.py index 25f1fcc9d..b776e17f3 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -1181,9 +1181,12 @@ def test_aliases_used_in_messages(self): "coverage-%d.%d" % sys.version_info[:2], ] for cmd in cmds: - out = self.run_command("%s foobar" % cmd) + out = self.run_command("{} foobar".format(cmd)) self.assertIn("Unknown command: 'foobar'", out) - self.assertIn("Use '%s help' for help" % cmd, out) + self.assertIn("Use '{} help' for help".format(cmd), out) + + out = self.run_command("{} help version".format(cmd)) + self.assertIn("ersion {coverage.__version__}".format(coverage=coverage), out) class PydocTest(CoverageTest):