From 0eb5170c26b939b162c9386203dcb0a09e547871 Mon Sep 17 00:00:00 2001 From: Manuel Jacob Date: Tue, 21 Feb 2023 03:03:55 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20don=E2=80=99t=20measure=20all=20third-pa?= =?UTF-8?q?rty=20packages=20if=20source=20is=20in=20third-party=20location?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- coverage/inorout.py | 25 +++++++++++++------------ tests/test_venv.py | 26 +++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/coverage/inorout.py b/coverage/inorout.py index babaa3d80..d0d0ef913 100644 --- a/coverage/inorout.py +++ b/coverage/inorout.py @@ -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: @@ -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 @@ -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" @@ -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: diff --git a/tests/test_venv.py b/tests/test_venv.py index de7ebbe18..56050606f 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -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 @@ -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, )