Replace crash guard with SIGUSR1 unlock and crash logging
Remove sys.excepthook that unlocked on crash — this violated ext-session-lock-v1 security model where the compositor must keep the screen locked if the client dies (per protocol spec and hyprlock reference). Now: crashes are logged but session stays locked. SIGUSR1 handler added for external recovery (e.g. wrapper script).
This commit is contained in:
+27
-32
@@ -170,10 +170,10 @@ class TestDefensiveWindowCreation:
|
||||
@patch("moonlock.main.FingerprintListener")
|
||||
@patch("moonlock.main.Gdk.Display.get_default")
|
||||
@patch("moonlock.main.Gtk4SessionLock")
|
||||
def test_all_windows_fail_unlocks_session(
|
||||
def test_all_windows_fail_does_not_unlock_session(
|
||||
self, mock_session_lock, mock_display, mock_fp, mock_window_cls
|
||||
):
|
||||
"""If ALL windows fail to create, session should be unlocked to prevent lockout."""
|
||||
"""If ALL windows fail, session stays locked (compositor policy)."""
|
||||
MoonlockApp, _ = _import_main()
|
||||
app = MoonlockApp.__new__(MoonlockApp)
|
||||
app._config = MagicMock()
|
||||
@@ -195,48 +195,43 @@ class TestDefensiveWindowCreation:
|
||||
with patch("moonlock.main.logger"):
|
||||
app._activate_with_session_lock()
|
||||
|
||||
# Session should have been unlocked to prevent permanent lockout
|
||||
lock_instance.unlock.assert_called_once()
|
||||
# Session must NOT be unlocked — compositor keeps screen locked
|
||||
lock_instance.unlock.assert_not_called()
|
||||
|
||||
|
||||
class TestCrashGuard:
|
||||
"""Tests for the global exception handler that prevents lockout on crash."""
|
||||
class TestSignalHandlers:
|
||||
"""Tests for signal handlers and excepthook behavior."""
|
||||
|
||||
def test_crash_guard_unlocks_session_on_unhandled_exception(self):
|
||||
"""sys.excepthook should unlock the session when an exception reaches it."""
|
||||
def test_sigusr1_triggers_unlock(self):
|
||||
"""SIGUSR1 should schedule an unlock via GLib.idle_add."""
|
||||
MoonlockApp, _ = _import_main()
|
||||
from moonlock.main import _install_crash_guard
|
||||
from moonlock.main import _install_signal_handlers
|
||||
import signal
|
||||
|
||||
app = MoonlockApp.__new__(MoonlockApp)
|
||||
app._lock_instance = MagicMock()
|
||||
|
||||
_install_signal_handlers(app)
|
||||
|
||||
handler = signal.getsignal(signal.SIGUSR1)
|
||||
assert handler is not signal.SIG_DFL
|
||||
|
||||
def test_excepthook_does_not_unlock(self):
|
||||
"""Unhandled exceptions must NOT unlock the session."""
|
||||
MoonlockApp, _ = _import_main()
|
||||
from moonlock.main import _install_signal_handlers
|
||||
|
||||
app = MoonlockApp.__new__(MoonlockApp)
|
||||
app._lock_instance = MagicMock()
|
||||
|
||||
original_hook = sys.excepthook
|
||||
_install_crash_guard(app)
|
||||
|
||||
try:
|
||||
# Simulate an unhandled exception reaching the hook
|
||||
with patch("moonlock.main.logger"):
|
||||
sys.excepthook(RuntimeError, RuntimeError("segfault"), None)
|
||||
|
||||
app._lock_instance.unlock.assert_called_once()
|
||||
finally:
|
||||
sys.excepthook = original_hook
|
||||
|
||||
def test_crash_guard_survives_unlock_failure(self):
|
||||
"""Crash guard should not raise even if unlock() itself fails."""
|
||||
MoonlockApp, _ = _import_main()
|
||||
from moonlock.main import _install_crash_guard
|
||||
|
||||
app = MoonlockApp.__new__(MoonlockApp)
|
||||
app._lock_instance = MagicMock()
|
||||
app._lock_instance.unlock.side_effect = Exception("protocol error")
|
||||
|
||||
original_hook = sys.excepthook
|
||||
_install_crash_guard(app)
|
||||
_install_signal_handlers(app)
|
||||
|
||||
try:
|
||||
with patch("moonlock.main.logger"):
|
||||
# Should not raise despite unlock failure
|
||||
sys.excepthook(RuntimeError, RuntimeError("crash"), None)
|
||||
|
||||
# Must NOT have called unlock
|
||||
app._lock_instance.unlock.assert_not_called()
|
||||
finally:
|
||||
sys.excepthook = original_hook
|
||||
|
||||
Reference in New Issue
Block a user