summaryrefslogtreecommitdiff
path: root/internal/executor/executor_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/executor/executor_test.go')
-rw-r--r--internal/executor/executor_test.go51
1 files changed, 51 insertions, 0 deletions
diff --git a/internal/executor/executor_test.go b/internal/executor/executor_test.go
index e2b9b44..d505a3a 100644
--- a/internal/executor/executor_test.go
+++ b/internal/executor/executor_test.go
@@ -1169,6 +1169,57 @@ func TestPool_RecoverStaleBlocked_PromotesNestedSubtaskParent(t *testing.T) {
}
}
+// TestPool_MaybeUnblockParent_ResolvesSubtaskThroughCurrentAttempt proves
+// the parent's "are all subtasks done" gate resolves each subtask through
+// task.CurrentAttempt first -- so a subtask that was superseded by a
+// fix-attempt sibling (same ParentTaskID, DependsOn=[original]) correctly
+// waits for the FIX ATTEMPT to complete, not just the stale original that
+// already reached COMPLETED before being superseded.
+func TestPool_MaybeUnblockParent_ResolvesSubtaskThroughCurrentAttempt(t *testing.T) {
+ store := testStore(t)
+ logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
+ pool := NewPool(2, map[string]Runner{"claude": &mockRunner{}}, store, logger)
+
+ parent := makeTask("resolve-parent")
+ parent.State = task.StateBlocked
+ store.CreateTask(parent)
+
+ original := makeTask("resolve-original")
+ original.ParentTaskID = parent.ID
+ original.State = task.StateCompleted // rejected, but its own execution finished
+ store.CreateTask(original)
+
+ fixAttempt := makeTask("resolve-fix-attempt")
+ fixAttempt.ParentTaskID = parent.ID
+ fixAttempt.DependsOn = []string{original.ID}
+ fixAttempt.State = task.StateRunning // still in progress
+ store.CreateTask(fixAttempt)
+
+ pool.maybeUnblockParent(parent.ID)
+
+ got, err := store.GetTask(parent.ID)
+ if err != nil {
+ t.Fatalf("get parent: %v", err)
+ }
+ if got.State != task.StateBlocked {
+ t.Errorf("parent state: want still BLOCKED (fix attempt not done yet), got %v", got.State)
+ }
+
+ // Now the fix attempt finishes.
+ if err := store.UpdateTaskState(fixAttempt.ID, task.StateCompleted); err != nil {
+ t.Fatalf("complete fix attempt: %v", err)
+ }
+ pool.maybeUnblockParent(parent.ID)
+
+ got, err = store.GetTask(parent.ID)
+ if err != nil {
+ t.Fatalf("get parent: %v", err)
+ }
+ if got.State != task.StateReady {
+ t.Errorf("parent state: want READY (fix attempt now done), got %v", got.State)
+ }
+}
+
// TestPool_Submit_ParentNotBlocked_NoTransition verifies that completing a subtask
// does not change the parent's state when the parent is not BLOCKED.
func TestPool_Submit_ParentNotBlocked_NoTransition(t *testing.T) {