Skip to content

Commit 3be2f8f

Browse files
Drain the pty masters in ScreenTests so endwin() does not hang on macOS
ScreenTests drives curses over pseudo-terminals whose master ends are never read. On macOS (unlike Linux) the tcdrain() that curses performs inside endwin(), and even a plain write(), blocks once the unread output fills the pty buffer, so the test hung until the timeout. Drain the masters synchronously before endwin(), leaving room for its output. A background reader thread does not help: endwin() runs with the GIL held, so the thread cannot read after its first chunk. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0028058 commit 3be2f8f

1 file changed

Lines changed: 19 additions & 0 deletions

File tree

Lib/test/test_curses.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,11 +1773,28 @@ def setUp(self):
17731773
self.save_signals = SaveSignals()
17741774
self.save_signals.save()
17751775
self.addCleanup(self.save_signals.restore)
1776+
self.pty_masters = []
1777+
1778+
def drain_ptys(self):
1779+
# Discard whatever curses has written to the screens. Nothing reads
1780+
# the master end, so on platforms such as macOS (but not Linux) the
1781+
# tcdrain() that curses performs inside endwin() -- and even a plain
1782+
# write() -- blocks once the unread output fills the pty buffer.
1783+
# Draining here, before endwin(), leaves room for its output to drain.
1784+
# A background reader thread cannot help: endwin() runs with the GIL
1785+
# held, so the thread could never read after its first chunk.
1786+
for master in self.pty_masters:
1787+
try:
1788+
while os.read(master, 65536):
1789+
pass
1790+
except BlockingIOError:
1791+
pass
17761792

17771793
def tearDown(self):
17781794
# Leave visual mode and reclaim the screens the test created, while
17791795
# their pseudo-terminals are still open (closing them happens later,
17801796
# via the make_pty() cleanups).
1797+
self.drain_ptys()
17811798
try:
17821799
curses.endwin()
17831800
except curses.error:
@@ -1786,6 +1803,8 @@ def tearDown(self):
17861803

17871804
def make_pty(self):
17881805
master, slave = os.openpty()
1806+
os.set_blocking(master, False)
1807+
self.pty_masters.append(master)
17891808
self.addCleanup(os.close, master)
17901809
self.addCleanup(os.close, slave)
17911810
return slave

0 commit comments

Comments
 (0)