From cab4d576aa95e8579202988a92d8eafbcd3d888a Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 14 Jul 2021 06:59:30 -0400 Subject: [PATCH] fix: generate flat file names differently Fixes a few unusual issues with reports: - #580: HTML report generation fails on too long path - #584: File collisions in coverage report html - #1167: Remove leading underscore in coverage html --- CHANGES.rst | 13 +++++++++++-- coverage/files.py | 10 ++++------ tests/test_files.py | 22 ++++++++++++---------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 850c30ecf..d2c4ae06d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -46,11 +46,20 @@ Unreleased - TOML parsing now uses the `tomli`_ library. -- Use a modern hash algorithm when fingerprinting to speed HTML reports - (`issue 1189`_). +- Some minor changes to usually invisible details of the HTML report: + + - Use a modern hash algorithm when fingerprinting, for high-security + environments (`issue 1189`_). + + - Change how report file names are generated, to avoid leading underscores + (`issue 1167`_), to avoid rare file name collisions (`issue 584`_), and to + avoid file names becoming too long (`issue 580`_). .. _Django coverage plugin: https://pypi.org/project/django-coverage-plugin/ +.. _issue 580: https://github.com/nedbat/coveragepy/issues/580 +.. _issue 584: https://github.com/nedbat/coveragepy/issues/584 .. _issue 1150: https://github.com/nedbat/coveragepy/issues/1150 +.. _issue 1167: https://github.com/nedbat/coveragepy/issues/1167 .. _issue 1168: https://github.com/nedbat/coveragepy/issues/1168 .. _issue 1189: https://github.com/nedbat/coveragepy/issues/1189 .. _tomli: https://pypi.org/project/tomli/ diff --git a/coverage/files.py b/coverage/files.py index 8de2ec676..12ad081a4 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -77,7 +77,7 @@ def canonical_filename(filename): return CANONICAL_FILENAME_CACHE[filename] -MAX_FLAT = 200 +MAX_FLAT = 100 @contract(filename='unicode', returns='unicode') def flat_rootname(filename): @@ -90,11 +90,9 @@ def flat_rootname(filename): For example, the file a/b/c.py will return 'a_b_c_py' """ - name = ntpath.splitdrive(filename)[1] - name = re.sub(r"[\\/.:]", "_", name) - if len(name) > MAX_FLAT: - h = hashlib.sha1(name.encode('UTF-8')).hexdigest() - name = name[-(MAX_FLAT-len(h)-1):] + '_' + h + fp = hashlib.new("sha3_256", filename.encode("UTF-8")).hexdigest() + name = re.sub(r"[\\/.:]", "_", filename) + name = fp[:16] + "_" + name[-(MAX_FLAT-17):] return name diff --git a/tests/test_files.py b/tests/test_files.py index 98ece632f..d97c2cd32 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -69,18 +69,20 @@ def test_canonical_filename_ensure_cache_hit(self): @pytest.mark.parametrize("original, flat", [ - ("a/b/c.py", "a_b_c_py"), - (r"c:\foo\bar.html", "_foo_bar_html"), - ("Montréal/☺/conf.py", "Montréal_☺_conf_py"), + ("a/b/c.py", "9740bf4b0a33df83_a_b_c_py"), + ("/a/b/c.py", "055993932a538df4__a_b_c_py"), + (r"c:\foo\bar.html", "e402c17adf14f7d1_c__foo_bar_html"), + (r"d:\foo\bar.html", "85b26652b26cac26_d__foo_bar_html"), + ("Montréal/☺/conf.py", "1c7e19adc3b6fe6b_Montréal_☺_conf_py"), ( # original: - r"c:\lorem\ipsum\quia\dolor\sit\amet\consectetur\adipisci\velit\sed\quia\non" - r"\numquam\eius\modi\tempora\incidunt\ut\labore\et\dolore\magnam\aliquam" - r"\quaerat\voluptatem\ut\enim\ad\minima\veniam\quis\nostrum\exercitationem" - r"\ullam\corporis\suscipit\laboriosam\Montréal\☺\my_program.py", + r"c:\lorem\ipsum\quia\dolor\sit\amet\consectetur\adipisci\velit\sed" + + r"\quia\non\numquam\eius\modi\tempora\incidunt\ut\labore\et\dolore" + + r"\magnam\aliquam\quaerat\voluptatem\ut\enim\ad\minima\veniam\quis" + + r"\nostrum\exercitationem\ullam\corporis\suscipit\laboriosam" + + r"\Montréal\☺\my_program.py", # flat: - "re_et_dolore_magnam_aliquam_quaerat_voluptatem_ut_enim_ad_minima_veniam_quis_" - "nostrum_exercitationem_ullam_corporis_suscipit_laboriosam_Montréal_☺_my_program_py_" - "97eaca41b860faaa1a21349b1f3009bb061cf0a8" + "0e99262918baa54c__nostrum_exercitationem_ullam_corporis_suscipit_" + + "laboriosam_Montréal_☺_my_program_py" ), ]) def test_flat_rootname(original, flat):