From 1b19799edfbfc65dea254e8fcaa0dea23e9709f9 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 14 Mar 2024 11:33:50 -0400 Subject: [PATCH] fix: ensure absolute paths are relative when combined #1752 If two data files are combined, one with absolute paths, and one with relative, with relative_files=True in effect, the results depended on the order of combining. If the absolute files were seen first, they were added as absolute paths. If the relative files were seen first, a mapping rule was generated that would then remap the absolute paths when they were seen. This fix ensures that absolute paths are remapped even if they are seen first. --- CHANGES.rst | 8 ++++++++ coverage/data.py | 5 ++++- coverage/files.py | 3 +++ tests/test_api.py | 20 ++++++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1ae94d2a4..3e4589fd8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -23,6 +23,13 @@ upgrading your version of coverage.py. Unreleased ---------- +- Fix: in some cases, even with ``[run] relative_files=True``, a data file + could be created with absolute path names. When combined with other relative + data files, it was random whether the absolute file names would be made + relative or not. If they weren't, then a file would be listed twice in + reports, as detailed in `issue 1752`_. This is now fixed: absolute file + names are always made relative when combining. + - Fix: the last case of a match/case statement had an incorrect message if the branch was missed. It said the pattern never matched, when actually the branch is missed if the last case always matched. @@ -33,6 +40,7 @@ Unreleased string. Thanks, `Tanaydin Sirin `_. It is also now documented on the :ref:`configuration page `. +.. _issue 1752: https://github.com/nedbat/coveragepy/issues/1752 .. _pull 1754: https://github.com/nedbat/coveragepy/pull/1754 diff --git a/coverage/data.py b/coverage/data.py index 0db07d156..9513adfca 100644 --- a/coverage/data.py +++ b/coverage/data.py @@ -88,7 +88,10 @@ def combinable_files(data_file: str, data_paths: Iterable[str] | None = None) -> # We never want to combine those. files_to_combine = [fnm for fnm in files_to_combine if not fnm.endswith("-journal")] - return files_to_combine + # Sorting isn't usually needed, since it shouldn't matter what order files + # are combined, but sorting makes tests more predictable, and makes + # debugging more understandable when things go wrong. + return sorted(files_to_combine) def combine_parallel_data( diff --git a/coverage/files.py b/coverage/files.py index 71352b8eb..0dd3c4e01 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -489,6 +489,9 @@ def map(self, path: str, exists:Callable[[str], bool] = source_exists) -> str: # If we get here, no pattern matched. + if self.relative: + path = relative_filename(path) + if self.relative and not isabs_anywhere(path): # Auto-generate a pattern to implicitly match relative files parts = re.split(r"[/\\]", path) diff --git a/tests/test_api.py b/tests/test_api.py index b6ab9cda0..9f65166b9 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1465,3 +1465,23 @@ def test_combine_parallel_data_keep(self) -> None: # After combining, the .coverage file & the original combined file should still be there. self.assert_exists(".coverage") self.assert_file_count(".coverage.*", 2) + + @pytest.mark.parametrize("abs_order, rel_order", [(1, 2), (2, 1)]) + def test_combine_absolute_then_relative_1752(self, abs_order: int, rel_order: int) -> None: + # https://github.com/nedbat/coveragepy/issues/1752 + # If we're combining a relative data file and an absolute data file, + # the absolutes were made relative only if the relative file name was + # encountered first. Test combining in both orders and check that the + # absolute file name is properly relative in either order. + FILE = "sub/myprog.py" + self.make_file(FILE, "a = 1") + + self.make_data_file(suffix=f"{abs_order}.abs", lines={abs_file(FILE): [1]}) + self.make_data_file(suffix=f"{rel_order}.rel", lines={FILE: [1]}) + + self.make_file(".coveragerc", "[run]\nrelative_files = True\n") + cov = coverage.Coverage() + cov.combine() + data = coverage.CoverageData() + data.read() + assert {os_sep("sub/myprog.py")} == data.measured_files()