Skip to content

Commit 4574ecf

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 dd7959d commit 4574ecf

File tree

4 files changed

+44
-15
lines changed

4 files changed

+44
-15
lines changed

CHANGES.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ development at the same time, such as 4.5.x and 5.0.
2020
Unreleased
2121
----------
2222

23+
- Fix: if a virtualenv was created inside a source directory, and a sourced
24+
package was installed inside the virtualenv, then all of the third-party
25+
packages inside the virtualenv would be measured. This was incorrect, but
26+
has now been fixed: only the specified packages will be measured, thanks to
27+
`Manuel Jacob <pull 1560_>`_.
28+
2329
- Fix: the ``coverage lcov`` command could create a .lcov file with incorrect
2430
LF (lines found) and LH (lines hit) totals. This is now fixed, thanks to
2531
`Ian Moore <pull 1583_>`_.
@@ -28,6 +34,7 @@ Unreleased
2834
duplicate ``<package>`` elements. This is now fixed, thanks to `Benjamin
2935
Parzella <pull 1574_>`_, closing `issue 1573`_.
3036

37+
.. _pull 1560: https://github.com/nedbat/coveragepy/pull/1560
3138
.. _issue 1573: https://github.com/nedbat/coveragepy/issues/1573
3239
.. _pull 1574: https://github.com/nedbat/coveragepy/pull/1574
3340
.. _pull 1583: https://github.com/nedbat/coveragepy/pull/1583

CONTRIBUTORS.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ Leonardo Pistone
107107
Lex Berezhny
108108
Loïc Dachary
109109
Lorenzo Micò
110+
Manuel Jacob
110111
Marc Abramowitz
111112
Marc Legendre
112113
Marcelo Trylesinski

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", ignore_errors=True)
217+
shutil.rmtree("myproduct.egg-info", ignore_errors=True)
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)