github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/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"} 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 247 tempDir string 248 err error 249 } 250 251 // startTest 252 func startTest(dir, gofile string) *test { 253 t := &test{ 254 dir: dir, 255 gofile: gofile, 256 donec: make(chan bool, 1), 257 } 258 if toRun == nil { 259 toRun = make(chan *test, maxTests) 260 go runTests() 261 } 262 select { 263 case toRun <- t: 264 default: 265 panic("toRun buffer size (maxTests) is too small") 266 } 267 return t 268 } 269 270 // runTests runs tests in parallel, but respecting the order they 271 // were enqueued on the toRun channel. 272 func runTests() { 273 for { 274 ratec <- true 275 t := <-toRun 276 go func() { 277 t.run() 278 <-ratec 279 }() 280 } 281 } 282 283 var cwd, _ = os.Getwd() 284 285 func (t *test) goFileName() string { 286 return filepath.Join(t.dir, t.gofile) 287 } 288 289 func (t *test) goDirName() string { 290 return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1)) 291 } 292 293 func goDirFiles(longdir string) (filter []os.FileInfo, err error) { 294 files, dirErr := ioutil.ReadDir(longdir) 295 if dirErr != nil { 296 return nil, dirErr 297 } 298 for _, gofile := range files { 299 if filepath.Ext(gofile.Name()) == ".go" { 300 filter = append(filter, gofile) 301 } 302 } 303 return 304 } 305 306 var packageRE = regexp.MustCompile(`(?m)^package (\w+)`) 307 308 // If singlefilepkgs is set, each file is considered a separate package 309 // even if the package names are the same. 310 func goDirPackages(longdir string, singlefilepkgs bool) ([][]string, error) { 311 files, err := goDirFiles(longdir) 312 if err != nil { 313 return nil, err 314 } 315 var pkgs [][]string 316 m := make(map[string]int) 317 for _, file := range files { 318 name := file.Name() 319 data, err := ioutil.ReadFile(filepath.Join(longdir, name)) 320 if err != nil { 321 return nil, err 322 } 323 pkgname := packageRE.FindStringSubmatch(string(data)) 324 if pkgname == nil { 325 return nil, fmt.Errorf("cannot find package name in %s", name) 326 } 327 i, ok := m[pkgname[1]] 328 if singlefilepkgs || !ok { 329 i = len(pkgs) 330 pkgs = append(pkgs, nil) 331 m[pkgname[1]] = i 332 } 333 pkgs[i] = append(pkgs[i], name) 334 } 335 return pkgs, nil 336 } 337 338 type context struct { 339 GOOS string 340 GOARCH string 341 } 342 343 // shouldTest looks for build tags in a source file and returns 344 // whether the file should be used according to the tags. 345 func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) { 346 if *runSkips { 347 return true, "" 348 } 349 for _, line := range strings.Split(src, "\n") { 350 line = strings.TrimSpace(line) 351 if strings.HasPrefix(line, "//") { 352 line = line[2:] 353 } else { 354 continue 355 } 356 line = strings.TrimSpace(line) 357 if len(line) == 0 || line[0] != '+' { 358 continue 359 } 360 ctxt := &context{ 361 GOOS: goos, 362 GOARCH: goarch, 363 } 364 words := strings.Fields(line) 365 if words[0] == "+build" { 366 ok := false 367 for _, word := range words[1:] { 368 if ctxt.match(word) { 369 ok = true 370 break 371 } 372 } 373 if !ok { 374 // no matching tag found. 375 return false, line 376 } 377 } 378 } 379 // no build tags 380 return true, "" 381 } 382 383 func (ctxt *context) match(name string) bool { 384 if name == "" { 385 return false 386 } 387 if i := strings.Index(name, ","); i >= 0 { 388 // comma-separated list 389 return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) 390 } 391 if strings.HasPrefix(name, "!!") { // bad syntax, reject always 392 return false 393 } 394 if strings.HasPrefix(name, "!") { // negation 395 return len(name) > 1 && !ctxt.match(name[1:]) 396 } 397 398 // Tags must be letters, digits, underscores or dots. 399 // Unlike in Go identifiers, all digits are fine (e.g., "386"). 400 for _, c := range name { 401 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { 402 return false 403 } 404 } 405 406 if name == ctxt.GOOS || name == ctxt.GOARCH { 407 return true 408 } 409 410 if name == "test_run" { 411 return true 412 } 413 414 return false 415 } 416 417 func init() { checkShouldTest() } 418 419 // run runs a test. 420 func (t *test) run() { 421 start := time.Now() 422 defer func() { 423 t.dt = time.Since(start) 424 close(t.donec) 425 }() 426 427 srcBytes, err := ioutil.ReadFile(t.goFileName()) 428 if err != nil { 429 t.err = err 430 return 431 } 432 t.src = string(srcBytes) 433 if t.src[0] == '\n' { 434 t.err = skipError("starts with newline") 435 return 436 } 437 438 // Execution recipe stops at first blank line. 439 pos := strings.Index(t.src, "\n\n") 440 if pos == -1 { 441 t.err = errors.New("double newline not found") 442 return 443 } 444 action := t.src[:pos] 445 if nl := strings.Index(action, "\n"); nl >= 0 && strings.Contains(action[:nl], "+build") { 446 // skip first line 447 action = action[nl+1:] 448 } 449 if strings.HasPrefix(action, "//") { 450 action = action[2:] 451 } 452 453 // Check for build constraints only up to the actual code. 454 pkgPos := strings.Index(t.src, "\npackage") 455 if pkgPos == -1 { 456 pkgPos = pos // some files are intentionally malformed 457 } 458 if ok, why := shouldTest(t.src[:pkgPos], goos, goarch); !ok { 459 if *showSkips { 460 fmt.Printf("%-20s %-20s: %s\n", "skip", t.goFileName(), why) 461 } 462 return 463 } 464 465 var args, flags []string 466 var tim int 467 wantError := false 468 wantAuto := false 469 singlefilepkgs := false 470 f := strings.Fields(action) 471 if len(f) > 0 { 472 action = f[0] 473 args = f[1:] 474 } 475 476 // TODO: Clean up/simplify this switch statement. 477 switch action { 478 case "rundircmpout": 479 action = "rundir" 480 case "cmpout": 481 action = "run" // the run case already looks for <dir>/<test>.out files 482 case "compile", "compiledir", "build", "run", "buildrun", "runoutput", "rundir": 483 // nothing to do 484 case "errorcheckandrundir": 485 wantError = false // should be no error if also will run 486 case "errorcheckwithauto": 487 action = "errorcheck" 488 wantAuto = true 489 wantError = true 490 case "errorcheck", "errorcheckdir", "errorcheckoutput": 491 wantError = true 492 case "skip": 493 if *runSkips { 494 break 495 } 496 return 497 default: 498 t.err = skipError("skipped; unknown pattern: " + action) 499 return 500 } 501 502 // collect flags 503 for len(args) > 0 && strings.HasPrefix(args[0], "-") { 504 switch args[0] { 505 case "-0": 506 wantError = false 507 case "-s": 508 singlefilepkgs = true 509 case "-t": // timeout in seconds 510 args = args[1:] 511 var err error 512 tim, err = strconv.Atoi(args[0]) 513 if err != nil { 514 t.err = fmt.Errorf("need number of seconds for -t timeout, got %s instead", args[0]) 515 } 516 517 default: 518 flags = append(flags, args[0]) 519 } 520 args = args[1:] 521 } 522 523 t.makeTempDir() 524 if !*keep { 525 defer os.RemoveAll(t.tempDir) 526 } 527 528 err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644) 529 check(err) 530 531 // A few tests (of things like the environment) require these to be set. 532 if os.Getenv("GOOS") == "" { 533 os.Setenv("GOOS", runtime.GOOS) 534 } 535 if os.Getenv("GOARCH") == "" { 536 os.Setenv("GOARCH", runtime.GOARCH) 537 } 538 539 useTmp := true 540 runcmd := func(args ...string) ([]byte, error) { 541 cmd := exec.Command(args[0], args[1:]...) 542 var buf bytes.Buffer 543 cmd.Stdout = &buf 544 cmd.Stderr = &buf 545 if useTmp { 546 cmd.Dir = t.tempDir 547 cmd.Env = envForDir(cmd.Dir) 548 } else { 549 cmd.Env = os.Environ() 550 } 551 552 var err error 553 554 if tim != 0 { 555 err = cmd.Start() 556 // This command-timeout code adapted from cmd/go/test.go 557 if err == nil { 558 tick := time.NewTimer(time.Duration(tim) * time.Second) 559 done := make(chan error) 560 go func() { 561 done <- cmd.Wait() 562 }() 563 select { 564 case err = <-done: 565 // ok 566 case <-tick.C: 567 cmd.Process.Kill() 568 err = <-done 569 // err = errors.New("Test timeout") 570 } 571 tick.Stop() 572 } 573 } else { 574 err = cmd.Run() 575 } 576 if err != nil { 577 err = fmt.Errorf("%s\n%s", err, buf.Bytes()) 578 } 579 return buf.Bytes(), err 580 } 581 582 long := filepath.Join(cwd, t.goFileName()) 583 switch action { 584 default: 585 t.err = fmt.Errorf("unimplemented action %q", action) 586 587 case "errorcheck": 588 // TODO(gri) remove need for -C (disable printing of columns in error messages) 589 cmdline := []string{"go", "tool", "compile", "-C", "-e", "-o", "a.o"} 590 // No need to add -dynlink even if linkshared if we're just checking for errors... 591 cmdline = append(cmdline, flags...) 592 cmdline = append(cmdline, long) 593 out, err := runcmd(cmdline...) 594 if wantError { 595 if err == nil { 596 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 597 return 598 } 599 } else { 600 if err != nil { 601 t.err = err 602 return 603 } 604 } 605 if *updateErrors { 606 t.updateErrors(string(out), long) 607 } 608 t.err = t.errorCheck(string(out), wantAuto, long, t.gofile) 609 return 610 611 case "compile": 612 _, t.err = compileFile(runcmd, long) 613 614 case "compiledir": 615 // Compile all files in the directory in lexicographic order. 616 longdir := filepath.Join(cwd, t.goDirName()) 617 pkgs, err := goDirPackages(longdir, singlefilepkgs) 618 if err != nil { 619 t.err = err 620 return 621 } 622 for _, gofiles := range pkgs { 623 _, t.err = compileInDir(runcmd, longdir, flags, gofiles...) 624 if t.err != nil { 625 return 626 } 627 } 628 629 case "errorcheckdir", "errorcheckandrundir": 630 // errorcheck all files in lexicographic order 631 // useful for finding importing errors 632 longdir := filepath.Join(cwd, t.goDirName()) 633 pkgs, err := goDirPackages(longdir, singlefilepkgs) 634 if err != nil { 635 t.err = err 636 return 637 } 638 for i, gofiles := range pkgs { 639 out, err := compileInDir(runcmd, longdir, flags, gofiles...) 640 if i == len(pkgs)-1 { 641 if wantError && err == nil { 642 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 643 return 644 } else if !wantError && err != nil { 645 t.err = err 646 return 647 } 648 } else if err != nil { 649 t.err = err 650 return 651 } 652 var fullshort []string 653 for _, name := range gofiles { 654 fullshort = append(fullshort, filepath.Join(longdir, name), name) 655 } 656 t.err = t.errorCheck(string(out), wantAuto, fullshort...) 657 if t.err != nil { 658 break 659 } 660 } 661 if action == "errorcheckdir" { 662 return 663 } 664 fallthrough 665 666 case "rundir": 667 // Compile all files in the directory in lexicographic order. 668 // then link as if the last file is the main package and run it 669 longdir := filepath.Join(cwd, t.goDirName()) 670 pkgs, err := goDirPackages(longdir, singlefilepkgs) 671 if err != nil { 672 t.err = err 673 return 674 } 675 for i, gofiles := range pkgs { 676 _, err := compileInDir(runcmd, longdir, flags, gofiles...) 677 if err != nil { 678 t.err = err 679 return 680 } 681 if i == len(pkgs)-1 { 682 err = linkFile(runcmd, gofiles[0]) 683 if err != nil { 684 t.err = err 685 return 686 } 687 var cmd []string 688 cmd = append(cmd, findExecCmd()...) 689 cmd = append(cmd, filepath.Join(t.tempDir, "a.exe")) 690 cmd = append(cmd, args...) 691 out, err := runcmd(cmd...) 692 if err != nil { 693 t.err = err 694 return 695 } 696 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 697 t.err = fmt.Errorf("incorrect output\n%s", out) 698 } 699 } 700 } 701 702 case "build": 703 _, err := runcmd("go", "build", "-o", "a.exe", long) 704 if err != nil { 705 t.err = err 706 } 707 708 case "buildrun": // build binary, then run binary, instead of go run. Useful for timeout tests where failure mode is infinite loop. 709 // TODO: not supported on NaCl 710 useTmp = true 711 cmd := []string{"go", "build", "-o", "a.exe"} 712 if *linkshared { 713 cmd = append(cmd, "-linkshared") 714 } 715 longdirgofile := filepath.Join(filepath.Join(cwd, t.dir), t.gofile) 716 cmd = append(cmd, flags...) 717 cmd = append(cmd, longdirgofile) 718 out, err := runcmd(cmd...) 719 if err != nil { 720 t.err = err 721 return 722 } 723 cmd = []string{"./a.exe"} 724 out, err = runcmd(append(cmd, args...)...) 725 if err != nil { 726 t.err = err 727 return 728 } 729 730 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 731 t.err = fmt.Errorf("incorrect output\n%s", out) 732 } 733 734 case "run": 735 useTmp = false 736 cmd := []string{"go", "run"} 737 if *linkshared { 738 cmd = append(cmd, "-linkshared") 739 } 740 cmd = append(cmd, flags...) 741 cmd = append(cmd, t.goFileName()) 742 out, err := runcmd(append(cmd, args...)...) 743 if err != nil { 744 t.err = err 745 return 746 } 747 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 748 t.err = fmt.Errorf("incorrect output\n%s", out) 749 } 750 751 case "runoutput": 752 rungatec <- true 753 defer func() { 754 <-rungatec 755 }() 756 useTmp = false 757 cmd := []string{"go", "run"} 758 if *linkshared { 759 cmd = append(cmd, "-linkshared") 760 } 761 cmd = append(cmd, t.goFileName()) 762 out, err := runcmd(append(cmd, args...)...) 763 if err != nil { 764 t.err = err 765 return 766 } 767 tfile := filepath.Join(t.tempDir, "tmp__.go") 768 if err := ioutil.WriteFile(tfile, out, 0666); err != nil { 769 t.err = fmt.Errorf("write tempfile:%s", err) 770 return 771 } 772 cmd = []string{"go", "run"} 773 if *linkshared { 774 cmd = append(cmd, "-linkshared") 775 } 776 cmd = append(cmd, tfile) 777 out, err = runcmd(cmd...) 778 if err != nil { 779 t.err = err 780 return 781 } 782 if string(out) != t.expectedOutput() { 783 t.err = fmt.Errorf("incorrect output\n%s", out) 784 } 785 786 case "errorcheckoutput": 787 useTmp = false 788 cmd := []string{"go", "run"} 789 if *linkshared { 790 cmd = append(cmd, "-linkshared") 791 } 792 cmd = append(cmd, t.goFileName()) 793 out, err := runcmd(append(cmd, args...)...) 794 if err != nil { 795 t.err = err 796 return 797 } 798 tfile := filepath.Join(t.tempDir, "tmp__.go") 799 err = ioutil.WriteFile(tfile, out, 0666) 800 if err != nil { 801 t.err = fmt.Errorf("write tempfile:%s", err) 802 return 803 } 804 cmdline := []string{"go", "tool", "compile", "-e", "-o", "a.o"} 805 cmdline = append(cmdline, flags...) 806 cmdline = append(cmdline, tfile) 807 out, err = runcmd(cmdline...) 808 if wantError { 809 if err == nil { 810 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 811 return 812 } 813 } else { 814 if err != nil { 815 t.err = err 816 return 817 } 818 } 819 t.err = t.errorCheck(string(out), false, tfile, "tmp__.go") 820 return 821 } 822 } 823 824 var execCmd []string 825 826 func findExecCmd() []string { 827 if execCmd != nil { 828 return execCmd 829 } 830 execCmd = []string{} // avoid work the second time 831 if goos == runtime.GOOS && goarch == runtime.GOARCH { 832 return execCmd 833 } 834 path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch)) 835 if err == nil { 836 execCmd = []string{path} 837 } 838 return execCmd 839 } 840 841 func (t *test) String() string { 842 return filepath.Join(t.dir, t.gofile) 843 } 844 845 func (t *test) makeTempDir() { 846 var err error 847 t.tempDir, err = ioutil.TempDir("", "") 848 check(err) 849 if *keep { 850 log.Printf("Temporary directory is %s", t.tempDir) 851 } 852 } 853 854 func (t *test) expectedOutput() string { 855 filename := filepath.Join(t.dir, t.gofile) 856 filename = filename[:len(filename)-len(".go")] 857 filename += ".out" 858 b, _ := ioutil.ReadFile(filename) 859 return string(b) 860 } 861 862 func splitOutput(out string, wantAuto bool) []string { 863 // gc error messages continue onto additional lines with leading tabs. 864 // Split the output at the beginning of each line that doesn't begin with a tab. 865 // <autogenerated> lines are impossible to match so those are filtered out. 866 var res []string 867 for _, line := range strings.Split(out, "\n") { 868 if strings.HasSuffix(line, "\r") { // remove '\r', output by compiler on windows 869 line = line[:len(line)-1] 870 } 871 if strings.HasPrefix(line, "\t") { 872 res[len(res)-1] += "\n" + line 873 } else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") { 874 continue 875 } else if strings.TrimSpace(line) != "" { 876 res = append(res, line) 877 } 878 } 879 return res 880 } 881 882 func (t *test) errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) { 883 defer func() { 884 if *verbose && err != nil { 885 log.Printf("%s gc output:\n%s", t, outStr) 886 } 887 }() 888 var errs []error 889 out := splitOutput(outStr, wantAuto) 890 891 // Cut directory name. 892 for i := range out { 893 for j := 0; j < len(fullshort); j += 2 { 894 full, short := fullshort[j], fullshort[j+1] 895 out[i] = strings.Replace(out[i], full, short, -1) 896 } 897 } 898 899 var want []wantedError 900 for j := 0; j < len(fullshort); j += 2 { 901 full, short := fullshort[j], fullshort[j+1] 902 want = append(want, t.wantedErrors(full, short)...) 903 } 904 905 for _, we := range want { 906 var errmsgs []string 907 if we.auto { 908 errmsgs, out = partitionStrings("<autogenerated>", out) 909 } else { 910 errmsgs, out = partitionStrings(we.prefix, out) 911 } 912 if len(errmsgs) == 0 { 913 errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr)) 914 continue 915 } 916 matched := false 917 n := len(out) 918 for _, errmsg := range errmsgs { 919 // Assume errmsg says "file:line: foo". 920 // Cut leading "file:line: " to avoid accidental matching of file name instead of message. 921 text := errmsg 922 if i := strings.Index(text, " "); i >= 0 { 923 text = text[i+1:] 924 } 925 if we.re.MatchString(text) { 926 matched = true 927 } else { 928 out = append(out, errmsg) 929 } 930 } 931 if !matched { 932 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"))) 933 continue 934 } 935 } 936 937 if len(out) > 0 { 938 errs = append(errs, fmt.Errorf("Unmatched Errors:")) 939 for _, errLine := range out { 940 errs = append(errs, fmt.Errorf("%s", errLine)) 941 } 942 } 943 944 if len(errs) == 0 { 945 return nil 946 } 947 if len(errs) == 1 { 948 return errs[0] 949 } 950 var buf bytes.Buffer 951 fmt.Fprintf(&buf, "\n") 952 for _, err := range errs { 953 fmt.Fprintf(&buf, "%s\n", err.Error()) 954 } 955 return errors.New(buf.String()) 956 } 957 958 func (t *test) updateErrors(out, file string) { 959 base := path.Base(file) 960 // Read in source file. 961 src, err := ioutil.ReadFile(file) 962 if err != nil { 963 fmt.Fprintln(os.Stderr, err) 964 return 965 } 966 lines := strings.Split(string(src), "\n") 967 // Remove old errors. 968 for i, ln := range lines { 969 pos := strings.Index(ln, " // ERROR ") 970 if pos >= 0 { 971 lines[i] = ln[:pos] 972 } 973 } 974 // Parse new errors. 975 errors := make(map[int]map[string]bool) 976 tmpRe := regexp.MustCompile(`autotmp_[0-9]+`) 977 for _, errStr := range splitOutput(out, false) { 978 colon1 := strings.Index(errStr, ":") 979 if colon1 < 0 || errStr[:colon1] != file { 980 continue 981 } 982 colon2 := strings.Index(errStr[colon1+1:], ":") 983 if colon2 < 0 { 984 continue 985 } 986 colon2 += colon1 + 1 987 line, err := strconv.Atoi(errStr[colon1+1 : colon2]) 988 line-- 989 if err != nil || line < 0 || line >= len(lines) { 990 continue 991 } 992 msg := errStr[colon2+2:] 993 msg = strings.Replace(msg, file, base, -1) // normalize file mentions in error itself 994 msg = strings.TrimLeft(msg, " \t") 995 for _, r := range []string{`\`, `*`, `+`, `[`, `]`, `(`, `)`} { 996 msg = strings.Replace(msg, r, `\`+r, -1) 997 } 998 msg = strings.Replace(msg, `"`, `.`, -1) 999 msg = tmpRe.ReplaceAllLiteralString(msg, `autotmp_[0-9]+`) 1000 if errors[line] == nil { 1001 errors[line] = make(map[string]bool) 1002 } 1003 errors[line][msg] = true 1004 } 1005 // Add new errors. 1006 for line, errs := range errors { 1007 var sorted []string 1008 for e := range errs { 1009 sorted = append(sorted, e) 1010 } 1011 sort.Strings(sorted) 1012 lines[line] += " // ERROR" 1013 for _, e := range sorted { 1014 lines[line] += fmt.Sprintf(` "%s$"`, e) 1015 } 1016 } 1017 // Write new file. 1018 err = ioutil.WriteFile(file, []byte(strings.Join(lines, "\n")), 0640) 1019 if err != nil { 1020 fmt.Fprintln(os.Stderr, err) 1021 return 1022 } 1023 // Polish. 1024 exec.Command("go", "fmt", file).CombinedOutput() 1025 } 1026 1027 // matchPrefix reports whether s is of the form ^(.*/)?prefix(:|[), 1028 // That is, it needs the file name prefix followed by a : or a [, 1029 // and possibly preceded by a directory name. 1030 func matchPrefix(s, prefix string) bool { 1031 i := strings.Index(s, ":") 1032 if i < 0 { 1033 return false 1034 } 1035 j := strings.LastIndex(s[:i], "/") 1036 s = s[j+1:] 1037 if len(s) <= len(prefix) || s[:len(prefix)] != prefix { 1038 return false 1039 } 1040 switch s[len(prefix)] { 1041 case '[', ':': 1042 return true 1043 } 1044 return false 1045 } 1046 1047 func partitionStrings(prefix string, strs []string) (matched, unmatched []string) { 1048 for _, s := range strs { 1049 if matchPrefix(s, prefix) { 1050 matched = append(matched, s) 1051 } else { 1052 unmatched = append(unmatched, s) 1053 } 1054 } 1055 return 1056 } 1057 1058 type wantedError struct { 1059 reStr string 1060 re *regexp.Regexp 1061 lineNum int 1062 auto bool // match <autogenerated> line 1063 file string 1064 prefix string 1065 } 1066 1067 var ( 1068 errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`) 1069 errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`) 1070 errQuotesRx = regexp.MustCompile(`"([^"]*)"`) 1071 lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`) 1072 ) 1073 1074 func (t *test) wantedErrors(file, short string) (errs []wantedError) { 1075 cache := make(map[string]*regexp.Regexp) 1076 1077 src, _ := ioutil.ReadFile(file) 1078 for i, line := range strings.Split(string(src), "\n") { 1079 lineNum := i + 1 1080 if strings.Contains(line, "////") { 1081 // double comment disables ERROR 1082 continue 1083 } 1084 var auto bool 1085 m := errAutoRx.FindStringSubmatch(line) 1086 if m != nil { 1087 auto = true 1088 } else { 1089 m = errRx.FindStringSubmatch(line) 1090 } 1091 if m == nil { 1092 continue 1093 } 1094 all := m[1] 1095 mm := errQuotesRx.FindAllStringSubmatch(all, -1) 1096 if mm == nil { 1097 log.Fatalf("%s:%d: invalid errchk line: %s", t.goFileName(), lineNum, line) 1098 } 1099 for _, m := range mm { 1100 rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string { 1101 n := lineNum 1102 if strings.HasPrefix(m, "LINE+") { 1103 delta, _ := strconv.Atoi(m[5:]) 1104 n += delta 1105 } else if strings.HasPrefix(m, "LINE-") { 1106 delta, _ := strconv.Atoi(m[5:]) 1107 n -= delta 1108 } 1109 return fmt.Sprintf("%s:%d", short, n) 1110 }) 1111 re := cache[rx] 1112 if re == nil { 1113 var err error 1114 re, err = regexp.Compile(rx) 1115 if err != nil { 1116 log.Fatalf("%s:%d: invalid regexp \"%s\" in ERROR line: %v", t.goFileName(), lineNum, rx, err) 1117 } 1118 cache[rx] = re 1119 } 1120 prefix := fmt.Sprintf("%s:%d", short, lineNum) 1121 errs = append(errs, wantedError{ 1122 reStr: rx, 1123 re: re, 1124 prefix: prefix, 1125 auto: auto, 1126 lineNum: lineNum, 1127 file: short, 1128 }) 1129 } 1130 } 1131 1132 return 1133 } 1134 1135 // defaultRunOutputLimit returns the number of runoutput tests that 1136 // can be executed in parallel. 1137 func defaultRunOutputLimit() int { 1138 const maxArmCPU = 2 1139 1140 cpu := runtime.NumCPU() 1141 if runtime.GOARCH == "arm" && cpu > maxArmCPU { 1142 cpu = maxArmCPU 1143 } 1144 return cpu 1145 } 1146 1147 // checkShouldTest runs sanity checks on the shouldTest function. 1148 func checkShouldTest() { 1149 assert := func(ok bool, _ string) { 1150 if !ok { 1151 panic("fail") 1152 } 1153 } 1154 assertNot := func(ok bool, _ string) { assert(!ok, "") } 1155 1156 // Simple tests. 1157 assert(shouldTest("// +build linux", "linux", "arm")) 1158 assert(shouldTest("// +build !windows", "linux", "arm")) 1159 assertNot(shouldTest("// +build !windows", "windows", "amd64")) 1160 1161 // A file with no build tags will always be tested. 1162 assert(shouldTest("// This is a test.", "os", "arch")) 1163 1164 // Build tags separated by a space are OR-ed together. 1165 assertNot(shouldTest("// +build arm 386", "linux", "amd64")) 1166 1167 // Build tags separated by a comma are AND-ed together. 1168 assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64")) 1169 assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386")) 1170 1171 // Build tags on multiple lines are AND-ed together. 1172 assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64")) 1173 assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64")) 1174 1175 // Test that (!a OR !b) matches anything. 1176 assert(shouldTest("// +build !windows !plan9", "windows", "amd64")) 1177 } 1178 1179 // envForDir returns a copy of the environment 1180 // suitable for running in the given directory. 1181 // The environment is the current process's environment 1182 // but with an updated $PWD, so that an os.Getwd in the 1183 // child will be faster. 1184 func envForDir(dir string) []string { 1185 env := os.Environ() 1186 for i, kv := range env { 1187 if strings.HasPrefix(kv, "PWD=") { 1188 env[i] = "PWD=" + dir 1189 return env 1190 } 1191 } 1192 env = append(env, "PWD="+dir) 1193 return env 1194 } 1195 1196 func getenv(key, def string) string { 1197 value := os.Getenv(key) 1198 if value != "" { 1199 return value 1200 } 1201 return def 1202 }