chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-09-04: - update .custodian-brief.md for rein-aharness Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a06ba0-10aa-7ea0-b20a-4f3fac39efe9
This commit is contained in:
parent
7641fcde40
commit
f01b765668
20 changed files with 1027 additions and 165 deletions
113
tests/test_execution_cancel.py
Normal file
113
tests/test_execution_cancel.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from rein_aharness.execution_cancel import (
|
||||
ExecutionCancel,
|
||||
ExecutionCancelled,
|
||||
active_cancel,
|
||||
using_cancel,
|
||||
)
|
||||
|
||||
|
||||
def test_cancel_is_one_shot_and_bounded() -> None:
|
||||
cancel = ExecutionCancel()
|
||||
first = cancel.cancel("lease-loss")
|
||||
second = cancel.cancel("timeout")
|
||||
|
||||
assert first == second == "lease-loss"
|
||||
assert cancel.cancelled is True
|
||||
assert cancel.reason == "lease-loss"
|
||||
assert cancel.evidence() == {"cancelled": "true", "reason": "lease-loss"}
|
||||
|
||||
|
||||
def test_unknown_reason_is_normalized() -> None:
|
||||
cancel = ExecutionCancel()
|
||||
cancel.cancel("provider exploded with secrets")
|
||||
assert cancel.reason == "cancelled"
|
||||
|
||||
|
||||
def test_check_raises_only_after_cancel() -> None:
|
||||
cancel = ExecutionCancel()
|
||||
cancel.check()
|
||||
cancel.cancel("timeout")
|
||||
with pytest.raises(ExecutionCancelled, match="timeout") as excinfo:
|
||||
cancel.check()
|
||||
assert excinfo.value.reason == "timeout"
|
||||
|
||||
|
||||
def test_register_process_is_killed_on_cancel() -> None:
|
||||
proc = MagicMock()
|
||||
proc.poll.return_value = None
|
||||
cancel = ExecutionCancel()
|
||||
cancel.register_process(proc)
|
||||
cancel.cancel("lease-loss")
|
||||
proc.kill.assert_called_once()
|
||||
|
||||
|
||||
def test_already_cancelled_register_kills_immediately() -> None:
|
||||
proc = MagicMock()
|
||||
proc.poll.return_value = None
|
||||
cancel = ExecutionCancel()
|
||||
cancel.cancel("signal")
|
||||
cancel.register_process(proc)
|
||||
proc.kill.assert_called_once()
|
||||
|
||||
|
||||
def test_exited_process_is_not_killed() -> None:
|
||||
proc = MagicMock()
|
||||
proc.poll.return_value = 0
|
||||
cancel = ExecutionCancel()
|
||||
cancel.register_process(proc)
|
||||
cancel.cancel("timeout")
|
||||
proc.kill.assert_not_called()
|
||||
|
||||
|
||||
def test_stop_callback_failure_does_not_block_cancel() -> None:
|
||||
cancel = ExecutionCancel()
|
||||
cancel.register_stop(lambda: (_ for _ in ()).throw(RuntimeError("stop failed")))
|
||||
assert cancel.cancel("signal") == "signal"
|
||||
|
||||
|
||||
def test_concurrent_cancel_invokes_stop_once() -> None:
|
||||
seen = []
|
||||
cancel = ExecutionCancel()
|
||||
cancel.register_stop(lambda: seen.append("stop"))
|
||||
threads = [
|
||||
threading.Thread(target=cancel.cancel, args=("lease-loss",)) for _ in range(8)
|
||||
]
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
assert seen == ["stop"]
|
||||
|
||||
|
||||
def test_using_cancel_exposes_active_cancel() -> None:
|
||||
cancel = ExecutionCancel()
|
||||
assert active_cancel() is None
|
||||
with using_cancel(cancel):
|
||||
assert active_cancel() is cancel
|
||||
cancel.cancel("timeout")
|
||||
assert active_cancel() is cancel
|
||||
assert active_cancel() is None
|
||||
|
||||
|
||||
def test_wait_returns_after_cancel() -> None:
|
||||
cancel = ExecutionCancel()
|
||||
started = threading.Event()
|
||||
|
||||
def _cancel_soon() -> None:
|
||||
started.wait(timeout=1)
|
||||
time.sleep(0.01)
|
||||
cancel.cancel("signal")
|
||||
|
||||
thread = threading.Thread(target=_cancel_soon)
|
||||
thread.start()
|
||||
started.set()
|
||||
assert cancel.wait(timeout=1) is True
|
||||
thread.join()
|
||||
Loading…
Add table
Add a link
Reference in a new issue