Files
tailscale/cmd/testwrapper/args.go
Brad Fitzpatrick 3ce13eb2b9 cmd/testwrapper: add support for the -vet test flag
So callers can run testwrapper with -vet=off if they're already
running vet explicitly in a concurrent test job.

Updates tailscale/corp#28679

Change-Id: I74ad56e560076d187f5e3a7d7381e1dac89d860c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-01-30 12:49:05 -08:00

116 lines
3.3 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"flag"
"io"
"os"
"slices"
"strings"
"testing"
)
// registerTestFlags registers all flags from the testing package with the
// provided flag set. It does so by calling testing.Init() and then iterating
// over all flags registered on flag.CommandLine.
func registerTestFlags(fs *flag.FlagSet) {
testing.Init()
type bv interface {
IsBoolFlag() bool
}
flag.CommandLine.VisitAll(func(f *flag.Flag) {
if b, ok := f.Value.(bv); ok && b.IsBoolFlag() {
fs.Bool(f.Name, f.DefValue == "true", f.Usage)
if name, ok := strings.CutPrefix(f.Name, "test."); ok {
fs.Bool(name, f.DefValue == "true", f.Usage)
}
return
}
// We don't actually care about the value of the flag, so we just
// register it as a string. The values will be passed to `go test` which
// will parse and validate them anyway.
fs.String(f.Name, f.DefValue, f.Usage)
if name, ok := strings.CutPrefix(f.Name, "test."); ok {
fs.String(name, f.DefValue, f.Usage)
}
})
}
// splitArgs splits args into three parts as consumed by go test.
//
// go test [build/test flags] [packages] [build/test flags & test binary flags]
//
// We return these as three slices of strings [pre] [pkgs] [post].
//
// It is used to split the arguments passed to testwrapper into the arguments
// passed to go test and the arguments passed to the tests.
func splitArgs(args []string) (pre, pkgs, post []string, _ error) {
if len(args) == 0 {
return nil, nil, nil, nil
}
fs := newTestFlagSet()
// Parse stops at the first non-flag argument, so this allows us
// to parse those as values and then reconstruct them as args.
if err := fs.Parse(args); err != nil {
return nil, nil, nil, err
}
fs.Visit(func(f *flag.Flag) {
if f.Value.String() != f.DefValue && f.DefValue != "false" {
pre = append(pre, "-"+f.Name, f.Value.String())
} else {
pre = append(pre, "-"+f.Name)
}
})
// fs.Args() now contains [packages]+[build/test flags & test binary flags],
// to split it we need to find the first non-flag argument.
rem := fs.Args()
ix := slices.IndexFunc(rem, func(s string) bool { return strings.HasPrefix(s, "-") })
if ix == -1 {
return pre, rem, nil, nil
}
pkgs = rem[:ix]
post = rem[ix:]
return pre, pkgs, post, nil
}
func newTestFlagSet() *flag.FlagSet {
fs := flag.NewFlagSet("testwrapper", flag.ContinueOnError)
fs.SetOutput(io.Discard)
// Register all flags from the testing package.
registerTestFlags(fs)
// Also register the -exec flag, which is not part of the testing package.
// TODO(maisem): figure out what other flags we need to register explicitly.
fs.String("exec", "", "Command to run tests with")
fs.Bool("race", false, "build with race detector")
fs.String("vet", "", "vet checks to run, or 'off' or 'all'")
return fs
}
// testingVerbose reports whether the test is being run with verbose logging.
var testingVerbose = func() bool {
verbose := false
// Likely doesn't matter, but to be correct follow the go flag parsing logic
// of overriding previous values.
for _, arg := range os.Args[1:] {
switch arg {
case "-test.v", "--test.v",
"-test.v=true", "--test.v=true",
"-v", "--v",
"-v=true", "--v=true":
verbose = true
case "-test.v=false", "--test.v=false",
"-v=false", "--v=false":
verbose = false
}
}
return verbose
}()