Skip to content

Commit df3a0b2

Browse files
authored
impl {raise_,str}signal (#6411)
* year>=1900 on windows * signal * set_wakeup_fd
1 parent 4bec0ad commit df3a0b2

File tree

5 files changed

+140
-23
lines changed

5 files changed

+140
-23
lines changed

Lib/test/test_regrtest.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -820,8 +820,6 @@ def test_fromfile(self):
820820
output = self.run_tests('--fromfile', filename)
821821
self.check_executed_tests(output, tests)
822822

823-
# TODO: RUSTPYTHON
824-
@unittest.expectedFailure
825823
def test_interrupted(self):
826824
code = TEST_INTERRUPTED
827825
test = self.create_test('sigint', code=code)
@@ -839,8 +837,6 @@ def test_slowest(self):
839837
% (self.TESTNAME_REGEX, len(tests)))
840838
self.check_line(output, regex)
841839

842-
# TODO: RUSTPYTHON
843-
@unittest.expectedFailure
844840
def test_slowest_interrupted(self):
845841
# Issue #25373: test --slowest with an interrupted test
846842
code = TEST_INTERRUPTED

Lib/test/test_signal.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ def trivial_signal_handler(self, *args):
8282
def create_handler_with_partial(self, argument):
8383
return functools.partial(self.trivial_signal_handler, argument)
8484

85-
# TODO: RUSTPYTHON
86-
@unittest.expectedFailure
8785
def test_out_of_range_signal_number_raises_error(self):
8886
self.assertRaises(ValueError, signal.getsignal, 4242)
8987

@@ -126,8 +124,6 @@ def __repr__(self):
126124
self.assertEqual(signal.getsignal(signal.SIGHUP), hup)
127125
self.assertEqual(0, argument.repr_count)
128126

129-
# TODO: RUSTPYTHON
130-
@unittest.expectedFailure
131127
def test_strsignal(self):
132128
self.assertIn("Interrupt", signal.strsignal(signal.SIGINT))
133129
self.assertIn("Terminated", signal.strsignal(signal.SIGTERM))
@@ -141,8 +137,6 @@ def test_interprocess_signal(self):
141137
script = os.path.join(dirname, 'signalinterproctester.py')
142138
assert_python_ok(script)
143139

