aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2022-06-06 15:52:19 -0700
committerGopher Robot <gobot@golang.org>2022-06-07 22:24:02 +0000
commita7551fe24524fb960fbe4cd74dae13afe9ca6a5c (patch)
treebd9437ba0e688b7e111f9aeacc7b213653b6cc45
parent19d71acd978891b201bc5ce79bdcd20b36d04a2e (diff)
downloadgo-a7551fe24524fb960fbe4cd74dae13afe9ca6a5c.tar.gz
go-a7551fe24524fb960fbe4cd74dae13afe9ca6a5c.zip
net: use synthetic network in TestDialParallel
TestDialParallel is testing the Happy Eyeballs algorithm implementation, which dials IPv4 and IPv6 addresses in parallel with the preferred address family getting a head start. This test doesn't care about the actual network operations, just the handling of the parallel connections. Use testHookDialTCP to replace socket creation with a function that returns successfully, with an error, or after context cancellation as required. Limit tests of elapsed times to a check that the fallback deadline has been exceeded in cases where this is expected. This should fix persistent test flakiness. Fixes #52173. Change-Id: Ic93f270fccb63b24a91105a4d541479fc33a2de4 Reviewed-on: https://go-review.googlesource.com/c/go/+/410754 Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Ian Lance Taylor <iant@google.com> Run-TryBot: Damien Neil <dneil@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
-rw-r--r--src/net/dial_test.go174
1 files changed, 71 insertions, 103 deletions
diff --git a/src/net/dial_test.go b/src/net/dial_test.go
index afec31f636..0550acb01d 100644
--- a/src/net/dial_test.go
+++ b/src/net/dial_test.go
@@ -9,6 +9,8 @@ package net
import (
"bufio"
"context"
+ "errors"
+ "fmt"
"internal/testenv"
"io"
"os"
@@ -175,31 +177,9 @@ func dialClosedPort(t *testing.T) (dialLatency time.Duration) {
}
func TestDialParallel(t *testing.T) {
- testenv.MustHaveExternalNetwork(t)
-
- if !supportsIPv4() || !supportsIPv6() {
- t.Skip("both IPv4 and IPv6 are required")
- }
-
- closedPortDelay := dialClosedPort(t)
-
const instant time.Duration = 0
const fallbackDelay = 200 * time.Millisecond
- // Some cases will run quickly when "connection refused" is fast,
- // or trigger the fallbackDelay on Windows. This value holds the
- // lesser of the two delays.
- var closedPortOrFallbackDelay time.Duration
- if closedPortDelay < fallbackDelay {
- closedPortOrFallbackDelay = closedPortDelay
- } else {
- closedPortOrFallbackDelay = fallbackDelay
- }
-
- origTestHookDialTCP := testHookDialTCP
- defer func() { testHookDialTCP = origTestHookDialTCP }()
- testHookDialTCP = slowDialTCP
-
nCopies := func(s string, n int) []string {
out := make([]string, n)
for i := 0; i < n; i++ {
@@ -223,31 +203,21 @@ func TestDialParallel(t *testing.T) {
// Primary is slow; fallback should kick in.
{[]string{slowDst4}, []string{"::1"}, "", true, fallbackDelay},
// Skip a "connection refused" in the primary thread.
- {[]string{"127.0.0.1", "::1"}, []string{}, "tcp4", true, closedPortDelay},
- {[]string{"::1", "127.0.0.1"}, []string{}, "tcp6", true, closedPortDelay},
+ {[]string{"127.0.0.1", "::1"}, []string{}, "tcp4", true, instant},
+ {[]string{"::1", "127.0.0.1"}, []string{}, "tcp6", true, instant},
// Skip a "connection refused" in the fallback thread.
- {[]string{slowDst4, slowDst6}, []string{"::1", "127.0.0.1"}, "tcp6", true, fallbackDelay + closedPortDelay},
+ {[]string{slowDst4, slowDst6}, []string{"::1", "127.0.0.1"}, "tcp6", true, fallbackDelay},
// Primary refused, fallback without delay.
- {[]string{"127.0.0.1"}, []string{"::1"}, "tcp4", true, closedPortOrFallbackDelay},
- {[]string{"::1"}, []string{"127.0.0.1"}, "tcp6", true, closedPortOrFallbackDelay},
+ {[]string{"127.0.0.1"}, []string{"::1"}, "tcp4", true, instant},
+ {[]string{"::1"}, []string{"127.0.0.1"}, "tcp6", true, instant},
// Everything is refused.
- {[]string{"127.0.0.1"}, []string{}, "tcp4", false, closedPortDelay},
+ {[]string{"127.0.0.1"}, []string{}, "tcp4", false, instant},
// Nothing to do; fail instantly.
{[]string{}, []string{}, "", false, instant},
// Connecting to tons of addresses should not trip the deadline.
{nCopies("::1", 1000), []string{}, "", true, instant},
}
- handler := func(dss *dualStackServer, ln Listener) {
- for {
- c, err := ln.Accept()
- if err != nil {
- return
- }
- c.Close()
- }
- }
-
// Convert a list of IP strings into TCPAddrs.
makeAddrs := func(ips []string, port string) addrList {
var out addrList
@@ -262,76 +232,74 @@ func TestDialParallel(t *testing.T) {
}
for i, tt := range testCases {
- dss, err := newDualStackServer()
- if err != nil {
- t.Fatal(err)
- }
- defer dss.teardown()
- if err := dss.buildup(handler); err != nil {
- t.Fatal(err)
- }
- if tt.teardownNetwork != "" {
- // Destroy one of the listening sockets, creating an unreachable port.
- dss.teardownNetwork(tt.teardownNetwork)
- }
+ i, tt := i, tt
+ t.Run(fmt.Sprint(i), func(t *testing.T) {
+ origTestHookDialTCP := testHookDialTCP
+ defer func() { testHookDialTCP = origTestHookDialTCP }()
+ testHookDialTCP = func(ctx context.Context, network string, laddr, raddr *TCPAddr) (*TCPConn, error) {
+ n := "tcp6"
+ if raddr.IP.To4() != nil {
+ n = "tcp4"
+ }
+ if n == tt.teardownNetwork {
+ return nil, errors.New("unreachable")
+ }
+ if r := raddr.IP.String(); r == slowDst4 || r == slowDst6 {
+ <-ctx.Done()
+ return nil, ctx.Err()
+ }
+ return &TCPConn{}, nil
+ }
- primaries := makeAddrs(tt.primaries, dss.port)
- fallbacks := makeAddrs(tt.fallbacks, dss.port)
- d := Dialer{
- FallbackDelay: fallbackDelay,
- }
- startTime := time.Now()
- sd := &sysDialer{
- Dialer: d,
- network: "tcp",
- address: "?",
- }
- c, err := sd.dialParallel(context.Background(), primaries, fallbacks)
- elapsed := time.Since(startTime)
+ primaries := makeAddrs(tt.primaries, "80")
+ fallbacks := makeAddrs(tt.fallbacks, "80")
+ d := Dialer{
+ FallbackDelay: fallbackDelay,
+ }
+ const forever = 60 * time.Minute
+ if tt.expectElapsed == instant {
+ d.FallbackDelay = forever
+ }
+ startTime := time.Now()
+ sd := &sysDialer{
+ Dialer: d,
+ network: "tcp",
+ address: "?",
+ }
+ c, err := sd.dialParallel(context.Background(), primaries, fallbacks)
+ elapsed := time.Since(startTime)
- if c != nil {
- c.Close()
- }
+ if c != nil {
+ c.Close()
+ }
- if tt.expectOk && err != nil {
- t.Errorf("#%d: got %v; want nil", i, err)
- } else if !tt.expectOk && err == nil {
- t.Errorf("#%d: got nil; want non-nil", i)
- }
+ if tt.expectOk && err != nil {
+ t.Errorf("#%d: got %v; want nil", i, err)
+ } else if !tt.expectOk && err == nil {
+ t.Errorf("#%d: got nil; want non-nil", i)
+ }
- // We used to always use 95 milliseconds as the slop,
- // but that was flaky on Windows. See issue 35616.
- slop := 95 * time.Millisecond
- if half := tt.expectElapsed / 2; half > slop {
- slop = half
- }
- expectElapsedMin := tt.expectElapsed - slop
- expectElapsedMax := tt.expectElapsed + slop
- if elapsed < expectElapsedMin {
- t.Errorf("#%d: got %v; want >= %v", i, elapsed, expectElapsedMin)
- } else if elapsed > expectElapsedMax {
- t.Errorf("#%d: got %v; want <= %v", i, elapsed, expectElapsedMax)
- }
+ if elapsed < tt.expectElapsed || elapsed >= forever {
+ t.Errorf("#%d: got %v; want >= %v, < forever", i, elapsed, tt.expectElapsed)
+ }
- // Repeat each case, ensuring that it can be canceled quickly.
- ctx, cancel := context.WithCancel(context.Background())
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- time.Sleep(5 * time.Millisecond)
- cancel()
- wg.Done()
- }()
- startTime = time.Now()
- c, err = sd.dialParallel(ctx, primaries, fallbacks)
- if c != nil {
- c.Close()
- }
- elapsed = time.Now().Sub(startTime)
- if elapsed > 100*time.Millisecond {
- t.Errorf("#%d (cancel): got %v; want <= 100ms", i, elapsed)
- }
- wg.Wait()
+ // Repeat each case, ensuring that it can be canceled.
+ ctx, cancel := context.WithCancel(context.Background())
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ time.Sleep(5 * time.Millisecond)
+ cancel()
+ wg.Done()
+ }()
+ // Ignore errors, since all we care about is that the
+ // call can be canceled.
+ c, _ = sd.dialParallel(ctx, primaries, fallbacks)
+ if c != nil {
+ c.Close()
+ }
+ wg.Wait()
+ })
}
}