mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
Implements the chatd stabilization RFC. Combines: - https://github.com/coder/coder/pull/25908 - https://github.com/coder/coder/pull/25923 - https://github.com/coder/coder/pull/26109 - https://github.com/coder/coder/pull/26110 - https://github.com/coder/coder/pull/26111 - https://github.com/coder/coder/pull/26112
149 lines
3.8 KiB
Go
149 lines
3.8 KiB
Go
package chatd //nolint:testpackage // Exercises unexported generation retry helpers.
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/xerrors"
|
|
)
|
|
|
|
func TestTerminalGeneration(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
require.Nil(t, terminalGeneration(nil))
|
|
|
|
cause := xerrors.New("boom")
|
|
wrapped := terminalGeneration(cause)
|
|
require.True(t, isTerminalGeneration(wrapped))
|
|
require.ErrorIs(t, wrapped, cause)
|
|
require.ErrorIs(t, wrapped, errTerminalGeneration)
|
|
require.Equal(t, cause.Error(), wrapped.Error())
|
|
|
|
require.False(t, isTerminalGeneration(cause))
|
|
require.False(t, isTerminalGeneration(nil))
|
|
}
|
|
|
|
func TestGenerationPhaseBackoff(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
require.Equal(t, generationPhaseBaseBackoff, generationPhaseBackoff(0))
|
|
require.Equal(t, 2*generationPhaseBaseBackoff, generationPhaseBackoff(1))
|
|
require.Equal(t, 4*generationPhaseBaseBackoff, generationPhaseBackoff(2))
|
|
}
|
|
|
|
func TestRetryGenerationPhase(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
noopWait := func(context.Context, time.Duration) error { return nil }
|
|
|
|
t.Run("SuccessFirstTry", func(t *testing.T) {
|
|
t.Parallel()
|
|
calls := 0
|
|
waits := 0
|
|
wait := func(context.Context, time.Duration) error {
|
|
waits++
|
|
return nil
|
|
}
|
|
got, err := retryGenerationPhase(context.Background(), wait, func() (int, error) {
|
|
calls++
|
|
return 42, nil
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, 42, got)
|
|
require.Equal(t, 1, calls)
|
|
require.Equal(t, 0, waits)
|
|
})
|
|
|
|
t.Run("RetryThenSuccess", func(t *testing.T) {
|
|
t.Parallel()
|
|
calls := 0
|
|
waits := 0
|
|
var delays []time.Duration
|
|
wait := func(_ context.Context, d time.Duration) error {
|
|
waits++
|
|
delays = append(delays, d)
|
|
return nil
|
|
}
|
|
got, err := retryGenerationPhase(context.Background(), wait, func() (string, error) {
|
|
calls++
|
|
if calls < 2 {
|
|
return "", xerrors.New("transient")
|
|
}
|
|
return "ok", nil
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "ok", got)
|
|
require.Equal(t, 2, calls)
|
|
require.Equal(t, 1, waits)
|
|
require.Equal(t, []time.Duration{generationPhaseBackoff(0)}, delays)
|
|
})
|
|
|
|
t.Run("ExhaustsAndReturnsLastError", func(t *testing.T) {
|
|
t.Parallel()
|
|
calls := 0
|
|
waits := 0
|
|
wait := func(context.Context, time.Duration) error {
|
|
waits++
|
|
return nil
|
|
}
|
|
_, err := retryGenerationPhase(context.Background(), wait, func() (int, error) {
|
|
calls++
|
|
return 0, xerrors.Errorf("attempt %d", calls)
|
|
})
|
|
require.EqualError(t, err, "attempt 3")
|
|
require.Equal(t, generationPhaseMaxAttempts, calls)
|
|
require.Equal(t, generationPhaseMaxAttempts-1, waits)
|
|
})
|
|
|
|
t.Run("TerminalShortCircuits", func(t *testing.T) {
|
|
t.Parallel()
|
|
calls := 0
|
|
waits := 0
|
|
wait := func(context.Context, time.Duration) error {
|
|
waits++
|
|
return nil
|
|
}
|
|
cause := xerrors.New("deterministic")
|
|
_, err := retryGenerationPhase(context.Background(), wait, func() (int, error) {
|
|
calls++
|
|
return 0, terminalGeneration(cause)
|
|
})
|
|
require.ErrorIs(t, err, cause)
|
|
require.True(t, isTerminalGeneration(err))
|
|
require.Equal(t, 1, calls)
|
|
require.Equal(t, 0, waits)
|
|
})
|
|
|
|
t.Run("ContextCanceledExitsCleanly", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
calls := 0
|
|
_, err := retryGenerationPhase(ctx, noopWait, func() (int, error) {
|
|
calls++
|
|
return 0, xerrors.New("transient")
|
|
})
|
|
require.ErrorIs(t, err, errTaskExpectedExit)
|
|
require.Equal(t, 1, calls)
|
|
})
|
|
|
|
t.Run("WaitCancellationExitsCleanly", func(t *testing.T) {
|
|
t.Parallel()
|
|
calls := 0
|
|
waits := 0
|
|
wait := func(context.Context, time.Duration) error {
|
|
waits++
|
|
return errTaskExpectedExit
|
|
}
|
|
_, err := retryGenerationPhase(context.Background(), wait, func() (int, error) {
|
|
calls++
|
|
return 0, xerrors.New("transient")
|
|
})
|
|
require.ErrorIs(t, err, errTaskExpectedExit)
|
|
require.Equal(t, 1, calls)
|
|
require.Equal(t, 1, waits)
|
|
})
|
|
}
|