github.com/megatontech/mynoteforgo@v0.0.0-20200507084910-5d0c6ea6e890/源码/cmd/vet/vet_test.go (about) 1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main_test 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "internal/testenv" 12 "io/ioutil" 13 "log" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "regexp" 18 "strconv" 19 "strings" 20 "sync" 21 "testing" 22 ) 23 24 const ( 25 dataDir = "testdata" 26 binary = "./testvet.exe" 27 ) 28 29 // We implement TestMain so remove the test binary when all is done. 30 func TestMain(m *testing.M) { 31 result := m.Run() 32 os.Remove(binary) 33 os.Exit(result) 34 } 35 36 var ( 37 buildMu sync.Mutex // guards following 38 built = false // We have built the binary. 39 failed = false // We have failed to build the binary, don't try again. 40 ) 41 42 func Build(t *testing.T) { 43 buildMu.Lock() 44 defer buildMu.Unlock() 45 if built { 46 return 47 } 48 if failed { 49 t.Skip("cannot run on this environment") 50 } 51 testenv.MustHaveGoBuild(t) 52 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", binary) 53 output, err := cmd.CombinedOutput() 54 if err != nil { 55 failed = true 56 fmt.Fprintf(os.Stderr, "%s\n", output) 57 t.Fatal(err) 58 } 59 built = true 60 } 61 62 func vetCmd(t *testing.T, args ...string) *exec.Cmd { 63 cmd := exec.Command(testenv.GoToolPath(t), "vet", "-vettool="+binary) 64 cmd.Args = append(cmd.Args, args...) 65 testdata, err := filepath.Abs("testdata") 66 if err != nil { 67 t.Fatal(err) 68 } 69 cmd.Env = append(os.Environ(), "GOPATH="+testdata) 70 return cmd 71 } 72 73 func TestVet(t *testing.T) { 74 t.Parallel() 75 Build(t) 76 for _, pkg := range []string{ 77 "asm", 78 "assign", 79 "atomic", 80 "bool", 81 "buildtag", 82 "cgo", 83 "composite", 84 "copylock", 85 "deadcode", 86 "httpresponse", 87 "lostcancel", 88 "method", 89 "nilfunc", 90 "print", 91 "rangeloop", 92 "shift", 93 "structtag", 94 "testingpkg", 95 // "testtag" has its own test 96 "unmarshal", 97 "unsafeptr", 98 "unused", 99 } { 100 pkg := pkg 101 t.Run(pkg, func(t *testing.T) { 102 t.Parallel() 103 104 // Skip cgo test on platforms without cgo. 105 if pkg == "cgo" && !cgoEnabled(t) { 106 return 107 } 108 109 cmd := vetCmd(t, "-printfuncs=Warn,Warnf", pkg) 110 111 // The asm test assumes amd64. 112 if pkg == "asm" { 113 cmd.Env = append(cmd.Env, "GOOS=linux", "GOARCH=amd64") 114 } 115 116 dir := filepath.Join("testdata/src", pkg) 117 gos, err := filepath.Glob(filepath.Join(dir, "*.go")) 118 if err != nil { 119 t.Fatal(err) 120 } 121 asms, err := filepath.Glob(filepath.Join(dir, "*.s")) 122 if err != nil { 123 t.Fatal(err) 124 } 125 var files []string 126 files = append(files, gos...) 127 files = append(files, asms...) 128 129 errchk(cmd, files, t) 130 }) 131 } 132 } 133 134 func cgoEnabled(t *testing.T) bool { 135 // Don't trust build.Default.CgoEnabled as it is false for 136 // cross-builds unless CGO_ENABLED is explicitly specified. 137 // That's fine for the builders, but causes commands like 138 // 'GOARCH=386 go test .' to fail. 139 // Instead, we ask the go command. 140 cmd := exec.Command(testenv.GoToolPath(t), "list", "-f", "{{context.CgoEnabled}}") 141 out, _ := cmd.CombinedOutput() 142 return string(out) == "true\n" 143 } 144 145 func errchk(c *exec.Cmd, files []string, t *testing.T) { 146 output, err := c.CombinedOutput() 147 if _, ok := err.(*exec.ExitError); !ok { 148 t.Logf("vet output:\n%s", output) 149 t.Fatal(err) 150 } 151 fullshort := make([]string, 0, len(files)*2) 152 for _, f := range files { 153 fullshort = append(fullshort, f, filepath.Base(f)) 154 } 155 err = errorCheck(string(output), false, fullshort...) 156 if err != nil { 157 t.Errorf("error check failed: %s", err) 158 } 159 } 160 161 // TestTags verifies that the -tags argument controls which files to check. 162 func TestTags(t *testing.T) { 163 t.Parallel() 164 Build(t) 165 for tag, wantFile := range map[string]int{ 166 "testtag": 1, // file1 167 "x testtag y": 1, 168 "othertag": 2, 169 } { 170 tag, wantFile := tag, wantFile 171 t.Run(tag, func(t *testing.T) { 172 t.Parallel() 173 t.Logf("-tags=%s", tag) 174 cmd := vetCmd(t, "-tags="+tag, "tagtest") 175 output, err := cmd.CombinedOutput() 176 177 want := fmt.Sprintf("file%d.go", wantFile) 178 dontwant := fmt.Sprintf("file%d.go", 3-wantFile) 179 180 // file1 has testtag and file2 has !testtag. 181 if !bytes.Contains(output, []byte(filepath.Join("tagtest", want))) { 182 t.Errorf("%s: %s was excluded, should be included", tag, want) 183 } 184 if bytes.Contains(output, []byte(filepath.Join("tagtest", dontwant))) { 185 t.Errorf("%s: %s was included, should be excluded", tag, dontwant) 186 } 187 if t.Failed() { 188 t.Logf("err=%s, output=<<%s>>", err, output) 189 } 190 }) 191 } 192 } 193 194 // All declarations below were adapted from test/run.go. 195 196 // errorCheck matches errors in outStr against comments in source files. 197 // For each line of the source files which should generate an error, 198 // there should be a comment of the form // ERROR "regexp". 199 // If outStr has an error for a line which has no such comment, 200 // this function will report an error. 201 // Likewise if outStr does not have an error for a line which has a comment, 202 // or if the error message does not match the <regexp>. 203 // The <regexp> syntax is Perl but it's best to stick to egrep. 204 // 205 // Sources files are supplied as fullshort slice. 206 // It consists of pairs: full path to source file and its base name. 207 func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) { 208 var errs []error 209 out := splitOutput(outStr, wantAuto) 210 // Cut directory name. 211 for i := range out { 212 for j := 0; j < len(fullshort); j += 2 { 213 full, short := fullshort[j], fullshort[j+1] 214 out[i] = strings.ReplaceAll(out[i], full, short) 215 } 216 } 217 218 var want []wantedError 219 for j := 0; j < len(fullshort); j += 2 { 220 full, short := fullshort[j], fullshort[j+1] 221 want = append(want, wantedErrors(full, short)...) 222 } 223 for _, we := range want { 224 var errmsgs []string 225 if we.auto { 226 errmsgs, out = partitionStrings("<autogenerated>", out) 227 } else { 228 errmsgs, out = partitionStrings(we.prefix, out) 229 } 230 if len(errmsgs) == 0 { 231 errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr)) 232 continue 233 } 234 matched := false 235 n := len(out) 236 for _, errmsg := range errmsgs { 237 // Assume errmsg says "file:line: foo". 238 // Cut leading "file:line: " to avoid accidental matching of file name instead of message. 239 text := errmsg 240 if i := strings.Index(text, " "); i >= 0 { 241 text = text[i+1:] 242 } 243 if we.re.MatchString(text) { 244 matched = true 245 } else { 246 out = append(out, errmsg) 247 } 248 } 249 if !matched { 250 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"))) 251 continue 252 } 253 } 254 255 if len(out) > 0 { 256 errs = append(errs, fmt.Errorf("Unmatched Errors:")) 257 for _, errLine := range out { 258 errs = append(errs, fmt.Errorf("%s", errLine)) 259 } 260 } 261 262 if len(errs) == 0 { 263 return nil 264 } 265 if len(errs) == 1 { 266 return errs[0] 267 } 268 var buf bytes.Buffer 269 fmt.Fprintf(&buf, "\n") 270 for _, err := range errs { 271 fmt.Fprintf(&buf, "%s\n", err.Error()) 272 } 273 return errors.New(buf.String()) 274 } 275 276 func splitOutput(out string, wantAuto bool) []string { 277 // gc error messages continue onto additional lines with leading tabs. 278 // Split the output at the beginning of each line that doesn't begin with a tab. 279 // <autogenerated> lines are impossible to match so those are filtered out. 280 var res []string 281 for _, line := range strings.Split(out, "\n") { 282 line = strings.TrimSuffix(line, "\r") // normalize Windows output 283 if strings.HasPrefix(line, "\t") { 284 res[len(res)-1] += "\n" + line 285 } else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") { 286 continue 287 } else if strings.TrimSpace(line) != "" { 288 res = append(res, line) 289 } 290 } 291 return res 292 } 293 294 // matchPrefix reports whether s starts with file name prefix followed by a :, 295 // and possibly preceded by a directory name. 296 func matchPrefix(s, prefix string) bool { 297 i := strings.Index(s, ":") 298 if i < 0 { 299 return false 300 } 301 j := strings.LastIndex(s[:i], "/") 302 s = s[j+1:] 303 if len(s) <= len(prefix) || s[:len(prefix)] != prefix { 304 return false 305 } 306 if s[len(prefix)] == ':' { 307 return true 308 } 309 return false 310 } 311 312 func partitionStrings(prefix string, strs []string) (matched, unmatched []string) { 313 for _, s := range strs { 314 if matchPrefix(s, prefix) { 315 matched = append(matched, s) 316 } else { 317 unmatched = append(unmatched, s) 318 } 319 } 320 return 321 } 322 323 type wantedError struct { 324 reStr string 325 re *regexp.Regexp 326 lineNum int 327 auto bool // match <autogenerated> line 328 file string 329 prefix string 330 } 331 332 var ( 333 errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`) 334 errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`) 335 errQuotesRx = regexp.MustCompile(`"([^"]*)"`) 336 lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`) 337 ) 338 339 // wantedErrors parses expected errors from comments in a file. 340 func wantedErrors(file, short string) (errs []wantedError) { 341 cache := make(map[string]*regexp.Regexp) 342 343 src, err := ioutil.ReadFile(file) 344 if err != nil { 345 log.Fatal(err) 346 } 347 for i, line := range strings.Split(string(src), "\n") { 348 lineNum := i + 1 349 if strings.Contains(line, "////") { 350 // double comment disables ERROR 351 continue 352 } 353 var auto bool 354 m := errAutoRx.FindStringSubmatch(line) 355 if m != nil { 356 auto = true 357 } else { 358 m = errRx.FindStringSubmatch(line) 359 } 360 if m == nil { 361 continue 362 } 363 all := m[1] 364 mm := errQuotesRx.FindAllStringSubmatch(all, -1) 365 if mm == nil { 366 log.Fatalf("%s:%d: invalid errchk line: %s", file, lineNum, line) 367 } 368 for _, m := range mm { 369 replacedOnce := false 370 rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string { 371 if replacedOnce { 372 return m 373 } 374 replacedOnce = true 375 n := lineNum 376 if strings.HasPrefix(m, "LINE+") { 377 delta, _ := strconv.Atoi(m[5:]) 378 n += delta 379 } else if strings.HasPrefix(m, "LINE-") { 380 delta, _ := strconv.Atoi(m[5:]) 381 n -= delta 382 } 383 return fmt.Sprintf("%s:%d", short, n) 384 }) 385 re := cache[rx] 386 if re == nil { 387 var err error 388 re, err = regexp.Compile(rx) 389 if err != nil { 390 log.Fatalf("%s:%d: invalid regexp \"%#q\" in ERROR line: %v", file, lineNum, rx, err) 391 } 392 cache[rx] = re 393 } 394 prefix := fmt.Sprintf("%s:%d", short, lineNum) 395 errs = append(errs, wantedError{ 396 reStr: rx, 397 re: re, 398 prefix: prefix, 399 auto: auto, 400 lineNum: lineNum, 401 file: short, 402 }) 403 } 404 } 405 406 return 407 }