Files
plane-agentd/tests/test_local_runner.py
T
jj.wang 31850c4a81 Scheduling core, state store, CLI and unit tests
One tick polls in-flight runs, posts results, classifies the first line and
moves state/assignee; failures get a structured note with transient retry and
backoff, timeouts are killed and handed over, resume failures and long threads
rebuild with a handoff summary. Then the lowest Todo items are dispatched within
terminal concurrency and serialize groups, and a heartbeat is written.
CLI: tick, new, show, comment, state, assign, smoke, check-config.
2026-10-21 07:04:32 +00:00

82 lines
3.1 KiB
Python

"""Real LocalTransport + runner executors against throwaway scripts (local processes only, no network)."""
import os
import tempfile
import time
import unittest
from pathlib import Path
from agentd.config import Config
from agentd.executors import Run, make_executor
from agentd.transports.local import LocalTransport
from tests.fakes import config_data
FAKE_CLAUDE = """#!/usr/bin/env bash
prompt="$(cat)"
echo "args: $*" >&2
printf '{"type":"result","subtype":"success","is_error":false,"result":"[review] got %s","session_id":"sess-42"}' "${#prompt}"
"""
def wait(ex, handle, seconds=10):
end = time.time() + seconds
while time.time() < end:
if ex.poll(handle) == "finished":
return True
time.sleep(0.1)
return False
class LocalRunnerTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
root = Path(self.tmp.name)
fake = root / "fake-claude"
fake.write_text(FAKE_CLAUDE)
fake.chmod(0o755)
data = config_data()
data["agentd"]["state_dir"] = str(root / "state")
data["terminals"]["oregon"]["bins"] = {"claude": str(fake)}
data["terminals"]["oregon"]["repo"] = str(root)
self.cfg = Config(data)
self.t = LocalTransport(self.cfg, "oregon")
self.root = root
def tearDown(self):
self.tmp.cleanup()
def test_shell_executor_round(self):
ex = make_executor(self.cfg, "oregon", "shell", self.t)
self.assertFalse(ex.resume)
run = Run("GW-1-1", str(self.root), "hello prompt", script="read line; echo \"[review] echo: $line\"")
h = ex.start(run)
self.assertTrue(wait(ex, h))
res = ex.collect(h)
self.assertEqual((res.last_message, res.exit_code, res.thread_id), ("[review] echo: hello prompt", 0, None))
def test_claude_executor_round_and_resume(self):
ex = make_executor(self.cfg, "oregon", "claude-code", self.t)
h = ex.start(Run("GW-2-1", str(self.root), "abc", model="sonnet"))
self.assertTrue(wait(ex, h))
res = ex.collect(h)
self.assertEqual((res.last_message, res.thread_id, res.exit_code), ("[review] got 3", "sess-42", 0))
h2 = ex.start(Run("GW-2-2", str(self.root), "abcd", model="sonnet", thread_id="sess-42"))
self.assertTrue(wait(ex, h2))
self.assertIn("--resume sess-42", self.t.read_text(os.path.join(h2["run_dir"], "stderr.txt")))
def test_kill_and_exit_code(self):
ex = make_executor(self.cfg, "oregon", "shell", self.t)
h = ex.start(Run("GW-3-1", str(self.root), "", script="sleep 30"))
time.sleep(0.5)
self.assertEqual(ex.poll(h), "running")
ex.kill(h)
time.sleep(0.3)
self.assertEqual(ex.poll(h), "running") # killed runner never writes exit.txt; core handles it as timeout
h = ex.start(Run("GW-3-2", str(self.root), "", script="echo partial; exit 3"))
self.assertTrue(wait(ex, h))
res = ex.collect(h)
self.assertEqual((res.exit_code, res.last_message), (3, "partial"))
if __name__ == "__main__":
unittest.main()