Skip to content

Commit eee14de

Browse files
committed
Add test_nonlocal.py that mirrors test_global.py
1 parent c45be8a commit eee14de

File tree

1 file changed

+276
-0
lines changed

1 file changed

+276
-0
lines changed

Lib/test/test_nonlocal.py

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
"""This module includes tests for syntax errors that occur when a name
2+
declared as `nonlocal` is used in ways that violate the language
3+
specification, such as after assignment, usage, or annotation. The tests
4+
verify that syntax errors are correctly raised for improper `nonlocal`
5+
statements following variable use or assignment within functions.
6+
Additionally, it tests various name-binding scenarios for nonlocal
7+
variables to ensure correct behavior.
8+
9+
See `test_scope.py` for additional related behavioral tests covering
10+
variable scoping and usage in different contexts.
11+
"""
12+
13+
import contextlib
14+
from test.support import check_syntax_error
15+
from test.support.warnings_helper import check_warnings
16+
from types import SimpleNamespace
17+
import unittest
18+
import warnings
19+
20+
21+
class NonlocalTests(unittest.TestCase):
22+
23+
def setUp(self):
24+
self.enterContext(check_warnings())
25+
warnings.filterwarnings("error", module="<test string>")
26+
27+
######################################################
28+
### Syntax error cases as covered in Python/symtable.c
29+
######################################################
30+
31+
def test_name_param(self):
32+
prog_text = """\
33+
def fn(name_param):
34+
nonlocal name_param
35+
"""
36+
check_syntax_error(self, prog_text, lineno=2, offset=5)
37+
38+
def test_name_after_assign(self):
39+
prog_text = """\
40+
def fn():
41+
name_assign = 1
42+
nonlocal name_assign
43+
"""
44+
check_syntax_error(self, prog_text, lineno=3, offset=5)
45+
46+
def test_name_after_use(self):
47+
prog_text = """\
48+
def fn():
49+
print(name_use)
50+
nonlocal name_use
51+
"""
52+
check_syntax_error(self, prog_text, lineno=3, offset=5)
53+
54+
def test_name_annot(self):
55+
prog_text = """\
56+
def fn():
57+
name_annot: int
58+
nonlocal name_annot
59+
"""
60+
check_syntax_error(self, prog_text, lineno=3, offset=5)
61+
62+
###############################################################
63+
### Tests for nonlocal variables across all name binding cases,
64+
### as described in executionmodel.rst
65+
###############################################################
66+
67+
def test_assignment_statement(self):
68+
name_assignment_statement = None
69+
value = object()
70+
71+
def inner():
72+
nonlocal name_assignment_statement
73+
name_assignment_statement = value
74+
75+
inner()
76+
self.assertIs(name_assignment_statement, value)
77+
78+
def test_unpacking_assignment(self):
79+
name_unpacking_assignment = None
80+
value = object()
81+
82+
def inner():
83+
nonlocal name_unpacking_assignment
84+
_, name_unpacking_assignment = [None, value]
85+
86+
inner()
87+
self.assertIs(name_unpacking_assignment, value)
88+
89+
def test_assignment_expression(self):
90+
name_assignment_expression = None
91+
value = object()
92+
93+
def inner():
94+
nonlocal name_assignment_expression
95+
if name_assignment_expression := value:
96+
pass
97+
98+
inner()
99+
self.assertIs(name_assignment_expression, value)
100+
101+
def test_iteration_variable(self):
102+
name_iteration_variable = None
103+
value = object()
104+
105+
def inner():
106+
nonlocal name_iteration_variable
107+
for name_iteration_variable in [value]:
108+
pass
109+
110+
inner()
111+
self.assertIs(name_iteration_variable, value)
112+
113+
def test_func_def(self):
114+
name_func_def = None
115+
116+
def inner():
117+
nonlocal name_func_def
118+
119+
def name_func_def():
120+
pass
121+
122+
inner()
123+
self.assertIs(name_func_def, name_func_def)
124+
125+
def test_class_def(self):
126+
name_class_def = None
127+
128+
def inner():
129+
nonlocal name_class_def
130+
131+
class name_class_def:
132+
pass
133+
134+
inner()
135+
self.assertIs(name_class_def, name_class_def)
136+
137+
def test_type_alias(self):
138+
name_type_alias = None
139+
140+
def inner():
141+
nonlocal name_type_alias
142+
type name_type_alias = tuple[int, int]
143+
144+
inner()
145+
self.assertIs(name_type_alias, name_type_alias)
146+
147+
def test_caught_exception(self):
148+
name_caught_exc = None
149+
150+
def inner():
151+
nonlocal self, inner, name_caught_exc
152+
idx = inner.__code__.co_freevars.index("name_caught_exc")
153+
try:
154+
1 / 0
155+
except ZeroDivisionError as name_caught_exc:
156+
# `name_caught_exc` is cleared automatically after the except block
157+
self.assertIs(inner.__closure__[idx].cell_contents, name_caught_exc)
158+
159+
inner()
160+
161+
def test_caught_exception_group(self):
162+
name_caught_exc_group = None
163+
164+
def inner():
165+
nonlocal self, inner, name_caught_exc_group
166+
idx = inner.__code__.co_freevars.index("name_caught_exc_group")
167+
168+
try:
169+
try:
170+
1 / 0
171+
except ZeroDivisionError as exc:
172+
raise ExceptionGroup("eg", [exc])
173+
except* ZeroDivisionError as name_caught_exc_group:
174+
# `name_caught_exc_group` is cleared automatically after the except block
175+
self.assertIs(
176+
inner.__closure__[idx].cell_contents, name_caught_exc_group
177+
)
178+
179+
inner()
180+
181+
def test_enter_result(self):
182+
name_enter_result = None
183+
value = object()
184+
185+
def inner():
186+
nonlocal name_enter_result
187+
with contextlib.nullcontext(value) as name_enter_result:
188+
pass
189+
190+
inner()
191+
self.assertIs(name_enter_result, value)
192+
193+
def test_import_result(self):
194+
name_import_result = None
195+
value = contextlib
196+
197+
def inner():
198+
nonlocal name_import_result
199+
import contextlib as name_import_result
200+
201+
inner()
202+
self.assertIs(name_import_result, value)
203+
204+
def test_match(self):
205+
name_match = None
206+
value = object()
207+
208+
def inner():
209+
nonlocal name_match
210+
match value:
211+
case name_match:
212+
pass
213+
214+
inner()
215+
self.assertIs(name_match, value)
216+
217+
def test_match_as(self):
218+
name_match_as = None
219+
value = object()
220+
221+
def inner():
222+
nonlocal name_match_as
223+
match value:
224+
case _ as name_match_as:
225+
pass
226+
227+
inner()
228+
self.assertIs(name_match_as, value)
229+
230+
def test_match_seq(self):
231+
name_match_seq = None
232+
value = object()
233+
234+
def inner():
235+
nonlocal name_match_seq
236+
match (None, value):
237+
case (_, name_match_seq):
238+
pass
239+
240+
inner()
241+
self.assertIs(name_match_seq, value)
242+
243+
def test_match_map(self):
244+
name_match_map = None
245+
value = object()
246+
247+
def inner():
248+
nonlocal name_match_map
249+
match {"key": value}:
250+
case {"key": name_match_map}:
251+
pass
252+
253+
inner()
254+
self.assertIs(name_match_map, value)
255+
256+
def test_match_attr(self):
257+
name_match_attr = None
258+
value = object()
259+
260+
def inner():
261+
nonlocal name_match_attr
262+
match SimpleNamespace(key=value):
263+
case SimpleNamespace(key=name_match_attr):
264+
pass
265+
266+
inner()
267+
self.assertIs(name_match_attr, value)
268+
269+
270+
def setUpModule():
271+
unittest.enterModuleContext(warnings.catch_warnings())
272+
warnings.filterwarnings("error", module="<test string>")
273+
274+
275+
if __name__ == "__main__":
276+
unittest.main()

0 commit comments

Comments
 (0)