package shared import ( "context" "net" "strconv" "strings" "testing" "time" ) // startLoopbackListener 在 127.0.0.1 上开一个监听并返回其端口(关闭由 t.Cleanup 托管)。 func startLoopbackListener(t *testing.T) int { t.Helper() ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listen: %v", err) } t.Cleanup(func() { _ = ln.Close() }) return ln.Addr().(*net.TCPAddr).Port } func testDialer() *net.Dialer { return &net.Dialer{Timeout: 3 * time.Second, KeepAlive: 30 * time.Second} } // TestDialFirstReachablePicksReachableIP 验证多候选并发拨号会跳过不可达 IP、 // 选中可达的那个(顺序上不可达在前也不影响结果)。 func TestDialFirstReachablePicksReachableIP(t *testing.T) { port := startLoopbackListener(t) // 127.0.0.2 上没有任何监听 → 该候选必然拨号失败; // 127.0.0.1:port 有监听 → 可达。把坏 IP 放前面,验证不受顺序影响。 conn, err := DialFirstReachable(context.Background(), testDialer(), "tcp", []net.IP{net.ParseIP("127.0.0.2"), net.ParseIP("127.0.0.1")}, strconv.Itoa(port), 6) if err != nil { t.Fatalf("DialFirstReachable returned error: %v", err) } if conn == nil { t.Fatal("DialFirstReachable returned nil conn without error") } // 连接应落在可达的 127.0.0.1 上 remote := conn.RemoteAddr().(*net.TCPAddr) if !remote.IP.Equal(net.ParseIP("127.0.0.1")) { t.Fatalf("expected connection to 127.0.0.1, got %s", remote) } _ = conn.Close() } // TestDialFirstReachableAllFail 验证全部候选不可达时返回错误而非死等或空结果。 func TestDialFirstReachableAllFail(t *testing.T) { // 先开再关,拿到一个确定无监听的端口 ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listen: %v", err) } closedPort := ln.Addr().(*net.TCPAddr).Port _ = ln.Close() start := time.Now() conn, err := DialFirstReachable(context.Background(), testDialer(), "tcp", []net.IP{net.ParseIP("127.0.0.2"), net.ParseIP("127.0.0.1")}, strconv.Itoa(closedPort), 6) if err == nil { if conn != nil { _ = conn.Close() } t.Fatal("expected error when all candidates unreachable, got nil") } if conn != nil { _ = conn.Close() t.Fatal("expected nil conn on total failure") } if elapsed := time.Since(start); elapsed > 15*time.Second { t.Fatalf("all-fail path took too long: %v", elapsed) } } // TestDialFirstReachableCtxCancel 验证父 context 取消后拨号立即返回(不泄漏、不死锁)。 func TestDialFirstReachableCtxCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() // 拨号前已取消 start := time.Now() conn, err := DialFirstReachable(ctx, testDialer(), "tcp", []net.IP{net.ParseIP("127.0.0.1")}, "81", 6) if err == nil { if conn != nil { _ = conn.Close() } t.Fatal("expected error after context cancel, got nil") } if conn != nil { _ = conn.Close() t.Fatal("expected nil conn on cancelled context") } if !strings.Contains(err.Error(), "canceled") && !strings.Contains(err.Error(), "deadline") { t.Fatalf("expected context cancellation error, got: %v", err) } if elapsed := time.Since(start); elapsed > 5*time.Second { t.Fatalf("context cancel path took too long: %v", elapsed) } } // TestDialFirstReachableEmptyCandidates 验证空候选返回错误而非 panic。 func TestDialFirstReachableEmptyCandidates(t *testing.T) { conn, err := DialFirstReachable(context.Background(), testDialer(), "tcp", nil, "80", 6) if err == nil { if conn != nil { _ = conn.Close() } t.Fatal("expected error for empty candidates, got nil") } } // TestDialFirstReachableMaxCandidatesCapped 验证 maxCandidates 截断候选数不会 panic。 func TestDialFirstReachableMaxCandidatesCapped(t *testing.T) { port := startLoopbackListener(t) conn, err := DialFirstReachable(context.Background(), testDialer(), "tcp", []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("127.0.0.1")}, strconv.Itoa(port), 1) if err != nil { t.Fatalf("DialFirstReachable with capped candidates returned error: %v", err) } if conn == nil { t.Fatal("DialFirstReachable returned nil conn without error") } _ = conn.Close() }