11import os
2+ import sys
3+ import tempfile
24import unittest
35from collections import UserDict
6+ from test import support
47from test .support import import_helper
58from test .support .os_helper import unlink , TESTFN , TESTFN_ASCII , TESTFN_UNDECODABLE
69
10+
711NULL = None
812_testcapi = import_helper .import_module ('_testcapi' )
913Py_single_input = _testcapi .Py_single_input
1014Py_file_input = _testcapi .Py_file_input
1115Py_eval_input = _testcapi .Py_eval_input
16+ STDIN = '<stdin>'
17+ STDERR_FD = 2
18+
19+ # Code raising a SyntaxError
20+ SYNTAX_ERROR = 'True = 1'
21+
22+ # Raise a SystemExit with exit code 42
23+ CODE_EXIT_42 = 'raise SystemExit(42)'
24+
25+
26+ def create_text_file (filename , text ):
27+ with open (filename , 'w' , encoding = 'utf8' ) as fp :
28+ fp .write (text )
1229
1330
1431class DictSubclass (dict ):
1532 pass
1633
1734
18- class CAPITest (unittest .TestCase ):
19- # TODO: Test the following functions:
20- #
21- # PyRun_SimpleStringFlags
22- # PyRun_AnyFileExFlags
23- # PyRun_SimpleFileExFlags
24- # PyRun_InteractiveOneFlags
25- # PyRun_InteractiveOneObject
26- # PyRun_InteractiveLoopFlags
27- # PyRun_String (may be a macro)
28- # PyRun_AnyFile (may be a macro)
29- # PyRun_AnyFileEx (may be a macro)
30- # PyRun_AnyFileFlags (may be a macro)
31- # PyRun_SimpleString (may be a macro)
32- # PyRun_SimpleFile (may be a macro)
33- # PyRun_SimpleFileEx (may be a macro)
34- # PyRun_InteractiveOne (may be a macro)
35- # PyRun_InteractiveLoop (may be a macro)
36- # PyRun_File (may be a macro)
37- # PyRun_FileEx (may be a macro)
38- # PyRun_FileFlags (may be a macro)
35+ class capture_excepthook :
36+ def __init__ (self ):
37+ self .exc = None
38+ self ._old_hook = None
3939
40- def test_run_stringflags (self ):
41- # Test PyRun_StringFlags().
42- # XXX: fopen() uses different path encoding than Python on Windows.
40+ def _hook (self , exc_type , exc_value , exc_tb ):
41+ # Storing the exception instance creates a reference cycle.
42+ self .exc = exc_value
43+
44+ def __enter__ (self ):
45+ self ._old_hook = sys .excepthook
46+ sys .excepthook = self ._hook
47+ return self
48+
49+ def __exit__ (self , * exc_info ):
50+ sys .excepthook = self ._old_hook
51+ self .exc = None
52+
53+
54+ class CAPITest (unittest .TestCase ):
55+ def check_run_string (self , func ):
4356 def run (s , * args ):
44- return _testcapi .run_stringflags (s , Py_file_input , * args )
45- source = b'a\n '
57+ return func (s , Py_file_input , * args )
58+
59+ ns = {}
60+ self .assertIsNone (run (b'x = 1' , ns , ns ))
61+ self .assertEqual (ns ['x' ], 1 )
62+
63+ with self .assertRaises (SyntaxError ) as cm :
64+ run (SYNTAX_ERROR .encode (), {})
65+ self .assertEqual (cm .exception .filename , '<string>' )
66+
67+ with self .assertRaises (ValueError ) as cm :
68+ run (b'raise ValueError("BUG")' , {})
69+ self .assertEqual (str (cm .exception ), 'BUG' )
4670
4771 self .assertIsNone (run (b'a\n ' , dict (a = 1 )))
4872 self .assertIsNone (run (b'a\n ' , dict (a = 1 ), {}))
@@ -70,20 +94,29 @@ def run(s, *args):
7094
7195 # CRASHES run(NULL, {})
7296
73- def test_run_fileexflags (self ):
74- # Test PyRun_FileExFlags().
97+ def test_run_string (self ):
98+ # Test PyRun_String()
99+ self .check_run_string (_testcapi .run_string )
100+
101+ def test_run_stringflags (self ):
102+ # Test PyRun_StringFlags().
103+ self .check_run_string (_testcapi .run_stringflags )
104+
105+ def check_run_file (self , func , support_closeit ):
106+ # XXX: fopen() uses different path encoding than Python on Windows.
75107 filename = os .fsencode (TESTFN if os .name != 'nt' else TESTFN_ASCII )
76- with open (filename , 'wb' ) as fp :
77- fp .write (b'a\n ' )
108+ create_text_file (filename , 'a\n ' )
78109 self .addCleanup (unlink , filename )
79110 def run (* args ):
80- return _testcapi . run_fileexflags (filename , Py_file_input , * args )
111+ return func (filename , Py_file_input , * args )
81112
82113 self .assertIsNone (run (dict (a = 1 )))
83114 self .assertIsNone (run (dict (a = 1 ), {}))
84115 self .assertIsNone (run ({}, dict (a = 1 )))
85116 self .assertIsNone (run ({}, UserDict (a = 1 )))
86- self .assertIsNone (run (dict (a = 1 ), {}, 1 )) # closeit = True
117+ if support_closeit :
118+ closeit = 1
119+ self .assertIsNone (run (dict (a = 1 ), {}, closeit ))
87120
88121 self .assertRaises (NameError , run , {})
89122 self .assertRaises (NameError , run , {}, {})
@@ -101,17 +134,228 @@ def run(*args):
101134 self .assertRaises (SystemError , run , UserDict (), {})
102135 self .assertRaises (SystemError , run , UserDict (), dict (a = 1 ))
103136
104- @unittest .skipUnless (TESTFN_UNDECODABLE , 'only works if there are undecodable paths' )
105- @unittest .skipIf (os .name == 'nt' , 'does not work on Windows' )
106- def test_run_fileexflags_with_undecodable_filename (self ):
107- run = _testcapi .run_fileexflags
137+ # syntax error
138+ create_text_file (filename , SYNTAX_ERROR )
139+ with self .assertRaises (SyntaxError ) as cm :
140+ self .assertIsNone (run ({}))
141+ self .assertEqual (cm .exception .filename , os .fsdecode (filename ))
142+
143+ # raise exception
144+ create_text_file (filename , 'raise ValueError("BUG")' )
145+ with self .assertRaises (ValueError ) as cm :
146+ self .assertIsNone (run ({}))
147+ self .assertEqual (str (cm .exception ), 'BUG' )
148+
149+ # Test undecodable filename
150+ if TESTFN_UNDECODABLE and os .name != 'nt' :
151+ try :
152+ create_text_file (TESTFN_UNDECODABLE , 'a\n ' )
153+ self .addCleanup (unlink , TESTFN_UNDECODABLE )
154+ except OSError :
155+ # undecodable paths are not supported
156+ pass
157+ else :
158+ self .assertIsNone (func (TESTFN_UNDECODABLE , Py_file_input , dict (a = 1 )))
159+
160+ def test_run_file (self ):
161+ # Test PyRun_File().
162+ self .check_run_file (_testcapi .run_file , False )
163+
164+ def test_run_fileex (self ):
165+ # Test PyRun_FileEx().
166+ self .check_run_file (_testcapi .run_fileex , True )
167+
168+ def test_run_fileflags (self ):
169+ # Test PyRun_FileFlags().
170+ self .check_run_file (_testcapi .run_fileflags , True )
171+
172+ def test_run_fileexflags (self ):
173+ # Test PyRun_FileExFlags().
174+ self .check_run_file (_testcapi .run_fileexflags , True )
175+
176+ def check_run_simplefile (self , func_name , have_closeit ):
177+ run = getattr (_testcapi , func_name )
178+
179+ open_filename = TESTFN
180+ with open (open_filename , 'w' ) as fp :
181+ print ('import sys' , file = fp )
182+ print ('print(__file__)' , file = fp )
183+ print ('mod = sys.modules["__main__"]' , file = fp )
184+ print ('print(type(mod.__loader__).__name__)' , file = fp )
185+ self .addCleanup (unlink , open_filename )
186+
187+ class MockLoader :
188+ pass
189+ loader = MockLoader ()
190+
191+ for closeit in (0 , 1 ):
192+ for set_file in (False , True ):
193+ for filename in (open_filename , 'custom filename' , STDIN ):
194+ set_loader = (filename != STDIN )
195+ with self .subTest (closeit = closeit ,
196+ set_file = set_file ,
197+ filename = filename ):
198+ mod = sys .modules ['__main__' ]
199+ mod_file = mod .__file__
200+ with (support .captured_stdout () as stdout ,
201+ support .swap_attr (mod , '__loader__' , loader )):
202+ try :
203+ if set_file :
204+ del mod .__file__
205+ filename_arg = os .fsencode (filename )
206+ if have_closeit :
207+ res = run (open_filename , filename_arg , closeit )
208+ else :
209+ res = run (open_filename , filename_arg )
210+ if set_file :
211+ self .assertFalse (hasattr (mod , '__file__' ))
212+ finally :
213+ mod .__file__ = mod_file
214+
215+ self .assertEqual (res , 0 )
216+ expected = [filename if set_file else mod_file ]
217+ if set_loader :
218+ expected .append ('SourceFileLoader' )
219+ else :
220+ expected .append ('MockLoader' )
221+ self .assertEqual (stdout .getvalue ().splitlines (),
222+ expected )
223+
224+ def test_run_simplefile (self ):
225+ # Test PyRun_SimpleFile()
226+ self .check_run_simplefile ('run_simplefile' , False )
227+
228+ def test_run_simplefileex (self ):
229+ # Test PyRun_SimpleFileEx()
230+ self .check_run_simplefile ('run_simplefileex' , True )
231+
232+ def test_run_simplefileexflags (self ):
233+ # Test PyRun_SimpleFileExFlags()
234+ self .check_run_simplefile ('run_simplefileexflags' , True )
235+
236+ def test_run_anyfile (self ):
237+ # Test PyRun_AnyFile()
238+ self .check_run_simplefile ('run_anyfile' , False )
239+
240+ def test_run_anyfileex (self ):
241+ # Test PyRun_AnyFileEx()
242+ self .check_run_simplefile ('run_anyfileex' , True )
243+
244+ def test_run_anyfileflags (self ):
245+ # Test PyRun_AnyFileFlags()
246+ self .check_run_simplefile ('run_anyfileflags' , True )
247+
248+ def _check_run_interactive (self , run , encode_filename , use_loop ):
249+ open_filename = TESTFN
250+ if use_loop :
251+ with open (open_filename , 'w' ) as fp :
252+ print ('welcome = "hello REPL"' , file = fp )
253+ print ('print(welcome)' , file = fp )
254+ else :
255+ create_text_file (open_filename , 'print("hello REPL")' )
256+ self .addCleanup (unlink , open_filename )
257+
258+ for filename in ('filename' , STDIN ):
259+ with self .subTest (filename = filename ):
260+ with support .captured_stdout () as stdout :
261+ if encode_filename :
262+ filename = filename .encode ()
263+ self .assertEqual (run (open_filename , filename ), 0 )
264+ self .assertEqual (stdout .getvalue (), 'hello REPL\n ' )
265+
266+ create_text_file (open_filename , SYNTAX_ERROR )
267+ filename = 'custom filename'
268+ if encode_filename :
269+ filename_arg = filename .encode ()
270+ else :
271+ filename_arg = filename
272+ with capture_excepthook () as excepthook :
273+ expected = (0 if use_loop else - 1 )
274+ self .assertEqual (run (open_filename , filename_arg ), expected )
275+ self .assertIsInstance (excepthook .exc , SyntaxError )
276+ self .assertEqual (excepthook .exc .filename , filename )
277+
278+ create_text_file (open_filename , 'raise ValueError("BUG")' )
279+ with capture_excepthook () as excepthook :
280+ expected = (0 if use_loop else - 1 )
281+ self .assertEqual (run (open_filename , filename_arg ), expected )
282+ self .assertIsInstance (excepthook .exc , ValueError )
283+ self .assertEqual (str (excepthook .exc ), 'BUG' )
284+
285+ if not encode_filename :
286+ # wrong type for the second parameter (filename)
287+ with capture_excepthook () as excepthook :
288+ self .assertEqual (run (open_filename , b'bytes' ), - 1 )
289+ self .assertIsInstance (excepthook .exc , TypeError )
290+
291+ def check_run_interactive (self , run , encode_filename , use_loop = False ):
292+ # Redirect stderr to a temporary file to hide '>>> ' from the REPL
108293 try :
109- with open (TESTFN_UNDECODABLE , 'wb' ) as fp :
110- fp .write (b'a\n ' )
111- self .addCleanup (unlink , TESTFN_UNDECODABLE )
294+ stderr_copy = os .dup (STDERR_FD )
112295 except OSError :
113- self .skipTest ('undecodable paths are not supported' )
114- self .assertIsNone (run (TESTFN_UNDECODABLE , Py_file_input , dict (a = 1 )))
296+ # On WASI, dup(STDERR_FD) fails with "OSError: [Errno 58] Not
297+ # supported". In this case, run the test without redirecting
298+ # stderr to a temporary file.
299+ self ._check_run_interactive (run , encode_filename , use_loop )
300+ else :
301+ with tempfile .TemporaryFile () as tmp :
302+ try :
303+ os .dup2 (tmp .fileno (), STDERR_FD )
304+ self ._check_run_interactive (run , encode_filename , use_loop )
305+ finally :
306+ os .dup2 (stderr_copy , STDERR_FD )
307+
308+ def test_run_interactiveone (self ):
309+ # Test PyRun_InteractiveOne()
310+ run = _testcapi .run_interactiveone
311+ self .check_run_interactive (run , True )
312+
313+ def test_run_interactiveoneflags (self ):
314+ # Test PyRun_InteractiveOneFlags()
315+ run = _testcapi .run_interactiveoneflags
316+ self .check_run_interactive (run , True )
317+
318+ def test_run_interactiveoneobject (self ):
319+ # Test PyRun_InteractiveOneObject()
320+ run = _testcapi .run_interactiveoneobject
321+ self .check_run_interactive (run , False )
322+
323+ def test_run_interactiveloop (self ):
324+ # Test PyRun_InteractiveLoop()
325+ run = _testcapi .run_interactiveloop
326+ self .check_run_interactive (run , True , use_loop = True )
327+
328+ def test_run_interactiveloopflags (self ):
329+ # Test PyRun_InteractiveLoopFlags()
330+ run = _testcapi .run_interactiveloopflags
331+ self .check_run_interactive (run , True , use_loop = True )
332+
333+ def test_run_anyfileexflags (self ):
334+ # Test PyRun_AnyFileExFlags()
335+ self .check_run_simplefile ('run_anyfileexflags' , True )
336+
337+ def check_run_simplestring (self , run ):
338+ with support .captured_stdout () as stdout :
339+ run (b'print("simple hello")' )
340+ self .assertEqual (stdout .getvalue (), 'simple hello\n ' )
341+
342+ with capture_excepthook () as excepthook :
343+ self .assertEqual (run (SYNTAX_ERROR .encode ()), - 1 )
344+ self .assertIsInstance (excepthook .exc , SyntaxError )
345+ self .assertEqual (excepthook .exc .filename , '<string>' )
346+
347+ with capture_excepthook () as excepthook :
348+ self .assertEqual (run (b'raise ValueError("BUG")' ), - 1 )
349+ self .assertIsInstance (excepthook .exc , ValueError )
350+ self .assertEqual (str (excepthook .exc ), 'BUG' )
351+
352+ def test_run_simplestring (self ):
353+ # Test PyRun_SimpleString()
354+ self .check_run_simplestring (_testcapi .run_simplestring )
355+
356+ def test_run_simplestringflags (self ):
357+ # Test PyRun_SimpleStringFlags()
358+ self .check_run_simplestring (_testcapi .run_simplestringflags )
115359
116360
117361if __name__ == '__main__' :
0 commit comments