github.com/tcnksm/go@v0.0.0-20141208075154-439b32936367/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 runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run") 40 ) 41 42 var ( 43 // gc and ld are [568][gl]. 44 gc, ld string 45 46 // letter is the build.ArchChar 47 letter string 48 49 goos, goarch string 50 51 // dirs are the directories to look for *.go files in. 52 // TODO(bradfitz): just use all directories? 53 dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "bugs"} 54 55 // ratec controls the max number of tests running at a time. 56 ratec chan bool 57 58 // toRun is the channel of tests to run. 59 // It is nil until the first test is started. 60 toRun chan *test 61 62 // rungatec controls the max number of runoutput tests 63 // executed in parallel as they can each consume a lot of memory. 64 rungatec chan bool 65 ) 66 67 // maxTests is an upper bound on the total number of tests. 68 // It is used as a channel buffer size to make sure sends don't block. 69 const maxTests = 5000 70 71 func main() { 72 flag.Parse() 73 74 goos = getenv("GOOS", runtime.GOOS) 75 goarch = getenv("GOARCH", runtime.GOARCH) 76 77 findExecCmd() 78 79 // Disable parallelism if printing or if using a simulator. 80 if *verbose || len(findExecCmd()) > 0 { 81 *numParallel = 1 82 } 83 84 ratec = make(chan bool, *numParallel) 85 rungatec = make(chan bool, *runoutputLimit) 86 var err error 87 letter, err = build.ArchChar(build.Default.GOARCH) 88 check(err) 89 gc = letter + "g" 90 ld = letter + "l" 91 92 var tests []*test 93 if flag.NArg() > 0 { 94 for _, arg := range flag.Args() { 95 if arg == "-" || arg == "--" { 96 // Permit running: 97 // $ go run run.go - env.go 98 // $ go run run.go -- env.go 99 // $ go run run.go - ./fixedbugs 100 // $ go run run.go -- ./fixedbugs 101 continue 102 } 103 if fi, err := os.Stat(arg); err == nil && fi.IsDir() { 104 for _, baseGoFile := range goFiles(arg) { 105 tests = append(tests, startTest(arg, baseGoFile)) 106 } 107 } else if strings.HasSuffix(arg, ".go") { 108 dir, file := filepath.Split(arg) 109 tests = append(tests, startTest(dir, file)) 110 } else { 111 log.Fatalf("can't yet deal with non-directory and non-go file %q", arg) 112 } 113 } 114 } else { 115 for _, dir := range dirs { 116 for _, baseGoFile := range goFiles(dir) { 117 tests = append(tests, startTest(dir, baseGoFile)) 118 } 119 } 120 } 121 122 failed := false 123 resCount := map[string]int{} 124 for _, test := range tests { 125 <-test.donec 126 status := "ok " 127 errStr := "" 128 if _, isSkip := test.err.(skipError); isSkip { 129 status = "skip" 130 test.err = nil 131 if !skipOkay[path.Join(test.dir, test.gofile)] { 132 errStr = "unexpected skip for " + path.Join(test.dir, test.gofile) + ": " + errStr 133 status = "FAIL" 134 } 135 } 136 if test.err != nil { 137 status = "FAIL" 138 errStr = test.err.Error() 139 } 140 if status == "FAIL" { 141 failed = true 142 } 143 resCount[status]++ 144 if status == "skip" && !*verbose && !*showSkips { 145 continue 146 } 147 dt := fmt.Sprintf("%.3fs", test.dt.Seconds()) 148 if status == "FAIL" { 149 fmt.Printf("# go run run.go -- %s\n%s\nFAIL\t%s\t%s\n", 150 path.Join(test.dir, test.gofile), 151 errStr, test.goFileName(), dt) 152 continue 153 } 154 if !*verbose { 155 continue 156 } 157 fmt.Printf("%s\t%s\t%s\n", status, test.goFileName(), dt) 158 } 159 160 if *summary { 161 for k, v := range resCount { 162 fmt.Printf("%5d %s\n", v, k) 163 } 164 } 165 166 if failed { 167 os.Exit(1) 168 } 169 } 170 171 func toolPath(name string) string { 172 p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name) 173 if _, err := os.Stat(p); err != nil { 174 log.Fatalf("didn't find binary at %s", p) 175 } 176 return p 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") { 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", gc, "-e", longname) 198 } 199 200 func compileInDir(runcmd runCmd, dir string, names ...string) (out []byte, err error) { 201 cmd := []string{"go", "tool", gc, "-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", "."+letter, -1) 210 _, err = runcmd("go", "tool", ld, "-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 if idx := strings.Index(src, "\npackage"); idx >= 0 { 332 src = src[:idx] 333 } 334 for _, line := range strings.Split(src, "\n") { 335 line = strings.TrimSpace(line) 336 if strings.HasPrefix(line, "//") { 337 line = line[2:] 338 } else { 339 continue 340 } 341 line = strings.TrimSpace(line) 342 if len(line) == 0 || line[0] != '+' { 343 continue 344 } 345 ctxt := &context{ 346 GOOS: goos, 347 GOARCH: goarch, 348 } 349 words := strings.Fields(line) 350 if words[0] == "+build" { 351 ok := false 352 for _, word := range words[1:] { 353 if ctxt.match(word) { 354 ok = true 355 break 356 } 357 } 358 if !ok { 359 // no matching tag found. 360 return false, line 361 } 362 } 363 } 364 // no build tags 365 return true, "" 366 } 367 368 func (ctxt *context) match(name string) bool { 369 if name == "" { 370 return false 371 } 372 if i := strings.Index(name, ","); i >= 0 { 373 // comma-separated list 374 return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) 375 } 376 if strings.HasPrefix(name, "!!") { // bad syntax, reject always 377 return false 378 } 379 if strings.HasPrefix(name, "!") { // negation 380 return len(name) > 1 && !ctxt.match(name[1:]) 381 } 382 383 // Tags must be letters, digits, underscores or dots. 384 // Unlike in Go identifiers, all digits are fine (e.g., "386"). 385 for _, c := range name { 386 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { 387 return false 388 } 389 } 390 391 if name == ctxt.GOOS || name == ctxt.GOARCH { 392 return true 393 } 394 395 return false 396 } 397 398 func init() { checkShouldTest() } 399 400 // run runs a test. 401 func (t *test) run() { 402 start := time.Now() 403 defer func() { 404 t.dt = time.Since(start) 405 close(t.donec) 406 }() 407 408 srcBytes, err := ioutil.ReadFile(t.goFileName()) 409 if err != nil { 410 t.err = err 411 return 412 } 413 t.src = string(srcBytes) 414 if t.src[0] == '\n' { 415 t.err = skipError("starts with newline") 416 return 417 } 418 pos := strings.Index(t.src, "\n\n") 419 if pos == -1 { 420 t.err = errors.New("double newline not found") 421 return 422 } 423 if ok, why := shouldTest(t.src, goos, goarch); !ok { 424 t.action = "skip" 425 if *showSkips { 426 fmt.Printf("%-20s %-20s: %s\n", t.action, t.goFileName(), why) 427 } 428 return 429 } 430 action := t.src[:pos] 431 if nl := strings.Index(action, "\n"); nl >= 0 && strings.Contains(action[:nl], "+build") { 432 // skip first line 433 action = action[nl+1:] 434 } 435 if strings.HasPrefix(action, "//") { 436 action = action[2:] 437 } 438 439 var args, flags []string 440 wantError := false 441 f := strings.Fields(action) 442 if len(f) > 0 { 443 action = f[0] 444 args = f[1:] 445 } 446 447 switch action { 448 case "rundircmpout": 449 action = "rundir" 450 t.action = "rundir" 451 case "cmpout": 452 action = "run" // the run case already looks for <dir>/<test>.out files 453 fallthrough 454 case "compile", "compiledir", "build", "run", "runoutput", "rundir": 455 t.action = action 456 case "errorcheck", "errorcheckdir", "errorcheckoutput": 457 t.action = action 458 wantError = true 459 for len(args) > 0 && strings.HasPrefix(args[0], "-") { 460 if args[0] == "-0" { 461 wantError = false 462 } else { 463 flags = append(flags, args[0]) 464 } 465 args = args[1:] 466 } 467 case "skip": 468 t.action = "skip" 469 return 470 default: 471 t.err = skipError("skipped; unknown pattern: " + action) 472 t.action = "??" 473 return 474 } 475 476 t.makeTempDir() 477 defer os.RemoveAll(t.tempDir) 478 479 err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644) 480 check(err) 481 482 // A few tests (of things like the environment) require these to be set. 483 if os.Getenv("GOOS") == "" { 484 os.Setenv("GOOS", runtime.GOOS) 485 } 486 if os.Getenv("GOARCH") == "" { 487 os.Setenv("GOARCH", runtime.GOARCH) 488 } 489 490 useTmp := true 491 runcmd := func(args ...string) ([]byte, error) { 492 cmd := exec.Command(args[0], args[1:]...) 493 var buf bytes.Buffer 494 cmd.Stdout = &buf 495 cmd.Stderr = &buf 496 if useTmp { 497 cmd.Dir = t.tempDir 498 cmd.Env = envForDir(cmd.Dir) 499 } 500 err := cmd.Run() 501 if err != nil { 502 err = fmt.Errorf("%s\n%s", err, buf.Bytes()) 503 } 504 return buf.Bytes(), err 505 } 506 507 long := filepath.Join(cwd, t.goFileName()) 508 switch action { 509 default: 510 t.err = fmt.Errorf("unimplemented action %q", action) 511 512 case "errorcheck": 513 cmdline := []string{"go", "tool", gc, "-e", "-o", "a." + letter} 514 cmdline = append(cmdline, flags...) 515 cmdline = append(cmdline, long) 516 out, err := runcmd(cmdline...) 517 if wantError { 518 if err == nil { 519 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 520 return 521 } 522 } else { 523 if err != nil { 524 t.err = err 525 return 526 } 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 (t *test) errorCheck(outStr string, fullshort ...string) (err error) { 729 defer func() { 730 if *verbose && err != nil { 731 log.Printf("%s gc output:\n%s", t, outStr) 732 } 733 }() 734 var errs []error 735 736 var out []string 737 // 6g 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 for _, line := range strings.Split(outStr, "\n") { 740 if strings.HasSuffix(line, "\r") { // remove '\r', output by compiler on windows 741 line = line[:len(line)-1] 742 } 743 if strings.HasPrefix(line, "\t") { 744 out[len(out)-1] += "\n" + line 745 } else if strings.HasPrefix(line, "go tool") { 746 continue 747 } else if strings.TrimSpace(line) != "" { 748 out = append(out, line) 749 } 750 } 751 752 // Cut directory name. 753 for i := range out { 754 for j := 0; j < len(fullshort); j += 2 { 755 full, short := fullshort[j], fullshort[j+1] 756 out[i] = strings.Replace(out[i], full, short, -1) 757 } 758 } 759 760 var want []wantedError 761 for j := 0; j < len(fullshort); j += 2 { 762 full, short := fullshort[j], fullshort[j+1] 763 want = append(want, t.wantedErrors(full, short)...) 764 } 765 766 for _, we := range want { 767 var errmsgs []string 768 errmsgs, out = partitionStrings(we.prefix, out) 769 if len(errmsgs) == 0 { 770 errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr)) 771 continue 772 } 773 matched := false 774 n := len(out) 775 for _, errmsg := range errmsgs { 776 if we.re.MatchString(errmsg) { 777 matched = true 778 } else { 779 out = append(out, errmsg) 780 } 781 } 782 if !matched { 783 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"))) 784 continue 785 } 786 } 787 788 if len(out) > 0 { 789 errs = append(errs, fmt.Errorf("Unmatched Errors:")) 790 for _, errLine := range out { 791 errs = append(errs, fmt.Errorf("%s", errLine)) 792 } 793 } 794 795 if len(errs) == 0 { 796 return nil 797 } 798 if len(errs) == 1 { 799 return errs[0] 800 } 801 var buf bytes.Buffer 802 fmt.Fprintf(&buf, "\n") 803 for _, err := range errs { 804 fmt.Fprintf(&buf, "%s\n", err.Error()) 805 } 806 return errors.New(buf.String()) 807 808 } 809 810 // matchPrefix reports whether s is of the form ^(.*/)?prefix(:|[), 811 // That is, it needs the file name prefix followed by a : or a [, 812 // and possibly preceded by a directory name. 813 func matchPrefix(s, prefix string) bool { 814 i := strings.Index(s, ":") 815 if i < 0 { 816 return false 817 } 818 j := strings.LastIndex(s[:i], "/") 819 s = s[j+1:] 820 if len(s) <= len(prefix) || s[:len(prefix)] != prefix { 821 return false 822 } 823 switch s[len(prefix)] { 824 case '[', ':': 825 return true 826 } 827 return false 828 } 829 830 func partitionStrings(prefix string, strs []string) (matched, unmatched []string) { 831 for _, s := range strs { 832 if matchPrefix(s, prefix) { 833 matched = append(matched, s) 834 } else { 835 unmatched = append(unmatched, s) 836 } 837 } 838 return 839 } 840 841 type wantedError struct { 842 reStr string 843 re *regexp.Regexp 844 lineNum int 845 file string 846 prefix string 847 } 848 849 var ( 850 errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`) 851 errQuotesRx = regexp.MustCompile(`"([^"]*)"`) 852 lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`) 853 ) 854 855 func (t *test) wantedErrors(file, short string) (errs []wantedError) { 856 cache := make(map[string]*regexp.Regexp) 857 858 src, _ := ioutil.ReadFile(file) 859 for i, line := range strings.Split(string(src), "\n") { 860 lineNum := i + 1 861 if strings.Contains(line, "////") { 862 // double comment disables ERROR 863 continue 864 } 865 m := errRx.FindStringSubmatch(line) 866 if m == nil { 867 continue 868 } 869 all := m[1] 870 mm := errQuotesRx.FindAllStringSubmatch(all, -1) 871 if mm == nil { 872 log.Fatalf("%s:%d: invalid errchk line: %s", t.goFileName(), lineNum, line) 873 } 874 for _, m := range mm { 875 rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string { 876 n := lineNum 877 if strings.HasPrefix(m, "LINE+") { 878 delta, _ := strconv.Atoi(m[5:]) 879 n += delta 880 } else if strings.HasPrefix(m, "LINE-") { 881 delta, _ := strconv.Atoi(m[5:]) 882 n -= delta 883 } 884 return fmt.Sprintf("%s:%d", short, n) 885 }) 886 re := cache[rx] 887 if re == nil { 888 var err error 889 re, err = regexp.Compile(rx) 890 if err != nil { 891 log.Fatalf("%s:%d: invalid regexp in ERROR line: %v", t.goFileName(), lineNum, err) 892 } 893 cache[rx] = re 894 } 895 prefix := fmt.Sprintf("%s:%d", short, lineNum) 896 errs = append(errs, wantedError{ 897 reStr: rx, 898 re: re, 899 prefix: prefix, 900 lineNum: lineNum, 901 file: short, 902 }) 903 } 904 } 905 906 return 907 } 908 909 var skipOkay = map[string]bool{ 910 "fixedbugs/bug248.go": true, // combines errorcheckdir and rundir in the same dir. 911 "fixedbugs/bug302.go": true, // tests both .$O and .a imports. 912 "fixedbugs/bug345.go": true, // needs the appropriate flags in gc invocation. 913 "fixedbugs/bug369.go": true, // needs compiler flags. 914 "fixedbugs/bug429.go": true, // like "run" but program should fail 915 "bugs/bug395.go": true, 916 } 917 918 // defaultRunOutputLimit returns the number of runoutput tests that 919 // can be executed in parallel. 920 func defaultRunOutputLimit() int { 921 const maxArmCPU = 2 922 923 cpu := runtime.NumCPU() 924 if runtime.GOARCH == "arm" && cpu > maxArmCPU { 925 cpu = maxArmCPU 926 } 927 return cpu 928 } 929 930 // checkShouldTest runs sanity checks on the shouldTest function. 931 func checkShouldTest() { 932 assert := func(ok bool, _ string) { 933 if !ok { 934 panic("fail") 935 } 936 } 937 assertNot := func(ok bool, _ string) { assert(!ok, "") } 938 939 // Simple tests. 940 assert(shouldTest("// +build linux", "linux", "arm")) 941 assert(shouldTest("// +build !windows", "linux", "arm")) 942 assertNot(shouldTest("// +build !windows", "windows", "amd64")) 943 944 // A file with no build tags will always be tested. 945 assert(shouldTest("// This is a test.", "os", "arch")) 946 947 // Build tags separated by a space are OR-ed together. 948 assertNot(shouldTest("// +build arm 386", "linux", "amd64")) 949 950 // Build tags separated by a comma are AND-ed together. 951 assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64")) 952 assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386")) 953 954 // Build tags on multiple lines are AND-ed together. 955 assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64")) 956 assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64")) 957 958 // Test that (!a OR !b) matches anything. 959 assert(shouldTest("// +build !windows !plan9", "windows", "amd64")) 960 } 961 962 // envForDir returns a copy of the environment 963 // suitable for running in the given directory. 964 // The environment is the current process's environment 965 // but with an updated $PWD, so that an os.Getwd in the 966 // child will be faster. 967 func envForDir(dir string) []string { 968 env := os.Environ() 969 for i, kv := range env { 970 if strings.HasPrefix(kv, "PWD=") { 971 env[i] = "PWD=" + dir 972 return env 973 } 974 } 975 env = append(env, "PWD="+dir) 976 return env 977 } 978 979 func getenv(key, def string) string { 980 value := os.Getenv(key) 981 if value != "" { 982 return value 983 } 984 return def 985 }