Files
ARTEX/server/engine_cancelcause_test.go
T
pphua 9793b522d9 feat(tasks): add inherited context and resilient LLM execution
Add read-only task context inheritance, ordered task-level LLM failover, live Worker controls, cascading task cleanup, FIFO task concurrency, cancellation diagnostics, UI updates, tests, build script, and changelog.
2026-08-20 18:34:24 +08:00

110 lines
3.1 KiB
Go

package server
import (
"context"
"testing"
"github.com/Autumn-27/artex/agent"
)
func TestPauseCancelsWithNamedCause(t *testing.T) {
e := NewEngine(nil)
execCtx := e.execContextFor(context.Background(), "t1")
workCtx, workCancel := context.WithCancelCause(execCtx)
defer workCancel(nil)
e.Pause("t1", agent.AbortPausedByUser)
if code, _, _, ok := agent.AbortReason(workCtx); !ok || code != "paused_by_user" {
t.Fatalf("code=%q ok=%v, want paused_by_user", code, ok)
}
}
func TestKillWorkCancelsOnlyThatWorkWithNamedCause(t *testing.T) {
e := NewEngine(nil)
execCtx := e.execContextFor(context.Background(), "t1")
workCtx, workCancel := context.WithCancelCause(execCtx)
e.registerWork(42, workCancel)
if err := e.KillWork(42); err != nil {
t.Fatal(err)
}
if code, _, _, ok := agent.AbortReason(workCtx); !ok || code != "killed_by_planner" {
t.Fatalf("code=%q ok=%v, want killed_by_planner", code, ok)
}
if execCtx.Err() != nil {
t.Fatal("kill_work cancelled the entire task context")
}
_, complete := e.detachWork(42)
complete()
}
func TestControlWorkCarriesUserActionCause(t *testing.T) {
for _, tc := range []struct {
action string
code string
}{
{action: "pause", code: "work_paused_by_user"},
{action: "cancel", code: "work_cancelled_by_user"},
} {
t.Run(tc.action, func(t *testing.T) {
e := NewEngine(nil)
ctx, cancel := context.WithCancelCause(context.Background())
e.registerWork(42, cancel)
done := make(chan error, 1)
go func() { done <- e.ControlWork(42, tc.action) }()
<-ctx.Done()
if code, _, _, _ := agent.AbortReason(ctx); code != tc.code {
t.Fatalf("code=%q, want %q", code, tc.code)
}
action, complete := e.detachWork(42)
if action != tc.action {
t.Fatalf("action=%q, want %q", action, tc.action)
}
complete()
if err := <-done; err != nil {
t.Fatal(err)
}
})
}
}
func TestSettleDrainAndGoalMetCausesAreDistinct(t *testing.T) {
for _, tc := range []struct {
cause *agent.AbortCause
code string
}{
{agent.AbortSettleDrainTimeout, "settle_drain_timeout"},
{agent.AbortGoalMet, "goal_met"},
} {
e := NewEngine(nil)
ctx := e.execContextFor(context.Background(), "t1")
e.cancelExec("t1", tc.cause)
if code, _, _, _ := agent.AbortReason(ctx); code != tc.code {
t.Fatalf("code=%q, want %q", code, tc.code)
}
}
}
func TestExecContextWhilePausedCarriesRaceGuardCause(t *testing.T) {
e := NewEngine(nil)
e.Pause("t1", agent.AbortPausedByUser)
ctx := e.execContextFor(context.Background(), "t1")
if ctx.Err() == nil {
t.Fatal("paused task received a live execution context")
}
if code, _, _, _ := agent.AbortReason(ctx); code != "paused_race_guard" {
t.Fatalf("code=%q, want paused_race_guard", code)
}
}
func TestResumeYieldsFreshContext(t *testing.T) {
e := NewEngine(nil)
old := e.execContextFor(context.Background(), "t1")
e.Pause("t1", agent.AbortPausedByUser)
if old.Err() == nil {
t.Fatal("Pause did not cancel old context")
}
e.paused.Delete("t1")
if fresh := e.execContextFor(context.Background(), "t1"); fresh.Err() != nil {
t.Fatal("resume did not create a fresh execution context")
}
}