109 lines
4.4 KiB
Python
109 lines
4.4 KiB
Python
"""Run-directory status heartbeat: progress visible WITHOUT the source repo."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from opensquilla.contrib.codetask import config, runner
|
|
|
|
|
|
def test_write_status_writes_run_dir_heartbeat(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("OPENSQUILLA_CODETASK_RUNS_DIR", str(tmp_path / "runs"))
|
|
runner._write_status("run-x", "agent_running", repo="/tmp/foo")
|
|
|
|
status = config.run_dir("run-x") / "status.json"
|
|
assert status.is_file()
|
|
d = json.loads(status.read_text())
|
|
assert d["run_id"] == "run-x"
|
|
assert d["phase"] == "agent_running"
|
|
assert d["repo"] == "/tmp/foo"
|
|
assert "updated" in d
|
|
|
|
|
|
def test_write_status_overwrites_with_latest_phase(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("OPENSQUILLA_CODETASK_RUNS_DIR", str(tmp_path / "runs"))
|
|
runner._write_status("run-y", "preparing")
|
|
runner._write_status("run-y", "verifying")
|
|
|
|
d = json.loads((config.run_dir("run-y") / "status.json").read_text())
|
|
assert d["phase"] == "verifying" # latest wins
|
|
|
|
|
|
def test_write_status_never_raises_on_bad_dir(tmp_path, monkeypatch):
|
|
# Point the runs root at a file so mkdir fails — must be swallowed.
|
|
bad = tmp_path / "afile"
|
|
bad.write_text("x")
|
|
monkeypatch.setenv("OPENSQUILLA_CODETASK_RUNS_DIR", str(bad))
|
|
runner._write_status("run-z", "preparing") # should not raise
|
|
|
|
|
|
def test_scaffold_build_app_copies_template_and_writes_status(tmp_path, monkeypatch):
|
|
from opensquilla.contrib.codetask import build_verify
|
|
|
|
monkeypatch.setenv("OPENSQUILLA_CODETASK_RUNS_DIR", str(tmp_path / "runs"))
|
|
monkeypatch.setattr(build_verify, "_resolve_cli", lambda name: name)
|
|
monkeypatch.setattr(build_verify, "_build_env", lambda: {})
|
|
phases = []
|
|
commands = []
|
|
|
|
def fake_run_step(run_id, phase, cmd, *, cwd, stdout_path, stderr_path, timeout, env):
|
|
phases.append(phase)
|
|
commands.append(cmd)
|
|
if phase == "scaffold_running":
|
|
app = cwd / "app"
|
|
(app / "src").mkdir(parents=True)
|
|
(app / "package.json").write_text('{"scripts": {}}', encoding="utf-8")
|
|
(app / "src" / "main.ts").write_text("export {}\n", encoding="utf-8")
|
|
if phase == "scaffold_lockfile":
|
|
(cwd / "package-lock.json").write_text("{}", encoding="utf-8")
|
|
stdout_path.write_text("ok\n", encoding="utf-8")
|
|
stderr_path.write_text("", encoding="utf-8")
|
|
return True, "ok"
|
|
|
|
monkeypatch.setattr(runner, "_run_logged_step", fake_run_step)
|
|
repo = tmp_path / "repo"
|
|
repo.mkdir()
|
|
artifact_dir = config.run_dir("run-scaffold")
|
|
artifact_dir.mkdir(parents=True)
|
|
|
|
ok, detail = runner._scaffold_build_app("run-scaffold", repo, artifact_dir)
|
|
|
|
assert ok is True, detail
|
|
assert phases == ["scaffold_running", "scaffold_lockfile"]
|
|
assert commands[0][-5:] == ["app", "--", "--template", "react-ts", "--skip"]
|
|
assert (repo / "package.json").is_file()
|
|
assert (repo / "src" / "main.ts").is_file()
|
|
assert (repo / "package-lock.json").is_file()
|
|
status = json.loads((artifact_dir / "status.json").read_text())
|
|
assert status["phase"] == "scaffold_complete"
|
|
|
|
|
|
def test_scaffold_build_app_records_failure_status(tmp_path, monkeypatch):
|
|
from opensquilla.contrib.codetask import build_verify
|
|
|
|
monkeypatch.setenv("OPENSQUILLA_CODETASK_RUNS_DIR", str(tmp_path / "runs"))
|
|
monkeypatch.setattr(build_verify, "_resolve_cli", lambda name: name)
|
|
monkeypatch.setattr(build_verify, "_build_env", lambda: {})
|
|
|
|
def fake_run_step(run_id, phase, cmd, *, cwd, stdout_path, stderr_path, timeout, env):
|
|
stderr_path.write_text("hung\n", encoding="utf-8")
|
|
return False, "scaffold timeout"
|
|
|
|
monkeypatch.setattr(runner, "_run_logged_step", fake_run_step)
|
|
repo = tmp_path / "repo"
|
|
repo.mkdir()
|
|
artifact_dir = config.run_dir("run-scaffold-fail")
|
|
artifact_dir.mkdir(parents=True)
|
|
|
|
ok, detail = runner._scaffold_build_app("run-scaffold-fail", repo, artifact_dir)
|
|
|
|
assert ok is False
|
|
# The underlying failure is preserved and an actionable hint is appended so a
|
|
# stuck/opaque scaffold (issue #470) names its likely cause.
|
|
assert detail.startswith("scaffold timeout")
|
|
assert "interactive prompt" in detail
|
|
status = json.loads((artifact_dir / "status.json").read_text())
|
|
assert status["phase"] == "scaffold_failed"
|
|
assert status["error"].startswith("scaffold timeout")
|
|
assert "interactive prompt" in status["error"]
|