@@ -53,9 +53,11 @@ def wrapped(self, *args, **kwargs):
5353term = os .environ .get ('TERM' )
5454SHORT_MAX = 0x7fff
5555
56- # If newterm was supported we could use it instead of initscr and not exit
56+ # newterm() is used when available (it reports errors instead of exiting), but
57+ # initscr() is still the fallback, and an unusable $TERM has no terminal to
58+ # drive either way.
5759@unittest .skipIf (not term or term == 'unknown' ,
58- "$TERM=%r, calling initscr() may cause exit " % term )
60+ "$TERM=%r, no usable terminal " % term )
5961@unittest .skipIf (sys .platform == "cygwin" ,
6062 "cygwin's curses mostly just hangs" )
6163class TestCurses (unittest .TestCase ):
@@ -110,7 +112,23 @@ def setUp(self):
110112 sys .stderr .flush ()
111113 sys .stdout .flush ()
112114 print (file = self .output , flush = True )
113- self .stdscr = curses .initscr ()
115+ if hasattr (curses , 'newterm' ):
116+ # Use newterm() rather than initscr(): it reports errors instead of
117+ # exiting, and gives each test a fresh screen, which also lets
118+ # ScreenTests run newterm()/set_term() in the same process.
119+ try :
120+ infd = sys .__stdin__ .fileno ()
121+ except (AttributeError , ValueError , OSError ):
122+ infd = stdout_fd
123+ self .screen = curses .newterm (term , stdout_fd , infd )
124+ self .stdscr = self .screen .stdscr
125+ # Drop the screen after the test so the screens do not pile up: a
126+ # window keeps its screen alive through a reference cycle, and
127+ # unittest keeps every test instance for the whole run.
128+ self .addCleanup (setattr , self , 'screen' , None )
129+ self .addCleanup (setattr , self , 'stdscr' , None )
130+ else :
131+ self .stdscr = curses .initscr ()
114132 if self .isatty :
115133 curses .savetty ()
116134 self .addCleanup (curses .endwin )
@@ -119,10 +137,12 @@ def setUp(self):
119137
120138 @requires_curses_func ('filter' )
121139 def test_filter (self ):
122- # TODO: Should be called before initscr() or newterm() are called.
140+ # filter() must be called before initscr()/newterm(); it confines
141+ # curses to a single line. Undo it with nofilter() afterwards so that
142+ # it does not shrink the screens created by later tests.
123143 curses .filter ()
124144 if hasattr (curses , 'nofilter' ):
125- curses .nofilter ( )
145+ self . addCleanup ( curses .nofilter )
126146
127147 @requires_curses_func ('use_env' )
128148 def test_use_env (self ):
@@ -1089,6 +1109,22 @@ def test_use_default_colors(self):
10891109 self .skipTest ('cannot change color (use_default_colors() failed)' )
10901110 self .assertEqual (curses .pair_content (0 ), (- 1 , - 1 ))
10911111
1112+ @requires_curses_window_meth ('use' )
1113+ def test_use_window (self ):
1114+ win = self .stdscr
1115+ self .assertEqual (win .use (lambda w , a , b : (w is win , a , b ), 5 , b = 6 ),
1116+ (True , 5 , 6 ))
1117+ with self .assertRaises (ZeroDivisionError ):
1118+ win .use (lambda w : 1 / 0 )
1119+
1120+ @unittest .skipUnless (hasattr (curses .screen , 'use' ),
1121+ 'requires screen.use()' )
1122+ def test_use_screen (self ):
1123+ screen = self .screen
1124+ self .assertEqual (
1125+ screen .use (lambda sc , flag : (sc is screen , flag ), flag = True ),
1126+ (True , True ))
1127+
10921128 @requires_curses_func ('assume_default_colors' )
10931129 @requires_colors
10941130 def test_assume_default_colors (self ):
@@ -1387,9 +1423,11 @@ def test_resize_term(self):
13871423 curses .resize_term (35000 , 1 )
13881424 with self .assertRaises (OverflowError ):
13891425 curses .resize_term (1 , 35000 )
1390- # GH-120378: Overflow failure in resize_term() causes refresh to fail
1391- tmp = curses .initscr ()
1392- tmp .erase ()
1426+ # GH-120378: a failed resize can leave refresh broken; restore the
1427+ # original size to recover. Avoid initscr(), which would switch away
1428+ # from the shared newterm() screen and corrupt later tests.
1429+ curses .resize_term (lines , cols )
1430+ self .stdscr .erase ()
13931431
13941432 @requires_curses_func ('resizeterm' )
13951433 def test_resizeterm (self ):
@@ -1409,9 +1447,11 @@ def test_resizeterm(self):
14091447 curses .resizeterm (35000 , 1 )
14101448 with self .assertRaises (OverflowError ):
14111449 curses .resizeterm (1 , 35000 )
1412- # GH-120378: Overflow failure in resizeterm() causes refresh to fail
1413- tmp = curses .initscr ()
1414- tmp .erase ()
1450+ # GH-120378: a failed resize can leave refresh broken; restore the
1451+ # original size to recover. Avoid initscr(), which would switch away
1452+ # from the shared newterm() screen and corrupt later tests.
1453+ curses .resizeterm (lines , cols )
1454+ self .stdscr .erase ()
14151455
14161456 def test_ungetch (self ):
14171457 curses .ungetch (b'A' )
@@ -1717,5 +1757,98 @@ def test_move_down(self):
17171757 self .mock_win .reset_mock ()
17181758
17191759
1760+ @unittest .skipUnless (hasattr (curses , 'newterm' ), 'requires curses.newterm()' )
1761+ @unittest .skipIf (not term or term == 'unknown' ,
1762+ "$TERM=%r, newterm() may not work" % term )
1763+ @unittest .skipIf (sys .platform == "cygwin" ,
1764+ "cygwin's curses mostly just hangs" )
1765+ class ScreenTests (unittest .TestCase ):
1766+ # newterm()/set_term() mutate global curses state, but each test drives its
1767+ # own pseudo-terminal(s) and never touches the screen shared by TestCurses,
1768+ # whose setUp() makes that screen current again. So these can run in this
1769+ # process, without a real terminal and without a subprocess.
1770+
1771+ def setUp (self ):
1772+ # newterm() may install signal handlers; restore them afterwards.
1773+ self .save_signals = SaveSignals ()
1774+ self .save_signals .save ()
1775+ self .addCleanup (self .save_signals .restore )
1776+
1777+ def tearDown (self ):
1778+ # Leave visual mode and reclaim the screens the test created, while
1779+ # their pseudo-terminals are still open (closing them happens later,
1780+ # via the make_pty() cleanups).
1781+ try :
1782+ curses .endwin ()
1783+ except curses .error :
1784+ pass
1785+ gc_collect ()
1786+
1787+ def make_pty (self ):
1788+ master , slave = os .openpty ()
1789+ self .addCleanup (os .close , master )
1790+ self .addCleanup (os .close , slave )
1791+ return slave
1792+
1793+ def test_newterm (self ):
1794+ s = self .make_pty ()
1795+ screen = curses .newterm ('xterm' , s , s )
1796+ self .assertIsInstance (screen , curses .screen )
1797+ win = screen .stdscr
1798+ self .assertIsInstance (win , curses .window )
1799+ self .assertEqual (win .getmaxyx (), (24 , 80 ))
1800+ win .addstr (0 , 0 , 'hello' )
1801+ win .refresh ()
1802+
1803+ def test_newterm_file_object (self ):
1804+ # type=None uses $TERM; the file arguments accept file objects too.
1805+ s = self .make_pty ()
1806+ out = os .fdopen (os .dup (s ), 'wb' , buffering = 0 )
1807+ self .addCleanup (out .close )
1808+ screen = curses .newterm (None , out , s )
1809+ self .assertIsInstance (screen , curses .screen )
1810+
1811+ def test_set_term (self ):
1812+ s = self .make_pty ()
1813+ s2 = self .make_pty ()
1814+ a = curses .newterm ('xterm' , s , s ) # current screen is a
1815+ b = curses .newterm ('xterm' , s2 , s2 ) # current screen is b
1816+ self .assertIs (curses .set_term (a ), b ) # returns the previous one
1817+ self .assertIs (curses .set_term (b ), a )
1818+
1819+ def test_window_keeps_screen_alive (self ):
1820+ # The standard window keeps its screen alive; dropping every other
1821+ # reference and collecting must not invalidate the window.
1822+ s = self .make_pty ()
1823+ win = curses .newterm ('xterm' , s , s ).stdscr
1824+ gc_collect ()
1825+ win .addstr (0 , 0 , 'still alive' )
1826+ win .refresh ()
1827+
1828+ def test_screen_freed (self ):
1829+ # Dropping all references to a (non-current) screen and its windows
1830+ # frees it without error.
1831+ s = self .make_pty ()
1832+ s2 = self .make_pty ()
1833+ a = curses .newterm ('xterm' , s , s )
1834+ b = curses .newterm ('xterm' , s2 , s2 ) # a is no longer current
1835+ del a
1836+ gc_collect ()
1837+
1838+ @unittest .skipUnless (hasattr (curses , 'new_prescr' ),
1839+ 'requires curses.new_prescr()' )
1840+ def test_new_prescr (self ):
1841+ screen = curses .new_prescr ()
1842+ self .assertIsInstance (screen , curses .screen )
1843+ self .assertIsNone (screen .stdscr )
1844+ del screen
1845+ gc_collect ()
1846+
1847+ @cpython_only
1848+ def test_disallow_instantiation (self ):
1849+ # The screen type cannot be instantiated directly (bpo-43916).
1850+ check_disallow_instantiation (self , curses .screen )
1851+
1852+
17201853if __name__ == '__main__' :
17211854 unittest .main ()
0 commit comments