##// END OF EJS Templates
TST: Check that we can get completions for second-level Numpy modules
Carlos Cordoba -
Show More
@@ -1,570 +1,582 b''
1 from contextlib import contextmanager
1 from contextlib import contextmanager
2 from typing import NamedTuple
2 from typing import NamedTuple
3 from functools import partial
3 from functools import partial
4 from IPython.core.guarded_eval import (
4 from IPython.core.guarded_eval import (
5 EvaluationContext,
5 EvaluationContext,
6 GuardRejection,
6 GuardRejection,
7 guarded_eval,
7 guarded_eval,
8 _unbind_method,
8 _unbind_method,
9 )
9 )
10 from IPython.testing import decorators as dec
10 from IPython.testing import decorators as dec
11 import pytest
11 import pytest
12
12
13
13
14 def create_context(evaluation: str, **kwargs):
14 def create_context(evaluation: str, **kwargs):
15 return EvaluationContext(locals=kwargs, globals={}, evaluation=evaluation)
15 return EvaluationContext(locals=kwargs, globals={}, evaluation=evaluation)
16
16
17
17
18 forbidden = partial(create_context, "forbidden")
18 forbidden = partial(create_context, "forbidden")
19 minimal = partial(create_context, "minimal")
19 minimal = partial(create_context, "minimal")
20 limited = partial(create_context, "limited")
20 limited = partial(create_context, "limited")
21 unsafe = partial(create_context, "unsafe")
21 unsafe = partial(create_context, "unsafe")
22 dangerous = partial(create_context, "dangerous")
22 dangerous = partial(create_context, "dangerous")
23
23
24 LIMITED_OR_HIGHER = [limited, unsafe, dangerous]
24 LIMITED_OR_HIGHER = [limited, unsafe, dangerous]
25 MINIMAL_OR_HIGHER = [minimal, *LIMITED_OR_HIGHER]
25 MINIMAL_OR_HIGHER = [minimal, *LIMITED_OR_HIGHER]
26
26
27
27
28 @contextmanager
28 @contextmanager
29 def module_not_installed(module: str):
29 def module_not_installed(module: str):
30 import sys
30 import sys
31
31
32 try:
32 try:
33 to_restore = sys.modules[module]
33 to_restore = sys.modules[module]
34 del sys.modules[module]
34 del sys.modules[module]
35 except KeyError:
35 except KeyError:
36 to_restore = None
36 to_restore = None
37 try:
37 try:
38 yield
38 yield
39 finally:
39 finally:
40 sys.modules[module] = to_restore
40 sys.modules[module] = to_restore
41
41
42
42
43 def test_external_not_installed():
43 def test_external_not_installed():
44 """
44 """
45 Because attribute check requires checking if object is not of allowed
45 Because attribute check requires checking if object is not of allowed
46 external type, this tests logic for absence of external module.
46 external type, this tests logic for absence of external module.
47 """
47 """
48
48
49 class Custom:
49 class Custom:
50 def __init__(self):
50 def __init__(self):
51 self.test = 1
51 self.test = 1
52
52
53 def __getattr__(self, key):
53 def __getattr__(self, key):
54 return key
54 return key
55
55
56 with module_not_installed("pandas"):
56 with module_not_installed("pandas"):
57 context = limited(x=Custom())
57 context = limited(x=Custom())
58 with pytest.raises(GuardRejection):
58 with pytest.raises(GuardRejection):
59 guarded_eval("x.test", context)
59 guarded_eval("x.test", context)
60
60
61
61
62 @dec.skip_without("pandas")
62 @dec.skip_without("pandas")
63 def test_external_changed_api(monkeypatch):
63 def test_external_changed_api(monkeypatch):
64 """Check that the execution rejects if external API changed paths"""
64 """Check that the execution rejects if external API changed paths"""
65 import pandas as pd
65 import pandas as pd
66
66
67 series = pd.Series([1], index=["a"])
67 series = pd.Series([1], index=["a"])
68
68
69 with monkeypatch.context() as m:
69 with monkeypatch.context() as m:
70 m.delattr(pd, "Series")
70 m.delattr(pd, "Series")
71 context = limited(data=series)
71 context = limited(data=series)
72 with pytest.raises(GuardRejection):
72 with pytest.raises(GuardRejection):
73 guarded_eval("data.iloc[0]", context)
73 guarded_eval("data.iloc[0]", context)
74
74
75
75
76 @dec.skip_without("pandas")
76 @dec.skip_without("pandas")
77 def test_pandas_series_iloc():
77 def test_pandas_series_iloc():
78 import pandas as pd
78 import pandas as pd
79
79
80 series = pd.Series([1], index=["a"])
80 series = pd.Series([1], index=["a"])
81 context = limited(data=series)
81 context = limited(data=series)
82 assert guarded_eval("data.iloc[0]", context) == 1
82 assert guarded_eval("data.iloc[0]", context) == 1
83
83
84
84
85 def test_rejects_custom_properties():
85 def test_rejects_custom_properties():
86 class BadProperty:
86 class BadProperty:
87 @property
87 @property
88 def iloc(self):
88 def iloc(self):
89 return [None]
89 return [None]
90
90
91 series = BadProperty()
91 series = BadProperty()
92 context = limited(data=series)
92 context = limited(data=series)
93
93
94 with pytest.raises(GuardRejection):
94 with pytest.raises(GuardRejection):
95 guarded_eval("data.iloc[0]", context)
95 guarded_eval("data.iloc[0]", context)
96
96
97
97
98 @dec.skip_without("pandas")
98 @dec.skip_without("pandas")
99 def test_accepts_non_overriden_properties():
99 def test_accepts_non_overriden_properties():
100 import pandas as pd
100 import pandas as pd
101
101
102 class GoodProperty(pd.Series):
102 class GoodProperty(pd.Series):
103 pass
103 pass
104
104
105 series = GoodProperty([1], index=["a"])
105 series = GoodProperty([1], index=["a"])
106 context = limited(data=series)
106 context = limited(data=series)
107
107
108 assert guarded_eval("data.iloc[0]", context) == 1
108 assert guarded_eval("data.iloc[0]", context) == 1
109
109
110
110
111 @dec.skip_without("pandas")
111 @dec.skip_without("pandas")
112 def test_pandas_series():
112 def test_pandas_series():
113 import pandas as pd
113 import pandas as pd
114
114
115 context = limited(data=pd.Series([1], index=["a"]))
115 context = limited(data=pd.Series([1], index=["a"]))
116 assert guarded_eval('data["a"]', context) == 1
116 assert guarded_eval('data["a"]', context) == 1
117 with pytest.raises(KeyError):
117 with pytest.raises(KeyError):
118 guarded_eval('data["c"]', context)
118 guarded_eval('data["c"]', context)
119
119
120
120
121 @dec.skip_without("pandas")
121 @dec.skip_without("pandas")
122 def test_pandas_bad_series():
122 def test_pandas_bad_series():
123 import pandas as pd
123 import pandas as pd
124
124
125 class BadItemSeries(pd.Series):
125 class BadItemSeries(pd.Series):
126 def __getitem__(self, key):
126 def __getitem__(self, key):
127 return "CUSTOM_ITEM"
127 return "CUSTOM_ITEM"
128
128
129 class BadAttrSeries(pd.Series):
129 class BadAttrSeries(pd.Series):
130 def __getattr__(self, key):
130 def __getattr__(self, key):
131 return "CUSTOM_ATTR"
131 return "CUSTOM_ATTR"
132
132
133 bad_series = BadItemSeries([1], index=["a"])
133 bad_series = BadItemSeries([1], index=["a"])
134 context = limited(data=bad_series)
134 context = limited(data=bad_series)
135
135
136 with pytest.raises(GuardRejection):
136 with pytest.raises(GuardRejection):
137 guarded_eval('data["a"]', context)
137 guarded_eval('data["a"]', context)
138 with pytest.raises(GuardRejection):
138 with pytest.raises(GuardRejection):
139 guarded_eval('data["c"]', context)
139 guarded_eval('data["c"]', context)
140
140
141 # note: here result is a bit unexpected because
141 # note: here result is a bit unexpected because
142 # pandas `__getattr__` calls `__getitem__`;
142 # pandas `__getattr__` calls `__getitem__`;
143 # FIXME - special case to handle it?
143 # FIXME - special case to handle it?
144 assert guarded_eval("data.a", context) == "CUSTOM_ITEM"
144 assert guarded_eval("data.a", context) == "CUSTOM_ITEM"
145
145
146 context = unsafe(data=bad_series)
146 context = unsafe(data=bad_series)
147 assert guarded_eval('data["a"]', context) == "CUSTOM_ITEM"
147 assert guarded_eval('data["a"]', context) == "CUSTOM_ITEM"
148
148
149 bad_attr_series = BadAttrSeries([1], index=["a"])
149 bad_attr_series = BadAttrSeries([1], index=["a"])
150 context = limited(data=bad_attr_series)
150 context = limited(data=bad_attr_series)
151 assert guarded_eval('data["a"]', context) == 1
151 assert guarded_eval('data["a"]', context) == 1
152 with pytest.raises(GuardRejection):
152 with pytest.raises(GuardRejection):
153 guarded_eval("data.a", context)
153 guarded_eval("data.a", context)
154
154
155
155
156 @dec.skip_without("pandas")
156 @dec.skip_without("pandas")
157 def test_pandas_dataframe_loc():
157 def test_pandas_dataframe_loc():
158 import pandas as pd
158 import pandas as pd
159 from pandas.testing import assert_series_equal
159 from pandas.testing import assert_series_equal
160
160
161 data = pd.DataFrame([{"a": 1}])
161 data = pd.DataFrame([{"a": 1}])
162 context = limited(data=data)
162 context = limited(data=data)
163 assert_series_equal(guarded_eval('data.loc[:, "a"]', context), data["a"])
163 assert_series_equal(guarded_eval('data.loc[:, "a"]', context), data["a"])
164
164
165
165
166 def test_named_tuple():
166 def test_named_tuple():
167 class GoodNamedTuple(NamedTuple):
167 class GoodNamedTuple(NamedTuple):
168 a: str
168 a: str
169 pass
169 pass
170
170
171 class BadNamedTuple(NamedTuple):
171 class BadNamedTuple(NamedTuple):
172 a: str
172 a: str
173
173
174 def __getitem__(self, key):
174 def __getitem__(self, key):
175 return None
175 return None
176
176
177 good = GoodNamedTuple(a="x")
177 good = GoodNamedTuple(a="x")
178 bad = BadNamedTuple(a="x")
178 bad = BadNamedTuple(a="x")
179
179
180 context = limited(data=good)
180 context = limited(data=good)
181 assert guarded_eval("data[0]", context) == "x"
181 assert guarded_eval("data[0]", context) == "x"
182
182
183 context = limited(data=bad)
183 context = limited(data=bad)
184 with pytest.raises(GuardRejection):
184 with pytest.raises(GuardRejection):
185 guarded_eval("data[0]", context)
185 guarded_eval("data[0]", context)
186
186
187
187
188 def test_dict():
188 def test_dict():
189 context = limited(data={"a": 1, "b": {"x": 2}, ("x", "y"): 3})
189 context = limited(data={"a": 1, "b": {"x": 2}, ("x", "y"): 3})
190 assert guarded_eval('data["a"]', context) == 1
190 assert guarded_eval('data["a"]', context) == 1
191 assert guarded_eval('data["b"]', context) == {"x": 2}
191 assert guarded_eval('data["b"]', context) == {"x": 2}
192 assert guarded_eval('data["b"]["x"]', context) == 2
192 assert guarded_eval('data["b"]["x"]', context) == 2
193 assert guarded_eval('data["x", "y"]', context) == 3
193 assert guarded_eval('data["x", "y"]', context) == 3
194
194
195 assert guarded_eval("data.keys", context)
195 assert guarded_eval("data.keys", context)
196
196
197
197
198 def test_set():
198 def test_set():
199 context = limited(data={"a", "b"})
199 context = limited(data={"a", "b"})
200 assert guarded_eval("data.difference", context)
200 assert guarded_eval("data.difference", context)
201
201
202
202
203 def test_list():
203 def test_list():
204 context = limited(data=[1, 2, 3])
204 context = limited(data=[1, 2, 3])
205 assert guarded_eval("data[1]", context) == 2
205 assert guarded_eval("data[1]", context) == 2
206 assert guarded_eval("data.copy", context)
206 assert guarded_eval("data.copy", context)
207
207
208
208
209 def test_dict_literal():
209 def test_dict_literal():
210 context = limited()
210 context = limited()
211 assert guarded_eval("{}", context) == {}
211 assert guarded_eval("{}", context) == {}
212 assert guarded_eval('{"a": 1}', context) == {"a": 1}
212 assert guarded_eval('{"a": 1}', context) == {"a": 1}
213
213
214
214
215 def test_list_literal():
215 def test_list_literal():
216 context = limited()
216 context = limited()
217 assert guarded_eval("[]", context) == []
217 assert guarded_eval("[]", context) == []
218 assert guarded_eval('[1, "a"]', context) == [1, "a"]
218 assert guarded_eval('[1, "a"]', context) == [1, "a"]
219
219
220
220
221 def test_set_literal():
221 def test_set_literal():
222 context = limited()
222 context = limited()
223 assert guarded_eval("set()", context) == set()
223 assert guarded_eval("set()", context) == set()
224 assert guarded_eval('{"a"}', context) == {"a"}
224 assert guarded_eval('{"a"}', context) == {"a"}
225
225
226
226
227 def test_evaluates_if_expression():
227 def test_evaluates_if_expression():
228 context = limited()
228 context = limited()
229 assert guarded_eval("2 if True else 3", context) == 2
229 assert guarded_eval("2 if True else 3", context) == 2
230 assert guarded_eval("4 if False else 5", context) == 5
230 assert guarded_eval("4 if False else 5", context) == 5
231
231
232
232
233 def test_object():
233 def test_object():
234 obj = object()
234 obj = object()
235 context = limited(obj=obj)
235 context = limited(obj=obj)
236 assert guarded_eval("obj.__dir__", context) == obj.__dir__
236 assert guarded_eval("obj.__dir__", context) == obj.__dir__
237
237
238
238
239 @pytest.mark.parametrize(
239 @pytest.mark.parametrize(
240 "code,expected",
240 "code,expected",
241 [
241 [
242 ["int.numerator", int.numerator],
242 ["int.numerator", int.numerator],
243 ["float.is_integer", float.is_integer],
243 ["float.is_integer", float.is_integer],
244 ["complex.real", complex.real],
244 ["complex.real", complex.real],
245 ],
245 ],
246 )
246 )
247 def test_number_attributes(code, expected):
247 def test_number_attributes(code, expected):
248 assert guarded_eval(code, limited()) == expected
248 assert guarded_eval(code, limited()) == expected
249
249
250
250
251 def test_method_descriptor():
251 def test_method_descriptor():
252 context = limited()
252 context = limited()
253 assert guarded_eval("list.copy.__name__", context) == "copy"
253 assert guarded_eval("list.copy.__name__", context) == "copy"
254
254
255
255
256 @pytest.mark.parametrize(
256 @pytest.mark.parametrize(
257 "data,good,bad,expected",
257 "data,good,bad,expected",
258 [
258 [
259 [[1, 2, 3], "data.index(2)", "data.append(4)", 1],
259 [[1, 2, 3], "data.index(2)", "data.append(4)", 1],
260 [{"a": 1}, "data.keys().isdisjoint({})", "data.update()", True],
260 [{"a": 1}, "data.keys().isdisjoint({})", "data.update()", True],
261 ],
261 ],
262 )
262 )
263 def test_evaluates_calls(data, good, bad, expected):
263 def test_evaluates_calls(data, good, bad, expected):
264 context = limited(data=data)
264 context = limited(data=data)
265 assert guarded_eval(good, context) == expected
265 assert guarded_eval(good, context) == expected
266
266
267 with pytest.raises(GuardRejection):
267 with pytest.raises(GuardRejection):
268 guarded_eval(bad, context)
268 guarded_eval(bad, context)
269
269
270
270
271 @pytest.mark.parametrize(
271 @pytest.mark.parametrize(
272 "code,expected",
272 "code,expected",
273 [
273 [
274 ["(1\n+\n1)", 2],
274 ["(1\n+\n1)", 2],
275 ["list(range(10))[-1:]", [9]],
275 ["list(range(10))[-1:]", [9]],
276 ["list(range(20))[3:-2:3]", [3, 6, 9, 12, 15]],
276 ["list(range(20))[3:-2:3]", [3, 6, 9, 12, 15]],
277 ],
277 ],
278 )
278 )
279 @pytest.mark.parametrize("context", LIMITED_OR_HIGHER)
279 @pytest.mark.parametrize("context", LIMITED_OR_HIGHER)
280 def test_evaluates_complex_cases(code, expected, context):
280 def test_evaluates_complex_cases(code, expected, context):
281 assert guarded_eval(code, context()) == expected
281 assert guarded_eval(code, context()) == expected
282
282
283
283
284 @pytest.mark.parametrize(
284 @pytest.mark.parametrize(
285 "code,expected",
285 "code,expected",
286 [
286 [
287 ["1", 1],
287 ["1", 1],
288 ["1.0", 1.0],
288 ["1.0", 1.0],
289 ["0xdeedbeef", 0xDEEDBEEF],
289 ["0xdeedbeef", 0xDEEDBEEF],
290 ["True", True],
290 ["True", True],
291 ["None", None],
291 ["None", None],
292 ["{}", {}],
292 ["{}", {}],
293 ["[]", []],
293 ["[]", []],
294 ],
294 ],
295 )
295 )
296 @pytest.mark.parametrize("context", MINIMAL_OR_HIGHER)
296 @pytest.mark.parametrize("context", MINIMAL_OR_HIGHER)
297 def test_evaluates_literals(code, expected, context):
297 def test_evaluates_literals(code, expected, context):
298 assert guarded_eval(code, context()) == expected
298 assert guarded_eval(code, context()) == expected
299
299
300
300
301 @pytest.mark.parametrize(
301 @pytest.mark.parametrize(
302 "code,expected",
302 "code,expected",
303 [
303 [
304 ["-5", -5],
304 ["-5", -5],
305 ["+5", +5],
305 ["+5", +5],
306 ["~5", -6],
306 ["~5", -6],
307 ],
307 ],
308 )
308 )
309 @pytest.mark.parametrize("context", LIMITED_OR_HIGHER)
309 @pytest.mark.parametrize("context", LIMITED_OR_HIGHER)
310 def test_evaluates_unary_operations(code, expected, context):
310 def test_evaluates_unary_operations(code, expected, context):
311 assert guarded_eval(code, context()) == expected
311 assert guarded_eval(code, context()) == expected
312
312
313
313
314 @pytest.mark.parametrize(
314 @pytest.mark.parametrize(
315 "code,expected",
315 "code,expected",
316 [
316 [
317 ["1 + 1", 2],
317 ["1 + 1", 2],
318 ["3 - 1", 2],
318 ["3 - 1", 2],
319 ["2 * 3", 6],
319 ["2 * 3", 6],
320 ["5 // 2", 2],
320 ["5 // 2", 2],
321 ["5 / 2", 2.5],
321 ["5 / 2", 2.5],
322 ["5**2", 25],
322 ["5**2", 25],
323 ["2 >> 1", 1],
323 ["2 >> 1", 1],
324 ["2 << 1", 4],
324 ["2 << 1", 4],
325 ["1 | 2", 3],
325 ["1 | 2", 3],
326 ["1 & 1", 1],
326 ["1 & 1", 1],
327 ["1 & 2", 0],
327 ["1 & 2", 0],
328 ],
328 ],
329 )
329 )
330 @pytest.mark.parametrize("context", LIMITED_OR_HIGHER)
330 @pytest.mark.parametrize("context", LIMITED_OR_HIGHER)
331 def test_evaluates_binary_operations(code, expected, context):
331 def test_evaluates_binary_operations(code, expected, context):
332 assert guarded_eval(code, context()) == expected
332 assert guarded_eval(code, context()) == expected
333
333
334
334
335 @pytest.mark.parametrize(
335 @pytest.mark.parametrize(
336 "code,expected",
336 "code,expected",
337 [
337 [
338 ["2 > 1", True],
338 ["2 > 1", True],
339 ["2 < 1", False],
339 ["2 < 1", False],
340 ["2 <= 1", False],
340 ["2 <= 1", False],
341 ["2 <= 2", True],
341 ["2 <= 2", True],
342 ["1 >= 2", False],
342 ["1 >= 2", False],
343 ["2 >= 2", True],
343 ["2 >= 2", True],
344 ["2 == 2", True],
344 ["2 == 2", True],
345 ["1 == 2", False],
345 ["1 == 2", False],
346 ["1 != 2", True],
346 ["1 != 2", True],
347 ["1 != 1", False],
347 ["1 != 1", False],
348 ["1 < 4 < 3", False],
348 ["1 < 4 < 3", False],
349 ["(1 < 4) < 3", True],
349 ["(1 < 4) < 3", True],
350 ["4 > 3 > 2 > 1", True],
350 ["4 > 3 > 2 > 1", True],
351 ["4 > 3 > 2 > 9", False],
351 ["4 > 3 > 2 > 9", False],
352 ["1 < 2 < 3 < 4", True],
352 ["1 < 2 < 3 < 4", True],
353 ["9 < 2 < 3 < 4", False],
353 ["9 < 2 < 3 < 4", False],
354 ["1 < 2 > 1 > 0 > -1 < 1", True],
354 ["1 < 2 > 1 > 0 > -1 < 1", True],
355 ["1 in [1] in [[1]]", True],
355 ["1 in [1] in [[1]]", True],
356 ["1 in [1] in [[2]]", False],
356 ["1 in [1] in [[2]]", False],
357 ["1 in [1]", True],
357 ["1 in [1]", True],
358 ["0 in [1]", False],
358 ["0 in [1]", False],
359 ["1 not in [1]", False],
359 ["1 not in [1]", False],
360 ["0 not in [1]", True],
360 ["0 not in [1]", True],
361 ["True is True", True],
361 ["True is True", True],
362 ["False is False", True],
362 ["False is False", True],
363 ["True is False", False],
363 ["True is False", False],
364 ["True is not True", False],
364 ["True is not True", False],
365 ["False is not True", True],
365 ["False is not True", True],
366 ],
366 ],
367 )
367 )
368 @pytest.mark.parametrize("context", LIMITED_OR_HIGHER)
368 @pytest.mark.parametrize("context", LIMITED_OR_HIGHER)
369 def test_evaluates_comparisons(code, expected, context):
369 def test_evaluates_comparisons(code, expected, context):
370 assert guarded_eval(code, context()) == expected
370 assert guarded_eval(code, context()) == expected
371
371
372
372
373 def test_guards_comparisons():
373 def test_guards_comparisons():
374 class GoodEq(int):
374 class GoodEq(int):
375 pass
375 pass
376
376
377 class BadEq(int):
377 class BadEq(int):
378 def __eq__(self, other):
378 def __eq__(self, other):
379 assert False
379 assert False
380
380
381 context = limited(bad=BadEq(1), good=GoodEq(1))
381 context = limited(bad=BadEq(1), good=GoodEq(1))
382
382
383 with pytest.raises(GuardRejection):
383 with pytest.raises(GuardRejection):
384 guarded_eval("bad == 1", context)
384 guarded_eval("bad == 1", context)
385
385
386 with pytest.raises(GuardRejection):
386 with pytest.raises(GuardRejection):
387 guarded_eval("bad != 1", context)
387 guarded_eval("bad != 1", context)
388
388
389 with pytest.raises(GuardRejection):
389 with pytest.raises(GuardRejection):
390 guarded_eval("1 == bad", context)
390 guarded_eval("1 == bad", context)
391
391
392 with pytest.raises(GuardRejection):
392 with pytest.raises(GuardRejection):
393 guarded_eval("1 != bad", context)
393 guarded_eval("1 != bad", context)
394
394
395 assert guarded_eval("good == 1", context) is True
395 assert guarded_eval("good == 1", context) is True
396 assert guarded_eval("good != 1", context) is False
396 assert guarded_eval("good != 1", context) is False
397 assert guarded_eval("1 == good", context) is True
397 assert guarded_eval("1 == good", context) is True
398 assert guarded_eval("1 != good", context) is False
398 assert guarded_eval("1 != good", context) is False
399
399
400
400
401 def test_guards_unary_operations():
401 def test_guards_unary_operations():
402 class GoodOp(int):
402 class GoodOp(int):
403 pass
403 pass
404
404
405 class BadOpInv(int):
405 class BadOpInv(int):
406 def __inv__(self, other):
406 def __inv__(self, other):
407 assert False
407 assert False
408
408
409 class BadOpInverse(int):
409 class BadOpInverse(int):
410 def __inv__(self, other):
410 def __inv__(self, other):
411 assert False
411 assert False
412
412
413 context = limited(good=GoodOp(1), bad1=BadOpInv(1), bad2=BadOpInverse(1))
413 context = limited(good=GoodOp(1), bad1=BadOpInv(1), bad2=BadOpInverse(1))
414
414
415 with pytest.raises(GuardRejection):
415 with pytest.raises(GuardRejection):
416 guarded_eval("~bad1", context)
416 guarded_eval("~bad1", context)
417
417
418 with pytest.raises(GuardRejection):
418 with pytest.raises(GuardRejection):
419 guarded_eval("~bad2", context)
419 guarded_eval("~bad2", context)
420
420
421
421
422 def test_guards_binary_operations():
422 def test_guards_binary_operations():
423 class GoodOp(int):
423 class GoodOp(int):
424 pass
424 pass
425
425
426 class BadOp(int):
426 class BadOp(int):
427 def __add__(self, other):
427 def __add__(self, other):
428 assert False
428 assert False
429
429
430 context = limited(good=GoodOp(1), bad=BadOp(1))
430 context = limited(good=GoodOp(1), bad=BadOp(1))
431
431
432 with pytest.raises(GuardRejection):
432 with pytest.raises(GuardRejection):
433 guarded_eval("1 + bad", context)
433 guarded_eval("1 + bad", context)
434
434
435 with pytest.raises(GuardRejection):
435 with pytest.raises(GuardRejection):
436 guarded_eval("bad + 1", context)
436 guarded_eval("bad + 1", context)
437
437
438 assert guarded_eval("good + 1", context) == 2
438 assert guarded_eval("good + 1", context) == 2
439 assert guarded_eval("1 + good", context) == 2
439 assert guarded_eval("1 + good", context) == 2
440
440
441
441
442 def test_guards_attributes():
442 def test_guards_attributes():
443 class GoodAttr(float):
443 class GoodAttr(float):
444 pass
444 pass
445
445
446 class BadAttr1(float):
446 class BadAttr1(float):
447 def __getattr__(self, key):
447 def __getattr__(self, key):
448 assert False
448 assert False
449
449
450 class BadAttr2(float):
450 class BadAttr2(float):
451 def __getattribute__(self, key):
451 def __getattribute__(self, key):
452 assert False
452 assert False
453
453
454 context = limited(good=GoodAttr(0.5), bad1=BadAttr1(0.5), bad2=BadAttr2(0.5))
454 context = limited(good=GoodAttr(0.5), bad1=BadAttr1(0.5), bad2=BadAttr2(0.5))
455
455
456 with pytest.raises(GuardRejection):
456 with pytest.raises(GuardRejection):
457 guarded_eval("bad1.as_integer_ratio", context)
457 guarded_eval("bad1.as_integer_ratio", context)
458
458
459 with pytest.raises(GuardRejection):
459 with pytest.raises(GuardRejection):
460 guarded_eval("bad2.as_integer_ratio", context)
460 guarded_eval("bad2.as_integer_ratio", context)
461
461
462 assert guarded_eval("good.as_integer_ratio()", context) == (1, 2)
462 assert guarded_eval("good.as_integer_ratio()", context) == (1, 2)
463
463
464
464
465 @pytest.mark.parametrize("context", MINIMAL_OR_HIGHER)
465 @pytest.mark.parametrize("context", MINIMAL_OR_HIGHER)
466 def test_access_builtins(context):
466 def test_access_builtins(context):
467 assert guarded_eval("round", context()) == round
467 assert guarded_eval("round", context()) == round
468
468
469
469
470 def test_access_builtins_fails():
470 def test_access_builtins_fails():
471 context = limited()
471 context = limited()
472 with pytest.raises(NameError):
472 with pytest.raises(NameError):
473 guarded_eval("this_is_not_builtin", context)
473 guarded_eval("this_is_not_builtin", context)
474
474
475
475
476 def test_rejects_forbidden():
476 def test_rejects_forbidden():
477 context = forbidden()
477 context = forbidden()
478 with pytest.raises(GuardRejection):
478 with pytest.raises(GuardRejection):
479 guarded_eval("1", context)
479 guarded_eval("1", context)
480
480
481
481
482 def test_guards_locals_and_globals():
482 def test_guards_locals_and_globals():
483 context = EvaluationContext(
483 context = EvaluationContext(
484 locals={"local_a": "a"}, globals={"global_b": "b"}, evaluation="minimal"
484 locals={"local_a": "a"}, globals={"global_b": "b"}, evaluation="minimal"
485 )
485 )
486
486
487 with pytest.raises(GuardRejection):
487 with pytest.raises(GuardRejection):
488 guarded_eval("local_a", context)
488 guarded_eval("local_a", context)
489
489
490 with pytest.raises(GuardRejection):
490 with pytest.raises(GuardRejection):
491 guarded_eval("global_b", context)
491 guarded_eval("global_b", context)
492
492
493
493
494 def test_access_locals_and_globals():
494 def test_access_locals_and_globals():
495 context = EvaluationContext(
495 context = EvaluationContext(
496 locals={"local_a": "a"}, globals={"global_b": "b"}, evaluation="limited"
496 locals={"local_a": "a"}, globals={"global_b": "b"}, evaluation="limited"
497 )
497 )
498 assert guarded_eval("local_a", context) == "a"
498 assert guarded_eval("local_a", context) == "a"
499 assert guarded_eval("global_b", context) == "b"
499 assert guarded_eval("global_b", context) == "b"
500
500
501
501
502 @pytest.mark.parametrize(
502 @pytest.mark.parametrize(
503 "code",
503 "code",
504 ["def func(): pass", "class C: pass", "x = 1", "x += 1", "del x", "import ast"],
504 ["def func(): pass", "class C: pass", "x = 1", "x += 1", "del x", "import ast"],
505 )
505 )
506 @pytest.mark.parametrize("context", [minimal(), limited(), unsafe()])
506 @pytest.mark.parametrize("context", [minimal(), limited(), unsafe()])
507 def test_rejects_side_effect_syntax(code, context):
507 def test_rejects_side_effect_syntax(code, context):
508 with pytest.raises(SyntaxError):
508 with pytest.raises(SyntaxError):
509 guarded_eval(code, context)
509 guarded_eval(code, context)
510
510
511
511
512 def test_subscript():
512 def test_subscript():
513 context = EvaluationContext(
513 context = EvaluationContext(
514 locals={}, globals={}, evaluation="limited", in_subscript=True
514 locals={}, globals={}, evaluation="limited", in_subscript=True
515 )
515 )
516 empty_slice = slice(None, None, None)
516 empty_slice = slice(None, None, None)
517 assert guarded_eval("", context) == tuple()
517 assert guarded_eval("", context) == tuple()
518 assert guarded_eval(":", context) == empty_slice
518 assert guarded_eval(":", context) == empty_slice
519 assert guarded_eval("1:2:3", context) == slice(1, 2, 3)
519 assert guarded_eval("1:2:3", context) == slice(1, 2, 3)
520 assert guarded_eval(':, "a"', context) == (empty_slice, "a")
520 assert guarded_eval(':, "a"', context) == (empty_slice, "a")
521
521
522
522
523 def test_unbind_method():
523 def test_unbind_method():
524 class X(list):
524 class X(list):
525 def index(self, k):
525 def index(self, k):
526 return "CUSTOM"
526 return "CUSTOM"
527
527
528 x = X()
528 x = X()
529 assert _unbind_method(x.index) is X.index
529 assert _unbind_method(x.index) is X.index
530 assert _unbind_method([].index) is list.index
530 assert _unbind_method([].index) is list.index
531 assert _unbind_method(list.index) is None
531 assert _unbind_method(list.index) is None
532
532
533
533
534 def test_assumption_instance_attr_do_not_matter():
534 def test_assumption_instance_attr_do_not_matter():
535 """This is semi-specified in Python documentation.
535 """This is semi-specified in Python documentation.
536
536
537 However, since the specification says 'not guaranted
537 However, since the specification says 'not guaranted
538 to work' rather than 'is forbidden to work', future
538 to work' rather than 'is forbidden to work', future
539 versions could invalidate this assumptions. This test
539 versions could invalidate this assumptions. This test
540 is meant to catch such a change if it ever comes true.
540 is meant to catch such a change if it ever comes true.
541 """
541 """
542
542
543 class T:
543 class T:
544 def __getitem__(self, k):
544 def __getitem__(self, k):
545 return "a"
545 return "a"
546
546
547 def __getattr__(self, k):
547 def __getattr__(self, k):
548 return "a"
548 return "a"
549
549
550 def f(self):
550 def f(self):
551 return "b"
551 return "b"
552
552
553 t = T()
553 t = T()
554 t.__getitem__ = f
554 t.__getitem__ = f
555 t.__getattr__ = f
555 t.__getattr__ = f
556 assert t[1] == "a"
556 assert t[1] == "a"
557 assert t[1] == "a"
557 assert t[1] == "a"
558
558
559
559
560 def test_assumption_named_tuples_share_getitem():
560 def test_assumption_named_tuples_share_getitem():
561 """Check assumption on named tuples sharing __getitem__"""
561 """Check assumption on named tuples sharing __getitem__"""
562 from typing import NamedTuple
562 from typing import NamedTuple
563
563
564 class A(NamedTuple):
564 class A(NamedTuple):
565 pass
565 pass
566
566
567 class B(NamedTuple):
567 class B(NamedTuple):
568 pass
568 pass
569
569
570 assert A.__getitem__ == B.__getitem__
570 assert A.__getitem__ == B.__getitem__
571
572
573 @dec.skip_without("numpy")
574 def test_module_access():
575 import numpy
576
577 context = limited(numpy=numpy)
578 assert guarded_eval("numpy.linalg.norm", context) == numpy.linalg.norm
579
580 context = minimal(numpy=numpy)
581 with pytest.raises(GuardRejection):
582 guarded_eval("np.linalg.norm", context)
General Comments 0
You need to be logged in to leave comments. Login now