mirror of
https://github.com/usestrix/strix.git
synced 2026-10-10 03:28:11 +00:00
Merge cf1897281b into 55bc07991a
This commit is contained in:
commit
774c42da27
2 changed files with 60 additions and 0 deletions
|
|
@ -76,6 +76,7 @@ class GoTuiRuntime:
|
|||
self._output_syncs: set[asyncio.Task[None]] = set()
|
||||
self.model_verified = False
|
||||
self._setup_preflight: asyncio.Task[None] | None = None
|
||||
self._quit_task: asyncio.Task[None] | None = None
|
||||
self.controller = TuiController(
|
||||
args,
|
||||
live_view=self.live_view,
|
||||
|
|
@ -370,6 +371,21 @@ class GoTuiRuntime:
|
|||
await asyncio.sleep(0.5)
|
||||
|
||||
async def quit(self) -> None:
|
||||
"""Stop the scan once, however many callers ask for it.
|
||||
|
||||
Ctrl+Q quits twice: the sidecar sends ``app.quit`` and exits in the
|
||||
same batch, so the command handler and ``run``'s finally both land
|
||||
here. A second cancel while the first is tearing the sandbox down
|
||||
interrupts the docker delete and leaves the container running, so
|
||||
callers after the first wait on the quit already in flight.
|
||||
"""
|
||||
if self._quit_task is None:
|
||||
self._quit_task = asyncio.create_task(self._quit_once())
|
||||
# Shielded: awaiting a task hands it the waiter's own cancellation,
|
||||
# which is what this guard exists to keep out of the teardown.
|
||||
await asyncio.shield(self._quit_task)
|
||||
|
||||
async def _quit_once(self) -> None:
|
||||
self.controller.close_viewer()
|
||||
self.coordinator.mark_shutting_down()
|
||||
scan_task = self.scan_task
|
||||
|
|
|
|||
|
|
@ -877,6 +877,50 @@ async def test_scan_passes_max_turns_and_budget(monkeypatch: pytest.MonkeyPatch)
|
|||
assert runtime.controller.scan_state == "stopped"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_quitting_twice_lets_the_sandbox_teardown_finish(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Ctrl+Q quits twice: the app.quit command and run()'s finally."""
|
||||
runtime = GoTuiRuntime(args())
|
||||
runtime.scan_config = {"run_name": "test-run"}
|
||||
torn_down: list[str] = []
|
||||
scanning = asyncio.Event()
|
||||
tearing_down = asyncio.Event()
|
||||
docker_replied = asyncio.Event()
|
||||
|
||||
async def run_scan(**kwargs: Any) -> None:
|
||||
try:
|
||||
scanning.set()
|
||||
await asyncio.sleep(3600)
|
||||
finally:
|
||||
# session_manager.cleanup() waits on the docker daemon here
|
||||
tearing_down.set()
|
||||
await docker_replied.wait()
|
||||
torn_down.append(kwargs["scan_id"])
|
||||
|
||||
monkeypatch.setattr(
|
||||
go_tui,
|
||||
"load_settings",
|
||||
lambda: SimpleNamespace(runtime=SimpleNamespace(image="test-image")),
|
||||
)
|
||||
monkeypatch.setattr(go_tui, "run_strix_scan", run_scan)
|
||||
monkeypatch.setattr(go_tui, "read_workspace_files", lambda _value: [])
|
||||
|
||||
runtime.start_scan()
|
||||
await scanning.wait()
|
||||
|
||||
command_quit = asyncio.create_task(runtime.quit())
|
||||
await tearing_down.wait()
|
||||
final_quit = asyncio.create_task(runtime.quit())
|
||||
for _ in range(3):
|
||||
await asyncio.sleep(0)
|
||||
docker_replied.set()
|
||||
await asyncio.gather(command_quit, final_quit)
|
||||
|
||||
assert torn_down == ["test-run"], "the sandbox container was left running"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_setup_preflight_failure_does_not_start_scan(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue