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