Skip to content

Commit f1f9922

Browse files
gh-152789: Skip curses tests needing newterm() on ncurses before 6.5
ncurses before 6.5 can crash on repeated newterm(). Fall back to initscr() in TestCurses and skip ScreenTests and SLKTests, which need several screens. The shared initscr() screen also needs its rendition and background reset between tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ddb6539 commit f1f9922

1 file changed

Lines changed: 14 additions & 1 deletion

File tree

Lib/test/test_curses.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ def wrapped(self, *args, **kwargs):
8080
term = os.environ.get('TERM')
8181
SHORT_MAX = 0x7fff
8282

83+
# ncurses before 6.5 can crash on repeated newterm(). Fall back to initscr()
84+
# and skip the tests that need several screens.
85+
_ncurses_version = getattr(curses, 'ncurses_version', None)
86+
BROKEN_NEWTERM = _ncurses_version is not None and _ncurses_version < (6, 5)
87+
USE_NEWTERM = hasattr(curses, 'newterm') and not BROKEN_NEWTERM
88+
8389
# newterm() is used when available (it reports errors instead of exiting), but
8490
# initscr() is still the fallback, and an unusable $TERM has no terminal to
8591
# drive either way.
@@ -139,7 +145,7 @@ def setUp(self):
139145
sys.stderr.flush()
140146
sys.stdout.flush()
141147
print(file=self.output, flush=True)
142-
if hasattr(curses, 'newterm'):
148+
if USE_NEWTERM:
143149
# Use newterm() rather than initscr(): it reports errors instead of
144150
# exiting, and gives each test a fresh screen, which also lets
145151
# ScreenTests run newterm()/set_term() in the same process.
@@ -157,7 +163,11 @@ def setUp(self):
157163
self.addCleanup(setattr, self, 'screen', None)
158164
self.addCleanup(setattr, self, 'stdscr', None)
159165
else:
166+
# Tests share one initscr() screen; clear the rendition and
167+
# background so a previous test's does not bleed in.
160168
self.stdscr = curses.initscr()
169+
self.stdscr.attrset(curses.A_NORMAL)
170+
self.stdscr.bkgdset(' ')
161171
if self.isatty:
162172
curses.savetty()
163173
self.addCleanup(curses.endwin)
@@ -2026,6 +2036,7 @@ def test_use_window(self):
20262036

20272037
@unittest.skipUnless(hasattr(curses.screen, 'use'),
20282038
'requires screen.use()')
2039+
@unittest.skipUnless(USE_NEWTERM, 'no screen object without newterm()')
20292040
def test_use_screen(self):
20302041
screen = self.screen
20312042
self.assertEqual(
@@ -2913,6 +2924,7 @@ def stop_reader():
29132924

29142925

29152926
@unittest.skipUnless(hasattr(curses, 'newterm'), 'requires curses.newterm()')
2927+
@unittest.skipIf(BROKEN_NEWTERM, 'ncurses < 6.5 mishandles repeated newterm()')
29162928
@unittest.skipIf(not term or term == 'unknown',
29172929
f"$TERM={term!r}, newterm() may not work")
29182930
@unittest.skipIf(sys.platform == "cygwin",
@@ -2994,6 +3006,7 @@ def test_disallow_instantiation(self):
29943006

29953007
@unittest.skipUnless(hasattr(curses, 'slk_init'), 'requires curses.slk_init()')
29963008
@unittest.skipUnless(hasattr(curses, 'newterm'), 'requires curses.newterm()')
3009+
@unittest.skipIf(BROKEN_NEWTERM, 'ncurses < 6.5 mishandles repeated newterm()')
29973010
@unittest.skipIf(not term or term == 'unknown',
29983011
f"$TERM={term!r}, newterm() may not work")
29993012
@unittest.skipIf(sys.platform == "cygwin",

0 commit comments

Comments
 (0)