// upstream_timeout_test.go 钉住 isUpstreamTimeout 的三态判定口径:超时和 // "网络抖动"此前共用同一条换号路径——注定超时的请求会轮转 MaxRotate 次, // 期间还给一串健康号喂连败计数。移植自 OkRoromori 分支(七例对照)。 package server import ( "context" "errors" "fmt" "net" "testing" ) type timeoutErr struct{ timeout bool } func (e timeoutErr) Error() string { return "stub" } func (e timeoutErr) Timeout() bool { return e.timeout } func (e timeoutErr) Temporary() bool { return e.timeout } func TestIsUpstreamTimeout(t *testing.T) { cases := []struct { name string err error clientGone bool want bool }{ {"net.Error 超时", timeoutErr{true}, false, true}, {"net.Error 非超时(连接被拒)", timeoutErr{false}, false, false}, {"显式 deadline", fmt.Errorf("read: %w", context.DeadlineExceeded), false, true}, {"客户端断连(ctx 已取消)", context.Canceled, true, false}, {"空闲看门狗掐流(客户端仍在)", context.Canceled, false, true}, {"普通错误", errors.New("boom"), false, false}, {"nil", nil, false, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { if got := isUpstreamTimeout(c.err, c.clientGone); got != c.want { t.Fatalf("isUpstreamTimeout(%v, clientGone=%v) = %v, want %v", c.err, c.clientGone, got, c.want) } }) } // 保证 stub 真的实现了 net.Error(否则上面的用例会静默走不到 Timeout 分支) var ne net.Error = timeoutErr{true} _ = ne }