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