aboutsummaryrefslogtreecommitdiff
path: root/internal/cli/worker_pool_test.go
blob: db3edf07f5380eafdf5cfc3ca5fa7cc1e381aea8 (plain) (blame)
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
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))
	}
}