Skip to content

Commit

Permalink
fix: don’t measure all third-party packages if source is in third-par…
Browse files Browse the repository at this point in the history
…ty location

There is logic to not measure third-party packages inside configured sources. However, when a (i.e. another) configured source was inside a third-party location, this logic was previously disabled completely.

This caused a problem if a virtual env is set up inside a configured source directory and a configured source package gets installed inside the virtual env. Previously in this case, coverage was measured for all files in the virtual env for the reason described in the previous paragraph.

This commit changes the code to collect all configured source directories inside third-party locations and disable coverage for code in third-party locations only if its not in one of these collected source directories.
  • Loading branch information
manueljacob authored and nedbat committed Feb 22, 2023
1 parent 51f395d commit b49f830
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 15 deletions.
25 changes: 13 additions & 12 deletions coverage/inorout.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def _debug(msg: str) -> None:
# 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
self.source_in_third_paths = set()
with sys_modules_saved():
for pkg in self.source_pkgs:
try:
Expand All @@ -274,22 +274,23 @@ def _debug(msg: str) -> None:
if modfile:
if self.third_match.match(modfile):
_debug(
f"Source is in third-party because of source_pkg {pkg!r} at {modfile!r}"
f"Source in third-party: source_pkg {pkg!r} at {modfile!r}"
)
self.source_in_third = True
self.source_in_third_paths.add(canonical_path(source_for_file(modfile)))
else:
for pathdir in path:
if self.third_match.match(pathdir):
_debug(
f"Source is in third-party because of {pkg!r} path directory " +
f"at {pathdir!r}"
f"Source in third-party: {pkg!r} path directory at {pathdir!r}"
)
self.source_in_third = True
self.source_in_third_paths.add(pathdir)

for src in self.source:
if self.third_match.match(src):
_debug(f"Source is in third-party because of source directory {src!r}")
self.source_in_third = True
_debug(f"Source in third-party: source directory {src!r}")
self.source_in_third_paths.add(src)
self.source_in_third_match = TreeMatcher(self.source_in_third_paths, "source_in_third")
_debug(f"Source in third-party matching: {self.source_in_third_match}")

self.plugins: Plugins
self.disp_class: Type[TFileDisposition] = FileDisposition
Expand Down Expand Up @@ -419,9 +420,8 @@ def check_include_omit_etc(self, filename: str, frame: Optional[FrameType]) -> O
ok = True
if not ok:
return extra + "falls outside the --source spec"
if not self.source_in_third:
if self.third_match.match(filename):
return "inside --source, but is third-party"
if self.third_match.match(filename) and not self.source_in_third_match.match(filename):
return "inside --source, but is third-party"
elif self.include_match:
if not self.include_match.match(filename):
return "falls outside the --include trees"
Expand Down Expand Up @@ -576,12 +576,13 @@ def sys_info(self) -> Iterable[Tuple[str, Any]]:
("coverage_paths", self.cover_paths),
("stdlib_paths", self.pylib_paths),
("third_party_paths", self.third_paths),
("source_in_third_party_paths", self.source_in_third_paths),
]

matcher_names = [
'source_match', 'source_pkgs_match',
'include_match', 'omit_match',
'cover_match', 'pylib_match', 'third_match',
'cover_match', 'pylib_match', 'third_match', 'source_in_third_match',
]

for matcher_name in matcher_names:
Expand Down
26 changes: 23 additions & 3 deletions tests/test_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,28 @@ def get_trace_output(self) -> str:
with open("debug_out.txt") as f:
return f.read()

def test_third_party_venv_isnt_measured(self, coverage_command: str) -> None:
out = run_in_venv(coverage_command + " run --source=. myproduct.py")
@pytest.mark.parametrize('install_source_in_venv', [True, False])
def test_third_party_venv_isnt_measured(
self, coverage_command: str, install_source_in_venv: bool
) -> None:
if install_source_in_venv:
make_file("setup.py", """\
import setuptools
setuptools.setup(
name="myproduct",
py_modules = ["myproduct"],
)
""")
try:
run_in_venv("python -m pip install .")
finally:
shutil.rmtree("build")
shutil.rmtree("myproduct.egg-info")
# Ensure that coverage doesn't run the non-installed module.
os.remove('myproduct.py')
out = run_in_venv(coverage_command + " run --source=.,myproduct -m myproduct")
else:
out = run_in_venv(coverage_command + " run --source=. myproduct.py")
# In particular, this warning doesn't appear:
# Already imported a file that will be measured: .../coverage/__main__.py
assert out == self.expected_stdout
Expand All @@ -213,7 +233,7 @@ def test_third_party_venv_isnt_measured(self, coverage_command: str) -> None:
)
assert re_lines(r"^Tracing .*\bmyproduct.py", debug_out)
assert re_lines(
r"^Not tracing .*\bcolorsys.py': falls outside the --source spec",
r"^Not tracing .*\bcolorsys.py': (module 'colorsys' |)?falls outside the --source spec",
debug_out,
)

Expand Down

0 comments on commit b49f830

Please sign in to comment.