Skip to content

Commit 3d437ee

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 fe3ba33 commit 3d437ee

3 files changed

Lines changed: 201 additions & 200 deletions

File tree

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

‎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)