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
+111 -4
View File
@@ -15,12 +15,16 @@ import (
"encoding/json"
"errors"
"fmt"
"hash/fnv"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"slices"
"sort"
"strconv"
"strings"
"time"
@@ -83,6 +87,94 @@ type goTestOutput struct {
var debug = os.Getenv("TS_TESTWRAPPER_DEBUG") != ""
// testsForShard returns the test names in pkg that belong to the given shard
// spec (e.g. "2/3"). It uses "go list -json" to find test source files (no
// compilation) and scans them for top-level test function names, assigning
// each to a shard by hashing. Returns nil if the spec is invalid or if
// listing fails (the main run will surface the error).
func testsForShard(ctx context.Context, pkg, shardSpec string) ([]string, error) {
a, b, ok := strings.Cut(shardSpec, "/")
if !ok {
return nil, nil
}
wantShard, err := strconv.Atoi(a)
if err != nil || wantShard < 1 {
return nil, nil
}
shards, err := strconv.Atoi(b)
if err != nil || shards < 1 {
return nil, nil
}
out, err := exec.CommandContext(ctx, "go", "list", "-json", pkg).Output()
if err != nil {
// Errors will be surfaced by the main test run.
return nil, nil
}
type pkgJSON struct {
Dir string
TestGoFiles []string
XTestGoFiles []string
}
seen := map[string]bool{}
var result []string
dec := json.NewDecoder(bytes.NewReader(out))
for dec.More() {
var p pkgJSON
if err := dec.Decode(&p); err != nil {
break
}
for _, f := range append(p.TestGoFiles, p.XTestGoFiles...) {
names, err := testFuncNames(filepath.Join(p.Dir, f))
if err != nil {
continue
}
for _, name := range names {
if seen[name] {
continue
}
seen[name] = true
h := fnv.New32a()
io.WriteString(h, name)
if int(h.Sum32()%uint32(shards)) == wantShard-1 {
result = append(result, name)
}
}
}
}
return result, nil
}
// testFuncNames scans a Go source file and returns the names of all top-level
// test functions (Test*, Benchmark*, Example*, Fuzz*).
func testFuncNames(path string) ([]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
var names []string
sc := bufio.NewScanner(f)
for sc.Scan() {
rest, ok := strings.CutPrefix(sc.Text(), "func ")
if !ok {
continue
}
for _, prefix := range []string{"Test", "Benchmark", "Example", "Fuzz"} {
if strings.HasPrefix(rest, prefix) {
if i := strings.IndexByte(rest, '('); i > 0 {
names = append(names, rest[:i])
}
break
}
}
}
return names, sc.Err()
}
// runTests runs the tests in pt and sends the results on ch. It sends a
// testAttempt for each test and a final testAttempt per pkg with pkgFinished
// set to true. Package build errors will not emit a testAttempt (as no valid
@@ -94,8 +186,24 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, goTestArgs, te
args = append(args, goTestArgs...)
args = append(args, pt.Pattern)
if len(pt.Tests) > 0 {
// Specific tests requested (e.g. flaky test retry).
runArg := strings.Join(pt.Tests, "|")
args = append(args, "--run", runArg)
} else if shardSpec := os.Getenv("TS_TEST_SHARD"); shardSpec != "" {
// Automatic test-name sharding: list tests and filter by hash.
shardTests, err := testsForShard(ctx, pt.Pattern, shardSpec)
if err != nil {
return err
}
if len(shardTests) == 0 {
ch <- &testAttempt{pkg: pt.Pattern, outcome: "skip", pkgFinished: true}
return nil
}
quoted := make([]string, len(shardTests))
for i, name := range shardTests {
quoted[i] = regexp.QuoteMeta(name)
}
args = append(args, "--run", "^("+strings.Join(quoted, "|")+")$")
}
args = append(args, testArgs...)
args = append(args, "-json")
@@ -103,9 +211,6 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, goTestArgs, te
fmt.Println("running", strings.Join(args, " "))
}
cmd := exec.CommandContext(ctx, "go", args...)
if len(pt.Tests) > 0 {
cmd.Env = append(os.Environ(), "TS_TEST_SHARD=") // clear test shard; run all tests we say to run
}
r, err := cmd.StdoutPipe()
if err != nil {
log.Printf("error creating stdout pipe: %v", err)
@@ -113,7 +218,9 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, goTestArgs, te
defer r.Close()
cmd.Stderr = os.Stderr
cmd.Env = os.Environ()
cmd.Env = slices.DeleteFunc(os.Environ(), func(s string) bool {
return strings.HasPrefix(s, "TS_TEST_SHARD=")
})
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", flakytest.FlakeAttemptEnv, attempt))
if err := cmd.Start(); err != nil {
-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()
-2
View File
@@ -14,7 +14,6 @@ import (
// TestPeerCapMap tests that the node capability map (CapMap) is included in peer information.
func TestPeerCapMap(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
@@ -97,7 +96,6 @@ func TestPeerCapMap(t *testing.T) {
// TestSetNodeCapMap tests that SetNodeCapMap updates are propagated to peers.
func TestSetNodeCapMap(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
-35
View File
@@ -79,7 +79,6 @@ func fetchNetMapForTest(ctx context.Context, lc *local.Client) (*netmap.NetworkM
// Tests that tailscaled starts up in TUN mode, and also without data races:
// https://github.com/tailscale/tailscale/issues/7894
func TestTUNMode(t *testing.T) {
tstest.Shard(t)
tstest.RequireRoot(t)
tstest.Parallel(t)
env := NewTestEnv(t)
@@ -97,7 +96,6 @@ func TestTUNMode(t *testing.T) {
}
func TestOneNodeUpNoAuth(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n1 := NewTestNode(t, env)
@@ -115,7 +113,6 @@ func TestOneNodeUpNoAuth(t *testing.T) {
}
func TestOneNodeExpiredKey(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n1 := NewTestNode(t, env)
@@ -152,7 +149,6 @@ func TestOneNodeExpiredKey(t *testing.T) {
}
func TestControlKnobs(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n1 := NewTestNode(t, env)
@@ -183,7 +179,6 @@ func TestControlKnobs(t *testing.T) {
}
func TestExpectedFeaturesLinked(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n1 := NewTestNode(t, env)
@@ -205,7 +200,6 @@ func TestExpectedFeaturesLinked(t *testing.T) {
}
func TestCollectPanic(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n := NewTestNode(t, env)
@@ -248,7 +242,6 @@ func TestCollectPanic(t *testing.T) {
}
func TestControlTimeLogLine(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
env.LogCatcher.StoreRawJSON()
@@ -272,7 +265,6 @@ func TestControlTimeLogLine(t *testing.T) {
// test Issue 2321: Start with UpdatePrefs should save prefs to disk
func TestStateSavedOnStart(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n1 := NewTestNode(t, env)
@@ -473,7 +465,6 @@ func TestOneNodeUpAuth(t *testing.T) {
},
},
} {
tstest.Shard(t)
t.Run(tt.name, func(t *testing.T) {
tstest.Parallel(t)
@@ -559,7 +550,6 @@ func isNonZeroExitCode(err error) bool {
// If we interrupt `tailscale up` and then run it again, we should only
// print a single auth URL.
func TestOneNodeUpInterruptedAuth(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t, ConfigureControl(
@@ -638,7 +628,6 @@ func TestOneNodeUpInterruptedAuth(t *testing.T) {
// complete the device approval, we should see the device approval URL
// when we run `tailscale up` a second time.
func TestOneNodeUpInterruptedDeviceApproval(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t, ConfigureControl(
@@ -718,8 +707,6 @@ func TestOneNodeUpInterruptedDeviceApproval(t *testing.T) {
}
func TestConfigFileAuthKey(t *testing.T) {
tstest.SkipOnUnshardedCI(t)
tstest.Shard(t)
t.Parallel()
const authKey = "opensesame"
env := NewTestEnv(t, ConfigureControl(func(control *testcontrol.Server) {
@@ -745,7 +732,6 @@ func TestConfigFileAuthKey(t *testing.T) {
}
func TestTwoNodes(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
@@ -831,7 +817,6 @@ func TestTwoNodes(t *testing.T) {
// tests two nodes where the first gets a incremental MapResponse (with only
// PeersRemoved set) saying that the second node disappeared.
func TestIncrementalMapUpdatePeersRemoved(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
@@ -919,7 +904,6 @@ func TestIncrementalMapUpdatePeersRemoved(t *testing.T) {
// This covers VIP additions at runtime, where the VIP route is not reachable
// before the map mutation but is reachable over TSMP afterward.
func TestIncrementalMapUpdatePeerAllowedIPsReachability(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
@@ -1005,7 +989,6 @@ func TestIncrementalMapUpdatePeerAllowedIPsReachability(t *testing.T) {
}
func TestNodeAddressIPFields(t *testing.T) {
tstest.Shard(t)
flakytest.Mark(t, "https://github.com/tailscale/tailscale/issues/7008")
tstest.Parallel(t)
env := NewTestEnv(t)
@@ -1033,7 +1016,6 @@ func TestNodeAddressIPFields(t *testing.T) {
}
func TestAddPingRequest(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n1 := NewTestNode(t, env)
@@ -1086,7 +1068,6 @@ func TestAddPingRequest(t *testing.T) {
}
func TestC2NPingRequest(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
@@ -1148,7 +1129,6 @@ func TestC2NPingRequest(t *testing.T) {
// Issue 2434: when "down" (WantRunning false), tailscaled shouldn't
// be connected to control.
func TestNoControlConnWhenDown(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n1 := NewTestNode(t, env)
@@ -1197,7 +1177,6 @@ func TestNoControlConnWhenDown(t *testing.T) {
// Issue 2137: make sure Windows tailscaled works with the CLI alone,
// without the GUI to kick off a Start.
func TestOneNodeUpWindowsStyle(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n1 := NewTestNode(t, env)
@@ -1218,7 +1197,6 @@ func TestOneNodeUpWindowsStyle(t *testing.T) {
// node can initiate connections to the jailed node.
func TestClientSideJailing(t *testing.T) {
flakytest.Mark(t, "https://github.com/tailscale/tailscale/issues/17419")
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
registerNode := func() (*TestNode, key.NodePublic) {
@@ -1331,7 +1309,6 @@ func TestClientSideJailing(t *testing.T) {
// tries to do bi-directional pings between them.
func TestNATPing(t *testing.T) {
flakytest.Mark(t, "https://github.com/tailscale/tailscale/issues/12169")
tstest.Shard(t)
tstest.Parallel(t)
for _, v6 := range []bool{false, true} {
env := NewTestEnv(t)
@@ -1459,7 +1436,6 @@ func TestNATPing(t *testing.T) {
}
func TestLogoutRemovesAllPeers(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
// Spin up some nodes.
@@ -1521,7 +1497,6 @@ func TestAutoUpdateDefaults_cap(t *testing.T) { testAutoUpdateDefaults(t, true)
func testAutoUpdateDefaults(t *testing.T, useCap bool) {
t.Cleanup(feature.HookCanAutoUpdate.SetForTest(func() bool { return true }))
tstest.Shard(t)
env := NewTestEnv(t)
var (
@@ -1654,7 +1629,6 @@ func testAutoUpdateDefaults(t *testing.T, useCap bool) {
// gVisor/netstack.
// https://github.com/tailscale/corp/issues/22511
func TestDNSOverTCPIntervalResolver(t *testing.T) {
tstest.Shard(t)
tstest.RequireRoot(t)
env := NewTestEnv(t)
env.tunMode = true
@@ -1724,7 +1698,6 @@ func TestDNSOverTCPIntervalResolver(t *testing.T) {
// TestNetstackTCPLoopback tests netstack loopback of a TCP stream, in both
// directions.
func TestNetstackTCPLoopback(t *testing.T) {
tstest.Shard(t)
tstest.RequireRoot(t)
env := NewTestEnv(t)
@@ -1864,7 +1837,6 @@ func TestNetstackTCPLoopback(t *testing.T) {
// TestNetstackUDPLoopback tests netstack loopback of UDP packets, in both
// directions.
func TestNetstackUDPLoopback(t *testing.T) {
tstest.Shard(t)
tstest.RequireRoot(t)
env := NewTestEnv(t)
@@ -2013,7 +1985,6 @@ func TestEncryptStateMigration(t *testing.T) {
if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
t.Skip("--encrypt-state for tailscaled state not supported on this platform")
}
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n := NewTestNode(t, env)
@@ -2069,7 +2040,6 @@ func TestEncryptStateMigration(t *testing.T) {
// expected values.
func TestPeerRelayPing(t *testing.T) {
flakytest.Mark(t, "https://github.com/tailscale/tailscale/issues/17251")
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t, ConfigureControl(func(server *testcontrol.Server) {
@@ -2209,7 +2179,6 @@ func TestPeerRelayPing(t *testing.T) {
}
func TestC2NDebugNetmap(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t, ConfigureControl(func(s *testcontrol.Server) {
s.CollectServices = opt.False
@@ -2352,7 +2321,6 @@ func TestTailnetLock(t *testing.T) {
// If you run `tailscale lock log` on a node where Tailnet Lock isn't
// enabled, you get an error explaining that.
t.Run("log-when-not-enabled", func(t *testing.T) {
tstest.Shard(t)
t.Parallel()
env := NewTestEnv(t)
@@ -2390,7 +2358,6 @@ func TestTailnetLock(t *testing.T) {
// the signed nodes can talk to each other but the unsigned node cannot
// talk to anybody.
t.Run("node-connectivity", func(t *testing.T) {
tstest.Shard(t)
t.Parallel()
env := NewTestEnv(t)
@@ -2466,7 +2433,6 @@ func TestTailnetLock(t *testing.T) {
t.Run("no-keys-is-error", func(t *testing.T) {
for _, verb := range []string{"add", "remove", "revoke-keys"} {
t.Run(verb, func(t *testing.T) {
tstest.Shard(t)
t.Parallel()
env := NewTestEnv(t)
@@ -2493,7 +2459,6 @@ func TestTailnetLock(t *testing.T) {
}
func TestNodeWithBadStateFile(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n1 := NewTestNode(t, env)
-1
View File
@@ -21,7 +21,6 @@ import (
// netstack forwards the connection to localhost, and the listener
// calls WhoIs on n2's LocalAPI to identify the remote peer as n1.
func TestUserspaceWhoIsProxyMap(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
+1 -2
View File
@@ -57,7 +57,6 @@ func metricByName(t testing.TB, name string) *clientmetric.Metric {
// [tstest/largetailnet/BenchmarkGiantTailnet], which only measures cost
// of the same fast path — this test verifies correctness.
func TestNetmapDeltaFastPath(t *testing.T) {
tstest.Shard(t)
logf := logger.Discard
if testing.Verbose() {
@@ -105,7 +104,7 @@ func TestNetmapDeltaFastPath(t *testing.T) {
// Snapshot baseline metric values; we'll assert deltas against
// these. Globals make per-test isolation impossible, but deltas
// are robust against interleaving (assuming no other test runs in
// parallel here, hence tstest.Shard above).
// parallel here).
mFast := metricByName(t, "controlclient_map_response_handled_incrementally")
mFull := metricByName(t, "controlclient_map_response_handled_full_rebuild")
mUpsert := metricByName(t, "localbackend_netmap_delta_peer_upserted")
-37
View File
@@ -8,16 +8,12 @@ import (
"context"
"fmt"
"os"
"strconv"
"strings"
"sync/atomic"
"testing"
"time"
"tailscale.com/envknob"
"tailscale.com/types/logger"
"tailscale.com/util/backoff"
"tailscale.com/util/cibuild"
)
// AssertNotParallel asserts that t has not been marked as parallel.
@@ -48,7 +44,6 @@ func Replace[T any](t testing.TB, target *T, val T) {
})
*target = val
return
}
// WaitFor retries try for up to maxWait.
@@ -68,38 +63,6 @@ func WaitFor(maxWait time.Duration, try func() error) error {
return err
}
var testNum atomic.Int32
// Shard skips t if it's not running if the TS_TEST_SHARD test shard is set to
// "n/m" and this test execution number in the process mod m is not equal to n-1.
// That is, to run with 4 shards, set TS_TEST_SHARD=1/4, ..., TS_TEST_SHARD=4/4
// for the four jobs.
func Shard(t testing.TB) {
e := os.Getenv("TS_TEST_SHARD")
a, b, ok := strings.Cut(e, "/")
if !ok {
return
}
wantShard, _ := strconv.ParseInt(a, 10, 32)
shards, _ := strconv.ParseInt(b, 10, 32)
if wantShard == 0 || shards == 0 {
return
}
shard := ((testNum.Add(1) - 1) % int32(shards)) + 1
if shard != int32(wantShard) {
t.Skipf("skipping shard %d/%d (process has TS_TEST_SHARD=%q)", shard, shards, e)
}
}
// SkipOnUnshardedCI skips t if we're in CI and the TS_TEST_SHARD
// environment variable isn't set.
func SkipOnUnshardedCI(t testing.TB) {
if cibuild.On() && os.Getenv("TS_TEST_SHARD") == "" {
t.Skip("skipping on CI without TS_TEST_SHARD")
}
}
var serializeParallel = envknob.RegisterBool("TS_SERIAL_TESTS")
// Parallel calls t.Parallel, unless TS_SERIAL_TESTS is set true.