From 0ba3386b8dfb9f22bdf64c3eb258070768b10bf3 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> Date: Sat, 31 Oct 2026 05:57:17 -0700 Subject: [PATCH] gh-160518: Fix hz encoder dropping half of the ~~ escape (GH-160519) Write both bytes of the escape before consuming the input character, so that a retry after the output buffer is enlarged starts at the tilde. (cherry picked from commit 1259f1602f870185a4980c80c525e110c4b9c614) Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> --- Lib/test/test_codecencodings_cn.py | 10 ++++++++++ .../2026-10-22-08-15-57.gh-issue-160518.hZt1Ld.rst | 3 +++ 2 files changed, 13 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-10-22-08-15-57.gh-issue-160518.hZt1Ld.rst diff --git a/Lib/test/test_codecencodings_cn.py b/Lib/test/test_codecencodings_cn.py index 2a450718d5aae57..35a2d7fb1ebf297 100644 --- a/Lib/test/test_codecencodings_cn.py +++ b/Lib/test/test_codecencodings_cn.py @@ -92,5 +92,15 @@ class Test_HZ(multibytecodec_support.TestBase, unittest.TestCase): (b'~{Dc~\n:C~}', 'strict', None), # ~\n only in ASCII mode ) + def test_escape_at_buffer_boundary(self): + # '~~' must not be cut in two when the output buffer is enlarged. + for pairs in range(40): + for letters in range(3): + text = 'a\u4e2d' * pairs + 'x' * letters + '~{' + with self.subTest(pairs=pairs, letters=letters): + encoded = text.encode('hz') + self.assertEndsWith(encoded, b'~~{') + self.assertEqual(encoded.decode('hz'), text) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-10-22-08-15-57.gh-issue-160518.hZt1Ld.rst b/Misc/NEWS.d/next/Library/2026-10-22-08-15-57.gh-issue-160518.hZt1Ld.rst new file mode 100644 index 000000000000000..3a3c6805ec82aea --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-22-08-15-57.gh-issue-160518.hZt1Ld.rst @@ -0,0 +1,3 @@ +Fix the ``hz`` codec sometimes encoding ``~`` as a single ``~`` instead of +``~~`` in text that mixes ASCII and Chinese characters, which produced data +that did not decode back to the original text.