Skip to content

Commit 0eb5170

Browse files
manueljacobnedbat
authored andcommitted
fix: don’t measure all third-party packages if source is in third-party 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.
1 parent 857833e commit 0eb5170

File tree

2 files changed

+36
-15
lines changed

2 files changed

+36
-15
lines changed

coverage/inorout.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def _debug(msg: str) -> None:
262262
# Check if the source we want to measure has been installed as a
263263
# third-party package.
264264
# Is the source inside a third-party area?
265-
self.source_in_third = False
265+
self.source_in_third_paths = set()
266266
with sys_modules_saved():
267267
for pkg in self.source_pkgs:
268268
try:
@@ -274,22 +274,23 @@ def _debug(msg: str) -> None:
274274
if modfile:
275275
if self.third_match.match(modfile):
276276
_debug(
277-
f"Source is in third-party because of source_pkg {pkg!r} at {modfile!r}"
277+
f"Source in third-party: source_pkg {pkg!r} at {modfile!r}"
278278
)
279-
self.source_in_third = True
279+
self.source_in_third_paths.add(canonical_path(source_for_file(modfile)))
280280
else:
281281
for pathdir in path:
282282
if self.third_match.match(pathdir):
283283
_debug(
284-
f"Source is in third-party because of {pkg!r} path directory " +
285-
f"at {pathdir!r}"
284+
f"Source in third-party: {pkg!r} path directory at {pathdir!r}"
286285
)
287-
self.source_in_third = True
286+
self.source_in_third_paths.add(pathdir)
288287

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

294295
self.plugins: Plugins
295296
self.disp_class: Type[TFileDisposition] = FileDisposition
@@ -419,9 +420,8 @@ def check_include_omit_etc(self, filename: str, frame: Optional[FrameType]) -> O
419420
ok = True
420421
if not ok:
421422
return extra + "falls outside the --source spec"
422-
if not self.source_in_third:
423-
if self.third_match.match(filename):
424-
return "inside --source, but is third-party"
423+
if self.third_match.match(filename) and not self.source_in_third_match.match(filename):
424+
return "inside --source, but is third-party"
425425
elif self.include_match:
426426
if not self.include_match.match(filename):
427427
return "falls outside the --include trees"
@@ -576,12 +576,13 @@ def sys_info(self) -> Iterable[Tuple[str, Any]]:
576576
("coverage_paths", self.cover_paths),
577577
("stdlib_paths", self.pylib_paths),
578578
("third_party_paths", self.third_paths),
579+
("source_in_third_party_paths", self.source_in_third_paths),
579580
]
580581

581582
matcher_names = [
582583
'source_match', 'source_pkgs_match',
583584
'include_match', 'omit_match',
584-
'cover_match', 'pylib_match', 'third_match',
585+
'cover_match', 'pylib_match', 'third_match', 'source_in_third_match',
585586
]
586587

587588
for matcher_name in matcher_names:

tests/test_venv.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,28 @@ def get_trace_output(self) -> str:
198198
with open("debug_out.txt") as f:
199199
return f.read()
200200

201-
def test_third_party_venv_isnt_measured(self, coverage_command: str) -> None:
202-
out = run_in_venv(coverage_command + " run --source=. myproduct.py")
201+
@pytest.mark.parametrize('install_source_in_venv', [True, False])
202+
def test_third_party_venv_isnt_measured(
203+
self, coverage_command: str, install_source_in_venv: bool
204+
) -> None:
205+
if install_source_in_venv:
206+
make_file("setup.py", """\
207+
import setuptools
208+
setuptools.setup(
209+
name="myproduct",
210+
py_modules = ["myproduct"],
211+
)
212+
""")
213+
try:
214+
run_in_venv("python -m pip install .")
215+
finally:
216+
shutil.rmtree("build")
217+
shutil.rmtree("myproduct.egg-info")
218+
# Ensure that coverage doesn't run the non-installed module.
219+
os.remove('myproduct.py')
220+
out = run_in_venv(coverage_command + " run --source=.,myproduct -m myproduct")
221+
else:
222+
out = run_in_venv(coverage_command + " run --source=. myproduct.py")
203223
# In particular, this warning doesn't appear:
204224
# Already imported a file that will be measured: .../coverage/__main__.py
205225
assert out == self.expected_stdout
@@ -213,7 +233,7 @@ def test_third_party_venv_isnt_measured(self, coverage_command: str) -> None:
213233
)
214234
assert re_lines(r"^Tracing .*\bmyproduct.py", debug_out)
215235
assert re_lines(
216-
r"^Not tracing .*\bcolorsys.py': falls outside the --source spec",
236+
r"^Not tracing .*\bcolorsys.py': (module 'colorsys' |)?falls outside the --source spec",
217237
debug_out,
218238
)
219239

0 commit comments

Comments
 (0)