15 "github.com/stretchr/testify/require"
18 func TestRunRefreshesDriversAndWaitsForCleanup(t *testing.T) {
19 synctest.Test(t, func(t *testing.T) {
20 ctx, cancel := context.WithCancel(t.Context())
24 stopped: make(chan struct{}),
25 refreshed: make(chan struct{}, 1),
27 second := &fakeDriver{
28 stopped: make(chan struct{}),
29 refreshed: make(chan struct{}, 1),
31 connected := make(chan struct{}, 1)
32 finished := make(chan error, 1)
34 finished <- Run(ctx, []Driver{first, second}, connected)
39 connected <- struct{}{}
42 require.Len(t, first.refreshed, 1)
43 require.Len(t, second.refreshed, 1)
46 require.Empty(t, finished)
48 require.NoError(t, <-finished)
54 func TestRunClassifiesStopsAndJoinedFailures(t *testing.T) {
55 failure := errors.New("readback failed")
56 testCases := []struct {
63 name: "normal completion",
67 name: "cancellation is a stop",
68 err: context.Canceled,
72 name: "closed transport is a stop",
77 name: "closed pipe is a stop",
78 err: io.ErrClosedPipe,
82 name: "broken pipe is a stop",
87 name: "reset connection is a stop",
88 err: syscall.ECONNRESET,
92 name: "wrapped stop stays a stop",
93 err: fmt.Errorf("read: %w", net.ErrClosed),
97 name: "joined stops stay stops",
98 err: errors.Join(context.Canceled, net.ErrClosed),
102 name: "joined failure stays a failure",
103 err: errors.Join(context.Canceled, failure),
108 name: "request timeout stays a failure",
109 err: context.DeadlineExceeded,
113 name: "active transport close is a failure",
118 name: "independent failure stays a failure",
125 for _, tc := range testCases {
126 t.Run(tc.name, func(t *testing.T) {
127 var output bytes.Buffer
129 previous := slog.Default()
131 slog.SetDefault(slog.New(slog.NewTextHandler(&output, nil)))
133 slog.SetDefault(previous)
136 ctx, cancel := context.WithCancel(t.Context())
143 integration := &resultDriver{err: tc.err}
144 err := Run(ctx, []Driver{integration}, nil)
146 require.ErrorIs(t, err, tc.err)
147 require.Contains(t, output.String(), "level=ERROR")
149 require.NoError(t, err)
150 require.Empty(t, output.String())
156 func TestRunCancelsPeersAndKeepsTheirIndependentFailure(t *testing.T) {
157 firstFailure := errors.New("first failed")
158 cleanupFailure := errors.New("cleanup failed")
159 first := &resultDriver{err: firstFailure}
160 second := &fakeDriver{
161 stopped: make(chan struct{}),
162 refreshed: make(chan struct{}, 1),
165 err := Run(t.Context(), []Driver{first, second}, nil)
166 require.ErrorIs(t, err, firstFailure)
167 require.ErrorIs(t, err, cleanupFailure)
171 func TestRunEmptyListWaitsForCancellation(t *testing.T) {
172 synctest.Test(t, func(t *testing.T) {
173 ctx, cancel := context.WithCancel(t.Context())
176 finished := make(chan error, 1)
178 finished <- Run(ctx, nil, nil)
182 require.Empty(t, finished)
184 require.NoError(t, <-finished)
188 type fakeDriver struct {
189 stopped chan struct{}
190 refreshed chan struct{}
194 func (d *fakeDriver) Run(ctx context.Context) error {
197 defer close(d.stopped)
199 return errors.Join(ctx.Err(), d.err)
202 func (d *fakeDriver) Refresh() {
203 d.refreshed <- struct{}{}
206 type resultDriver struct {
210 func (d *resultDriver) Run(_ context.Context) error {
214 func (d *resultDriver) Refresh() {