github.com/yanyiwu/go@v0.0.0-20150106053140-03d6637dbb7f/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 test.err = nil 130 errStr = "unexpected skip for " + path.Join(test.dir, test.gofile) + ": " + errStr 131 status = "FAIL" 132 } 133 if test.err != nil { 134 status = "FAIL" 135 errStr = test.err.Error() 136 } 137 if status == "FAIL" { 138 failed = true 139 } 140 resCount[status]++ 141 if status == "skip" && !*verbose && !*showSkips { 142 continue 143 } 144 dt := fmt.Sprintf("%.3fs", test.dt.Seconds()) 145 if status == "FAIL" { 146 fmt.Printf("# go run run.go -- %s\n%s\nFAIL\t%s\t%s\n", 147 path.Join(test.dir, test.gofile), 148 errStr, test.goFileName(), dt) 149 continue 150 } 151 if !*verbose { 152 continue 153 } 154 fmt.Printf("%s\t%s\t%s\n", status, test.goFileName(), dt) 155 } 156 157 if *summary { 158 for k, v := range resCount { 159 fmt.Printf("%5d %s\n", v, k) 160 } 161 } 162 163 if failed { 164 os.Exit(1) 165 } 166 } 167 168 func toolPath(name string) string { 169 p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name) 170 if _, err := os.Stat(p); err != nil { 171 log.Fatalf("didn't find binary at %s", p) 172 } 173 return p 174 } 175 176 func goFiles(dir string) []string { 177 f, err := os.Open(dir) 178 check(err) 179 dirnames, err := f.Readdirnames(-1) 180 check(err) 181 names := []string{} 182 for _, name := range dirnames { 183 if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") { 184 names = append(names, name) 185 } 186 } 187 sort.Strings(names) 188 return names 189 } 190 191 type runCmd func(...string) ([]byte, error) 192 193 func compileFile(runcmd runCmd, longname string) (out []byte, err error) { 194 return runcmd("go", "tool", gc, "-e", longname) 195 } 196 197 func compileInDir(runcmd runCmd, dir string, names ...string) (out []byte, err error) { 198 cmd := []string{"go", "tool", gc, "-e", "-D", ".", "-I", "."} 199 for _, name := range names { 200 cmd = append(cmd, filepath.Join(dir, name)) 201 } 202 return runcmd(cmd...) 203 } 204 205 func linkFile(runcmd runCmd, goname string) (err error) { 206 pfile := strings.Replace(goname, ".go", "."+letter, -1) 207 _, err = runcmd("go", "tool", ld, "-w", "-o", "a.exe", "-L", ".", pfile) 208 return 209 } 210 211 // skipError describes why a test was skipped. 212 type skipError string 213 214 func (s skipError) Error() string { return string(s) } 215 216 func check(err error) { 217 if err != nil { 218 log.Fatal(err) 219 } 220 } 221 222 // test holds the state of a test. 223 type test struct { 224 dir, gofile string 225 donec chan bool // closed when done 226 dt time.Duration 227 228 src string 229 action string // "compile", "build", etc. 230 231 tempDir string 232 err error 233 } 234 235 // startTest 236 func startTest(dir, gofile string) *test { 237 t := &test{ 238 dir: dir, 239 gofile: gofile, 240 donec: make(chan bool, 1), 241 } 242 if toRun == nil { 243 toRun = make(chan *test, maxTests) 244 go runTests() 245 } 246 select { 247 case toRun <- t: 248 default: 249 panic("toRun buffer size (maxTests) is too small") 250 } 251 return t 252 } 253 254 // runTests runs tests in parallel, but respecting the order they 255 // were enqueued on the toRun channel. 256 func runTests() { 257 for { 258 ratec <- true 259 t := <-toRun 260 go func() { 261 t.run() 262 <-ratec 263 }() 264 } 265 } 266 267 var cwd, _ = os.Getwd() 268 269 func (t *test) goFileName() string { 270 return filepath.Join(t.dir, t.gofile) 271 } 272 273 func (t *test) goDirName() string { 274 return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1)) 275 } 276 277 func goDirFiles(longdir string) (filter []os.FileInfo, err error) { 278 files, dirErr := ioutil.ReadDir(longdir) 279 if dirErr != nil { 280 return nil, dirErr 281 } 282 for _, gofile := range files { 283 if filepath.Ext(gofile.Name()) == ".go" { 284 filter = append(filter, gofile) 285 } 286 } 287 return 288 } 289 290 var packageRE = regexp.MustCompile(`(?m)^package (\w+)`) 291 292 func goDirPackages(longdir string) ([][]string, error) { 293 files, err := goDirFiles(longdir) 294 if err != nil { 295 return nil, err 296 } 297 var pkgs [][]string 298 m := make(map[string]int) 299 for _, file := range files { 300 name := file.Name() 301 data, err := ioutil.ReadFile(filepath.Join(longdir, name)) 302 if err != nil { 303 return nil, err 304 } 305 pkgname := packageRE.FindStringSubmatch(string(data)) 306 if pkgname == nil { 307 return nil, fmt.Errorf("cannot find package name in %s", name) 308 } 309 i, ok := m[pkgname[1]] 310 if !ok { 311 i = len(pkgs) 312 pkgs = append(pkgs, nil) 313 m[pkgname[1]] = i 314 } 315 pkgs[i] = append(pkgs[i], name) 316 } 317 return pkgs, nil 318 } 319 320 type context struct { 321 GOOS string 322 GOARCH string 323 } 324 325 // shouldTest looks for build tags in a source file and returns 326 // whether the file should be used according to the tags. 327 func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) { 328 if idx := strings.Index(src, "\npackage"); idx >= 0 { 329 src = src[:idx] 330 } 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 pos := strings.Index(t.src, "\n\n") 416 if pos == -1 { 417 t.err = errors.New("double newline not found") 418 return 419 } 420 if ok, why := shouldTest(t.src, 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 t.err = t.errorCheck(string(out), long, t.gofile) 526 return 527 528 case "compile": 529 _, t.err = compileFile(runcmd, long) 530 531 case "compiledir": 532 // Compile all files in the directory in lexicographic order. 533 longdir := filepath.Join(cwd, t.goDirName()) 534 pkgs, err := goDirPackages(longdir) 535 if err != nil { 536 t.err = err 537 return 538 } 539 for _, gofiles := range pkgs { 540 _, t.err = compileInDir(runcmd, longdir, gofiles...) 541 if t.err != nil { 542 return 543 } 544 } 545 546 case "errorcheckdir": 547 // errorcheck all files in lexicographic order 548 // useful for finding importing errors 549 longdir := filepath.Join(cwd, t.goDirName()) 550 pkgs, err := goDirPackages(longdir) 551 if err != nil { 552 t.err = err 553 return 554 } 555 for i, gofiles := range pkgs { 556 out, err := compileInDir(runcmd, longdir, gofiles...) 557 if i == len(pkgs)-1 { 558 if wantError && err == nil { 559 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 560 return 561 } else if !wantError && err != nil { 562 t.err = err 563 return 564 } 565 } else if err != nil { 566 t.err = err 567 return 568 } 569 var fullshort []string 570 for _, name := range gofiles { 571 fullshort = append(fullshort, filepath.Join(longdir, name), name) 572 } 573 t.err = t.errorCheck(string(out), fullshort...) 574 if t.err != nil { 575 break 576 } 577 } 578 579 case "rundir": 580 // Compile all files in the directory in lexicographic order. 581 // then link as if the last file is the main package and run it 582 longdir := filepath.Join(cwd, t.goDirName()) 583 pkgs, err := goDirPackages(longdir) 584 if err != nil { 585 t.err = err 586 return 587 } 588 for i, gofiles := range pkgs { 589 _, err := compileInDir(runcmd, longdir, gofiles...) 590 if err != nil { 591 t.err = err 592 return 593 } 594 if i == len(pkgs)-1 { 595 err = linkFile(runcmd, gofiles[0]) 596 if err != nil { 597 t.err = err 598 return 599 } 600 var cmd []string 601 cmd = append(cmd, findExecCmd()...) 602 cmd = append(cmd, filepath.Join(t.tempDir, "a.exe")) 603 cmd = append(cmd, args...) 604 out, err := runcmd(cmd...) 605 if err != nil { 606 t.err = err 607 return 608 } 609 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 610 t.err = fmt.Errorf("incorrect output\n%s", out) 611 } 612 } 613 } 614 615 case "build": 616 _, err := runcmd("go", "build", "-o", "a.exe", long) 617 if err != nil { 618 t.err = err 619 } 620 621 case "run": 622 useTmp = false 623 out, err := runcmd(append([]string{"go", "run", t.goFileName()}, args...)...) 624 if err != nil { 625 t.err = err 626 return 627 } 628 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 629 t.err = fmt.Errorf("incorrect output\n%s", out) 630 } 631 632 case "runoutput": 633 rungatec <- true 634 defer func() { 635 <-rungatec 636 }() 637 useTmp = false 638 out, err := runcmd(append([]string{"go", "run", t.goFileName()}, args...)...) 639 if err != nil { 640 t.err = err 641 return 642 } 643 tfile := filepath.Join(t.tempDir, "tmp__.go") 644 if err := ioutil.WriteFile(tfile, out, 0666); err != nil { 645 t.err = fmt.Errorf("write tempfile:%s", err) 646 return 647 } 648 out, err = runcmd("go", "run", tfile) 649 if err != nil { 650 t.err = err 651 return 652 } 653 if string(out) != t.expectedOutput() { 654 t.err = fmt.Errorf("incorrect output\n%s", out) 655 } 656 657 case "errorcheckoutput": 658 useTmp = false 659 out, err := runcmd(append([]string{"go", "run", t.goFileName()}, args...)...) 660 if err != nil { 661 t.err = err 662 return 663 } 664 tfile := filepath.Join(t.tempDir, "tmp__.go") 665 err = ioutil.WriteFile(tfile, out, 0666) 666 if err != nil { 667 t.err = fmt.Errorf("write tempfile:%s", err) 668 return 669 } 670 cmdline := []string{"go", "tool", gc, "-e", "-o", "a." + letter} 671 cmdline = append(cmdline, flags...) 672 cmdline = append(cmdline, tfile) 673 out, err = runcmd(cmdline...) 674 if wantError { 675 if err == nil { 676 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 677 return 678 } 679 } else { 680 if err != nil { 681 t.err = err 682 return 683 } 684 } 685 t.err = t.errorCheck(string(out), tfile, "tmp__.go") 686 return 687 } 688 } 689 690 var execCmd []string 691 692 func findExecCmd() []string { 693 if execCmd != nil { 694 return execCmd 695 } 696 execCmd = []string{} // avoid work the second time 697 if goos == runtime.GOOS && goarch == runtime.GOARCH { 698 return execCmd 699 } 700 path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch)) 701 if err == nil { 702 execCmd = []string{path} 703 } 704 return execCmd 705 } 706 707 func (t *test) String() string { 708 return filepath.Join(t.dir, t.gofile) 709 } 710 711 func (t *test) makeTempDir() { 712 var err error 713 t.tempDir, err = ioutil.TempDir("", "") 714 check(err) 715 } 716 717 func (t *test) expectedOutput() string { 718 filename := filepath.Join(t.dir, t.gofile) 719 filename = filename[:len(filename)-len(".go")] 720 filename += ".out" 721 b, _ := ioutil.ReadFile(filename) 722 return string(b) 723 } 724 725 func (t *test) errorCheck(outStr string, fullshort ...string) (err error) { 726 defer func() { 727 if *verbose && err != nil { 728 log.Printf("%s gc output:\n%s", t, outStr) 729 } 730 }() 731 var errs []error 732 733 var out []string 734 // 6g error messages continue onto additional lines with leading tabs. 735 // Split the output at the beginning of each line that doesn't begin with a tab. 736 for _, line := range strings.Split(outStr, "\n") { 737 if strings.HasSuffix(line, "\r") { // remove '\r', output by compiler on windows 738 line = line[:len(line)-1] 739 } 740 if strings.HasPrefix(line, "\t") { 741 out[len(out)-1] += "\n" + line 742 } else if strings.HasPrefix(line, "go tool") { 743 continue 744 } else if strings.TrimSpace(line) != "" { 745 out = append(out, line) 746 } 747 } 748 749 // Cut directory name. 750 for i := range out { 751 for j := 0; j < len(fullshort); j += 2 { 752 full, short := fullshort[j], fullshort[j+1] 753 out[i] = strings.Replace(out[i], full, short, -1) 754 } 755 } 756 757 var want []wantedError 758 for j := 0; j < len(fullshort); j += 2 { 759 full, short := fullshort[j], fullshort[j+1] 760 want = append(want, t.wantedErrors(full, short)...) 761 } 762 763 for _, we := range want { 764 var errmsgs []string 765 errmsgs, out = partitionStrings(we.prefix, out) 766 if len(errmsgs) == 0 { 767 errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr)) 768 continue 769 } 770 matched := false 771 n := len(out) 772 for _, errmsg := range errmsgs { 773 if we.re.MatchString(errmsg) { 774 matched = true 775 } else { 776 out = append(out, errmsg) 777 } 778 } 779 if !matched { 780 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"))) 781 continue 782 } 783 } 784 785 if len(out) > 0 { 786 errs = append(errs, fmt.Errorf("Unmatched Errors:")) 787 for _, errLine := range out { 788 errs = append(errs, fmt.Errorf("%s", errLine)) 789 } 790 } 791 792 if len(errs) == 0 { 793 return nil 794 } 795 if len(errs) == 1 { 796 return errs[0] 797 } 798 var buf bytes.Buffer 799 fmt.Fprintf(&buf, "\n") 800 for _, err := range errs { 801 fmt.Fprintf(&buf, "%s\n", err.Error()) 802 } 803 return errors.New(buf.String()) 804 805 } 806 807 // matchPrefix reports whether s is of the form ^(.*/)?prefix(:|[), 808 // That is, it needs the file name prefix followed by a : or a [, 809 // and possibly preceded by a directory name. 810 func matchPrefix(s, prefix string) bool { 811 i := strings.Index(s, ":") 812 if i < 0 { 813 return false 814 } 815 j := strings.LastIndex(s[:i], "/") 816 s = s[j+1:] 817 if len(s) <= len(prefix) || s[:len(prefix)] != prefix { 818 return false 819 } 820 switch s[len(prefix)] { 821 case '[', ':': 822 return true 823 } 824 return false 825 } 826 827 func partitionStrings(prefix string, strs []string) (matched, unmatched []string) { 828 for _, s := range strs { 829 if matchPrefix(s, prefix) { 830 matched = append(matched, s) 831 } else { 832 unmatched = append(unmatched, s) 833 } 834 } 835 return 836 } 837 838 type wantedError struct { 839 reStr string 840 re *regexp.Regexp 841 lineNum int 842 file string 843 prefix string 844 } 845 846 var ( 847 errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`) 848 errQuotesRx = regexp.MustCompile(`"([^"]*)"`) 849 lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`) 850 ) 851 852 func (t *test) wantedErrors(file, short string) (errs []wantedError) { 853 cache := make(map[string]*regexp.Regexp) 854 855 src, _ := ioutil.ReadFile(file) 856 for i, line := range strings.Split(string(src), "\n") { 857 lineNum := i + 1 858 if strings.Contains(line, "////") { 859 // double comment disables ERROR 860 continue 861 } 862 m := errRx.FindStringSubmatch(line) 863 if m == nil { 864 continue 865 } 866 all := m[1] 867 mm := errQuotesRx.FindAllStringSubmatch(all, -1) 868 if mm == nil { 869 log.Fatalf("%s:%d: invalid errchk line: %s", t.goFileName(), lineNum, line) 870 } 871 for _, m := range mm { 872 rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string { 873 n := lineNum 874 if strings.HasPrefix(m, "LINE+") { 875 delta, _ := strconv.Atoi(m[5:]) 876 n += delta 877 } else if strings.HasPrefix(m, "LINE-") { 878 delta, _ := strconv.Atoi(m[5:]) 879 n -= delta 880 } 881 return fmt.Sprintf("%s:%d", short, n) 882 }) 883 re := cache[rx] 884 if re == nil { 885 var err error 886 re, err = regexp.Compile(rx) 887 if err != nil { 888 log.Fatalf("%s:%d: invalid regexp in ERROR line: %v", t.goFileName(), lineNum, err) 889 } 890 cache[rx] = re 891 } 892 prefix := fmt.Sprintf("%s:%d", short, lineNum) 893 errs = append(errs, wantedError{ 894 reStr: rx, 895 re: re, 896 prefix: prefix, 897 lineNum: lineNum, 898 file: short, 899 }) 900 } 901 } 902 903 return 904 } 905 906 // defaultRunOutputLimit returns the number of runoutput tests that 907 // can be executed in parallel. 908 func defaultRunOutputLimit() int { 909 const maxArmCPU = 2 910 911 cpu := runtime.NumCPU() 912 if runtime.GOARCH == "arm" && cpu > maxArmCPU { 913 cpu = maxArmCPU 914 } 915 return cpu 916 } 917 918 // checkShouldTest runs sanity checks on the shouldTest function. 919 func checkShouldTest() { 920 assert := func(ok bool, _ string) { 921 if !ok { 922 panic("fail") 923 } 924 } 925 assertNot := func(ok bool, _ string) { assert(!ok, "") } 926 927 // Simple tests. 928 assert(shouldTest("// +build linux", "linux", "arm")) 929 assert(shouldTest("// +build !windows", "linux", "arm")) 930 assertNot(shouldTest("// +build !windows", "windows", "amd64")) 931 932 // A file with no build tags will always be tested. 933 assert(shouldTest("// This is a test.", "os", "arch")) 934 935 // Build tags separated by a space are OR-ed together. 936 assertNot(shouldTest("// +build arm 386", "linux", "amd64")) 937 938 // Build tags separated by a comma are AND-ed together. 939 assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64")) 940 assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386")) 941 942 // Build tags on multiple lines are AND-ed together. 943 assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64")) 944 assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64")) 945 946 // Test that (!a OR !b) matches anything. 947 assert(shouldTest("// +build !windows !plan9", "windows", "amd64")) 948 } 949 950 // envForDir returns a copy of the environment 951 // suitable for running in the given directory. 952 // The environment is the current process's environment 953 // but with an updated $PWD, so that an os.Getwd in the 954 // child will be faster. 955 func envForDir(dir string) []string { 956 env := os.Environ() 957 for i, kv := range env { 958 if strings.HasPrefix(kv, "PWD=") { 959 env[i] = "PWD=" + dir 960 return env 961 } 962 } 963 env = append(env, "PWD="+dir) 964 return env 965 } 966 967 func getenv(key, def string) string { 968 value := os.Getenv(key) 969 if value != "" { 970 return value 971 } 972 return def 973 }