Source file src/cmd/go/internal/test/testflag.go

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

View as plain text