summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2026-04-20 19:40:35 +0000
committerBrad Fitzpatrick <brad@danga.com>2026-04-20 13:33:10 -0700
commit1e68a11721fd59408b68bce8be47ef77e3776572 (patch)
treea8cc1f6a6b2bead34ebf6c06a2fe9a0b8626ac75
parent5b06e32f330a37bc26ac959e2541c271a6966e82 (diff)
downloadtailscale-1e68a11721fd59408b68bce8be47ef77e3776572.tar.xz
tailscale-1e68a11721fd59408b68bce8be47ef77e3776572.zip
logtail: run HTTP tests in-memory with memnet + synctest
TestEncodeAndUploadMessages waited on the default 2s FlushDelay, making the logtail package the slowest non-integration test in the tree (~2s real time). Switch the shared harness from an httptest.Server-on-loopback to a memnet.Listener-backed *http.Server and run the tests inside synctest.Test, so fake time advances the flush timer instantly. Drops the net/http/httptest dependency from these tests. Combined with the TestMain non-localhost dial guard added in the previous commit, no test in this package can accidentally reach the real log.tailscale.com server. Whole package now runs in ~7ms. Updates tailscale/corp#28679 Change-Id: Ie0e7a6a79641384ed0eecb99d767e17cda8bb944 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--logtail/logtail_test.go89
1 files changed, 47 insertions, 42 deletions
diff --git a/logtail/logtail_test.go b/logtail/logtail_test.go
index f1d0585f5..8273097c3 100644
--- a/logtail/logtail_test.go
+++ b/logtail/logtail_test.go
@@ -11,7 +11,6 @@ import (
"io"
"net"
"net/http"
- "net/http/httptest"
"os"
"strings"
"sync"
@@ -20,6 +19,7 @@ import (
"time"
"github.com/go-json-experiment/json/jsontext"
+ "tailscale.com/net/memnet"
"tailscale.com/tstest"
"tailscale.com/tstime"
"tailscale.com/util/eventbus/eventbustest"
@@ -30,6 +30,7 @@ import (
// test in this package. Config.BaseURL defaults to https://log.tailscale.com
// and Config.HTTPC defaults to http.DefaultClient, so a test that forgets to
// override either can otherwise silently hit the real logtail server.
+// Tests that need an HTTP server should use memnet (see newTestLogtailServer).
func TestMain(m *testing.M) {
tr := http.DefaultTransport.(*http.Transport)
orig := tr.DialContext
@@ -38,25 +39,19 @@ func TestMain(m *testing.M) {
if err == nil && (host == "127.0.0.1" || host == "::1" || host == "localhost") {
return orig(ctx, network, addr)
}
- return nil, fmt.Errorf("logtail tests: refusing to dial non-localhost address %q; use httptest.Server or a custom Config.HTTPC", addr)
+ return nil, fmt.Errorf("logtail tests: refusing to dial non-localhost address %q; use memnet or a custom Config.HTTPC", addr)
}
os.Exit(m.Run())
}
-func TestFastShutdown(t *testing.T) {
+func TestFastShutdown(t *testing.T) { synctest.Test(t, synctestFastShutdown) }
+
+func synctestFastShutdown(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
- testServ := httptest.NewServer(http.HandlerFunc(
- func(w http.ResponseWriter, r *http.Request) {}))
- defer testServ.Close()
-
- logger := NewLogger(Config{
- BaseURL: testServ.URL,
- Bus: eventbustest.NewBus(t),
- }, t.Logf)
- err := logger.Shutdown(ctx)
- if err != nil {
+ _, logger := newTestLogtailServer(t)
+ if err := logger.Shutdown(ctx); err != nil {
t.Error(err)
}
}
@@ -65,49 +60,60 @@ func TestFastShutdown(t *testing.T) {
const logLines = 3
type LogtailTestServer struct {
- srv *httptest.Server // Log server
uploaded chan []byte
}
-func NewLogtailTestHarness(t *testing.T) (*LogtailTestServer, *Logger) {
- ts := LogtailTestServer{}
-
- // max channel backlog = 1 "started" + #logLines x "log line" + 1 "closed"
- ts.uploaded = make(chan []byte, 2+logLines)
+// newTestLogtailServer wires up an in-memory HTTP server (via memnet) and a
+// *Logger whose HTTPC dials it. Lives inside the caller's synctest bubble so
+// the default FlushDelay and any other fake timers advance automatically.
+func newTestLogtailServer(t *testing.T) (*LogtailTestServer, *Logger) {
+ ts := &LogtailTestServer{
+ // max channel backlog = 1 "started" + #logLines x "log line" + 1 "closed"
+ uploaded: make(chan []byte, 2+logLines),
+ }
- ts.srv = httptest.NewServer(http.HandlerFunc(
- func(w http.ResponseWriter, r *http.Request) {
- body, err := io.ReadAll(r.Body)
- if err != nil {
- t.Error("failed to read HTTP request")
- }
- ts.uploaded <- body
- }))
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+ t.Error("failed to read HTTP request")
+ }
+ ts.uploaded <- body
+ })
- t.Cleanup(ts.srv.Close)
+ ln := memnet.Listen("logtail-test:0")
+ httpsrv := &http.Server{Handler: handler}
+ go httpsrv.Serve(ln)
+ t.Cleanup(func() {
+ httpsrv.Close()
+ ln.Close()
+ })
logger := NewLogger(Config{
- BaseURL: ts.srv.URL,
+ BaseURL: "http://" + ln.Addr().String(),
Bus: eventbustest.NewBus(t),
+ HTTPC: &http.Client{
+ Transport: &http.Transport{DialContext: ln.Dial},
+ },
}, t.Logf)
- // There is always an initial "logtail started" message
+ // There is always an initial "logtail started" message.
body := <-ts.uploaded
if !strings.Contains(string(body), "started") {
t.Errorf("unknown start logging statement: %q", string(body))
}
-
- return &ts, logger
+ return ts, logger
}
-func TestDrainPendingMessages(t *testing.T) {
- ts, logger := NewLogtailTestHarness(t)
+func TestDrainPendingMessages(t *testing.T) { synctest.Test(t, synctestDrainPendingMessages) }
+
+func synctestDrainPendingMessages(t *testing.T) {
+ ts, logger := newTestLogtailServer(t)
for range logLines {
logger.Write([]byte("log line"))
}
- // all of the "log line" messages usually arrive at once, but poll if needed.
+ // All the "log line" messages usually arrive at once, but poll if needed.
var body strings.Builder
for i := 0; i <= logLines; i++ {
body.WriteString(string(<-ts.uploaded))
@@ -115,17 +121,17 @@ func TestDrainPendingMessages(t *testing.T) {
if count == logLines {
break
}
- // if we never find count == logLines, the test will eventually time out.
}
- err := logger.Shutdown(context.Background())
- if err != nil {
+ if err := logger.Shutdown(context.Background()); err != nil {
t.Error(err)
}
}
-func TestEncodeAndUploadMessages(t *testing.T) {
- ts, logger := NewLogtailTestHarness(t)
+func TestEncodeAndUploadMessages(t *testing.T) { synctest.Test(t, synctestEncodeAndUploadMessages) }
+
+func synctestEncodeAndUploadMessages(t *testing.T) {
+ ts, logger := newTestLogtailServer(t)
tests := []struct {
name string
@@ -166,8 +172,7 @@ func TestEncodeAndUploadMessages(t *testing.T) {
}
}
- err := logger.Shutdown(context.Background())
- if err != nil {
+ if err := logger.Shutdown(context.Background()); err != nil {
t.Error(err)
}
}