File size: 8,544 Bytes
e2f048d 24c5a49 03c1ae9 24c5a49 627c6cf 16e3e7d 627c6cf 16e3e7d 627c6cf e2f048d 03c1ae9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | import json
import pytest
from tiny_computer_agent import __main__ as cli
from tiny_computer_agent import verify_audit
def test_demo_runs_dry_run_by_default(capsys):
assert cli.main([]) == 0
out = capsys.readouterr().out
assert "dry-run" in out
assert "(dry-run) would click" in out
assert "result: success" in out
def test_real_input_needs_the_allow_env_var(monkeypatch, capsys):
monkeypatch.delenv(cli.ALLOW_REAL_INPUT_ENV, raising=False)
assert cli.main(["--real-input"]) == 2
assert "refusing --real-input" in capsys.readouterr().err
def test_bad_script_json_is_reported(capsys):
assert cli.main(["--script", "{not json"]) == 2
assert "not valid JSON" in capsys.readouterr().err
def test_script_replay(capsys):
assert cli.main(["--script", '[{"kind": "done", "reason": "nothing to do"}]']) == 0
assert "nothing to do" in capsys.readouterr().out
def test_failure_exit_code(capsys):
# the planner gives up when the target never appears, so the CLI exits non-zero
assert cli.main(["click 'Nonexistent button'"]) == 1
assert "gave up" in capsys.readouterr().out
def test_doctor_exits_with_a_status(capsys):
assert cli.main(["--doctor"]) in (0, 1)
assert "preflight" in capsys.readouterr().out
def test_live_is_refused_without_a_display(capsys, monkeypatch):
from tiny_computer_agent.desktop import display_available
if display_available()[0]:
pytest.skip("this machine has a display, so --live would really run")
assert cli.main(["--live"]) == 2
captured = capsys.readouterr()
assert "refusing to run live" in captured.err
assert "no DISPLAY" in captured.out
def test_screenshot_replay_needs_no_display(capsys):
"""The whole pipeline - OCR index, menu, action - on a still image."""
from conftest import require_ocr
require_ocr() # one tested guard, shared with the perception tests
assert cli.main(["--image", "example", "--brain", "planner"]) == 0
out = capsys.readouterr().out
assert "screenshot replay" in out
assert "elements detected" in out
assert "would click" in out
def test_a_missing_image_is_reported_not_crashed(capsys):
assert cli.main(["--image", "/nonexistent/shot.png"]) == 2
assert "could not start" in capsys.readouterr().err
def test_vlm_brain_requires_a_live_screen(capsys):
assert cli.main(["--brain", "vlm"]) == 2
assert "needs --live" in capsys.readouterr().err
# --- 2.0: the subcommands, which are how a run becomes a habit --------------- #
def test_skills_list_on_an_empty_library_says_how_to_start(tmp_path, capsys):
assert cli.main(["skills", "list", "--path", str(tmp_path / "none.json")]) == 0
assert "no skills" in capsys.readouterr().out
def test_learning_refuses_without_evidence(tmp_path, capsys):
code = cli.main(["skills", "learn", "click 'Save'",
"--script", '[{"kind": "click", "label": "Save"}]',
"--path", str(tmp_path / "skills.json")])
assert code == 2
assert "refusing to learn" in capsys.readouterr().err
def test_learn_then_list_then_show_then_remove(tmp_path, capsys):
path = str(tmp_path / "skills.json")
script = '[{"kind": "click", "label": "Files"}, {"kind": "click", "label": "Save"}]'
assert cli.main(["skills", "learn", "click 'Save' to save the document",
"--script", script, "--verify-gone", "Name", "--path", path]) == 0
assert "learned 'save-document'" in capsys.readouterr().out
assert cli.main(["skills", "list", "--path", path]) == 0
out = capsys.readouterr().out
assert "save-document" in out and "verified" in out
assert cli.main(["skills", "show", "save-document", "--path", path]) == 0
assert "'Name' goes away" in capsys.readouterr().out
assert cli.main(["skills", "remove", "save-document", "--path", path]) == 0
assert "removed" in capsys.readouterr().out
assert cli.main(["skills", "show", "save-document", "--path", path]) == 1
def test_a_learned_skill_can_be_replayed_by_the_brain(tmp_path):
"""The point of the whole feature: the second run needs no script."""
path = tmp_path / "skills.json"
script = '[{"kind": "click", "label": "Files"}, {"kind": "click", "label": "Save"}]'
assert cli.main(["skills", "learn", "click 'Save' to save the document",
"--script", script, "--verify-gone", "Name", "--path", str(path)]) == 0
from tiny_computer_agent import SkillBrain, SkillLibrary
from tiny_computer_agent.__main__ import demo_screen
from tiny_computer_agent.loop import AgentLoop
library = SkillLibrary.load(path)
loop = AgentLoop(screen=demo_screen(), brain=SkillBrain(library))
episode = loop.run("click 'Save' to save the document")
assert [s.action.label for s in episode.steps if s.action and s.action.kind == "click"] \
== ["Files", "Save"]
def test_batch_runs_a_queue_and_exits_on_its_report(tmp_path, capsys):
tasks = tmp_path / "tasks.json"
tasks.write_text(json.dumps([{"goal": "click 'Save' to save the document",
"verify_gone": "Name"},
{"goal": "click 'Files' to open the file manager",
"verify_text": "untitled"}]))
state = tmp_path / "state.jsonl"
report = tmp_path / "report.json"
assert cli.main(["batch", str(tasks), "--state", str(state), "--report", str(report)]) == 0
out = capsys.readouterr().out
assert "2/2 verified" in out
assert json.loads(report.read_text())["passed"] == 2
assert cli.main(["batch", str(tasks), "--state", str(state)]) == 0
assert "already run" in capsys.readouterr().out
def test_batch_rejects_tasks_that_are_not_tasks(tmp_path, capsys):
bad = tmp_path / "bad.json"
bad.write_text(json.dumps([{"nothing": "here"}]))
assert cli.main(["batch", str(bad)]) == 2
assert "needs at least a 'goal'" in capsys.readouterr().err
missing = tmp_path / "absent.json"
assert cli.main(["batch", str(missing)]) == 2
assert "cannot read" in capsys.readouterr().err
# -- 2.0: the operator's gate, and a record of the run that can be checked ------- #
SCRIPT = '[{"kind": "click", "label": "Save"}, {"kind": "done", "reason": "saved"}]'
def test_the_audit_flag_writes_a_chain_that_verifies(tmp_path, capsys):
path = tmp_path / "run.jsonl"
assert cli.main(["--image", "example", "--script", SCRIPT, "--audit", str(path)]) == 0
out = capsys.readouterr().out
assert "recording this run to" in out
assert "audit:" in out and "chain intact" in out
ok, detail = verify_audit(path)
assert ok, detail
def test_verify_audit_accepts_an_intact_chain(tmp_path, capsys):
path = tmp_path / "run.jsonl"
cli.main(["--image", "example", "--script", SCRIPT, "--audit", str(path)])
assert cli.main(["--verify-audit", str(path)]) == 0
assert "ok" in capsys.readouterr().out
def test_verify_audit_fails_loudly_on_a_tampered_chain(tmp_path, capsys):
path = tmp_path / "run.jsonl"
cli.main(["--image", "example", "--script", SCRIPT, "--audit", str(path)])
lines = path.read_text(encoding="utf-8").splitlines()
entry = json.loads(lines[0])
entry["outcome"] = "nothing happened" # a nicer story
lines[0] = json.dumps(entry, sort_keys=True, separators=(",", ":"))
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
assert cli.main(["--verify-audit", str(path)]) == 1
out = capsys.readouterr().out
assert "FAIL" in out and "entry 1" in out
def test_a_bad_gate_spec_is_a_usage_error(capsys):
assert cli.main(["--gate", "yes-please"]) == 2
assert "bad --gate" in capsys.readouterr().err
def test_the_read_only_gate_refuses_to_change_anything(capsys):
"""The gate is the operator's yes; read-only means the answer is no to any change."""
code = cli.main(["--image", "example", "--script",
'[{"kind": "click", "label": "Save"}]', "--gate", "read-only"])
out = capsys.readouterr().out
assert code == 1, "a refused action is a failed episode, not a success"
assert "gate: read-only" in out
assert "operator refused" in out and "read-only gate" in out
def test_the_labels_gate_lets_a_whitelisted_click_through(capsys):
code = cli.main(["--image", "example", "--script", SCRIPT, "--gate", "labels:Save"])
out = capsys.readouterr().out
assert code == 0, out
assert "is whitelisted" in out or "would click" in out
|