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