From 4574ecf128ae51c2b950f6c9cb2486b86f5354e7 Mon Sep 17 00:00:00 2001 From: Manuel Jacob Date: Tue, 21 Feb 2023 03:03:55 +0100 Subject: 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. --- CHANGES.rst | 7 +++++++ CONTRIBUTORS.txt | 1 + coverage/inorout.py | 25 +++++++++++++------------ tests/test_venv.py | 26 +++++++++++++++++++++++--- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 74f2d4bc..b4060746 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,6 +20,12 @@ development at the same time, such as 4.5.x and 5.0. Unreleased ---------- +- Fix: if a virtualenv was created inside a source directory, and a sourced + package was installed inside the virtualenv, then all of the third-party + packages inside the virtualenv would be measured. This was incorrect, but + has now been fixed: only the specified packages will be measured, thanks to + `Manuel Jacob `_. + - Fix: the ``coverage lcov`` command could create a .lcov file with incorrect LF (lines found) and LH (lines hit) totals. This is now fixed, thanks to `Ian Moore `_. @@ -28,6 +34,7 @@ Unreleased duplicate ```` elements. This is now fixed, thanks to `Benjamin Parzella `_, closing `issue 1573`_. +.. _pull 1560: https://github.com/nedbat/coveragepy/pull/1560 .. _issue 1573: https://github.com/nedbat/coveragepy/issues/1573 .. _pull 1574: https://github.com/nedbat/coveragepy/pull/1574 .. _pull 1583: https://github.com/nedbat/coveragepy/pull/1583 diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index bb69b823..a50138f8 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -107,6 +107,7 @@ Leonardo Pistone Lex Berezhny Loïc Dachary Lorenzo Micò +Manuel Jacob Marc Abramowitz Marc Legendre Marcelo Trylesinski diff --git a/coverage/inorout.py b/coverage/inorout.py index babaa3d8..d0d0ef91 100644 --- a/coverage/inorout.py +++ b/coverage/inorout.py @@ -262,7 +262,7 @@ class InOrOut: # 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: @@ -274,22 +274,23 @@ class InOrOut: 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 @@ -419,9 +420,8 @@ class InOrOut: 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" @@ -576,12 +576,13 @@ class InOrOut: ("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: diff --git a/tests/test_venv.py b/tests/test_venv.py index de7ebbe1..ae5b303f 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -198,8 +198,28 @@ class VirtualenvTest(CoverageTest): 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", ignore_errors=True) + shutil.rmtree("myproduct.egg-info", ignore_errors=True) + # 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 @@ -213,7 +233,7 @@ class VirtualenvTest(CoverageTest): ) 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, ) -- cgit v1.2.1