Spaces:
Configuration error
Configuration error
File size: 5,595 Bytes
9644d0b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | """Tests for crash_handler.py β verify the install() hooks actually kill
the process on unhandled exceptions, and the PX_HARD_CRASH=0 kill-switch
disables them.
Run: /run/media/julian/ML4/open-mythos_p2/venv_openmythos/bin/python tests/test_crash_handler.py
Each test launches a fresh subprocess (since the hooks affect the current
process and we can't easily undo them in-process). We assert on the
subprocess return code + stderr content.
"""
import os
import subprocess
import sys
import textwrap
_HERE = os.path.dirname(os.path.abspath(__file__))
_ROOT = os.path.dirname(_HERE)
_PYTHON = sys.executable
def _run_subprocess(script, env_extra=None, timeout=10):
"""Run ``script`` (Python code) in a fresh subprocess and return
(returncode, stderr). Inject sys.path so ``import crash_handler``
resolves to the project copy."""
full_env = os.environ.copy()
if env_extra:
full_env.update(env_extra)
full_env["PYTHONPATH"] = _ROOT + os.pathsep + full_env.get("PYTHONPATH", "")
r = subprocess.run(
[_PYTHON, "-c", script],
capture_output=True, text=True, timeout=timeout, env=full_env,
)
return r.returncode, r.stderr
# ββ Tests ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def test_sys_hook_kills_process():
"""sys.excepthook: raise RuntimeError β exit code 1 + CRASH_HANDLER banner."""
script = textwrap.dedent("""
import crash_handler
crash_handler.install()
raise RuntimeError("deliberate sys-hook test crash")
""")
rc, stderr = _run_subprocess(script)
assert rc == 1, f"expected exit 1, got {rc}; stderr={stderr!r}"
assert "CRASH_HANDLER" in stderr, f"missing banner in stderr={stderr!r}"
assert "deliberate sys-hook test crash" in stderr, stderr
assert "Traceback" in stderr, stderr
assert "sys.excepthook" in stderr, stderr
def test_threading_hook_kills_process():
"""threading.excepthook: Thread raises β process dies with banner."""
script = textwrap.dedent("""
import crash_handler
crash_handler.install()
import threading, time
def boom():
raise RuntimeError("deliberate threading-hook test crash")
t = threading.Thread(target=boom, name="test-boomer")
t.start()
# Main thread sleeps; the worker crash should fire threading.excepthook
# which calls os._exit(1) and tears down the whole process.
time.sleep(10)
""")
rc, stderr = _run_subprocess(script, timeout=15)
assert rc == 1, f"expected exit 1, got {rc}; stderr={stderr!r}"
assert "CRASH_HANDLER" in stderr, f"missing banner in stderr={stderr!r}"
assert "deliberate threading-hook test crash" in stderr, stderr
assert "threading.excepthook" in stderr, stderr
assert "test-boomer" in stderr, stderr
def test_env_flag_disables_hook():
"""PX_HARD_CRASH=0 β install() is no-op; unhandled exception propagates
normally (Python prints traceback, exit code != 0 but NOT 1 from us)."""
script = textwrap.dedent("""
import crash_handler
crash_handler.install()
raise RuntimeError("kill-switch test crash")
""")
rc, stderr = _run_subprocess(script, env_extra={"PX_HARD_CRASH": "0"})
# When hook is disabled, Python's default behavior prints the traceback
# and exits with code 1 (uncaught exception). The CRASH_HANDLER banner
# must NOT appear (that's the kill-switch assertion).
assert "CRASH_HANDLER" not in stderr, (
f"banner should be suppressed when PX_HARD_CRASH=0, got stderr={stderr!r}"
)
# The exception itself still propagates β that's the point of disabling
# the hook is NOT to hide bugs from the user, but to opt out of the
# forced process termination.
assert "kill-switch test crash" in stderr, stderr
# Default uncaught-exception exit code is 1 too, so rc check is the same.
assert rc == 1, f"expected exit 1 (Python default), got {rc}"
def test_keyboard_interrupt_not_caught():
"""KeyboardInterrupt must NOT trigger hard-crash β graceful Ctrl-C."""
script = textwrap.dedent("""
import crash_handler
crash_handler.install()
raise KeyboardInterrupt()
""")
rc, stderr = _run_subprocess(script)
# KeyboardInterrupt exits with 130 by default (128 + SIGINT=2).
# Our hook must NOT fire the CRASH_HANDLER banner.
assert "CRASH_HANDLER" not in stderr, (
f"banner should NOT appear for KeyboardInterrupt, got stderr={stderr!r}"
)
assert rc != 1, f"KeyboardInterrupt should not exit with 1, got {rc}"
# ββ Runner ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def _run_all():
tests = [v for k, v in sorted(globals().items())
if k.startswith("test_") and callable(v)]
passed = 0
failed = 0
for t in tests:
try:
t()
print(f"PASS {t.__name__}")
passed += 1
except AssertionError as e:
print(f"FAIL {t.__name__}: {e}")
failed += 1
except Exception as e: # noqa
print(f"ERROR {t.__name__}: {type(e).__name__}: {e}")
failed += 1
print(f"\n{passed} passed, {failed} failed")
return failed == 0
if __name__ == "__main__":
sys.exit(0 if _run_all() else 1) |