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