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