Home | History | Annotate | Download | only in 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 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", "mutexprofile":
    155 				testProfile = true
    156 				testNeedBinary = true
    157 			case "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 }
    295