github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/src/cmd/go/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  	"flag"
     9  	"fmt"
    10  	"os"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"cmd/go/internal/base"
    15  	"cmd/go/internal/cfg"
    16  	"cmd/go/internal/str"
    17  	"cmd/go/internal/work"
    18  )
    19  
    20  // The flag handling part of go test is large and distracting.
    21  // We can't use the flag package because some of the flags from
    22  // our command line are for us, and some are for 6.out, and
    23  // some are for both.
    24  
    25  // testFlagSpec defines a flag we know about.
    26  type testFlagSpec struct {
    27  	name       string
    28  	boolVar    *bool
    29  	flagValue  flag.Value
    30  	passToTest bool // pass to Test
    31  	multiOK    bool // OK to have multiple instances
    32  	present    bool // flag has been seen
    33  }
    34  
    35  // testFlagDefn is the set of flags we process.
    36  var testFlagDefn = []*testFlagSpec{
    37  	// local.
    38  	{name: "c", boolVar: &testC},
    39  	{name: "i", boolVar: &cfg.BuildI},
    40  	{name: "o"},
    41  	{name: "cover", boolVar: &testCover},
    42  	{name: "covermode"},
    43  	{name: "coverpkg"},
    44  	{name: "exec"},
    45  
    46  	// passed to 6.out, adding a "test." prefix to the name if necessary: -v becomes -test.v.
    47  	{name: "bench", passToTest: true},
    48  	{name: "benchmem", boolVar: new(bool), passToTest: true},
    49  	{name: "benchtime", passToTest: true},
    50  	{name: "count", passToTest: true},
    51  	{name: "coverprofile", passToTest: true},
    52  	{name: "cpu", passToTest: true},
    53  	{name: "cpuprofile", passToTest: true},
    54  	{name: "memprofile", passToTest: true},
    55  	{name: "memprofilerate", passToTest: true},
    56  	{name: "blockprofile", passToTest: true},
    57  	{name: "blockprofilerate", passToTest: true},
    58  	{name: "mutexprofile", passToTest: true},
    59  	{name: "mutexprofilefraction", passToTest: true},
    60  	{name: "outputdir", passToTest: true},
    61  	{name: "parallel", passToTest: true},
    62  	{name: "run", passToTest: true},
    63  	{name: "short", boolVar: new(bool), passToTest: true},
    64  	{name: "timeout", passToTest: true},
    65  	{name: "trace", passToTest: true},
    66  	{name: "v", boolVar: &testV, passToTest: true},
    67  }
    68  
    69  // add build flags to testFlagDefn
    70  func init() {
    71  	var cmd base.Command
    72  	work.AddBuildFlags(&cmd)
    73  	cmd.Flag.VisitAll(func(f *flag.Flag) {
    74  		if f.Name == "v" {
    75  			// test overrides the build -v flag
    76  			return
    77  		}
    78  		testFlagDefn = append(testFlagDefn, &testFlagSpec{
    79  			name:      f.Name,
    80  			flagValue: f.Value,
    81  		})
    82  	})
    83  }
    84  
    85  // testFlags processes the command line, grabbing -x and -c, rewriting known flags
    86  // to have "test" before them, and reading the command line for the 6.out.
    87  // Unfortunately for us, we need to do our own flag processing because go test
    88  // grabs some flags but otherwise its command line is just a holding place for
    89  // pkg.test's arguments.
    90  // We allow known flags both before and after the package name list,
    91  // to allow both
    92  //	go test fmt -custom-flag-for-fmt-test
    93  //	go test -x math
    94  func testFlags(args []string) (packageNames, passToTest []string) {
    95  	inPkg := false
    96  	outputDir := ""
    97  	var explicitArgs []string
    98  	for i := 0; i < len(args); i++ {
    99  		if !strings.HasPrefix(args[i], "-") {
   100  			if !inPkg && packageNames == nil {
   101  				// First package name we've seen.
   102  				inPkg = true
   103  			}
   104  			if inPkg {
   105  				packageNames = append(packageNames, args[i])
   106  				continue
   107  			}
   108  		}
   109  
   110  		if inPkg {
   111  			// Found an argument beginning with "-"; end of package list.
   112  			inPkg = false
   113  		}
   114  
   115  		f, value, extraWord := testFlag(args, i)
   116  		if f == nil {
   117  			// This is a flag we do not know; we must assume
   118  			// that any args we see after this might be flag
   119  			// arguments, not package names.
   120  			inPkg = false
   121  			if packageNames == nil {
   122  				// make non-nil: we have seen the empty package list
   123  				packageNames = []string{}
   124  			}
   125  			if args[i] == "-args" || args[i] == "--args" {
   126  				// -args or --args signals that everything that follows
   127  				// should be passed to the test.
   128  				explicitArgs = args[i+1:]
   129  				break
   130  			}
   131  			passToTest = append(passToTest, args[i])
   132  			continue
   133  		}
   134  		if f.flagValue != nil {
   135  			if err := f.flagValue.Set(value); err != nil {
   136  				base.Fatalf("invalid flag argument for -%s: %v", f.name, err)
   137  			}
   138  		} else {
   139  			// Test-only flags.
   140  			// Arguably should be handled by f.flagValue, but aren't.
   141  			switch f.name {
   142  			// bool flags.
   143  			case "c", "i", "v", "cover":
   144  				setBoolFlag(f.boolVar, value)
   145  			case "o":
   146  				testO = value
   147  				testNeedBinary = true
   148  			case "exec":
   149  				xcmd, err := str.SplitQuotedFields(value)
   150  				if err != nil {
   151  					base.Fatalf("invalid flag argument for -%s: %v", f.name, err)
   152  				}
   153  				work.ExecCmd = xcmd
   154  			case "bench":
   155  				// record that we saw the flag; don't care about the value
   156  				testBench = true
   157  			case "timeout":
   158  				testTimeout = value
   159  			case "blockprofile", "cpuprofile", "memprofile", "mutexprofile":
   160  				testProfile = true
   161  				testNeedBinary = true
   162  			case "trace":
   163  				testProfile = true
   164  			case "coverpkg":
   165  				testCover = true
   166  				if value == "" {
   167  					testCoverPaths = nil
   168  				} else {
   169  					testCoverPaths = strings.Split(value, ",")
   170  				}
   171  			case "coverprofile":
   172  				testCover = true
   173  				testProfile = true
   174  			case "covermode":
   175  				switch value {
   176  				case "set", "count", "atomic":
   177  					cfg.TestCoverMode = value
   178  				default:
   179  					base.Fatalf("invalid flag argument for -covermode: %q", value)
   180  				}
   181  				testCover = true
   182  			case "outputdir":
   183  				outputDir = value
   184  			}
   185  		}
   186  		if extraWord {
   187  			i++
   188  		}
   189  		if f.passToTest {
   190  			passToTest = append(passToTest, "-test."+f.name+"="+value)
   191  		}
   192  	}
   193  
   194  	if cfg.TestCoverMode == "" {
   195  		cfg.TestCoverMode = "set"
   196  		if cfg.BuildRace {
   197  			// Default coverage mode is atomic when -race is set.
   198  			cfg.TestCoverMode = "atomic"
   199  		}
   200  	}
   201  
   202  	// Tell the test what directory we're running in, so it can write the profiles there.
   203  	if testProfile && outputDir == "" {
   204  		dir, err := os.Getwd()
   205  		if err != nil {
   206  			base.Fatalf("error from os.Getwd: %s", err)
   207  		}
   208  		passToTest = append(passToTest, "-test.outputdir", dir)
   209  	}
   210  
   211  	passToTest = append(passToTest, explicitArgs...)
   212  	return
   213  }
   214  
   215  // testFlag sees if argument i is a known flag and returns its definition, value, and whether it consumed an extra word.
   216  func testFlag(args []string, i int) (f *testFlagSpec, value string, extra bool) {
   217  	arg := args[i]
   218  	if strings.HasPrefix(arg, "--") { // reduce two minuses to one
   219  		arg = arg[1:]
   220  	}
   221  	switch arg {
   222  	case "-?", "-h", "-help":
   223  		base.Usage()
   224  	}
   225  	if arg == "" || arg[0] != '-' {
   226  		return
   227  	}
   228  	name := arg[1:]
   229  	// If there's already "test.", drop it for now.
   230  	name = strings.TrimPrefix(name, "test.")
   231  	equals := strings.Index(name, "=")
   232  	if equals >= 0 {
   233  		value = name[equals+1:]
   234  		name = name[:equals]
   235  	}
   236  	for _, f = range testFlagDefn {
   237  		if name == f.name {
   238  			// Booleans are special because they have modes -x, -x=true, -x=false.
   239  			if f.boolVar != nil || isBoolFlag(f.flagValue) {
   240  				if equals < 0 { // otherwise, it's been set and will be verified in setBoolFlag
   241  					value = "true"
   242  				} else {
   243  					// verify it parses
   244  					setBoolFlag(new(bool), value)
   245  				}
   246  			} else { // Non-booleans must have a value.
   247  				extra = equals < 0
   248  				if extra {
   249  					if i+1 >= len(args) {
   250  						testSyntaxError("missing argument for flag " + f.name)
   251  					}
   252  					value = args[i+1]
   253  				}
   254  			}
   255  			if f.present && !f.multiOK {
   256  				testSyntaxError(f.name + " flag may be set only once")
   257  			}
   258  			f.present = true
   259  			return
   260  		}
   261  	}
   262  	f = nil
   263  	return
   264  }
   265  
   266  // isBoolFlag reports whether v is a bool flag.
   267  func isBoolFlag(v flag.Value) bool {
   268  	vv, ok := v.(interface {
   269  		IsBoolFlag() bool
   270  	})
   271  	if ok {
   272  		return vv.IsBoolFlag()
   273  	}
   274  	return false
   275  }
   276  
   277  // setBoolFlag sets the addressed boolean to the value.
   278  func setBoolFlag(flag *bool, value string) {
   279  	x, err := strconv.ParseBool(value)
   280  	if err != nil {
   281  		testSyntaxError("illegal bool flag value " + value)
   282  	}
   283  	*flag = x
   284  }
   285  
   286  // setIntFlag sets the addressed integer to the value.
   287  func setIntFlag(flag *int, value string) {
   288  	x, err := strconv.Atoi(value)
   289  	if err != nil {
   290  		testSyntaxError("illegal int flag value " + value)
   291  	}
   292  	*flag = x
   293  }
   294  
   295  func testSyntaxError(msg string) {
   296  	fmt.Fprintf(os.Stderr, "go test: %s\n", msg)
   297  	fmt.Fprintf(os.Stderr, `run "go help test" or "go help testflag" for more information`+"\n")
   298  	os.Exit(2)
   299  }