summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2026-04-10 21:30:27 +0000
committerBrad Fitzpatrick <brad@danga.com>2026-04-10 16:22:05 -0700
commitcf59a6fb237b153521a00977208e1fc0c895b867 (patch)
treed326ffb641c33c6bcabc48514bd7869d8cea3be5
parentca5db865b40055336a59471bb413c96dfa87fb5c (diff)
downloadtailscale-cf59a6fb237b153521a00977208e1fc0c895b867.tar.xz
tailscale-cf59a6fb237b153521a00977208e1fc0c895b867.zip
.github, tool/listpkgs: automatically find tests which use tstest.RequireRoot
Updates tailscale/corp#40007 Change-Id: I677d3d9e276cb6633a14ac07e4b58ea08e52fac4 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--.github/workflows/test.yml2
-rw-r--r--derp/xdp/xdp_linux_test.go6
-rw-r--r--ssh/tailssh/tailssh_test.go9
-rw-r--r--tool/listpkgs/listpkgs.go70
-rw-r--r--util/linuxfw/nftables_runner_test.go6
5 files changed, 82 insertions, 11 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 38ebd1291..1a7690022 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -361,7 +361,7 @@ jobs:
run: chown -R $(id -u):$(id -g) $PWD
- name: privileged tests
working-directory: src
- run: ./tool/go test ./util/linuxfw ./derp/xdp
+ run: ./tool/go test $(./tool/go run ./tool/listpkgs --has-root-tests)
vm:
needs: gomod-cache
diff --git a/derp/xdp/xdp_linux_test.go b/derp/xdp/xdp_linux_test.go
index cb59721f7..d8de2bf62 100644
--- a/derp/xdp/xdp_linux_test.go
+++ b/derp/xdp/xdp_linux_test.go
@@ -18,6 +18,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
"tailscale.com/net/stun"
+ "tailscale.com/tstest"
)
type xdpAction uint32
@@ -271,6 +272,7 @@ func getIPv6STUNBindingResp() []byte {
}
func TestXDP(t *testing.T) {
+ tstest.RequireRoot(t)
ipv4STUNBindingReqTX := getIPv4STUNBindingReq(nil)
ipv6STUNBindingReqTX := getIPv6STUNBindingReq(nil)
@@ -957,10 +959,6 @@ func TestXDP(t *testing.T) {
server, err := NewSTUNServer(&STUNServerConfig{DeviceName: "fake", DstPort: defaultSTUNPort},
&noAttachOption{})
if err != nil {
- if errors.Is(err, unix.EPERM) {
- // TODO(jwhited): get this running
- t.Skip("skipping due to EPERM error; test requires elevated privileges")
- }
t.Fatalf("error constructing STUN server: %v", err)
}
defer server.Close()
diff --git a/ssh/tailssh/tailssh_test.go b/ssh/tailssh/tailssh_test.go
index c8b5f698b..bd2cf759d 100644
--- a/ssh/tailssh/tailssh_test.go
+++ b/ssh/tailssh/tailssh_test.go
@@ -386,7 +386,14 @@ type localState struct {
serverActions map[string]*tailcfg.SSHAction
}
-var currentUser = os.Getenv("USER") // Use the current user for the test.
+var currentUser = func() string {
+ // Prefer user.Current because the USER env var is not set in
+ // some environments (e.g. the golang:latest container used by CI).
+ if u, err := user.Current(); err == nil {
+ return u.Username
+ }
+ return os.Getenv("USER")
+}()
func (ts *localState) Dialer() *tsdial.Dialer {
return &tsdial.Dialer{}
diff --git a/tool/listpkgs/listpkgs.go b/tool/listpkgs/listpkgs.go
index 1c2dda257..3cb4ab790 100644
--- a/tool/listpkgs/listpkgs.go
+++ b/tool/listpkgs/listpkgs.go
@@ -10,9 +10,12 @@ import (
"flag"
"fmt"
"go/build/constraint"
+ "io/fs"
"log"
"os"
+ "path/filepath"
"slices"
+ "sort"
"strings"
"sync"
@@ -27,11 +30,17 @@ var (
withoutTagsAnyStr = flag.String("without-tags-any", "", "if non-empty, a comma-separated list of build constraints to exclude (a package will be omitted if it contains any of these build tags)")
shard = flag.String("shard", "", "if non-empty, a string of the form 'N/M' to only print packages in shard N of M (e.g. '1/3', '2/3', '3/3/' for different thirds of the list)")
affectedByTag = flag.String("affected-by-tag", "", "if non-empty, only list packages whose test binary would be affected by the presence or absence of this build tag")
+ hasRootTests = flag.Bool("has-root-tests", false, "list packages (as ./relative/path) containing _test.go files that call tstest.RequireRoot")
)
func main() {
flag.Parse()
+ if *hasRootTests {
+ printRootTestPkgs()
+ return
+ }
+
patterns := flag.Args()
if len(patterns) == 0 {
flag.Usage()
@@ -281,3 +290,64 @@ func fileMentionsTag(filename, tag string) (bool, error) {
}
return tags[tag], nil
}
+
+// printRootTestPkgs walks the current directory tree looking for _test.go
+// files that contain "tstest.RequireRoot" and prints the unique package
+// directories as ./relative/path.
+func printRootTestPkgs() {
+ root, err := os.Getwd()
+ if err != nil {
+ log.Fatal(err)
+ }
+ seen := map[string]bool{}
+ var dirs []string
+ filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return nil
+ }
+ name := d.Name()
+ if d.IsDir() {
+ // Skip hidden dirs and common non-Go dirs.
+ if strings.HasPrefix(name, ".") || name == "vendor" || name == "node_modules" {
+ return filepath.SkipDir
+ }
+ return nil
+ }
+ if !strings.HasSuffix(name, "_test.go") {
+ return nil
+ }
+ rel, err := filepath.Rel(root, path)
+ if err != nil {
+ return nil
+ }
+ dir := filepath.Dir(rel)
+ if seen[dir] {
+ return nil // already found a match in this dir
+ }
+ if fileContains(path, "tstest.RequireRoot") {
+ seen[dir] = true
+ dirs = append(dirs, dir)
+ }
+ return nil
+ })
+ sort.Strings(dirs)
+ for _, d := range dirs {
+ fmt.Println("./" + filepath.ToSlash(d))
+ }
+}
+
+// fileContains reports whether the file at path contains the given substring.
+func fileContains(path, substr string) bool {
+ f, err := os.Open(path)
+ if err != nil {
+ return false
+ }
+ defer f.Close()
+ s := bufio.NewScanner(f)
+ for s.Scan() {
+ if strings.Contains(s.Text(), substr) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/util/linuxfw/nftables_runner_test.go b/util/linuxfw/nftables_runner_test.go
index 17945e245..58a1f96ed 100644
--- a/util/linuxfw/nftables_runner_test.go
+++ b/util/linuxfw/nftables_runner_test.go
@@ -10,7 +10,6 @@ import (
"errors"
"fmt"
"net/netip"
- "os"
"runtime"
"slices"
"strings"
@@ -522,10 +521,7 @@ func TestAddMatchSubnetRouteMarkRuleAccept(t *testing.T) {
func newSysConn(t *testing.T) *nftables.Conn {
t.Helper()
- if os.Geteuid() != 0 {
- t.Skip(t.Name(), " requires privileges to create a namespace in order to run")
- return nil
- }
+ tstest.RequireRoot(t)
runtime.LockOSThread()