args.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. // Copyright (c) Tailscale Inc & contributors
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package main
  4. import (
  5. "flag"
  6. "io"
  7. "os"
  8. "slices"
  9. "strconv"
  10. "strings"
  11. "testing"
  12. )
  13. // registerTestFlags registers all flags from the testing package with the
  14. // provided flag set. It does so by calling testing.Init() and then iterating
  15. // over all flags registered on flag.CommandLine.
  16. func registerTestFlags(fs *flag.FlagSet) {
  17. testing.Init()
  18. type bv interface {
  19. IsBoolFlag() bool
  20. }
  21. flag.CommandLine.VisitAll(func(f *flag.Flag) {
  22. if b, ok := f.Value.(bv); ok && b.IsBoolFlag() {
  23. fs.Bool(f.Name, f.DefValue == "true", f.Usage)
  24. if name, ok := strings.CutPrefix(f.Name, "test."); ok {
  25. fs.Bool(name, f.DefValue == "true", f.Usage)
  26. }
  27. return
  28. }
  29. // We don't actually care about the value of the flag, so we just
  30. // register it as a string. The values will be passed to `go test` which
  31. // will parse and validate them anyway.
  32. fs.String(f.Name, f.DefValue, f.Usage)
  33. if name, ok := strings.CutPrefix(f.Name, "test."); ok {
  34. fs.String(name, f.DefValue, f.Usage)
  35. }
  36. })
  37. }
  38. // splitArgs splits args into three parts as consumed by go test.
  39. //
  40. // go test [build/test flags] [packages] [build/test flags & test binary flags]
  41. //
  42. // We return these as three slices of strings [pre] [pkgs] [post].
  43. //
  44. // It is used to split the arguments passed to testwrapper into the arguments
  45. // passed to go test and the arguments passed to the tests.
  46. func splitArgs(args []string) (pre, pkgs, post []string, _ error) {
  47. if len(args) == 0 {
  48. return nil, nil, nil, nil
  49. }
  50. fs := newTestFlagSet()
  51. // Parse stops at the first non-flag argument, so this allows us
  52. // to parse those as values and then reconstruct them as args.
  53. if err := fs.Parse(args); err != nil {
  54. return nil, nil, nil, err
  55. }
  56. fs.Visit(func(f *flag.Flag) {
  57. if f.Name == "cachelink" && !cacheLink.enabled {
  58. return
  59. }
  60. if f.Value.String() != f.DefValue && f.DefValue != "false" {
  61. pre = append(pre, "-"+f.Name, f.Value.String())
  62. } else {
  63. pre = append(pre, "-"+f.Name)
  64. }
  65. })
  66. // fs.Args() now contains [packages]+[build/test flags & test binary flags],
  67. // to split it we need to find the first non-flag argument.
  68. rem := fs.Args()
  69. ix := slices.IndexFunc(rem, func(s string) bool { return strings.HasPrefix(s, "-") })
  70. if ix == -1 {
  71. return pre, rem, nil, nil
  72. }
  73. pkgs = rem[:ix]
  74. post = rem[ix:]
  75. return pre, pkgs, post, nil
  76. }
  77. // cacheLink is whether the -cachelink flag is enabled.
  78. //
  79. // The -cachelink flag is Tailscale-specific addition to the "go test" command;
  80. // see https://github.com/tailscale/go/issues/149 and
  81. // https://github.com/golang/go/issues/77349.
  82. //
  83. // In that PR, it's only a boolean, but we implement a custom flag type
  84. // so we can support -cachelink=auto, which enables cachelink if GOCACHEPROG
  85. // is set, which is a behavior we want in our CI environment.
  86. var cacheLink cacheLinkVal
  87. type cacheLinkVal struct {
  88. enabled bool
  89. }
  90. func (c *cacheLinkVal) String() string {
  91. return strconv.FormatBool(c.enabled)
  92. }
  93. func (c *cacheLinkVal) Set(s string) error {
  94. if s == "auto" {
  95. c.enabled = os.Getenv("GOCACHEPROG") != ""
  96. return nil
  97. }
  98. var err error
  99. c.enabled, err = strconv.ParseBool(s)
  100. return err
  101. }
  102. func (*cacheLinkVal) IsBoolFlag() bool { return true }
  103. func newTestFlagSet() *flag.FlagSet {
  104. fs := flag.NewFlagSet("testwrapper", flag.ContinueOnError)
  105. fs.SetOutput(io.Discard)
  106. // Register all flags from the testing package.
  107. registerTestFlags(fs)
  108. // Also register the -exec flag, which is not part of the testing package.
  109. // TODO(maisem): figure out what other flags we need to register explicitly.
  110. fs.String("exec", "", "Command to run tests with")
  111. fs.Bool("race", false, "build with race detector")
  112. fs.String("vet", "", "vet checks to run, or 'off' or 'all'")
  113. fs.Var(&cacheLink, "cachelink", "Go -cachelink value (bool); or 'auto' to enable if GOCACHEPROG is set")
  114. return fs
  115. }
  116. // testingVerbose reports whether the test is being run with verbose logging.
  117. var testingVerbose = func() bool {
  118. verbose := false
  119. // Likely doesn't matter, but to be correct follow the go flag parsing logic
  120. // of overriding previous values.
  121. for _, arg := range os.Args[1:] {
  122. switch arg {
  123. case "-test.v", "--test.v",
  124. "-test.v=true", "--test.v=true",
  125. "-v", "--v",
  126. "-v=true", "--v=true":
  127. verbose = true
  128. case "-test.v=false", "--test.v=false",
  129. "-v=false", "--v=false":
  130. verbose = false
  131. }
  132. }
  133. return verbose
  134. }()