55import subprocess
66import sys
77import unittest
8+ from contextlib import contextmanager
89from functools import partial
910from textwrap import dedent
1011from test import support
2829 raise unittest .SkipTest ("test module requires subprocess" )
2930
3031
31- def spawn_repl (* args , stdout = subprocess .PIPE , stderr = subprocess .STDOUT , custom = False , ** kw ):
32+ def spawn_repl (* args , stdout = subprocess .PIPE , stderr = subprocess .STDOUT , custom = False , isolated = True , ** kw ):
3233 """Run the Python REPL with the given arguments.
3334
3435 kw is extra keyword args to pass to subprocess.Popen. Returns a Popen
@@ -42,7 +43,11 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F
4243 # path may be used by PyConfig_Get("module_search_paths") to build the
4344 # default module search path.
4445 stdin_fname = os .path .join (os .path .dirname (sys .executable ), "<stdin>" )
45- cmd_line = [stdin_fname , '-I' ]
46+ cmd_line = [stdin_fname ]
47+ # Isolated mode implies -E, -P and -s, purifies sys.path and ignores PYTHON*
48+ # variables.
49+ if isolated :
50+ cmd_line .append ('-I' )
4651 # Don't re-run the built-in REPL from interactive mode
4752 # if we're testing a custom REPL (such as the asyncio REPL).
4853 if not custom :
@@ -197,63 +202,6 @@ def foo(x):
197202 ]
198203 self .assertEqual (traceback_lines , expected_lines )
199204
200- def test_pythonstartup_success (self ):
201- # errors based on https://github.com/python/cpython/issues/137576
202- # case 1: error in user input, but PYTHONSTARTUP is fine
203- for repl_name , repl in ("REPL" , spawn_repl ), ("asyncio REPL" , spawn_asyncio_repl ):
204- with os_helper .temp_dir () as tmpdir :
205- script = os .path .join (tmpdir , "pythonstartup.py" )
206- with open (script , "w" ) as f :
207- f .write ("print('from pythonstartup')" + os .linesep )
208-
209- env = os .environ .copy ()
210- env ['PYTHONSTARTUP' ] = script
211- env ["PYTHON_HISTORY" ] = os .path .join (tmpdir , ".pythonhist" )
212- p = repl (env = env )
213- p .stdin .write ("1/0" )
214- output = kill_python (p )
215-
216- with self .subTest (repl_name ):
217- self .assertIn ("Traceback (most recent call last):" , output )
218- expected = dedent ("""
219- File "<stdin>", line 1, in <module>
220- 1/0
221- ~^~
222- ZeroDivisionError: division by zero
223- """ )
224- self .assertIn ("from pythonstartup" , output )
225- self .assertIn (expected , output )
226-
227- def test_pythonstartup_failure (self ):
228- # case 2: error in PYTHONSTARTUP triggered by user input
229- for repl_name , repl in ("REPL" , spawn_repl ), ("asyncio REPL" , spawn_asyncio_repl ):
230- with os_helper .temp_dir () as tmpdir :
231- script = os .path .join (tmpdir , "pythonstartup.py" )
232- with open (script , "w" ) as f :
233- f .write ("def foo():\n 1/0\n " )
234-
235- env = os .environ .copy ()
236- env ['PYTHONSTARTUP' ] = script
237- env ["PYTHON_HISTORY" ] = os .path .join (tmpdir , ".pythonhist" )
238- p = repl (env = env )
239- p .stdin .write ('foo()' )
240- output = kill_python (p )
241-
242- with self .subTest (repl_name ):
243- self .assertIn ("Traceback (most recent call last):" , output )
244- expected = dedent ("""
245- File "<stdin>", line 1, in <module>
246- foo()
247- ~~~^^
248- File "%s", line 2, in foo
249- 1/0
250- ~^~
251- ZeroDivisionError: division by zero
252- """ ) % script
253- self .assertIn (expected , output )
254-
255-
256-
257205 def test_runsource_show_syntax_error_location (self ):
258206 user_input = dedent ("""def f(x, x): ...
259207 """ )
@@ -287,24 +235,6 @@ def bar(x):
287235 expected = "(30, None, [\' def foo(x):\\ n\' , \' return x + 1\\ n\' , \' \\ n\' ], \' <stdin>\' )"
288236 self .assertIn (expected , output , expected )
289237
290- def test_asyncio_repl_reaches_python_startup_script (self ):
291- with os_helper .temp_dir () as tmpdir :
292- script = os .path .join (tmpdir , "pythonstartup.py" )
293- with open (script , "w" ) as f :
294- f .write ("print('pythonstartup done!')" + os .linesep )
295- f .write ("exit(0)" + os .linesep )
296-
297- env = os .environ .copy ()
298- env ["PYTHON_HISTORY" ] = os .path .join (tmpdir , ".asyncio_history" )
299- env ["PYTHONSTARTUP" ] = script
300- subprocess .check_call (
301- [sys .executable , "-m" , "asyncio" ],
302- stdout = subprocess .PIPE ,
303- stderr = subprocess .PIPE ,
304- env = env ,
305- timeout = SHORT_TIMEOUT ,
306- )
307-
308238 @unittest .skipUnless (pty , "requires pty" )
309239 def test_asyncio_repl_is_ok (self ):
310240 m , s = pty .openpty ()
@@ -341,6 +271,75 @@ def test_asyncio_repl_is_ok(self):
341271 self .assertEqual (exit_code , 0 , "" .join (output ))
342272
343273
274+ @contextmanager
275+ def pythonstartup_env (* , script : str , histfile : str = ".pythonhist" , env = None ):
276+ with os_helper .temp_dir () as tmpdir :
277+ filename = os .path .join (tmpdir , "pythonstartup.py" )
278+ with open (filename , "w" ) as f :
279+ f .write (os .linesep .join (script .splitlines ()))
280+ if env is None :
281+ env = os .environ .copy ()
282+ yield env | {"PYTHONSTARTUP" : filename , "PYTHON_HISTORY" : os .path .join (tmpdir , histfile )}
283+
284+
285+ class TestPythonStartup (unittest .TestCase ):
286+ REPLS = [
287+ ("REPL" , spawn_repl , ".pythonhist" ),
288+ ("asyncio REPL" , spawn_asyncio_repl , ".asyncio_history" ),
289+ ]
290+
291+ def test_pythonstartup_success (self ):
292+ # errors based on https://github.com/python/cpython/issues/137576
293+ # case 1: error in user input, but PYTHONSTARTUP is fine
294+ for repl_name , repl_factory , histfile in self .REPLS :
295+ with (
296+ self .subTest (repl_name ),
297+ pythonstartup_env (script = "print('from pythonstartup')" , histfile = histfile ) as env
298+ ):
299+ p = repl_factory (env = env , isolated = False )
300+ p .stdin .write ("1/0" )
301+ output = kill_python (p )
302+
303+ for chunk in (
304+ "from pythonstartup" ,
305+ "Traceback (most recent call last):" ,
306+ """\
307+ File "<stdin>", line 1, in <module>
308+ 1/0
309+ ~^~
310+ ZeroDivisionError: division by zero
311+ """
312+ ):
313+ self .assertIn (dedent (chunk ), output )
314+
315+ def test_pythonstartup_failure (self ):
316+ # case 2: error in PYTHONSTARTUP triggered by user input
317+ for repl_name , repl_factory , histfile in self .REPLS :
318+ with (
319+ self .subTest (repl_name ),
320+ pythonstartup_env (script = "def foo():\n 1/0\n " , histfile = histfile ) as env
321+ ):
322+ p = repl_factory (env = env , isolated = False )
323+ p .stdin .write ('foo()' )
324+ output = kill_python (p )
325+
326+ for chunk in (
327+ "Traceback (most recent call last):" ,
328+ """\
329+ File "<stdin>", line 1, in <module>
330+ foo()
331+ ~~~^^
332+ """ ,
333+ f"""\
334+ File "{ env ['PYTHONSTARTUP' ]} ", line 2, in foo
335+ 1/0
336+ ~^~
337+ ZeroDivisionError: division by zero
338+ """
339+ ):
340+ self .assertIn (dedent (chunk ), output )
341+
342+
344343@support .force_not_colorized_test_class
345344class TestInteractiveModeSyntaxErrors (unittest .TestCase ):
346345
0 commit comments