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