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