.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>
This commit is contained in:
Brad Fitzpatrick 2026-04-10 21:30:27 +00:00 committed by Brad Fitzpatrick
parent ca5db865b4
commit cf59a6fb23
5 changed files with 82 additions and 11 deletions

View File

@ -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

View File

@ -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()

View File

@ -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{}

View File

@ -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
}

View File

@ -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()