diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index fc56ec4afc9..d9ae2f35487 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -820,8 +820,6 @@ def test_fromfile(self): output = self.run_tests('--fromfile', filename) self.check_executed_tests(output, tests) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_interrupted(self): code = TEST_INTERRUPTED test = self.create_test('sigint', code=code) @@ -839,8 +837,6 @@ def test_slowest(self): % (self.TESTNAME_REGEX, len(tests))) self.check_line(output, regex) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_slowest_interrupted(self): # Issue #25373: test --slowest with an interrupted test code = TEST_INTERRUPTED diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 5fbb26df831..34ef13adf33 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -82,8 +82,6 @@ def trivial_signal_handler(self, *args): def create_handler_with_partial(self, argument): return functools.partial(self.trivial_signal_handler, argument) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_out_of_range_signal_number_raises_error(self): self.assertRaises(ValueError, signal.getsignal, 4242) @@ -126,8 +124,6 @@ def __repr__(self): self.assertEqual(signal.getsignal(signal.SIGHUP), hup) self.assertEqual(0, argument.repr_count) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_strsignal(self): self.assertIn("Interrupt", signal.strsignal(signal.SIGINT)) self.assertIn("Terminated", signal.strsignal(signal.SIGTERM)) @@ -141,8 +137,6 @@ def test_interprocess_signal(self): script = os.path.join(dirname, 'signalinterproctester.py') assert_python_ok(script) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipUnless( hasattr(signal, "valid_signals"), "requires signal.valid_signals" @@ -190,8 +184,6 @@ def test_keyboard_interrupt_exit_code(self): @unittest.skipUnless(sys.platform == "win32", "Windows specific") class WindowsSignalTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_valid_signals(self): s = signal.valid_signals() self.assertIsInstance(s, set) @@ -251,13 +243,11 @@ def test_invalid_call(self): with self.assertRaises(TypeError): signal.set_wakeup_fd(signal.SIGINT, False) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_invalid_fd(self): fd = os_helper.make_bad_fd() self.assertRaises((ValueError, OSError), signal.set_wakeup_fd, fd) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @unittest.skipUnless(support.has_socket_support, "needs working sockets.") def test_invalid_socket(self): sock = socket.socket() @@ -266,7 +256,6 @@ def test_invalid_socket(self): self.assertRaises((ValueError, OSError), signal.set_wakeup_fd, fd) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") # Emscripten does not support fstat on pipes yet. # https://github.com/emscripten-core/emscripten/issues/16414 @unittest.skipIf(support.is_emscripten, "Emscripten cannot fstat pipes.") @@ -288,7 +277,6 @@ def test_set_wakeup_fd_result(self): self.assertEqual(signal.set_wakeup_fd(-1), w2) self.assertEqual(signal.set_wakeup_fd(-1), -1) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @unittest.skipIf(support.is_emscripten, "Emscripten cannot fstat pipes.") @unittest.skipUnless(support.has_socket_support, "needs working sockets.") def test_set_wakeup_fd_socket_result(self): @@ -1440,8 +1428,6 @@ def cycle_handlers(): class RaiseSignalTest(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_sigint(self): with self.assertRaises(KeyboardInterrupt): signal.raise_signal(signal.SIGINT) @@ -1460,8 +1446,6 @@ def test_invalid_argument(self): else: raise - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_handler(self): is_ok = False def handler(a, b): diff --git a/Lib/test/test_strftime.py b/Lib/test/test_strftime.py index f5024d8e6da..be43c49e40a 100644 --- a/Lib/test/test_strftime.py +++ b/Lib/test/test_strftime.py @@ -184,7 +184,6 @@ class Y1900Tests(unittest.TestCase): a date before 1900 is passed with a format string containing "%y" """ - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_y_before_1900(self): # Issue #13674, #19634 t = (1899, 1, 1, 0, 0, 0, 0, 0, 0) diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index 5c3d8825f78..4eacb10154c 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -228,7 +228,7 @@ pub(crate) mod _signal { } #[pyfunction] - fn set_wakeup_fd(args: SetWakeupFdArgs, vm: &VirtualMachine) -> PyResult { + fn set_wakeup_fd(args: SetWakeupFdArgs, vm: &VirtualMachine) -> PyResult { // TODO: implement warn_on_full_buffer let _ = args.warn_on_full_buffer; #[cfg(windows)] @@ -264,6 +264,15 @@ pub(crate) mod _signal { if err.raw_os_error() != Some(WinSock::WSAENOTSOCK) { return Err(err.into_pyexception(vm)); } + // Validate that fd is a valid file descriptor using fstat + // First check if SOCKET can be safely cast to i32 (file descriptor) + let fd_i32 = + i32::try_from(fd).map_err(|_| vm.new_value_error("invalid fd".to_owned()))?; + // Verify the fd is valid by trying to fstat it + let borrowed_fd = + unsafe { crate::common::crt_fd::Borrowed::try_borrow_raw(fd_i32) } + .map_err(|e| e.into_pyexception(vm))?; + crate::common::fileutils::fstat(borrowed_fd).map_err(|e| e.into_pyexception(vm))?; } is_socket } else { @@ -287,7 +296,18 @@ pub(crate) mod _signal { #[cfg(windows)] WAKEUP_IS_SOCKET.store(is_socket, Ordering::Relaxed); - Ok(old_fd) + #[cfg(windows)] + { + if old_fd == INVALID_WAKEUP { + Ok(-1) + } else { + Ok(old_fd as i64) + } + } + #[cfg(not(windows))] + { + Ok(old_fd as i64) + } } #[cfg(all(unix, not(target_os = "redox")))] @@ -302,6 +322,116 @@ pub(crate) mod _signal { } } + /// CPython: signal_raise_signal (signalmodule.c) + #[cfg(any(unix, windows))] + #[pyfunction] + fn raise_signal(signalnum: i32, vm: &VirtualMachine) -> PyResult<()> { + signal::assert_in_range(signalnum, vm)?; + + // On Windows, only certain signals are supported + #[cfg(windows)] + { + use crate::convert::IntoPyException; + // Windows supports: SIGINT(2), SIGILL(4), SIGFPE(8), SIGSEGV(11), SIGTERM(15), SIGABRT(22) + const VALID_SIGNALS: &[i32] = &[ + libc::SIGINT, + libc::SIGILL, + libc::SIGFPE, + libc::SIGSEGV, + libc::SIGTERM, + libc::SIGABRT, + ]; + if !VALID_SIGNALS.contains(&signalnum) { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL).into_pyexception(vm)); + } + } + + let res = unsafe { libc::raise(signalnum) }; + if res != 0 { + return Err(vm.new_os_error(format!("raise_signal failed for signal {}", signalnum))); + } + + // Check if a signal was triggered and handle it + signal::check_signals(vm)?; + + Ok(()) + } + + /// CPython: signal_strsignal (signalmodule.c) + #[cfg(unix)] + #[pyfunction] + fn strsignal(signalnum: i32, vm: &VirtualMachine) -> PyResult> { + if signalnum < 1 || signalnum >= signal::NSIG as i32 { + return Err(vm.new_value_error(format!("signal number {} out of range", signalnum))); + } + let s = unsafe { libc::strsignal(signalnum) }; + if s.is_null() { + Ok(None) + } else { + let cstr = unsafe { std::ffi::CStr::from_ptr(s) }; + Ok(Some(cstr.to_string_lossy().into_owned())) + } + } + + #[cfg(windows)] + #[pyfunction] + fn strsignal(signalnum: i32, vm: &VirtualMachine) -> PyResult> { + if signalnum < 1 || signalnum >= signal::NSIG as i32 { + return Err(vm.new_value_error(format!("signal number {} out of range", signalnum))); + } + // Windows doesn't have strsignal(), provide our own mapping + let name = match signalnum { + libc::SIGINT => "Interrupt", + libc::SIGILL => "Illegal instruction", + libc::SIGFPE => "Floating-point exception", + libc::SIGSEGV => "Segmentation fault", + libc::SIGTERM => "Terminated", + libc::SIGABRT => "Aborted", + _ => return Ok(None), + }; + Ok(Some(name.to_owned())) + } + + /// CPython: signal_valid_signals (signalmodule.c) + #[pyfunction] + fn valid_signals(vm: &VirtualMachine) -> PyResult { + use crate::PyPayload; + use crate::builtins::PySet; + let set = PySet::default().into_ref(&vm.ctx); + #[cfg(unix)] + { + // On Unix, most signals 1..NSIG are valid + for signum in 1..signal::NSIG { + // Skip signals that cannot be caught + #[cfg(not(target_os = "wasi"))] + if signum == libc::SIGKILL as usize || signum == libc::SIGSTOP as usize { + continue; + } + set.add(vm.ctx.new_int(signum as i32).into(), vm)?; + } + } + #[cfg(windows)] + { + // Windows only supports a limited set of signals + for &signum in &[ + libc::SIGINT, + libc::SIGILL, + libc::SIGFPE, + libc::SIGSEGV, + libc::SIGTERM, + libc::SIGABRT, + ] { + set.add(vm.ctx.new_int(signum).into(), vm)?; + } + } + #[cfg(not(any(unix, windows)))] + { + // Empty set for platforms without signal support (e.g., WASM) + let _ = &set; + } + Ok(set.into()) + } + #[cfg(any(unix, windows))] pub extern "C" fn run_signal(signum: i32) { signal::TRIGGERS[signum as usize].store(true, Ordering::Relaxed); diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index 1c6113bad77..b9b53cdc5c5 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -360,6 +360,14 @@ mod decl { use std::fmt::Write; let instant = t.naive_or_local(vm)?; + + // On Windows/AIX/Solaris, %y format with year < 1900 is not supported + #[cfg(any(windows, target_os = "aix", target_os = "solaris"))] + if instant.year() < 1900 && format.as_str().contains("%y") { + let msg = "format %y requires year >= 1900 on Windows"; + return Err(vm.new_value_error(msg.to_owned())); + } + let mut formatted_time = String::new(); /*