Heartbeat: record why each Todo item was not dispatched
Each tick lists skipped items with terminal and reason (concurrency n/m with the holding runs, serialize group holder, busy_process not started by agentd, relay down, no route for assignee, start failed) in heartbeat.json "skipped". A reason is logged (agentd.log / cron.log) only when it is new for that item; the last set is kept in the state file as "skips".
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import json
|
||||
import unittest
|
||||
|
||||
from tests.fakes import Harness
|
||||
|
||||
|
||||
class FakeProbe:
|
||||
def __init__(self, counts):
|
||||
self.counts = counts
|
||||
|
||||
def process_count(self, name):
|
||||
return self.counts.get(name, 0)
|
||||
|
||||
|
||||
class SkipReasonTest(unittest.TestCase):
|
||||
"""Why a Todo item was not dispatched: heartbeat "skipped", logged once per new reason."""
|
||||
|
||||
def setUp(self):
|
||||
self.h = Harness()
|
||||
self.p = self.h.plane
|
||||
self.lines = []
|
||||
self.h.d.log = lambda msg: self.lines.append(msg)
|
||||
|
||||
def tearDown(self):
|
||||
self.h.close()
|
||||
|
||||
def skipped(self):
|
||||
hb = json.loads((self.h.d.store.dir / "heartbeat.json").read_text())
|
||||
return {s["item"]: (s["terminal"], s["reason"]) for s in hb["skipped"]}
|
||||
|
||||
def not_dispatched(self):
|
||||
return [l for l in self.lines if "not dispatched" in l]
|
||||
|
||||
def test_reasons_in_heartbeat_and_logged_once(self):
|
||||
self.p.add_issue(1) # starts on win10
|
||||
self.p.add_issue(2) # win10 full
|
||||
self.p.add_issue(3, assignee="kappiee") # no terminal for kappiee
|
||||
self.p.add_issue(4, assignee="oregon", labels=["needs-game"]) # starts on oregon, holds the game group
|
||||
self.p.add_issue(5, assignee="oregon", labels=["needs-game"]) # group held
|
||||
self.h.tick()
|
||||
self.assertEqual(self.skipped(), {
|
||||
"GW-2": ("win10", "concurrency 1/1 (GW-1)"),
|
||||
"GW-3": (None, "no route for assignee kappiee"),
|
||||
"GW-5": ("oregon", "serialize_group game held by GW-4"),
|
||||
})
|
||||
self.assertEqual(len(self.not_dispatched()), 3)
|
||||
self.h.tick() # same reasons: heartbeat still has them, the log does not repeat them
|
||||
self.assertEqual(len(self.skipped()), 3)
|
||||
self.assertEqual(len(self.not_dispatched()), 3)
|
||||
|
||||
def test_busy_process_and_relay_down(self):
|
||||
self.h.cfg.terminals["win10"]["busy_processes"] = ["codex"]
|
||||
probe = FakeProbe({"codex": 1})
|
||||
self.h.d.transport_factory = lambda term: probe
|
||||
self.p.add_issue(1)
|
||||
self.h.tick()
|
||||
self.assertEqual(self.skipped(), {"GW-1": ("win10", "busy_process codex (1 running, not started by agentd)")})
|
||||
probe.counts = {}
|
||||
self.h.tick() # leftover codex.exe gone: dispatched, nothing skipped
|
||||
self.assertEqual(self.skipped(), {})
|
||||
self.assertEqual(self.p.state_of("i1"), "In Progress")
|
||||
self.p.add_issue(2, assignee="oregon")
|
||||
self.h.cfg.terminals["oregon"]["busy_processes"] = ["claude"]
|
||||
probe.counts = {"claude": None} # probe fails: relay down
|
||||
probe.process_count = lambda name: None
|
||||
self.h.tick()
|
||||
self.assertEqual(self.skipped()["GW-2"], ("oregon", "relay down"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user