summaryrefslogtreecommitdiff
path: root/internal/task/currentattempt_test.go
blob: 58f7b08da5f0184e4a37ffed9ef52cff11c18d4b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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)
	}
}

// TestCurrentAttempt_DependentWithDifferentRole_NotTreatedAsFixAttempt proves
// a top-level evaluator task depending on a top-level builder task (both with
// ParentTaskID=="") is not mistaken for the builder's fix attempt, because its
// role differs from the builder's. This is the story-orchestrator scenario:
// builder (role="builder") has evaluator dependents (role="evaluator_quality"
// etc.) and later a fix-attempt (role="builder") -- only the fix-attempt must
// be resolved as "next", not the evaluators.
func TestCurrentAttempt_DependentWithDifferentRole_NotTreatedAsFixAttempt(t *testing.T) {
	lookup := newFakeLookup()
	lookup.tasks["builder"] = &Task{ID: "builder", ParentTaskID: "", Agent: AgentConfig{Role: "builder"}}
	lookup.tasks["evaluator"] = &Task{ID: "evaluator", ParentTaskID: "", Agent: AgentConfig{Role: "evaluator_quality"}, DependsOn: []string{"builder"}}

	got, err := CurrentAttempt(lookup, "builder")
	if err != nil {
		t.Fatalf("CurrentAttempt: %v", err)
	}
	if got.ID != "builder" {
		t.Errorf("got %q, want builder (evaluator with different role must not be treated as a fix attempt)", got.ID)
	}
}

// TestCurrentAttempt_FixAttemptWithSameRole_ResolvedCorrectly proves that a
// fix-attempt task with the same role as the anchor IS resolved as "next",
// even when evaluators with different roles also depend on the anchor.
func TestCurrentAttempt_FixAttemptWithSameRole_ResolvedCorrectly(t *testing.T) {
	lookup := newFakeLookup()
	lookup.tasks["builder"] = &Task{ID: "builder", ParentTaskID: "", Agent: AgentConfig{Role: "builder"}}
	lookup.tasks["evaluator"] = &Task{ID: "evaluator", ParentTaskID: "", Agent: AgentConfig{Role: "evaluator_quality"}, DependsOn: []string{"builder"}}
	lookup.tasks["fix1"] = &Task{ID: "fix1", ParentTaskID: "", Agent: AgentConfig{Role: "builder"}, DependsOn: []string{"builder"}}

	got, err := CurrentAttempt(lookup, "builder")
	if err != nil {
		t.Fatalf("CurrentAttempt: %v", err)
	}
	if got.ID != "fix1" {
		t.Errorf("got %q, want fix1 (same-role fix attempt must be resolved as current)", 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")
	}
}