Skip to content

Commit bc52a22

Browse files
committed
debug: re-organize Matchers to show more of what they do
1 parent f338d81 commit bc52a22

File tree

4 files changed

+84
-63
lines changed

4 files changed

+84
-63
lines changed

coverage/files.py

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from __future__ import annotations
77

8+
import abc
89
import hashlib
910
import ntpath
1011
import os
@@ -16,8 +17,7 @@
1617

1718
from coverage import env
1819
from coverage.exceptions import ConfigError
19-
from coverage.misc import human_sorted, isolate_module, join_regex
20-
from coverage.types import TMatcher
20+
from coverage.misc import human_sorted, isolate_module, join_regex, plural
2121

2222
os = isolate_module(os)
2323

@@ -215,26 +215,59 @@ def prep_patterns(patterns: Iterable[str]) -> list[str]:
215215
return prepped
216216

217217

218-
class TreeMatcher(TMatcher):
219-
"""A matcher for files in a tree.
218+
DebugFn = Callable[[str], None] | None
220219

221-
Construct with a list of paths, either files or directories. Paths match
222-
with the `match` method if they are one of the files, or if they are
223-
somewhere in a subtree rooted at one of the directories.
224220

225-
"""
221+
class Matcher(abc.ABC):
222+
"""Common behavior for matchers."""
226223

227-
def __init__(self, paths: Iterable[str], name: str = "unknown") -> None:
228-
self.original_paths: list[str] = human_sorted(paths)
229-
self.paths = [os.path.normcase(p) for p in paths]
224+
def __init__(self, strs: list[str], name: str, caption: str, debug: DebugFn) -> None:
225+
self.strs = strs
230226
self.name = name
227+
if debug:
228+
debug(f"{caption} matching {self}")
229+
for inf in self.info():
230+
debug(f" {inf}")
231+
232+
def __str__(self) -> str:
233+
n = len(self.strs)
234+
return f"{self.__class__.__name__} {self.name!r} {n} {plural(n, 'item')}"
231235

232236
def __repr__(self) -> str:
233-
return f"<TreeMatcher {self.name} {self.original_paths!r}>"
237+
return f"<{self.__class__.__name__} {self.name} {self.strs!r}>"
238+
239+
@abc.abstractmethod
240+
def match(self, s: str) -> bool:
241+
"""Does this string match?"""
234242

235243
def info(self) -> list[str]:
236244
"""A list of strings for displaying when dumping state."""
237-
return self.original_paths
245+
return self.strs
246+
247+
248+
class TreeMatcher(Matcher):
249+
"""A matcher for files in a tree.
250+
251+
Construct with a list of paths, either files or directories. Paths match
252+
with the `match` method if they are one of the files, or if they are
253+
somewhere in a subtree rooted at one of the directories.
254+
"""
255+
256+
def __init__(
257+
self,
258+
paths: Iterable[str],
259+
name: str = "unknown",
260+
caption: str = "",
261+
debug: DebugFn = None,
262+
) -> None:
263+
self.original_paths = human_sorted(paths)
264+
super().__init__(self.original_paths, name=name, caption=caption, debug=debug)
265+
self.paths = []
266+
for p in paths:
267+
ap = os.path.normcase(p)
268+
if ap != p and debug:
269+
debug(f" Normalized {p!r} to {ap!r}")
270+
self.paths.append(ap)
238271

239272
def match(self, fpath: str) -> bool: # pylint: disable=arguments-renamed
240273
"""Does `fpath` indicate a file in one of our trees?"""
@@ -250,19 +283,18 @@ def match(self, fpath: str) -> bool: # pylint: disable=arguments-renamed
250283
return False
251284

252285

253-
class ModuleMatcher(TMatcher):
286+
class ModuleMatcher(Matcher):
254287
"""A matcher for modules in a tree."""
255288

256-
def __init__(self, module_names: Iterable[str], name: str = "unknown") -> None:
289+
def __init__(
290+
self,
291+
module_names: Iterable[str],
292+
name: str = "unknown",
293+
caption: str = "",
294+
debug: DebugFn = None,
295+
) -> None:
257296
self.modules = list(module_names)
258-
self.name = name
259-
260-
def __repr__(self) -> str:
261-
return f"<ModuleMatcher {self.name} {self.modules!r}>"
262-
263-
def info(self) -> list[str]:
264-
"""A list of strings for displaying when dumping state."""
265-
return self.modules
297+
super().__init__(self.modules, name=name, caption=caption, debug=debug)
266298

267299
def match(self, module_name: str) -> bool: # pylint: disable=arguments-renamed
268300
"""Does `module_name` indicate a module in one of our packages?"""
@@ -280,20 +312,19 @@ def match(self, module_name: str) -> bool: # pylint: disable=arguments-renamed
280312
return False
281313

282314

283-
class GlobMatcher(TMatcher):
315+
class GlobMatcher(Matcher):
284316
"""A matcher for files by file name pattern."""
285317

286-
def __init__(self, pats: Iterable[str], name: str = "unknown") -> None:
318+
def __init__(
319+
self,
320+
pats: Iterable[str],
321+
name: str = "unknown",
322+
caption: str = "",
323+
debug: DebugFn = None,
324+
) -> None:
287325
self.pats = list(pats)
326+
super().__init__(self.pats, name=name, caption=caption, debug=debug)
288327
self.re = globs_to_regex(self.pats, case_insensitive=env.WINDOWS)
289-
self.name = name
290-
291-
def __repr__(self) -> str:
292-
return f"<GlobMatcher {self.name} {self.pats!r}>"
293-
294-
def info(self) -> list[str]:
295-
"""A list of strings for displaying when dumping state."""
296-
return self.pats
297328

