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. 8package main 9 10import ( 11 "bytes" 12 "errors" 13 "flag" 14 "fmt" 15 "hash/fnv" 16 "io" 17 "io/fs" 18 "io/ioutil" 19 "log" 20 "os" 21 "os/exec" 22 "path" 23 "path/filepath" 24 "regexp" 25 "runtime" 26 "sort" 27 "strconv" 28 "strings" 29 "time" 30 "unicode" 31) 32 33var ( 34 verbose = flag.Bool("v", false, "verbose. if set, parallelism is set to 1.") 35 keep = flag.Bool("k", false, "keep. keep temporary directory.") 36 numParallel = flag.Int("n", runtime.NumCPU(), "number of parallel tests to run") 37 summary = flag.Bool("summary", false, "show summary of results") 38 allCodegen = flag.Bool("all_codegen", defaultAllCodeGen(), "run all goos/goarch for codegen") 39 showSkips = flag.Bool("show_skips", false, "show skipped tests") 40 runSkips = flag.Bool("run_skips", false, "run skipped tests (ignore skip and build tags)") 41 linkshared = flag.Bool("linkshared", false, "") 42 updateErrors = flag.Bool("update_errors", false, "update error messages in test file based on compiler output") 43 runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run") 44 45 shard = flag.Int("shard", 0, "shard index to run. Only applicable if -shards is non-zero.") 46 shards = flag.Int("shards", 0, "number of shards. If 0, all tests are run. This is used by the continuous build.") 47) 48 49// defaultAllCodeGen returns the default value of the -all_codegen 50// flag. By default, we prefer to be fast (returning false), except on 51// the linux-amd64 builder that's already very fast, so we get more 52// test coverage on trybots. See https://golang.org/issue/34297. 53func defaultAllCodeGen() bool { 54 return os.Getenv("GO_BUILDER_NAME") == "linux-amd64" 55} 56 57var ( 58 goos, goarch string 59 60 // dirs are the directories to look for *.go files in. 61 // TODO(bradfitz): just use all directories? 62 dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "codegen", "runtime"} 63 64 // ratec controls the max number of tests running at a time. 65 ratec chan bool 66 67 // toRun is the channel of tests to run. 68 // It is nil until the first test is started. 69 toRun chan *test 70 71 // rungatec controls the max number of runoutput tests 72 // executed in parallel as they can each consume a lot of memory. 73 rungatec chan bool 74) 75 76// maxTests is an upper bound on the total number of tests. 77// It is used as a channel buffer size to make sure sends don't block. 78const maxTests = 5000 79 80func main() { 81 flag.Parse() 82 83 goos = getenv("GOOS", runtime.GOOS) 84 goarch = getenv("GOARCH", runtime.GOARCH) 85 86 findExecCmd() 87 88 // Disable parallelism if printing or if using a simulator. 89 if *verbose || len(findExecCmd()) > 0 { 90 *numParallel = 1 91 *runoutputLimit = 1 92 } 93 94 ratec = make(chan bool, *numParallel) 95 rungatec = make(chan bool, *runoutputLimit) 96 97 var tests []*test 98 if flag.NArg() > 0 { 99 for _, arg := range flag.Args() { 100 if arg == "-" || arg == "--" { 101 // Permit running: 102 // $ go run run.go - env.go 103 // $ go run run.go -- env.go 104 // $ go run run.go - ./fixedbugs 105 // $ go run run.go -- ./fixedbugs 106 continue 107 } 108 if fi, err := os.Stat(arg); err == nil && fi.IsDir() { 109 for _, baseGoFile := range goFiles(arg) { 110 tests = append(tests, startTest(arg, baseGoFile)) 111 } 112 } else if strings.HasSuffix(arg, ".go") { 113 dir, file := filepath.Split(arg) 114 tests = append(tests, startTest(dir, file)) 115 } else { 116 log.Fatalf("can't yet deal with non-directory and non-go file %q", arg) 117 } 118 } 119 } else { 120 for _, dir := range dirs { 121 for _, baseGoFile := range goFiles(dir) { 122 tests = append(tests, startTest(dir, baseGoFile)) 123 } 124 } 125 } 126 127 failed := false 128 resCount := map[string]int{} 129 for _, test := range tests { 130 <-test.donec 131 status := "ok " 132 errStr := "" 133 if e, isSkip := test.err.(skipError); isSkip { 134 test.err = nil 135 errStr = "unexpected skip for " + path.Join(test.dir, test.gofile) + ": " + string(e) 136 status = "FAIL" 137 } 138 if test.err != nil { 139 status = "FAIL" 140 errStr = test.err.Error() 141 } 142 if status == "FAIL" { 143 failed = true 144 } 145 resCount[status]++ 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// goTool reports the path of the go tool to use to run the tests. 171// If possible, use the same Go used to run run.go, otherwise 172// fallback to the go version found in the PATH. 173func goTool() string { 174 var exeSuffix string 175 if runtime.GOOS == "windows" { 176 exeSuffix = ".exe" 177 } 178 path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix) 179 if _, err := os.Stat(path); err == nil { 180 return path 181 } 182 // Just run "go" from PATH 183 return "go" 184} 185 186func shardMatch(name string) bool { 187 if *shards == 0 { 188 return true 189 } 190 h := fnv.New32() 191 io.WriteString(h, name) 192 return int(h.Sum32()%uint32(*shards)) == *shard 193} 194 195func goFiles(dir string) []string { 196 f, err := os.Open(dir) 197 if err != nil { 198 log.Fatal(err) 199 } 200 dirnames, err := f.Readdirnames(-1) 201 f.Close() 202 if err != nil { 203 log.Fatal(err) 204 } 205 names := []string{} 206 for _, name := range dirnames { 207 if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && shardMatch(name) { 208 names = append(names, name) 209 } 210 } 211 sort.Strings(names) 212 return names 213} 214 215type runCmd func(...string) ([]byte, error) 216 217func compileFile(runcmd runCmd, longname string, flags []string) (out []byte, err error) { 218 cmd := []string{goTool(), "tool", "compile", "-e"} 219 cmd = append(cmd, flags...) 220 if *linkshared { 221 cmd = append(cmd, "-dynlink", "-installsuffix=dynlink") 222 } 223 cmd = append(cmd, longname) 224 return runcmd(cmd...) 225} 226 227func compileInDir(runcmd runCmd, dir string, flags []string, localImports bool, names ...string) (out []byte, err error) { 228 cmd := []string{goTool(), "tool", "compile", "-e"} 229 if localImports { 230 // Set relative path for local imports and import search path to current dir. 231 cmd = append(cmd, "-D", ".", "-I", ".") 232 } 233 cmd = append(cmd, flags...) 234 if *linkshared { 235 cmd = append(cmd, "-dynlink", "-installsuffix=dynlink") 236 } 237 for _, name := range names { 238 cmd = append(cmd, filepath.Join(dir, name)) 239 } 240 return runcmd(cmd...) 241} 242 243func linkFile(runcmd runCmd, goname string, ldflags []string) (err error) { 244 pfile := strings.Replace(goname, ".go", ".o", -1) 245 cmd := []string{goTool(), "tool", "link", "-w", "-o", "a.exe", "-L", "."} 246 if *linkshared { 247 cmd = append(cmd, "-linkshared", "-installsuffix=dynlink") 248 } 249 if ldflags != nil { 250 cmd = append(cmd, ldflags...) 251 } 252 cmd = append(cmd, pfile) 253 _, err = runcmd(cmd...) 254 return 255} 256 257// skipError describes why a test was skipped. 258type skipError string 259 260func (s skipError) Error() string { return string(s) } 261 262// test holds the state of a test. 263type test struct { 264 dir, gofile string 265 donec chan bool // closed when done 266 dt time.Duration 267 268 src string 269 270 tempDir string 271 err error 272} 273 274// startTest 275func startTest(dir, gofile string) *test { 276 t := &test{ 277 dir: dir, 278 gofile: gofile, 279 donec: make(chan bool, 1), 280 } 281 if toRun == nil { 282 toRun = make(chan *test, maxTests) 283 go runTests() 284 } 285 select { 286 case toRun <- t: 287 default: 288 panic("toRun buffer size (maxTests) is too small") 289 } 290 return t 291} 292 293// runTests runs tests in parallel, but respecting the order they 294// were enqueued on the toRun channel. 295func runTests() { 296 for { 297 ratec <- true 298 t := <-toRun 299 go func() { 300 t.run() 301 <-ratec 302 }() 303 } 304} 305 306var cwd, _ = os.Getwd() 307 308func (t *test) goFileName() string { 309 return filepath.Join(t.dir, t.gofile) 310} 311 312func (t *test) goDirName() string { 313 return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1)) 314} 315 316func goDirFiles(longdir string) (filter []os.FileInfo, err error) { 317 files, dirErr := ioutil.ReadDir(longdir) 318 if dirErr != nil { 319 return nil, dirErr 320 } 321 for _, gofile := range files { 322 if filepath.Ext(gofile.Name()) == ".go" { 323 filter = append(filter, gofile) 324 } 325 } 326 return 327} 328 329var packageRE = regexp.MustCompile(`(?m)^package ([\p{Lu}\p{Ll}\w]+)`) 330 331func getPackageNameFromSource(fn string) (string, error) { 332 data, err := ioutil.ReadFile(fn) 333 if err != nil { 334 return "", err 335 } 336 pkgname := packageRE.FindStringSubmatch(string(data)) 337 if pkgname == nil { 338 return "", fmt.Errorf("cannot find package name in %s", fn) 339 } 340 return pkgname[1], nil 341} 342 343// If singlefilepkgs is set, each file is considered a separate package 344// even if the package names are the same. 345func goDirPackages(longdir string, singlefilepkgs bool) ([][]string, error) { 346 files, err := goDirFiles(longdir) 347 if err != nil { 348 return nil, err 349 } 350 var pkgs [][]string 351 m := make(map[string]int) 352 for _, file := range files { 353 name := file.Name() 354 pkgname, err := getPackageNameFromSource(filepath.Join(longdir, name)) 355 if err != nil { 356 log.Fatal(err) 357 } 358 i, ok := m[pkgname] 359 if singlefilepkgs || !ok { 360 i = len(pkgs) 361 pkgs = append(pkgs, nil) 362 m[pkgname] = i 363 } 364 pkgs[i] = append(pkgs[i], name) 365 } 366 return pkgs, nil 367} 368 369type context struct { 370 GOOS string 371 GOARCH string 372 noOptEnv bool 373} 374 375// shouldTest looks for build tags in a source file and returns 376// whether the file should be used according to the tags. 377func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) { 378 if *runSkips { 379 return true, "" 380 } 381 for _, line := range strings.Split(src, "\n") { 382 line = strings.TrimSpace(line) 383 if strings.HasPrefix(line, "//") { 384 line = line[2:] 385 } else { 386 continue 387 } 388 line = strings.TrimSpace(line) 389 if len(line) == 0 || line[0] != '+' { 390 continue 391 } 392 gcFlags := os.Getenv("GO_GCFLAGS") 393 ctxt := &context{ 394 GOOS: goos, 395 GOARCH: goarch, 396 noOptEnv: strings.Contains(gcFlags, "-N") || strings.Contains(gcFlags, "-l"), 397 } 398 399 words := strings.Fields(line) 400 if words[0] == "+build" { 401 ok := false 402 for _, word := range words[1:] { 403 if ctxt.match(word) { 404 ok = true 405 break 406 } 407 } 408 if !ok { 409 // no matching tag found. 410 return false, line 411 } 412 } 413 } 414 // no build tags 415 return true, "" 416} 417 418func (ctxt *context) match(name string) bool { 419 if name == "" { 420 return false 421 } 422 if i := strings.Index(name, ","); i >= 0 { 423 // comma-separated list 424 return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) 425 } 426 if strings.HasPrefix(name, "!!") { // bad syntax, reject always 427 return false 428 } 429 if strings.HasPrefix(name, "!") { // negation 430 return len(name) > 1 && !ctxt.match(name[1:]) 431 } 432 433 // Tags must be letters, digits, underscores or dots. 434 // Unlike in Go identifiers, all digits are fine (e.g., "386"). 435 for _, c := range name { 436 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { 437 return false 438 } 439 } 440 441 if name == ctxt.GOOS || name == ctxt.GOARCH || name == "gc" { 442 return true 443 } 444 445 if ctxt.noOptEnv && name == "gcflags_noopt" { 446 return true 447 } 448 449 if name == "test_run" { 450 return true 451 } 452 453 return false 454} 455 456func init() { checkShouldTest() } 457 458// goGcflags returns the -gcflags argument to use with go build / go run. 459// This must match the flags used for building the standard library, 460// or else the commands will rebuild any needed packages (like runtime) 461// over and over. 462func goGcflags() string { 463 return "-gcflags=all=" + os.Getenv("GO_GCFLAGS") 464} 465 466func goGcflagsIsEmpty() bool { 467 return "" == os.Getenv("GO_GCFLAGS") 468} 469 470// run runs a test. 471func (t *test) run() { 472 start := time.Now() 473 defer func() { 474 t.dt = time.Since(start) 475 close(t.donec) 476 }() 477 478 srcBytes, err := ioutil.ReadFile(t.goFileName()) 479 if err != nil { 480 t.err = err 481 return 482 } 483 t.src = string(srcBytes) 484 if t.src[0] == '\n' { 485 t.err = skipError("starts with newline") 486 return 487 } 488 489 // Execution recipe stops at first blank line. 490 pos := strings.Index(t.src, "\n\n") 491 if pos == -1 { 492 t.err = errors.New("double newline not found") 493 return 494 } 495 action := t.src[:pos] 496 if nl := strings.Index(action, "\n"); nl >= 0 && strings.Contains(action[:nl], "+build") { 497 // skip first line 498 action = action[nl+1:] 499 } 500 action = strings.TrimPrefix(action, "//") 501 502 // Check for build constraints only up to the actual code. 503 pkgPos := strings.Index(t.src, "\npackage") 504 if pkgPos == -1 { 505 pkgPos = pos // some files are intentionally malformed 506 } 507 if ok, why := shouldTest(t.src[:pkgPos], goos, goarch); !ok { 508 if *showSkips { 509 fmt.Printf("%-20s %-20s: %s\n", "skip", t.goFileName(), why) 510 } 511 return 512 } 513 514 var args, flags []string 515 var tim int 516 wantError := false 517 wantAuto := false 518 singlefilepkgs := false 519 setpkgpaths := false 520 localImports := true 521 f := strings.Fields(action) 522 if len(f) > 0 { 523 action = f[0] 524 args = f[1:] 525 } 526 527 // TODO: Clean up/simplify this switch statement. 528 switch action { 529 case "compile", "compiledir", "build", "builddir", "buildrundir", "run", "buildrun", "runoutput", "rundir", "runindir", "asmcheck": 530 // nothing to do 531 case "errorcheckandrundir": 532 wantError = false // should be no error if also will run 533 case "errorcheckwithauto": 534 action = "errorcheck" 535 wantAuto = true 536 wantError = true 537 case "errorcheck", "errorcheckdir", "errorcheckoutput": 538 wantError = true 539 case "skip": 540 if *runSkips { 541 break 542 } 543 return 544 default: 545 t.err = skipError("skipped; unknown pattern: " + action) 546 return 547 } 548 549 // collect flags 550 for len(args) > 0 && strings.HasPrefix(args[0], "-") { 551 switch args[0] { 552 case "-1": 553 wantError = true 554 case "-0": 555 wantError = false 556 case "-s": 557 singlefilepkgs = true 558 case "-P": 559 setpkgpaths = true 560 case "-n": 561 // Do not set relative path for local imports to current dir, 562 // e.g. do not pass -D . -I . to the compiler. 563 // Used in fixedbugs/bug345.go to allow compilation and import of local pkg. 564 // See golang.org/issue/25635 565 localImports = false 566 case "-t": // timeout in seconds 567 args = args[1:] 568 var err error 569 tim, err = strconv.Atoi(args[0]) 570 if err != nil { 571 t.err = fmt.Errorf("need number of seconds for -t timeout, got %s instead", args[0]) 572 } 573 574 default: 575 flags = append(flags, args[0]) 576 } 577 args = args[1:] 578 } 579 if action == "errorcheck" { 580 found := false 581 for i, f := range flags { 582 if strings.HasPrefix(f, "-d=") { 583 flags[i] = f + ",ssa/check/on" 584 found = true 585 break 586 } 587 } 588 if !found { 589 flags = append(flags, "-d=ssa/check/on") 590 } 591 } 592 593 t.makeTempDir() 594 if !*keep { 595 defer os.RemoveAll(t.tempDir) 596 } 597 598 err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644) 599 if err != nil { 600 log.Fatal(err) 601 } 602 603 // A few tests (of things like the environment) require these to be set. 604 if os.Getenv("GOOS") == "" { 605 os.Setenv("GOOS", runtime.GOOS) 606 } 607 if os.Getenv("GOARCH") == "" { 608 os.Setenv("GOARCH", runtime.GOARCH) 609 } 610 611 var ( 612 runInDir = t.tempDir 613 tempDirIsGOPATH = false 614 ) 615 runcmd := func(args ...string) ([]byte, error) { 616 cmd := exec.Command(args[0], args[1:]...) 617 var buf bytes.Buffer 618 cmd.Stdout = &buf 619 cmd.Stderr = &buf 620 cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=") 621 if runInDir != "" { 622 cmd.Dir = runInDir 623 // Set PWD to match Dir to speed up os.Getwd in the child process. 624 cmd.Env = append(cmd.Env, "PWD="+cmd.Dir) 625 } 626 if tempDirIsGOPATH { 627 cmd.Env = append(cmd.Env, "GOPATH="+t.tempDir) 628 } 629 630 var err error 631 632 if tim != 0 { 633 err = cmd.Start() 634 // This command-timeout code adapted from cmd/go/test.go 635 if err == nil { 636 tick := time.NewTimer(time.Duration(tim) * time.Second) 637 done := make(chan error) 638 go func() { 639 done <- cmd.Wait() 640 }() 641 select { 642 case err = <-done: 643 // ok 644 case <-tick.C: 645 cmd.Process.Kill() 646 err = <-done 647 // err = errors.New("Test timeout") 648 } 649 tick.Stop() 650 } 651 } else { 652 err = cmd.Run() 653 } 654 if err != nil { 655 err = fmt.Errorf("%s\n%s", err, buf.Bytes()) 656 } 657 return buf.Bytes(), err 658 } 659 660 long := filepath.Join(cwd, t.goFileName()) 661 switch action { 662 default: 663 t.err = fmt.Errorf("unimplemented action %q", action) 664 665 case "asmcheck": 666 // Compile Go file and match the generated assembly 667 // against a set of regexps in comments. 668 ops := t.wantedAsmOpcodes(long) 669 self := runtime.GOOS + "/" + runtime.GOARCH 670 for _, env := range ops.Envs() { 671 // Only run checks relevant to the current GOOS/GOARCH, 672 // to avoid triggering a cross-compile of the runtime. 673 if string(env) != self && !strings.HasPrefix(string(env), self+"/") && !*allCodegen { 674 continue 675 } 676 // -S=2 forces outermost line numbers when disassembling inlined code. 677 cmdline := []string{"build", "-gcflags", "-S=2"} 678 679 // Append flags, but don't override -gcflags=-S=2; add to it instead. 680 for i := 0; i < len(flags); i++ { 681 flag := flags[i] 682 switch { 683 case strings.HasPrefix(flag, "-gcflags="): 684 cmdline[2] += " " + strings.TrimPrefix(flag, "-gcflags=") 685 case strings.HasPrefix(flag, "--gcflags="): 686 cmdline[2] += " " + strings.TrimPrefix(flag, "--gcflags=") 687 case flag == "-gcflags", flag == "--gcflags": 688 i++ 689 if i < len(flags) { 690 cmdline[2] += " " + flags[i] 691 } 692 default: 693 cmdline = append(cmdline, flag) 694 } 695 } 696 697 cmdline = append(cmdline, long) 698 cmd := exec.Command(goTool(), cmdline...) 699 cmd.Env = append(os.Environ(), env.Environ()...) 700 if len(flags) > 0 && flags[0] == "-race" { 701 cmd.Env = append(cmd.Env, "CGO_ENABLED=1") 702 } 703 704 var buf bytes.Buffer 705 cmd.Stdout, cmd.Stderr = &buf, &buf 706 if err := cmd.Run(); err != nil { 707 fmt.Println(env, "\n", cmd.Stderr) 708 t.err = err 709 return 710 } 711 712 t.err = t.asmCheck(buf.String(), long, env, ops[env]) 713 if t.err != nil { 714 return 715 } 716 } 717 return 718 719 case "errorcheck": 720 // Compile Go file. 721 // Fail if wantError is true and compilation was successful and vice versa. 722 // Match errors produced by gc against errors in comments. 723 // TODO(gri) remove need for -C (disable printing of columns in error messages) 724 cmdline := []string{goTool(), "tool", "compile", "-C", "-e", "-o", "a.o"} 725 // No need to add -dynlink even if linkshared if we're just checking for errors... 726 cmdline = append(cmdline, flags...) 727 cmdline = append(cmdline, long) 728 out, err := runcmd(cmdline...) 729 if wantError { 730 if err == nil { 731 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 732 return 733 } 734 } else { 735 if err != nil { 736 t.err = err 737 return 738 } 739 } 740 if *updateErrors { 741 t.updateErrors(string(out), long) 742 } 743 t.err = t.errorCheck(string(out), wantAuto, long, t.gofile) 744 return 745 746 case "compile": 747 // Compile Go file. 748 _, t.err = compileFile(runcmd, long, flags) 749 750 case "compiledir": 751 // Compile all files in the directory as packages in lexicographic order. 752 longdir := filepath.Join(cwd, t.goDirName()) 753 pkgs, err := goDirPackages(longdir, singlefilepkgs) 754 if err != nil { 755 t.err = err 756 return 757 } 758 for _, gofiles := range pkgs { 759 _, t.err = compileInDir(runcmd, longdir, flags, localImports, gofiles...) 760 if t.err != nil { 761 return 762 } 763 } 764 765 case "errorcheckdir", "errorcheckandrundir": 766 // Compile and errorCheck all files in the directory as packages in lexicographic order. 767 // If errorcheckdir and wantError, compilation of the last package must fail. 768 // If errorcheckandrundir and wantError, compilation of the package prior the last must fail. 769 longdir := filepath.Join(cwd, t.goDirName()) 770 pkgs, err := goDirPackages(longdir, singlefilepkgs) 771 if err != nil { 772 t.err = err 773 return 774 } 775 errPkg := len(pkgs) - 1 776 if wantError && action == "errorcheckandrundir" { 777 // The last pkg should compiled successfully and will be run in next case. 778 // Preceding pkg must return an error from compileInDir. 779 errPkg-- 780 } 781 for i, gofiles := range pkgs { 782 out, err := compileInDir(runcmd, longdir, flags, localImports, gofiles...) 783 if i == errPkg { 784 if wantError && err == nil { 785 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 786 return 787 } else if !wantError && err != nil { 788 t.err = err 789 return 790 } 791 } else if err != nil { 792 t.err = err 793 return 794 } 795 var fullshort []string 796 for _, name := range gofiles { 797 fullshort = append(fullshort, filepath.Join(longdir, name), name) 798 } 799 t.err = t.errorCheck(string(out), wantAuto, fullshort...) 800 if t.err != nil { 801 break 802 } 803 } 804 if action == "errorcheckdir" { 805 return 806 } 807 fallthrough 808 809 case "rundir": 810 // Compile all files in the directory as packages in lexicographic order. 811 // In case of errorcheckandrundir, ignore failed compilation of the package before the last. 812 // Link as if the last file is the main package, run it. 813 // Verify the expected output. 814 longdir := filepath.Join(cwd, t.goDirName()) 815 pkgs, err := goDirPackages(longdir, singlefilepkgs) 816 if err != nil { 817 t.err = err 818 return 819 } 820 // Split flags into gcflags and ldflags 821 ldflags := []string{} 822 for i, fl := range flags { 823 if fl == "-ldflags" { 824 ldflags = flags[i+1:] 825 flags = flags[0:i] 826 break 827 } 828 } 829 830 for i, gofiles := range pkgs { 831 pflags := []string{} 832 pflags = append(pflags, flags...) 833 if setpkgpaths { 834 fp := filepath.Join(longdir, gofiles[0]) 835 pkgname, err := getPackageNameFromSource(fp) 836 if err != nil { 837 log.Fatal(err) 838 } 839 pflags = append(pflags, "-p", pkgname) 840 } 841 _, err := compileInDir(runcmd, longdir, pflags, localImports, gofiles...) 842 // Allow this package compilation fail based on conditions below; 843 // its errors were checked in previous case. 844 if err != nil && !(wantError && action == "errorcheckandrundir" && i == len(pkgs)-2) { 845 t.err = err 846 return 847 } 848 if i == len(pkgs)-1 { 849 err = linkFile(runcmd, gofiles[0], ldflags) 850 if err != nil { 851 t.err = err 852 return 853 } 854 var cmd []string 855 cmd = append(cmd, findExecCmd()...) 856 cmd = append(cmd, filepath.Join(t.tempDir, "a.exe")) 857 cmd = append(cmd, args...) 858 out, err := runcmd(cmd...) 859 if err != nil { 860 t.err = err 861 return 862 } 863 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 864 t.err = fmt.Errorf("incorrect output\n%s", out) 865 } 866 } 867 } 868 869 case "runindir": 870 // Make a shallow copy of t.goDirName() in its own module and GOPATH, and 871 // run "go run ." in it. The module path (and hence import path prefix) of 872 // the copy is equal to the basename of the source directory. 873 // 874 // It's used when test a requires a full 'go build' in order to compile 875 // the sources, such as when importing multiple packages (issue29612.dir) 876 // or compiling a package containing assembly files (see issue15609.dir), 877 // but still needs to be run to verify the expected output. 878 tempDirIsGOPATH = true 879 srcDir := t.goDirName() 880 modName := filepath.Base(srcDir) 881 gopathSrcDir := filepath.Join(t.tempDir, "src", modName) 882 runInDir = gopathSrcDir 883 884 if err := overlayDir(gopathSrcDir, srcDir); err != nil { 885 t.err = err 886 return 887 } 888 889 modFile := fmt.Sprintf("module %s\ngo 1.14\n", modName) 890 if err := ioutil.WriteFile(filepath.Join(gopathSrcDir, "go.mod"), []byte(modFile), 0666); err != nil { 891 t.err = err 892 return 893 } 894 895 cmd := []string{goTool(), "run", goGcflags()} 896 if *linkshared { 897 cmd = append(cmd, "-linkshared") 898 } 899 cmd = append(cmd, ".") 900 out, err := runcmd(cmd...) 901 if err != nil { 902 t.err = err 903 return 904 } 905 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 906 t.err = fmt.Errorf("incorrect output\n%s", out) 907 } 908 909 case "build": 910 // Build Go file. 911 _, err := runcmd(goTool(), "build", goGcflags(), "-o", "a.exe", long) 912 if err != nil { 913 t.err = err 914 } 915 916 case "builddir", "buildrundir": 917 // Build an executable from all the .go and .s files in a subdirectory. 918 // Run it and verify its output in the buildrundir case. 919 longdir := filepath.Join(cwd, t.goDirName()) 920 files, dirErr := ioutil.ReadDir(longdir) 921 if dirErr != nil { 922 t.err = dirErr 923 break 924 } 925 var gos []string 926 var asms []string 927 for _, file := range files { 928 switch filepath.Ext(file.Name()) { 929 case ".go": 930 gos = append(gos, filepath.Join(longdir, file.Name())) 931 case ".s": 932 asms = append(asms, filepath.Join(longdir, file.Name())) 933 } 934 935 } 936 if len(asms) > 0 { 937 emptyHdrFile := filepath.Join(t.tempDir, "go_asm.h") 938 if err := ioutil.WriteFile(emptyHdrFile, nil, 0666); err != nil { 939 t.err = fmt.Errorf("write empty go_asm.h: %s", err) 940 return 941 } 942 cmd := []string{goTool(), "tool", "asm", "-gensymabis", "-o", "symabis"} 943 cmd = append(cmd, asms...) 944 _, err = runcmd(cmd...) 945 if err != nil { 946 t.err = err 947 break 948 } 949 } 950 var objs []string 951 cmd := []string{goTool(), "tool", "compile", "-e", "-D", ".", "-I", ".", "-o", "go.o"} 952 if len(asms) > 0 { 953 cmd = append(cmd, "-asmhdr", "go_asm.h", "-symabis", "symabis") 954 } 955 cmd = append(cmd, gos...) 956 _, err := runcmd(cmd...) 957 if err != nil { 958 t.err = err 959 break 960 } 961 objs = append(objs, "go.o") 962 if len(asms) > 0 { 963 cmd = []string{goTool(), "tool", "asm", "-e", "-I", ".", "-o", "asm.o"} 964 cmd = append(cmd, asms...) 965 _, err = runcmd(cmd...) 966 if err != nil { 967 t.err = err 968 break 969 } 970 objs = append(objs, "asm.o") 971 } 972 cmd = []string{goTool(), "tool", "pack", "c", "all.a"} 973 cmd = append(cmd, objs...) 974 _, err = runcmd(cmd...) 975 if err != nil { 976 t.err = err 977 break 978 } 979 cmd = []string{goTool(), "tool", "link", "-o", "a.exe", "all.a"} 980 _, err = runcmd(cmd...) 981 if err != nil { 982 t.err = err 983 break 984 } 985 if action == "buildrundir" { 986 cmd = append(findExecCmd(), filepath.Join(t.tempDir, "a.exe")) 987 out, err := runcmd(cmd...) 988 if err != nil { 989 t.err = err 990 break 991 } 992 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 993 t.err = fmt.Errorf("incorrect output\n%s", out) 994 } 995 } 996 997 case "buildrun": 998 // Build an executable from Go file, then run it, verify its output. 999 // Useful for timeout tests where failure mode is infinite loop. 1000 // TODO: not supported on NaCl 1001 cmd := []string{goTool(), "build", goGcflags(), "-o", "a.exe"} 1002 if *linkshared { 1003 cmd = append(cmd, "-linkshared") 1004 } 1005 longdirgofile := filepath.Join(filepath.Join(cwd, t.dir), t.gofile) 1006 cmd = append(cmd, flags...) 1007 cmd = append(cmd, longdirgofile) 1008 _, err := runcmd(cmd...) 1009 if err != nil { 1010 t.err = err 1011 return 1012 } 1013 cmd = []string{"./a.exe"} 1014 out, err := runcmd(append(cmd, args...)...) 1015 if err != nil { 1016 t.err = err 1017 return 1018 } 1019 1020 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 1021 t.err = fmt.Errorf("incorrect output\n%s", out) 1022 } 1023 1024 case "run": 1025 // Run Go file if no special go command flags are provided; 1026 // otherwise build an executable and run it. 1027 // Verify the output. 1028 runInDir = "" 1029 var out []byte 1030 var err error 1031 if len(flags)+len(args) == 0 && goGcflagsIsEmpty() && !*linkshared && goarch == runtime.GOARCH && goos == runtime.GOOS { 1032 // If we're not using special go command flags, 1033 // skip all the go command machinery. 1034 // This avoids any time the go command would 1035 // spend checking whether, for example, the installed 1036 // package runtime is up to date. 1037 // Because we run lots of trivial test programs, 1038 // the time adds up. 1039 pkg := filepath.Join(t.tempDir, "pkg.a") 1040 if _, err := runcmd(goTool(), "tool", "compile", "-o", pkg, t.goFileName()); err != nil { 1041 t.err = err 1042 return 1043 } 1044 exe := filepath.Join(t.tempDir, "test.exe") 1045 cmd := []string{goTool(), "tool", "link", "-s", "-w"} 1046 cmd = append(cmd, "-o", exe, pkg) 1047 if _, err := runcmd(cmd...); err != nil { 1048 t.err = err 1049 return 1050 } 1051 out, err = runcmd(append([]string{exe}, args...)...) 1052 } else { 1053 cmd := []string{goTool(), "run", goGcflags()} 1054 if *linkshared { 1055 cmd = append(cmd, "-linkshared") 1056 } 1057 cmd = append(cmd, flags...) 1058 cmd = append(cmd, t.goFileName()) 1059 out, err = runcmd(append(cmd, args...)...) 1060 } 1061 if err != nil { 1062 t.err = err 1063 return 1064 } 1065 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 1066 t.err = fmt.Errorf("incorrect output\n%s", out) 1067 } 1068 1069 case "runoutput": 1070 // Run Go file and write its output into temporary Go file. 1071 // Run generated Go file and verify its output. 1072 rungatec <- true 1073 defer func() { 1074 <-rungatec 1075 }() 1076 runInDir = "" 1077 cmd := []string{goTool(), "run", goGcflags()} 1078 if *linkshared { 1079 cmd = append(cmd, "-linkshared") 1080 } 1081 cmd = append(cmd, t.goFileName()) 1082 out, err := runcmd(append(cmd, args...)...) 1083 if err != nil { 1084 t.err = err 1085 return 1086 } 1087 tfile := filepath.Join(t.tempDir, "tmp__.go") 1088 if err := ioutil.WriteFile(tfile, out, 0666); err != nil { 1089 t.err = fmt.Errorf("write tempfile:%s", err) 1090 return 1091 } 1092 cmd = []string{goTool(), "run", goGcflags()} 1093 if *linkshared { 1094 cmd = append(cmd, "-linkshared") 1095 } 1096 cmd = append(cmd, tfile) 1097 out, err = runcmd(cmd...) 1098 if err != nil { 1099 t.err = err 1100 return 1101 } 1102 if string(out) != t.expectedOutput() { 1103 t.err = fmt.Errorf("incorrect output\n%s", out) 1104 } 1105 1106 case "errorcheckoutput": 1107 // Run Go file and write its output into temporary Go file. 1108 // Compile and errorCheck generated Go file. 1109 runInDir = "" 1110 cmd := []string{goTool(), "run", goGcflags()} 1111 if *linkshared { 1112 cmd = append(cmd, "-linkshared") 1113 } 1114 cmd = append(cmd, t.goFileName()) 1115 out, err := runcmd(append(cmd, args...)...) 1116 if err != nil { 1117 t.err = err 1118 return 1119 } 1120 tfile := filepath.Join(t.tempDir, "tmp__.go") 1121 err = ioutil.WriteFile(tfile, out, 0666) 1122 if err != nil { 1123 t.err = fmt.Errorf("write tempfile:%s", err) 1124 return 1125 } 1126 cmdline := []string{goTool(), "tool", "compile", "-e", "-o", "a.o"} 1127 cmdline = append(cmdline, flags...) 1128 cmdline = append(cmdline, tfile) 1129 out, err = runcmd(cmdline...) 1130 if wantError { 1131 if err == nil { 1132 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 1133 return 1134 } 1135 } else { 1136 if err != nil { 1137 t.err = err 1138 return 1139 } 1140 } 1141 t.err = t.errorCheck(string(out), false, tfile, "tmp__.go") 1142 return 1143 } 1144} 1145 1146var execCmd []string 1147 1148func findExecCmd() []string { 1149 if execCmd != nil { 1150 return execCmd 1151 } 1152 execCmd = []string{} // avoid work the second time 1153 if goos == runtime.GOOS && goarch == runtime.GOARCH { 1154 return execCmd 1155 } 1156 path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch)) 1157 if err == nil { 1158 execCmd = []string{path} 1159 } 1160 return execCmd 1161} 1162 1163func (t *test) String() string { 1164 return filepath.Join(t.dir, t.gofile) 1165} 1166 1167func (t *test) makeTempDir() { 1168 var err error 1169 t.tempDir, err = ioutil.TempDir("", "") 1170 if err != nil { 1171 log.Fatal(err) 1172 } 1173 if *keep { 1174 log.Printf("Temporary directory is %s", t.tempDir) 1175 } 1176} 1177 1178func (t *test) expectedOutput() string { 1179 filename := filepath.Join(t.dir, t.gofile) 1180 filename = filename[:len(filename)-len(".go")] 1181 filename += ".out" 1182 b, _ := ioutil.ReadFile(filename) 1183 return string(b) 1184} 1185 1186func splitOutput(out string, wantAuto bool) []string { 1187 // gc error messages continue onto additional lines with leading tabs. 1188 // Split the output at the beginning of each line that doesn't begin with a tab. 1189 // <autogenerated> lines are impossible to match so those are filtered out. 1190 var res []string 1191 for _, line := range strings.Split(out, "\n") { 1192 if strings.HasSuffix(line, "\r") { // remove '\r', output by compiler on windows 1193 line = line[:len(line)-1] 1194 } 1195 if strings.HasPrefix(line, "\t") { 1196 res[len(res)-1] += "\n" + line 1197 } else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") { 1198 continue 1199 } else if strings.TrimSpace(line) != "" { 1200 res = append(res, line) 1201 } 1202 } 1203 return res 1204} 1205 1206// errorCheck matches errors in outStr against comments in source files. 1207// For each line of the source files which should generate an error, 1208// there should be a comment of the form // ERROR "regexp". 1209// If outStr has an error for a line which has no such comment, 1210// this function will report an error. 1211// Likewise if outStr does not have an error for a line which has a comment, 1212// or if the error message does not match the <regexp>. 1213// The <regexp> syntax is Perl but it's best to stick to egrep. 1214// 1215// Sources files are supplied as fullshort slice. 1216// It consists of pairs: full path to source file and its base name. 1217func (t *test) errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) { 1218 defer func() { 1219 if *verbose && err != nil { 1220 log.Printf("%s gc output:\n%s", t, outStr) 1221 } 1222 }() 1223 var errs []error 1224 out := splitOutput(outStr, wantAuto) 1225 1226 // Cut directory name. 1227 for i := range out { 1228 for j := 0; j < len(fullshort); j += 2 { 1229 full, short := fullshort[j], fullshort[j+1] 1230 out[i] = strings.Replace(out[i], full, short, -1) 1231 } 1232 } 1233 1234 var want []wantedError 1235 for j := 0; j < len(fullshort); j += 2 { 1236 full, short := fullshort[j], fullshort[j+1] 1237 want = append(want, t.wantedErrors(full, short)...) 1238 } 1239 1240 for _, we := range want { 1241 var errmsgs []string 1242 if we.auto { 1243 errmsgs, out = partitionStrings("<autogenerated>", out) 1244 } else { 1245 errmsgs, out = partitionStrings(we.prefix, out) 1246 } 1247 if len(errmsgs) == 0 { 1248 errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr)) 1249 continue 1250 } 1251 matched := false 1252 n := len(out) 1253 for _, errmsg := range errmsgs { 1254 // Assume errmsg says "file:line: foo". 1255 // Cut leading "file:line: " to avoid accidental matching of file name instead of message. 1256 text := errmsg 1257 if i := strings.Index(text, " "); i >= 0 { 1258 text = text[i+1:] 1259 } 1260 if we.re.MatchString(text) { 1261 matched = true 1262 } else { 1263 out = append(out, errmsg) 1264 } 1265 } 1266 if !matched { 1267 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"))) 1268 continue 1269 } 1270 } 1271 1272 if len(out) > 0 { 1273 errs = append(errs, fmt.Errorf("Unmatched Errors:")) 1274 for _, errLine := range out { 1275 errs = append(errs, fmt.Errorf("%s", errLine)) 1276 } 1277 } 1278 1279 if len(errs) == 0 { 1280 return nil 1281 } 1282 if len(errs) == 1 { 1283 return errs[0] 1284 } 1285 var buf bytes.Buffer 1286 fmt.Fprintf(&buf, "\n") 1287 for _, err := range errs { 1288 fmt.Fprintf(&buf, "%s\n", err.Error()) 1289 } 1290 return errors.New(buf.String()) 1291} 1292 1293func (t *test) updateErrors(out, file string) { 1294 base := path.Base(file) 1295 // Read in source file. 1296 src, err := ioutil.ReadFile(file) 1297 if err != nil { 1298 fmt.Fprintln(os.Stderr, err) 1299 return 1300 } 1301 lines := strings.Split(string(src), "\n") 1302 // Remove old errors. 1303 for i, ln := range lines { 1304 pos := strings.Index(ln, " // ERROR ") 1305 if pos >= 0 { 1306 lines[i] = ln[:pos] 1307 } 1308 } 1309 // Parse new errors. 1310 errors := make(map[int]map[string]bool) 1311 tmpRe := regexp.MustCompile(`autotmp_[0-9]+`) 1312 for _, errStr := range splitOutput(out, false) { 1313 colon1 := strings.Index(errStr, ":") 1314 if colon1 < 0 || errStr[:colon1] != file { 1315 continue 1316 } 1317 colon2 := strings.Index(errStr[colon1+1:], ":") 1318 if colon2 < 0 { 1319 continue 1320 } 1321 colon2 += colon1 + 1 1322 line, err := strconv.Atoi(errStr[colon1+1 : colon2]) 1323 line-- 1324 if err != nil || line < 0 || line >= len(lines) { 1325 continue 1326 } 1327 msg := errStr[colon2+2:] 1328 msg = strings.Replace(msg, file, base, -1) // normalize file mentions in error itself 1329 msg = strings.TrimLeft(msg, " \t") 1330 for _, r := range []string{`\`, `*`, `+`, `?`, `[`, `]`, `(`, `)`} { 1331 msg = strings.Replace(msg, r, `\`+r, -1) 1332 } 1333 msg = strings.Replace(msg, `"`, `.`, -1) 1334 msg = tmpRe.ReplaceAllLiteralString(msg, `autotmp_[0-9]+`) 1335 if errors[line] == nil { 1336 errors[line] = make(map[string]bool) 1337 } 1338 errors[line][msg] = true 1339 } 1340 // Add new errors. 1341 for line, errs := range errors { 1342 var sorted []string 1343 for e := range errs { 1344 sorted = append(sorted, e) 1345 } 1346 sort.Strings(sorted) 1347 lines[line] += " // ERROR" 1348 for _, e := range sorted { 1349 lines[line] += fmt.Sprintf(` "%s$"`, e) 1350 } 1351 } 1352 // Write new file. 1353 err = ioutil.WriteFile(file, []byte(strings.Join(lines, "\n")), 0640) 1354 if err != nil { 1355 fmt.Fprintln(os.Stderr, err) 1356 return 1357 } 1358 // Polish. 1359 exec.Command(goTool(), "fmt", file).CombinedOutput() 1360} 1361 1362// matchPrefix reports whether s is of the form ^(.*/)?prefix(:|[), 1363// That is, it needs the file name prefix followed by a : or a [, 1364// and possibly preceded by a directory name. 1365func matchPrefix(s, prefix string) bool { 1366 i := strings.Index(s, ":") 1367 if i < 0 { 1368 return false 1369 } 1370 j := strings.LastIndex(s[:i], "/") 1371 s = s[j+1:] 1372 if len(s) <= len(prefix) || s[:len(prefix)] != prefix { 1373 return false 1374 } 1375 switch s[len(prefix)] { 1376 case '[', ':': 1377 return true 1378 } 1379 return false 1380} 1381 1382func partitionStrings(prefix string, strs []string) (matched, unmatched []string) { 1383 for _, s := range strs { 1384 if matchPrefix(s, prefix) { 1385 matched = append(matched, s) 1386 } else { 1387 unmatched = append(unmatched, s) 1388 } 1389 } 1390 return 1391} 1392 1393type wantedError struct { 1394 reStr string 1395 re *regexp.Regexp 1396 lineNum int 1397 auto bool // match <autogenerated> line 1398 file string 1399 prefix string 1400} 1401 1402var ( 1403 errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`) 1404 errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`) 1405 errQuotesRx = regexp.MustCompile(`"([^"]*)"`) 1406 lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`) 1407) 1408 1409func (t *test) wantedErrors(file, short string) (errs []wantedError) { 1410 cache := make(map[string]*regexp.Regexp) 1411 1412 src, _ := ioutil.ReadFile(file) 1413 for i, line := range strings.Split(string(src), "\n") { 1414 lineNum := i + 1 1415 if strings.Contains(line, "////") { 1416 // double comment disables ERROR 1417 continue 1418 } 1419 var auto bool 1420 m := errAutoRx.FindStringSubmatch(line) 1421 if m != nil { 1422 auto = true 1423 } else { 1424 m = errRx.FindStringSubmatch(line) 1425 } 1426 if m == nil { 1427 continue 1428 } 1429 all := m[1] 1430 mm := errQuotesRx.FindAllStringSubmatch(all, -1) 1431 if mm == nil { 1432 log.Fatalf("%s:%d: invalid errchk line: %s", t.goFileName(), lineNum, line) 1433 } 1434 for _, m := range mm { 1435 rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string { 1436 n := lineNum 1437 if strings.HasPrefix(m, "LINE+") { 1438 delta, _ := strconv.Atoi(m[5:]) 1439 n += delta 1440 } else if strings.HasPrefix(m, "LINE-") { 1441 delta, _ := strconv.Atoi(m[5:]) 1442 n -= delta 1443 } 1444 return fmt.Sprintf("%s:%d", short, n) 1445 }) 1446 re := cache[rx] 1447 if re == nil { 1448 var err error 1449 re, err = regexp.Compile(rx) 1450 if err != nil { 1451 log.Fatalf("%s:%d: invalid regexp \"%s\" in ERROR line: %v", t.goFileName(), lineNum, rx, err) 1452 } 1453 cache[rx] = re 1454 } 1455 prefix := fmt.Sprintf("%s:%d", short, lineNum) 1456 errs = append(errs, wantedError{ 1457 reStr: rx, 1458 re: re, 1459 prefix: prefix, 1460 auto: auto, 1461 lineNum: lineNum, 1462 file: short, 1463 }) 1464 } 1465 } 1466 1467 return 1468} 1469 1470const ( 1471 // Regexp to match a single opcode check: optionally begin with "-" (to indicate 1472 // a negative check), followed by a string literal enclosed in "" or ``. For "", 1473 // backslashes must be handled. 1474 reMatchCheck = `-?(?:\x60[^\x60]*\x60|"(?:[^"\\]|\\.)*")` 1475) 1476 1477var ( 1478 // Regexp to split a line in code and comment, trimming spaces 1479 rxAsmComment = regexp.MustCompile(`^\s*(.*?)\s*(?://\s*(.+)\s*)?$`) 1480 1481 // Regexp to extract an architecture check: architecture name (or triplet), 1482 // followed by semi-colon, followed by a comma-separated list of opcode checks. 1483 // Extraneous spaces are ignored. 1484 rxAsmPlatform = regexp.MustCompile(`(\w+)(/\w+)?(/\w*)?\s*:\s*(` + reMatchCheck + `(?:\s*,\s*` + reMatchCheck + `)*)`) 1485 1486 // Regexp to extract a single opcoded check 1487 rxAsmCheck = regexp.MustCompile(reMatchCheck) 1488 1489 // List of all architecture variants. Key is the GOARCH architecture, 1490 // value[0] is the variant-changing environment variable, and values[1:] 1491 // are the supported variants. 1492 archVariants = map[string][]string{ 1493 "386": {"GO386", "sse2", "softfloat"}, 1494 "amd64": {}, 1495 "arm": {"GOARM", "5", "6", "7"}, 1496 "arm64": {}, 1497 "mips": {"GOMIPS", "hardfloat", "softfloat"}, 1498 "mips64": {"GOMIPS64", "hardfloat", "softfloat"}, 1499 "ppc64": {"GOPPC64", "power8", "power9"}, 1500 "ppc64le": {"GOPPC64", "power8", "power9"}, 1501 "s390x": {}, 1502 "wasm": {}, 1503 } 1504) 1505 1506// wantedAsmOpcode is a single asmcheck check 1507type wantedAsmOpcode struct { 1508 fileline string // original source file/line (eg: "/path/foo.go:45") 1509 line int // original source line 1510 opcode *regexp.Regexp // opcode check to be performed on assembly output 1511 negative bool // true if the check is supposed to fail rather than pass 1512 found bool // true if the opcode check matched at least one in the output 1513} 1514 1515// A build environment triplet separated by slashes (eg: linux/386/sse2). 1516// The third field can be empty if the arch does not support variants (eg: "plan9/amd64/") 1517type buildEnv string 1518 1519// Environ returns the environment it represents in cmd.Environ() "key=val" format 1520// For instance, "linux/386/sse2".Environ() returns {"GOOS=linux", "GOARCH=386", "GO386=sse2"} 1521func (b buildEnv) Environ() []string { 1522 fields := strings.Split(string(b), "/") 1523 if len(fields) != 3 { 1524 panic("invalid buildEnv string: " + string(b)) 1525 } 1526 env := []string{"GOOS=" + fields[0], "GOARCH=" + fields[1]} 1527 if fields[2] != "" { 1528 env = append(env, archVariants[fields[1]][0]+"="+fields[2]) 1529 } 1530 return env 1531} 1532 1533// asmChecks represents all the asmcheck checks present in a test file 1534// The outer map key is the build triplet in which the checks must be performed. 1535// The inner map key represent the source file line ("filename.go:1234") at which the 1536// checks must be performed. 1537type asmChecks map[buildEnv]map[string][]wantedAsmOpcode 1538 1539// Envs returns all the buildEnv in which at least one check is present 1540func (a asmChecks) Envs() []buildEnv { 1541 var envs []buildEnv 1542 for e := range a { 1543 envs = append(envs, e) 1544 } 1545 sort.Slice(envs, func(i, j int) bool { 1546 return string(envs[i]) < string(envs[j]) 1547 }) 1548 return envs 1549} 1550 1551func (t *test) wantedAsmOpcodes(fn string) asmChecks { 1552 ops := make(asmChecks) 1553 1554 comment := "" 1555 src, _ := ioutil.ReadFile(fn) 1556 for i, line := range strings.Split(string(src), "\n") { 1557 matches := rxAsmComment.FindStringSubmatch(line) 1558 code, cmt := matches[1], matches[2] 1559 1560 // Keep comments pending in the comment variable until 1561 // we find a line that contains some code. 1562 comment += " " + cmt 1563 if code == "" { 1564 continue 1565 } 1566 1567 // Parse and extract any architecture check from comments, 1568 // made by one architecture name and multiple checks. 1569 lnum := fn + ":" + strconv.Itoa(i+1) 1570 for _, ac := range rxAsmPlatform.FindAllStringSubmatch(comment, -1) { 1571 archspec, allchecks := ac[1:4], ac[4] 1572 1573 var arch, subarch, os string 1574 switch { 1575 case archspec[2] != "": // 3 components: "linux/386/sse2" 1576 os, arch, subarch = archspec[0], archspec[1][1:], archspec[2][1:] 1577 case archspec[1] != "": // 2 components: "386/sse2" 1578 os, arch, subarch = "linux", archspec[0], archspec[1][1:] 1579 default: // 1 component: "386" 1580 os, arch, subarch = "linux", archspec[0], "" 1581 if arch == "wasm" { 1582 os = "js" 1583 } 1584 } 1585 1586 if _, ok := archVariants[arch]; !ok { 1587 log.Fatalf("%s:%d: unsupported architecture: %v", t.goFileName(), i+1, arch) 1588 } 1589 1590 // Create the build environments corresponding the above specifiers 1591 envs := make([]buildEnv, 0, 4) 1592 if subarch != "" { 1593 envs = append(envs, buildEnv(os+"/"+arch+"/"+subarch)) 1594 } else { 1595 subarchs := archVariants[arch] 1596 if len(subarchs) == 0 { 1597 envs = append(envs, buildEnv(os+"/"+arch+"/")) 1598 } else { 1599 for _, sa := range archVariants[arch][1:] { 1600 envs = append(envs, buildEnv(os+"/"+arch+"/"+sa)) 1601 } 1602 } 1603 } 1604 1605 for _, m := range rxAsmCheck.FindAllString(allchecks, -1) { 1606 negative := false 1607 if m[0] == '-' { 1608 negative = true 1609 m = m[1:] 1610 } 1611 1612 rxsrc, err := strconv.Unquote(m) 1613 if err != nil { 1614 log.Fatalf("%s:%d: error unquoting string: %v", t.goFileName(), i+1, err) 1615 } 1616 1617 // Compile the checks as regular expressions. Notice that we 1618 // consider checks as matching from the beginning of the actual 1619 // assembler source (that is, what is left on each line of the 1620 // compile -S output after we strip file/line info) to avoid 1621 // trivial bugs such as "ADD" matching "FADD". This 1622 // doesn't remove genericity: it's still possible to write 1623 // something like "F?ADD", but we make common cases simpler 1624 // to get right. 1625 oprx, err := regexp.Compile("^" + rxsrc) 1626 if err != nil { 1627 log.Fatalf("%s:%d: %v", t.goFileName(), i+1, err) 1628 } 1629 1630 for _, env := range envs { 1631 if ops[env] == nil { 1632 ops[env] = make(map[string][]wantedAsmOpcode) 1633 } 1634 ops[env][lnum] = append(ops[env][lnum], wantedAsmOpcode{ 1635 negative: negative, 1636 fileline: lnum, 1637 line: i + 1, 1638 opcode: oprx, 1639 }) 1640 } 1641 } 1642 } 1643 comment = "" 1644 } 1645 1646 return ops 1647} 1648 1649func (t *test) asmCheck(outStr string, fn string, env buildEnv, fullops map[string][]wantedAsmOpcode) (err error) { 1650 // The assembly output contains the concatenated dump of multiple functions. 1651 // the first line of each function begins at column 0, while the rest is 1652 // indented by a tabulation. These data structures help us index the 1653 // output by function. 1654 functionMarkers := make([]int, 1) 1655 lineFuncMap := make(map[string]int) 1656 1657 lines := strings.Split(outStr, "\n") 1658 rxLine := regexp.MustCompile(fmt.Sprintf(`\((%s:\d+)\)\s+(.*)`, regexp.QuoteMeta(fn))) 1659 1660 for nl, line := range lines { 1661 // Check if this line begins a function 1662 if len(line) > 0 && line[0] != '\t' { 1663 functionMarkers = append(functionMarkers, nl) 1664 } 1665 1666 // Search if this line contains a assembly opcode (which is prefixed by the 1667 // original source file/line in parenthesis) 1668 matches := rxLine.FindStringSubmatch(line) 1669 if len(matches) == 0 { 1670 continue 1671 } 1672 srcFileLine, asm := matches[1], matches[2] 1673 1674 // Associate the original file/line information to the current 1675 // function in the output; it will be useful to dump it in case 1676 // of error. 1677 lineFuncMap[srcFileLine] = len(functionMarkers) - 1 1678 1679 // If there are opcode checks associated to this source file/line, 1680 // run the checks. 1681 if ops, found := fullops[srcFileLine]; found { 1682 for i := range ops { 1683 if !ops[i].found && ops[i].opcode.FindString(asm) != "" { 1684 ops[i].found = true 1685 } 1686 } 1687 } 1688 } 1689 functionMarkers = append(functionMarkers, len(lines)) 1690 1691 var failed []wantedAsmOpcode 1692 for _, ops := range fullops { 1693 for _, o := range ops { 1694 // There's a failure if a negative match was found, 1695 // or a positive match was not found. 1696 if o.negative == o.found { 1697 failed = append(failed, o) 1698 } 1699 } 1700 } 1701 if len(failed) == 0 { 1702 return 1703 } 1704 1705 // At least one asmcheck failed; report them 1706 sort.Slice(failed, func(i, j int) bool { 1707 return failed[i].line < failed[j].line 1708 }) 1709 1710 lastFunction := -1 1711 var errbuf bytes.Buffer 1712 fmt.Fprintln(&errbuf) 1713 for _, o := range failed { 1714 // Dump the function in which this opcode check was supposed to 1715 // pass but failed. 1716 funcIdx := lineFuncMap[o.fileline] 1717 if funcIdx != 0 && funcIdx != lastFunction { 1718 funcLines := lines[functionMarkers[funcIdx]:functionMarkers[funcIdx+1]] 1719 log.Println(strings.Join(funcLines, "\n")) 1720 lastFunction = funcIdx // avoid printing same function twice 1721 } 1722 1723 if o.negative { 1724 fmt.Fprintf(&errbuf, "%s:%d: %s: wrong opcode found: %q\n", t.goFileName(), o.line, env, o.opcode.String()) 1725 } else { 1726 fmt.Fprintf(&errbuf, "%s:%d: %s: opcode not found: %q\n", t.goFileName(), o.line, env, o.opcode.String()) 1727 } 1728 } 1729 err = errors.New(errbuf.String()) 1730 return 1731} 1732 1733// defaultRunOutputLimit returns the number of runoutput tests that 1734// can be executed in parallel. 1735func defaultRunOutputLimit() int { 1736 const maxArmCPU = 2 1737 1738 cpu := runtime.NumCPU() 1739 if runtime.GOARCH == "arm" && cpu > maxArmCPU { 1740 cpu = maxArmCPU 1741 } 1742 return cpu 1743} 1744 1745// checkShouldTest runs sanity checks on the shouldTest function. 1746func checkShouldTest() { 1747 assert := func(ok bool, _ string) { 1748 if !ok { 1749 panic("fail") 1750 } 1751 } 1752 assertNot := func(ok bool, _ string) { assert(!ok, "") } 1753 1754 // Simple tests. 1755 assert(shouldTest("// +build linux", "linux", "arm")) 1756 assert(shouldTest("// +build !windows", "linux", "arm")) 1757 assertNot(shouldTest("// +build !windows", "windows", "amd64")) 1758 1759 // A file with no build tags will always be tested. 1760 assert(shouldTest("// This is a test.", "os", "arch")) 1761 1762 // Build tags separated by a space are OR-ed together. 1763 assertNot(shouldTest("// +build arm 386", "linux", "amd64")) 1764 1765 // Build tags separated by a comma are AND-ed together. 1766 assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64")) 1767 assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386")) 1768 1769 // Build tags on multiple lines are AND-ed together. 1770 assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64")) 1771 assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64")) 1772 1773 // Test that (!a OR !b) matches anything. 1774 assert(shouldTest("// +build !windows !plan9", "windows", "amd64")) 1775} 1776 1777func getenv(key, def string) string { 1778 value := os.Getenv(key) 1779 if value != "" { 1780 return value 1781 } 1782 return def 1783} 1784 1785// overlayDir makes a minimal-overhead copy of srcRoot in which new files may be added. 1786func overlayDir(dstRoot, srcRoot string) error { 1787 dstRoot = filepath.Clean(dstRoot) 1788 if err := os.MkdirAll(dstRoot, 0777); err != nil { 1789 return err 1790 } 1791 1792 srcRoot, err := filepath.Abs(srcRoot) 1793 if err != nil { 1794 return err 1795 } 1796 1797 return filepath.WalkDir(srcRoot, func(srcPath string, d fs.DirEntry, err error) error { 1798 if err != nil || srcPath == srcRoot { 1799 return err 1800 } 1801 1802 suffix := strings.TrimPrefix(srcPath, srcRoot) 1803 for len(suffix) > 0 && suffix[0] == filepath.Separator { 1804 suffix = suffix[1:] 1805 } 1806 dstPath := filepath.Join(dstRoot, suffix) 1807 1808 var info fs.FileInfo 1809 if d.Type()&os.ModeSymlink != 0 { 1810 info, err = os.Stat(srcPath) 1811 } else { 1812 info, err = d.Info() 1813 } 1814 if err != nil { 1815 return err 1816 } 1817 perm := info.Mode() & os.ModePerm 1818 1819 // Always copy directories (don't symlink them). 1820 // If we add a file in the overlay, we don't want to add it in the original. 1821 if info.IsDir() { 1822 return os.MkdirAll(dstPath, perm|0200) 1823 } 1824 1825 // If the OS supports symlinks, use them instead of copying bytes. 1826 if err := os.Symlink(srcPath, dstPath); err == nil { 1827 return nil 1828 } 1829 1830 // Otherwise, copy the bytes. 1831 src, err := os.Open(srcPath) 1832 if err != nil { 1833 return err 1834 } 1835 defer src.Close() 1836 1837 dst, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm) 1838 if err != nil { 1839 return err 1840 } 1841 1842 _, err = io.Copy(dst, src) 1843 if closeErr := dst.Close(); err == nil { 1844 err = closeErr 1845 } 1846 return err 1847 }) 1848} 1849