diff options
Diffstat (limited to 'coverage/inorout.py')
-rw-r--r-- | coverage/inorout.py | 150 |
1 files changed, 76 insertions, 74 deletions
diff --git a/coverage/inorout.py b/coverage/inorout.py index fcb45974..65aec83c 100644 --- a/coverage/inorout.py +++ b/coverage/inorout.py @@ -3,6 +3,8 @@ """Determining whether files are being measured/reported or not.""" +from __future__ import annotations + import importlib.util import inspect import itertools @@ -13,6 +15,9 @@ import sys import sysconfig import traceback +from types import FrameType, ModuleType +from typing import cast, Iterable, List, Optional, Set, Tuple, TYPE_CHECKING + from coverage import env from coverage.disposition import FileDisposition, disposition_init from coverage.exceptions import CoverageException, PluginError @@ -20,26 +25,36 @@ from coverage.files import TreeMatcher, GlobMatcher, ModuleMatcher from coverage.files import prep_patterns, find_python_files, canonical_filename from coverage.misc import sys_modules_saved from coverage.python import source_for_file, source_for_morf +from coverage.types import TMorf, TWarnFn, TDebugCtl, TSysInfo + +if TYPE_CHECKING: + from coverage.config import CoverageConfig + from coverage.plugin_support import Plugins # Pypy has some unusual stuff in the "stdlib". Consider those locations # when deciding where the stdlib is. These modules are not used for anything, # they are modules importable from the pypy lib directories, so that we can # find those directories. -_structseq = _pypy_irc_topic = None +modules_we_happen_to_have: List[ModuleType] = [ + inspect, itertools, os, platform, re, sysconfig, traceback, +] + if env.PYPY: try: import _structseq + modules_we_happen_to_have.append(_structseq) except ImportError: pass try: import _pypy_irc_topic + modules_we_happen_to_have.append(_pypy_irc_topic) except ImportError: pass -def canonical_path(morf, directory=False): +def canonical_path(morf: TMorf, directory: bool=False) -> str: """Return the canonical path of the module or file `morf`. If the module is a package, then return its directory. If it is a @@ -53,7 +68,7 @@ def canonical_path(morf, directory=False): return morf_path -def name_for_module(filename, frame): +def name_for_module(filename: str, frame: Optional[FrameType]) -> str: """Get the name of the module for a filename and frame. For configurability's sake, we allow __main__ modules to be matched by @@ -68,9 +83,9 @@ def name_for_module(filename, frame): module_globals = frame.f_globals if frame is not None else {} if module_globals is None: # pragma: only ironpython # IronPython doesn't provide globals: https://github.com/IronLanguages/main/issues/1296 - module_globals = {} + module_globals = {} # type: ignore[unreachable] - dunder_name = module_globals.get('__name__', None) + dunder_name: str = module_globals.get('__name__', None) if isinstance(dunder_name, str) and dunder_name != '__main__': # This is the usual case: an imported module. @@ -95,12 +110,12 @@ def name_for_module(filename, frame): return dunder_name -def module_is_namespace(mod): +def module_is_namespace(mod: ModuleType) -> bool: """Is the module object `mod` a PEP420 namespace module?""" return hasattr(mod, '__path__') and getattr(mod, '__file__', None) is None -def module_has_file(mod): +def module_has_file(mod: ModuleType) -> bool: """Does the module object `mod` have an existing __file__ ?""" mod__file__ = getattr(mod, '__file__', None) if mod__file__ is None: @@ -108,7 +123,7 @@ def module_has_file(mod): return os.path.exists(mod__file__) -def file_and_path_for_module(modulename): +def file_and_path_for_module(modulename: str) -> Tuple[Optional[str], List[str]]: """Find the file and search path for `modulename`. Returns: @@ -129,32 +144,19 @@ def file_and_path_for_module(modulename): return filename, path -def add_stdlib_paths(paths): +def add_stdlib_paths(paths: Set[str]) -> None: """Add paths where the stdlib can be found to the set `paths`.""" # Look at where some standard modules are located. That's the # indication for "installed with the interpreter". In some # environments (virtualenv, for example), these modules may be # spread across a few locations. Look at all the candidate modules # we've imported, and take all the different ones. - modules_we_happen_to_have = [ - inspect, itertools, os, platform, re, sysconfig, traceback, - _pypy_irc_topic, _structseq, - ] for m in modules_we_happen_to_have: - if m is not None and hasattr(m, "__file__"): + if hasattr(m, "__file__"): paths.add(canonical_path(m, directory=True)) - if _structseq and not hasattr(_structseq, '__file__'): - # PyPy 2.4 has no __file__ in the builtin modules, but the code - # objects still have the file names. So dig into one to find - # the path to exclude. The "filename" might be synthetic, - # don't be fooled by those. - structseq_file = _structseq.structseq_new.__code__.co_filename - if not structseq_file.startswith("<"): - paths.add(canonical_path(structseq_file)) - -def add_third_party_paths(paths): +def add_third_party_paths(paths: Set[str]) -> None: """Add locations for third-party packages to the set `paths`.""" # Get the paths that sysconfig knows about. scheme_names = set(sysconfig.get_scheme_names()) @@ -168,7 +170,7 @@ def add_third_party_paths(paths): paths.add(config_paths[path_name]) -def add_coverage_paths(paths): +def add_coverage_paths(paths: Set[str]) -> None: """Add paths where coverage.py code can be found to the set `paths`.""" cover_path = canonical_path(__file__, directory=True) paths.add(cover_path) @@ -180,31 +182,19 @@ def add_coverage_paths(paths): class InOrOut: """Machinery for determining what files to measure.""" - def __init__(self, warn, debug, include_namespace_packages): + def __init__( + self, + config: CoverageConfig, + warn: TWarnFn, + debug: Optional[TDebugCtl], + include_namespace_packages: bool, + ) -> None: self.warn = warn self.debug = debug self.include_namespace_packages = include_namespace_packages - # The matchers for should_trace. - self.source_match = None - self.source_pkgs_match = None - self.pylib_paths = self.cover_paths = self.third_paths = None - self.pylib_match = self.cover_match = self.third_match = None - self.include_match = self.omit_match = None - self.plugins = [] - self.disp_class = FileDisposition - - # The source argument can be directories or package names. - self.source = [] - self.source_pkgs = [] - self.source_pkgs_unmatched = [] - self.omit = self.include = None - - # Is the source inside a third-party area? - self.source_in_third = False - - def configure(self, config): - """Apply the configuration to get ready for decision-time.""" + self.source: List[str] = [] + self.source_pkgs: List[str] = [] self.source_pkgs.extend(config.source_pkgs) for src in config.source or []: if os.path.isdir(src): @@ -217,27 +207,34 @@ class InOrOut: self.include = prep_patterns(config.run_include) # The directories for files considered "installed with the interpreter". - self.pylib_paths = set() + self.pylib_paths: Set[str] = set() if not config.cover_pylib: add_stdlib_paths(self.pylib_paths) # To avoid tracing the coverage.py code itself, we skip anything # located where we are. - self.cover_paths = set() + self.cover_paths: Set[str] = set() add_coverage_paths(self.cover_paths) # Find where third-party packages are installed. - self.third_paths = set() + self.third_paths: Set[str] = set() add_third_party_paths(self.third_paths) - def debug(msg): + def _debug(msg: str) -> None: if self.debug: self.debug.write(msg) + # The matchers for should_trace. + # Generally useful information - debug("sys.path:" + "".join(f"\n {p}" for p in sys.path)) + _debug("sys.path:" + "".join(f"\n {p}" for p in sys.path)) # Create the matchers we need for should_trace + self.source_match = None + self.source_pkgs_match = None + self.pylib_match = None + self.include_match = self.omit_match = None + if self.source or self.source_pkgs: against = [] if self.source: @@ -246,44 +243,46 @@ class InOrOut: if self.source_pkgs: self.source_pkgs_match = ModuleMatcher(self.source_pkgs, "source_pkgs") against.append(f"modules {self.source_pkgs_match!r}") - debug("Source matching against " + " and ".join(against)) + _debug("Source matching against " + " and ".join(against)) else: if self.pylib_paths: self.pylib_match = TreeMatcher(self.pylib_paths, "pylib") - debug(f"Python stdlib matching: {self.pylib_match!r}") + _debug(f"Python stdlib matching: {self.pylib_match!r}") if self.include: self.include_match = GlobMatcher(self.include, "include") - debug(f"Include matching: {self.include_match!r}") + _debug(f"Include matching: {self.include_match!r}") if self.omit: self.omit_match = GlobMatcher(self.omit, "omit") - debug(f"Omit matching: {self.omit_match!r}") + _debug(f"Omit matching: {self.omit_match!r}") self.cover_match = TreeMatcher(self.cover_paths, "coverage") - debug(f"Coverage code matching: {self.cover_match!r}") + _debug(f"Coverage code matching: {self.cover_match!r}") self.third_match = TreeMatcher(self.third_paths, "third") - debug(f"Third-party lib matching: {self.third_match!r}") + _debug(f"Third-party lib matching: {self.third_match!r}") # Check if the source we want to measure has been installed as a # third-party package. + # Is the source inside a third-party area? + self.source_in_third = False with sys_modules_saved(): for pkg in self.source_pkgs: try: modfile, path = file_and_path_for_module(pkg) - debug(f"Imported source package {pkg!r} as {modfile!r}") + _debug(f"Imported source package {pkg!r} as {modfile!r}") except CoverageException as exc: - debug(f"Couldn't import source package {pkg!r}: {exc}") + _debug(f"Couldn't import source package {pkg!r}: {exc}") continue if modfile: if self.third_match.match(modfile): - debug( + _debug( f"Source is in third-party because of source_pkg {pkg!r} at {modfile!r}" ) self.source_in_third = True else: for pathdir in path: if self.third_match.match(pathdir): - debug( + _debug( f"Source is in third-party because of {pkg!r} path directory " + f"at {pathdir!r}" ) @@ -291,10 +290,13 @@ class InOrOut: for src in self.source: if self.third_match.match(src): - debug(f"Source is in third-party because of source directory {src!r}") + _debug(f"Source is in third-party because of source directory {src!r}") self.source_in_third = True - def should_trace(self, filename, frame=None): + self.plugins: Plugins + self.disp_class = FileDisposition + + def should_trace(self, filename: str, frame: Optional[FrameType]=None) -> FileDisposition: """Decide whether to trace execution in `filename`, with a reason. This function is called from the trace function. As each new file name @@ -306,7 +308,7 @@ class InOrOut: original_filename = filename disp = disposition_init(self.disp_class, filename) - def nope(disp, reason): + def nope(disp: FileDisposition, reason: str) -> FileDisposition: """Simple helper to make it easy to return NO.""" disp.trace = False disp.reason = reason @@ -395,7 +397,7 @@ class InOrOut: return disp - def check_include_omit_etc(self, filename, frame): + def check_include_omit_etc(self, filename: str, frame: Optional[FrameType]) -> Optional[str]: """Check a file name against the include, omit, etc, rules. Returns a string or None. String means, don't trace, and is the reason @@ -457,13 +459,13 @@ class InOrOut: # No reason found to skip this file. return None - def warn_conflicting_settings(self): + def warn_conflicting_settings(self) -> None: """Warn if there are settings that conflict.""" if self.include: if self.source or self.source_pkgs: self.warn("--include is ignored because --source is set", slug="include-ignored") - def warn_already_imported_files(self): + def warn_already_imported_files(self) -> None: """Warn if files have already been imported that we will be measuring.""" if self.include or self.source or self.source_pkgs: warned = set() @@ -495,12 +497,12 @@ class InOrOut: ) ) - def warn_unimported_source(self): + def warn_unimported_source(self) -> None: """Warn about source packages that were of interest, but never traced.""" for pkg in self.source_pkgs_unmatched: self._warn_about_unmeasured_code(pkg) - def _warn_about_unmeasured_code(self, pkg): + def _warn_about_unmeasured_code(self, pkg: str) -> None: """Warn about a package or module that we never traced. `pkg` is a string, the name of the package or module. @@ -526,7 +528,7 @@ class InOrOut: msg = f"Module {pkg} was previously imported, but not measured" self.warn(msg, slug="module-not-measured") - def find_possibly_unexecuted_files(self): + def find_possibly_unexecuted_files(self) -> Iterable[Tuple[str, Optional[str]]]: """Find files in the areas of interest that might be untraced. Yields pairs: file path, and responsible plug-in name. @@ -535,19 +537,19 @@ class InOrOut: if (not pkg in sys.modules or not module_has_file(sys.modules[pkg])): continue - pkg_file = source_for_file(sys.modules[pkg].__file__) + pkg_file = source_for_file(cast(str, sys.modules[pkg].__file__)) yield from self._find_executable_files(canonical_path(pkg_file)) for src in self.source: yield from self._find_executable_files(src) - def _find_plugin_files(self, src_dir): + def _find_plugin_files(self, src_dir: str) -> Iterable[Tuple[str, str]]: """Get executable files from the plugins.""" for plugin in self.plugins.file_tracers: for x_file in plugin.find_executable_files(src_dir): yield x_file, plugin._coverage_plugin_name - def _find_executable_files(self, src_dir): + def _find_executable_files(self, src_dir: str) -> Iterable[Tuple[str, Optional[str]]]: """Find executable files in `src_dir`. Search for files in `src_dir` that can be executed because they @@ -571,7 +573,7 @@ class InOrOut: continue yield file_path, plugin_name - def sys_info(self): + def sys_info(self) -> TSysInfo: """Our information for Coverage.sys_info. Returns a list of (key, value) pairs. |