Skip to content

Commit bf6509e

Browse files
committed
Observe the hook's output synchronously instead of via the reader thread
The reader thread no longer feeds any assertion: the hook drains the pty master itself, so the check is an exact comparison. A drainer is still needed for restore(), which writes before switching modes.
1 parent 3436671 commit bf6509e

1 file changed

Lines changed: 39 additions & 47 deletions

File tree

Lib/test/test_pyrepl/test_unix_console.py

Lines changed: 39 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -445,34 +445,21 @@ class TestUnixConsoleInputHook(TestCase):
445445

446446
def test_input_hook_output_is_cooked(self):
447447
master_fd, slave_fd = pty.openpty()
448-
449-
# Drain the master continuously: on some platforms (e.g. macOS)
450-
# tcsetattr(TCSADRAIN) blocks until the master side is read, so an
451-
# undrained pty would deadlock the mode switch.
452-
chunks = []
453-
reading = True
454-
455-
def reader():
456-
while reading:
457-
r, _, _ = select.select([master_fd], [], [], 0.1)
458-
if master_fd in r:
459-
try:
460-
data = os.read(master_fd, 4096)
461-
except OSError:
462-
break
463-
if not data:
464-
break
465-
chunks.append(data)
466-
467-
reader_thread = threading.Thread(target=reader)
468-
reader_thread.start()
469-
470-
def cleanup():
471-
nonlocal reading
472-
reading = False
473-
reader_thread.join()
474-
os.close(master_fd)
475-
self.addCleanup(cleanup)
448+
self.addCleanup(os.close, master_fd)
449+
450+
# tcsetattr(TCSADRAIN) blocks on some platforms (e.g. macOS) while the
451+
# master still holds unread output, so empty it before each mode switch.
452+
def drain():
453+
out = b""
454+
while select.select([master_fd], [], [], 0)[0]:
455+
try:
456+
data = os.read(master_fd, 4096)
457+
except OSError:
458+
break
459+
if not data:
460+
break
461+
out += data
462+
return out
476463

477464
# Start from a cooked terminal so there are saved flags to restore.
478465
attr = _termios.tcgetattr(slave_fd)
@@ -482,6 +469,7 @@ def cleanup():
482469
console = UnixConsole(slave_fd, slave_fd, term="xterm")
483470
console.prepare()
484471
try:
472+
drain() # discard prepare()'s own setup sequences
485473
# pyrepl's own rendering runs with OPOST cleared.
486474
self.assertFalse(_termios.tcgetattr(slave_fd)[1] & _termios.OPOST)
487475

@@ -490,6 +478,7 @@ def cleanup():
490478
def fake_hook():
491479
observed["oflag"] = _termios.tcgetattr(slave_fd)[1]
492480
os.write(slave_fd, b"line1\nline2\n")
481+
observed["output"] = drain()
493482
return 0
494483

495484
with patch("_pyrepl.unix_console.posix") as mock_posix:
@@ -503,24 +492,27 @@ def fake_hook():
503492
self.assertTrue(observed["oflag"] & _termios.OPOST)
504493
# ...and raw mode was restored afterwards.
505494
self.assertFalse(_termios.tcgetattr(slave_fd)[1] & _termios.OPOST)
495+
# The tty translated the hook's bare '\n' into '\r\n'.
496+
self.assertEqual(observed["output"], b"line1\r\nline2\r\n")
506497
finally:
507-
console.restore()
508-
os.close(slave_fd)
509-
510-
# The switch back to raw mode already drained the hook's output, so
511-
# joining the reader is enough -- no sleep needed.
512-
reading = False
513-
reader_thread.join()
514-
while select.select([master_fd], [], [], 0)[0]:
498+
# restore() writes and only then switches modes, so there is no
499+
# point left to drain from here; keep the master empty elsewhere.
500+
stop = threading.Event()
501+
502+
def pump():
503+
while not stop.is_set():
504+
if select.select([master_fd], [], [], 0.05)[0]:
505+
try:
506+
if not os.read(master_fd, 4096):
507+
break
508+
except OSError:
509+
break
510+
511+
pump_thread = threading.Thread(target=pump)
512+
pump_thread.start()
515513
try:
516-
extra = os.read(master_fd, 4096)
517-
except OSError:
518-
break
519-
if not extra:
520-
break
521-
chunks.append(extra)
522-
523-
data = b"".join(chunks)
524-
# The tty translated the hook's bare '\n' into '\r\n'.
525-
self.assertIn(b"line1\r\nline2\r\n", data)
526-
self.assertNotIn(b"line1\nline2\n", data)
514+
console.restore()
515+
finally:
516+
stop.set()
517+
pump_thread.join()
518+
os.close(slave_fd)

0 commit comments

Comments
 (0)