Files
executus/checkpoint/checkpoint_test.go
T
steve dc2d4ec425
executus CI / test (push) Failing after 1m6s
P4c: remaining batteries — checkpoint + schedule + critic
Completes the P4 battery set (squashed onto main from phase-4c-batteries).
- checkpoint/: run.Checkpointer durable-resume (CheckpointStore + throttled
  handle + Memory).
- schedule/: generic cron Runner (Tick/Loop; no cron grammar of its own).
- critic/: two-tier timeout watchdog (run.Critic) + Escalator policy seam +
  ExtendOnce default.
Includes the verified gadfly #6 fixes (ExtendOnce per-run, Kill-sticky, watch
panic-recovery; checkpoint throttle-after-success; schedule Next-before-Run +
nil-guard + Loop recovery).

P4 battery set complete: audit, budget, persona, skill, checkpoint, schedule,
critic — each nil-safe, each with a default, each core-import-clean. Executor
wiring for Critic/Checkpointer remains a P2 follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 00:15:32 -04:00

65 lines
2.1 KiB
Go

package checkpoint
import (
"context"
"testing"
"time"
"gitea.stevedudenhoeffer.com/steve/executus/run"
"gitea.stevedudenhoeffer.com/steve/majordomo/llm"
)
func TestHandleSaveCompleteDelete(t *testing.T) {
ctx := context.Background()
mem := NewMemory()
meta := RunCheckpointMeta{RunID: "r1", AgentID: "a1", CallerID: "c1"}
cp := New(mem, meta, 0, nil) // throttle 0 = save every call
if err := cp.Save(ctx, run.RunCheckpointState{Messages: []llm.Message{{Role: "user"}}, Iteration: 2}); err != nil {
t.Fatal(err)
}
got, _ := mem.Load(ctx, "r1")
if got == nil || got.Iteration != 2 || got.Meta.AgentID != "a1" {
t.Fatalf("checkpoint not persisted: %+v", got)
}
if il, _ := mem.ListInterrupted(ctx); len(il) != 1 {
t.Errorf("ListInterrupted = %d, want 1 (in-flight)", len(il))
}
// Complete clears it (no longer a recovery candidate).
if err := cp.Complete(ctx); err != nil {
t.Fatal(err)
}
if il, _ := mem.ListInterrupted(ctx); len(il) != 0 {
t.Errorf("after Complete, ListInterrupted = %d, want 0", len(il))
}
}
func TestHandleThrottle(t *testing.T) {
ctx := context.Background()
mem := NewMemory()
now := time.Now()
cp := New(mem, RunCheckpointMeta{RunID: "r"}, time.Minute, func() time.Time { return now })
cp.Save(ctx, run.RunCheckpointState{Iteration: 1})
now = now.Add(10 * time.Second) // within throttle window
cp.Save(ctx, run.RunCheckpointState{Iteration: 2})
if got, _ := mem.Load(ctx, "r"); got.Iteration != 1 {
t.Errorf("throttled save should keep iteration 1, got %d", got.Iteration)
}
now = now.Add(time.Minute) // past throttle
cp.Save(ctx, run.RunCheckpointState{Iteration: 3})
if got, _ := mem.Load(ctx, "r"); got.Iteration != 3 {
t.Errorf("post-throttle save should land iteration 3, got %d", got.Iteration)
}
}
func TestNilStoreNoop(t *testing.T) {
cp := New(nil, RunCheckpointMeta{RunID: "r"}, 0, nil)
if err := cp.Save(context.Background(), run.RunCheckpointState{}); err != nil {
t.Errorf("nil-store Save should be a no-op, got %v", err)
}
if err := cp.Complete(context.Background()); err != nil {
t.Error(err)
}
}