github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/test/testflag.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package test 6 7 import ( 8 "errors" 9 "flag" 10 "fmt" 11 "os" 12 "path/filepath" 13 "strconv" 14 "strings" 15 "time" 16 17 "github.com/go-asm/go/cmd/go/base" 18 "github.com/go-asm/go/cmd/go/cmdflag" 19 "github.com/go-asm/go/cmd/go/work" 20 ) 21 22 //go:generate go run ./genflags.go 23 24 // The flag handling part of go test is large and distracting. 25 // We can't use (*flag.FlagSet).Parse because some of the flags from 26 // our command line are for us, and some are for the test binary, and 27 // some are for both. 28 29 func init() { 30 work.AddBuildFlags(CmdTest, work.OmitVFlag) 31 32 cf := CmdTest.Flag 33 cf.BoolVar(&testC, "c", false, "") 34 cf.StringVar(&testO, "o", "", "") 35 work.AddCoverFlags(CmdTest, &testCoverProfile) 36 cf.Var((*base.StringsFlag)(&work.ExecCmd), "exec", "") 37 cf.BoolVar(&testJSON, "json", false, "") 38 cf.Var(&testVet, "vet", "") 39 40 // Register flags to be forwarded to the test binary. We retain variables for 41 // some of them so that cmd/go knows what to do with the test output, or knows 42 // to build the test in a way that supports the use of the flag. 43 44 cf.StringVar(&testBench, "bench", "", "") 45 cf.Bool("benchmem", false, "") 46 cf.String("benchtime", "", "") 47 cf.StringVar(&testBlockProfile, "blockprofile", "", "") 48 cf.String("blockprofilerate", "", "") 49 cf.Int("count", 0, "") 50 cf.String("cpu", "", "") 51 cf.StringVar(&testCPUProfile, "cpuprofile", "", "") 52 cf.Bool("failfast", false, "") 53 cf.StringVar(&testFuzz, "fuzz", "", "") 54 cf.Bool("fullpath", false, "") 55 cf.StringVar(&testList, "list", "", "") 56 cf.StringVar(&testMemProfile, "memprofile", "", "") 57 cf.String("memprofilerate", "", "") 58 cf.StringVar(&testMutexProfile, "mutexprofile", "", "") 59 cf.String("mutexprofilefraction", "", "") 60 cf.Var(&testOutputDir, "outputdir", "") 61 cf.Int("parallel", 0, "") 62 cf.String("run", "", "") 63 cf.Bool("short", false, "") 64 cf.String("skip", "", "") 65 cf.DurationVar(&testTimeout, "timeout", 10*time.Minute, "") // known to cmd/dist 66 cf.String("fuzztime", "", "") 67 cf.String("fuzzminimizetime", "", "") 68 cf.StringVar(&testTrace, "trace", "", "") 69 cf.Var(&testV, "v", "") 70 cf.Var(&testShuffle, "shuffle", "") 71 72 for name, ok := range passFlagToTest { 73 if ok { 74 cf.Var(cf.Lookup(name).Value, "test."+name, "") 75 } 76 } 77 } 78 79 // outputdirFlag implements the -outputdir flag. 80 // It interprets an empty value as the working directory of the 'go' command. 81 type outputdirFlag struct { 82 abs string 83 } 84 85 func (f *outputdirFlag) String() string { 86 return f.abs 87 } 88 89 func (f *outputdirFlag) Set(value string) (err error) { 90 if value == "" { 91 f.abs = "" 92 } else { 93 f.abs, err = filepath.Abs(value) 94 } 95 return err 96 } 97 98 func (f *outputdirFlag) getAbs() string { 99 if f.abs == "" { 100 return base.Cwd() 101 } 102 return f.abs 103 } 104 105 // vetFlag implements the special parsing logic for the -vet flag: 106 // a comma-separated list, with distinguished values "all" and 107 // "off", plus a boolean tracking whether it was set explicitly. 108 // 109 // "all" is encoded as vetFlag{true, false, nil}, since it will 110 // pass no flags to the vet binary, and by default, it runs all 111 // analyzers. 112 type vetFlag struct { 113 explicit bool 114 off bool 115 flags []string // passed to vet when invoked automatically during 'go test' 116 } 117 118 func (f *vetFlag) String() string { 119 switch { 120 case !f.off && !f.explicit && len(f.flags) == 0: 121 return "all" 122 case f.off: 123 return "off" 124 } 125 126 var buf strings.Builder 127 for i, f := range f.flags { 128 if i > 0 { 129 buf.WriteByte(',') 130 } 131 buf.WriteString(f) 132 } 133 return buf.String() 134 } 135 136 func (f *vetFlag) Set(value string) error { 137 switch { 138 case value == "": 139 *f = vetFlag{flags: defaultVetFlags} 140 return nil 141 case strings.Contains(value, "="): 142 return fmt.Errorf("-vet argument cannot contain equal signs") 143 case strings.Contains(value, " "): 144 return fmt.Errorf("-vet argument is comma-separated list, cannot contain spaces") 145 } 146 147 *f = vetFlag{explicit: true} 148 var single string 149 for _, arg := range strings.Split(value, ",") { 150 switch arg { 151 case "": 152 return fmt.Errorf("-vet argument contains empty list element") 153 case "all": 154 single = arg 155 *f = vetFlag{explicit: true} 156 continue 157 case "off": 158 single = arg 159 *f = vetFlag{ 160 explicit: true, 161 off: true, 162 } 163 continue 164 default: 165 if _, ok := passAnalyzersToVet[arg]; !ok { 166 return fmt.Errorf("-vet argument must be a supported analyzer or a distinguished value; found %s", arg) 167 } 168 f.flags = append(f.flags, "-"+arg) 169 } 170 } 171 if len(f.flags) > 1 && single != "" { 172 return fmt.Errorf("-vet does not accept %q in a list with other analyzers", single) 173 } 174 if len(f.flags) > 1 && single != "" { 175 return fmt.Errorf("-vet does not accept %q in a list with other analyzers", single) 176 } 177 return nil 178 } 179 180 type shuffleFlag struct { 181 on bool 182 seed *int64 183 } 184 185 func (f *shuffleFlag) String() string { 186 if !f.on { 187 return "off" 188 } 189 if f.seed == nil { 190 return "on" 191 } 192 return fmt.Sprintf("%d", *f.seed) 193 } 194 195 func (f *shuffleFlag) Set(value string) error { 196 if value == "off" { 197 *f = shuffleFlag{on: false} 198 return nil 199 } 200 201 if value == "on" { 202 *f = shuffleFlag{on: true} 203 return nil 204 } 205 206 seed, err := strconv.ParseInt(value, 10, 64) 207 if err != nil { 208 return fmt.Errorf(`-shuffle argument must be "on", "off", or an int64: %v`, err) 209 } 210 211 *f = shuffleFlag{on: true, seed: &seed} 212 return nil 213 } 214 215 // testFlags processes the command line, grabbing -x and -c, rewriting known flags 216 // to have "test" before them, and reading the command line for the test binary. 217 // Unfortunately for us, we need to do our own flag processing because go test 218 // grabs some flags but otherwise its command line is just a holding place for 219 // pkg.test's arguments. 220 // We allow known flags both before and after the package name list, 221 // to allow both 222 // 223 // go test fmt -custom-flag-for-fmt-test 224 // go test -x math 225 func testFlags(args []string) (packageNames, passToTest []string) { 226 base.SetFromGOFLAGS(&CmdTest.Flag) 227 addFromGOFLAGS := map[string]bool{} 228 CmdTest.Flag.Visit(func(f *flag.Flag) { 229 if short := strings.TrimPrefix(f.Name, "test."); passFlagToTest[short] { 230 addFromGOFLAGS[f.Name] = true 231 } 232 }) 233 234 // firstUnknownFlag helps us report an error when flags not known to 'go 235 // test' are used along with -i or -c. 236 firstUnknownFlag := "" 237 238 explicitArgs := make([]string, 0, len(args)) 239 inPkgList := false 240 afterFlagWithoutValue := false 241 for len(args) > 0 { 242 f, remainingArgs, err := cmdflag.ParseOne(&CmdTest.Flag, args) 243 244 wasAfterFlagWithoutValue := afterFlagWithoutValue 245 afterFlagWithoutValue = false // provisionally 246 247 if errors.Is(err, flag.ErrHelp) { 248 exitWithUsage() 249 } 250 251 if errors.Is(err, cmdflag.ErrFlagTerminator) { 252 // 'go list' allows package arguments to be named either before or after 253 // the terminator, but 'go test' has historically allowed them only 254 // before. Preserve that behavior and treat all remaining arguments — 255 // including the terminator itself! — as arguments to the test. 256 explicitArgs = append(explicitArgs, args...) 257 break 258 } 259 260 if nf := (cmdflag.NonFlagError{}); errors.As(err, &nf) { 261 if !inPkgList && packageNames != nil { 262 // We already saw the package list previously, and this argument is not 263 // a flag, so it — and everything after it — must be either a value for 264 // a preceding flag or a literal argument to the test binary. 265 if wasAfterFlagWithoutValue { 266 // This argument could syntactically be a flag value, so 267 // optimistically assume that it is and keep looking for go command 268 // flags after it. 269 // 270 // (If we're wrong, we'll at least be consistent with historical 271 // behavior; see https://golang.org/issue/40763.) 272 explicitArgs = append(explicitArgs, nf.RawArg) 273 args = remainingArgs 274 continue 275 } else { 276 // This argument syntactically cannot be a flag value, so it must be a 277 // positional argument, and so must everything after it. 278 explicitArgs = append(explicitArgs, args...) 279 break 280 } 281 } 282 283 inPkgList = true 284 packageNames = append(packageNames, nf.RawArg) 285 args = remainingArgs // Consume the package name. 286 continue 287 } 288 289 if inPkgList { 290 // This argument is syntactically a flag, so if we were in the package 291 // list we're not anymore. 292 inPkgList = false 293 } 294 295 if nd := (cmdflag.FlagNotDefinedError{}); errors.As(err, &nd) { 296 // This is a flag we do not know. We must assume that any args we see 297 // after this might be flag arguments, not package names, so make 298 // packageNames non-nil to indicate that the package list is complete. 299 // 300 // (Actually, we only strictly need to assume that if the flag is not of 301 // the form -x=value, but making this more precise would be a breaking 302 // change in the command line API.) 303 if packageNames == nil { 304 packageNames = []string{} 305 } 306 307 if nd.RawArg == "-args" || nd.RawArg == "--args" { 308 // -args or --args signals that everything that follows 309 // should be passed to the test. 310 explicitArgs = append(explicitArgs, remainingArgs...) 311 break 312 } 313 314 if firstUnknownFlag == "" { 315 firstUnknownFlag = nd.RawArg 316 } 317 318 explicitArgs = append(explicitArgs, nd.RawArg) 319 args = remainingArgs 320 if !nd.HasValue { 321 afterFlagWithoutValue = true 322 } 323 continue 324 } 325 326 if err != nil { 327 fmt.Fprintln(os.Stderr, err) 328 exitWithUsage() 329 } 330 331 if short := strings.TrimPrefix(f.Name, "test."); passFlagToTest[short] { 332 explicitArgs = append(explicitArgs, fmt.Sprintf("-test.%s=%v", short, f.Value)) 333 334 // This flag has been overridden explicitly, so don't forward its implicit 335 // value from GOFLAGS. 336 delete(addFromGOFLAGS, short) 337 delete(addFromGOFLAGS, "test."+short) 338 } 339 340 args = remainingArgs 341 } 342 if firstUnknownFlag != "" && testC { 343 fmt.Fprintf(os.Stderr, "go: unknown flag %s cannot be used with -c\n", firstUnknownFlag) 344 exitWithUsage() 345 } 346 347 var injectedFlags []string 348 if testJSON { 349 // If converting to JSON, we need the full output in order to pipe it to test2json. 350 // The -test.v=test2json flag is like -test.v=true but causes the test to add 351 // extra ^V characters before testing output lines and other framing, 352 // which helps test2json do a better job creating the JSON events. 353 injectedFlags = append(injectedFlags, "-test.v=test2json") 354 delete(addFromGOFLAGS, "v") 355 delete(addFromGOFLAGS, "test.v") 356 } 357 358 // Inject flags from GOFLAGS before the explicit command-line arguments. 359 // (They must appear before the flag terminator or first non-flag argument.) 360 // Also determine whether flags with awkward defaults have already been set. 361 var timeoutSet, outputDirSet bool 362 CmdTest.Flag.Visit(func(f *flag.Flag) { 363 short := strings.TrimPrefix(f.Name, "test.") 364 if addFromGOFLAGS[f.Name] { 365 injectedFlags = append(injectedFlags, fmt.Sprintf("-test.%s=%v", short, f.Value)) 366 } 367 switch short { 368 case "timeout": 369 timeoutSet = true 370 case "outputdir": 371 outputDirSet = true 372 } 373 }) 374 375 // 'go test' has a default timeout, but the test binary itself does not. 376 // If the timeout wasn't set (and forwarded) explicitly, add the default 377 // timeout to the command line. 378 if testTimeout > 0 && !timeoutSet { 379 injectedFlags = append(injectedFlags, fmt.Sprintf("-test.timeout=%v", testTimeout)) 380 } 381 382 // Similarly, the test binary defaults -test.outputdir to its own working 383 // directory, but 'go test' defaults it to the working directory of the 'go' 384 // command. Set it explicitly if it is needed due to some other flag that 385 // requests output. 386 if testProfile() != "" && !outputDirSet { 387 injectedFlags = append(injectedFlags, "-test.outputdir="+testOutputDir.getAbs()) 388 } 389 390 // If the user is explicitly passing -help or -h, show output 391 // of the test binary so that the help output is displayed 392 // even though the test will exit with success. 393 // This loop is imperfect: it will do the wrong thing for a case 394 // like -args -test.outputdir -help. Such cases are probably rare, 395 // and getting this wrong doesn't do too much harm. 396 helpLoop: 397 for _, arg := range explicitArgs { 398 switch arg { 399 case "--": 400 break helpLoop 401 case "-h", "-help", "--help": 402 testHelp = true 403 break helpLoop 404 } 405 } 406 407 // Forward any unparsed arguments (following --args) to the test binary. 408 return packageNames, append(injectedFlags, explicitArgs...) 409 } 410 411 func exitWithUsage() { 412 fmt.Fprintf(os.Stderr, "usage: %s\n", CmdTest.UsageLine) 413 fmt.Fprintf(os.Stderr, "Run 'go help %s' and 'go help %s' for details.\n", CmdTest.LongName(), HelpTestflag.LongName()) 414 415 base.SetExitStatus(2) 416 base.Exit() 417 }