package driver import ( "bytes" "context" "errors" "fmt" "io" "log/slog" "net" "syscall" "testing" "testing/synctest" "github.com/stretchr/testify/require" ) func TestRunRefreshesDriversAndWaitsForCleanup(t *testing.T) { synctest.Test(t, func(t *testing.T) { ctx, cancel := context.WithCancel(t.Context()) defer cancel() first := &fakeDriver{ stopped: make(chan struct{}), refreshed: make(chan struct{}, 1), } second := &fakeDriver{ stopped: make(chan struct{}), refreshed: make(chan struct{}, 1), } connected := make(chan struct{}, 1) finished := make(chan error, 1) go func() { finished <- Run(ctx, []Driver{first, second}, connected) }() synctest.Wait() connected <- struct{}{} synctest.Wait() require.Len(t, first.refreshed, 1) require.Len(t, second.refreshed, 1) close(connected) synctest.Wait() require.Empty(t, finished) cancel() require.NoError(t, <-finished) <-first.stopped <-second.stopped }) } func TestRunClassifiesStopsAndJoinedFailures(t *testing.T) { failure := errors.New("readback failed") testCases := []struct { name string err error canceled bool failure bool }{ { name: "normal completion", err: nil, }, { name: "cancellation is a stop", err: context.Canceled, canceled: true, }, { name: "closed transport is a stop", err: net.ErrClosed, canceled: true, }, { name: "closed pipe is a stop", err: io.ErrClosedPipe, canceled: true, }, { name: "broken pipe is a stop", err: syscall.EPIPE, canceled: true, }, { name: "reset connection is a stop", err: syscall.ECONNRESET, canceled: true, }, { name: "wrapped stop stays a stop", err: fmt.Errorf("read: %w", net.ErrClosed), canceled: true, }, { name: "joined stops stay stops", err: errors.Join(context.Canceled, net.ErrClosed), canceled: true, }, { name: "joined failure stays a failure", err: errors.Join(context.Canceled, failure), canceled: true, failure: true, }, { name: "request timeout stays a failure", err: context.DeadlineExceeded, failure: true, }, { name: "active transport close is a failure", err: net.ErrClosed, failure: true, }, { name: "independent failure stays a failure", err: failure, canceled: true, failure: true, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var output bytes.Buffer previous := slog.Default() slog.SetDefault(slog.New(slog.NewTextHandler(&output, nil))) t.Cleanup(func() { slog.SetDefault(previous) }) ctx, cancel := context.WithCancel(t.Context()) defer cancel() if tc.canceled { cancel() } integration := &resultDriver{err: tc.err} err := Run(ctx, []Driver{integration}, nil) if tc.failure { require.ErrorIs(t, err, tc.err) require.Contains(t, output.String(), "level=ERROR") } else { require.NoError(t, err) require.Empty(t, output.String()) } }) } } func TestRunCancelsPeersAndKeepsTheirIndependentFailure(t *testing.T) { firstFailure := errors.New("first failed") cleanupFailure := errors.New("cleanup failed") first := &resultDriver{err: firstFailure} second := &fakeDriver{ stopped: make(chan struct{}), refreshed: make(chan struct{}, 1), err: cleanupFailure, } err := Run(t.Context(), []Driver{first, second}, nil) require.ErrorIs(t, err, firstFailure) require.ErrorIs(t, err, cleanupFailure) <-second.stopped } func TestRunEmptyListWaitsForCancellation(t *testing.T) { synctest.Test(t, func(t *testing.T) { ctx, cancel := context.WithCancel(t.Context()) defer cancel() finished := make(chan error, 1) go func() { finished <- Run(ctx, nil, nil) }() synctest.Wait() require.Empty(t, finished) cancel() require.NoError(t, <-finished) }) } type fakeDriver struct { stopped chan struct{} refreshed chan struct{} err error } func (d *fakeDriver) Run(ctx context.Context) error { <-ctx.Done() defer close(d.stopped) return errors.Join(ctx.Err(), d.err) } func (d *fakeDriver) Refresh() { d.refreshed <- struct{}{} } type resultDriver struct { err error } func (d *resultDriver) Run(_ context.Context) error { return d.err } func (d *resultDriver) Refresh() { }