77 lines
2.4 KiB
Python
77 lines
2.4 KiB
Python
"""Web run cancellation: broker fast path and shared-DB fallback."""
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
from contextlib import contextmanager
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock, patch
|
|
from uuid import uuid4
|
|
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
|
|
|
|
class RunCancelCheckTests(unittest.TestCase):
|
|
def test_broker_signal_stops_without_db_poll(self) -> None:
|
|
from web import runs
|
|
|
|
tid = uuid4()
|
|
broker = MagicMock()
|
|
broker.is_cancelled.return_value = True
|
|
with (
|
|
patch.object(runs, "broker", broker),
|
|
patch.object(runs, "session_scope") as scope,
|
|
):
|
|
check = runs._RunCancelCheck(tid)
|
|
self.assertTrue(check())
|
|
|
|
scope.assert_not_called()
|
|
broker.is_cancelled.assert_called_once_with(tid)
|
|
|
|
def test_db_cancelling_falls_back_across_processes(self) -> None:
|
|
from web import runs
|
|
|
|
tid = uuid4()
|
|
broker = MagicMock()
|
|
broker.is_cancelled.return_value = False
|
|
result = MagicMock()
|
|
result.scalar_one_or_none.return_value = "cancelling"
|
|
session = SimpleNamespace(execute=MagicMock(return_value=result))
|
|
|
|
@contextmanager
|
|
def fake_scope():
|
|
yield session
|
|
|
|
with (
|
|
patch.object(runs, "broker", broker),
|
|
patch.object(runs, "session_scope", fake_scope),
|
|
patch.object(runs.time, "monotonic", side_effect=[10.0, 10.5, 11.0]),
|
|
):
|
|
check = runs._RunCancelCheck(tid)
|
|
self.assertFalse(check(), "一秒节流窗口内不查询 DB")
|
|
self.assertTrue(check(), "共享 DB 的 cancelling 应成为可靠兜底")
|
|
|
|
session.execute.assert_called_once()
|
|
|
|
def test_db_poll_failure_does_not_abort_run(self) -> None:
|
|
from web import runs
|
|
|
|
tid = uuid4()
|
|
broker = MagicMock()
|
|
broker.is_cancelled.return_value = False
|
|
|
|
@contextmanager
|
|
def broken_scope():
|
|
raise SQLAlchemyError("db unavailable")
|
|
yield
|
|
|
|
with (
|
|
patch.object(runs, "broker", broker),
|
|
patch.object(runs, "session_scope", broken_scope),
|
|
patch.object(runs.time, "monotonic", side_effect=[20.0, 21.0]),
|
|
):
|
|
self.assertFalse(runs._RunCancelCheck(tid)())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|