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