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