298329
def match(self, fpath: str) -> bool: # pylint: disable=arguments-renamed
299330
"""Does `fpath` match one of our file name patterns?"""

coverage/inorout.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -206,46 +206,42 @@ def _debug(msg: str) -> None:
206206
self.debug.write(msg)
207207

208208
# Generally useful information
209-
_debug("sys.path:" + "".join(f"\n {p}" for p in sys.path))
209+
_debug("sys.path:" + "".join(f"\n {p!r}" for p in sys.path))
210210

211211
if self.debug:
212212
_debug("sysconfig paths:")
213213
for scheme in sorted(sysconfig.get_scheme_names()):
214214
_debug(f" {scheme}:")
215215
for k, v in sysconfig.get_paths(scheme).items():
216-
_debug(f" {k}: {v}")
216+
_debug(f" {k}: {v!r}")
217217

218218
# Create the matchers we need for should_trace
219219
self.source_match = None
220220
self.source_pkgs_match = None
221221
self.pylib_match = None
222-
self.include_match = self.omit_match = None
222+
self.include_match = None
223+
self.omit_match = None
223224

224225
if self.source_dirs or self.source_pkgs:
225-
against = []
226226
if self.source_dirs:
227-
self.source_match = TreeMatcher(self.source_dirs, "source")
228-
against.append(f"trees {self.source_match!r}")
227+
self.source_match = TreeMatcher(
228+
self.source_dirs, "source", "Source directory", _debug
229+
)
229230
if self.source_pkgs:
230-
self.source_pkgs_match = ModuleMatcher(self.source_pkgs, "source_pkgs")
231-
against.append(f"modules {self.source_pkgs_match!r}")
232-
_debug("Source matching against " + " and ".join(against))
231+
self.source_pkgs_match = ModuleMatcher(
232+
self.source_pkgs, "source_pkgs", "Source imports", _debug
233+
)
233234
else:
234235
if self.pylib_paths:
235-
self.pylib_match = TreeMatcher(self.pylib_paths, "pylib")
236-
_debug(f"Python stdlib matching: {self.pylib_match!r}")
236+
self.pylib_match = TreeMatcher(self.pylib_paths, "pylib", "Python stdlib", _debug)
237237
if self.include:
238-
self.include_match = GlobMatcher(self.include, "include")
239-
_debug(f"Include matching: {self.include_match!r}")
238+
self.include_match = GlobMatcher(self.include, "include", "Include", _debug)
240239
if self.omit:
241-
self.omit_match = GlobMatcher(self.omit, "omit")
242-
_debug(f"Omit matching: {self.omit_match!r}")
240+
self.omit_match = GlobMatcher(self.omit, "omit", "Omit", _debug)
243241

244-
self.cover_match = TreeMatcher(self.cover_paths, "coverage")
245-
_debug(f"Coverage code matching: {self.cover_match!r}")
242+
self.cover_match = TreeMatcher(self.cover_paths, "coverage", "Coverage code", _debug)
246243

247-
self.third_match = TreeMatcher(self.third_paths, "third")
248-
_debug(f"Third-party lib matching: {self.third_match!r}")
244+
self.third_match = TreeMatcher(self.third_paths, "third", "Third-party lib", _debug)
249245

250246
# Check if the source we want to measure has been installed as a
251247
# third-party package.
@@ -277,8 +273,9 @@ def _debug(msg: str) -> None:
277273
if self.third_match.match(src):
278274
_debug(f"Source in third-party: source directory {src!r}")
279275
self.source_in_third_paths.add(src)
280-
self.source_in_third_match = TreeMatcher(self.source_in_third_paths, "source_in_third")
281-
_debug(f"Source in third-party matching: {self.source_in_third_match}")
276+
self.source_in_third_match = TreeMatcher(
277+
self.source_in_third_paths, "source_in_third", "Source in third-party", _debug
278+
)
282279

283280
self.plugins: Plugins
284281
self.disp_class: type[TFileDisposition] = FileDisposition

coverage/types.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,6 @@ class TFileDisposition(Protocol):
6565
has_dynamic_filename: bool
6666

6767

68-
class TMatcher(Protocol):
69-
"""The shape all Matchers have."""
70-
71-
def match(self, s: str) -> bool:
72-
"""Does this string match?"""
73-
74-
7568
# When collecting data, we use a dictionary with a few possible shapes. The
7669
# keys are always file names.
7770
# - If measuring line coverage, the values are sets of line numbers.

tests/test_files.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from coverage.exceptions import ConfigError
2121
from coverage.files import (
2222
GlobMatcher,
23+
Matcher,
2324
ModuleMatcher,
2425
PathAliases,
2526
TreeMatcher,
@@ -29,7 +30,6 @@
2930
flat_rootname,
3031
globs_to_regex,
3132
)
32-
from coverage.types import TMatcher
3333

3434
from tests.coveragetest import CoverageTest
3535
from tests.helpers import os_sep
@@ -369,7 +369,7 @@ def setUp(self) -> None:
369369
super().setUp()
370370
files.set_relative_directory()
371371

372-
def assertMatches(self, matcher: TMatcher, filepath: str, matches: bool) -> None:
372+
def assertMatches(self, matcher: Matcher, filepath: str, matches: bool) -> None:
373373
"""The `matcher` should agree with `matches` about `filepath`."""
374374
canonical = files.canonical_filename(filepath)
375375
msg = f"File {filepath} should have matched as {matches}"

0 commit comments

Comments
 (0)