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