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())
23 first := &fakeDriver{stopped: make(chan struct{}), refreshed: make(chan struct{}, 1)}
24 second := &fakeDriver{stopped: make(chan struct{}), refreshed: make(chan struct{}, 1)}
25 connected := make(chan struct{}, 1)
26 finished := make(chan error, 1)
28 finished <- Run(ctx, []Driver{first, second}, connected)
33 connected <- struct{}{}
36 require.Len(t, first.refreshed, 1)
37 require.Len(t, second.refreshed, 1)
40 require.Empty(t, finished)
42 require.NoError(t, <-finished)
48 func TestRunClassifiesStopsAndJoinedFailures(t *testing.T) {
49 failure := errors.New("readback failed")
50 testCases := []struct {
56 {name: "normal completion", err: nil},
57 {name: "cancellation is a stop", err: context.Canceled, canceled: true},
58 {name: "closed transport is a stop", err: net.ErrClosed, canceled: true},
59 {name: "closed pipe is a stop", err: io.ErrClosedPipe, canceled: true},
60 {name: "broken pipe is a stop", err: syscall.EPIPE, canceled: true},
61 {name: "reset connection is a stop", err: syscall.ECONNRESET, canceled: true},
62 {name: "wrapped stop stays a stop", err: fmt.Errorf("read: %w", net.ErrClosed), canceled: true},
63 {name: "joined stops stay stops", err: errors.Join(context.Canceled, net.ErrClosed), canceled: true},
64 {name: "joined failure stays a failure", err: errors.Join(context.Canceled, failure), canceled: true, failure: true},
65 {name: "request timeout stays a failure", err: context.DeadlineExceeded, failure: true},
66 {name: "active transport close is a failure", err: net.ErrClosed, failure: true},
67 {name: "independent failure stays a failure", err: failure, canceled: true, failure: true},
70 for _, tc := range testCases {
71 t.Run(tc.name, func(t *testing.T) {
72 var output bytes.Buffer
74 previous := slog.Default()
76 slog.SetDefault(slog.New(slog.NewTextHandler(&output, nil)))
78 slog.SetDefault(previous)
81 ctx, cancel := context.WithCancel(t.Context())
88 integration := &resultDriver{err: tc.err}
89 err := Run(ctx, []Driver{integration}, nil)
91 require.ErrorIs(t, err, tc.err)
92 require.Contains(t, output.String(), "level=ERROR")
94 require.NoError(t, err)
95 require.Empty(t, output.String())
101 func TestRunCancelsPeersAndKeepsTheirIndependentFailure(t *testing.T) {
102 firstFailure := errors.New("first failed")
103 cleanupFailure := errors.New("cleanup failed")
104 first := &resultDriver{err: firstFailure}
105 second := &fakeDriver{
106 stopped: make(chan struct{}),
107 refreshed: make(chan struct{}, 1),
110 err := Run(t.Context(), []Driver{first, second}, nil)
111 require.ErrorIs(t, err, firstFailure)
112 require.ErrorIs(t, err, cleanupFailure)
116 func TestRunEmptyListWaitsForCancellation(t *testing.T) {
117 synctest.Test(t, func(t *testing.T) {
118 ctx, cancel := context.WithCancel(t.Context())
121 finished := make(chan error, 1)
123 finished <- Run(ctx, nil, nil)
127 require.Empty(t, finished)
129 require.NoError(t, <-finished)
133 type fakeDriver struct {
134 stopped chan struct{}
135 refreshed chan struct{}
139 func (d *fakeDriver) Run(ctx context.Context) error {
142 defer close(d.stopped)
144 return errors.Join(ctx.Err(), d.err)
147 func (d *fakeDriver) Refresh() {
148 d.refreshed <- struct{}{}
151 type resultDriver struct {
155 func (d *resultDriver) Run(_ context.Context) error {
159 func (d *resultDriver) Refresh() {