66
77import sys
88import nose .tools as nt
9- from textwrap import dedent
9+ from textwrap import dedent , indent
1010from unittest import TestCase
1111
1212ip = get_ipython ()
1313iprc = lambda x : ip .run_cell (dedent (x ))
14+ iprc_err = lambda x : iprc (x ).raise_error ()
1415
1516if sys .version_info > (3 , 5 ):
1617 from IPython .core .async_helpers import _should_be_async
@@ -31,6 +32,183 @@ async def awaitable():
3132 )
3233 )
3334
35+ def _get_top_level_cases (self ):
36+ # These are test cases that should be valid in a function
37+ # but invalid outside of a function.
38+ test_cases = []
39+ test_cases .append (('basic' , "{val}" ))
40+
41+ # Note, in all conditional cases, I use True instead of
42+ # False so that the peephole optimizer won't optimize away
43+ # the return, so CPython will see this as a syntax error:
44+ #
45+ # while True:
46+ # break
47+ # return
48+ #
49+ # But not this:
50+ #
51+ # while False:
52+ # return
53+ #
54+ # See https://bugs.python.org/issue1875
55+
56+ test_cases .append (('if' , dedent ("""
57+ if True:
58+ {val}
59+ """ )))
60+
61+ test_cases .append (('while' , dedent ("""
62+ while True:
63+ {val}
64+ break
65+ """ )))
66+
67+ test_cases .append (('try' , dedent ("""
68+ try:
69+ {val}
70+ except:
71+ pass
72+ """ )))
73+
74+ test_cases .append (('except' , dedent ("""
75+ try:
76+ pass
77+ except:
78+ {val}
79+ """ )))
80+
81+ test_cases .append (('finally' , dedent ("""
82+ try:
83+ pass
84+ except:
85+ pass
86+ finally:
87+ {val}
88+ """ )))
89+
90+ test_cases .append (('for' , dedent ("""
91+ for _ in range(4):
92+ {val}
93+ """ )))
94+
95+
96+ test_cases .append (('nested' , dedent ("""
97+ if True:
98+ while True:
99+ {val}
100+ break
101+ """ )))
102+
103+ test_cases .append (('deep-nested' , dedent ("""
104+ if True:
105+ while True:
106+ break
107+ for x in range(3):
108+ if True:
109+ while True:
110+ for x in range(3):
111+ {val}
112+ """ )))
113+
114+ return test_cases
115+
116+ def _get_ry_syntax_errors (self ):
117+ # This is a mix of tests that should be a syntax error if
118+ # return or yield whether or not they are in a function
119+
120+ test_cases = []
121+
122+ test_cases .append (('class' , dedent ("""
123+ class V:
124+ {val}
125+ """ )))
126+
127+ test_cases .append (('nested-class' , dedent ("""
128+ class V:
129+ class C:
130+ {val}
131+ """ )))
132+
133+ return test_cases
134+
135+
136+ def test_top_level_return_error (self ):
137+ tl_err_test_cases = self ._get_top_level_cases ()
138+ tl_err_test_cases .extend (self ._get_ry_syntax_errors ())
139+
140+ vals = ('return' , 'yield' , 'yield from (_ for _ in range(3))' )
141+
142+ for test_name , test_case in tl_err_test_cases :
143+ # This example should work if 'pass' is used as the value
144+ with self .subTest ((test_name , 'pass' )):
145+ iprc_err (test_case .format (val = 'pass' ))
146+
147+ # It should fail with all the values
148+ for val in vals :
149+ with self .subTest ((test_name , val )):
150+ msg = "Syntax error not raised for %s, %s" % (test_name , val )
151+ with self .assertRaises (SyntaxError , msg = msg ):
152+ iprc_err (test_case .format (val = val ))
153+
154+ def test_in_func_no_error (self ):
155+ # Test that the implementation of top-level return/yield
156+ # detection isn't *too* aggressive, and works inside a function
157+ func_contexts = []
158+
159+ func_contexts .append (('func' , dedent ("""
160+ def f():""" )))
161+
162+ func_contexts .append (('method' , dedent ("""
163+ class MyClass:
164+ def __init__(self):
165+ """ )))
166+
167+ func_contexts .append (('async-func' , dedent ("""
168+ async def f():""" )))
169+
170+ func_contexts .append (('closure' , dedent ("""
171+ def f():
172+ def g():
173+ """ )))
174+
175+ def nest_case (context , case ):
176+ # Detect indentation
177+ lines = context .strip ().splitlines ()
178+ prefix_len = 0
179+ for c in lines [- 1 ]:
180+ if c != ' ' :
181+ break
182+ prefix_len += 1
183+
184+ indented_case = indent (case , ' ' * (prefix_len + 4 ))
185+ return context + '\n ' + indented_case
186+
187+ # Gather and run the tests
188+ vals = ('return' , 'yield' )
189+
190+ success_tests = self ._get_top_level_cases ()
191+ failure_tests = self ._get_ry_syntax_errors ()
192+
193+ for context_name , context in func_contexts :
194+ # These tests should now successfully run
195+ for test_name , test_case in success_tests :
196+ nested_case = nest_case (context , test_case )
197+
198+ for val in vals :
199+ with self .subTest ((test_name , context_name , val )):
200+ iprc_err (nested_case .format (val = val ))
201+
202+ # These tests should still raise a SyntaxError
203+ for test_name , test_case in failure_tests :
204+ nested_case = nest_case (context , test_case )
205+
206+ for val in vals :
207+ with self .subTest ((test_name , context_name , val )):
208+ with self .assertRaises (SyntaxError ):
209+ iprc_err (nested_case .format (val = val ))
210+
211+
34212 def test_execute (self ):
35213 iprc (
36214 """
0 commit comments