diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2022-09-16 17:48:02 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2022-09-16 18:17:55 -0400 |
commit | 7b690aa976a6bb30261d68344104f817a812a677 (patch) | |
tree | e81568929e612ee14674d7a71abf3370c6d346b1 | |
parent | 30d37e59e50b75c057a09fddf5b92869c0858949 (diff) | |
download | python-coveragepy-git-7b690aa976a6bb30261d68344104f817a812a677.tar.gz |
feat: --debug=pathmap will show details of re-mapping due to [paths] setting.
-rw-r--r-- | CHANGES.rst | 3 | ||||
-rw-r--r-- | coverage/control.py | 5 | ||||
-rw-r--r-- | coverage/files.py | 33 | ||||
-rw-r--r-- | coverage/sqldata.py | 3 | ||||
-rw-r--r-- | doc/cmd.rst | 3 | ||||
-rw-r--r-- | doc/config.rst | 3 | ||||
-rw-r--r-- | tests/test_files.py | 28 |
7 files changed, 63 insertions, 15 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index b2feb990..01d9f33e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -22,6 +22,9 @@ Unreleased - Packaging is now compliant with `PEP 517`_, closing `issue 1395`_. +- A new debug option ``--debug=pathmap`` shows details of the remapping of + paths that happens during combine due to the ``[paths]`` setting. + - Fix an internal problem with caching of invalid Python parsing. Found by OSS-Fuzz, fixing their `bug 50381`_. diff --git a/coverage/control.py b/coverage/control.py index 9c4a2ac7..5e1e54bf 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -743,7 +743,10 @@ class Coverage: aliases = None if self.config.paths: - aliases = PathAliases(relative=self.config.relative_files) + aliases = PathAliases( + debugfn=(self._debug.write if self._debug.should("pathmap") else None), + relative=self.config.relative_files, + ) for paths in self.config.paths.values(): result = paths[0] for pattern in paths[1:]: diff --git a/coverage/files.py b/coverage/files.py index b5895cfc..4d0c1a2b 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -3,8 +3,8 @@ """File wrangling.""" -import hashlib import fnmatch +import hashlib import ntpath import os import os.path @@ -326,15 +326,17 @@ class PathAliases: map a path through those aliases to produce a unified path. """ - def __init__(self, relative=False): - self.aliases = [] + 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): # pragma: debugging + def pprint(self): """Dump the important parts of the PathAliases, for debugging.""" - print(f"Aliases (relative={self.relative}):") - for regex, result in self.aliases: - print(f"{regex.pattern!r} --> {result!r}") + 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. @@ -349,6 +351,7 @@ class PathAliases: match an entire tree, and not just its root. """ + original_pattern = pattern pattern_sep = sep(pattern) if len(pattern) > 1: @@ -360,8 +363,7 @@ class PathAliases: # 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): + if not pattern.startswith('*') and not isabs_anywhere(pattern + pattern_sep): pattern = abs_file(pattern) if not pattern.endswith(pattern_sep): pattern += pattern_sep @@ -372,7 +374,7 @@ class PathAliases: # Normalize the result: it must end with a path separator. result_sep = sep(result) result = result.rstrip(r"\/") + result_sep - self.aliases.append((regex, result)) + self.aliases.append((original_pattern, regex, result)) def map(self, path): """Map `path` through the aliases. @@ -390,14 +392,23 @@ class PathAliases: of `path` unchanged. """ - for regex, result in self.aliases: + 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 diff --git a/coverage/sqldata.py b/coverage/sqldata.py index 564d4ec9..5d62b15b 100644 --- a/coverage/sqldata.py +++ b/coverage/sqldata.py @@ -609,8 +609,7 @@ class CoverageData(SimpleReprMixin): aliases = aliases or PathAliases() - # Force the database we're writing to to exist before we start nesting - # contexts. + # Force the database we're writing to to exist before we start nesting contexts. self._start_using() # Collector for all arcs, lines and tracers diff --git a/doc/cmd.rst b/doc/cmd.rst index e2a60fc2..c05b7bce 100644 --- a/doc/cmd.rst +++ b/doc/cmd.rst @@ -1000,6 +1000,9 @@ of operation to log: * ``multiproc``: log the start and stop of multiprocessing processes. +* ``pathmap``: log the remapping of paths that happens during ``coverage + combine`` due to the ``[paths]`` setting. See :ref:`config_paths`. + * ``pid``: annotate all warnings and debug output with the process and thread ids. diff --git a/doc/config.rst b/doc/config.rst index 1b6f128d..66b02eac 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -350,6 +350,9 @@ against the source file found at "src/module.py". If you specify more than one list of paths, they will be considered in order. The first list that has a match will be used. +The ``--debug=pathmap`` option can be used to log details of the re-mapping of +paths. See :ref:`the --debug option <cmd_run_debug>`. + See :ref:`cmd_combine` for more information. diff --git a/tests/test_files.py b/tests/test_files.py index 9f7f6278..b1e85610 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -289,11 +289,37 @@ class PathAliasesTest(CoverageTest): self.assert_unchanged(aliases, '/home/foo/srcetc') def test_multiple_patterns(self, rel_yn): - aliases = PathAliases(relative=rel_yn) + # also test the debugfn... + msgs = [] + aliases = PathAliases(debugfn=msgs.append, relative=rel_yn) aliases.add('/home/*/src', './mysrc') aliases.add('/lib/*/libsrc', './mylib') self.assert_mapped(aliases, '/home/foo/src/a.py', './mysrc/a.py', relative=rel_yn) self.assert_mapped(aliases, '/lib/foo/libsrc/a.py', './mylib/a.py', relative=rel_yn) + if rel_yn: + assert msgs == [ + "Aliases (relative=True):", + " Rule: '/home/*/src' -> './mysrc/' using regex " + + "'(?:(?s:[\\\\\\\\/]home[\\\\\\\\/].*[\\\\\\\\/]src[\\\\\\\\/]))'", + " Rule: '/lib/*/libsrc' -> './mylib/' using regex " + + "'(?:(?s:[\\\\\\\\/]lib[\\\\\\\\/].*[\\\\\\\\/]libsrc[\\\\\\\\/]))'", + "Matched path '/home/foo/src/a.py' to rule '/home/*/src' -> './mysrc/', " + + "producing './mysrc/a.py'", + "Matched path '/lib/foo/libsrc/a.py' to rule '/lib/*/libsrc' -> './mylib/', " + + "producing './mylib/a.py'", + ] + else: + assert msgs == [ + "Aliases (relative=False):", + " Rule: '/home/*/src' -> './mysrc/' using regex " + + "'(?:(?s:[\\\\\\\\/]home[\\\\\\\\/].*[\\\\\\\\/]src[\\\\\\\\/]))'", + " Rule: '/lib/*/libsrc' -> './mylib/' using regex " + + "'(?:(?s:[\\\\\\\\/]lib[\\\\\\\\/].*[\\\\\\\\/]libsrc[\\\\\\\\/]))'", + "Matched path '/home/foo/src/a.py' to rule '/home/*/src' -> './mysrc/', " + + f"producing {files.canonical_filename('./mysrc/a.py')!r}", + "Matched path '/lib/foo/libsrc/a.py' to rule '/lib/*/libsrc' -> './mylib/', " + + f"producing {files.canonical_filename('./mylib/a.py')!r}", + ] @pytest.mark.parametrize("badpat", [ "/ned/home/*", |