Files
learningcircuit--local-deep…/tests/web/test_thread_excepthook.py
T
wehub-resource-sync 6e7352b6f7
Create Release / version-check (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / test-gate (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Failing after 0s
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Version Auto-Bump / version-bump (push) Failing after 0s
Backwards Compatibility / Verify Encryption Constants (push) Failing after 1s
Backwards Compatibility / PyPI Version Compatibility (push) Failing after 1s
Backwards Compatibility / Database Migration Tests (push) Failing after 0s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
CodeQL Advanced / Analyze (python) (push) Failing after 1s
Docker Tests (Consolidated) / detect-changes (push) Failing after 1s
Docker Tests (Consolidated) / Build Test Image (push) Failing after 1s
Sync repo labels / sync-labels (push) Failing after 0s
MCP Server Tests / MCP Server Tests (push) Failing after 1s
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been skipped
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:25:48 +08:00

86 lines
2.9 KiB
Python

"""Tests for the daemon-thread excepthook.
Covers:
- _install_thread_excepthook logs uncaught exceptions from daemon threads.
- _perform_post_login_tasks wraps its body so an otherwise-uncaught exception
from inside the `with` context managers is logged instead of disappearing.
"""
import threading
from unittest.mock import MagicMock, patch
def test_install_thread_excepthook_logs_uncaught_exception():
"""After installing the hook, an uncaught exception on a daemon thread
must be logged at ERROR level including the exception type and the
thread name. Without this, silent crashes in the queue processor
or APScheduler jobs are the mechanism by which the login path
gradually starves.
"""
from local_deep_research.web.app import _install_thread_excepthook
previous = threading.excepthook
_install_thread_excepthook()
try:
with patch("local_deep_research.web.app.logger") as mock_logger:
def raiser():
raise RuntimeError("boom from daemon")
t = threading.Thread(target=raiser, name="test-daemon", daemon=True)
t.start()
t.join(timeout=2.0)
assert not t.is_alive()
assert mock_logger.error.called, "excepthook did not log"
logged = mock_logger.error.call_args[0][0]
assert "test-daemon" in logged
assert "RuntimeError" in logged
assert "boom from daemon" in logged
finally:
threading.excepthook = previous
def test_perform_post_login_tasks_catches_outer_exceptions():
"""If the outer structure of _perform_post_login_tasks itself raises
(for example from inside a `with` context manager's __enter__) —
not inside a per-step try/except — the wrapper must log the
exception and not let the daemon thread die silently.
"""
from local_deep_research.web.auth import routes
with (
patch.object(
routes,
"_perform_post_login_tasks_body",
side_effect=RuntimeError("outer failure"),
),
patch.object(routes, "logger") as mock_logger,
):
# Must not raise.
routes._perform_post_login_tasks("alice", "pw")
assert mock_logger.exception.called
msg = mock_logger.exception.call_args[0][0]
assert "alice" in msg
assert "crashed" in msg.lower() or "post-login" in msg.lower()
def test_perform_post_login_tasks_body_runs_when_no_outer_error():
"""Positive path: when the body succeeds, the wrapper forwards to
it exactly once with the right args, and does not log any
crash.
"""
from local_deep_research.web.auth import routes
body = MagicMock()
with (
patch.object(routes, "_perform_post_login_tasks_body", body),
patch.object(routes, "logger") as mock_logger,
):
routes._perform_post_login_tasks("bob", "pw")
body.assert_called_once_with("bob", "pw")
mock_logger.exception.assert_not_called()