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