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