144-
# TODO: RUSTPYTHON
145-
@unittest.expectedFailure
146140
@unittest.skipUnless(
147141
hasattr(signal, "valid_signals"),
148142
"requires signal.valid_signals"
@@ -190,8 +184,6 @@ def test_keyboard_interrupt_exit_code(self):
190184
@unittest.skipUnless(sys.platform == "win32", "Windows specific")
191185
class WindowsSignalTests(unittest.TestCase):
192186

193-
# TODO: RUSTPYTHON
194-
@unittest.expectedFailure
195187
def test_valid_signals(self):
196188
s = signal.valid_signals()
197189
self.assertIsInstance(s, set)
@@ -251,13 +243,11 @@ def test_invalid_call(self):
251243
with self.assertRaises(TypeError):
252244
signal.set_wakeup_fd(signal.SIGINT, False)
253245

254-
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
255246
def test_invalid_fd(self):
256247
fd = os_helper.make_bad_fd()
257248
self.assertRaises((ValueError, OSError),
258249
signal.set_wakeup_fd, fd)
259250

260-
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
261251
@unittest.skipUnless(support.has_socket_support, "needs working sockets.")
262252
def test_invalid_socket(self):
263253
sock = socket.socket()
@@ -266,7 +256,6 @@ def test_invalid_socket(self):
266256
self.assertRaises((ValueError, OSError),
267257
signal.set_wakeup_fd, fd)
268258

269-
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
270259
# Emscripten does not support fstat on pipes yet.
271260
# https://github.com/emscripten-core/emscripten/issues/16414
272261
@unittest.skipIf(support.is_emscripten, "Emscripten cannot fstat pipes.")
@@ -288,7 +277,6 @@ def test_set_wakeup_fd_result(self):
288277
self.assertEqual(signal.set_wakeup_fd(-1), w2)
289278
self.assertEqual(signal.set_wakeup_fd(-1), -1)
290279

291-
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
292280
@unittest.skipIf(support.is_emscripten, "Emscripten cannot fstat pipes.")
293281
@unittest.skipUnless(support.has_socket_support, "needs working sockets.")
294282
def test_set_wakeup_fd_socket_result(self):
@@ -1440,8 +1428,6 @@ def cycle_handlers():
14401428

14411429
class RaiseSignalTest(unittest.TestCase):
14421430

1443-
# TODO: RUSTPYTHON
1444-
@unittest.expectedFailure
14451431
def test_sigint(self):
14461432
with self.assertRaises(KeyboardInterrupt):
14471433
signal.raise_signal(signal.SIGINT)
@@ -1460,8 +1446,6 @@ def test_invalid_argument(self):
14601446
else:
14611447
raise
14621448

1463-
# TODO: RUSTPYTHON
1464-
@unittest.expectedFailure
14651449
def test_handler(self):
14661450
is_ok = False
14671451
def handler(a, b):

Lib/test/test_strftime.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ class Y1900Tests(unittest.TestCase):
184184
a date before 1900 is passed with a format string containing "%y"
185185
"""
186186

187-
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
188187
def test_y_before_1900(self):
189188
# Issue #13674, #19634
190189
t = (1899, 1, 1, 0, 0, 0, 0, 0, 0)

crates/vm/src/stdlib/signal.rs

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ pub(crate) mod _signal {
228228
}
229229

230230
#[pyfunction]
231-
fn set_wakeup_fd(args: SetWakeupFdArgs, vm: &VirtualMachine) -> PyResult<WakeupFdRaw> {
231+
fn set_wakeup_fd(args: SetWakeupFdArgs, vm: &VirtualMachine) -> PyResult<i64> {
232232
// TODO: implement warn_on_full_buffer
233233
let _ = args.warn_on_full_buffer;
234234
#[cfg(windows)]
@@ -264,6 +264,15 @@ pub(crate) mod _signal {
264264
if err.raw_os_error() != Some(WinSock::WSAENOTSOCK) {
265265
return Err(err.into_pyexception(vm));
266266
}
267+
// Validate that fd is a valid file descriptor using fstat
268+
// First check if SOCKET can be safely cast to i32 (file descriptor)
269+
let fd_i32 =
270+
i32::try_from(fd).map_err(|_| vm.new_value_error("invalid fd".to_owned()))?;
271+
// Verify the fd is valid by trying to fstat it
272+
let borrowed_fd =
273+
unsafe { crate::common::crt_fd::Borrowed::try_borrow_raw(fd_i32) }
274+
.map_err(|e| e.into_pyexception(vm))?;
275+
crate::common::fileutils::fstat(borrowed_fd).map_err(|e| e.into_pyexception(vm))?;
267276
}
268277
is_socket
269278
} else {
@@ -287,7 +296,18 @@ pub(crate) mod _signal {
287296
#[cfg(windows)]
288297
WAKEUP_IS_SOCKET.store(is_socket, Ordering::Relaxed);
289298

290-
Ok(old_fd)
299+
#[cfg(windows)]
300+
{
301+
if old_fd == INVALID_WAKEUP {
302+
Ok(-1)
303+
} else {
304+
Ok(old_fd as i64)
305+
}
306+
}
307+
#[cfg(not(windows))]
308+
{
309+
Ok(old_fd as i64)
310+
}
291311
}
292312

293313
#[cfg(all(unix, not(target_os = "redox")))]
@@ -302,6 +322,116 @@ pub(crate) mod _signal {
302322
}
303323
}
304324

325+
/// CPython: signal_raise_signal (signalmodule.c)
326+
#[cfg(any(unix, windows))]
327+
#[pyfunction]
328+
fn raise_signal(signalnum: i32, vm: &VirtualMachine) -> PyResult<()> {
329+
signal::assert_in_range(signalnum, vm)?;
330+
331+
// On Windows, only certain signals are supported
332+
#[cfg(windows)]
333+
{
334+
use crate::convert::IntoPyException;
335+
// Windows supports: SIGINT(2), SIGILL(4), SIGFPE(8), SIGSEGV(11), SIGTERM(15), SIGABRT(22)
336+
const VALID_SIGNALS: &[i32] = &[
337+
libc::SIGINT,
338+
libc::SIGILL,
339+
libc::SIGFPE,
340+
libc::SIGSEGV,
341+
libc::SIGTERM,
342+
libc::SIGABRT,
343+
];
344+
if !VALID_SIGNALS.contains(&signalnum) {
345+
return Err(std::io::Error::from_raw_os_error(libc::EINVAL).into_pyexception(vm));
346+
}
347+
}
348+
349+
let res = unsafe { libc::raise(signalnum) };
350+
if res != 0 {
351+
return Err(vm.new_os_error(format!("raise_signal failed for signal {}", signalnum)));
352+
}
353+
354+
// Check if a signal was triggered and handle it
355+
signal::check_signals(vm)?;
356+
357+
Ok(())
358+
}
359+
360+
/// CPython: signal_strsignal (signalmodule.c)
361+
#[cfg(unix)]
362+
#[pyfunction]
363+
fn strsignal(signalnum: i32, vm: &VirtualMachine) -> PyResult<Option<String>> {
364+
if signalnum < 1 || signalnum >= signal::NSIG as i32 {
365+
return Err(vm.new_value_error(format!("signal number {} out of range", signalnum)));
366+
}
367+
let s = unsafe { libc::strsignal(signalnum) };
368+
if s.is_null() {
369+
Ok(None)
370+
} else {
371+
let cstr = unsafe { std::ffi::CStr::from_ptr(s) };
372+
Ok(Some(cstr.to_string_lossy().into_owned()))
373+
}
374+
}
375+
376+
#[cfg(windows)]
377+
#[pyfunction]
378+
fn strsignal(signalnum: i32, vm: &VirtualMachine) -> PyResult<Option<String>> {
379+
if signalnum < 1 || signalnum >= signal::NSIG as i32 {
380+
return Err(vm.new_value_error(format!("signal number {} out of range", signalnum)));
381+
}
382+
// Windows doesn't have strsignal(), provide our own mapping
383+
let name = match signalnum {
384+
libc::SIGINT => "Interrupt",
385+
libc::SIGILL => "Illegal instruction",
386+
libc::SIGFPE => "Floating-point exception",
387+
libc::SIGSEGV => "Segmentation fault",
388+
libc::SIGTERM => "Terminated",
389+
libc::SIGABRT => "Aborted",
390+
_ => return Ok(None),
391+
};
392+
Ok(Some(name.to_owned()))
393+
}
394+
395+
/// CPython: signal_valid_signals (signalmodule.c)
396+
#[pyfunction]
397+
fn valid_signals(vm: &VirtualMachine) -> PyResult {
398+
use crate::PyPayload;
399+
use crate::builtins::PySet;
400+
let set = PySet::default().into_ref(&vm.ctx);
401+
#[cfg(unix)]
402+
{
403+
// On Unix, most signals 1..NSIG are valid
404+
for signum in 1..signal::NSIG {
405+
// Skip signals that cannot be caught
406+
#[cfg(not(target_os = "wasi"))]
407+
if signum == libc::SIGKILL as usize || signum == libc::SIGSTOP as usize {
408+
continue;
409+
}
410+
set.add(vm.ctx.new_int(signum as i32).into(), vm)?;
411+
}
412+
}
413+
#[cfg(windows)]
414+
{
415+
// Windows only supports a limited set of signals
416+
for &signum in &[
417+
libc::SIGINT,
418+
libc::SIGILL,
419+
libc::SIGFPE,
420+
libc::SIGSEGV,
421+
libc::SIGTERM,
422+
libc::SIGABRT,
423+
] {
424+
set.add(vm.ctx.new_int(signum).into(), vm)?;
425+
}
426+
}
427+
#[cfg(not(any(unix, windows)))]
428+
{
429+
// Empty set for platforms without signal support (e.g., WASM)
430+
let _ = &set;
431+
}
432+
Ok(set.into())
433+
}
434+
305435
#[cfg(any(unix, windows))]
306436
pub extern "C" fn run_signal(signum: i32) {
307437
signal::TRIGGERS[signum as usize].store(true, Ordering::Relaxed);

crates/vm/src/stdlib/time.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,14 @@ mod decl {
360360
use std::fmt::Write;
361361

362362
let instant = t.naive_or_local(vm)?;
363+
364+
// On Windows/AIX/Solaris, %y format with year < 1900 is not supported
365+
#[cfg(any(windows, target_os = "aix", target_os = "solaris"))]
366+
if instant.year() < 1900 && format.as_str().contains("%y") {
367+
let msg = "format %y requires year >= 1900 on Windows";
368+
return Err(vm.new_value_error(msg.to_owned()));
369+
}
370+
363371
let mut formatted_time = String::new();
364372

365373
/*

0 commit comments

Comments
 (0)