cmd/swarm: solve rare cases of using the same random port in tests (#17352)

This commit is contained in:
Janoš Guljaš 2018-08-09 16:15:59 +02:00 committed by Balint Gabor
parent 3bcb501c8f
commit 45eaef2431
2 changed files with 132 additions and 20 deletions

View File

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"os" "os"
"os/exec" "os/exec"
"testing" "testing"
@ -559,3 +560,16 @@ func TestValidateConfig(t *testing.T) {
} }
} }
} }
func assignTCPPort() (string, error) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return "", err
}
l.Close()
_, port, err := net.SplitHostPort(l.Addr().String())
if err != nil {
return "", err
}
return port, nil
}

View File

@ -17,12 +17,15 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync"
"syscall"
"testing" "testing"
"time" "time"
@ -218,14 +221,12 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
} }
// assign ports // assign ports
httpPort, err := assignTCPPort() ports, err := getAvailableTCPPorts(2)
if err != nil {
t.Fatal(err)
}
p2pPort, err := assignTCPPort()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
p2pPort := ports[0]
httpPort := ports[1]
// start the node // start the node
node.Cmd = runSwarm(t, node.Cmd = runSwarm(t,
@ -246,6 +247,17 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
} }
}() }()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// ensure that all ports have active listeners
// so that the next node will not get the same
// when calling getAvailableTCPPorts
err = waitTCPPorts(ctx, ports...)
if err != nil {
t.Fatal(err)
}
// wait for the node to start // wait for the node to start
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) { for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
node.Client, err = rpc.Dial(conf.IPCEndpoint()) node.Client, err = rpc.Dial(conf.IPCEndpoint())
@ -280,14 +292,12 @@ func newTestNode(t *testing.T, dir string) *testNode {
node := &testNode{Dir: dir} node := &testNode{Dir: dir}
// assign ports // assign ports
httpPort, err := assignTCPPort() ports, err := getAvailableTCPPorts(2)
if err != nil {
t.Fatal(err)
}
p2pPort, err := assignTCPPort()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
p2pPort := ports[0]
httpPort := ports[1]
// start the node // start the node
node.Cmd = runSwarm(t, node.Cmd = runSwarm(t,
@ -308,6 +318,17 @@ func newTestNode(t *testing.T, dir string) *testNode {
} }
}() }()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// ensure that all ports have active listeners
// so that the next node will not get the same
// when calling getAvailableTCPPorts
err = waitTCPPorts(ctx, ports...)
if err != nil {
t.Fatal(err)
}
// wait for the node to start // wait for the node to start
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) { for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
node.Client, err = rpc.Dial(conf.IPCEndpoint()) node.Client, err = rpc.Dial(conf.IPCEndpoint())
@ -343,15 +364,92 @@ func (n *testNode) Shutdown() {
} }
} }
func assignTCPPort() (string, error) { // getAvailableTCPPorts returns a set of ports that
// nothing is listening on at the time.
//
// Function assignTCPPort cannot be called in sequence
// and guardantee that the same port will be returned in
// different calls as the listener is closed within the function,
// not after all listeners are started and selected unique
// available ports.
func getAvailableTCPPorts(count int) (ports []string, err error) {
for i := 0; i < count; i++ {
l, err := net.Listen("tcp", "127.0.0.1:0") l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil { if err != nil {
return "", err return nil, err
} }
l.Close() // defer close in the loop to be sure the same port will not
// be selected in the next iteration
defer l.Close()
_, port, err := net.SplitHostPort(l.Addr().String()) _, port, err := net.SplitHostPort(l.Addr().String())
if err != nil { if err != nil {
return "", err return nil, err
}
ports = append(ports, port)
}
return ports, nil
}
// waitTCPPorts blocks until tcp connections can be
// established on all provided ports. It runs all
// ports dialers in parallel, and returns the first
// encountered error.
// See waitTCPPort also.
func waitTCPPorts(ctx context.Context, ports ...string) error {
var err error
// mu locks err variable that is assigned in
// other goroutines
var mu sync.Mutex
// cancel is canceling all goroutines
// when the firs error is returned
// to prevent unnecessary waiting
ctx, cancel := context.WithCancel(ctx)
defer cancel()
var wg sync.WaitGroup
for _, port := range ports {
wg.Add(1)
go func(port string) {
defer wg.Done()
e := waitTCPPort(ctx, port)
mu.Lock()
defer mu.Unlock()
if e != nil && err == nil {
err = e
cancel()
}
}(port)
}
wg.Wait()
return err
}
// waitTCPPort blocks until tcp connection can be established
// ona provided port. It has a 3 minute timeout as maximum,
// to prevent long waiting, but it can be shortened with
// a provided context instance. Dialer has a 10 second timeout
// in every iteration, and connection refused error will be
// retried in 100 milliseconds periods.
func waitTCPPort(ctx context.Context, port string) error {
ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
defer cancel()
for {
c, err := (&net.Dialer{Timeout: 10 * time.Second}).DialContext(ctx, "tcp", "127.0.0.1:"+port)
if err != nil {
if operr, ok := err.(*net.OpError); ok {
if syserr, ok := operr.Err.(*os.SyscallError); ok && syserr.Err == syscall.ECONNREFUSED {
time.Sleep(100 * time.Millisecond)
continue
}
}
return err
}
return c.Close()
} }
return port, nil
} }