From 56370c53685ba47e3cc1ec4b64f969e6a22034ad Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Mon, 11 Dec 2023 20:35:13 -0500 Subject: [PATCH] fix: leading zeros can confused human sorting. #1709 --- CHANGES.rst | 5 +++++ coverage/misc.py | 10 +++++++--- tests/test_misc.py | 4 +++- tests/test_xml.py | 12 ++++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f46c11bb5..fee8b3958 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -24,6 +24,10 @@ Unreleased matching any of the lines, closing `issue 684`_. Thanks, `Jan Rusak, Maciej Kowalczyk and Joanna Ejzel `_. +- Fix: XML reports could fail with a TypeError if files had numeric components + that were duplicates except for leading zeroes, like ``file1.py`` and + ``file001.py``. Fixes `issue 1709`_. + - The ``coverage annotate`` command used to announce that it would be removed in a future version. Enough people got in touch to say that they use it, so it will stay. Don't expect it to keep up with other new features though. @@ -36,6 +40,7 @@ Unreleased .. _issue 684: https://github.com/nedbat/coveragepy/issues/684 .. _pull 1705: https://github.com/nedbat/coveragepy/pull/1705 +.. _issue 1709: https://github.com/nedbat/coveragepy/issues/1709 .. scriv-start-here diff --git a/coverage/misc.py b/coverage/misc.py index 8f9d4f12e..0280650d7 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -338,9 +338,13 @@ def import_local_file(modname: str, modfile: Optional[str] = None) -> ModuleType return mod -def _human_key(s: str) -> List[Union[str, int]]: +def _human_key(s: str) -> Tuple[List[Union[str, int]], str]: """Turn a string into a list of string and number chunks. - "z23a" -> ["z", 23, "a"] + + "z23a" -> (["z", 23, "a"], "z23a") + + The original string is appended as a last value to ensure the + key is unique enough so that "x1y" and "x001y" can be distinguished. """ def tryint(s: str) -> Union[str, int]: """If `s` is a number, return an int, else `s` unchanged.""" @@ -349,7 +353,7 @@ def tryint(s: str) -> Union[str, int]: except ValueError: return s - return [tryint(c) for c in re.split(r"(\d+)", s)] + return ([tryint(c) for c in re.split(r"(\d+)", s)], s) def human_sorted(strings: Iterable[str]) -> List[str]: """Sort the given iterable of strings the way that humans expect. diff --git a/tests/test_misc.py b/tests/test_misc.py index 4b83f8aec..5e674094d 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -137,7 +137,7 @@ def test_failure(self) -> None: HUMAN_DATA = [ - ("z1 a2z a2a a3 a1", "a1 a2a a2z a3 z1"), + ("z1 a2z a01 a2a a3 a1", "a01 a1 a2a a2z a3 z1"), ("a10 a9 a100 a1", "a1 a9 a10 a100"), ("4.0 3.10-win 3.10-mac 3.9-mac 3.9-win", "3.9-mac 3.9-win 3.10-mac 3.10-win 4.0"), ] @@ -149,6 +149,8 @@ def test_human_sorted(words: str, ordered: str) -> None: @pytest.mark.parametrize("words, ordered", HUMAN_DATA) def test_human_sorted_items(words: str, ordered: str) -> None: keys = words.split() + # Check that we never try to compare the values in the items + human_sorted_items([(k, object()) for k in keys]) items = [(k, 1) for k in keys] + [(k, 2) for k in keys] okeys = ordered.split() oitems = [(k, v) for k in okeys for v in [1, 2]] diff --git a/tests/test_xml.py b/tests/test_xml.py index 2e97ec897..020465520 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -350,6 +350,18 @@ def test_no_duplicate_packages(self) -> None: named_sub_package = dom.findall(".//package[@name='namespace.package.subpackage']") assert len(named_sub_package) == 1 + def test_bug_1709(self) -> None: + self.make_file("main.py", "import x1y, x01y, x001y") + self.make_file("x1y.py", "print('x1y')") + self.make_file("x01y.py", "print('x01y')") + self.make_file("x001y.py", "print('x001y')") + + cov = coverage.Coverage() + self.start_import_stop(cov, "main") + assert self.stdout() == "x1y\nx01y\nx001y\n" + # This used to raise: TypeError: '<' not supported between instances of 'Element' and 'Element' + cov.xml_report() + def unbackslash(v: Any) -> Any: """Find strings in `v`, and replace backslashes with slashes throughout."""