1
2
3
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
24
25
26
27
28
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
47
48
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(&testList, "list", "", "")
61 cf.StringVar(&testMemProfile, "memprofile", "", "")
62 cf.String("memprofilerate", "", "")
63 cf.StringVar(&testMutexProfile, "mutexprofile", "", "")
64 cf.String("mutexprofilefraction", "", "")
65 cf.Var(&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.StringVar(&testTrace, "trace", "", "")
71 cf.BoolVar(&testV, "v", false, "")
72 cf.Var(&testShuffle, "shuffle", "")
73
74 for name := range passFlagToTest {
75 cf.Var(cf.Lookup(name).Value, "test."+name, "")
76 }
77 }
78
79
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
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
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
129
130 type outputdirFlag struct {
131 abs string
132 }
133
134 func (f *outputdirFlag) String() string {
135 return f.abs
136 }
137 func (f *outputdirFlag) Set(value string) (err error) {
138 if value == "" {
139 f.abs = ""
140 } else {
141 f.abs, err = filepath.Abs(value)
142 }
143 return err
144 }
145 func (f *outputdirFlag) getAbs() string {
146 if f.abs == "" {
147 return base.Cwd()
148 }
149 return f.abs
150 }
151
152
153
154
155 type vetFlag struct {
156 explicit bool
157 off bool
158 flags []string
159 }
160
161 func (f *vetFlag) String() string {
162 if f.off {
163 return "off"
164 }
165
166 var buf strings.Builder
167 for i, f := range f.flags {
168 if i > 0 {
169 buf.WriteByte(',')
170 }
171 buf.WriteString(f)
172 }
173 return buf.String()
174 }
175
176 func (f *vetFlag) Set(value string) error {
177 if value == "" {
178 *f = vetFlag{flags: defaultVetFlags}
179 return nil
180 }
181
182 if value == "off" {
183 *f = vetFlag{
184 explicit: true,
185 off: true,
186 }
187 return nil
188 }
189
190 if strings.Contains(value, "=") {
191 return fmt.Errorf("-vet argument cannot contain equal signs")
192 }
193 if strings.Contains(value, " ") {
194 return fmt.Errorf("-vet argument is comma-separated list, cannot contain spaces")
195 }
196 *f = vetFlag{explicit: true}
197 for _, arg := range strings.Split(value, ",") {
198 if arg == "" {
199 return fmt.Errorf("-vet argument contains empty list element")
200 }
201 f.flags = append(f.flags, "-"+arg)
202 }
203 return nil
204 }
205
206 type shuffleFlag struct {
207 on bool
208 seed *int64
209 }
210
211 func (f *shuffleFlag) String() string {
212 if !f.on {
213 return "off"
214 }
215 if f.seed == nil {
216 return "on"
217 }
218 return fmt.Sprintf("%d", *f.seed)
219 }
220
221 func (f *shuffleFlag) Set(value string) error {
222 if value == "off" {
223 *f = shuffleFlag{on: false}
224 return nil
225 }
226
227 if value == "on" {
228 *f = shuffleFlag{on: true}
229 return nil
230 }
231
232 seed, err := strconv.ParseInt(value, 10, 64)
233 if err != nil {
234 return fmt.Errorf(`-shuffle argument must be "on", "off", or an int64: %v`, err)
235 }
236
237 *f = shuffleFlag{on: true, seed: &seed}
238 return nil
239 }
240
241
242
243
244
245
246
247
248
249
250 func testFlags(args []string) (packageNames, passToTest []string) {
251 base.SetFromGOFLAGS(&CmdTest.Flag)
252 addFromGOFLAGS := map[string]bool{}
253 CmdTest.Flag.Visit(func(f *flag.Flag) {
254 if short := strings.TrimPrefix(f.Name, "test."); passFlagToTest[short] {
255 addFromGOFLAGS[f.Name] = true
256 }
257 })
258
259
260
261 firstUnknownFlag := ""
262
263 explicitArgs := make([]string, 0, len(args))
264 inPkgList := false
265 afterFlagWithoutValue := false
266 for len(args) > 0 {
267 f, remainingArgs, err := cmdflag.ParseOne(&CmdTest.Flag, args)
268
269 wasAfterFlagWithoutValue := afterFlagWithoutValue
270 afterFlagWithoutValue = false
271
272 if errors.Is(err, flag.ErrHelp) {
273 exitWithUsage()
274 }
275
276 if errors.Is(err, cmdflag.ErrFlagTerminator) {
277
278
279
280
281 explicitArgs = append(explicitArgs, args...)
282 break
283 }
284
285 if nf := (cmdflag.NonFlagError{}); errors.As(err, &nf) {
286 if !inPkgList && packageNames != nil {
287
288
289
290 if wasAfterFlagWithoutValue {
291
292
293
294
295
296
297 explicitArgs = append(explicitArgs, nf.RawArg)
298 args = remainingArgs
299 continue
300 } else {
301
302
303 explicitArgs = append(explicitArgs, args...)
304 break
305 }
306 }
307
308 inPkgList = true
309 packageNames = append(packageNames, nf.RawArg)
310 args = remainingArgs
311 continue
312 }
313
314 if inPkgList {
315
316
317 inPkgList = false
318 }
319
320 if nd := (cmdflag.FlagNotDefinedError{}); errors.As(err, &nd) {
321
322
323
324
325
326
327
328 if packageNames == nil {
329 packageNames = []string{}
330 }
331
332 if nd.RawArg == "-args" || nd.RawArg == "--args" {
333
334
335 explicitArgs = append(explicitArgs, remainingArgs...)
336 break
337 }
338
339 if firstUnknownFlag == "" {
340 firstUnknownFlag = nd.RawArg
341 }
342
343 explicitArgs = append(explicitArgs, nd.RawArg)
344 args = remainingArgs
345 if !nd.HasValue {
346 afterFlagWithoutValue = true
347 }
348 continue
349 }
350
351 if err != nil {
352 fmt.Fprintln(os.Stderr, err)
353 exitWithUsage()
354 }
355
356 if short := strings.TrimPrefix(f.Name, "test."); passFlagToTest[short] {
357 explicitArgs = append(explicitArgs, fmt.Sprintf("-test.%s=%v", short, f.Value))
358
359
360
361 delete(addFromGOFLAGS, short)
362 delete(addFromGOFLAGS, "test."+short)
363 }
364
365 args = remainingArgs
366 }
367 if firstUnknownFlag != "" && (testC || cfg.BuildI) {
368 buildFlag := "-c"
369 if !testC {
370 buildFlag = "-i"
371 }
372 fmt.Fprintf(os.Stderr, "go test: unknown flag %s cannot be used with %s\n", firstUnknownFlag, buildFlag)
373 exitWithUsage()
374 }
375
376 var injectedFlags []string
377 if testJSON {
378
379
380 injectedFlags = append(injectedFlags, "-test.v=true")
381 delete(addFromGOFLAGS, "v")
382 delete(addFromGOFLAGS, "test.v")
383 }
384
385
386
387
388 var timeoutSet, outputDirSet bool
389 CmdTest.Flag.Visit(func(f *flag.Flag) {
390 short := strings.TrimPrefix(f.Name, "test.")
391 if addFromGOFLAGS[f.Name] {
392 injectedFlags = append(injectedFlags, fmt.Sprintf("-test.%s=%v", short, f.Value))
393 }
394 switch short {
395 case "timeout":
396 timeoutSet = true
397 case "outputdir":
398 outputDirSet = true
399 }
400 })
401
402
403
404
405 if testTimeout > 0 && !timeoutSet {
406 injectedFlags = append(injectedFlags, fmt.Sprintf("-test.timeout=%v", testTimeout))
407 }
408
409
410
411
412
413 if testProfile() != "" && !outputDirSet {
414 injectedFlags = append(injectedFlags, "-test.outputdir="+testOutputDir.getAbs())
415 }
416
417
418
419
420
421
422
423 helpLoop:
424 for _, arg := range explicitArgs {
425 switch arg {
426 case "--":
427 break helpLoop
428 case "-h", "-help", "--help":
429 testHelp = true
430 break helpLoop
431 }
432 }
433
434
435 if testCoverMode == "" {
436 testCoverMode = "set"
437 if cfg.BuildRace {
438
439 testCoverMode = "atomic"
440 }
441 }
442 if cfg.BuildRace && testCoverMode != "atomic" {
443 base.Fatalf(`-covermode must be "atomic", not %q, when -race is enabled`, testCoverMode)
444 }
445
446
447 return packageNames, append(injectedFlags, explicitArgs...)
448 }
449
450 func exitWithUsage() {
451 fmt.Fprintf(os.Stderr, "usage: %s\n", CmdTest.UsageLine)
452 fmt.Fprintf(os.Stderr, "Run 'go help %s' and 'go help %s' for details.\n", CmdTest.LongName(), HelpTestflag.LongName())
453
454 base.SetExitStatus(2)
455 base.Exit()
456 }
457
View as plain text