From 53db2923295e3979ffb6ef1104f7f182035c7dd5 Mon Sep 17 00:00:00 2001 From: akynte <104795207+akynte@users.noreply.github.com> Date: Tue, 13 Oct 2026 07:59:46 -0400 Subject: [PATCH] test(orchestrator): record what the SIGKILL resume test observes TestSIGKILLResume now writes down each step it checks (the dead runner's state, the refused second runner, the stale-lease takeover, the reconciled attempt, the task's event log and the final state) and compares the sequence with testdata/crash-replay.json. Any difference fails the test, so a passing CI run proves the file is what a real kill and resume produced; -update-replay rewrites it when behaviour changes on purpose. Task ids and wall-clock times are normalised so the file is identical on every run. This gives readers (and the portfolio's replay of the test) a transcript that cannot drift from the code. Signed-off-by: akynte <104795207+akynte@users.noreply.github.com> --- internal/orchestrator/crash_test.go | 111 ++++++++++++++++-- .../orchestrator/testdata/crash-replay.json | 61 ++++++++++ 2 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 internal/orchestrator/testdata/crash-replay.json diff --git a/internal/orchestrator/crash_test.go b/internal/orchestrator/crash_test.go index ab1de72..fc4ab62 100644 --- a/internal/orchestrator/crash_test.go +++ b/internal/orchestrator/crash_test.go @@ -1,10 +1,15 @@ package orchestrator import ( + "bytes" "context" + "encoding/json" + "flag" + "fmt" "os" "os/exec" "path/filepath" + "regexp" "strings" "syscall" "testing" @@ -17,6 +22,28 @@ import ( "github.com/akynte/boundedcode/internal/telemetry" ) +var updateReplay = flag.Bool("update-replay", false, "rewrite testdata/crash-replay.json from this run of TestSIGKILLResume") + +// crashReplay is what TestSIGKILLResume observes, in order. The test compares +// it with testdata/crash-replay.json and fails on any difference, so a passing +// run proves the file is what a real kill and resume produced. Readers (such as +// a site that replays it) can rely on that; -update-replay rewrites the file +// when the behaviour changes on purpose. +type crashReplay struct { + Test string `json:"test"` + About string `json:"about"` + Steps []replayStep `json:"steps"` +} + +// rfc3339 matches the wall-clock times in observed messages, which differ on every run. +var rfc3339 = regexp.MustCompile(`\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z`) + +type replayStep struct { + Actor string `json:"actor"` + Action string `json:"action"` + Observed string `json:"observed"` +} + // TestKilledRunnerHelper is the child process of TestSIGKILLResume: it runs // the task with an agent that edits the repository and then hangs, until the // parent SIGKILLs it. Not a test on its own. @@ -55,6 +82,16 @@ func TestSIGKILLResume(t *testing.T) { if err != nil { t.Fatal(err) } + l := task.Ledger{DB: s.DB} + rec := crashReplay{Test: "TestSIGKILLResume", About: "A runner process is killed with SIGKILL in the middle of an agent turn; " + + "a second runner is refused, a third takes over the stale lease and finishes the task. Each step is what the test observed."} + see := func(actor, action, observed string) { + observed = rfc3339.ReplaceAllString(strings.ReplaceAll(observed, tk.ID, ""), "