https://github.com/python/cpython/commit/3428959a3c67138ab56b181953f429b7e1a4ab8b
commit: 3428959a3c67138ab56b181953f429b7e1a4ab8b
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-07-02T17:07:30+03:00
summary:

gh-152789: Skip curses tests needing newterm() on ncurses before 6.5 (GH-152880)

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 <[email protected]>

files:
M Lib/test/test_curses.py

diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py
index 5015b053ff29fa..5fe2850460717b 100644
--- a/Lib/test/test_curses.py
+++ b/Lib/test/test_curses.py
@@ -80,6 +80,12 @@ def wrapped(self, *args, **kwargs):
 term = os.environ.get('TERM')
 SHORT_MAX = 0x7fff
 
+# ncurses before 6.5 can crash on repeated newterm().  Fall back to initscr()
+# and skip the tests that need several screens.
+_ncurses_version = getattr(curses, 'ncurses_version', None)
+BROKEN_NEWTERM = _ncurses_version is not None and _ncurses_version < (6, 5)
+USE_NEWTERM = hasattr(curses, 'newterm') and not BROKEN_NEWTERM
+
 # newterm() is used when available (it reports errors instead of exiting), but
 # initscr() is still the fallback, and an unusable $TERM has no terminal to
 # drive either way.
@@ -139,7 +145,7 @@ def setUp(self):
             sys.stderr.flush()
             sys.stdout.flush()
             print(file=self.output, flush=True)
-        if hasattr(curses, 'newterm'):
+        if USE_NEWTERM:
             # Use newterm() rather than initscr(): it reports errors instead of
             # exiting, and gives each test a fresh screen, which also lets
             # ScreenTests run newterm()/set_term() in the same process.
@@ -157,7 +163,11 @@ def setUp(self):
             self.addCleanup(setattr, self, 'screen', None)
             self.addCleanup(setattr, self, 'stdscr', None)
         else:
+            # Tests share one initscr() screen; clear the rendition and
+            # background so a previous test's does not bleed in.
             self.stdscr = curses.initscr()
+            self.stdscr.attrset(curses.A_NORMAL)
+            self.stdscr.bkgdset(' ')
         if self.isatty:
             curses.savetty()
             self.addCleanup(curses.endwin)
@@ -2026,6 +2036,7 @@ def test_use_window(self):
 
     @unittest.skipUnless(hasattr(curses.screen, 'use'),
                          'requires screen.use()')
+    @unittest.skipUnless(USE_NEWTERM, 'no screen object without newterm()')
     def test_use_screen(self):
         screen = self.screen
         self.assertEqual(
@@ -2913,6 +2924,7 @@ def stop_reader():
 
 
 @unittest.skipUnless(hasattr(curses, 'newterm'), 'requires curses.newterm()')
[email protected](BROKEN_NEWTERM, 'ncurses < 6.5 mishandles repeated newterm()')
 @unittest.skipIf(not term or term == 'unknown',
                  f"$TERM={term!r}, newterm() may not work")
 @unittest.skipIf(sys.platform == "cygwin",
@@ -2994,6 +3006,7 @@ def test_disallow_instantiation(self):
 
 @unittest.skipUnless(hasattr(curses, 'slk_init'), 'requires curses.slk_init()')
 @unittest.skipUnless(hasattr(curses, 'newterm'), 'requires curses.newterm()')
[email protected](BROKEN_NEWTERM, 'ncurses < 6.5 mishandles repeated newterm()')
 @unittest.skipIf(not term or term == 'unknown',
                  f"$TERM={term!r}, newterm() may not work")
 @unittest.skipIf(sys.platform == "cygwin",

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to