net/porttrack: add net.Listen wrapper to help tests allocate ports race-free
Updates tailscale/corp#27805 Updates tailscale/corp#27806 Updates tailscale/corp#37964 Change-Id: I7bb5ed7f258e840a8208e5d725c7b2f126d7ef96 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
120f27f383
commit
d42b3743b7
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package porttrack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCollectorAndListen(t *testing.T) {
|
||||
c := NewCollector(t)
|
||||
|
||||
labels := []string{"main", "plaintext", "debug"}
|
||||
ports := make([]int, len(labels))
|
||||
|
||||
for i, label := range labels {
|
||||
ln, err := Listen("tcp", c.Addr(label))
|
||||
if err != nil {
|
||||
t.Fatalf("Listen(%q): %v", label, err)
|
||||
}
|
||||
defer ln.Close()
|
||||
p, err := c.Port(t.Context(), label)
|
||||
if err != nil {
|
||||
t.Fatalf("Port(%q): %v", label, err)
|
||||
}
|
||||
ports[i] = p
|
||||
}
|
||||
|
||||
// All ports should be distinct non-zero values.
|
||||
seen := map[int]string{}
|
||||
for i, label := range labels {
|
||||
if ports[i] == 0 {
|
||||
t.Errorf("Port(%q) = 0", label)
|
||||
}
|
||||
if prev, ok := seen[ports[i]]; ok {
|
||||
t.Errorf("Port(%q) = Port(%q) = %d", label, prev, ports[i])
|
||||
}
|
||||
seen[ports[i]] = label
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenPassthrough(t *testing.T) {
|
||||
ln, err := Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
t.Fatalf("Listen passthrough: %v", err)
|
||||
}
|
||||
defer ln.Close()
|
||||
if ln.Addr().(*net.TCPAddr).Port == 0 {
|
||||
t.Fatal("expected non-zero port")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
c := NewCollector(t)
|
||||
|
||||
ln, err := Listen("tcp", c.Addr("http"))
|
||||
if err != nil {
|
||||
t.Fatalf("Listen: %v", err)
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
// Start a server on the listener.
|
||||
go http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
|
||||
port, err := c.Port(t.Context(), "http")
|
||||
if err != nil {
|
||||
t.Fatalf("Port: %v", err)
|
||||
}
|
||||
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port))
|
||||
if err != nil {
|
||||
t.Fatalf("http.Get: %v", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
t.Fatalf("status = %d, want %d", resp.StatusCode, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortContextCancelled(t *testing.T) {
|
||||
c := NewCollector(t)
|
||||
// Nobody will ever report "never", so Port should block until ctx is done.
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
cancel()
|
||||
_, err := c.Port(ctx, "never")
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Fatalf("Port with cancelled context: got %v, want %v", err, context.Canceled)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user