]> Cypherpunks repositories - gostls13.git/commitdiff
net: use synthetic network in TestDialParallel
authorDamien Neil <dneil@google.com>
Mon, 6 Jun 2022 22:52:19 +0000 (15:52 -0700)
committerGopher Robot <gobot@golang.org>
Tue, 7 Jun 2022 22:24:02 +0000 (22:24 +0000)
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>

src/net/dial_test.go

index afec31f636ec92e289501500687aaddb569ed723..0550acb01dbed3c35204a806a9e0ee36e6758d1e 100644 (file)
@@ -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()
+               })
        }
 }