github.com/mh-cbon/go@v0.0.0-20160603070303-9e112a3fe4c0/test/run.go (about) 1 // skip 2 3 // Copyright 2012 The Go Authors. All rights reserved. 4 // Use of this source code is governed by a BSD-style 5 // license that can be found in the LICENSE file. 6 7 // Run runs tests in the test directory. 8 // 9 // TODO(bradfitz): docs of some sort, once we figure out how we're changing 10 // headers of files 11 package main 12 13 import ( 14 "bytes" 15 "errors" 16 "flag" 17 "fmt" 18 "hash/fnv" 19 "io" 20 "io/ioutil" 21 "log" 22 "os" 23 "os/exec" 24 "path" 25 "path/filepath" 26 "regexp" 27 "runtime" 28 "sort" 29 "strconv" 30 "strings" 31 "time" 32 "unicode" 33 ) 34 35 var ( 36 verbose = flag.Bool("v", false, "verbose. if set, parallelism is set to 1.") 37 keep = flag.Bool("k", false, "keep. keep temporary directory.") 38 numParallel = flag.Int("n", runtime.NumCPU(), "number of parallel tests to run") 39 summary = flag.Bool("summary", false, "show summary of results") 40 showSkips = flag.Bool("show_skips", false, "show skipped tests") 41 runSkips = flag.Bool("run_skips", false, "run skipped tests (ignore skip and build tags)") 42 linkshared = flag.Bool("linkshared", false, "") 43 updateErrors = flag.Bool("update_errors", false, "update error messages in test file based on compiler output") 44 runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run") 45 46 shard = flag.Int("shard", 0, "shard index to run. Only applicable if -shards is non-zero.") 47 shards = flag.Int("shards", 0, "number of shards. If 0, all tests are run. This is used by the continuous build.") 48 ) 49 50 var ( 51 goos, goarch string 52 53 // dirs are the directories to look for *.go files in. 54 // TODO(bradfitz): just use all directories? 55 dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "bugs"} 56 57 // ratec controls the max number of tests running at a time. 58 ratec chan bool 59 60 // toRun is the channel of tests to run. 61 // It is nil until the first test is started. 62 toRun chan *test 63 64 // rungatec controls the max number of runoutput tests 65 // executed in parallel as they can each consume a lot of memory. 66 rungatec chan bool 67 ) 68 69 // maxTests is an upper bound on the total number of tests. 70 // It is used as a channel buffer size to make sure sends don't block. 71 const maxTests = 5000 72 73 func main() { 74 flag.Parse() 75 76 goos = getenv("GOOS", runtime.GOOS) 77 goarch = getenv("GOARCH", runtime.GOARCH) 78 79 findExecCmd() 80 81 // Disable parallelism if printing or if using a simulator. 82 if *verbose || len(findExecCmd()) > 0 { 83 *numParallel = 1 84 } 85 86 ratec = make(chan bool, *numParallel) 87 rungatec = make(chan bool, *runoutputLimit) 88 89 var tests []*test 90 if flag.NArg() > 0 { 91 for _, arg := range flag.Args() { 92 if arg == "-" || arg == "--" { 93 // Permit running: 94 // $ go run run.go - env.go 95 // $ go run run.go -- env.go 96 // $ go run run.go - ./fixedbugs 97 // $ go run run.go -- ./fixedbugs 98 continue 99 } 100 if fi, err := os.Stat(arg); err == nil && fi.IsDir() { 101 for _, baseGoFile := range goFiles(arg) { 102 tests = append(tests, startTest(arg, baseGoFile)) 103 } 104 } else if strings.HasSuffix(arg, ".go") { 105 dir, file := filepath.Split(arg) 106 tests = append(tests, startTest(dir, file)) 107 } else { 108 log.Fatalf("can't yet deal with non-directory and non-go file %q", arg) 109 } 110 } 111 } else { 112 for _, dir := range dirs { 113 for _, baseGoFile := range goFiles(dir) { 114 tests = append(tests, startTest(dir, baseGoFile)) 115 } 116 } 117 } 118 119 failed := false 120 resCount := map[string]int{} 121 for _, test := range tests { 122 <-test.donec 123 status := "ok " 124 errStr := "" 125 if e, isSkip := test.err.(skipError); isSkip { 126 test.err = nil 127 errStr = "unexpected skip for " + path.Join(test.dir, test.gofile) + ": " + string(e) 128 status = "FAIL" 129 } 130 if test.err != nil { 131 status = "FAIL" 132 errStr = test.err.Error() 133 } 134 if status == "FAIL" { 135 failed = true 136 } 137 resCount[status]++ 138 dt := fmt.Sprintf("%.3fs", test.dt.Seconds()) 139 if status == "FAIL" { 140 fmt.Printf("# go run run.go -- %s\n%s\nFAIL\t%s\t%s\n", 141 path.Join(test.dir, test.gofile), 142 errStr, test.goFileName(), dt) 143 continue 144 } 145 if !*verbose { 146 continue 147 } 148 fmt.Printf("%s\t%s\t%s\n", status, test.goFileName(), dt) 149 } 150 151 if *summary { 152 for k, v := range resCount { 153 fmt.Printf("%5d %s\n", v, k) 154 } 155 } 156 157 if failed { 158 os.Exit(1) 159 } 160 } 161 162 func toolPath(name string) string { 163 p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name) 164 if _, err := os.Stat(p); err != nil { 165 log.Fatalf("didn't find binary at %s", p) 166 } 167 return p 168 } 169 170 func shardMatch(name string) bool { 171 if *shards == 0 { 172 return true 173 } 174 h := fnv.New32() 175 io.WriteString(h, name) 176 return int(h.Sum32()%uint32(*shards)) == *shard 177 } 178 179 func goFiles(dir string) []string { 180 f, err := os.Open(dir) 181 check(err) 182 dirnames, err := f.Readdirnames(-1) 183 check(err) 184 names := []string{} 185 for _, name := range dirnames { 186 if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && shardMatch(name) { 187 names = append(names, name) 188 } 189 } 190 sort.Strings(names) 191 return names 192 } 193 194 type runCmd func(...string) ([]byte, error) 195 196 func compileFile(runcmd runCmd, longname string) (out []byte, err error) { 197 cmd := []string{"go", "tool", "compile", "-e"} 198 if *linkshared { 199 cmd = append(cmd, "-dynlink", "-installsuffix=dynlink") 200 } 201 cmd = append(cmd, longname) 202 return runcmd(cmd...) 203 } 204 205 func compileInDir(runcmd runCmd, dir string, flags []string, names ...string) (out []byte, err error) { 206 cmd := []string{"go", "tool", "compile", "-e", "-D", ".", "-I", "."} 207 cmd = append(cmd, flags...) 208 if *linkshared { 209 cmd = append(cmd, "-dynlink", "-installsuffix=dynlink") 210 } 211 for _, name := range names { 212 cmd = append(cmd, filepath.Join(dir, name)) 213 } 214 return runcmd(cmd...) 215 } 216 217 func linkFile(runcmd runCmd, goname string) (err error) { 218 pfile := strings.Replace(goname, ".go", ".o", -1) 219 cmd := []string{"go", "tool", "link", "-w", "-o", "a.exe", "-L", "."} 220 if *linkshared { 221 cmd = append(cmd, "-linkshared", "-installsuffix=dynlink") 222 } 223 cmd = append(cmd, pfile) 224 _, err = runcmd(cmd...) 225 return 226 } 227 228 // skipError describes why a test was skipped. 229 type skipError string 230 231 func (s skipError) Error() string { return string(s) } 232 233 func check(err error) { 234 if err != nil { 235 log.Fatal(err) 236 } 237 } 238 239 // test holds the state of a test. 240 type test struct { 241 dir, gofile string 242 donec chan bool // closed when done 243 dt time.Duration 244 245 src string 246 action string // "compile", "build", etc. 247 248 tempDir string 249 err error 250 } 251 252 // startTest 253 func startTest(dir, gofile string) *test { 254 t := &test{ 255 dir: dir, 256 gofile: gofile, 257 donec: make(chan bool, 1), 258 } 259 if toRun == nil { 260 toRun = make(chan *test, maxTests) 261 go runTests() 262 } 263 select { 264 case toRun <- t: 265 default: 266 panic("toRun buffer size (maxTests) is too small") 267 } 268 return t 269 } 270 271 // runTests runs tests in parallel, but respecting the order they 272 // were enqueued on the toRun channel. 273 func runTests() { 274 for { 275 ratec <- true 276 t := <-toRun 277 go func() { 278 t.run() 279 <-ratec 280 }() 281 } 282 } 283 284 var cwd, _ = os.Getwd() 285 286 func (t *test) goFileName() string { 287 return filepath.Join(t.dir, t.gofile) 288 } 289 290 func (t *test) goDirName() string { 291 return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1)) 292 } 293 294 func goDirFiles(longdir string) (filter []os.FileInfo, err error) { 295 files, dirErr := ioutil.ReadDir(longdir) 296 if dirErr != nil { 297 return nil, dirErr 298 } 299 for _, gofile := range files { 300 if filepath.Ext(gofile.Name()) == ".go" { 301 filter = append(filter, gofile) 302 } 303 } 304 return 305 } 306 307 var packageRE = regexp.MustCompile(`(?m)^package (\w+)`) 308 309 func goDirPackages(longdir string) ([][]string, error) { 310 files, err := goDirFiles(longdir) 311 if err != nil { 312 return nil, err 313 } 314 var pkgs [][]string 315 m := make(map[string]int) 316 for _, file := range files { 317 name := file.Name() 318 data, err := ioutil.ReadFile(filepath.Join(longdir, name)) 319 if err != nil { 320 return nil, err 321 } 322 pkgname := packageRE.FindStringSubmatch(string(data)) 323 if pkgname == nil { 324 return nil, fmt.Errorf("cannot find package name in %s", name) 325 } 326 i, ok := m[pkgname[1]] 327 if !ok { 328 i = len(pkgs) 329 pkgs = append(pkgs, nil) 330 m[pkgname[1]] = i 331 } 332 pkgs[i] = append(pkgs[i], name) 333 } 334 return pkgs, nil 335 } 336 337 type context struct { 338 GOOS string 339 GOARCH string 340 } 341 342 // shouldTest looks for build tags in a source file and returns 343 // whether the file should be used according to the tags. 344 func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) { 345 if *runSkips { 346 return true, "" 347 } 348 for _, line := range strings.Split(src, "\n") { 349 line = strings.TrimSpace(line) 350 if strings.HasPrefix(line, "//") { 351 line = line[2:] 352 } else { 353 continue 354 } 355 line = strings.TrimSpace(line) 356 if len(line) == 0 || line[0] != '+' { 357 continue 358 } 359 ctxt := &context{ 360 GOOS: goos, 361 GOARCH: goarch, 362 } 363 words := strings.Fields(line) 364 if words[0] == "+build" { 365 ok := false 366 for _, word := range words[1:] { 367 if ctxt.match(word) { 368 ok = true 369 break 370 } 371 } 372 if !ok { 373 // no matching tag found. 374 return false, line 375 } 376 } 377 } 378 // no build tags 379 return true, "" 380 } 381 382 func (ctxt *context) match(name string) bool { 383 if name == "" { 384 return false 385 } 386 if i := strings.Index(name, ","); i >= 0 { 387 // comma-separated list 388 return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) 389 } 390 if strings.HasPrefix(name, "!!") { // bad syntax, reject always 391 return false 392 } 393 if strings.HasPrefix(name, "!") { // negation 394 return len(name) > 1 && !ctxt.match(name[1:]) 395 } 396 397 // Tags must be letters, digits, underscores or dots. 398 // Unlike in Go identifiers, all digits are fine (e.g., "386"). 399 for _, c := range name { 400 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { 401 return false 402 } 403 } 404 405 if name == ctxt.GOOS || name == ctxt.GOARCH { 406 return true 407 } 408 409 if name == "test_run" { 410 return true 411 } 412 413 return false 414 } 415 416 func init() { checkShouldTest() } 417 418 // run runs a test. 419 func (t *test) run() { 420 start := time.Now() 421 defer func() { 422 t.dt = time.Since(start) 423 close(t.donec) 424 }() 425 426 srcBytes, err := ioutil.ReadFile(t.goFileName()) 427 if err != nil { 428 t.err = err 429 return 430 } 431 t.src = string(srcBytes) 432 if t.src[0] == '\n' { 433 t.err = skipError("starts with newline") 434 return 435 } 436 437 // Execution recipe stops at first blank line. 438 pos := strings.Index(t.src, "\n\n") 439 if pos == -1 { 440 t.err = errors.New("double newline not found") 441 return 442 } 443 action := t.src[:pos] 444 if nl := strings.Index(action, "\n"); nl >= 0 && strings.Contains(action[:nl], "+build") { 445 // skip first line 446 action = action[nl+1:] 447 } 448 if strings.HasPrefix(action, "//") { 449 action = action[2:] 450 } 451 452 // Check for build constraints only up to the actual code. 453 pkgPos := strings.Index(t.src, "\npackage") 454 if pkgPos == -1 { 455 pkgPos = pos // some files are intentionally malformed 456 } 457 if ok, why := shouldTest(t.src[:pkgPos], goos, goarch); !ok { 458 t.action = "skip" 459 if *showSkips { 460 fmt.Printf("%-20s %-20s: %s\n", t.action, t.goFileName(), why) 461 } 462 return 463 } 464 465 var args, flags []string 466 wantError := false 467 f := strings.Fields(action) 468 if len(f) > 0 { 469 action = f[0] 470 args = f[1:] 471 } 472 473 switch action { 474 case "rundircmpout": 475 action = "rundir" 476 t.action = "rundir" 477 case "cmpout": 478 action = "run" // the run case already looks for <dir>/<test>.out files 479 fallthrough 480 case "compile", "compiledir", "build", "run", "runoutput", "rundir": 481 t.action = action 482 case "errorcheckandrundir": 483 wantError = false // should be no error if also will run 484 fallthrough 485 case "errorcheck", "errorcheckdir", "errorcheckoutput": 486 t.action = action 487 wantError = true 488 for len(args) > 0 && strings.HasPrefix(args[0], "-") { 489 if args[0] == "-0" { 490 wantError = false 491 } else { 492 flags = append(flags, args[0]) 493 } 494 args = args[1:] 495 } 496 case "skip": 497 if *runSkips { 498 break 499 } 500 t.action = "skip" 501 return 502 default: 503 t.err = skipError("skipped; unknown pattern: " + action) 504 t.action = "??" 505 return 506 } 507 508 t.makeTempDir() 509 if !*keep { 510 defer os.RemoveAll(t.tempDir) 511 } 512 513 err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644) 514 check(err) 515 516 // A few tests (of things like the environment) require these to be set. 517 if os.Getenv("GOOS") == "" { 518 os.Setenv("GOOS", runtime.GOOS) 519 } 520 if os.Getenv("GOARCH") == "" { 521 os.Setenv("GOARCH", runtime.GOARCH) 522 } 523 524 useTmp := true 525 ssaMain := false 526 runcmd := func(args ...string) ([]byte, error) { 527 cmd := exec.Command(args[0], args[1:]...) 528 var buf bytes.Buffer 529 cmd.Stdout = &buf 530 cmd.Stderr = &buf 531 if useTmp { 532 cmd.Dir = t.tempDir 533 cmd.Env = envForDir(cmd.Dir) 534 } else { 535 cmd.Env = os.Environ() 536 } 537 if ssaMain && os.Getenv("GOARCH") == "amd64" { 538 cmd.Env = append(cmd.Env, "GOSSAPKG=main") 539 } 540 err := cmd.Run() 541 if err != nil { 542 err = fmt.Errorf("%s\n%s", err, buf.Bytes()) 543 } 544 return buf.Bytes(), err 545 } 546 547 long := filepath.Join(cwd, t.goFileName()) 548 switch action { 549 default: 550 t.err = fmt.Errorf("unimplemented action %q", action) 551 552 case "errorcheck": 553 cmdline := []string{"go", "tool", "compile", "-e", "-o", "a.o"} 554 // No need to add -dynlink even if linkshared if we're just checking for errors... 555 cmdline = append(cmdline, flags...) 556 cmdline = append(cmdline, long) 557 out, err := runcmd(cmdline...) 558 if wantError { 559 if err == nil { 560 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 561 return 562 } 563 } else { 564 if err != nil { 565 t.err = err 566 return 567 } 568 } 569 if *updateErrors { 570 t.updateErrors(string(out), long) 571 } 572 t.err = t.errorCheck(string(out), long, t.gofile) 573 return 574 575 case "compile": 576 _, t.err = compileFile(runcmd, long) 577 578 case "compiledir": 579 // Compile all files in the directory in lexicographic order. 580 longdir := filepath.Join(cwd, t.goDirName()) 581 pkgs, err := goDirPackages(longdir) 582 if err != nil { 583 t.err = err 584 return 585 } 586 for _, gofiles := range pkgs { 587 _, t.err = compileInDir(runcmd, longdir, flags, gofiles...) 588 if t.err != nil { 589 return 590 } 591 } 592 593 case "errorcheckdir", "errorcheckandrundir": 594 // errorcheck all files in lexicographic order 595 // useful for finding importing errors 596 longdir := filepath.Join(cwd, t.goDirName()) 597 pkgs, err := goDirPackages(longdir) 598 if err != nil { 599 t.err = err 600 return 601 } 602 for i, gofiles := range pkgs { 603 out, err := compileInDir(runcmd, longdir, flags, gofiles...) 604 if i == len(pkgs)-1 { 605 if wantError && err == nil { 606 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 607 return 608 } else if !wantError && err != nil { 609 t.err = err 610 return 611 } 612 } else if err != nil { 613 t.err = err 614 return 615 } 616 var fullshort []string 617 for _, name := range gofiles { 618 fullshort = append(fullshort, filepath.Join(longdir, name), name) 619 } 620 t.err = t.errorCheck(string(out), fullshort...) 621 if t.err != nil { 622 break 623 } 624 } 625 if action == "errorcheckdir" { 626 return 627 } 628 fallthrough 629 630 case "rundir": 631 // Compile all files in the directory in lexicographic order. 632 // then link as if the last file is the main package and run it 633 longdir := filepath.Join(cwd, t.goDirName()) 634 pkgs, err := goDirPackages(longdir) 635 if err != nil { 636 t.err = err 637 return 638 } 639 for i, gofiles := range pkgs { 640 _, err := compileInDir(runcmd, longdir, flags, gofiles...) 641 if err != nil { 642 t.err = err 643 return 644 } 645 if i == len(pkgs)-1 { 646 err = linkFile(runcmd, gofiles[0]) 647 if err != nil { 648 t.err = err 649 return 650 } 651 var cmd []string 652 cmd = append(cmd, findExecCmd()...) 653 cmd = append(cmd, filepath.Join(t.tempDir, "a.exe")) 654 cmd = append(cmd, args...) 655 out, err := runcmd(cmd...) 656 if err != nil { 657 t.err = err 658 return 659 } 660 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 661 t.err = fmt.Errorf("incorrect output\n%s", out) 662 } 663 } 664 } 665 666 case "build": 667 _, err := runcmd("go", "build", "-o", "a.exe", long) 668 if err != nil { 669 t.err = err 670 } 671 672 case "run": 673 useTmp = false 674 ssaMain = true 675 cmd := []string{"go", "run"} 676 if *linkshared { 677 cmd = append(cmd, "-linkshared") 678 } 679 cmd = append(cmd, t.goFileName()) 680 out, err := runcmd(append(cmd, args...)...) 681 if err != nil { 682 t.err = err 683 return 684 } 685 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 686 t.err = fmt.Errorf("incorrect output\n%s", out) 687 } 688 689 case "runoutput": 690 rungatec <- true 691 defer func() { 692 <-rungatec 693 }() 694 useTmp = false 695 cmd := []string{"go", "run"} 696 if *linkshared { 697 cmd = append(cmd, "-linkshared") 698 } 699 cmd = append(cmd, t.goFileName()) 700 out, err := runcmd(append(cmd, args...)...) 701 if err != nil { 702 t.err = err 703 return 704 } 705 tfile := filepath.Join(t.tempDir, "tmp__.go") 706 if err := ioutil.WriteFile(tfile, out, 0666); err != nil { 707 t.err = fmt.Errorf("write tempfile:%s", err) 708 return 709 } 710 ssaMain = true 711 cmd = []string{"go", "run"} 712 if *linkshared { 713 cmd = append(cmd, "-linkshared") 714 } 715 cmd = append(cmd, tfile) 716 out, err = runcmd(cmd...) 717 if err != nil { 718 t.err = err 719 return 720 } 721 if string(out) != t.expectedOutput() { 722 t.err = fmt.Errorf("incorrect output\n%s", out) 723 } 724 725 case "errorcheckoutput": 726 useTmp = false 727 cmd := []string{"go", "run"} 728 if *linkshared { 729 cmd = append(cmd, "-linkshared") 730 } 731 cmd = append(cmd, t.goFileName()) 732 out, err := runcmd(append(cmd, args...)...) 733 if err != nil { 734 t.err = err 735 return 736 } 737 tfile := filepath.Join(t.tempDir, "tmp__.go") 738 err = ioutil.WriteFile(tfile, out, 0666) 739 if err != nil { 740 t.err = fmt.Errorf("write tempfile:%s", err) 741 return 742 } 743 cmdline := []string{"go", "tool", "compile", "-e", "-o", "a.o"} 744 cmdline = append(cmdline, flags...) 745 cmdline = append(cmdline, tfile) 746 out, err = runcmd(cmdline...) 747 if wantError { 748 if err == nil { 749 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 750 return 751 } 752 } else { 753 if err != nil { 754 t.err = err 755 return 756 } 757 } 758 t.err = t.errorCheck(string(out), tfile, "tmp__.go") 759 return 760 } 761 } 762 763 var execCmd []string 764 765 func findExecCmd() []string { 766 if execCmd != nil { 767 return execCmd 768 } 769 execCmd = []string{} // avoid work the second time 770 if goos == runtime.GOOS && goarch == runtime.GOARCH { 771 return execCmd 772 } 773 path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch)) 774 if err == nil { 775 execCmd = []string{path} 776 } 777 return execCmd 778 } 779 780 func (t *test) String() string { 781 return filepath.Join(t.dir, t.gofile) 782 } 783 784 func (t *test) makeTempDir() { 785 var err error 786 t.tempDir, err = ioutil.TempDir("", "") 787 check(err) 788 if *keep { 789 log.Printf("Temporary directory is %s", t.tempDir) 790 } 791 } 792 793 func (t *test) expectedOutput() string { 794 filename := filepath.Join(t.dir, t.gofile) 795 filename = filename[:len(filename)-len(".go")] 796 filename += ".out" 797 b, _ := ioutil.ReadFile(filename) 798 return string(b) 799 } 800 801 func splitOutput(out string) []string { 802 // gc error messages continue onto additional lines with leading tabs. 803 // Split the output at the beginning of each line that doesn't begin with a tab. 804 // <autogenerated> lines are impossible to match so those are filtered out. 805 var res []string 806 for _, line := range strings.Split(out, "\n") { 807 if strings.HasSuffix(line, "\r") { // remove '\r', output by compiler on windows 808 line = line[:len(line)-1] 809 } 810 if strings.HasPrefix(line, "\t") { 811 res[len(res)-1] += "\n" + line 812 } else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "<autogenerated>") || strings.HasPrefix(line, "#") { 813 continue 814 } else if strings.TrimSpace(line) != "" { 815 res = append(res, line) 816 } 817 } 818 return res 819 } 820 821 func (t *test) errorCheck(outStr string, fullshort ...string) (err error) { 822 defer func() { 823 if *verbose && err != nil { 824 log.Printf("%s gc output:\n%s", t, outStr) 825 } 826 }() 827 var errs []error 828 out := splitOutput(outStr) 829 830 // Cut directory name. 831 for i := range out { 832 for j := 0; j < len(fullshort); j += 2 { 833 full, short := fullshort[j], fullshort[j+1] 834 out[i] = strings.Replace(out[i], full, short, -1) 835 } 836 } 837 838 var want []wantedError 839 for j := 0; j < len(fullshort); j += 2 { 840 full, short := fullshort[j], fullshort[j+1] 841 want = append(want, t.wantedErrors(full, short)...) 842 } 843 844 for _, we := range want { 845 var errmsgs []string 846 errmsgs, out = partitionStrings(we.prefix, out) 847 if len(errmsgs) == 0 { 848 errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr)) 849 continue 850 } 851 matched := false 852 n := len(out) 853 for _, errmsg := range errmsgs { 854 if we.re.MatchString(errmsg) { 855 matched = true 856 } else { 857 out = append(out, errmsg) 858 } 859 } 860 if !matched { 861 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"))) 862 continue 863 } 864 } 865 866 if len(out) > 0 { 867 errs = append(errs, fmt.Errorf("Unmatched Errors:")) 868 for _, errLine := range out { 869 errs = append(errs, fmt.Errorf("%s", errLine)) 870 } 871 } 872 873 if len(errs) == 0 { 874 return nil 875 } 876 if len(errs) == 1 { 877 return errs[0] 878 } 879 var buf bytes.Buffer 880 fmt.Fprintf(&buf, "\n") 881 for _, err := range errs { 882 fmt.Fprintf(&buf, "%s\n", err.Error()) 883 } 884 return errors.New(buf.String()) 885 } 886 887 func (t *test) updateErrors(out, file string) { 888 base := path.Base(file) 889 // Read in source file. 890 src, err := ioutil.ReadFile(file) 891 if err != nil { 892 fmt.Fprintln(os.Stderr, err) 893 return 894 } 895 lines := strings.Split(string(src), "\n") 896 // Remove old errors. 897 for i, ln := range lines { 898 pos := strings.Index(ln, " // ERROR ") 899 if pos >= 0 { 900 lines[i] = ln[:pos] 901 } 902 } 903 // Parse new errors. 904 errors := make(map[int]map[string]bool) 905 tmpRe := regexp.MustCompile(`autotmp_[0-9]+`) 906 for _, errStr := range splitOutput(out) { 907 colon1 := strings.Index(errStr, ":") 908 if colon1 < 0 || errStr[:colon1] != file { 909 continue 910 } 911 colon2 := strings.Index(errStr[colon1+1:], ":") 912 if colon2 < 0 { 913 continue 914 } 915 colon2 += colon1 + 1 916 line, err := strconv.Atoi(errStr[colon1+1 : colon2]) 917 line-- 918 if err != nil || line < 0 || line >= len(lines) { 919 continue 920 } 921 msg := errStr[colon2+2:] 922 msg = strings.Replace(msg, file, base, -1) // normalize file mentions in error itself 923 msg = strings.TrimLeft(msg, " \t") 924 for _, r := range []string{`\`, `*`, `+`, `[`, `]`, `(`, `)`} { 925 msg = strings.Replace(msg, r, `\`+r, -1) 926 } 927 msg = strings.Replace(msg, `"`, `.`, -1) 928 msg = tmpRe.ReplaceAllLiteralString(msg, `autotmp_[0-9]+`) 929 if errors[line] == nil { 930 errors[line] = make(map[string]bool) 931 } 932 errors[line][msg] = true 933 } 934 // Add new errors. 935 for line, errs := range errors { 936 var sorted []string 937 for e := range errs { 938 sorted = append(sorted, e) 939 } 940 sort.Strings(sorted) 941 lines[line] += " // ERROR" 942 for _, e := range sorted { 943 lines[line] += fmt.Sprintf(` "%s$"`, e) 944 } 945 } 946 // Write new file. 947 err = ioutil.WriteFile(file, []byte(strings.Join(lines, "\n")), 0640) 948 if err != nil { 949 fmt.Fprintln(os.Stderr, err) 950 return 951 } 952 // Polish. 953 exec.Command("go", "fmt", file).CombinedOutput() 954 } 955 956 // matchPrefix reports whether s is of the form ^(.*/)?prefix(:|[), 957 // That is, it needs the file name prefix followed by a : or a [, 958 // and possibly preceded by a directory name. 959 func matchPrefix(s, prefix string) bool { 960 i := strings.Index(s, ":") 961 if i < 0 { 962 return false 963 } 964 j := strings.LastIndex(s[:i], "/") 965 s = s[j+1:] 966 if len(s) <= len(prefix) || s[:len(prefix)] != prefix { 967 return false 968 } 969 switch s[len(prefix)] { 970 case '[', ':': 971 return true 972 } 973 return false 974 } 975 976 func partitionStrings(prefix string, strs []string) (matched, unmatched []string) { 977 for _, s := range strs { 978 if matchPrefix(s, prefix) { 979 matched = append(matched, s) 980 } else { 981 unmatched = append(unmatched, s) 982 } 983 } 984 return 985 } 986 987 type wantedError struct { 988 reStr string 989 re *regexp.Regexp 990 lineNum int 991 file string 992 prefix string 993 } 994 995 var ( 996 errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`) 997 errQuotesRx = regexp.MustCompile(`"([^"]*)"`) 998 lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`) 999 ) 1000 1001 func (t *test) wantedErrors(file, short string) (errs []wantedError) { 1002 cache := make(map[string]*regexp.Regexp) 1003 1004 src, _ := ioutil.ReadFile(file) 1005 for i, line := range strings.Split(string(src), "\n") { 1006 lineNum := i + 1 1007 if strings.Contains(line, "////") { 1008 // double comment disables ERROR 1009 continue 1010 } 1011 m := errRx.FindStringSubmatch(line) 1012 if m == nil { 1013 continue 1014 } 1015 all := m[1] 1016 mm := errQuotesRx.FindAllStringSubmatch(all, -1) 1017 if mm == nil { 1018 log.Fatalf("%s:%d: invalid errchk line: %s", t.goFileName(), lineNum, line) 1019 } 1020 for _, m := range mm { 1021 rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string { 1022 n := lineNum 1023 if strings.HasPrefix(m, "LINE+") { 1024 delta, _ := strconv.Atoi(m[5:]) 1025 n += delta 1026 } else if strings.HasPrefix(m, "LINE-") { 1027 delta, _ := strconv.Atoi(m[5:]) 1028 n -= delta 1029 } 1030 return fmt.Sprintf("%s:%d", short, n) 1031 }) 1032 re := cache[rx] 1033 if re == nil { 1034 var err error 1035 re, err = regexp.Compile(rx) 1036 if err != nil { 1037 log.Fatalf("%s:%d: invalid regexp \"%s\" in ERROR line: %v", t.goFileName(), lineNum, rx, err) 1038 } 1039 cache[rx] = re 1040 } 1041 prefix := fmt.Sprintf("%s:%d", short, lineNum) 1042 errs = append(errs, wantedError{ 1043 reStr: rx, 1044 re: re, 1045 prefix: prefix, 1046 lineNum: lineNum, 1047 file: short, 1048 }) 1049 } 1050 } 1051 1052 return 1053 } 1054 1055 // defaultRunOutputLimit returns the number of runoutput tests that 1056 // can be executed in parallel. 1057 func defaultRunOutputLimit() int { 1058 const maxArmCPU = 2 1059 1060 cpu := runtime.NumCPU() 1061 if runtime.GOARCH == "arm" && cpu > maxArmCPU { 1062 cpu = maxArmCPU 1063 } 1064 return cpu 1065 } 1066 1067 // checkShouldTest runs sanity checks on the shouldTest function. 1068 func checkShouldTest() { 1069 assert := func(ok bool, _ string) { 1070 if !ok { 1071 panic("fail") 1072 } 1073 } 1074 assertNot := func(ok bool, _ string) { assert(!ok, "") } 1075 1076 // Simple tests. 1077 assert(shouldTest("// +build linux", "linux", "arm")) 1078 assert(shouldTest("// +build !windows", "linux", "arm")) 1079 assertNot(shouldTest("// +build !windows", "windows", "amd64")) 1080 1081 // A file with no build tags will always be tested. 1082 assert(shouldTest("// This is a test.", "os", "arch")) 1083 1084 // Build tags separated by a space are OR-ed together. 1085 assertNot(shouldTest("// +build arm 386", "linux", "amd64")) 1086 1087 // Build tags separated by a comma are AND-ed together. 1088 assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64")) 1089 assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386")) 1090 1091 // Build tags on multiple lines are AND-ed together. 1092 assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64")) 1093 assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64")) 1094 1095 // Test that (!a OR !b) matches anything. 1096 assert(shouldTest("// +build !windows !plan9", "windows", "amd64")) 1097 } 1098 1099 // envForDir returns a copy of the environment 1100 // suitable for running in the given directory. 1101 // The environment is the current process's environment 1102 // but with an updated $PWD, so that an os.Getwd in the 1103 // child will be faster. 1104 func envForDir(dir string) []string { 1105 env := os.Environ() 1106 for i, kv := range env { 1107 if strings.HasPrefix(kv, "PWD=") { 1108 env[i] = "PWD=" + dir 1109 return env 1110 } 1111 } 1112 env = append(env, "PWD="+dir) 1113 return env 1114 } 1115 1116 func getenv(key, def string) string { 1117 value := os.Getenv(key) 1118 if value != "" { 1119 return value 1120 } 1121 return def 1122 }