tailscale/net/porttrack/porttrack_test.go
Brad Fitzpatrick d42b3743b7 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>
2026-03-03 20:56:20 -08:00

96 lines
2.2 KiB
Go

// 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)
}
}