88import subprocess
99import sys
1010import tempfile
11- from unittest import TestCase , skipUnless
11+ from unittest import TestCase , skipUnless , skipIf
1212from unittest .mock import patch
1313from test .support import force_not_colorized
1414from test .support import SHORT_TIMEOUT
3535except ImportError :
3636 pty = None
3737
38+
39+ class ReplTestCase (TestCase ):
40+ def run_repl (
41+ self ,
42+ repl_input : str | list [str ],
43+ env : dict | None = None ,
44+ * ,
45+ cmdline_args : list [str ] | None = None ,
46+ cwd : str | None = None ,
47+ ) -> tuple [str , int ]:
48+ temp_dir = None
49+ if cwd is None :
50+ temp_dir = tempfile .TemporaryDirectory (ignore_cleanup_errors = True )
51+ cwd = temp_dir .name
52+ try :
53+ return self ._run_repl (
54+ repl_input , env = env , cmdline_args = cmdline_args , cwd = cwd
55+ )
56+ finally :
57+ if temp_dir is not None :
58+ temp_dir .cleanup ()
59+
60+ def _run_repl (
61+ self ,
62+ repl_input : str | list [str ],
63+ * ,
64+ env : dict | None ,
65+ cmdline_args : list [str ] | None ,
66+ cwd : str ,
67+ ) -> tuple [str , int ]:
68+ assert pty
69+ master_fd , slave_fd = pty .openpty ()
70+ cmd = [sys .executable , "-i" , "-u" ]
71+ if env is None :
72+ cmd .append ("-I" )
73+ elif "PYTHON_HISTORY" not in env :
74+ env ["PYTHON_HISTORY" ] = os .path .join (cwd , ".regrtest_history" )
75+ if cmdline_args is not None :
76+ cmd .extend (cmdline_args )
77+
78+ try :
79+ import termios
80+ except ModuleNotFoundError :
81+ pass
82+ else :
83+ term_attr = termios .tcgetattr (slave_fd )
84+ term_attr [6 ][termios .VREPRINT ] = 0 # pass through CTRL-R
85+ term_attr [6 ][termios .VINTR ] = 0 # pass through CTRL-C
86+ termios .tcsetattr (slave_fd , termios .TCSANOW , term_attr )
87+
88+ process = subprocess .Popen (
89+ cmd ,
90+ stdin = slave_fd ,
91+ stdout = slave_fd ,
92+ stderr = slave_fd ,
93+ cwd = cwd ,
94+ text = True ,
95+ close_fds = True ,
96+ env = env if env else os .environ ,
97+ )
98+ os .close (slave_fd )
99+ if isinstance (repl_input , list ):
100+ repl_input = "\n " .join (repl_input ) + "\n "
101+ os .write (master_fd , repl_input .encode ("utf-8" ))
102+
103+ output = []
104+ while select .select ([master_fd ], [], [], SHORT_TIMEOUT )[0 ]:
105+ try :
106+ data = os .read (master_fd , 1024 ).decode ("utf-8" )
107+ if not data :
108+ break
109+ except OSError :
110+ break
111+ output .append (data )
112+ else :
113+ os .close (master_fd )
114+ process .kill ()
115+ self .fail (f"Timeout while waiting for output, got: { '' .join (output )} " )
116+
117+ os .close (master_fd )
118+ try :
119+ exit_code = process .wait (timeout = SHORT_TIMEOUT )
120+ except subprocess .TimeoutExpired :
121+ process .kill ()
122+ exit_code = process .wait ()
123+ return "" .join (output ), exit_code
124+
125+
38126class TestCursorPosition (TestCase ):
39127 def prepare_reader (self , events ):
40128 console = FakeConsole (events )
@@ -968,7 +1056,20 @@ def test_bracketed_paste_single_line(self):
9681056
9691057
9701058@skipUnless (pty , "requires pty" )
971- class TestMain (TestCase ):
1059+ class TestDumbTerminal (ReplTestCase ):
1060+ def test_dumb_terminal_exits_cleanly (self ):
1061+ env = os .environ .copy ()
1062+ env .update ({"TERM" : "dumb" })
1063+ output , exit_code = self .run_repl ("exit()\n " , env = env )
1064+ self .assertEqual (exit_code , 0 )
1065+ self .assertIn ("warning: can't use pyrepl" , output )
1066+ self .assertNotIn ("Exception" , output )
1067+ self .assertNotIn ("Traceback" , output )
1068+
1069+
1070+ @skipUnless (pty , "requires pty" )
1071+ @skipIf ((os .environ .get ("TERM" ) or "dumb" ) == "dumb" , "can't use pyrepl in dumb terminal" )
1072+ class TestMain (ReplTestCase ):
9721073 def setUp (self ):
9731074 # Cleanup from PYTHON* variables to isolate from local
9741075 # user settings, see #121359. Such variables should be
@@ -1078,15 +1179,6 @@ def test_inspect_keeps_globals_from_inspected_module(self):
10781179 }
10791180 self ._run_repl_globals_test (expectations , as_module = True )
10801181
1081- def test_dumb_terminal_exits_cleanly (self ):
1082- env = os .environ .copy ()
1083- env .update ({"TERM" : "dumb" })
1084- output , exit_code = self .run_repl ("exit()\n " , env = env )
1085- self .assertEqual (exit_code , 0 )
1086- self .assertIn ("warning: can't use pyrepl" , output )
1087- self .assertNotIn ("Exception" , output )
1088- self .assertNotIn ("Traceback" , output )
1089-
10901182 @force_not_colorized
10911183 def test_python_basic_repl (self ):
10921184 env = os .environ .copy ()
@@ -1209,80 +1301,6 @@ def test_proper_tracebacklimit(self):
12091301 self .assertIn ("in x3" , output )
12101302 self .assertIn ("in <module>" , output )
12111303
1212- def run_repl (
1213- self ,
1214- repl_input : str | list [str ],
1215- env : dict | None = None ,
1216- * ,
1217- cmdline_args : list [str ] | None = None ,
1218- cwd : str | None = None ,
1219- ) -> tuple [str , int ]:
1220- temp_dir = None
1221- if cwd is None :
1222- temp_dir = tempfile .TemporaryDirectory (ignore_cleanup_errors = True )
1223- cwd = temp_dir .name
1224- try :
1225- return self ._run_repl (
1226- repl_input , env = env , cmdline_args = cmdline_args , cwd = cwd
1227- )
1228- finally :
1229- if temp_dir is not None :
1230- temp_dir .cleanup ()
1231-
1232- def _run_repl (
1233- self ,
1234- repl_input : str | list [str ],
1235- * ,
1236- env : dict | None ,
1237- cmdline_args : list [str ] | None ,
1238- cwd : str ,
1239- ) -> tuple [str , int ]:
1240- assert pty
1241- master_fd , slave_fd = pty .openpty ()
1242- cmd = [sys .executable , "-i" , "-u" ]
1243- if env is None :
1244- cmd .append ("-I" )
1245- elif "PYTHON_HISTORY" not in env :
1246- env ["PYTHON_HISTORY" ] = os .path .join (cwd , ".regrtest_history" )
1247- if cmdline_args is not None :
1248- cmd .extend (cmdline_args )
1249- process = subprocess .Popen (
1250- cmd ,
1251- stdin = slave_fd ,
1252- stdout = slave_fd ,
1253- stderr = slave_fd ,
1254- cwd = cwd ,
1255- text = True ,
1256- close_fds = True ,
1257- env = env if env else os .environ ,
1258- )
1259- os .close (slave_fd )
1260- if isinstance (repl_input , list ):
1261- repl_input = "\n " .join (repl_input ) + "\n "
1262- os .write (master_fd , repl_input .encode ("utf-8" ))
1263-
1264- output = []
1265- while select .select ([master_fd ], [], [], SHORT_TIMEOUT )[0 ]:
1266- try :
1267- data = os .read (master_fd , 1024 ).decode ("utf-8" )
1268- if not data :
1269- break
1270- except OSError :
1271- break
1272- output .append (data )
1273- else :
1274- os .close (master_fd )
1275- process .kill ()
1276- self .fail (f"Timeout while waiting for output, got: { '' .join (output )} " )
1277-
1278- os .close (master_fd )
1279- try :
1280- exit_code = process .wait (timeout = SHORT_TIMEOUT )
1281- except subprocess .TimeoutExpired :
1282- process .kill ()
1283- exit_code = process .wait ()
1284- return "" .join (output ), exit_code
1285-
12861304 def test_readline_history_file (self ):
12871305 # skip, if readline module is not available
12881306 readline = import_module ('readline' )
@@ -1305,3 +1323,7 @@ def test_readline_history_file(self):
13051323 output , exit_code = self .run_repl ("exit\n " , env = env )
13061324 self .assertEqual (exit_code , 0 )
13071325 self .assertNotIn ("\\ 040" , pathlib .Path (hfile .name ).read_text ())
1326+
1327+ def test_keyboard_interrupt_after_isearch (self ):
1328+ output , exit_code = self .run_repl (["\x12 " , "\x03 " , "exit" ])
1329+ self .assertEqual (exit_code , 0 )
0 commit comments