import random
from tiny_agent.chat import parse_assistant, render
from tiny_agent.tasks import KINDS, check, grounded, make_task, oracle_trajectory
from tiny_agent.tools import Workspace, truncate
def test_sandbox_isolation():
with Workspace({"a.txt": "hello\n"}) as ws:
assert "hello" in ws.tool_bash("cat a.txt")
assert "nonet" in ws.tool_bash("curl -s -m 2 https://example.com >/dev/null && echo net || echo nonet")
assert "No such file" in ws.tool_bash("ls /home")
assert "Read-only" in ws.tool_bash("touch /usr/x")
assert "timed out" in ws.tool_bash("sleep 5", timeout=1)
def test_read_edit_rules():
with Workspace({"f.py": "x = 1\ny = 1\n"}) as ws:
assert "read f.py before" in ws.tool_edit("f.py", "x = 1", "x = 2")
out = ws.tool_read("f.py")
assert " 1\tx = 1" in out
assert "appears 2 times" in ws.tool_edit("f.py", "= 1", "= 3")
assert "not found" in ws.tool_edit("f.py", "z = 1", "z = 2")
assert "Edited" in ws.tool_edit("f.py", "x = 1", "x = 2")
assert "outside /work" in ws.tool_read("../../etc/passwd")
assert "unknown tool" in ws.call("browse", {})
assert "bad arguments" in ws.call("read", {"file": "f.py"})
def test_truncation_marker():
t = truncate("x\n" * 10000, limit=600)
assert "truncated" in t and len(t) < 800
def test_parse_assistant():
m = parse_assistant('\nhmm\n\n\n{"name": "bash", "arguments": {"command": "ls"}}\n'
'{bad json}<|im_end|>')
assert m["think"] == "hmm"
assert m["tool_calls"][0] == {"name": "bash", "arguments": {"command": "ls"}}
assert "error" in m["tool_calls"][1]
def test_render_roundtrip_assistant():
msg = {"role": "assistant", "think": "t", "content": "", "tool_calls": [{"name": "submit", "arguments": {"answer": "7"}}]}
text = render([msg]).split("<|im_start|>assistant\n", 1)[1]
back = parse_assistant(text)
assert back["tool_calls"] == msg["tool_calls"] and back["think"] == "t"
def test_oracle_solves_every_kind():
for i, kind in enumerate(KINDS * 3):
t = make_task(random.Random(5000 + i), kind)
msgs, ok = oracle_trajectory(t, random.Random(i))
assert ok, (kind, t.question)
assert grounded(t, msgs)
def test_wrong_answers_fail():
t = make_task(random.Random(1), "config_value")
assert not check(t, "definitely wrong")
assert not check(t, None)
assert check(t, f" {t.answer}. ")