From eba8cd7c07cbe5680afc636083e9dd17de5e9f93 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 29 Feb 2024 12:51:44 +0000 Subject: [PATCH] chore: consolidate various randomPort() implementations (#12362) Consolidates our existing randomPort() implementations to package testutil --- agent/agent_test.go | 17 +-------- cli/server_test.go | 13 +------ enterprise/cli/provisionerdaemons_test.go | 13 +------ enterprise/cli/proxyserver_test.go | 4 +- enterprise/cli/server_test.go | 2 +- testutil/port.go | 45 +++++++++++++++++++++++ 6 files changed, 52 insertions(+), 42 deletions(-) create mode 100644 testutil/port.go diff --git a/agent/agent_test.go b/agent/agent_test.go index d3c400173a..5626f52c93 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "io" - "math/rand" "net" "net/http" "net/http/httptest" @@ -838,7 +837,7 @@ func TestAgent_TCPRemoteForwarding(t *testing.T) { var ll net.Listener var err error for { - randomPort = pickRandomPort() + randomPort = testutil.RandomPortNoListen(t) addr := net.TCPAddrFromAddrPort(netip.AddrPortFrom(localhost, randomPort)) ll, err = sshClient.ListenTCP(addr) if err != nil { @@ -2666,20 +2665,6 @@ func (s *syncWriter) Write(p []byte) (int, error) { return s.w.Write(p) } -// pickRandomPort picks a random port number for the ephemeral range. We do this entirely randomly -// instead of opening a listener and closing it to find a port that is likely to be free, since -// sometimes the OS reallocates the port very quickly. -func pickRandomPort() uint16 { - const ( - // Overlap of windows, linux in https://en.wikipedia.org/wiki/Ephemeral_port - min = 49152 - max = 60999 - ) - n := max - min - x := rand.Intn(n) //nolint: gosec - return uint16(min + x) -} - // echoOnce accepts a single connection, reads 4 bytes and echos them back func echoOnce(t *testing.T, ll net.Listener) { t.Helper() diff --git a/cli/server_test.go b/cli/server_test.go index 57040a8d41..9699e8a48e 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -937,22 +937,13 @@ func TestServer(t *testing.T) { t.Run("Prometheus", func(t *testing.T) { t.Parallel() - randomPort := func(t *testing.T) int { - random, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - _ = random.Close() - tcpAddr, valid := random.Addr().(*net.TCPAddr) - require.True(t, valid) - return tcpAddr.Port - } - t.Run("DBMetricsDisabled", func(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) defer cancel() - randPort := randomPort(t) + randPort := testutil.RandomPort(t) inv, cfg := clitest.New(t, "server", "--in-memory", @@ -1008,7 +999,7 @@ func TestServer(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) defer cancel() - randPort := randomPort(t) + randPort := testutil.RandomPort(t) inv, cfg := clitest.New(t, "server", "--in-memory", diff --git a/enterprise/cli/provisionerdaemons_test.go b/enterprise/cli/provisionerdaemons_test.go index e53fadcb4a..67054938d9 100644 --- a/enterprise/cli/provisionerdaemons_test.go +++ b/enterprise/cli/provisionerdaemons_test.go @@ -4,7 +4,6 @@ import ( "bufio" "context" "fmt" - "net" "net/http" "strings" "testing" @@ -170,7 +169,7 @@ func TestProvisionerDaemon_SessionToken(t *testing.T) { t.Run("PrometheusEnabled", func(t *testing.T) { t.Parallel() - prometheusPort := randomPort(t) + prometheusPort := testutil.RandomPort(t) // Configure CLI client client, admin := coderdenttest.New(t, &coderdenttest.Options{ @@ -242,13 +241,3 @@ func TestProvisionerDaemon_SessionToken(t *testing.T) { require.True(t, hasPromHTTP, "Prometheus HTTP metrics are missing") }) } - -// randomPort is a helper function to find a free random port, for instance to spawn Prometheus endpoint. -func randomPort(t *testing.T) int { - random, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - _ = random.Close() - tcpAddr, valid := random.Addr().(*net.TCPAddr) - require.True(t, valid) - return tcpAddr.Port -} diff --git a/enterprise/cli/proxyserver_test.go b/enterprise/cli/proxyserver_test.go index 05e1aeb0fa..688075bf1f 100644 --- a/enterprise/cli/proxyserver_test.go +++ b/enterprise/cli/proxyserver_test.go @@ -63,7 +63,7 @@ func Test_Headers(t *testing.T) { func TestWorkspaceProxy_Server_PrometheusEnabled(t *testing.T) { t.Parallel() - prometheusPort := randomPort(t) + prometheusPort := testutil.RandomPort(t) var wg sync.WaitGroup wg.Add(1) @@ -96,7 +96,7 @@ func TestWorkspaceProxy_Server_PrometheusEnabled(t *testing.T) { "--primary-access-url", srv.URL, "--proxy-session-token", "test-token", "--access-url", "http://foobar:3001", - "--http-address", fmt.Sprintf("127.0.0.1:%d", randomPort(t)), + "--http-address", fmt.Sprintf("127.0.0.1:%d", testutil.RandomPort(t)), "--prometheus-enable", "--prometheus-address", fmt.Sprintf("127.0.0.1:%d", prometheusPort), ) diff --git a/enterprise/cli/server_test.go b/enterprise/cli/server_test.go index b70d6eab3c..c9e3a6e7f7 100644 --- a/enterprise/cli/server_test.go +++ b/enterprise/cli/server_test.go @@ -22,7 +22,7 @@ func TestServer(t *testing.T) { var root cli.RootCmd cmd, err := root.Command(root.EnterpriseSubcommands()) require.NoError(t, err) - port := randomPort(t) + port := testutil.RandomPort(t) inv, _ := clitest.NewWithCommand(t, cmd, "server", "--in-memory", diff --git a/testutil/port.go b/testutil/port.go new file mode 100644 index 0000000000..b5720e44a0 --- /dev/null +++ b/testutil/port.go @@ -0,0 +1,45 @@ +package testutil + +import ( + "math/rand" + "net" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +var ( + // nolint:gosec // not used for cryptography + rnd = rand.New(rand.NewSource(time.Now().Unix())) + rndMu sync.Mutex +) + +// RandomPort is a helper function to find a free random port. +// Note that the OS may reallocate the port very quickly, so +// this is not _guaranteed_. +func RandomPort(t *testing.T) int { + random, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err, "failed to listen on localhost") + _ = random.Close() + tcpAddr, valid := random.Addr().(*net.TCPAddr) + require.True(t, valid, "random port address is not a *net.TCPAddr?!") + return tcpAddr.Port +} + +// RandomPortNoListen returns a random port in the ephemeral port range. +// Does not attempt to listen and close to find a port as the OS may +// reallocate the port very quickly. +func RandomPortNoListen(*testing.T) uint16 { + const ( + // Overlap of windows, linux in https://en.wikipedia.org/wiki/Ephemeral_port + min = 49152 + max = 60999 + ) + n := max - min + rndMu.Lock() + x := rnd.Intn(n) + rndMu.Unlock() + return uint16(min + x) +}