##// END OF EJS Templates
whatsnew 8.31
whatsnew 8.31

File last commit:

r28965:24de1225
r29036:5fc548f6
Show More
test_guarded_eval.py
782 lines | 20.4 KiB | text/x-python | PythonLexer
/ IPython / core / tests / test_guarded_eval.py
krassowski
Add version guards
r28679 import sys
krassowski
Guard against custom properties
r27926 from contextlib import contextmanager
krassowski
Implement remaining special `typing` wrappers
r28680 from typing import (
Annotated,
AnyStr,
NamedTuple,
Literal,
NewType,
Optional,
Protocol,
TypeGuard,
Union,
TypedDict,
)
krassowski
Increase coverage of `guard_eval`
r27921 from functools import partial
krassowski
You Want It Darker
r27913 from IPython.core.guarded_eval import (
EvaluationContext,
GuardRejection,
guarded_eval,
krassowski
Polish documentation, hide private functions
r27918 _unbind_method,
krassowski
You Want It Darker
r27913 )
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 from IPython.testing import decorators as dec
import pytest
M Bussonnier
Drop Python 3.10
r28965 from typing import Self, LiteralString
krassowski
Add version guards
r28679
if sys.version_info < (3, 12):
from typing_extensions import TypeAliasType
else:
from typing import TypeAliasType
krassowski
Increase coverage of `guard_eval`
r27921 def create_context(evaluation: str, **kwargs):
return EvaluationContext(locals=kwargs, globals={}, evaluation=evaluation)
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
krassowski
Increase coverage of `guard_eval`
r27921 forbidden = partial(create_context, "forbidden")
minimal = partial(create_context, "minimal")
limited = partial(create_context, "limited")
unsafe = partial(create_context, "unsafe")
dangerous = partial(create_context, "dangerous")
LIMITED_OR_HIGHER = [limited, unsafe, dangerous]
MINIMAL_OR_HIGHER = [minimal, *LIMITED_OR_HIGHER]
krassowski
You Want It Darker
r27913
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
krassowski
Guard against custom properties
r27926 @contextmanager
def module_not_installed(module: str):
import sys
try:
to_restore = sys.modules[module]
del sys.modules[module]
except KeyError:
to_restore = None
try:
yield
finally:
sys.modules[module] = to_restore
krassowski
Add more tests
r27929 def test_external_not_installed():
"""
Because attribute check requires checking if object is not of allowed
external type, this tests logic for absence of external module.
"""
class Custom:
def __init__(self):
self.test = 1
def __getattr__(self, key):
return key
with module_not_installed("pandas"):
context = limited(x=Custom())
with pytest.raises(GuardRejection):
guarded_eval("x.test", context)
@dec.skip_without("pandas")
def test_external_changed_api(monkeypatch):
"""Check that the execution rejects if external API changed paths"""
import pandas as pd
series = pd.Series([1], index=["a"])
with monkeypatch.context() as m:
m.delattr(pd, "Series")
context = limited(data=series)
with pytest.raises(GuardRejection):
guarded_eval("data.iloc[0]", context)
krassowski
You Want It Darker
r27913 @dec.skip_without("pandas")
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 def test_pandas_series_iloc():
import pandas as pd
krassowski
You Want It Darker
r27913
series = pd.Series([1], index=["a"])
krassowski
Fix typos
r27914 context = limited(data=series)
krassowski
You Want It Darker
r27913 assert guarded_eval("data.iloc[0]", context) == 1
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
krassowski
Guard against custom properties
r27926 def test_rejects_custom_properties():
class BadProperty:
@property
def iloc(self):
return [None]
series = BadProperty()
context = limited(data=series)
with pytest.raises(GuardRejection):
guarded_eval("data.iloc[0]", context)
@dec.skip_without("pandas")
def test_accepts_non_overriden_properties():
import pandas as pd
class GoodProperty(pd.Series):
pass
series = GoodProperty([1], index=["a"])
context = limited(data=series)
assert guarded_eval("data.iloc[0]", context) == 1
krassowski
You Want It Darker
r27913 @dec.skip_without("pandas")
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 def test_pandas_series():
import pandas as pd
krassowski
You Want It Darker
r27913
krassowski
Fix typos
r27914 context = limited(data=pd.Series([1], index=["a"]))
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 assert guarded_eval('data["a"]', context) == 1
with pytest.raises(KeyError):
guarded_eval('data["c"]', context)
krassowski
You Want It Darker
r27913 @dec.skip_without("pandas")
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 def test_pandas_bad_series():
import pandas as pd
krassowski
You Want It Darker
r27913
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 class BadItemSeries(pd.Series):
def __getitem__(self, key):
krassowski
You Want It Darker
r27913 return "CUSTOM_ITEM"
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
class BadAttrSeries(pd.Series):
def __getattr__(self, key):
krassowski
You Want It Darker
r27913 return "CUSTOM_ATTR"
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
krassowski
You Want It Darker
r27913 bad_series = BadItemSeries([1], index=["a"])
krassowski
Fix typos
r27914 context = limited(data=bad_series)
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
with pytest.raises(GuardRejection):
guarded_eval('data["a"]', context)
with pytest.raises(GuardRejection):
guarded_eval('data["c"]', context)
# note: here result is a bit unexpected because
# pandas `__getattr__` calls `__getitem__`;
# FIXME - special case to handle it?
krassowski
You Want It Darker
r27913 assert guarded_eval("data.a", context) == "CUSTOM_ITEM"
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
context = unsafe(data=bad_series)
krassowski
You Want It Darker
r27913 assert guarded_eval('data["a"]', context) == "CUSTOM_ITEM"
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
krassowski
You Want It Darker
r27913 bad_attr_series = BadAttrSeries([1], index=["a"])
krassowski
Fix typos
r27914 context = limited(data=bad_attr_series)
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 assert guarded_eval('data["a"]', context) == 1
with pytest.raises(GuardRejection):
krassowski
You Want It Darker
r27913 guarded_eval("data.a", context)
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
krassowski
You Want It Darker
r27913 @dec.skip_without("pandas")
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 def test_pandas_dataframe_loc():
import pandas as pd
from pandas.testing import assert_series_equal
krassowski
You Want It Darker
r27913
data = pd.DataFrame([{"a": 1}])
krassowski
Fix typos
r27914 context = limited(data=data)
krassowski
You Want It Darker
r27913 assert_series_equal(guarded_eval('data.loc[:, "a"]', context), data["a"])
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
def test_named_tuple():
class GoodNamedTuple(NamedTuple):
a: str
pass
class BadNamedTuple(NamedTuple):
a: str
krassowski
You Want It Darker
r27913
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 def __getitem__(self, key):
return None
krassowski
You Want It Darker
r27913 good = GoodNamedTuple(a="x")
bad = BadNamedTuple(a="x")
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
krassowski
Fix typos
r27914 context = limited(data=good)
krassowski
You Want It Darker
r27913 assert guarded_eval("data[0]", context) == "x"
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
krassowski
Fix typos
r27914 context = limited(data=bad)
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 with pytest.raises(GuardRejection):
krassowski
You Want It Darker
r27913 guarded_eval("data[0]", context)
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
def test_dict():
krassowski
Fix typos
r27914 context = limited(data={"a": 1, "b": {"x": 2}, ("x", "y"): 3})
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 assert guarded_eval('data["a"]', context) == 1
krassowski
You Want It Darker
r27913 assert guarded_eval('data["b"]', context) == {"x": 2}
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 assert guarded_eval('data["b"]["x"]', context) == 2
assert guarded_eval('data["x", "y"]', context) == 3
krassowski
You Want It Darker
r27913 assert guarded_eval("data.keys", context)
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
def test_set():
krassowski
Fix typos
r27914 context = limited(data={"a", "b"})
krassowski
You Want It Darker
r27913 assert guarded_eval("data.difference", context)
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
def test_list():
krassowski
Fix typos
r27914 context = limited(data=[1, 2, 3])
krassowski
You Want It Darker
r27913 assert guarded_eval("data[1]", context) == 2
assert guarded_eval("data.copy", context)
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
def test_dict_literal():
krassowski
Fix typos
r27914 context = limited()
krassowski
You Want It Darker
r27913 assert guarded_eval("{}", context) == {}
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 assert guarded_eval('{"a": 1}', context) == {"a": 1}
def test_list_literal():
krassowski
Fix typos
r27914 context = limited()
krassowski
You Want It Darker
r27913 assert guarded_eval("[]", context) == []
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 assert guarded_eval('[1, "a"]', context) == [1, "a"]
def test_set_literal():
krassowski
Fix typos
r27914 context = limited()
krassowski
You Want It Darker
r27913 assert guarded_eval("set()", context) == set()
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 assert guarded_eval('{"a"}', context) == {"a"}
krassowski
Increase coverage of `guard_eval`
r27921 def test_evaluates_if_expression():
krassowski
Fix typos
r27914 context = limited()
krassowski
You Want It Darker
r27913 assert guarded_eval("2 if True else 3", context) == 2
assert guarded_eval("4 if False else 5", context) == 5
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
def test_object():
obj = object()
krassowski
Fix typos
r27914 context = limited(obj=obj)
krassowski
You Want It Darker
r27913 assert guarded_eval("obj.__dir__", context) == obj.__dir__
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
@pytest.mark.parametrize(
"code,expected",
[
krassowski
You Want It Darker
r27913 ["int.numerator", int.numerator],
["float.is_integer", float.is_integer],
["complex.real", complex.real],
],
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 )
def test_number_attributes(code, expected):
krassowski
Fix typos
r27914 assert guarded_eval(code, limited()) == expected
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
def test_method_descriptor():
krassowski
Fix typos
r27914 context = limited()
krassowski
You Want It Darker
r27913 assert guarded_eval("list.copy.__name__", context) == "copy"
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
krassowski
Handle the case of built-ins, add tests
r28433 class HeapType:
pass
class CallCreatesHeapType:
def __call__(self) -> HeapType:
return HeapType()
class CallCreatesBuiltin:
def __call__(self) -> frozenset:
return frozenset()
krassowski
Infer type for `__init__` calls (including `__new__` mods)
r28675 class HasStaticMethod:
@staticmethod
def static_method() -> HeapType:
return HeapType()
class InitReturnsFrozenset:
def __new__(self) -> frozenset: # type:ignore[misc]
return frozenset()
krassowski
Support stringized return type annotations
r28676 class StringAnnotation:
def heap(self) -> "HeapType":
return HeapType()
def copy(self) -> "StringAnnotation":
return StringAnnotation()
krassowski
Support for `Self`, custom types, type aliases...
r28678 CustomIntType = NewType("CustomIntType", int)
CustomHeapType = NewType("CustomHeapType", HeapType)
IntTypeAlias = TypeAliasType("IntTypeAlias", int)
HeapTypeAlias = TypeAliasType("HeapTypeAlias", HeapType)
krassowski
Implement remaining special `typing` wrappers
r28680 class TestProtocol(Protocol):
def test_method(self) -> bool:
pass
class TestProtocolImplementer(TestProtocol):
def test_method(self) -> bool:
return True
class Movie(TypedDict):
name: str
year: int
krassowski
Support for `Self`, custom types, type aliases...
r28678 class SpecialTyping:
def custom_int_type(self) -> CustomIntType:
return CustomIntType(1)
def custom_heap_type(self) -> CustomHeapType:
return CustomHeapType(HeapType())
# TODO: remove type:ignore comment once mypy
# supports explicit calls to `TypeAliasType`, see:
# https://github.com/python/mypy/issues/16614
def int_type_alias(self) -> IntTypeAlias: # type:ignore[valid-type]
return 1
def heap_type_alias(self) -> HeapTypeAlias: # type:ignore[valid-type]
return 1
def literal(self) -> Literal[False]:
return False
krassowski
Implement remaining special `typing` wrappers
r28680 def literal_string(self) -> LiteralString:
return "test"
krassowski
Support for `Self`, custom types, type aliases...
r28678 def self(self) -> Self:
return self
krassowski
Implement remaining special `typing` wrappers
r28680 def any_str(self, x: AnyStr) -> AnyStr:
return x
def annotated(self) -> Annotated[float, "positive number"]:
return 1
def annotated_self(self) -> Annotated[Self, "self with metadata"]:
self._metadata = "test"
return self
def int_type_guard(self, x) -> TypeGuard[int]:
return isinstance(x, int)
def optional_float(self) -> Optional[float]:
return 1.0
def union_str_and_int(self) -> Union[str, int]:
return ""
def protocol(self) -> TestProtocol:
return TestProtocolImplementer()
def typed_dict(self) -> Movie:
return {"name": "The Matrix", "year": 1999}
krassowski
Support for `Self`, custom types, type aliases...
r28678
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 @pytest.mark.parametrize(
krassowski
Implement remaining special `typing` wrappers
r28680 "data,code,expected,equality",
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 [
krassowski
Infer type for `__init__` calls (including `__new__` mods)
r28675 [[1, 2, 3], "data.index(2)", 1, True],
[{"a": 1}, "data.keys().isdisjoint({})", True, True],
krassowski
Support stringized return type annotations
r28676 [StringAnnotation(), "data.heap()", HeapType, False],
[StringAnnotation(), "data.copy()", StringAnnotation, False],
krassowski
Infer type for `__init__` calls (including `__new__` mods)
r28675 # test cases for `__call__`
[CallCreatesHeapType(), "data()", HeapType, False],
[CallCreatesBuiltin(), "data()", frozenset, False],
# Test cases for `__init__`
[HeapType, "data()", HeapType, False],
[InitReturnsFrozenset, "data()", frozenset, False],
[HeapType(), "data.__class__()", HeapType, False],
krassowski
Support for `Self`, custom types, type aliases...
r28678 # supported special cases for typing
[SpecialTyping(), "data.custom_int_type()", int, False],
[SpecialTyping(), "data.custom_heap_type()", HeapType, False],
[SpecialTyping(), "data.int_type_alias()", int, False],
[SpecialTyping(), "data.heap_type_alias()", HeapType, False],
[SpecialTyping(), "data.self()", SpecialTyping, False],
[SpecialTyping(), "data.literal()", False, True],
krassowski
Implement remaining special `typing` wrappers
r28680 [SpecialTyping(), "data.literal_string()", str, False],
[SpecialTyping(), "data.any_str('a')", str, False],
[SpecialTyping(), "data.any_str(b'a')", bytes, False],
[SpecialTyping(), "data.annotated()", float, False],
[SpecialTyping(), "data.annotated_self()", SpecialTyping, False],
[SpecialTyping(), "data.int_type_guard()", int, False],
krassowski
Support stringized return type annotations
r28676 # test cases for static methods
krassowski
Infer type for `__init__` calls (including `__new__` mods)
r28675 [HasStaticMethod, "data.static_method()", HeapType, False],
krassowski
You Want It Darker
r27913 ],
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 )
krassowski
Implement remaining special `typing` wrappers
r28680 def test_evaluates_calls(data, code, expected, equality):
krassowski
Support stringized return type annotations
r28676 context = limited(data=data, HeapType=HeapType, StringAnnotation=StringAnnotation)
krassowski
Implement remaining special `typing` wrappers
r28680 value = guarded_eval(code, context)
krassowski
Handle the case of built-ins, add tests
r28433 if equality:
assert value == expected
else:
assert isinstance(value, expected)
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
krassowski
Infer type for `__init__` calls (including `__new__` mods)
r28675
@pytest.mark.parametrize(
krassowski
Implement remaining special `typing` wrappers
r28680 "data,code,expected_attributes",
[
[SpecialTyping(), "data.optional_float()", ["is_integer"]],
[
SpecialTyping(),
"data.union_str_and_int()",
["capitalize", "as_integer_ratio"],
],
[SpecialTyping(), "data.protocol()", ["test_method"]],
[SpecialTyping(), "data.typed_dict()", ["keys", "values", "items"]],
],
)
def test_mocks_attributes_of_call_results(data, code, expected_attributes):
context = limited(data=data, HeapType=HeapType, StringAnnotation=StringAnnotation)
result = guarded_eval(code, context)
for attr in expected_attributes:
assert hasattr(result, attr)
assert attr in dir(result)
@pytest.mark.parametrize(
"data,code,expected_items",
[
[SpecialTyping(), "data.typed_dict()", {"year": int, "name": str}],
],
)
def test_mocks_items_of_call_results(data, code, expected_items):
context = limited(data=data, HeapType=HeapType, StringAnnotation=StringAnnotation)
result = guarded_eval(code, context)
ipython_keys = result._ipython_key_completions_()
for key, value in expected_items.items():
assert isinstance(result[key], value)
assert key in ipython_keys
@pytest.mark.parametrize(
krassowski
Infer type for `__init__` calls (including `__new__` mods)
r28675 "data,bad",
[
[[1, 2, 3], "data.append(4)"],
[{"a": 1}, "data.update()"],
],
)
def test_rejects_calls_with_side_effects(data, bad):
context = limited(data=data)
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 with pytest.raises(GuardRejection):
guarded_eval(bad, context)
@pytest.mark.parametrize(
"code,expected",
[
krassowski
You Want It Darker
r27913 ["(1\n+\n1)", 2],
["list(range(10))[-1:]", [9]],
["list(range(20))[3:-2:3]", [3, 6, 9, 12, 15]],
],
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 )
krassowski
Increase coverage of `guard_eval`
r27921 @pytest.mark.parametrize("context", LIMITED_OR_HIGHER)
def test_evaluates_complex_cases(code, expected, context):
assert guarded_eval(code, context()) == expected
@pytest.mark.parametrize(
"code,expected",
[
["1", 1],
["1.0", 1.0],
["0xdeedbeef", 0xDEEDBEEF],
["True", True],
["None", None],
["{}", {}],
["[]", []],
],
)
@pytest.mark.parametrize("context", MINIMAL_OR_HIGHER)
def test_evaluates_literals(code, expected, context):
assert guarded_eval(code, context()) == expected
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
krassowski
Add guards for binary, unary operators and comparators
r27920 @pytest.mark.parametrize(
"code,expected",
[
["-5", -5],
["+5", +5],
["~5", -6],
],
)
krassowski
Increase coverage of `guard_eval`
r27921 @pytest.mark.parametrize("context", LIMITED_OR_HIGHER)
def test_evaluates_unary_operations(code, expected, context):
assert guarded_eval(code, context()) == expected
krassowski
Add guards for binary, unary operators and comparators
r27920
@pytest.mark.parametrize(
"code,expected",
[
["1 + 1", 2],
["3 - 1", 2],
["2 * 3", 6],
["5 // 2", 2],
["5 / 2", 2.5],
["5**2", 25],
["2 >> 1", 1],
["2 << 1", 4],
["1 | 2", 3],
["1 & 1", 1],
["1 & 2", 0],
],
)
krassowski
Increase coverage of `guard_eval`
r27921 @pytest.mark.parametrize("context", LIMITED_OR_HIGHER)
def test_evaluates_binary_operations(code, expected, context):
assert guarded_eval(code, context()) == expected
krassowski
Add guards for binary, unary operators and comparators
r27920
@pytest.mark.parametrize(
"code,expected",
[
["2 > 1", True],
["2 < 1", False],
["2 <= 1", False],
["2 <= 2", True],
["1 >= 2", False],
["2 >= 2", True],
["2 == 2", True],
["1 == 2", False],
["1 != 2", True],
["1 != 1", False],
["1 < 4 < 3", False],
["(1 < 4) < 3", True],
["4 > 3 > 2 > 1", True],
["4 > 3 > 2 > 9", False],
["1 < 2 < 3 < 4", True],
["9 < 2 < 3 < 4", False],
["1 < 2 > 1 > 0 > -1 < 1", True],
["1 in [1] in [[1]]", True],
["1 in [1] in [[2]]", False],
["1 in [1]", True],
["0 in [1]", False],
["1 not in [1]", False],
["0 not in [1]", True],
["True is True", True],
["False is False", True],
["True is False", False],
krassowski
Increase coverage of `guard_eval`
r27921 ["True is not True", False],
["False is not True", True],
krassowski
Add guards for binary, unary operators and comparators
r27920 ],
)
krassowski
Increase coverage of `guard_eval`
r27921 @pytest.mark.parametrize("context", LIMITED_OR_HIGHER)
def test_evaluates_comparisons(code, expected, context):
assert guarded_eval(code, context()) == expected
def test_guards_comparisons():
class GoodEq(int):
pass
class BadEq(int):
def __eq__(self, other):
assert False
context = limited(bad=BadEq(1), good=GoodEq(1))
with pytest.raises(GuardRejection):
guarded_eval("bad == 1", context)
with pytest.raises(GuardRejection):
guarded_eval("bad != 1", context)
with pytest.raises(GuardRejection):
guarded_eval("1 == bad", context)
with pytest.raises(GuardRejection):
guarded_eval("1 != bad", context)
assert guarded_eval("good == 1", context) is True
assert guarded_eval("good != 1", context) is False
assert guarded_eval("1 == good", context) is True
assert guarded_eval("1 != good", context) is False
def test_guards_unary_operations():
class GoodOp(int):
pass
class BadOpInv(int):
def __inv__(self, other):
assert False
class BadOpInverse(int):
def __inv__(self, other):
assert False
context = limited(good=GoodOp(1), bad1=BadOpInv(1), bad2=BadOpInverse(1))
with pytest.raises(GuardRejection):
guarded_eval("~bad1", context)
with pytest.raises(GuardRejection):
guarded_eval("~bad2", context)
def test_guards_binary_operations():
class GoodOp(int):
pass
krassowski
Add guards for binary, unary operators and comparators
r27920
krassowski
Increase coverage of `guard_eval`
r27921 class BadOp(int):
def __add__(self, other):
assert False
krassowski
Add guards for binary, unary operators and comparators
r27920
krassowski
Increase coverage of `guard_eval`
r27921 context = limited(good=GoodOp(1), bad=BadOp(1))
with pytest.raises(GuardRejection):
guarded_eval("1 + bad", context)
with pytest.raises(GuardRejection):
guarded_eval("bad + 1", context)
assert guarded_eval("good + 1", context) == 2
assert guarded_eval("1 + good", context) == 2
def test_guards_attributes():
class GoodAttr(float):
pass
class BadAttr1(float):
def __getattr__(self, key):
assert False
class BadAttr2(float):
def __getattribute__(self, key):
assert False
context = limited(good=GoodAttr(0.5), bad1=BadAttr1(0.5), bad2=BadAttr2(0.5))
with pytest.raises(GuardRejection):
guarded_eval("bad1.as_integer_ratio", context)
with pytest.raises(GuardRejection):
guarded_eval("bad2.as_integer_ratio", context)
assert guarded_eval("good.as_integer_ratio()", context) == (1, 2)
@pytest.mark.parametrize("context", MINIMAL_OR_HIGHER)
def test_access_builtins(context):
assert guarded_eval("round", context()) == round
def test_access_builtins_fails():
krassowski
Check types with mypy
r27915 context = limited()
krassowski
Increase coverage of `guard_eval`
r27921 with pytest.raises(NameError):
guarded_eval("this_is_not_builtin", context)
def test_rejects_forbidden():
context = forbidden()
with pytest.raises(GuardRejection):
guarded_eval("1", context)
def test_guards_locals_and_globals():
context = EvaluationContext(
locals={"local_a": "a"}, globals={"global_b": "b"}, evaluation="minimal"
)
with pytest.raises(GuardRejection):
guarded_eval("local_a", context)
with pytest.raises(GuardRejection):
guarded_eval("global_b", context)
def test_access_locals_and_globals():
context = EvaluationContext(
locals={"local_a": "a"}, globals={"global_b": "b"}, evaluation="limited"
)
assert guarded_eval("local_a", context) == "a"
assert guarded_eval("global_b", context) == "b"
@pytest.mark.parametrize(
"code",
["def func(): pass", "class C: pass", "x = 1", "x += 1", "del x", "import ast"],
)
@pytest.mark.parametrize("context", [minimal(), limited(), unsafe()])
def test_rejects_side_effect_syntax(code, context):
with pytest.raises(SyntaxError):
guarded_eval(code, context)
krassowski
Check types with mypy
r27915
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 def test_subscript():
context = EvaluationContext(
krassowski
Polish documentation, hide private functions
r27918 locals={}, globals={}, evaluation="limited", in_subscript=True
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 )
empty_slice = slice(None, None, None)
krassowski
You Want It Darker
r27913 assert guarded_eval("", context) == tuple()
assert guarded_eval(":", context) == empty_slice
assert guarded_eval("1:2:3", context) == slice(1, 2, 3)
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 assert guarded_eval(':, "a"', context) == (empty_slice, "a")
def test_unbind_method():
class X(list):
def index(self, k):
krassowski
You Want It Darker
r27913 return "CUSTOM"
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 x = X()
krassowski
Polish documentation, hide private functions
r27918 assert _unbind_method(x.index) is X.index
assert _unbind_method([].index) is list.index
krassowski
Add more tests
r27929 assert _unbind_method(list.index) is None
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
def test_assumption_instance_attr_do_not_matter():
"""This is semi-specified in Python documentation.
krassowski
Handle the case of built-ins, add tests
r28433 However, since the specification says 'not guaranteed
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 to work' rather than 'is forbidden to work', future
versions could invalidate this assumptions. This test
is meant to catch such a change if it ever comes true.
"""
krassowski
You Want It Darker
r27913
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 class T:
def __getitem__(self, k):
krassowski
You Want It Darker
r27913 return "a"
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 def __getattr__(self, k):
krassowski
You Want It Darker
r27913 return "a"
krassowski
Guard against custom properties
r27926 def f(self):
return "b"
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906 t = T()
krassowski
Guard against custom properties
r27926 t.__getitem__ = f
t.__getattr__ = f
krassowski
You Want It Darker
r27913 assert t[1] == "a"
assert t[1] == "a"
krassowski
Implement guarded evaluation, replace greedy, implement:...
r27906
def test_assumption_named_tuples_share_getitem():
"""Check assumption on named tuples sharing __getitem__"""
from typing import NamedTuple
class A(NamedTuple):
pass
class B(NamedTuple):
pass
assert A.__getitem__ == B.__getitem__
Carlos Cordoba
TST: Check that we can get completions for second-level Numpy modules
r28227
@dec.skip_without("numpy")
def test_module_access():
import numpy
context = limited(numpy=numpy)
assert guarded_eval("numpy.linalg.norm", context) == numpy.linalg.norm
context = minimal(numpy=numpy)
with pytest.raises(GuardRejection):
guarded_eval("np.linalg.norm", context)