Files
automaton/tests/test_self_improvement_loop.py

143 lines
4.8 KiB
Python

"""Tests for task add-self-improvement-loop.
Covers R1-R5 from tasks/add-self-improvement-loop/SPEC.md: install.sh and
update.sh wiring, template validation, and loop creation from template.
"""
import json
import subprocess
import sys
import importlib.util
from pathlib import Path
import pytest
_FRAMEWORK = Path.home() / ".automaton"
_INSTALL_SH = _FRAMEWORK / "scripts" / "install.sh"
_UPDATE_SH = _FRAMEWORK / "scripts" / "update.sh"
_STATUS_PY = _FRAMEWORK / "scripts" / "status.py"
_TEMPLATE = _FRAMEWORK / "templates" / "loops" / "self-improvement" / "loop.json"
_spec = importlib.util.spec_from_file_location("status_mod", _STATUS_PY)
st = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(st)
class TestInstallShWiring:
def test_install_sh_creates_self_improvement_loop(self):
content = _INSTALL_SH.read_text()
assert "--create-loop self-improvement" in content
assert "--from-template self-improvement" in content
def test_install_sh_schedules_self_improvement_loop(self):
content = _INSTALL_SH.read_text()
assert "--install-schedule self-improvement" in content
assert "--interval 3600" in content
def test_install_sh_has_opt_out_message(self):
content = _INSTALL_SH.read_text()
assert "--pause-loop self-improvement" in content
def test_install_sh_uses_framework_project(self):
content = _INSTALL_SH.read_text()
assert '--project "$FRAMEWORK_DIR"' in content
def test_install_sh_non_fatal_on_failure(self):
content = _INSTALL_SH.read_text()
assert "|| true" in content
class TestUpdateShWiring:
def test_update_sh_bootstraps_self_improvement_loop(self):
content = _UPDATE_SH.read_text()
assert "--create-loop self-improvement" in content
assert "--from-template self-improvement" in content
def test_update_sh_has_idempotent_check(self):
content = _UPDATE_SH.read_text()
assert "if [ ! -d" in content
assert "loops/self-improvement" in content
def test_update_sh_schedules_loop(self):
content = _UPDATE_SH.read_text()
assert "--install-schedule self-improvement" in content
def test_update_sh_non_fatal_on_failure(self):
content = _UPDATE_SH.read_text()
assert "|| true" in content
class TestSelfImprovementTemplate:
def test_template_has_audit_work_source(self):
cfg = json.loads(_TEMPLATE.read_text())
assert cfg["work_source"]["kind"] == "audit"
def test_template_has_correct_brakes(self):
cfg = json.loads(_TEMPLATE.read_text())
assert cfg["brakes"]["max_iterations"] == 10
assert cfg["brakes"]["score_plateau_window"] == 3
def test_template_has_worktree_enabled(self):
cfg = json.loads(_TEMPLATE.read_text())
assert cfg["blast_radius"]["use_worktree"] is True
def test_template_has_file_scope(self):
cfg = json.loads(_TEMPLATE.read_text())
scope = cfg["blast_radius"]["file_scope"]
assert "scripts/" in scope
assert "prompts/" in scope
assert "tests/" in scope
assert "design/" in scope
def test_template_has_role_prompts(self):
cfg = json.loads(_TEMPLATE.read_text())
roles = cfg["roles"]
assert roles["implement"]["prompt"] == "loop-implement.md"
assert roles["verify"]["prompt"] == "loop-verifier.md"
assert roles["orchestrate"]["prompt"] == "loop-orchestrate.md"
class TestCreateLoopFromTemplate:
def test_create_loop_self_improvement(self, tmp_path):
project = tmp_path / "fw"
(project / ".automaton").mkdir(parents=True)
class A:
pass
a = A()
a.create_loop = "self-improvement"
a.from_template = "self-improvement"
a.project = str(project)
rc = st.cmd_create_loop(a)
assert rc == 0
loop_dir = project / ".automaton" / "loops" / "self-improvement"
assert loop_dir.exists()
assert (loop_dir / "loop.json").exists()
assert (loop_dir / ".state.loop").exists()
assert (loop_dir / ".state.log").exists()
state = json.loads((loop_dir / ".state.loop").read_text())
assert state["status"] == "running"
assert state["name"] == "self-improvement"
cfg = json.loads((loop_dir / "loop.json").read_text())
assert cfg["name"] == "self-improvement"
assert cfg["work_source"]["kind"] == "audit"
def test_create_loop_idempotent_refuses_duplicate(self, tmp_path):
project = tmp_path / "fw"
(project / ".automaton").mkdir(parents=True)
class A:
pass
a = A()
a.create_loop = "self-improvement"
a.from_template = "self-improvement"
a.project = str(project)
assert st.cmd_create_loop(a) == 0
rc = st.cmd_create_loop(a)
assert rc == 2