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