Coverage for coverage / inorout.py: 84.082%
320 statements
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
4"""Determining whether files are being measured/reported or not."""
6from __future__ import annotations 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
8import importlib.util 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
9import inspect 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
10import itertools 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
11import os 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
12import os.path 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
13import platform 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
14import re 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
15import sys 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
16import sysconfig 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
17import traceback 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
18from collections.abc import Iterable 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
19from types import FrameType, ModuleType 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
20from typing import TYPE_CHECKING, Any, cast 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
22from coverage import env 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
23from coverage.disposition import FileDisposition, disposition_init 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
24from coverage.exceptions import ConfigError, CoverageException, PluginError 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
25from coverage.files import ( 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
26 GlobMatcher,
27 ModuleMatcher,
28 TreeMatcher,
29 canonical_filename,
30 find_python_files,
31 prep_patterns,
32)
33from coverage.misc import isolate_module, sys_modules_saved 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
34from coverage.python import source_for_file, source_for_morf 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
35from coverage.types import TDebugCtl, TFileDisposition, TMorf, TWarnFn 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
37if TYPE_CHECKING: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
38 from coverage.config import CoverageConfig
39 from coverage.plugin_support import Plugins
42modules_we_happen_to_have: list[ModuleType] = [ 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
43 inspect,
44 itertools,
45 os,
46 platform,
47 re,
48 sysconfig,
49 traceback,
50]
52if env.PYPY: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
53 # Pypy has some unusual stuff in the "stdlib". Consider those locations
54 # when deciding where the stdlib is. These modules are not used for anything,
55 # they are modules importable from the pypy lib directories, so that we can
56 # find those directories.
57 import _pypy_irc_topic # pylint: disable=import-error 1BCD
58 import _structseq # pylint: disable=import-error 1BCD
60 modules_we_happen_to_have.extend([_structseq, _pypy_irc_topic]) 1BCD
63os = isolate_module(os) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
66def canonical_path(morf: TMorf, directory: bool = False) -> str: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
67 """Return the canonical path of the module or file `morf`.
69 If the module is a package, then return its directory. If it is a
70 module, then return its file, unless `directory` is True, in which
71 case return its enclosing directory.
73 """
74 morf_path = canonical_filename(source_for_morf(morf)) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
75 if morf_path.endswith("__init__.py") or directory: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
76 morf_path = os.path.split(morf_path)[0] 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
77 return morf_path 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
80def name_for_module(filename: str, frame: FrameType | None) -> str | None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
81 """Get the name of the module for a filename and frame.
83 For configurability's sake, we allow __main__ modules to be matched by
84 their importable name.
86 If loaded via runpy (aka -m), we can usually recover the "original"
87 full dotted module name, otherwise, we resort to interpreting the
88 file name to get the module's name. In the case that the module name
89 can't be determined, None is returned.
91 """
92 module_globals = frame.f_globals if frame is not None else {} 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
93 dunder_name: str | None = module_globals.get("__name__", None) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
95 if isinstance(dunder_name, str) and dunder_name != "__main__": 95 ↛ 97line 95 didn't jump to line 97 because the condition on line 95 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
96 # This is the usual case: an imported module.
97 return dunder_name
99 spec = module_globals.get("__spec__", None) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
100 if spec: 100 ↛ 101line 100 didn't jump to line 101 because the condition on line 100 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
101 fullname = spec.name
102 if isinstance(fullname, str) and fullname != "__main__":
103 # Module loaded via: runpy -m
104 return fullname
106 # Script as first argument to Python command line.
107 inspectedname = inspect.getmodulename(filename) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
108 if inspectedname is not None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
109 return inspectedname 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
110 else:
111 return dunder_name 1aEbFcGdHeIfJgKhLiMjNkO5lP6mQ7nR8oS9pT!qU#rV$BC
114def module_is_namespace(mod: ModuleType) -> bool: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
115 """Is the module object `mod` a PEP420 namespace module?"""
116 return hasattr(mod, "__path__") and getattr(mod, "__file__", None) is None 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
119def module_has_file(mod: ModuleType) -> bool: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
120 """Does the module object `mod` have an existing __file__ ?"""
121 mod__file__ = getattr(mod, "__file__", None) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
122 if mod__file__ is None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
123 return False 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
124 return os.path.exists(mod__file__) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
127def file_and_path_for_module(modulename: str) -> tuple[str | None, list[str]]: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
128 """Find the file and search path for `modulename`.
130 Returns:
131 filename: The filename of the module, or None.
132 path: A list (possibly empty) of directories to find submodules in.
134 """
135 filename = None 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
136 path = [] 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
137 try: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
138 spec = importlib.util.find_spec(modulename) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
139 except Exception:
140 pass
141 else:
142 if spec is not None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
143 filename = spec.origin 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
144 path = list(spec.submodule_search_locations or ()) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
145 return filename, path 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
148def add_stdlib_paths(paths: set[str]) -> None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
149 """Add paths where the stdlib can be found to the set `paths`."""
150 # Look at where some standard modules are located. That's the
151 # indication for "installed with the interpreter". In some
152 # environments (virtualenv, for example), these modules may be
153 # spread across a few locations. Look at all the candidate modules
154 # we've imported, and take all the different ones.
155 for m in modules_we_happen_to_have: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
156 if hasattr(m, "__file__"): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
157 paths.add(canonical_path(m, directory=True)) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
160def add_third_party_paths(paths: set[str]) -> None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
161 """Add locations for third-party packages to the set `paths`."""
162 # Get the paths that sysconfig knows about.
163 scheme_names = set(sysconfig.get_scheme_names()) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
165 for scheme in scheme_names: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
166 # https://foss.heptapod.net/pypy/pypy/-/issues/3433
167 better_scheme = "pypy_posix" if scheme == "pypy" else scheme 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
168 if os.name in better_scheme.split("_"): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
169 config_paths = sysconfig.get_paths(scheme) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
170 for path_name in ["platlib", "purelib", "scripts"]: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
171 paths.add(config_paths[path_name]) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
174def add_coverage_paths(paths: set[str]) -> None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
175 """Add paths where coverage.py code can be found to the set `paths`."""
176 cover_path = canonical_path(__file__, directory=True) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
177 paths.add(cover_path) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
178 if env.TESTING: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
179 # Don't include our own test code.
180 paths.add(os.path.join(cover_path, "tests")) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
183class InOrOut: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
184 """Machinery for determining what files to measure."""
186 def __init__( 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
187 self,
188 config: CoverageConfig,
189 warn: TWarnFn,
190 debug: TDebugCtl | None,
191 include_namespace_packages: bool,
192 ) -> None:
193 self.warn = warn 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
194 self.debug = debug 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
195 self.include_namespace_packages = include_namespace_packages 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
197 self.source_pkgs: list[str] = [] 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
198 self.source_pkgs.extend(config.source_pkgs) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
199 self.source_dirs: list[str] = [] 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
200 self.source_dirs.extend(config.source_dirs) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
201 for src in config.source or []: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
202 if os.path.isdir(src): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
203 self.source_dirs.append(src) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
204 else:
205 self.source_pkgs.append(src) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
207 # Canonicalize everything in `source_dirs`.
208 # Also confirm that they actually are directories.
209 for i, src in enumerate(self.source_dirs): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
210 if not os.path.isdir(src): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
211 raise ConfigError(f"Source dir is not a directory: {src!r}") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kOlPx1mQnRy2oSpTz3qUrVA4BCD
212 self.source_dirs[i] = canonical_filename(src) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
214 self.source_pkgs_unmatched = self.source_pkgs[:] 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
216 self.include = prep_patterns(config.run_include) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
217 self.omit = prep_patterns(config.run_omit) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
219 # The directories for files considered "installed with the interpreter".
220 self.pylib_paths: set[str] = set() 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
221 if not config.cover_pylib: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
222 add_stdlib_paths(self.pylib_paths) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
224 # To avoid tracing the coverage.py code itself, we skip anything
225 # located where we are.
226 self.cover_paths: set[str] = set() 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
227 add_coverage_paths(self.cover_paths) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
229 # Find where third-party packages are installed.
230 self.third_paths: set[str] = set() 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
231 add_third_party_paths(self.third_paths) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
233 def _debug(msg: str) -> None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
234 if self.debug: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
235 self.debug.write(msg) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
237 # The matchers for should_trace.
239 # Generally useful information
240 _debug("sys.path:" + "".join(f"\n {p}" for p in sys.path)) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
242 # Create the matchers we need for should_trace
243 self.source_match = None 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
244 self.source_pkgs_match = None 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
245 self.pylib_match = None 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
246 self.include_match = self.omit_match = None 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
248 if self.source_dirs or self.source_pkgs: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
249 against = [] 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
250 if self.source_dirs: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
251 self.source_match = TreeMatcher(self.source_dirs, "source") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
252 against.append(f"trees {self.source_match!r}") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
253 if self.source_pkgs: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
254 self.source_pkgs_match = ModuleMatcher(self.source_pkgs, "source_pkgs") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
255 against.append(f"modules {self.source_pkgs_match!r}") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
256 _debug("Source matching against " + " and ".join(against)) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
257 else:
258 if self.pylib_paths: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
259 self.pylib_match = TreeMatcher(self.pylib_paths, "pylib") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
260 _debug(f"Python stdlib matching: {self.pylib_match!r}") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
261 if self.include: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
262 self.include_match = GlobMatcher(self.include, "include") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
263 _debug(f"Include matching: {self.include_match!r}") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
264 if self.omit: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
265 self.omit_match = GlobMatcher(self.omit, "omit") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
266 _debug(f"Omit matching: {self.omit_match!r}") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
268 self.cover_match = TreeMatcher(self.cover_paths, "coverage") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
269 _debug(f"Coverage code matching: {self.cover_match!r}") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
271 self.third_match = TreeMatcher(self.third_paths, "third") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
272 _debug(f"Third-party lib matching: {self.third_match!r}") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
274 # Check if the source we want to measure has been installed as a
275 # third-party package.
276 # Is the source inside a third-party area?
277 self.source_in_third_paths = set() 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
278 with sys_modules_saved(): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
279 for pkg in self.source_pkgs: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
280 try: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
281 modfile, path = file_and_path_for_module(pkg) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
282 _debug(f"Imported source package {pkg!r} as {modfile!r}") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
283 except CoverageException as exc:
284 _debug(f"Couldn't import source package {pkg!r}: {exc}")
285 continue
286 if modfile: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
287 if self.third_match.match(modfile): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
288 _debug( 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
289 f"Source in third-party: source_pkg {pkg!r} at {modfile!r}",
290 )
291 self.source_in_third_paths.add(canonical_path(source_for_file(modfile))) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
292 else:
293 for pathdir in path: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
294 if self.third_match.match(pathdir): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
295 _debug( 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
296 f"Source in third-party: {pkg!r} path directory at {pathdir!r}",
297 )
298 self.source_in_third_paths.add(pathdir) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
300 for src in self.source_dirs: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
301 if self.third_match.match(src): 301 ↛ 302line 301 didn't jump to line 302 because the condition on line 301 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
302 _debug(f"Source in third-party: source directory {src!r}")
303 self.source_in_third_paths.add(src)
304 self.source_in_third_match = TreeMatcher(self.source_in_third_paths, "source_in_third") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
305 _debug(f"Source in third-party matching: {self.source_in_third_match}") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
307 self.plugins: Plugins 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
308 self.disp_class: type[TFileDisposition] = FileDisposition 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
310 def should_trace(self, filename: str, frame: FrameType | None = None) -> TFileDisposition: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
311 """Decide whether to trace execution in `filename`, with a reason.
313 This function is called from the trace function. As each new file name
314 is encountered, this function determines whether it is traced or not.
316 Returns a FileDisposition object.
318 """
319 original_filename = filename 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
320 disp = disposition_init(self.disp_class, filename) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
322 def nope(disp: TFileDisposition, reason: str) -> TFileDisposition: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
323 """Simple helper to make it easy to return NO."""
324 disp.trace = False 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
325 disp.reason = reason 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
326 return disp 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
328 if original_filename.startswith("<"): 328 ↛ 329line 328 didn't jump to line 329 because the condition on line 328 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
329 return nope(disp, "original file name is not real")
331 if frame is not None: 331 ↛ 338line 331 didn't jump to line 338 because the condition on line 331 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
332 # Compiled Python files have two file names: frame.f_code.co_filename is
333 # the file name at the time the .pyc was compiled. The second name is
334 # __file__, which is where the .pyc was actually loaded from. Since
335 # .pyc files can be moved after compilation (for example, by being
336 # installed), we look for __file__ in the frame and prefer it to the
337 # co_filename value.
338 dunder_file = frame.f_globals and frame.f_globals.get("__file__")
339 if dunder_file:
340 # Danger: __file__ can (rarely?) be of type Path.
341 filename = source_for_file(str(dunder_file))
342 if original_filename and not original_filename.startswith("<"):
343 orig = os.path.basename(original_filename)
344 if orig != os.path.basename(filename):
345 # Files shouldn't be renamed when moved. This happens when
346 # exec'ing code. If it seems like something is wrong with
347 # the frame's file name, then just use the original.
348 filename = original_filename
350 if not filename: 350 ↛ 352line 350 didn't jump to line 352 because the condition on line 350 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
351 # Empty string is pretty useless.
352 return nope(disp, "empty string isn't a file name")
354 if filename.startswith("memory:"): 354 ↛ 355line 354 didn't jump to line 355 because the condition on line 354 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
355 return nope(disp, "memory isn't traceable")
357 if filename.startswith("<"): 357 ↛ 362line 357 didn't jump to line 362 because the condition on line 357 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
358 # Lots of non-file execution is represented with artificial
359 # file names like "<string>", "<doctest readme.txt[0]>", or
360 # "<exec_function>". Don't ever trace these executions, since we
361 # can't do anything with the data later anyway.
362 return nope(disp, "file name is not real")
364 canonical = canonical_filename(filename) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
365 disp.canonical_filename = canonical 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
367 # Try the plugins, see if they have an opinion about the file.
368 plugin = None 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
369 for plugin in self.plugins.file_tracers: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
370 if not plugin._coverage_enabled: 370 ↛ 371line 370 didn't jump to line 371 because the condition on line 370 was never true1abscdtefughvijwklxmnyopzqrA
371 continue
373 try: 1abscdtefughvijwklxmnyopzqrA
374 file_tracer = plugin.file_tracer(canonical) 1abscdtefughvijwklxmnyopzqrA
375 if file_tracer is not None: 1abscdtefughvijwklxmnyopzqrA
376 file_tracer._coverage_plugin = plugin 1abscdtefughvijwklxmnyopzqrA
377 disp.trace = True 1abscdtefughvijwklxmnyopzqrA
378 disp.file_tracer = file_tracer 1abscdtefughvijwklxmnyopzqrA
379 if file_tracer.has_dynamic_source_filename(): 379 ↛ 382line 379 didn't jump to line 382 because the condition on line 379 was always true1abscdtefughvijwklxmnyopzqrA
380 disp.has_dynamic_filename = True 1abscdtefughvijwklxmnyopzqrA
381 else:
382 disp.source_filename = canonical_filename(
383 file_tracer.source_filename(),
384 )
385 break 1abscdtefughvijwklxmnyopzqrA
386 except Exception:
387 plugin_name = plugin._coverage_plugin_name
388 tb = traceback.format_exc()
389 self.warn(f"Disabling plug-in {plugin_name!r} due to an exception:\n{tb}")
390 plugin._coverage_enabled = False
391 continue
392 else:
393 # No plugin wanted it: it's Python.
394 disp.trace = True 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
395 disp.source_filename = canonical 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
397 if not disp.has_dynamic_filename: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
398 if not disp.source_filename: 398 ↛ 399line 398 didn't jump to line 399 because the condition on line 398 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
399 raise PluginError(
400 f"Plugin {plugin!r} didn't set source_filename for '{disp.original_filename}'",
401 )
402 reason = self.check_include_omit_etc(disp.source_filename, frame) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
403 if reason: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
404 nope(disp, reason) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
406 return disp 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
408 def check_include_omit_etc(self, filename: str, frame: FrameType | None) -> str | None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
409 """Check a file name against the include, omit, etc, rules.
411 Returns a string or None. String means, don't trace, and is the reason
412 why. None means no reason found to not trace.
414 """
415 modulename = name_for_module(filename, frame) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
417 # If the user specified source or include, then that's authoritative
418 # about the outer bound of what to measure and we don't have to apply
419 # any canned exclusions. If they didn't, then we have to exclude the
420 # stdlib and coverage.py directories.
421 if self.source_match or self.source_pkgs_match: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
422 extra = "" 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
423 ok = False 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
424 if self.source_pkgs_match: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
425 if isinstance(modulename, str) and self.source_pkgs_match.match(modulename): 425 ↛ 426line 425 didn't jump to line 426 because the condition on line 425 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
426 ok = True
427 if modulename in self.source_pkgs_unmatched:
428 self.source_pkgs_unmatched.remove(modulename)
429 else:
430 extra = f"module {modulename!r} " 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
431 if not ok and self.source_match: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
432 if self.source_match.match(filename): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
433 ok = True 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
434 if not ok: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
435 return extra + "falls outside the --source spec" 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
436 if self.third_match.match(filename) and not self.source_in_third_match.match(filename): 436 ↛ 457line 436 didn't jump to line 457 because the condition on line 436 was always true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
437 return "inside --source, but is third-party" 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
438 elif self.include_match: 438 ↛ 444line 438 didn't jump to line 444 because the condition on line 438 was always true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
439 if not self.include_match.match(filename): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
440 return "falls outside the --include trees" 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
441 else:
442 # We exclude the coverage.py code itself, since a little of it
443 # will be measured otherwise.
444 if self.cover_match.match(filename):
445 return "is part of coverage.py"
447 # If we aren't supposed to trace installed code, then check if this
448 # is near the Python standard library and skip it if so.
449 if self.pylib_match and self.pylib_match.match(filename):
450 return "is in the stdlib"
452 # Exclude anything in the third-party installation areas.
453 if self.third_match.match(filename):
454 return "is a third-party module"
456 # Check the file against the omit pattern.
457 if self.omit_match and self.omit_match.match(filename): 457 ↛ 458line 457 didn't jump to line 458 because the condition on line 457 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
458 return "is inside an --omit pattern"
460 # No point tracing a file we can't later write to SQLite.
461 try: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
462 filename.encode("utf-8") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
463 except UnicodeEncodeError:
464 return "non-encodable filename"
466 # No reason found to skip this file.
467 return None 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
469 def warn_conflicting_settings(self) -> None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
470 """Warn if there are settings that conflict."""
471 if self.include: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
472 if self.source_dirs or self.source_pkgs: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
473 self.warn("--include is ignored because --source is set", slug="include-ignored") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kOlPx1mQnRy2oSpTz3qUrVA4BCD
475 def warn_already_imported_files(self) -> None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
476 """Warn if files have already been imported that we will be measuring."""
477 if self.include or self.source_dirs or self.source_pkgs: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
478 warned = set() 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
479 for mod in list(sys.modules.values()): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
480 filename = getattr(mod, "__file__", None) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
481 if filename is None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
482 continue 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
483 if filename in warned: 483 ↛ 484line 483 didn't jump to line 484 because the condition on line 483 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
484 continue
486 if len(getattr(mod, "__path__", ())) > 1: 486 ↛ 488line 486 didn't jump to line 488 because the condition on line 486 was never true1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
487 # A namespace package, which confuses this code, so ignore it.
488 continue
490 disp = self.should_trace(filename) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
491 if disp.has_dynamic_filename: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
492 # A plugin with dynamic filenames: the Python file
493 # shouldn't cause a warning, since it won't be the subject
494 # of tracing anyway.
495 continue 1abscdtefughvijwklxmnyopzqrA
496 if disp.trace: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
497 msg = f"Already imported a file that will be measured: {filename}" 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
498 self.warn(msg, slug="already-imported") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
499 warned.add(filename) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
500 elif self.debug and self.debug.should("trace"): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
501 self.debug.write( 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
502 "Didn't trace already imported file {!r}: {}".format(
503 disp.original_filename,
504 disp.reason,
505 ),
506 )
508 def warn_unimported_source(self) -> None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
509 """Warn about source packages that were of interest, but never traced."""
510 for pkg in self.source_pkgs_unmatched: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
511 self._warn_about_unmeasured_code(pkg) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
513 def _warn_about_unmeasured_code(self, pkg: str) -> None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
514 """Warn about a package or module that we never traced.
516 `pkg` is a string, the name of the package or module.
518 """
519 mod = sys.modules.get(pkg) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
520 if mod is None: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
521 self.warn(f"Module {pkg} was never imported.", slug="module-not-imported") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
522 return 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
524 if module_is_namespace(mod): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
525 # A namespace package. It's OK for this not to have been traced,
526 # since there is no code directly in it.
527 return 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
529 if not module_has_file(mod): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
530 self.warn(f"Module {pkg} has no Python source.", slug="module-not-python") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
531 return 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
533 # The module was in sys.modules, and seems like a module with code, but
534 # we never measured it. I guess that means it was imported before
535 # coverage even started.
536 msg = f"Module {pkg} was previously imported, but not measured" 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
537 self.warn(msg, slug="module-not-measured") 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
539 def find_possibly_unexecuted_files(self) -> Iterable[tuple[str, str | None]]: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
540 """Find files in the areas of interest that might be untraced.
542 Yields pairs: file path, and responsible plug-in name.
543 """
544 for pkg in self.source_pkgs: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
545 if pkg not in sys.modules or not module_has_file(sys.modules[pkg]): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
546 continue 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
547 pkg_file = source_for_file(cast(str, sys.modules[pkg].__file__)) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
548 yield from self._find_executable_files(canonical_path(pkg_file)) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
550 for src in self.source_dirs: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
551 yield from self._find_executable_files(src) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
553 def _find_plugin_files(self, src_dir: str) -> Iterable[tuple[str, str]]: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
554 """Get executable files from the plugins."""
555 for plugin in self.plugins.file_tracers: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
556 for x_file in plugin.find_executable_files(src_dir): 1abscdtefughvijwklxmnyopzqrA
557 yield x_file, plugin._coverage_plugin_name 1abscdtefughvijwklxmnyopzqrA
559 def _find_executable_files(self, src_dir: str) -> Iterable[tuple[str, str | None]]: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
560 """Find executable files in `src_dir`.
562 Search for files in `src_dir` that can be executed because they
563 are probably importable. Don't include ones that have been omitted
564 by the configuration.
566 Yield the file path, and the plugin name that handles the file.
568 """
569 py_files = ( 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
570 (py_file, None)
571 for py_file in find_python_files(src_dir, self.include_namespace_packages)
572 )
573 plugin_files = self._find_plugin_files(src_dir) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
575 for file_path, plugin_name in itertools.chain(py_files, plugin_files): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
576 file_path = canonical_filename(file_path) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
577 if self.omit_match and self.omit_match.match(file_path): 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
578 # Turns out this file was omitted, so don't pull it back
579 # in as un-executed.
580 continue 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
581 yield file_path, plugin_name 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
583 def sys_info(self) -> Iterable[tuple[str, Any]]: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
584 """Our information for Coverage.sys_info.
586 Returns a list of (key, value) pairs.
587 """
588 info = [ 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
589 ("coverage_paths", self.cover_paths),
590 ("stdlib_paths", self.pylib_paths),
591 ("third_party_paths", self.third_paths),
592 ("source_in_third_party_paths", self.source_in_third_paths),
593 ]
595 matcher_names = [ 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
596 "source_match",
597 "source_pkgs_match",
598 "include_match",
599 "omit_match",
600 "cover_match",
601 "pylib_match",
602 "third_match",
603 "source_in_third_match",
604 ]
606 for matcher_name in matcher_names: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
607 matcher = getattr(self, matcher_name) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
608 if matcher: 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
609 matcher_info = matcher.info() 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
610 else:
611 matcher_info = "-none-" 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
612 info.append((matcher_name, matcher_info)) 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD
614 return info 1aEbFsWcGdHtXeIfJuYgKhLvZiMjNw0kO5lP6x1%mQ7nR8y2'oS9pT!z3(qU#rV$A4)BCD