Source file
src/cmd/vet/vet_test.go
1
2
3
4
5 package main
6
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "internal/testenv"
12 "log"
13 "os"
14 "os/exec"
15 "path"
16 "path/filepath"
17 "regexp"
18 "strconv"
19 "strings"
20 "sync"
21 "testing"
22 )
23
24
25
26 func TestMain(m *testing.M) {
27 if os.Getenv("GO_VETTEST_IS_VET") != "" {
28 main()
29 os.Exit(0)
30 }
31
32 os.Setenv("GO_VETTEST_IS_VET", "1")
33 os.Exit(m.Run())
34 }
35
36
37 func vetPath(t testing.TB) string {
38 t.Helper()
39 testenv.MustHaveExec(t)
40
41 vetPathOnce.Do(func() {
42 vetExePath, vetPathErr = os.Executable()
43 })
44 if vetPathErr != nil {
45 t.Fatal(vetPathErr)
46 }
47 return vetExePath
48 }
49
50 var (
51 vetPathOnce sync.Once
52 vetExePath string
53 vetPathErr error
54 )
55
56 func vetCmd(t *testing.T, arg, pkg string) *exec.Cmd {
57 cmd := testenv.Command(t, testenv.GoToolPath(t), "vet", "-vettool="+vetPath(t), arg, path.Join("cmd/vet/testdata", pkg))
58 cmd.Env = os.Environ()
59 return cmd
60 }
61
62 func TestVet(t *testing.T) {
63 t.Parallel()
64 for _, pkg := range []string{
65 "appends",
66 "asm",
67 "assign",
68 "atomic",
69 "bool",
70 "buildtag",
71 "cgo",
72 "composite",
73 "copylock",
74 "deadcode",
75 "directive",
76 "httpresponse",
77 "lostcancel",
78 "method",
79 "nilfunc",
80 "print",
81 "shift",
82 "slog",
83 "structtag",
84 "testingpkg",
85
86 "unmarshal",
87 "unsafeptr",
88 "unused",
89 } {
90 pkg := pkg
91 t.Run(pkg, func(t *testing.T) {
92 t.Parallel()
93
94
95 if pkg == "cgo" && !cgoEnabled(t) {
96 return
97 }
98
99 cmd := vetCmd(t, "-printfuncs=Warn,Warnf", pkg)
100
101
102 if pkg == "asm" {
103 cmd.Env = append(cmd.Env, "GOOS=linux", "GOARCH=amd64")
104 }
105
106 dir := filepath.Join("testdata", pkg)
107 gos, err := filepath.Glob(filepath.Join(dir, "*.go"))
108 if err != nil {
109 t.Fatal(err)
110 }
111 asms, err := filepath.Glob(filepath.Join(dir, "*.s"))
112 if err != nil {
113 t.Fatal(err)
114 }
115 var files []string
116 files = append(files, gos...)
117 files = append(files, asms...)
118
119 errchk(cmd, files, t)
120 })
121 }
122
123
124
125
126
127
128 t.Run("loopclosure", func(t *testing.T) {
129 cmd := testenv.Command(t, testenv.GoToolPath(t), "vet", "-vettool="+vetPath(t), ".")
130 cmd.Env = append(os.Environ(), "GOWORK=off")
131 cmd.Dir = "testdata/rangeloop"
132 cmd.Stderr = new(strings.Builder)
133 cmd.Run()
134 stderr := cmd.Stderr.(fmt.Stringer).String()
135
136 filename := filepath.FromSlash("testdata/rangeloop/rangeloop.go")
137
138
139
140
141
142
143
144
145
146
147
148 stderr = strings.ReplaceAll(stderr, filepath.FromSlash("./rangeloop.go"), filename)
149
150 if err := errorCheck(stderr, false, filename, filepath.Base(filename)); err != nil {
151 t.Errorf("error check failed: %s", err)
152 t.Log("vet stderr:\n", cmd.Stderr)
153 }
154 })
155
156
157
158
159 t.Run("stdversion", func(t *testing.T) {
160 cmd := testenv.Command(t, testenv.GoToolPath(t), "vet", "-vettool="+vetPath(t), ".")
161 cmd.Env = append(os.Environ(), "GOWORK=off")
162 cmd.Dir = "testdata/stdversion"
163 cmd.Stderr = new(strings.Builder)
164 cmd.Run()
165 stderr := cmd.Stderr.(fmt.Stringer).String()
166
167 filename := filepath.FromSlash("testdata/stdversion/stdversion.go")
168
169
170
171
172
173
174
175
176
177
178
179 stderr = strings.ReplaceAll(stderr, filepath.FromSlash("./stdversion.go"), filename)
180
181 if err := errorCheck(stderr, false, filename, filepath.Base(filename)); err != nil {
182 t.Errorf("error check failed: %s", err)
183 t.Log("vet stderr:\n", cmd.Stderr)
184 }
185 })
186 }
187
188 func cgoEnabled(t *testing.T) bool {
189
190
191
192
193
194 cmd := testenv.Command(t, testenv.GoToolPath(t), "list", "-f", "{{context.CgoEnabled}}")
195 out, _ := cmd.CombinedOutput()
196 return string(out) == "true\n"
197 }
198
199 func errchk(c *exec.Cmd, files []string, t *testing.T) {
200 output, err := c.CombinedOutput()
201 if _, ok := err.(*exec.ExitError); !ok {
202 t.Logf("vet output:\n%s", output)
203 t.Fatal(err)
204 }
205 fullshort := make([]string, 0, len(files)*2)
206 for _, f := range files {
207 fullshort = append(fullshort, f, filepath.Base(f))
208 }
209 err = errorCheck(string(output), false, fullshort...)
210 if err != nil {
211 t.Errorf("error check failed: %s", err)
212 }
213 }
214
215
216 func TestTags(t *testing.T) {
217 t.Parallel()
218 for tag, wantFile := range map[string]int{
219 "testtag": 1,
220 "x testtag y": 1,
221 "othertag": 2,
222 } {
223 tag, wantFile := tag, wantFile
224 t.Run(tag, func(t *testing.T) {
225 t.Parallel()
226 t.Logf("-tags=%s", tag)
227 cmd := vetCmd(t, "-tags="+tag, "tagtest")
228 output, err := cmd.CombinedOutput()
229
230 want := fmt.Sprintf("file%d.go", wantFile)
231 dontwant := fmt.Sprintf("file%d.go", 3-wantFile)
232
233
234 if !bytes.Contains(output, []byte(filepath.Join("tagtest", want))) {
235 t.Errorf("%s: %s was excluded, should be included", tag, want)
236 }
237 if bytes.Contains(output, []byte(filepath.Join("tagtest", dontwant))) {
238 t.Errorf("%s: %s was included, should be excluded", tag, dontwant)
239 }
240 if t.Failed() {
241 t.Logf("err=%s, output=<<%s>>", err, output)
242 }
243 })
244 }
245 }
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260 func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
261 var errs []error
262 out := splitOutput(outStr, wantAuto)
263
264 for i := range out {
265 for j := 0; j < len(fullshort); j += 2 {
266 full, short := fullshort[j], fullshort[j+1]
267 out[i] = strings.ReplaceAll(out[i], full, short)
268 }
269 }
270
271 var want []wantedError
272 for j := 0; j < len(fullshort); j += 2 {
273 full, short := fullshort[j], fullshort[j+1]
274 want = append(want, wantedErrors(full, short)...)
275 }
276 for _, we := range want {
277 var errmsgs []string
278 if we.auto {
279 errmsgs, out = partitionStrings("<autogenerated>", out)
280 } else {
281 errmsgs, out = partitionStrings(we.prefix, out)
282 }
283 if len(errmsgs) == 0 {
284 errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr))
285 continue
286 }
287 matched := false
288 n := len(out)
289 for _, errmsg := range errmsgs {
290
291
292 text := errmsg
293 if _, suffix, ok := strings.Cut(text, " "); ok {
294 text = suffix
295 }
296 if we.re.MatchString(text) {
297 matched = true
298 } else {
299 out = append(out, errmsg)
300 }
301 }
302 if !matched {
303 errs = append(errs, fmt.Errorf("%s:%d: no match for %#q in:\n\t%s", we.file, we.lineNum, we.reStr, strings.Join(out[n:], "\n\t")))
304 continue
305 }
306 }
307
308 if len(out) > 0 {
309 errs = append(errs, fmt.Errorf("Unmatched Errors:"))
310 for _, errLine := range out {
311 errs = append(errs, fmt.Errorf("%s", errLine))
312 }
313 }
314
315 if len(errs) == 0 {
316 return nil
317 }
318 if len(errs) == 1 {
319 return errs[0]
320 }
321 var buf strings.Builder
322 fmt.Fprintf(&buf, "\n")
323 for _, err := range errs {
324 fmt.Fprintf(&buf, "%s\n", err.Error())
325 }
326 return errors.New(buf.String())
327 }
328
329 func splitOutput(out string, wantAuto bool) []string {
330
331
332
333 var res []string
334 for _, line := range strings.Split(out, "\n") {
335 line = strings.TrimSuffix(line, "\r")
336 if strings.HasPrefix(line, "\t") {
337 res[len(res)-1] += "\n" + line
338 } else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") {
339 continue
340 } else if strings.TrimSpace(line) != "" {
341 res = append(res, line)
342 }
343 }
344 return res
345 }
346
347
348
349 func matchPrefix(s, prefix string) bool {
350 i := strings.Index(s, ":")
351 if i < 0 {
352 return false
353 }
354 j := strings.LastIndex(s[:i], "/")
355 s = s[j+1:]
356 if len(s) <= len(prefix) || s[:len(prefix)] != prefix {
357 return false
358 }
359 if s[len(prefix)] == ':' {
360 return true
361 }
362 return false
363 }
364
365 func partitionStrings(prefix string, strs []string) (matched, unmatched []string) {
366 for _, s := range strs {
367 if matchPrefix(s, prefix) {
368 matched = append(matched, s)
369 } else {
370 unmatched = append(unmatched, s)
371 }
372 }
373 return
374 }
375
376 type wantedError struct {
377 reStr string
378 re *regexp.Regexp
379 lineNum int
380 auto bool
381 file string
382 prefix string
383 }
384
385 var (
386 errRx = regexp.MustCompile(`// (?:GC_)?ERROR(NEXT)? (.*)`)
387 errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO(NEXT)? (.*)`)
388 errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
389 lineRx = regexp.MustCompile(`LINE(([+-])(\d+))?`)
390 )
391
392
393 func wantedErrors(file, short string) (errs []wantedError) {
394 cache := make(map[string]*regexp.Regexp)
395
396 src, err := os.ReadFile(file)
397 if err != nil {
398 log.Fatal(err)
399 }
400 for i, line := range strings.Split(string(src), "\n") {
401 lineNum := i + 1
402 if strings.Contains(line, "////") {
403
404 continue
405 }
406 var auto bool
407 m := errAutoRx.FindStringSubmatch(line)
408 if m != nil {
409 auto = true
410 } else {
411 m = errRx.FindStringSubmatch(line)
412 }
413 if m == nil {
414 continue
415 }
416 if m[1] == "NEXT" {
417 lineNum++
418 }
419 all := m[2]
420 mm := errQuotesRx.FindAllStringSubmatch(all, -1)
421 if mm == nil {
422 log.Fatalf("%s:%d: invalid errchk line: %s", file, lineNum, line)
423 }
424 for _, m := range mm {
425 replacedOnce := false
426 rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
427 if replacedOnce {
428 return m
429 }
430 replacedOnce = true
431 n := lineNum
432 if strings.HasPrefix(m, "LINE+") {
433 delta, _ := strconv.Atoi(m[5:])
434 n += delta
435 } else if strings.HasPrefix(m, "LINE-") {
436 delta, _ := strconv.Atoi(m[5:])
437 n -= delta
438 }
439 return fmt.Sprintf("%s:%d", short, n)
440 })
441 re := cache[rx]
442 if re == nil {
443 var err error
444 re, err = regexp.Compile(rx)
445 if err != nil {
446 log.Fatalf("%s:%d: invalid regexp \"%#q\" in ERROR line: %v", file, lineNum, rx, err)
447 }
448 cache[rx] = re
449 }
450 prefix := fmt.Sprintf("%s:%d", short, lineNum)
451 errs = append(errs, wantedError{
452 reStr: rx,
453 re: re,
454 prefix: prefix,
455 auto: auto,
456 lineNum: lineNum,
457 file: short,
458 })
459 }
460 }
461
462 return
463 }
464
View as plain text