From 4c41f8b10d8c6c5bc72db80274472a5e29d35058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Fri, 2 Jan 2026 14:53:35 +0100 Subject: [PATCH] gh-128067: In test_pyrepl, discover escape sequences from terminfo instead of using hard-coded values --- Lib/test/test_pyrepl/test_pyrepl.py | 61 ++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 90cadd0b2ad494..6cf87522af2bc3 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -1862,24 +1862,49 @@ def test_no_newline(self): output, exit_code = self.run_repl(commands) self.assertEqual(exit_code, 0) - # Define escape sequences that don't affect cursor position or visual output - bracketed_paste_mode = r'\x1b\[\?2004[hl]' # Enable/disable bracketed paste - application_cursor_keys = r'\x1b\[\?1[hl]' # Enable/disable application cursor keys - application_keypad_mode = r'\x1b[=>]' # Enable/disable application keypad - insert_character = r'\x1b\[(?:1)?@(?=[ -~])' # Insert exactly 1 char (safe form) - cursor_visibility = r'\x1b\[\?25[hl]' # Show/hide cursor - cursor_blinking = r'\x1b\[\?12[hl]' # Start/stop cursor blinking - device_attributes = r'\x1b\[\?[01]c' # Device Attributes (DA) queries/responses - - safe_escapes = re.compile( - f'{bracketed_paste_mode}|' - f'{application_cursor_keys}|' - f'{application_keypad_mode}|' - f'{insert_character}|' - f'{cursor_visibility}|' - f'{cursor_blinking}|' - f'{device_attributes}' - ) + # Build patterns for escape sequences that don't affect cursor position + # or visual output. Use terminfo to get platform-specific sequences, + # falling back to hard-coded patterns for capabilities not in terminfo. + from _pyrepl.terminfo import TermInfo + ti = TermInfo(os.environ.get("TERM", "")) + + safe_patterns = [] + + # smkx/rmkx - application cursor keys and keypad mode + smkx = ti.get("smkx") + rmkx = ti.get("rmkx") + if smkx: + safe_patterns.append(re.escape(smkx.decode("ascii"))) + if rmkx: + safe_patterns.append(re.escape(rmkx.decode("ascii"))) + if not smkx and not rmkx: + safe_patterns.append(r'\x1b\[\?1[hl]') # application cursor keys + safe_patterns.append(r'\x1b[=>]') # application keypad mode + + # ich1 - insert character (only safe form that inserts exactly 1 char) + ich1 = ti.get("ich1") + if ich1: + safe_patterns.append(re.escape(ich1.decode("ascii")) + r'(?=[ -~])') + else: + safe_patterns.append(r'\x1b\[(?:1)?@(?=[ -~])') + + # civis/cnorm - cursor visibility (may include cursor blinking control) + civis = ti.get("civis") + cnorm = ti.get("cnorm") + if civis: + safe_patterns.append(re.escape(civis.decode("ascii"))) + if cnorm: + safe_patterns.append(re.escape(cnorm.decode("ascii"))) + if not civis and not cnorm: + safe_patterns.append(r'\x1b\[\?25[hl]') # cursor visibility + safe_patterns.append(r'\x1b\[\?12[hl]') # cursor blinking + + # Modern extensions not in standard terminfo - always use patterns + safe_patterns.append(r'\x1b\[\?2004[hl]') # bracketed paste mode + safe_patterns.append(r'\x1b\[\?12[hl]') # cursor blinking (may be separate) + safe_patterns.append(r'\x1b\[\?[01]c') # device attributes + + safe_escapes = re.compile('|'.join(safe_patterns)) cleaned_output = safe_escapes.sub('', output) self.assertIn(expected_output_sequence, cleaned_output)