From 339c7f79e61978fa8601a56dcf5e2fc335e016b1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 04:05:35 +0000 Subject: [PATCH] Pin each line break make_rules refuses, and its CRLF allowance The independent review of #650 found that dropping \v (mutant c6) or \x1c-\x1e, \x85, U+2028 and U+2029 (c7) from MAKE_LINE_BREAK_MAKE_IGNORES left all 102 tests passing, and that turning \r(?!\n) into \r (c4) failed only on a CRLF checkout. A parametrized test now writes each of the seven unpinned characters into a recipe line as bytes, so the result doesn't depend on the locale encoding, and expects make_rules to refuse it. A second test writes LF and CRLF copies of the root Makefile (normalized first, in case the checkout is already CRLF) and expects the same rules from both, so c4 fails on an LF checkout too. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6JwdC7UUfK44SGfEaj --- .../core650-line-break-pins.changed.md | 1 + tests/core/test_release_tagging.py | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 changelog.d/core650-line-break-pins.changed.md diff --git a/changelog.d/core650-line-break-pins.changed.md b/changelog.d/core650-line-break-pins.changed.md new file mode 100644 index 000000000..a5082a242 --- /dev/null +++ b/changelog.d/core650-line-break-pins.changed.md @@ -0,0 +1 @@ +The release tests now pin each line break their Makefile parser refuses (vertical tab, `\x1c` to `\x1e`, `\x85`, U+2028 and U+2029), and check that it reads a CRLF copy of the root `Makefile` the same as the LF one. diff --git a/tests/core/test_release_tagging.py b/tests/core/test_release_tagging.py index a5bffebce..18bcd89ba 100644 --- a/tests/core/test_release_tagging.py +++ b/tests/core/test_release_tagging.py @@ -1121,6 +1121,37 @@ def test_make_rules_rejects_syntax_it_cannot_follow(tmp_path, old, new): make_rules(makefile) +@pytest.mark.parametrize( + "line_break", ["\v", "\x1c", "\x1d", "\x1e", "\x85", "\u2028", "\u2029"] +) +def test_make_rules_rejects_each_line_break_make_ignores(tmp_path, line_break): + makefile = tmp_path / "Makefile" + makefile.write_bytes( + MAKEFILE.read_bytes().replace( + b"\tcoverage xml -i", + f"\tcoverage xml -i $(if ,{line_break})|| true".encode(), + ) + ) + + with pytest.raises(AssertionError, match="make_rules cannot read a line break"): + make_rules(makefile) + + +def test_make_rules_reads_crlf_makefile_as_lf(tmp_path): + # Normalize first, since a Windows checkout may already be CRLF. + lf = MAKEFILE.read_bytes().replace(b"\r\n", b"\n") + lf_makefile = tmp_path / "lf" / "Makefile" + crlf_makefile = tmp_path / "crlf" / "Makefile" + lf_makefile.parent.mkdir() + crlf_makefile.parent.mkdir() + lf_makefile.write_bytes(lf) + crlf_makefile.write_bytes(lf.replace(b"\n", b"\r\n")) + + lf_rules = make_rules(lf_makefile) + assert "coverage xml -i" in lf_rules["test"][1] + assert make_rules(crlf_makefile) == lf_rules + + def test_pypi_upload_check_reads_past_comment_line(tmp_path): makefile = edit_makefile( tmp_path,