cmd/testwrapper, tstest: move test sharding out of test code

Previously, sharding required tests to opt in by calling tstest.Shard,
which used a process-global counter to assign each test to a shard.
This had two problems: most tests didn't call it, so they ran on every
shard (defeating the purpose), and shard assignments were unstable
(depended on call order, so adding a test could reshuffle others).

Remove tstest.Shard and tstest.SkipOnUnshardedCI entirely. Instead,
have testwrapper implement sharding automatically for all tests: when
TS_TEST_SHARD=N/M is set, it uses "go list -json" (no compilation) to
find test source files, scans them for top-level Test/Benchmark/
Example/Fuzz function names, and filters by fnv32a(name) % M == N-1.
The filtered names are passed as an anchored -run regex to go test.

Using go list instead of "go test -list" avoids linking the test binary
twice (Go's build cache does not cache test binary linking).

Fixes #19886

Change-Id: I62ab7b3d757324d4c5fd0b5de50c1e3742681791
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2026-05-27 16:53:17 -07:00
committed by Brad Fitzpatrick
parent db60aa8eca
commit 94af1b00fb
8 changed files with 112 additions and 103 deletions
-2
View File
@@ -12,7 +12,6 @@ import (
"tailscale.com/ipn"
"tailscale.com/tailcfg"
"tailscale.com/tstest"
"tailscale.com/types/ipproto"
"tailscale.com/types/key"
"tailscale.com/types/netmap"
@@ -45,7 +44,6 @@ func waitFor(t testing.TB, ctx context.Context, s *Server, f func(*netmap.Networ
// netmaps and turning them into packet filters together. Only the control-plane
// side is mocked out.
func TestPacketFilterFromNetmap(t *testing.T) {
tstest.Shard(t)
t.Parallel()
var key key.NodePublic
-20
View File
@@ -340,7 +340,6 @@ func startServer(t *testing.T, ctx context.Context, controlURL, hostname string)
}
func TestDialBlocks(t *testing.T) {
tstest.Shard(t)
tstest.ResourceCheck(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
@@ -387,7 +386,6 @@ func TestDialBlocks(t *testing.T) {
// - s2 can dial through the subnet router functionality (getting a synthetic RST
// that we verify we generated & saw)
func TestConn(t *testing.T) {
tstest.Shard(t)
tstest.ResourceCheck(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
@@ -520,7 +518,6 @@ func TestConn(t *testing.T) {
func TestLoopbackLocalAPI(t *testing.T) {
flakytest.Mark(t, "https://github.com/tailscale/tailscale/issues/8557")
tstest.Shard(t)
tstest.ResourceCheck(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
@@ -596,7 +593,6 @@ func TestLoopbackLocalAPI(t *testing.T) {
func TestLoopbackSOCKS5(t *testing.T) {
flakytest.Mark(t, "https://github.com/tailscale/tailscale/issues/8198")
tstest.Shard(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
@@ -647,7 +643,6 @@ func TestLoopbackSOCKS5(t *testing.T) {
}
func TestTailscaleIPs(t *testing.T) {
tstest.Shard(t)
controlURL, _ := startControl(t)
tmp := t.TempDir()
@@ -690,7 +685,6 @@ func TestTailscaleIPs(t *testing.T) {
// TestListenerCleanup is a regression test to verify that s.Close doesn't
// deadlock if a listener is still open.
func TestListenerCleanup(t *testing.T) {
tstest.Shard(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
@@ -733,7 +727,6 @@ func (wc *closeTrackConn) Close() error {
// tests https://github.com/tailscale/tailscale/issues/6973 -- that we can start a tsnet server,
// stop it, and restart it, even on Windows.
func TestStartStopStartGetsSameIP(t *testing.T) {
tstest.Shard(t)
controlURL, _ := startControl(t)
tmp := t.TempDir()
@@ -783,7 +776,6 @@ func TestStartStopStartGetsSameIP(t *testing.T) {
}
func TestFunnel(t *testing.T) {
tstest.Shard(t)
ctx, dialCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer dialCancel()
@@ -848,7 +840,6 @@ func TestFunnel(t *testing.T) {
// after itself when closed. Specifically, changes made to the serve config
// should be cleared.
func TestFunnelClose(t *testing.T) {
tstest.Shard(t)
marshalServeConfig := func(t *testing.T, sc ipn.ServeConfigView) string {
t.Helper()
@@ -1034,7 +1025,6 @@ func setUpServiceState(t *testing.T, name, ip string, host, client *Server,
}
func TestListenService(t *testing.T) {
tstest.Shard(t)
type dialFn func(context.Context, string, string) (net.Conn, error)
@@ -1430,7 +1420,6 @@ func TestListenService(t *testing.T) {
}
func TestListenServiceClose(t *testing.T) {
tstest.Shard(t)
const serviceName = "svc:foo"
diffServeConfig := func(a, b ipn.ServeConfigView) string {
@@ -1586,7 +1575,6 @@ func TestListenServiceClose(t *testing.T) {
}
func TestListenerClose(t *testing.T) {
tstest.Shard(t)
ctx := context.Background()
controlURL, _ := startControl(t)
@@ -1666,7 +1654,6 @@ func (c *bufferedConn) Read(b []byte) (int, error) {
}
func TestFallbackTCPHandler(t *testing.T) {
tstest.Shard(t)
tstest.ResourceCheck(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
@@ -1709,7 +1696,6 @@ func TestFallbackTCPHandler(t *testing.T) {
}
func TestCapturePcap(t *testing.T) {
tstest.Shard(t)
const timeLimit = 120
ctx, cancel := context.WithTimeout(context.Background(), timeLimit*time.Second)
defer cancel()
@@ -1763,7 +1749,6 @@ func TestCapturePcap(t *testing.T) {
}
func TestUDPConn(t *testing.T) {
tstest.Shard(t)
tstest.ResourceCheck(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
@@ -1955,7 +1940,6 @@ func sendData(logf func(format string, args ...any), ctx context.Context, bytesC
}
func TestUserMetricsByteCounters(t *testing.T) {
tstest.Shard(t)
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
@@ -2070,7 +2054,6 @@ func TestUserMetricsByteCounters(t *testing.T) {
}
func TestUserMetricsRouteGauges(t *testing.T) {
tstest.Shard(t)
// Windows does not seem to support or report back routes when running in
// userspace via tsnet. So, we skip this check on Windows.
// TODO(kradalby): Figure out if this is correct.
@@ -2306,7 +2289,6 @@ type listenTest struct {
// If useTUN is true, s2 uses a chanTUN; otherwise it uses netstack only.
func setupTwoClientTest(t *testing.T, useTUN bool) *listenTest {
t.Helper()
tstest.Shard(t)
tstest.ResourceCheck(t)
ctx := t.Context()
controlURL, control := startControl(t)
@@ -2904,7 +2886,6 @@ func buildDNSQuery(name string, srcIP netip.Addr) []byte {
}
func TestDeps(t *testing.T) {
tstest.Shard(t)
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
@@ -3168,7 +3149,6 @@ func TestResolveAuthKey(t *testing.T) {
// packets were sent to WireGuard (which has no peer for the node's own IP)
// and silently dropped, causing Dial to hang indefinitely.
func TestSelfDial(t *testing.T) {
tstest.Shard(t)
tstest.ResourceCheck(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()