From 4ba335c8eacb3f9e20bccce1643b2e5320d5239d Mon Sep 17 00:00:00 2001 From: Claudomator Agent Date: Thu, 9 Jul 2026 04:39:05 +0000 Subject: feat(task,executor): add CurrentAttempt resolution, wire maybeUnblockParent to resolve through it --- internal/task/currentattempt_test.go | 130 +++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 internal/task/currentattempt_test.go (limited to 'internal/task/currentattempt_test.go') diff --git a/internal/task/currentattempt_test.go b/internal/task/currentattempt_test.go new file mode 100644 index 0000000..76661d8 --- /dev/null +++ b/internal/task/currentattempt_test.go @@ -0,0 +1,130 @@ +package task + +import ( + "errors" + "testing" +) + +type fakeLookup struct { + tasks map[string]*Task +} + +func newFakeLookup() *fakeLookup { + return &fakeLookup{tasks: make(map[string]*Task)} +} + +func (f *fakeLookup) GetTask(id string) (*Task, error) { + t, ok := f.tasks[id] + if !ok { + return nil, errors.New("not found") + } + return t, nil +} + +func (f *fakeLookup) ListDependents(taskID string) ([]*Task, error) { + var out []*Task + for _, t := range f.tasks { + for _, d := range t.DependsOn { + if d == taskID { + out = append(out, t) + break + } + } + } + return out, nil +} + +func TestCurrentAttempt_NoFixAttempt_ReturnsAnchorItself(t *testing.T) { + lookup := newFakeLookup() + lookup.tasks["a"] = &Task{ID: "a"} + + got, err := CurrentAttempt(lookup, "a") + if err != nil { + t.Fatalf("CurrentAttempt: %v", err) + } + if got.ID != "a" { + t.Errorf("got %q, want a", got.ID) + } +} + +func TestCurrentAttempt_OneFixAttempt_ResolvesToIt(t *testing.T) { + lookup := newFakeLookup() + lookup.tasks["a"] = &Task{ID: "a", ParentTaskID: "parent-1"} + lookup.tasks["a-fix1"] = &Task{ID: "a-fix1", ParentTaskID: "parent-1", DependsOn: []string{"a"}} + + got, err := CurrentAttempt(lookup, "a") + if err != nil { + t.Fatalf("CurrentAttempt: %v", err) + } + if got.ID != "a-fix1" { + t.Errorf("got %q, want a-fix1", got.ID) + } +} + +func TestCurrentAttempt_ChainOfFixAttempts_ResolvesToTip(t *testing.T) { + lookup := newFakeLookup() + lookup.tasks["a"] = &Task{ID: "a", ParentTaskID: "parent-1"} + lookup.tasks["a-fix1"] = &Task{ID: "a-fix1", ParentTaskID: "parent-1", DependsOn: []string{"a"}} + lookup.tasks["a-fix2"] = &Task{ID: "a-fix2", ParentTaskID: "parent-1", DependsOn: []string{"a-fix1"}} + + got, err := CurrentAttempt(lookup, "a") + if err != nil { + t.Fatalf("CurrentAttempt: %v", err) + } + if got.ID != "a-fix2" { + t.Errorf("got %q, want a-fix2 (tip of the chain)", got.ID) + } + + // Resolving from an intermediate link in the chain also lands on the tip. + got, err = CurrentAttempt(lookup, "a-fix1") + if err != nil { + t.Fatalf("CurrentAttempt: %v", err) + } + if got.ID != "a-fix2" { + t.Errorf("resolving from a-fix1: got %q, want a-fix2", got.ID) + } +} + +// TestCurrentAttempt_DependentWithDifferentParent_NotTreatedAsFixAttempt +// proves a task that depends on "a" but has a DIFFERENT ParentTaskID (e.g. +// an unrelated Evaluator task depending on a Builder for scheduling reasons) +// must not be mistaken for a's fix attempt. +func TestCurrentAttempt_DependentWithDifferentParent_NotTreatedAsFixAttempt(t *testing.T) { + lookup := newFakeLookup() + lookup.tasks["a"] = &Task{ID: "a", ParentTaskID: "parent-1"} + lookup.tasks["unrelated"] = &Task{ID: "unrelated", ParentTaskID: "", DependsOn: []string{"a"}} + + got, err := CurrentAttempt(lookup, "a") + if err != nil { + t.Fatalf("CurrentAttempt: %v", err) + } + if got.ID != "a" { + t.Errorf("got %q, want a (unrelated dependent must not be treated as a fix attempt)", got.ID) + } +} + +// TestCurrentAttempt_DependentWithMultipleDependsOn_NotTreatedAsFixAttempt +// proves a fix attempt has exactly one DependsOn entry (the task it +// supersedes); a task depending on "a" among several dependencies (e.g. an +// Arbitration task depending on all 4 Evaluators) must not be mistaken for +// a's fix attempt. +func TestCurrentAttempt_DependentWithMultipleDependsOn_NotTreatedAsFixAttempt(t *testing.T) { + lookup := newFakeLookup() + lookup.tasks["a"] = &Task{ID: "a", ParentTaskID: "parent-1"} + lookup.tasks["multi-dep"] = &Task{ID: "multi-dep", ParentTaskID: "parent-1", DependsOn: []string{"a", "b"}} + + got, err := CurrentAttempt(lookup, "a") + if err != nil { + t.Fatalf("CurrentAttempt: %v", err) + } + if got.ID != "a" { + t.Errorf("got %q, want a", got.ID) + } +} + +func TestCurrentAttempt_UnknownAnchor_ReturnsError(t *testing.T) { + lookup := newFakeLookup() + if _, err := CurrentAttempt(lookup, "does-not-exist"); err == nil { + t.Error("expected an error for an unknown anchor ID") + } +} -- cgit v1.2.3