Skip to content

Commit 9faa197

Browse files
committed
Force exit when per-test timeout watchdog fires
Replace the async exception injection/TextTestRunner override with a simple timeout handler that prints a clear message and terminates the process via os._exit(1), ensuring hung tests reliably stop even when the main thread is blocked.
1 parent 77384ac commit 9faa197

File tree

1 file changed

+8
-45
lines changed

1 file changed

+8
-45
lines changed

tests/base.py

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import threading
99
import time
1010
import unittest
11-
from ctypes import c_long, py_object, pythonapi
1211
from datetime import datetime
1312
from functools import partial
1413
from unittest.mock import patch
@@ -31,49 +30,13 @@
3130
"This plugin does not support raise()",
3231
]
3332

34-
_TIMEOUT_CONTEXT = {
35-
"testId": None,
36-
"timeout": None,
37-
}
3833

39-
40-
class TestTimeoutError(BaseException):
41-
pass
42-
43-
44-
def _raiseAsyncException(threadId: int, exceptionType: type):
45-
result = pythonapi.PyThreadState_SetAsyncExc(c_long(threadId), py_object(exceptionType))
46-
if result > 1:
47-
pythonapi.PyThreadState_SetAsyncExc(c_long(threadId), py_object(None))
48-
49-
50-
def _triggerTestTimeout(mainThreadId: int, testId: str, timeoutSeconds: float):
51-
_TIMEOUT_CONTEXT["testId"] = testId
52-
_TIMEOUT_CONTEXT["timeout"] = timeoutSeconds
53-
_raiseAsyncException(mainThreadId, TestTimeoutError)
54-
55-
56-
class TimeoutTextTestResult(unittest.TextTestResult):
57-
def addError(self, test, err):
58-
errorType, errorValue, errorTraceback = err
59-
if issubclass(errorType, TestTimeoutError):
60-
testId = _TIMEOUT_CONTEXT.get("testId") or test.id()
61-
timeoutSeconds = _TIMEOUT_CONTEXT.get("timeout")
62-
timeoutMessage = f"Test timed out after {timeoutSeconds}s: {testId}"
63-
super().addFailure(
64-
test,
65-
(AssertionError, AssertionError(timeoutMessage), errorTraceback)
66-
)
67-
self.stop()
68-
return
69-
super().addError(test, err)
70-
71-
72-
class TimeoutTextTestRunner(unittest.TextTestRunner):
73-
resultclass = TimeoutTextTestResult
74-
75-
76-
unittest.TextTestRunner = TimeoutTextTestRunner
34+
def _timeoutHandler(testId: str, timeoutSeconds: float):
35+
"""Force exit when test timeout fires."""
36+
msg = f"\nTEST TIMEOUT: {testId} exceeded {timeoutSeconds}s\n"
37+
sys.stderr.write(msg)
38+
sys.stderr.flush()
39+
os._exit(1)
7740

7841

7942
def _qt_message_handler(type: QtMsgType, context: QMessageLogContext, msg: str):
@@ -199,8 +162,8 @@ def run(self, result=None):
199162

200163
watchdog = threading.Timer(
201164
timeoutSeconds,
202-
_triggerTestTimeout,
203-
args=(threading.main_thread().ident, self.id(), timeoutSeconds),
165+
_timeoutHandler,
166+
args=(self.id(), timeoutSeconds),
204167
)
205168
watchdog.daemon = True
206169
watchdog.start()

0 commit comments

Comments
 (0)