a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
126 lines
4.4 KiB
Go
126 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/zzet/gortex/internal/daemon"
|
|
)
|
|
|
|
// withFastDialRetry shortens the retry cadence so tests don't sleep on the
|
|
// production window, restoring the originals on cleanup.
|
|
func withFastDialRetry(t *testing.T, window time.Duration) {
|
|
t.Helper()
|
|
origDial, origWin, origInt := dialDaemon, proxyDialRetryWindow, proxyDialRetryInterval
|
|
proxyDialRetryWindow = window
|
|
proxyDialRetryInterval = time.Millisecond
|
|
t.Cleanup(func() {
|
|
dialDaemon = origDial
|
|
proxyDialRetryWindow = origWin
|
|
proxyDialRetryInterval = origInt
|
|
})
|
|
}
|
|
|
|
func TestDialDaemonWithRetry_SuccessFirstTry(t *testing.T) {
|
|
withFastDialRetry(t, time.Second)
|
|
var calls int32
|
|
dialDaemon = func(daemon.Handshake) (*daemon.Client, error) {
|
|
atomic.AddInt32(&calls, 1)
|
|
return &daemon.Client{}, nil
|
|
}
|
|
client, recoverable, err := dialDaemonWithRetry(context.Background(), daemon.Handshake{})
|
|
if client == nil || recoverable || err != nil {
|
|
t.Fatalf("want (client, false, nil), got (%v, %v, %v)", client, recoverable, err)
|
|
}
|
|
if got := atomic.LoadInt32(&calls); got != 1 {
|
|
t.Fatalf("want 1 dial, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestDialDaemonWithRetry_RetriesThenConnects(t *testing.T) {
|
|
withFastDialRetry(t, 5*time.Second)
|
|
var calls int32
|
|
dialDaemon = func(daemon.Handshake) (*daemon.Client, error) {
|
|
// Fail the first two attempts as "unavailable" (socket up but accept()
|
|
// starved by warmup), then connect — the exact race the retry exists
|
|
// to ride out.
|
|
if atomic.AddInt32(&calls, 1) < 3 {
|
|
return nil, fmt.Errorf("%w: connection refused", daemon.ErrDaemonUnavailable)
|
|
}
|
|
return &daemon.Client{}, nil
|
|
}
|
|
client, recoverable, err := dialDaemonWithRetry(context.Background(), daemon.Handshake{})
|
|
if client == nil || recoverable || err != nil {
|
|
t.Fatalf("want (client, false, nil), got (%v, %v, %v)", client, recoverable, err)
|
|
}
|
|
if got := atomic.LoadInt32(&calls); got != 3 {
|
|
t.Fatalf("want 3 dials, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestDialDaemonWithRetry_WindowExpiry(t *testing.T) {
|
|
// A tiny window so the loop concedes almost immediately.
|
|
withFastDialRetry(t, 5*time.Millisecond)
|
|
dialDaemon = func(daemon.Handshake) (*daemon.Client, error) {
|
|
return nil, fmt.Errorf("%w: connection refused", daemon.ErrDaemonUnavailable)
|
|
}
|
|
client, recoverable, err := dialDaemonWithRetry(context.Background(), daemon.Handshake{})
|
|
if client != nil || !recoverable {
|
|
t.Fatalf("want (nil, true, err), got (%v, %v, %v)", client, recoverable, err)
|
|
}
|
|
if !errors.Is(err, daemon.ErrDaemonUnavailable) {
|
|
t.Fatalf("want ErrDaemonUnavailable, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDialDaemonWithRetry_ProtocolMismatchConcedesImmediately(t *testing.T) {
|
|
withFastDialRetry(t, 5*time.Second)
|
|
var calls int32
|
|
dialDaemon = func(daemon.Handshake) (*daemon.Client, error) {
|
|
atomic.AddInt32(&calls, 1)
|
|
return nil, fmt.Errorf("%w: stale", daemon.ErrProtocolVersionMismatch)
|
|
}
|
|
client, recoverable, err := dialDaemonWithRetry(context.Background(), daemon.Handshake{})
|
|
if client != nil || !recoverable || !errors.Is(err, daemon.ErrProtocolVersionMismatch) {
|
|
t.Fatalf("want (nil, true, mismatch), got (%v, %v, %v)", client, recoverable, err)
|
|
}
|
|
// A mismatch never resolves by waiting — must not retry.
|
|
if got := atomic.LoadInt32(&calls); got != 1 {
|
|
t.Fatalf("want 1 dial (no retry on mismatch), got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestDialDaemonWithRetry_NonRecoverableSurfaced(t *testing.T) {
|
|
withFastDialRetry(t, 5*time.Second)
|
|
var calls int32
|
|
boom := errors.New("permission denied")
|
|
dialDaemon = func(daemon.Handshake) (*daemon.Client, error) {
|
|
atomic.AddInt32(&calls, 1)
|
|
return nil, boom
|
|
}
|
|
client, recoverable, err := dialDaemonWithRetry(context.Background(), daemon.Handshake{})
|
|
if client != nil || recoverable || !errors.Is(err, boom) {
|
|
t.Fatalf("want (nil, false, boom), got (%v, %v, %v)", client, recoverable, err)
|
|
}
|
|
if got := atomic.LoadInt32(&calls); got != 1 {
|
|
t.Fatalf("want 1 dial (no retry on a real error), got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestDialDaemonWithRetry_ContextCancelConcedes(t *testing.T) {
|
|
withFastDialRetry(t, 10*time.Second)
|
|
dialDaemon = func(daemon.Handshake) (*daemon.Client, error) {
|
|
return nil, fmt.Errorf("%w: connection refused", daemon.ErrDaemonUnavailable)
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
client, recoverable, err := dialDaemonWithRetry(ctx, daemon.Handshake{})
|
|
if client != nil || !recoverable {
|
|
t.Fatalf("want (nil, true, err) on cancel, got (%v, %v, %v)", client, recoverable, err)
|
|
}
|
|
}
|