Skip to content

Commit

Permalink
net: deflake dual IP stack tests
Browse files Browse the repository at this point in the history
This change deflakes TestDialerDualStackFDLeak, TestDialerDualStack,
TestResolve{TCP,UDP,IP}Addr by removing external dependencies.

Fixes golang#8764.

Change-Id: I5cca0a93776cf05652e0e6a4a4ff4af392ccb885
Reviewed-on: https://go-review.googlesource.com/8485
Reviewed-by: Ian Lance Taylor <[email protected]>
  • Loading branch information
cixtor committed Apr 7, 2015
1 parent f8bcebe commit 5a83f06
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 166 deletions.
142 changes: 53 additions & 89 deletions src/net/dial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@
package net

import (
"bytes"
"fmt"
"net/internal/socktest"
"os"
"os/exec"
"reflect"
"regexp"
"runtime"
"strconv"
"sync"
"testing"
"time"
Expand Down Expand Up @@ -216,95 +212,63 @@ func TestDialTimeoutFDLeak(t *testing.T) {
}
}

func numTCP() (ntcp, nopen, nclose int, err error) {
lsof, err := exec.Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output()
if err != nil {
return 0, 0, 0, err
}
ntcp += bytes.Count(lsof, []byte("TCP"))
for _, state := range []string{"LISTEN", "SYN_SENT", "SYN_RECEIVED", "ESTABLISHED"} {
nopen += bytes.Count(lsof, []byte(state))
}
for _, state := range []string{"CLOSED", "CLOSE_WAIT", "LAST_ACK", "FIN_WAIT_1", "FIN_WAIT_2", "CLOSING", "TIME_WAIT"} {
nclose += bytes.Count(lsof, []byte(state))
func TestDialerDualStackFDLeak(t *testing.T) {
switch runtime.GOOS {
case "plan9":
t.Skipf("%s does not have full support of socktest", runtime.GOOS)
case "windows":
t.Skipf("not implemented a way to cancel dial racers in TCP SYN-SENT state on %s", runtime.GOOS)
}
return ntcp, nopen, nclose, nil
}

func TestDialMultiFDLeak(t *testing.T) {
t.Skip("flaky test - golang.org/issue/8764")

if !supportsIPv4 || !supportsIPv6 {
t.Skip("neither ipv4 nor ipv6 is supported")
t.Skip("ipv4 or ipv6 is not supported")
}

origTestHookLookupIP := testHookLookupIP
defer func() { testHookLookupIP = origTestHookLookupIP }()
testHookLookupIP = lookupLocalhost
handler := func(dss *dualStackServer, ln Listener) {
for {
if c, err := ln.Accept(); err != nil {
c, err := ln.Accept()
if err != nil {
return
} else {
// It just keeps established
// connections like a half-dead server
// does.
dss.putConn(c)
}
c.Close()
}
}
dss, err := newDualStackServer([]streamListener{
{network: "tcp4", address: "127.0.0.1"},
{network: "tcp6", address: "::1"},
})
if err != nil {
t.Fatalf("newDualStackServer failed: %v", err)
t.Fatal(err)
}
defer dss.teardown()
if err := dss.buildup(handler); err != nil {
t.Fatalf("dualStackServer.buildup failed: %v", err)
}

_, before, _, err := numTCP()
if err != nil {
t.Skipf("skipping test; error finding or running lsof: %v", err)
t.Fatal(err)
}

var wg sync.WaitGroup
portnum, _, _ := dtoi(dss.port, 0)
ras := addrList{
// Losers that will fail to connect, see RFC 6890.
&TCPAddr{IP: IPv4(198, 18, 0, 254), Port: portnum},
&TCPAddr{IP: ParseIP("2001:2::254"), Port: portnum},

// Winner candidates of this race.
&TCPAddr{IP: IPv4(127, 0, 0, 1), Port: portnum},
&TCPAddr{IP: IPv6loopback, Port: portnum},

// Losers that will have established connections.
&TCPAddr{IP: IPv4(127, 0, 0, 1), Port: portnum},
&TCPAddr{IP: IPv6loopback, Port: portnum},
}
const T1 = 10 * time.Millisecond
const T2 = 2 * T1
before := sw.Sockets()
const T = 100 * time.Millisecond
const N = 10
var wg sync.WaitGroup
wg.Add(N)
d := &Dialer{DualStack: true, Timeout: T}
for i := 0; i < N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if c, err := dialMulti("tcp", "fast failover test", nil, ras, time.Now().Add(T1)); err == nil {
c.Close()
c, err := d.Dial("tcp", JoinHostPort("localhost", dss.port))
if err != nil {
t.Error(err)
return
}
c.Close()
}()
}
wg.Wait()
time.Sleep(T2)

ntcp, after, nclose, err := numTCP()
if err != nil {
t.Skipf("skipping test; error finding or running lsof: %v", err)
}
t.Logf("tcp sessions: %v, open sessions: %v, closing sessions: %v", ntcp, after, nclose)

if after != before {
t.Fatalf("got %v open sessions; expected %v", after, before)
time.Sleep(2 * T) // wait for the dial racers to stop
after := sw.Sockets()
if len(after) != len(before) {
t.Errorf("got %d; want %d", len(after), len(before))
}
}

Expand Down Expand Up @@ -347,51 +311,51 @@ func TestDialerLocalAddr(t *testing.T) {
}

func TestDialerDualStack(t *testing.T) {
switch runtime.GOOS {
case "nacl":
t.Skipf("skipping test on %q", runtime.GOOS)
}

if ips, err := LookupIP("localhost"); err != nil {
t.Fatalf("LookupIP failed: %v", err)
} else if len(ips) < 2 || !supportsIPv4 || !supportsIPv6 {
t.Skip("localhost doesn't have a pair of different address family IP addresses")
if !supportsIPv4 || !supportsIPv6 {
t.Skip("ipv4 or ipv6 is not supported")
}

origTestHookLookupIP := testHookLookupIP
defer func() { testHookLookupIP = origTestHookLookupIP }()
testHookLookupIP = lookupLocalhost
handler := func(dss *dualStackServer, ln Listener) {
for {
if c, err := ln.Accept(); err != nil {
c, err := ln.Accept()
if err != nil {
return
} else {
c.Close()
}
c.Close()
}
}
dss, err := newDualStackServer([]streamListener{
{network: "tcp4", address: "127.0.0.1"},
{network: "tcp6", address: "::1"},
})
if err != nil {
t.Fatalf("newDualStackServer failed: %v", err)
t.Fatal(err)
}
defer dss.teardown()
if err := dss.buildup(handler); err != nil {
t.Fatalf("dualStackServer.buildup failed: %v", err)
t.Fatal(err)
}

d := &Dialer{DualStack: true}
const T = 100 * time.Millisecond
d := &Dialer{DualStack: true, Timeout: T}
for range dss.lns {
if c, err := d.Dial("tcp", JoinHostPort("localhost", dss.port)); err != nil {
t.Errorf("Dial failed: %v", err)
} else {
if addr := c.LocalAddr().(*TCPAddr); addr.IP.To4() != nil {
dss.teardownNetwork("tcp4")
} else if addr.IP.To16() != nil && addr.IP.To4() == nil {
dss.teardownNetwork("tcp6")
}
c.Close()
c, err := d.Dial("tcp", JoinHostPort("localhost", dss.port))
if err != nil {
t.Error(err)
continue
}
switch addr := c.LocalAddr().(*TCPAddr); {
case addr.IP.To4() != nil:
dss.teardownNetwork("tcp4")
case addr.IP.To16() != nil && addr.IP.To4() == nil:
dss.teardownNetwork("tcp6")
}
c.Close()
}
time.Sleep(2 * T) // wait for the dial racers to stop
}

func TestDialerKeepAlive(t *testing.T) {
Expand Down
41 changes: 18 additions & 23 deletions src/net/ipraw_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package net

import (
"fmt"
"reflect"
"testing"
)
Expand All @@ -17,7 +16,7 @@ import (
// golang.org/x/net/icmp

type resolveIPAddrTest struct {
net string
network string
litAddrOrName string
addr *IPAddr
err error
Expand All @@ -44,34 +43,30 @@ var resolveIPAddrTests = []resolveIPAddrTest{
{"tcp", "1.2.3.4:123", nil, UnknownNetworkError("tcp")},
}

func init() {
if ifi := loopbackInterface(); ifi != nil {
index := fmt.Sprintf("%v", ifi.Index)
resolveIPAddrTests = append(resolveIPAddrTests, []resolveIPAddrTest{
{"ip6", "fe80::1%" + ifi.Name, &IPAddr{IP: ParseIP("fe80::1"), Zone: zoneToString(ifi.Index)}, nil},
{"ip6", "fe80::1%" + index, &IPAddr{IP: ParseIP("fe80::1"), Zone: index}, nil},
}...)
}
if ips, err := LookupIP("localhost"); err == nil && len(ips) > 1 && supportsIPv4 && supportsIPv6 {
resolveIPAddrTests = append(resolveIPAddrTests, []resolveIPAddrTest{
{"ip", "localhost", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil},
{"ip4", "localhost", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil},
{"ip6", "localhost", &IPAddr{IP: IPv6loopback}, nil},
}...)
}
}

func TestResolveIPAddr(t *testing.T) {
if !testableNetwork("ip+nopriv") {
t.Skip("ip+nopriv test")
}

for _, tt := range resolveIPAddrTests {
addr, err := ResolveIPAddr(tt.net, tt.litAddrOrName)
origTestHookLookupIP := testHookLookupIP
defer func() { testHookLookupIP = origTestHookLookupIP }()
testHookLookupIP = lookupLocalhost

for i, tt := range resolveIPAddrTests {
addr, err := ResolveIPAddr(tt.network, tt.litAddrOrName)
if err != tt.err {
t.Fatalf("ResolveIPAddr(%v, %v) failed: %v", tt.net, tt.litAddrOrName, err)
t.Errorf("#%d: %v", i, err)
} else if !reflect.DeepEqual(addr, tt.addr) {
t.Fatalf("got %#v; expected %#v", addr, tt.addr)
t.Errorf("#%d: got %#v; want %#v", i, addr, tt.addr)
}
if err != nil {
continue
}
rtaddr, err := ResolveIPAddr(addr.Network(), addr.String())
if err != nil {
t.Errorf("#%d: %v", i, err)
} else if !reflect.DeepEqual(rtaddr, addr) {
t.Errorf("#%d: got %#v; want %#v", i, rtaddr, addr)
}
}
}
Expand Down
15 changes: 12 additions & 3 deletions src/net/lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// TODO It would be nice to use a mock DNS server, to eliminate
// external dependencies.

package net

import (
Expand All @@ -14,6 +11,18 @@ import (
"time"
)

func lookupLocalhost(fn func(string) ([]IPAddr, error), host string) ([]IPAddr, error) {
switch host {
case "localhost":
return []IPAddr{
{IP: IPv4(127, 0, 0, 1)},
{IP: IPv6loopback},
}, nil
default:
return fn(host)
}
}

var lookupGoogleSRVTests = []struct {
service, proto, name string
cname, target string
Expand Down
40 changes: 40 additions & 0 deletions src/net/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ var (
)

func TestMain(m *testing.M) {
setupTestData()
installTestHooks()

st := m.Run()
Expand All @@ -62,6 +63,45 @@ func TestMain(m *testing.M) {
os.Exit(st)
}

func setupTestData() {
if supportsIPv4 {
resolveTCPAddrTests = append(resolveTCPAddrTests, []resolveTCPAddrTest{
{"tcp", "localhost:1", &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 1}, nil},
{"tcp4", "localhost:2", &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 2}, nil},
}...)
resolveUDPAddrTests = append(resolveUDPAddrTests, []resolveUDPAddrTest{
{"udp", "localhost:1", &UDPAddr{IP: IPv4(127, 0, 0, 1), Port: 1}, nil},
{"udp4", "localhost:2", &UDPAddr{IP: IPv4(127, 0, 0, 1), Port: 2}, nil},
}...)
resolveIPAddrTests = append(resolveIPAddrTests, []resolveIPAddrTest{
{"ip", "localhost", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil},
{"ip4", "localhost", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil},
}...)
}

if supportsIPv6 {
resolveTCPAddrTests = append(resolveTCPAddrTests, resolveTCPAddrTest{"tcp6", "localhost:3", &TCPAddr{IP: IPv6loopback, Port: 3}, nil})
resolveUDPAddrTests = append(resolveUDPAddrTests, resolveUDPAddrTest{"udp6", "localhost:3", &UDPAddr{IP: IPv6loopback, Port: 3}, nil})
resolveIPAddrTests = append(resolveIPAddrTests, resolveIPAddrTest{"ip6", "localhost", &IPAddr{IP: IPv6loopback}, nil})
}

if ifi := loopbackInterface(); ifi != nil {
index := fmt.Sprintf("%v", ifi.Index)
resolveTCPAddrTests = append(resolveTCPAddrTests, []resolveTCPAddrTest{
{"tcp6", "[fe80::1%" + ifi.Name + "]:1", &TCPAddr{IP: ParseIP("fe80::1"), Port: 1, Zone: zoneToString(ifi.Index)}, nil},
{"tcp6", "[fe80::1%" + index + "]:2", &TCPAddr{IP: ParseIP("fe80::1"), Port: 2, Zone: index}, nil},
}...)
resolveUDPAddrTests = append(resolveUDPAddrTests, []resolveUDPAddrTest{
{"udp6", "[fe80::1%" + ifi.Name + "]:1", &UDPAddr{IP: ParseIP("fe80::1"), Port: 1, Zone: zoneToString(ifi.Index)}, nil},
{"udp6", "[fe80::1%" + index + "]:2", &UDPAddr{IP: ParseIP("fe80::1"), Port: 2, Zone: index}, nil},
}...)
resolveIPAddrTests = append(resolveIPAddrTests, []resolveIPAddrTest{
{"ip6", "fe80::1%" + ifi.Name, &IPAddr{IP: ParseIP("fe80::1"), Zone: zoneToString(ifi.Index)}, nil},
{"ip6", "fe80::1%" + index, &IPAddr{IP: ParseIP("fe80::1"), Zone: index}, nil},
}...)
}
}

func printLeakedGoroutines() {
gss := leakedGoroutines()
if len(gss) == 0 {
Expand Down
Loading

0 comments on commit 5a83f06

Please sign in to comment.