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