Black Lives Matter. Support the Equal Justice Initiative.

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

Documentation: cmd/go/internal/test

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

View as plain text