Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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
  • Loading branch information
youknowone committed Feb 21, 2026
commit add253912ebbab2c8642e0286ee1624f9ac3f23b
2 changes: 0 additions & 2 deletions Lib/test/test_c_locale_coercion.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 3 additions & 5 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 1 addition & 4 deletions crates/vm/src/object/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(()),
}
}

Expand Down
49 changes: 45 additions & 4 deletions crates/vm/src/stdlib/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -789,8 +789,22 @@ mod sys {

#[pyfunction]
fn exit(code: OptionalArg<PyObjectRef>, 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::<PyTuple>() {
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]
Expand Down Expand Up @@ -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<PyDictRef> {
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<PyDictRef> {
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]
Expand Down
7 changes: 6 additions & 1 deletion crates/vm/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down