Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

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)
}
}