modernc.org/gc@v1.0.1-0.20240304020402-f0dba7c97c2b/testdata/errchk/test/run.go (about) 1 // skip 2 3 // Copyright 2012 The Go Authors. All rights reserved. 4 // Use of this source code is governed by a BSD-style 5 // license that can be found in the LICENSE file. 6 7 // Run runs tests in the test directory. 8 // 9 // TODO(bradfitz): docs of some sort, once we figure out how we're changing 10 // headers of files 11 package main 12 13 import ( 14 "bytes" 15 "errors" 16 "flag" 17 "fmt" 18 "hash/fnv" 19 "io" 20 "io/ioutil" 21 "log" 22 "os" 23 "os/exec" 24 "path" 25 "path/filepath" 26 "regexp" 27 "runtime" 28 "sort" 29 "strconv" 30 "strings" 31 "time" 32 "unicode" 33 ) 34 35 var ( 36 verbose = flag.Bool("v", false, "verbose. if set, parallelism is set to 1.") 37 keep = flag.Bool("k", false, "keep. keep temporary directory.") 38 numParallel = flag.Int("n", runtime.NumCPU(), "number of parallel tests to run") 39 summary = flag.Bool("summary", false, "show summary of results") 40 showSkips = flag.Bool("show_skips", false, "show skipped tests") 41 runSkips = flag.Bool("run_skips", false, "run skipped tests (ignore skip and build tags)") 42 linkshared = flag.Bool("linkshared", false, "") 43 updateErrors = flag.Bool("update_errors", false, "update error messages in test file based on compiler output") 44 runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run") 45 46 shard = flag.Int("shard", 0, "shard index to run. Only applicable if -shards is non-zero.") 47 shards = flag.Int("shards", 0, "number of shards. If 0, all tests are run. This is used by the continuous build.") 48 ) 49 50 var ( 51 goos, goarch string 52 53 // dirs are the directories to look for *.go files in. 54 // TODO(bradfitz): just use all directories? 55 dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs"} 56 57 // ratec controls the max number of tests running at a time. 58 ratec chan bool 59 60 // toRun is the channel of tests to run. 61 // It is nil until the first test is started. 62 toRun chan *test 63 64 // rungatec controls the max number of runoutput tests 65 // executed in parallel as they can each consume a lot of memory. 66 rungatec chan bool 67 ) 68 69 // maxTests is an upper bound on the total number of tests. 70 // It is used as a channel buffer size to make sure sends don't block. 71 const maxTests = 5000 72 73 func main() { 74 flag.Parse() 75 76 goos = getenv("GOOS", runtime.GOOS) 77 goarch = getenv("GOARCH", runtime.GOARCH) 78 79 findExecCmd() 80 81 // Disable parallelism if printing or if using a simulator. 82 if *verbose || len(findExecCmd()) > 0 { 83 *numParallel = 1 84 } 85 86 ratec = make(chan bool, *numParallel) 87 rungatec = make(chan bool, *runoutputLimit) 88 89 var tests []*test 90 if flag.NArg() > 0 { 91 for _, arg := range flag.Args() { 92 if arg == "-" || arg == "--" { 93 // Permit running: 94 // $ go run run.go - env.go 95 // $ go run run.go -- env.go 96 // $ go run run.go - ./fixedbugs 97 // $ go run run.go -- ./fixedbugs 98 continue 99 } 100 if fi, err := os.Stat(arg); err == nil && fi.IsDir() { 101 for _, baseGoFile := range goFiles(arg) { 102 tests = append(tests, startTest(arg, baseGoFile)) 103 } 104 } else if strings.HasSuffix(arg, ".go") { 105 dir, file := filepath.Split(arg) 106 tests = append(tests, startTest(dir, file)) 107 } else { 108 log.Fatalf("can't yet deal with non-directory and non-go file %q", arg) 109 } 110 } 111 } else { 112 for _, dir := range dirs { 113 for _, baseGoFile := range goFiles(dir) { 114 tests = append(tests, startTest(dir, baseGoFile)) 115 } 116 } 117 } 118 119 failed := false 120 resCount := map[string]int{} 121 for _, test := range tests { 122 <-test.donec 123 status := "ok " 124 errStr := "" 125 if e, isSkip := test.err.(skipError); isSkip { 126 test.err = nil 127 errStr = "unexpected skip for " + path.Join(test.dir, test.gofile) + ": " + string(e) 128 status = "FAIL" 129 } 130 if test.err != nil { 131 status = "FAIL" 132 errStr = test.err.Error() 133 } 134 if status == "FAIL" { 135 failed = true 136 } 137 resCount[status]++ 138 dt := fmt.Sprintf("%.3fs", test.dt.Seconds()) 139 if status == "FAIL" { 140 fmt.Printf("# go run run.go -- %s\n%s\nFAIL\t%s\t%s\n", 141 path.Join(test.dir, test.gofile), 142 errStr, test.goFileName(), dt) 143 continue 144 } 145 if !*verbose { 146 continue 147 } 148 fmt.Printf("%s\t%s\t%s\n", status, test.goFileName(), dt) 149 } 150 151 if *summary { 152 for k, v := range resCount { 153 fmt.Printf("%5d %s\n", v, k) 154 } 155 } 156 157 if failed { 158 os.Exit(1) 159 } 160 } 161 162 func toolPath(name string) string { 163 p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name) 164 if _, err := os.Stat(p); err != nil { 165 log.Fatalf("didn't find binary at %s", p) 166 } 167 return p 168 } 169 170 func shardMatch(name string) bool { 171 if *shards == 0 { 172 return true 173 } 174 h := fnv.New32() 175 io.WriteString(h, name) 176 return int(h.Sum32()%uint32(*shards)) == *shard 177 } 178 179 func goFiles(dir string) []string { 180 f, err := os.Open(dir) 181 check(err) 182 dirnames, err := f.Readdirnames(-1) 183 check(err) 184 names := []string{} 185 for _, name := range dirnames { 186 if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && shardMatch(name) { 187 names = append(names, name) 188 } 189 } 190 sort.Strings(names) 191 return names 192 } 193 194 type runCmd func(...string) ([]byte, error) 195 196 func compileFile(runcmd runCmd, longname string, flags []string) (out []byte, err error) { 197 cmd := []string{"go", "tool", "compile", "-e"} 198 cmd = append(cmd, flags...) 199 if *linkshared { 200 cmd = append(cmd, "-dynlink", "-installsuffix=dynlink") 201 } 202 cmd = append(cmd, longname) 203 return runcmd(cmd...) 204 } 205 206 func compileInDir(runcmd runCmd, dir string, flags []string, names ...string) (out []byte, err error) { 207 cmd := []string{"go", "tool", "compile", "-e", "-D", ".", "-I", "."} 208 cmd = append(cmd, flags...) 209 if *linkshared { 210 cmd = append(cmd, "-dynlink", "-installsuffix=dynlink") 211 } 212 for _, name := range names { 213 cmd = append(cmd, filepath.Join(dir, name)) 214 } 215 return runcmd(cmd...) 216 } 217 218 func linkFile(runcmd runCmd, goname string) (err error) { 219 pfile := strings.Replace(goname, ".go", ".o", -1) 220 cmd := []string{"go", "tool", "link", "-w", "-o", "a.exe", "-L", "."} 221 if *linkshared { 222 cmd = append(cmd, "-linkshared", "-installsuffix=dynlink") 223 } 224 cmd = append(cmd, pfile) 225 _, err = runcmd(cmd...) 226 return 227 } 228 229 // skipError describes why a test was skipped. 230 type skipError string 231 232 func (s skipError) Error() string { return string(s) } 233 234 func check(err error) { 235 if err != nil { 236 log.Fatal(err) 237 } 238 } 239 240 // test holds the state of a test. 241 type test struct { 242 dir, gofile string 243 donec chan bool // closed when done 244 dt time.Duration 245 246 src string 247 248 tempDir string 249 err error 250 } 251 252 // startTest 253 func startTest(dir, gofile string) *test { 254 t := &test{ 255 dir: dir, 256 gofile: gofile, 257 donec: make(chan bool, 1), 258 } 259 if toRun == nil { 260 toRun = make(chan *test, maxTests) 261 go runTests() 262 } 263 select { 264 case toRun <- t: 265 default: 266 panic("toRun buffer size (maxTests) is too small") 267 } 268 return t 269 } 270 271 // runTests runs tests in parallel, but respecting the order they 272 // were enqueued on the toRun channel. 273 func runTests() { 274 for { 275 ratec <- true 276 t := <-toRun 277 go func() { 278 t.run() 279 <-ratec 280 }() 281 } 282 } 283 284 var cwd, _ = os.Getwd() 285 286 func (t *test) goFileName() string { 287 return filepath.Join(t.dir, t.gofile) 288 } 289 290 func (t *test) goDirName() string { 291 return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1)) 292 } 293 294 func goDirFiles(longdir string) (filter []os.FileInfo, err error) { 295 files, dirErr := ioutil.ReadDir(longdir) 296 if dirErr != nil { 297 return nil, dirErr 298 } 299 for _, gofile := range files { 300 if filepath.Ext(gofile.Name()) == ".go" { 301 filter = append(filter, gofile) 302 } 303 } 304 return 305 } 306 307 var packageRE = regexp.MustCompile(`(?m)^package (\w+)`) 308 309 // If singlefilepkgs is set, each file is considered a separate package 310 // even if the package names are the same. 311 func goDirPackages(longdir string, singlefilepkgs bool) ([][]string, error) { 312 files, err := goDirFiles(longdir) 313 if err != nil { 314 return nil, err 315 } 316 var pkgs [][]string 317 m := make(map[string]int) 318 for _, file := range files { 319 name := file.Name() 320 data, err := ioutil.ReadFile(filepath.Join(longdir, name)) 321 if err != nil { 322 return nil, err 323 } 324 pkgname := packageRE.FindStringSubmatch(string(data)) 325 if pkgname == nil { 326 return nil, fmt.Errorf("cannot find package name in %s", name) 327 } 328 i, ok := m[pkgname[1]] 329 if singlefilepkgs || !ok { 330 i = len(pkgs) 331 pkgs = append(pkgs, nil) 332 m[pkgname[1]] = i 333 } 334 pkgs[i] = append(pkgs[i], name) 335 } 336 return pkgs, nil 337 } 338 339 type context struct { 340 GOOS string 341 GOARCH string 342 } 343 344 // shouldTest looks for build tags in a source file and returns 345 // whether the file should be used according to the tags. 346 func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) { 347 if *runSkips { 348 return true, "" 349 } 350 for _, line := range strings.Split(src, "\n") { 351 line = strings.TrimSpace(line) 352 if strings.HasPrefix(line, "//") { 353 line = line[2:] 354 } else { 355 continue 356 } 357 line = strings.TrimSpace(line) 358 if len(line) == 0 || line[0] != '+' { 359 continue 360 } 361 ctxt := &context{ 362 GOOS: goos, 363 GOARCH: goarch, 364 } 365 words := strings.Fields(line) 366 if words[0] == "+build" { 367 ok := false 368 for _, word := range words[1:] { 369 if ctxt.match(word) { 370 ok = true 371 break 372 } 373 } 374 if !ok { 375 // no matching tag found. 376 return false, line 377 } 378 } 379 } 380 // no build tags 381 return true, "" 382 } 383 384 func (ctxt *context) match(name string) bool { 385 if name == "" { 386 return false 387 } 388 if i := strings.Index(name, ","); i >= 0 { 389 // comma-separated list 390 return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) 391 } 392 if strings.HasPrefix(name, "!!") { // bad syntax, reject always 393 return false 394 } 395 if strings.HasPrefix(name, "!") { // negation 396 return len(name) > 1 && !ctxt.match(name[1:]) 397 } 398 399 // Tags must be letters, digits, underscores or dots. 400 // Unlike in Go identifiers, all digits are fine (e.g., "386"). 401 for _, c := range name { 402 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { 403 return false 404 } 405 } 406 407 if name == ctxt.GOOS || name == ctxt.GOARCH { 408 return true 409 } 410 411 if name == "test_run" { 412 return true 413 } 414 415 return false 416 } 417 418 func init() { checkShouldTest() } 419 420 // goGcflags returns the -gcflags argument to use with go build / go run. 421 // This must match the flags used for building the standard libary, 422 // or else the commands will rebuild any needed packages (like runtime) 423 // over and over. 424 func goGcflags() string { 425 return "-gcflags=" + os.Getenv("GO_GCFLAGS") 426 } 427 428 // run runs a test. 429 func (t *test) run() { 430 start := time.Now() 431 defer func() { 432 t.dt = time.Since(start) 433 close(t.donec) 434 }() 435 436 srcBytes, err := ioutil.ReadFile(t.goFileName()) 437 if err != nil { 438 t.err = err 439 return 440 } 441 t.src = string(srcBytes) 442 if t.src[0] == '\n' { 443 t.err = skipError("starts with newline") 444 return 445 } 446 447 // Execution recipe stops at first blank line. 448 pos := strings.Index(t.src, "\n\n") 449 if pos == -1 { 450 t.err = errors.New("double newline not found") 451 return 452 } 453 action := t.src[:pos] 454 if nl := strings.Index(action, "\n"); nl >= 0 && strings.Contains(action[:nl], "+build") { 455 // skip first line 456 action = action[nl+1:] 457 } 458 if strings.HasPrefix(action, "//") { 459 action = action[2:] 460 } 461 462 // Check for build constraints only up to the actual code. 463 pkgPos := strings.Index(t.src, "\npackage") 464 if pkgPos == -1 { 465 pkgPos = pos // some files are intentionally malformed 466 } 467 if ok, why := shouldTest(t.src[:pkgPos], goos, goarch); !ok { 468 if *showSkips { 469 fmt.Printf("%-20s %-20s: %s\n", "skip", t.goFileName(), why) 470 } 471 return 472 } 473 474 var args, flags []string 475 var tim int 476 wantError := false 477 wantAuto := false 478 singlefilepkgs := false 479 f := strings.Fields(action) 480 if len(f) > 0 { 481 action = f[0] 482 args = f[1:] 483 } 484 485 // TODO: Clean up/simplify this switch statement. 486 switch action { 487 case "rundircmpout": 488 action = "rundir" 489 case "cmpout": 490 action = "run" // the run case already looks for <dir>/<test>.out files 491 case "compile", "compiledir", "build", "builddir", "run", "buildrun", "runoutput", "rundir": 492 // nothing to do 493 case "errorcheckandrundir": 494 wantError = false // should be no error if also will run 495 case "errorcheckwithauto": 496 action = "errorcheck" 497 wantAuto = true 498 wantError = true 499 case "errorcheck", "errorcheckdir", "errorcheckoutput": 500 wantError = true 501 case "skip": 502 if *runSkips { 503 break 504 } 505 return 506 default: 507 t.err = skipError("skipped; unknown pattern: " + action) 508 return 509 } 510 511 // collect flags 512 for len(args) > 0 && strings.HasPrefix(args[0], "-") { 513 switch args[0] { 514 case "-0": 515 wantError = false 516 case "-s": 517 singlefilepkgs = true 518 case "-t": // timeout in seconds 519 args = args[1:] 520 var err error 521 tim, err = strconv.Atoi(args[0]) 522 if err != nil { 523 t.err = fmt.Errorf("need number of seconds for -t timeout, got %s instead", args[0]) 524 } 525 526 default: 527 flags = append(flags, args[0]) 528 } 529 args = args[1:] 530 } 531 532 t.makeTempDir() 533 if !*keep { 534 defer os.RemoveAll(t.tempDir) 535 } 536 537 err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644) 538 check(err) 539 540 // A few tests (of things like the environment) require these to be set. 541 if os.Getenv("GOOS") == "" { 542 os.Setenv("GOOS", runtime.GOOS) 543 } 544 if os.Getenv("GOARCH") == "" { 545 os.Setenv("GOARCH", runtime.GOARCH) 546 } 547 548 useTmp := true 549 runcmd := func(args ...string) ([]byte, error) { 550 cmd := exec.Command(args[0], args[1:]...) 551 var buf bytes.Buffer 552 cmd.Stdout = &buf 553 cmd.Stderr = &buf 554 if useTmp { 555 cmd.Dir = t.tempDir 556 cmd.Env = envForDir(cmd.Dir) 557 } else { 558 cmd.Env = os.Environ() 559 } 560 561 var err error 562 563 if tim != 0 { 564 err = cmd.Start() 565 // This command-timeout code adapted from cmd/go/test.go 566 if err == nil { 567 tick := time.NewTimer(time.Duration(tim) * time.Second) 568 done := make(chan error) 569 go func() { 570 done <- cmd.Wait() 571 }() 572 select { 573 case err = <-done: 574 // ok 575 case <-tick.C: 576 cmd.Process.Kill() 577 err = <-done 578 // err = errors.New("Test timeout") 579 } 580 tick.Stop() 581 } 582 } else { 583 err = cmd.Run() 584 } 585 if err != nil { 586 err = fmt.Errorf("%s\n%s", err, buf.Bytes()) 587 } 588 return buf.Bytes(), err 589 } 590 591 long := filepath.Join(cwd, t.goFileName()) 592 switch action { 593 default: 594 t.err = fmt.Errorf("unimplemented action %q", action) 595 596 case "errorcheck": 597 // TODO(gri) remove need for -C (disable printing of columns in error messages) 598 cmdline := []string{"go", "tool", "compile", "-C", "-e", "-o", "a.o"} 599 // No need to add -dynlink even if linkshared if we're just checking for errors... 600 cmdline = append(cmdline, flags...) 601 cmdline = append(cmdline, long) 602 out, err := runcmd(cmdline...) 603 if wantError { 604 if err == nil { 605 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 606 return 607 } 608 } else { 609 if err != nil { 610 t.err = err 611 return 612 } 613 } 614 if *updateErrors { 615 t.updateErrors(string(out), long) 616 } 617 t.err = t.errorCheck(string(out), wantAuto, long, t.gofile) 618 return 619 620 case "compile": 621 _, t.err = compileFile(runcmd, long, flags) 622 623 case "compiledir": 624 // Compile all files in the directory in lexicographic order. 625 longdir := filepath.Join(cwd, t.goDirName()) 626 pkgs, err := goDirPackages(longdir, singlefilepkgs) 627 if err != nil { 628 t.err = err 629 return 630 } 631 for _, gofiles := range pkgs { 632 _, t.err = compileInDir(runcmd, longdir, flags, gofiles...) 633 if t.err != nil { 634 return 635 } 636 } 637 638 case "errorcheckdir", "errorcheckandrundir": 639 // errorcheck all files in lexicographic order 640 // useful for finding importing errors 641 longdir := filepath.Join(cwd, t.goDirName()) 642 pkgs, err := goDirPackages(longdir, singlefilepkgs) 643 if err != nil { 644 t.err = err 645 return 646 } 647 for i, gofiles := range pkgs { 648 out, err := compileInDir(runcmd, longdir, flags, gofiles...) 649 if i == len(pkgs)-1 { 650 if wantError && err == nil { 651 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 652 return 653 } else if !wantError && err != nil { 654 t.err = err 655 return 656 } 657 } else if err != nil { 658 t.err = err 659 return 660 } 661 var fullshort []string 662 for _, name := range gofiles { 663 fullshort = append(fullshort, filepath.Join(longdir, name), name) 664 } 665 t.err = t.errorCheck(string(out), wantAuto, fullshort...) 666 if t.err != nil { 667 break 668 } 669 } 670 if action == "errorcheckdir" { 671 return 672 } 673 fallthrough 674 675 case "rundir": 676 // Compile all files in the directory in lexicographic order. 677 // then link as if the last file is the main package and run it 678 longdir := filepath.Join(cwd, t.goDirName()) 679 pkgs, err := goDirPackages(longdir, singlefilepkgs) 680 if err != nil { 681 t.err = err 682 return 683 } 684 for i, gofiles := range pkgs { 685 _, err := compileInDir(runcmd, longdir, flags, gofiles...) 686 if err != nil { 687 t.err = err 688 return 689 } 690 if i == len(pkgs)-1 { 691 err = linkFile(runcmd, gofiles[0]) 692 if err != nil { 693 t.err = err 694 return 695 } 696 var cmd []string 697 cmd = append(cmd, findExecCmd()...) 698 cmd = append(cmd, filepath.Join(t.tempDir, "a.exe")) 699 cmd = append(cmd, args...) 700 out, err := runcmd(cmd...) 701 if err != nil { 702 t.err = err 703 return 704 } 705 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 706 t.err = fmt.Errorf("incorrect output\n%s", out) 707 } 708 } 709 } 710 711 case "build": 712 _, err := runcmd("go", "build", goGcflags(), "-o", "a.exe", long) 713 if err != nil { 714 t.err = err 715 } 716 717 case "builddir": 718 // Build an executable from all the .go and .s files in a subdirectory. 719 useTmp = true 720 longdir := filepath.Join(cwd, t.goDirName()) 721 files, dirErr := ioutil.ReadDir(longdir) 722 if dirErr != nil { 723 t.err = dirErr 724 break 725 } 726 var gos []os.FileInfo 727 var asms []os.FileInfo 728 for _, file := range files { 729 switch filepath.Ext(file.Name()) { 730 case ".go": 731 gos = append(gos, file) 732 case ".s": 733 asms = append(asms, file) 734 } 735 736 } 737 var objs []string 738 cmd := []string{"go", "tool", "compile", "-e", "-D", ".", "-I", ".", "-o", "go.o"} 739 if len(asms) > 0 { 740 cmd = append(cmd, "-asmhdr", "go_asm.h") 741 } 742 for _, file := range gos { 743 cmd = append(cmd, filepath.Join(longdir, file.Name())) 744 } 745 _, err := runcmd(cmd...) 746 if err != nil { 747 t.err = err 748 break 749 } 750 objs = append(objs, "go.o") 751 if len(asms) > 0 { 752 cmd = []string{"go", "tool", "asm", "-e", "-I", ".", "-o", "asm.o"} 753 for _, file := range asms { 754 cmd = append(cmd, filepath.Join(longdir, file.Name())) 755 } 756 _, err = runcmd(cmd...) 757 if err != nil { 758 t.err = err 759 break 760 } 761 objs = append(objs, "asm.o") 762 } 763 cmd = []string{"go", "tool", "pack", "c", "all.a"} 764 cmd = append(cmd, objs...) 765 _, err = runcmd(cmd...) 766 if err != nil { 767 t.err = err 768 break 769 } 770 cmd = []string{"go", "tool", "link", "all.a"} 771 _, err = runcmd(cmd...) 772 if err != nil { 773 t.err = err 774 break 775 } 776 777 case "buildrun": // build binary, then run binary, instead of go run. Useful for timeout tests where failure mode is infinite loop. 778 // TODO: not supported on NaCl 779 useTmp = true 780 cmd := []string{"go", "build", goGcflags(), "-o", "a.exe"} 781 if *linkshared { 782 cmd = append(cmd, "-linkshared") 783 } 784 longdirgofile := filepath.Join(filepath.Join(cwd, t.dir), t.gofile) 785 cmd = append(cmd, flags...) 786 cmd = append(cmd, longdirgofile) 787 out, err := runcmd(cmd...) 788 if err != nil { 789 t.err = err 790 return 791 } 792 cmd = []string{"./a.exe"} 793 out, err = runcmd(append(cmd, args...)...) 794 if err != nil { 795 t.err = err 796 return 797 } 798 799 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 800 t.err = fmt.Errorf("incorrect output\n%s", out) 801 } 802 803 case "run": 804 useTmp = false 805 var out []byte 806 var err error 807 if len(flags)+len(args) == 0 && goGcflags() == "" && !*linkshared { 808 // If we're not using special go command flags, 809 // skip all the go command machinery. 810 // This avoids any time the go command would 811 // spend checking whether, for example, the installed 812 // package runtime is up to date. 813 // Because we run lots of trivial test programs, 814 // the time adds up. 815 pkg := filepath.Join(t.tempDir, "pkg.a") 816 if _, err := runcmd("go", "tool", "compile", "-o", pkg, t.goFileName()); err != nil { 817 t.err = err 818 return 819 } 820 exe := filepath.Join(t.tempDir, "test.exe") 821 cmd := []string{"go", "tool", "link", "-s", "-w"} 822 cmd = append(cmd, "-o", exe, pkg) 823 if _, err := runcmd(cmd...); err != nil { 824 t.err = err 825 return 826 } 827 out, err = runcmd(append([]string{exe}, args...)...) 828 } else { 829 cmd := []string{"go", "run", goGcflags()} 830 if *linkshared { 831 cmd = append(cmd, "-linkshared") 832 } 833 cmd = append(cmd, flags...) 834 cmd = append(cmd, t.goFileName()) 835 out, err = runcmd(append(cmd, args...)...) 836 } 837 if err != nil { 838 t.err = err 839 return 840 } 841 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 842 t.err = fmt.Errorf("incorrect output\n%s", out) 843 } 844 845 case "runoutput": 846 rungatec <- true 847 defer func() { 848 <-rungatec 849 }() 850 useTmp = false 851 cmd := []string{"go", "run", goGcflags()} 852 if *linkshared { 853 cmd = append(cmd, "-linkshared") 854 } 855 cmd = append(cmd, t.goFileName()) 856 out, err := runcmd(append(cmd, args...)...) 857 if err != nil { 858 t.err = err 859 return 860 } 861 tfile := filepath.Join(t.tempDir, "tmp__.go") 862 if err := ioutil.WriteFile(tfile, out, 0666); err != nil { 863 t.err = fmt.Errorf("write tempfile:%s", err) 864 return 865 } 866 cmd = []string{"go", "run", goGcflags()} 867 if *linkshared { 868 cmd = append(cmd, "-linkshared") 869 } 870 cmd = append(cmd, tfile) 871 out, err = runcmd(cmd...) 872 if err != nil { 873 t.err = err 874 return 875 } 876 if string(out) != t.expectedOutput() { 877 t.err = fmt.Errorf("incorrect output\n%s", out) 878 } 879 880 case "errorcheckoutput": 881 useTmp = false 882 cmd := []string{"go", "run", goGcflags()} 883 if *linkshared { 884 cmd = append(cmd, "-linkshared") 885 } 886 cmd = append(cmd, t.goFileName()) 887 out, err := runcmd(append(cmd, args...)...) 888 if err != nil { 889 t.err = err 890 return 891 } 892 tfile := filepath.Join(t.tempDir, "tmp__.go") 893 err = ioutil.WriteFile(tfile, out, 0666) 894 if err != nil { 895 t.err = fmt.Errorf("write tempfile:%s", err) 896 return 897 } 898 cmdline := []string{"go", "tool", "compile", "-e", "-o", "a.o"} 899 cmdline = append(cmdline, flags...) 900 cmdline = append(cmdline, tfile) 901 out, err = runcmd(cmdline...) 902 if wantError { 903 if err == nil { 904 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 905 return 906 } 907 } else { 908 if err != nil { 909 t.err = err 910 return 911 } 912 } 913 t.err = t.errorCheck(string(out), false, tfile, "tmp__.go") 914 return 915 } 916 } 917 918 var execCmd []string 919 920 func findExecCmd() []string { 921 if execCmd != nil { 922 return execCmd 923 } 924 execCmd = []string{} // avoid work the second time 925 if goos == runtime.GOOS && goarch == runtime.GOARCH { 926 return execCmd 927 } 928 path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch)) 929 if err == nil { 930 execCmd = []string{path} 931 } 932 return execCmd 933 } 934 935 func (t *test) String() string { 936 return filepath.Join(t.dir, t.gofile) 937 } 938 939 func (t *test) makeTempDir() { 940 var err error 941 t.tempDir, err = ioutil.TempDir("", "") 942 check(err) 943 if *keep { 944 log.Printf("Temporary directory is %s", t.tempDir) 945 } 946 } 947 948 func (t *test) expectedOutput() string { 949 filename := filepath.Join(t.dir, t.gofile) 950 filename = filename[:len(filename)-len(".go")] 951 filename += ".out" 952 b, _ := ioutil.ReadFile(filename) 953 return string(b) 954 } 955 956 func splitOutput(out string, wantAuto bool) []string { 957 // gc error messages continue onto additional lines with leading tabs. 958 // Split the output at the beginning of each line that doesn't begin with a tab. 959 // <autogenerated> lines are impossible to match so those are filtered out. 960 var res []string 961 for _, line := range strings.Split(out, "\n") { 962 if strings.HasSuffix(line, "\r") { // remove '\r', output by compiler on windows 963 line = line[:len(line)-1] 964 } 965 if strings.HasPrefix(line, "\t") { 966 res[len(res)-1] += "\n" + line 967 } else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") { 968 continue 969 } else if strings.TrimSpace(line) != "" { 970 res = append(res, line) 971 } 972 } 973 return res 974 } 975 976 func (t *test) errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) { 977 defer func() { 978 if *verbose && err != nil { 979 log.Printf("%s gc output:\n%s", t, outStr) 980 } 981 }() 982 var errs []error 983 out := splitOutput(outStr, wantAuto) 984 985 // Cut directory name. 986 for i := range out { 987 for j := 0; j < len(fullshort); j += 2 { 988 full, short := fullshort[j], fullshort[j+1] 989 out[i] = strings.Replace(out[i], full, short, -1) 990 } 991 } 992 993 var want []wantedError 994 for j := 0; j < len(fullshort); j += 2 { 995 full, short := fullshort[j], fullshort[j+1] 996 want = append(want, t.wantedErrors(full, short)...) 997 } 998 999 for _, we := range want { 1000 var errmsgs []string 1001 if we.auto { 1002 errmsgs, out = partitionStrings("<autogenerated>", out) 1003 } else { 1004 errmsgs, out = partitionStrings(we.prefix, out) 1005 } 1006 if len(errmsgs) == 0 { 1007 errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr)) 1008 continue 1009 } 1010 matched := false 1011 n := len(out) 1012 for _, errmsg := range errmsgs { 1013 // Assume errmsg says "file:line: foo". 1014 // Cut leading "file:line: " to avoid accidental matching of file name instead of message. 1015 text := errmsg 1016 if i := strings.Index(text, " "); i >= 0 { 1017 text = text[i+1:] 1018 } 1019 if we.re.MatchString(text) { 1020 matched = true 1021 } else { 1022 out = append(out, errmsg) 1023 } 1024 } 1025 if !matched { 1026 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"))) 1027 continue 1028 } 1029 } 1030 1031 if len(out) > 0 { 1032 errs = append(errs, fmt.Errorf("Unmatched Errors:")) 1033 for _, errLine := range out { 1034 errs = append(errs, fmt.Errorf("%s", errLine)) 1035 } 1036 } 1037 1038 if len(errs) == 0 { 1039 return nil 1040 } 1041 if len(errs) == 1 { 1042 return errs[0] 1043 } 1044 var buf bytes.Buffer 1045 fmt.Fprintf(&buf, "\n") 1046 for _, err := range errs { 1047 fmt.Fprintf(&buf, "%s\n", err.Error()) 1048 } 1049 return errors.New(buf.String()) 1050 } 1051 1052 func (t *test) updateErrors(out, file string) { 1053 base := path.Base(file) 1054 // Read in source file. 1055 src, err := ioutil.ReadFile(file) 1056 if err != nil { 1057 fmt.Fprintln(os.Stderr, err) 1058 return 1059 } 1060 lines := strings.Split(string(src), "\n") 1061 // Remove old errors. 1062 for i, ln := range lines { 1063 pos := strings.Index(ln, " // ERROR ") 1064 if pos >= 0 { 1065 lines[i] = ln[:pos] 1066 } 1067 } 1068 // Parse new errors. 1069 errors := make(map[int]map[string]bool) 1070 tmpRe := regexp.MustCompile(`autotmp_[0-9]+`) 1071 for _, errStr := range splitOutput(out, false) { 1072 colon1 := strings.Index(errStr, ":") 1073 if colon1 < 0 || errStr[:colon1] != file { 1074 continue 1075 } 1076 colon2 := strings.Index(errStr[colon1+1:], ":") 1077 if colon2 < 0 { 1078 continue 1079 } 1080 colon2 += colon1 + 1 1081 line, err := strconv.Atoi(errStr[colon1+1 : colon2]) 1082 line-- 1083 if err != nil || line < 0 || line >= len(lines) { 1084 continue 1085 } 1086 msg := errStr[colon2+2:] 1087 msg = strings.Replace(msg, file, base, -1) // normalize file mentions in error itself 1088 msg = strings.TrimLeft(msg, " \t") 1089 for _, r := range []string{`\`, `*`, `+`, `[`, `]`, `(`, `)`} { 1090 msg = strings.Replace(msg, r, `\`+r, -1) 1091 } 1092 msg = strings.Replace(msg, `"`, `.`, -1) 1093 msg = tmpRe.ReplaceAllLiteralString(msg, `autotmp_[0-9]+`) 1094 if errors[line] == nil { 1095 errors[line] = make(map[string]bool) 1096 } 1097 errors[line][msg] = true 1098 } 1099 // Add new errors. 1100 for line, errs := range errors { 1101 var sorted []string 1102 for e := range errs { 1103 sorted = append(sorted, e) 1104 } 1105 sort.Strings(sorted) 1106 lines[line] += " // ERROR" 1107 for _, e := range sorted { 1108 lines[line] += fmt.Sprintf(` "%s$"`, e) 1109 } 1110 } 1111 // Write new file. 1112 err = ioutil.WriteFile(file, []byte(strings.Join(lines, "\n")), 0640) 1113 if err != nil { 1114 fmt.Fprintln(os.Stderr, err) 1115 return 1116 } 1117 // Polish. 1118 exec.Command("go", "fmt", file).CombinedOutput() 1119 } 1120 1121 // matchPrefix reports whether s is of the form ^(.*/)?prefix(:|[), 1122 // That is, it needs the file name prefix followed by a : or a [, 1123 // and possibly preceded by a directory name. 1124 func matchPrefix(s, prefix string) bool { 1125 i := strings.Index(s, ":") 1126 if i < 0 { 1127 return false 1128 } 1129 j := strings.LastIndex(s[:i], "/") 1130 s = s[j+1:] 1131 if len(s) <= len(prefix) || s[:len(prefix)] != prefix { 1132 return false 1133 } 1134 switch s[len(prefix)] { 1135 case '[', ':': 1136 return true 1137 } 1138 return false 1139 } 1140 1141 func partitionStrings(prefix string, strs []string) (matched, unmatched []string) { 1142 for _, s := range strs { 1143 if matchPrefix(s, prefix) { 1144 matched = append(matched, s) 1145 } else { 1146 unmatched = append(unmatched, s) 1147 } 1148 } 1149 return 1150 } 1151 1152 type wantedError struct { 1153 reStr string 1154 re *regexp.Regexp 1155 lineNum int 1156 auto bool // match <autogenerated> line 1157 file string 1158 prefix string 1159 } 1160 1161 var ( 1162 errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`) 1163 errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`) 1164 errQuotesRx = regexp.MustCompile(`"([^"]*)"`) 1165 lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`) 1166 ) 1167 1168 func (t *test) wantedErrors(file, short string) (errs []wantedError) { 1169 cache := make(map[string]*regexp.Regexp) 1170 1171 src, _ := ioutil.ReadFile(file) 1172 for i, line := range strings.Split(string(src), "\n") { 1173 lineNum := i + 1 1174 if strings.Contains(line, "////") { 1175 // double comment disables ERROR 1176 continue 1177 } 1178 var auto bool 1179 m := errAutoRx.FindStringSubmatch(line) 1180 if m != nil { 1181 auto = true 1182 } else { 1183 m = errRx.FindStringSubmatch(line) 1184 } 1185 if m == nil { 1186 continue 1187 } 1188 all := m[1] 1189 mm := errQuotesRx.FindAllStringSubmatch(all, -1) 1190 if mm == nil { 1191 log.Fatalf("%s:%d: invalid errchk line: %s", t.goFileName(), lineNum, line) 1192 } 1193 for _, m := range mm { 1194 rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string { 1195 n := lineNum 1196 if strings.HasPrefix(m, "LINE+") { 1197 delta, _ := strconv.Atoi(m[5:]) 1198 n += delta 1199 } else if strings.HasPrefix(m, "LINE-") { 1200 delta, _ := strconv.Atoi(m[5:]) 1201 n -= delta 1202 } 1203 return fmt.Sprintf("%s:%d", short, n) 1204 }) 1205 re := cache[rx] 1206 if re == nil { 1207 var err error 1208 re, err = regexp.Compile(rx) 1209 if err != nil { 1210 log.Fatalf("%s:%d: invalid regexp \"%s\" in ERROR line: %v", t.goFileName(), lineNum, rx, err) 1211 } 1212 cache[rx] = re 1213 } 1214 prefix := fmt.Sprintf("%s:%d", short, lineNum) 1215 errs = append(errs, wantedError{ 1216 reStr: rx, 1217 re: re, 1218 prefix: prefix, 1219 auto: auto, 1220 lineNum: lineNum, 1221 file: short, 1222 }) 1223 } 1224 } 1225 1226 return 1227 } 1228 1229 // defaultRunOutputLimit returns the number of runoutput tests that 1230 // can be executed in parallel. 1231 func defaultRunOutputLimit() int { 1232 const maxArmCPU = 2 1233 1234 cpu := runtime.NumCPU() 1235 if runtime.GOARCH == "arm" && cpu > maxArmCPU { 1236 cpu = maxArmCPU 1237 } 1238 return cpu 1239 } 1240 1241 // checkShouldTest runs sanity checks on the shouldTest function. 1242 func checkShouldTest() { 1243 assert := func(ok bool, _ string) { 1244 if !ok { 1245 panic("fail") 1246 } 1247 } 1248 assertNot := func(ok bool, _ string) { assert(!ok, "") } 1249 1250 // Simple tests. 1251 assert(shouldTest("// +build linux", "linux", "arm")) 1252 assert(shouldTest("// +build !windows", "linux", "arm")) 1253 assertNot(shouldTest("// +build !windows", "windows", "amd64")) 1254 1255 // A file with no build tags will always be tested. 1256 assert(shouldTest("// This is a test.", "os", "arch")) 1257 1258 // Build tags separated by a space are OR-ed together. 1259 assertNot(shouldTest("// +build arm 386", "linux", "amd64")) 1260 1261 // Build tags separated by a comma are AND-ed together. 1262 assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64")) 1263 assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386")) 1264 1265 // Build tags on multiple lines are AND-ed together. 1266 assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64")) 1267 assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64")) 1268 1269 // Test that (!a OR !b) matches anything. 1270 assert(shouldTest("// +build !windows !plan9", "windows", "amd64")) 1271 } 1272 1273 // envForDir returns a copy of the environment 1274 // suitable for running in the given directory. 1275 // The environment is the current process's environment 1276 // but with an updated $PWD, so that an os.Getwd in the 1277 // child will be faster. 1278 func envForDir(dir string) []string { 1279 env := os.Environ() 1280 for i, kv := range env { 1281 if strings.HasPrefix(kv, "PWD=") { 1282 env[i] = "PWD=" + dir 1283 return env 1284 } 1285 } 1286 env = append(env, "PWD="+dir) 1287 return env 1288 } 1289 1290 func getenv(key, def string) string { 1291 value := os.Getenv(key) 1292 if value != "" { 1293 return value 1294 } 1295 return def 1296 }