Add some tests for ways users can change or shadow globals and builtins.
This commit is contained in:
parent
0077b65a9e
commit
f2bf2b3ec4
|
@ -30,7 +30,8 @@ __all__ = ["Error", "TestFailed", "ResourceDenied", "import_module",
|
|||
"run_with_locale",
|
||||
"set_memlimit", "bigmemtest", "bigaddrspacetest", "BasicTestRunner",
|
||||
"run_unittest", "run_doctest", "threading_setup", "threading_cleanup",
|
||||
"reap_children", "cpython_only", "check_impl_detail", "get_attribute"]
|
||||
"reap_children", "cpython_only", "check_impl_detail", "get_attribute",
|
||||
"swap_item", "swap_attr"]
|
||||
|
||||
class Error(Exception):
|
||||
"""Base class for regression test exceptions."""
|
||||
|
@ -1074,3 +1075,57 @@ def reap_children():
|
|||
break
|
||||
except:
|
||||
break
|
||||
|
||||
@contextlib.contextmanager
|
||||
def swap_attr(obj, attr, new_val):
|
||||
"""Temporary swap out an attribute with a new object.
|
||||
|
||||
Usage:
|
||||
with swap_attr(obj, "attr", 5):
|
||||
...
|
||||
|
||||
This will set obj.attr to 5 for the duration of the with: block,
|
||||
restoring the old value at the end of the block. If `attr` doesn't
|
||||
exist on `obj`, it will be created and then deleted at the end of the
|
||||
block.
|
||||
"""
|
||||
if hasattr(obj, attr):
|
||||
real_val = getattr(obj, attr)
|
||||
setattr(obj, attr, new_val)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
setattr(obj, attr, real_val)
|
||||
else:
|
||||
setattr(obj, attr, new_val)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
delattr(obj, attr)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def swap_item(obj, item, new_val):
|
||||
"""Temporary swap out an item with a new object.
|
||||
|
||||
Usage:
|
||||
with swap_item(obj, "item", 5):
|
||||
...
|
||||
|
||||
This will set obj["item"] to 5 for the duration of the with: block,
|
||||
restoring the old value at the end of the block. If `item` doesn't
|
||||
exist on `obj`, it will be created and then deleted at the end of the
|
||||
block.
|
||||
"""
|
||||
if item in obj:
|
||||
real_val = obj[item]
|
||||
obj[item] = new_val
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
obj[item] = real_val
|
||||
else:
|
||||
obj[item] = new_val
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
del obj[item]
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
# Test the most dynamic corner cases of Python's runtime semantics.
|
||||
|
||||
import builtins
|
||||
import contextlib
|
||||
import unittest
|
||||
|
||||
from test.support import run_unittest, swap_item, swap_attr
|
||||
|
||||
|
||||
class RebindBuiltinsTests(unittest.TestCase):
|
||||
|
||||
"""Test all the ways that we can change/shadow globals/builtins."""
|
||||
|
||||
def configure_func(self, func, *args):
|
||||
"""Perform TestCase-specific configuration on a function before testing.
|
||||
|
||||
By default, this does nothing. Example usage: spinning a function so
|
||||
that a JIT will optimize it. Subclasses should override this as needed.
|
||||
|
||||
Args:
|
||||
func: function to configure.
|
||||
*args: any arguments that should be passed to func, if calling it.
|
||||
|
||||
Returns:
|
||||
Nothing. Work will be performed on func in-place.
|
||||
"""
|
||||
pass
|
||||
|
||||
def test_globals_shadow_builtins(self):
|
||||
# Modify globals() to shadow an entry in builtins.
|
||||
def foo():
|
||||
return len([1, 2, 3])
|
||||
self.configure_func(foo)
|
||||
|
||||
self.assertEqual(foo(), 3)
|
||||
with swap_item(globals(), "len", lambda x: 7):
|
||||
self.assertEqual(foo(), 7)
|
||||
|
||||
def test_modify_builtins(self):
|
||||
# Modify the builtins module directly.
|
||||
def foo():
|
||||
return len([1, 2, 3])
|
||||
self.configure_func(foo)
|
||||
|
||||
self.assertEqual(foo(), 3)
|
||||
with swap_attr(builtins, "len", lambda x: 7):
|
||||
self.assertEqual(foo(), 7)
|
||||
|
||||
def test_modify_builtins_while_generator_active(self):
|
||||
# Modify the builtins out from under a live generator.
|
||||
def foo():
|
||||
x = range(3)
|
||||
yield len(x)
|
||||
yield len(x)
|
||||
self.configure_func(foo)
|
||||
|
||||
g = foo()
|
||||
self.assertEqual(next(g), 3)
|
||||
with swap_attr(builtins, "len", lambda x: 7):
|
||||
self.assertEqual(next(g), 7)
|
||||
|
||||
def test_modify_builtins_from_leaf_function(self):
|
||||
# Verify that modifications made by leaf functions percolate up the
|
||||
# callstack.
|
||||
with swap_attr(builtins, "len", len):
|
||||
def bar():
|
||||
builtins.len = lambda x: 4
|
||||
|
||||
def foo(modifier):
|
||||
l = []
|
||||
l.append(len(range(7)))
|
||||
modifier()
|
||||
l.append(len(range(7)))
|
||||
return l
|
||||
self.configure_func(foo, lambda: None)
|
||||
|
||||
self.assertEqual(foo(bar), [7, 4])
|
||||
|
||||
def test_cannot_change_globals_or_builtins_with_eval(self):
|
||||
def foo():
|
||||
return len([1, 2, 3])
|
||||
self.configure_func(foo)
|
||||
|
||||
# Note that this *doesn't* change the definition of len() seen by foo().
|
||||
builtins_dict = {"len": lambda x: 7}
|
||||
globals_dict = {"foo": foo, "__builtins__": builtins_dict,
|
||||
"len": lambda x: 8}
|
||||
self.assertEqual(eval("foo()", globals_dict), 3)
|
||||
|
||||
self.assertEqual(eval("foo()", {"foo": foo}), 3)
|
||||
|
||||
def test_cannot_change_globals_or_builtins_with_exec(self):
|
||||
def foo():
|
||||
return len([1, 2, 3])
|
||||
self.configure_func(foo)
|
||||
|
||||
globals_dict = {"foo": foo}
|
||||
exec("x = foo()", globals_dict)
|
||||
self.assertEqual(globals_dict["x"], 3)
|
||||
|
||||
# Note that this *doesn't* change the definition of len() seen by foo().
|
||||
builtins_dict = {"len": lambda x: 7}
|
||||
globals_dict = {"foo": foo, "__builtins__": builtins_dict,
|
||||
"len": lambda x: 8}
|
||||
|
||||
exec("x = foo()", globals_dict)
|
||||
self.assertEqual(globals_dict["x"], 3)
|
||||
|
||||
def test_cannot_replace_builtins_dict_while_active(self):
|
||||
def foo():
|
||||
x = range(3)
|
||||
yield len(x)
|
||||
yield len(x)
|
||||
self.configure_func(foo)
|
||||
|
||||
g = foo()
|
||||
self.assertEqual(next(g), 3)
|
||||
with swap_item(globals(), "__builtins__", {"len": lambda x: 7}):
|
||||
self.assertEqual(next(g), 3)
|
||||
|
||||
def test_cannot_replace_builtins_dict_between_calls(self):
|
||||
def foo():
|
||||
return len([1, 2, 3])
|
||||
self.configure_func(foo)
|
||||
|
||||
self.assertEqual(foo(), 3)
|
||||
with swap_item(globals(), "__builtins__", {"len": lambda x: 7}):
|
||||
self.assertEqual(foo(), 3)
|
||||
|
||||
def test_eval_gives_lambda_custom_globals(self):
|
||||
globals_dict = {"len": lambda x: 7}
|
||||
foo = eval("lambda: len([])", globals_dict)
|
||||
self.configure_func(foo)
|
||||
|
||||
self.assertEqual(foo(), 7)
|
||||
|
||||
|
||||
def test_main():
|
||||
run_unittest(RebindBuiltinsTests)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_main()
|
Loading…
Reference in New Issue