4aa06f652e
executus CI / test (pull_request) Failing after 58s
From PR #9 (minimax + deepseek): - Run now has a top-level recover() — the "never propagates a panic" promise was unenforced; a panicking host Port (Critic/Audit/Palette) on the run goroutine now becomes Result.Err instead of unwinding into the caller. - The critic deadline-watch goroutine recovers panics from a host Deadline() (it's a separate goroutine, so Run's recover can't catch it) — a buggy CriticHandle can't crash the process. - CriticHandle interface documents its concurrency contract (Record*/Steer on the run goroutine vs Deadline()/Stop() from the watch goroutine — impls must be concurrent-safe; the critic battery already is). - startCritic's dead `soft <= 0 -> noop` guard (withFallbacks already coerces to 90s) replaced with a defensive inline 90s default, so a bypass of withFallbacks still gets a working critic instead of silently none. - Delivery tests made honest: the old "error path" test only checked the early-return (no delivery); added TestDeliverErrorOnRunFailure (in-loop model error -> DeliverError to the target) + renamed the early-return test. Graded all #9 findings in the gadfly MCP. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
115 lines
4.0 KiB
Go
115 lines
4.0 KiB
Go
package run_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"gitea.stevedudenhoeffer.com/steve/majordomo/llm"
|
|
"gitea.stevedudenhoeffer.com/steve/majordomo/provider/fake"
|
|
|
|
"gitea.stevedudenhoeffer.com/steve/executus/deliver"
|
|
"gitea.stevedudenhoeffer.com/steve/executus/run"
|
|
"gitea.stevedudenhoeffer.com/steve/executus/tool"
|
|
)
|
|
|
|
type recordingDelivery struct {
|
|
target deliver.Target
|
|
output string
|
|
errored error
|
|
delivers int
|
|
}
|
|
|
|
func (d *recordingDelivery) Deliver(_ context.Context, t deliver.Target, output string, _ []deliver.Artifact) (string, error) {
|
|
d.target, d.output, d.delivers = t, output, d.delivers+1
|
|
return "msg-1", nil
|
|
}
|
|
func (d *recordingDelivery) DeliverError(_ context.Context, t deliver.Target, e error) error {
|
|
d.target, d.errored = t, e
|
|
return nil
|
|
}
|
|
|
|
func TestDeliveryWired(t *testing.T) {
|
|
d := &recordingDelivery{}
|
|
fp := fake.New("fake")
|
|
fp.Enqueue("m", fake.Reply("the output"))
|
|
m, _ := fp.Model("m")
|
|
ex := run.New(run.Config{
|
|
Registry: tool.NewRegistry(),
|
|
Models: func(ctx context.Context, _ string) (context.Context, llm.Model, error) { return ctx, m, nil },
|
|
Ports: run.Ports{Delivery: d},
|
|
})
|
|
// With a delivery target, the executor posts the output.
|
|
ex.Run(context.Background(),
|
|
run.RunnableAgent{Name: "x", ModelTier: "m"},
|
|
tool.Invocation{RunID: "r", DeliveryKind: "channel", DeliveryID: "chan-9"}, "go")
|
|
if d.delivers != 1 || d.output != "the output" || d.target.ID != "chan-9" || d.target.Kind != "channel" {
|
|
t.Fatalf("delivery wrong: %+v out=%q", d.target, d.output)
|
|
}
|
|
}
|
|
|
|
func TestNoDeliveryWithoutTarget(t *testing.T) {
|
|
d := &recordingDelivery{}
|
|
fp := fake.New("fake")
|
|
fp.Enqueue("m", fake.Reply("x"))
|
|
m, _ := fp.Model("m")
|
|
ex := run.New(run.Config{
|
|
Registry: tool.NewRegistry(),
|
|
Models: func(ctx context.Context, _ string) (context.Context, llm.Model, error) { return ctx, m, nil },
|
|
Ports: run.Ports{Delivery: d},
|
|
})
|
|
// No DeliveryID → executor delivers nothing (caller reads Result.Output).
|
|
ex.Run(context.Background(),
|
|
run.RunnableAgent{Name: "x", ModelTier: "m"},
|
|
tool.Invocation{RunID: "r"}, "go")
|
|
if d.delivers != 0 {
|
|
t.Errorf("no target should mean no delivery, got %d", d.delivers)
|
|
}
|
|
}
|
|
|
|
// TestNoDeliveryOnEarlyResolveError: an error BEFORE the run starts (model
|
|
// resolve) returns before delivery is reached — neither Deliver nor DeliverError
|
|
// fires. (Delivery covers run OUTCOMES, not pre-run setup failures.)
|
|
func TestNoDeliveryOnEarlyResolveError(t *testing.T) {
|
|
d := &recordingDelivery{}
|
|
ex := run.New(run.Config{
|
|
Registry: tool.NewRegistry(),
|
|
Models: func(ctx context.Context, _ string) (context.Context, llm.Model, error) {
|
|
return ctx, nil, errors.New("resolve boom")
|
|
},
|
|
Ports: run.Ports{Delivery: d},
|
|
})
|
|
ex.Run(context.Background(),
|
|
run.RunnableAgent{Name: "x", ModelTier: "m"},
|
|
tool.Invocation{RunID: "r", DeliveryKind: "channel", DeliveryID: "chan-9"}, "go")
|
|
if d.delivers != 0 || d.errored != nil {
|
|
t.Errorf("early resolve failure should neither Deliver nor DeliverError: delivers=%d errored=%v", d.delivers, d.errored)
|
|
}
|
|
}
|
|
|
|
// TestDeliverErrorOnRunFailure: an in-loop run failure (the model errors) routes
|
|
// through DeliverError with the run error.
|
|
func TestDeliverErrorOnRunFailure(t *testing.T) {
|
|
d := &recordingDelivery{}
|
|
fp := fake.New("fake")
|
|
fp.Enqueue("m", fake.Step{Err: errors.New("model boom")}) // model errors mid-run
|
|
m, _ := fp.Model("m")
|
|
ex := run.New(run.Config{
|
|
Registry: tool.NewRegistry(),
|
|
Models: func(ctx context.Context, _ string) (context.Context, llm.Model, error) { return ctx, m, nil },
|
|
Ports: run.Ports{Delivery: d},
|
|
})
|
|
res := ex.Run(context.Background(),
|
|
run.RunnableAgent{Name: "x", ModelTier: "m"},
|
|
tool.Invocation{RunID: "r", DeliveryKind: "channel", DeliveryID: "chan-9"}, "go")
|
|
if res.Err == nil {
|
|
t.Fatal("expected a run error")
|
|
}
|
|
if d.delivers != 0 {
|
|
t.Errorf("a failed run should not Deliver (success path), got %d", d.delivers)
|
|
}
|
|
if d.errored == nil || d.target.ID != "chan-9" {
|
|
t.Errorf("a failed run with a target should DeliverError to chan-9, got errored=%v target=%+v", d.errored, d.target)
|
|
}
|
|
}
|