Files
RustPython/Lib/test/test_dynamic.py
T
28393e932d Implement PEP 814 frozendict (#8951)
* Restore consolidated frozendict implementation on current main

Preserve reverse dictionary iterator fixes and the 3.15 frame imports. Keep library refreshes separate pending the runtime dependency sequencing decision.

Assisted-by: OpenAI Codex:model identifier unavailable

* Support scoped canonical test updates in update_lib

Index conditional test classes and methods without crossing namespaces, reject ambiguous targets, and preserve inherited override indentation. Add hash-pinned whole-test copying that verifies execution context and exact source bytes, with focused Python fixtures.

Assisted-by: OpenAI Codex:model identifier unavailable

* Align frozendict copy support and tests with CPython 3.15

Import exact-final copy.py with update_lib; defer Mapping ABC registration to the later library stage. Generate only proven marker corrections, retain observed debug recursion-limit expected failures, and copy the complete canonical builtin-doc discovery test using hash-pinned update_lib copy-test.

Assisted-by: OpenAI Codex:model identifier unavailable

* Preserve decorator rationale during canonical test copying

Reject differing decorator source blocks even when their ASTs match. Cover inline and multiline marker comments, guarded async methods, and CLI no-write behavior.

Assisted-by: OpenAI Codex:model identifier unavailable

* Finish frozendict with borrowed PyAnyDict views

Use the landed PyRef target model for a transparent dict/frozendict header
view, retaining objects explicitly with PyRef<PyAnyDict>. Keep targets
sealed and preserve mutable-only entry points. Borrow frame globals from
the object header rather than movable reference wrappers.

Unify exact-TypeError key diagnostics with final CPython semantics,
including callback failure propagation and no explicit cause. Keep the
Python regressions in builtin_dict and test reference layout/ownership
in Rust. Preserve existing canonical library updates.

Assisted-by: OpenAI Codex:model identifier unavailable

* Align frozendict with CPython and use CPython 3.15 tests

- Name the target type in unhashable key errors through
  PyDict::update_arg (dict_update_arg), replacing the separate
  PyFrozenDict::merge_into copy of the merge logic.
- Split the | operator into dict_or (_PyDict_Or) and frozendict_or.
- Return the argument from frozendict(frozendict) only when the called
  type is frozendict.
- Call through the vectorcall slot in PyCallable::invoke when there
  are no keyword arguments, instead of special-casing frozendict.
- Add ensure_builtins (_PyEval_EnsureBuiltins) for eval/exec, and
  insert __builtins__ for source strings regardless of globals type.
- Read __warningregistry__ and __name__ from stored globals entries and
  accept only None or str for the module name in setup_context.
- Remove the unused PyFrozenDict::from_object.
- Migrate test_dict.py, mapping_tests.py, test_copy.py and
  test_builtin.py to v3.15.0 with update_lib, and add
  test_import/data/syntax_warnings.py.
- Drop builtin_dict.py frozendict tests covered by those modules,
  keeping the descriptor and namespace integration tests.

Assisted-by: Claude Code:claude-opus-5-5

* Update crates/vm/src/builtins/dict.rs

---------

Co-authored-by: Jeong YunWon <jeong@youknowone.org>
Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com>
2026-10-14 16:42:56 +09:00

200 lines
6.2 KiB
Python
Vendored

# Test the most dynamic corner cases of Python's runtime semantics.
import builtins
import sys
import unittest
from test.support import swap_item, swap_attr, skip_wasi_stack_overflow
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)
@unittest.expectedFailureIf(sys.getrecursionlimit() == 256, "TODO: RUSTPYTHON; default debug recursion limit is 256")
@skip_wasi_stack_overflow()
def test_load_global_specialization_failure_keeps_oparg(self):
# https://github.com/python/cpython/issues/91625
class MyGlobals(dict):
def __missing__(self, key):
return int(key.removeprefix("_number_"))
# Need more than 256 variables to use EXTENDED_ARGS
variables = 400
code = "lambda: " + "+".join(f"_number_{i}" for i in range(variables))
sum_func = eval(code, MyGlobals())
expected = sum(range(variables))
# Warm up the function for quickening (PEP 659)
for _ in range(30):
self.assertEqual(sum_func(), expected)
class TestTracing(unittest.TestCase):
def setUp(self):
self.addCleanup(sys.settrace, sys.gettrace())
sys.settrace(None)
def test_after_specialization(self):
def trace(frame, event, arg):
return trace
turn_on_trace = False
class C:
def __init__(self, x):
self.x = x
def __del__(self):
if turn_on_trace:
sys.settrace(trace)
def f():
# LOAD_GLOBAL[_BUILTIN] immediately follows the call to C.__del__
C(0).x, len
def g():
# BINARY_SUSCR[_LIST_INT] immediately follows the call to C.__del__
[0][C(0).x]
def h():
# BINARY_OP[_ADD_INT] immediately follows the call to C.__del__
0 + C(0).x
for func in (f, g, h):
with self.subTest(func.__name__):
for _ in range(58):
func()
turn_on_trace = True
func()
sys.settrace(None)
turn_on_trace = False
if __name__ == "__main__":
unittest.main()