package cli import ( "context" "errors" "testing" ) // TestWorkerPoolFailFastSwallowsCancellation locks in the ยง8.3 invariant that a // fail-fast pool reports only GENUINE failures: the first real error cancels the // pool, and the in-flight siblings that consequently return context.Canceled are // swallowed (cancellation collateral is never an independent per-file failure). func TestWorkerPoolFailFastSwallowsCancellation(t *testing.T) { pool := newWorkerPool(context.Background(), 8, true) // Occupy several slots with tasks that block until the pool is cancelled and // then return context.Canceled (the collateral that must be swallowed). const blockers = 5 for i := 0; i < blockers; i++ { pool.submit("block", func(ctx context.Context) error { <-ctx.Done() return ctx.Err() // context.Canceled }) } // One genuine failure triggers fail-fast cancellation of the blockers above. pool.submit("fail", func(ctx context.Context) error { return fileError{path: "a.js", err: errors.New("boom")} }) succeeded, errs := pool.wait() if succeeded != 0 { t.Fatalf("succeeded = %d, want 0", succeeded) } if len(errs) != 1 { t.Fatalf("errs = %d (%v), want exactly 1 genuine failure", len(errs), errs) } if errors.Is(errs[0], context.Canceled) { t.Fatalf("reported error is cancellation collateral, want the genuine failure: %v", errs[0]) } var fe fileError if !errors.As(errs[0], &fe) || fe.path != "a.js" { t.Fatalf("reported error = %v, want fileError for a.js", errs[0]) } } // TestWorkerPoolDrainReportsAll confirms the non-fail-fast (drain-and-report) // mode collects every failure and still runs all tasks. func TestWorkerPoolDrainReportsAll(t *testing.T) { pool := newWorkerPool(context.Background(), 4, false) const tasks = 6 for i := 0; i < tasks; i++ { odd := i%2 == 1 pool.submit("t", func(ctx context.Context) error { if odd { return errors.New("fail") } return nil }) } succeeded, errs := pool.wait() if succeeded != 3 { t.Fatalf("succeeded = %d, want 3", succeeded) } if len(errs) != 3 { t.Fatalf("errs = %d, want 3", len(errs)) } }