diff options
Diffstat (limited to 'internal/executor/executor_test.go')
| -rw-r--r-- | internal/executor/executor_test.go | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/internal/executor/executor_test.go b/internal/executor/executor_test.go index 91f7636..9896ba1 100644 --- a/internal/executor/executor_test.go +++ b/internal/executor/executor_test.go @@ -684,6 +684,127 @@ func TestPool_Submit_TopLevel_WithSubtasks_GoesBlocked(t *testing.T) { } } +// TestPool_Submit_LastSubtask_UnblocksParent verifies that when the last +// remaining subtask completes, the parent task transitions from BLOCKED to READY. +func TestPool_Submit_LastSubtask_UnblocksParent(t *testing.T) { + store := testStore(t) + runner := &mockRunner{} + runners := map[string]Runner{"claude": runner} + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) + pool := NewPool(2, runners, store, logger) + + // Parent starts BLOCKED (waiting for subtasks). + parent := makeTask("unblock-parent-1") + parent.State = task.StateBlocked + store.CreateTask(parent) + + // First subtask already completed. + sub1 := makeTask("unblock-sub-1a") + sub1.ParentTaskID = parent.ID + sub1.State = task.StateCompleted + store.CreateTask(sub1) + + // Second (last) subtask — the one we submit. + sub2 := makeTask("unblock-sub-1b") + sub2.ParentTaskID = parent.ID + store.CreateTask(sub2) + + if err := pool.Submit(context.Background(), sub2); err != nil { + t.Fatalf("submit: %v", err) + } + + result := <-pool.Results() + if result.Err != nil { + t.Errorf("expected no error, got: %v", result.Err) + } + if result.Execution.Status != "COMPLETED" { + t.Errorf("subtask status: want COMPLETED, got %q", result.Execution.Status) + } + + // Parent must now be READY. + 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, got %v", got.State) + } +} + +// TestPool_Submit_NotLastSubtask_ParentStaysBlocked verifies that when a subtask +// completes but another sibling subtask is still running, the parent stays BLOCKED. +func TestPool_Submit_NotLastSubtask_ParentStaysBlocked(t *testing.T) { + store := testStore(t) + runner := &mockRunner{} + runners := map[string]Runner{"claude": runner} + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) + pool := NewPool(2, runners, store, logger) + + parent := makeTask("blocked-parent-2") + parent.State = task.StateBlocked + store.CreateTask(parent) + + // First subtask still RUNNING — not done yet. + sub1 := makeTask("blocked-sub-2a") + sub1.ParentTaskID = parent.ID + sub1.State = task.StateRunning + store.CreateTask(sub1) + + // Second subtask — the one we submit. + sub2 := makeTask("blocked-sub-2b") + sub2.ParentTaskID = parent.ID + store.CreateTask(sub2) + + if err := pool.Submit(context.Background(), sub2); err != nil { + t.Fatalf("submit: %v", err) + } + + <-pool.Results() + + // Parent must remain BLOCKED because sub1 is still RUNNING. + 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 BLOCKED, 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) { + store := testStore(t) + runner := &mockRunner{} + runners := map[string]Runner{"claude": runner} + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) + pool := NewPool(2, runners, store, logger) + + // Parent is already READY (not BLOCKED). + parent := makeTask("ready-parent-3") + parent.State = task.StateReady + store.CreateTask(parent) + + sub1 := makeTask("ready-sub-3a") + sub1.ParentTaskID = parent.ID + store.CreateTask(sub1) + + if err := pool.Submit(context.Background(), sub1); err != nil { + t.Fatalf("submit: %v", err) + } + + <-pool.Results() + + // Parent must remain READY — no spurious state transition. + 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, got %v", got.State) + } +} + func TestPool_UnsupportedAgent(t *testing.T) { store := testStore(t) runners := map[string]Runner{"claude": &mockRunner{}} |
