mirror of
https://github.com/usestrix/strix.git
synced 2026-10-01 02:03:55 +00:00
106 lines
3.9 KiB
Python
106 lines
3.9 KiB
Python
"""Exercise live-scan worktree isolation with native tools and scripted inference."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from pathlib import Path
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
from agents import RunConfig
|
|
from agents.sandbox import SandboxRunConfig
|
|
|
|
from strix.core.agents import AgentCoordinator
|
|
from strix.core.hooks import ReportUsageHooks
|
|
from strix.fix import FindingContext
|
|
from strix.fix import runtime as fix_runtime
|
|
from strix.fix.scan import ScanFixes
|
|
from strix.fix.session import WorktreeSession
|
|
from tests.test_fix_completion import ScriptedModel, finish, patch, suite_commands
|
|
from tests.test_fix_reliability import LocalSandbox, existing_suite
|
|
from tests.test_fix_runtime import _git, _request, _workspace
|
|
|
|
|
|
def setup(tmp_path):
|
|
source, _ = _workspace(tmp_path)
|
|
commit = existing_suite(source)
|
|
parent = LocalSandbox(tmp_path / "sandbox")
|
|
coordinator = AgentCoordinator()
|
|
fixes = ScanFixes(
|
|
session=parent,
|
|
coordinator=coordinator,
|
|
parent_id="root",
|
|
scan_id="scan",
|
|
state_dir=tmp_path / "state",
|
|
local_sources=[{"source_path": str(source)}],
|
|
hooks=ReportUsageHooks(model="test", max_turns=1000),
|
|
)
|
|
fixes.base = str(tmp_path / "sandbox" / "fixes")
|
|
candidate = _request(commit).candidate
|
|
candidate.finding = FindingContext(validation_status="confirmed", title="Unsafe result")
|
|
report = {
|
|
"id": "finding",
|
|
"validation_status": "confirmed",
|
|
"fix_candidate": candidate.model_dump(mode="json"),
|
|
}
|
|
return fixes, report, source, parent
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_parallel_fixes_use_worktrees_without_modifying_scan_source(tmp_path, monkeypatch):
|
|
fixes, report, source, parent = setup(tmp_path)
|
|
models = {}
|
|
|
|
def config(env):
|
|
model = models.setdefault(
|
|
env.execution_id, ScriptedModel([*patch(), *suite_commands(), finish("done")])
|
|
)
|
|
return RunConfig(
|
|
model=model, sandbox=SandboxRunConfig(session=env.session), tracing_disabled=True
|
|
)
|
|
|
|
monkeypatch.setattr(fix_runtime, "_run_config", config)
|
|
fixes.notify(report)
|
|
fixes.notify({**report, "id": "another-finding"})
|
|
await fixes.wait()
|
|
assert len(models) == 2
|
|
assert all(record["status"] == "done" for record in fixes.records.values()), fixes.records
|
|
assert all(Path(record["artifact"]).exists() for record in fixes.records.values())
|
|
assert _git(source, "status", "--porcelain") == ""
|
|
assert "unsafe" in (source / "app.py").read_text()
|
|
assert parent.state.manifest.root == str(tmp_path / "sandbox")
|
|
assert not list((tmp_path / "sandbox/fixes/worktrees").glob("*/app.py"))
|
|
assert not list((tmp_path / "state/fixes").glob("*/source"))
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unconfirmed_duplicate_and_exhausted_findings_do_not_start_agent(tmp_path):
|
|
fixes, report, _, _ = setup(tmp_path)
|
|
fixes._run = AsyncMock()
|
|
fixes.notify({**report, "validation_status": "unconfirmed"})
|
|
await asyncio.sleep(0)
|
|
fixes._run.assert_not_called()
|
|
fixes.notify(report)
|
|
fixes.notify(report)
|
|
await fixes.wait()
|
|
assert fixes._run.await_count == 1
|
|
fixes.closed = False
|
|
fixes.records["finding"]["turns"] = 300
|
|
fixes.records["finding"]["status"] = "running"
|
|
fixes.tasks.clear()
|
|
fixes.notify(report)
|
|
await fixes.wait()
|
|
assert fixes._run.await_count == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_worktree_process_cleanup_never_terminates_parent_sessions(tmp_path):
|
|
parent = LocalSandbox(tmp_path)
|
|
parent.exec = AsyncMock()
|
|
parent.pty_terminate_all = AsyncMock()
|
|
child = WorktreeSession(parent, str(tmp_path / "fix"), "fix-one")
|
|
await child.pty_terminate_all()
|
|
parent.pty_terminate_all.assert_not_called()
|
|
assert parent.exec.call_args.args[3] == "fix-one"
|
|
with pytest.raises(ValueError, match="another agent"):
|
|
await child.pty_write_stdin(session_id=123, chars="kill")
|