summaryrefslogtreecommitdiff
path: root/coverage/inorout.py
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/inorout.py')
-rw-r--r--coverage/inorout.py150
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.