Skip to content

Commit 4669cb0

Browse files
committed
Separate faulthandler regression snippets
Move watchdog timeout and signal-registration probes into stdlib_faulthandler.py, preserving their assertions and guards. Keep the time and signal snippets focused on their own modules. Assisted-by: OpenAI Codex:model version not exposed
1 parent 285b621 commit 4669cb0

4 files changed

Lines changed: 200 additions & 202 deletions

File tree

‎crates/stdlib/src/faulthandler.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,7 @@ mod decl {
11141114
cancel: false,
11151115
fd: 2,
11161116
timeout_us: 60_000_000,
1117+
max_threads: 100,
11171118
repeat: false,
11181119
exit: false,
11191120
header: String::new(),

‎extra_tests/snippets/stdlib_faulthandler.py‎

Lines changed: 199 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,210 @@
1+
import faulthandler
2+
import signal
3+
import struct
14
import sys
25
import time
36

47
from testutils import assert_raises
58

9+
# Faulthandler watchdogs use the same signed-nanosecond conversion as sleep.
10+
11+
12+
def rejected_timeout(expected, message, timeout, **kwargs):
13+
try:
14+
with assert_raises(expected) as caught:
15+
faulthandler.dump_traceback_later(timeout, **kwargs)
16+
assert str(caught.exception) == message
17+
finally:
18+
faulthandler.cancel_dump_traceback_later()
19+
20+
21+
# Keyword binding must reach timeout validation before inspecting the file.
22+
try:
23+
with assert_raises(ValueError) as caught:
24+
faulthandler.dump_traceback_later(timeout=0, file=-1)
25+
assert str(caught.exception) == "timeout must be greater than 0"
26+
assert_raises(TypeError, faulthandler.dump_traceback_later)
27+
assert_raises(TypeError, faulthandler.dump_traceback_later, 60, timeout=60)
28+
assert_raises(TypeError, faulthandler.dump_traceback_later, 60, False, -1, False, 1)
29+
finally:
30+
faulthandler.cancel_dump_traceback_later()
31+
32+
33+
# All probes below reject the file before arming a watchdog, including tiny
34+
# positive durations that could expire before a subsequent cancellation.
35+
file_error = "file is not a valid file descriptor"
36+
for timeout in (1e-20, 5e-324, 1e-9):
37+
rejected_timeout(ValueError, file_error, timeout, file=-1)
38+
for timeout in (0, -0.0, -1, -1e-20, -5e-324):
39+
rejected_timeout(ValueError, "timeout must be greater than 0", timeout, file=-1)
40+
rejected_timeout(ValueError, "Invalid value NaN (not a number)", float("nan"), file=-1)
41+
for timeout in (float("inf"), -float("inf"), 10**100, -(10**100), 9223372037):
42+
rejected_timeout(
43+
OverflowError, "timestamp out of range for C PyTime_t", timeout, file=-1
44+
)
45+
for timeout in (None, "60", object()):
46+
rejected_timeout(
47+
TypeError,
48+
"must be real number, not " + type(timeout).__name__,
49+
timeout,
50+
file=-1,
51+
)
52+
53+
54+
calls = []
55+
56+
57+
class TruthValue:
58+
def __init__(self, name, error=None):
59+
self.name = name
60+
self.error = error
61+
62+
def __bool__(self):
63+
calls.append(self.name)
64+
if self.error is not None:
65+
raise self.error
66+
return False
67+
68+
69+
class IndexedTimeout:
70+
def __index__(self):
71+
calls.append("index")
72+
return 60
73+
74+
def __float__(self):
75+
raise AssertionError("timeout must prefer __index__")
76+
77+
78+
class FloatTimeout:
79+
def __float__(self):
80+
calls.append("float")
81+
return 60.0
82+
83+
84+
class InvalidFile:
85+
def fileno(self):
86+
calls.append("fileno")
87+
return -1
88+
89+
def flush(self):
90+
raise AssertionError("invalid descriptor must not be flushed")
91+
92+
93+
for timeout, conversion in ((IndexedTimeout(), "index"), (FloatTimeout(), "float")):
94+
calls.clear()
95+
rejected_timeout(
96+
RuntimeError,
97+
"file.fileno() is not a valid file descriptor",
98+
timeout,
99+
repeat=TruthValue("repeat"),
100+
exit=TruthValue("exit"),
101+
file=InvalidFile(),
102+
)
103+
assert calls == ["repeat", "exit", conversion, "fileno"]
104+
105+
for failing_argument in ("repeat", "exit"):
106+
calls.clear()
107+
failure = RuntimeError(failing_argument + " failed")
108+
rejected_timeout(
109+
RuntimeError,
110+
str(failure),
111+
None,
112+
repeat=TruthValue("repeat", failure if failing_argument == "repeat" else None),
113+
exit=TruthValue("exit", failure if failing_argument == "exit" else None),
114+
file=InvalidFile(),
115+
)
116+
assert calls == (["repeat"] if failing_argument == "repeat" else ["repeat", "exit"])
117+
118+
for timeout, expected, message in (
119+
(0, ValueError, "timeout must be greater than 0"),
120+
(float("nan"), ValueError, "Invalid value NaN (not a number)"),
121+
(float("inf"), OverflowError, "timestamp out of range for C PyTime_t"),
122+
):
123+
calls.clear()
124+
rejected_timeout(
125+
expected,
126+
message,
127+
timeout,
128+
repeat=TruthValue("repeat"),
129+
exit=TruthValue("exit"),
130+
file=InvalidFile(),
131+
)
132+
assert calls == ["repeat", "exit"]
133+
134+
135+
class FailingIndex:
136+
def __index__(self):
137+
raise RuntimeError("index failed")
138+
139+
def __float__(self):
140+
raise AssertionError("failed index must not fall back to float")
141+
142+
143+
class FailingFloat:
144+
def __float__(self):
145+
raise RuntimeError("float failed")
146+
147+
148+
class InvalidIndex:
149+
def __index__(self):
150+
return 60.0
151+
152+
def __float__(self):
153+
raise AssertionError("invalid index must not fall back to float")
154+
155+
156+
rejected_timeout(RuntimeError, "index failed", FailingIndex(), file=-1)
157+
rejected_timeout(RuntimeError, "float failed", FailingFloat(), file=-1)
158+
try:
159+
assert_raises(TypeError, faulthandler.dump_traceback_later, InvalidIndex(), file=-1)
160+
finally:
161+
faulthandler.cancel_dump_traceback_later()
162+
163+
164+
class FloatSubclass(float):
165+
def __float__(self):
166+
raise AssertionError("float subclass override must be bypassed")
167+
168+
169+
class IndexedFloatSubclass(FloatSubclass):
170+
def __index__(self):
171+
return -1
172+
173+
174+
rejected_timeout(ValueError, file_error, FloatSubclass(60), file=-1)
175+
rejected_timeout(
176+
ValueError, "timeout must be greater than 0", IndexedFloatSubclass(60), file=-1
177+
)
178+
179+
# The platform limit and the signed C long seconds limit precede file lookup.
180+
# This is below the signed-nanosecond limit on every tested platform.
181+
if sys.platform == "win32":
182+
rejected_timeout(OverflowError, "timeout value is too large", 4_294_968, file=-1)
183+
elif struct.calcsize("l") == 4:
184+
rejected_timeout(OverflowError, "timeout value is too large", 2**31, file=-1)
185+
else:
186+
rejected_timeout(ValueError, file_error, 9223372036, file=-1)
187+
188+
# Exercise the successful keyword path only with a long timeout, immediately
189+
# cancelled. Wasm targets do not provide the native watchdog thread.
190+
if sys.platform not in ("wasi", "emscripten"):
191+
try:
192+
assert faulthandler.dump_traceback_later(timeout=60) is None
193+
finally:
194+
faulthandler.cancel_dump_traceback_later()
195+
196+
197+
# An invalid file proves register's signum keyword binds without installing
198+
# any signal handler. This API is absent on Windows.
199+
if hasattr(faulthandler, "register") and hasattr(signal, "SIGUSR1"):
200+
with assert_raises(ValueError) as caught:
201+
faulthandler.register(signum=signal.SIGUSR1, file=-1)
202+
assert str(caught.exception) == "file is not a valid file descriptor"
203+
204+
6205
# The watchdog's thread cap is keyword-only and uses Py_ssize_t conversion.
7206
# Its output must distinguish an exact cap from a truncated thread dump.
8207
if sys.platform not in ("wasi", "emscripten"):
9-
import faulthandler
10-
11208
for cap in (None, 1.5):
12209
assert_raises(TypeError, faulthandler.dump_traceback_later, 60, max_threads=cap)
13210
for cap in (sys.maxsize + 1, -sys.maxsize - 2):

‎extra_tests/snippets/stdlib_signal.py‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -346,13 +346,3 @@ def write_input(data):
346346

347347

348348
check_native_stdin_eintr()
349-
350-
351-
import faulthandler
352-
353-
# An invalid file proves register's signum keyword binds without installing
354-
# any signal handler. This API is absent on Windows.
355-
if hasattr(faulthandler, "register") and hasattr(signal, "SIGUSR1"):
356-
with assert_raises(ValueError) as caught:
357-
faulthandler.register(signum=signal.SIGUSR1, file=-1)
358-
assert str(caught.exception) == "file is not a valid file descriptor"

0 commit comments

Comments
 (0)