From edca2dddf1fb6e0a3d3bbd2b2afa065e1e0becb3 Mon Sep 17 00:00:00 2001 From: CPython Developers <> Date: Wed, 11 Feb 2026 01:32:22 +0900 Subject: [PATCH 1/3] Update test_sys from v3.14.3 --- Lib/test/test_sys.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 9ebd6dd9cc1..524c57f6c54 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1,6 +1,5 @@ import builtins import codecs -# import _datetime # TODO: RUSTPYTHON import gc import io import locale @@ -210,7 +209,7 @@ class SysModuleTest(unittest.TestCase): def tearDown(self): test.support.reap_children() - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: (42,) != 42 def test_exit(self): # call with two arguments self.assertRaises(TypeError, sys.exit, 42, 42) @@ -352,7 +351,7 @@ def test_setrecursionlimit(self): finally: sys.setrecursionlimit(old_limit) - @unittest.skipIf(getattr(sys, '_rustpython_debugbuild', False), 'TODO: RUSTPYTHON; stack overflow on debug build') + @unittest.skipIf(getattr(sys, "_rustpython_debugbuild", False), "TODO: RUSTPYTHON; stack overflow on debug build") def test_recursionlimit_recovery(self): if hasattr(sys, 'gettrace') and sys.gettrace(): self.skipTest('fatal error if run with a trace function') @@ -402,7 +401,6 @@ def test_setrecursionlimit_to_depth(self): finally: sys.setrecursionlimit(old_limit) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_getwindowsversion(self): # Raise SkipTest if sys doesn't have getwindowsversion attribute test.support.get_attribute(sys, "getwindowsversion") @@ -433,7 +431,7 @@ def test_getwindowsversion(self): # still has 5 elements maj, min, buildno, plat, csd = sys.getwindowsversion() - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute 'call_tracing' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute 'call_tracing' def test_call_tracing(self): self.assertRaises(TypeError, sys.call_tracing, type, 2) @@ -499,8 +497,7 @@ def test_getframemodulename(self): self.assertIsNone(sys._getframemodulename(i)) # sys._current_frames() is a CPython-only gimmick. - # XXX RUSTPYTHON: above comment is from original cpython test; not sure why the cpython_only decorator wasn't added - @test.support.cpython_only + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: didn't find f123() on thread's call stack @threading_helper.reap_threads @threading_helper.requires_working_threading() def test_current_frames(self): @@ -568,7 +565,7 @@ def g456(): leave_g.set() t.join() - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute '_current_exceptions' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute '_current_exceptions' @threading_helper.reap_threads @threading_helper.requires_working_threading() def test_current_exceptions(self): @@ -856,7 +853,7 @@ def test_subinterp_intern_singleton(self): ''')) self.assertTrue(sys._is_interned(s)) - @unittest.expectedFailure # TODO: RUSTPYTHON; needs update for context_aware_warnings + @unittest.expectedFailure # TODO: RUSTPYTHON; needs update for context_aware_warnings def test_sys_flags(self): self.assertTrue(sys.flags) attrs = ("debug", @@ -885,7 +882,6 @@ def test_sys_flags_no_instantiation(self): def test_sys_version_info_no_instantiation(self): self.assert_raise_on_new_sys_type(sys.version_info) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_sys_getwindowsversion_no_instantiation(self): # Skip if not being run on Windows. test.support.get_attribute(sys, "getwindowsversion") @@ -897,7 +893,7 @@ def test_clear_type_cache(self): r"sys\._clear_type_cache\(\) is deprecated.*"): sys._clear_type_cache() - @unittest.skip('TODO: RUSTPYTHON; cp424 encoding not supported, causes panic') + @unittest.skip("TODO: RUSTPYTHON; cp424 encoding not supported, causes panic") @force_not_colorized @support.requires_subprocess() def test_ioencoding(self): @@ -1062,12 +1058,12 @@ def check_locale_surrogateescape(self, locale): 'stdout: surrogateescape\n' 'stderr: backslashreplace\n') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; stderr: backslashreplace @support.requires_subprocess() def test_c_locale_surrogateescape(self): self.check_locale_surrogateescape('C') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; stderr: backslashreplace @support.requires_subprocess() def test_posix_locale_surrogateescape(self): self.check_locale_surrogateescape('POSIX') @@ -1192,7 +1188,7 @@ def __del__(self): rc, stdout, stderr = assert_python_ok('-c', code) self.assertEqual(stdout.rstrip(), b'True') - @unittest.expectedFailure # TODO: RUSTPYTHON; IndexError: list index out of range + @unittest.expectedFailure # TODO: RUSTPYTHON; IndexError: list index out of range def test_issue20602(self): # sys.flags and sys.float_info were wiped during shutdown. code = """if 1: @@ -1225,7 +1221,7 @@ def __del__(self): self.assertEqual(stdout.rstrip(), b"") self.assertEqual(stderr.rstrip(), b"") - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute 'getandroidapilevel' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute 'getandroidapilevel' @unittest.skipUnless(sys.platform == "android", "Android only") def test_getandroidapilevel(self): level = sys.getandroidapilevel() @@ -1494,6 +1490,7 @@ def hook_func(args): def test_custom_unraisablehook_fail(self): _testcapi = import_helper.import_module('_testcapi') from _testcapi import err_writeunraisable + def hook_func(*args): raise Exception("hook_func failed") @@ -1742,7 +1739,12 @@ def delx(self): del self.__x x = property(getx, setx, delx, "") check(x, size('5Pi')) # PyCapsule - check(_datetime.datetime_CAPI, size('6P')) + try: + import _datetime + except ModuleNotFoundError: + pass + else: + check(_datetime.datetime_CAPI, size('6P')) # rangeiterator check(iter(range(1)), size('3l')) check(iter(range(2**65)), size('3P')) @@ -2227,7 +2229,7 @@ def test_jit_is_enabled(self): assert_python_ok("-c", script.format(enabled=False), PYTHON_JIT="0") assert_python_ok("-c", script.format(enabled=available), PYTHON_JIT="1") - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; --- def test_jit_is_active(self): available = sys._jit.is_available() script = textwrap.dedent( From add253912ebbab2c8642e0286ee1624f9ac3f23b Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Wed, 11 Feb 2026 01:46:27 +0900 Subject: [PATCH 2/3] Implement sys.call_tracing, sys._current_exceptions - Add sys.call_tracing as func(*args) dispatch - Add sys._current_exceptions with per-thread exception tracking via thread_exceptions registry synced on push/pop/set_exception - Fix f_back to use previous_frame pointer and cross-thread lookup - Add module="sys" to float_info struct sequence - Fix sys.exit to unpack tuple args - Set default stdio_errors to surrogateescape - Remove noisy __del__ warning in object core --- Lib/test/test_c_locale_coercion.py | 2 -- Lib/test/test_sys.py | 8 ++--- crates/vm/src/object/core.rs | 5 +-- crates/vm/src/stdlib/sys.rs | 49 +++++++++++++++++++++++++++--- crates/vm/src/vm/mod.rs | 7 ++++- 5 files changed, 55 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_c_locale_coercion.py b/Lib/test/test_c_locale_coercion.py index 818dc16b834..71f934756e2 100644 --- a/Lib/test/test_c_locale_coercion.py +++ b/Lib/test/test_c_locale_coercion.py @@ -243,8 +243,6 @@ def setUpClass(cls): if not AVAILABLE_TARGETS: raise unittest.SkipTest("No C-with-UTF-8 locale available") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_external_target_locale_configuration(self): # Explicitly setting a target locale should give the same behaviour as diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 524c57f6c54..9b7b67d813a 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -209,7 +209,7 @@ class SysModuleTest(unittest.TestCase): def tearDown(self): test.support.reap_children() - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: (42,) != 42 + @unittest.expectedFailure # TODO: RUSTPYTHON; latin-1 codec not registered def test_exit(self): # call with two arguments self.assertRaises(TypeError, sys.exit, 42, 42) @@ -401,6 +401,7 @@ def test_setrecursionlimit_to_depth(self): finally: sys.setrecursionlimit(old_limit) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_getwindowsversion(self): # Raise SkipTest if sys doesn't have getwindowsversion attribute test.support.get_attribute(sys, "getwindowsversion") @@ -431,7 +432,6 @@ def test_getwindowsversion(self): # still has 5 elements maj, min, buildno, plat, csd = sys.getwindowsversion() - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute 'call_tracing' def test_call_tracing(self): self.assertRaises(TypeError, sys.call_tracing, type, 2) @@ -497,7 +497,6 @@ def test_getframemodulename(self): self.assertIsNone(sys._getframemodulename(i)) # sys._current_frames() is a CPython-only gimmick. - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: didn't find f123() on thread's call stack @threading_helper.reap_threads @threading_helper.requires_working_threading() def test_current_frames(self): @@ -565,7 +564,6 @@ def g456(): leave_g.set() t.join() - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute '_current_exceptions' @threading_helper.reap_threads @threading_helper.requires_working_threading() def test_current_exceptions(self): @@ -882,6 +880,7 @@ def test_sys_flags_no_instantiation(self): def test_sys_version_info_no_instantiation(self): self.assert_raise_on_new_sys_type(sys.version_info) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_sys_getwindowsversion_no_instantiation(self): # Skip if not being run on Windows. test.support.get_attribute(sys, "getwindowsversion") @@ -1188,7 +1187,6 @@ def __del__(self): rc, stdout, stderr = assert_python_ok('-c', code) self.assertEqual(stdout.rstrip(), b'True') - @unittest.expectedFailure # TODO: RUSTPYTHON; IndexError: list index out of range def test_issue20602(self): # sys.flags and sys.float_info were wiped during shutdown. code = """if 1: diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 4d61a208241..b238835d28d 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -1078,10 +1078,7 @@ impl PyObject { Some(true) => Ok(()), // we've been resurrected by __del__ Some(false) => Err(()), - None => { - warn!("couldn't run __del__ method for object"); - Ok(()) - } + None => Ok(()), } } diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 3ee6caf2eae..492324277e9 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -33,8 +33,8 @@ mod sys { use crate::{ AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult, builtins::{ - PyBaseExceptionRef, PyDictRef, PyFrozenSet, PyNamespace, PyStr, PyStrRef, PyTupleRef, - PyTypeRef, + PyBaseExceptionRef, PyDictRef, PyFrozenSet, PyNamespace, PyStr, PyStrRef, PyTuple, + PyTupleRef, PyTypeRef, }, common::{ ascii, @@ -789,8 +789,22 @@ mod sys { #[pyfunction] fn exit(code: OptionalArg, vm: &VirtualMachine) -> PyResult { - let code = code.unwrap_or_none(vm); - Err(vm.new_exception(vm.ctx.exceptions.system_exit.to_owned(), vec![code])) + let status = code.unwrap_or_none(vm); + let args = if let Some(status_tuple) = status.downcast_ref::() { + status_tuple.as_slice().to_vec() + } else { + vec![status] + }; + let exc = vm.invoke_exception(vm.ctx.exceptions.system_exit.to_owned(), args)?; + Err(exc) + } + + #[pyfunction] + fn call_tracing(func: PyObjectRef, args: PyTupleRef, vm: &VirtualMachine) -> PyResult { + // CPython temporarily enables tracing state around this call. + // RustPython does not currently model the full C-level tracing toggles, + // but call semantics (func(*args)) are matched. + func.call(PosArgs::new(args.as_slice().to_vec()), vm) } #[pyfunction] @@ -1031,6 +1045,33 @@ mod sys { Ok(dict) } + /// Return a dictionary mapping each thread's identifier to its currently + /// active exception, or None if no exception is active. + #[cfg(feature = "threading")] + #[pyfunction] + fn _current_exceptions(vm: &VirtualMachine) -> PyResult { + use crate::AsObject; + use crate::vm::thread::get_all_current_exceptions; + + let dict = vm.ctx.new_dict(); + for (thread_id, exc) in get_all_current_exceptions(vm) { + let key = vm.ctx.new_int(thread_id); + let value = exc.map_or_else(|| vm.ctx.none(), |e| e.into()); + dict.set_item(key.as_object(), value, vm)?; + } + + Ok(dict) + } + + #[cfg(not(feature = "threading"))] + #[pyfunction] + fn _current_exceptions(vm: &VirtualMachine) -> PyResult { + let dict = vm.ctx.new_dict(); + let key = vm.ctx.new_int(0); + dict.set_item(key.as_object(), vm.topmost_exception().to_pyobject(vm), vm)?; + Ok(dict) + } + /// Stub for non-threading builds - returns empty dict #[cfg(not(feature = "threading"))] #[pyfunction] diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 07395b80460..dc56a04fed3 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -397,7 +397,12 @@ impl VirtualMachine { let errors = if fd == 2 { Some("backslashreplace") } else { - self.state.config.settings.stdio_errors.as_deref() + self.state + .config + .settings + .stdio_errors + .as_deref() + .or(Some("surrogateescape")) }; let stdio = self.call_method( From 5cc589980cfb1767c2200bcabc2e0f4febaac8a6 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 21 Feb 2026 13:34:00 +0900 Subject: [PATCH 3/3] Infer struct sequence module name from __module__ attribute in repr slot_repr now checks the __module__ attribute set by #[pymodule] at runtime, so explicit module = "..." in #[pystruct_sequence] is no longer needed for types defined inside a #[pymodule]. --- crates/vm/src/types/structseq.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/vm/src/types/structseq.rs b/crates/vm/src/types/structseq.rs index af35f92f656..3b89a62b9ad 100644 --- a/crates/vm/src/types/structseq.rs +++ b/crates/vm/src/types/structseq.rs @@ -1,7 +1,7 @@ use crate::common::lock::LazyLock; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, atomic_func, - builtins::{PyBaseExceptionRef, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef}, + builtins::{PyBaseExceptionRef, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef}, class::{PyClassImpl, StaticType}, function::{Either, FuncArgs, PyComparisonValue, PyMethodDef, PyMethodFlags}, iter::PyExactSizeIterator, @@ -225,7 +225,21 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { } else { (String::new(), "...") }; - let repr_str = format!("{}({}{})", Self::TP_NAME, body, suffix); + // Build qualified name: if MODULE_NAME is already in TP_NAME, use it directly. + // Otherwise, check __module__ attribute (set by #[pymodule] at runtime). + let type_name = if Self::MODULE_NAME.is_some() { + alloc::borrow::Cow::Borrowed(Self::TP_NAME) + } else { + let typ = zelf.class(); + match typ.get_attr(identifier!(vm.ctx, __module__)) { + Some(module) if module.downcastable::() => { + let module_str = module.downcast_ref::().unwrap(); + alloc::borrow::Cow::Owned(format!("{}.{}", module_str.as_str(), Self::NAME)) + } + _ => alloc::borrow::Cow::Borrowed(Self::TP_NAME), + } + }; + let repr_str = format!("{}({}{})", type_name, body, suffix); Ok(vm.ctx.new_str(repr_str)) }