Skip to content
Snippets Groups Projects
testdir_test.go 58.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • // Copyright 2012 The Go Authors. All rights reserved.
    
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.
    
    
    // Package testdir_test runs tests in the GOROOT/test directory.
    package testdir_test
    
    	"go/build/constraint"
    
    	"internal/testenv"
    
    	"path/filepath"
    	"regexp"
    	"runtime"
    
    	allCodegen     = flag.Bool("all_codegen", defaultAllCodeGen(), "run all goos/goarch for codegen")
    
    	runSkips       = flag.Bool("run_skips", false, "run skipped tests (ignore skip and build tags)")
    
    	linkshared     = flag.Bool("linkshared", false, "")
    
    	updateErrors   = flag.Bool("update_errors", false, "update error messages in test file based on compiler output")
    
    	runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run")
    
    	force          = flag.Bool("f", false, "ignore expected-failure test lists")
    
    	target         = flag.String("target", "", "cross-compile tests for `goos/goarch`")
    
    
    	shard  = flag.Int("shard", 0, "shard index to run. Only applicable if -shards is non-zero.")
    	shards = flag.Int("shards", 0, "number of shards. If 0, all tests are run. This is used by the continuous build.")
    
    // defaultAllCodeGen returns the default value of the -all_codegen
    // flag. By default, we prefer to be fast (returning false), except on
    // the linux-amd64 builder that's already very fast, so we get more
    
    // test coverage on trybots. See https://go.dev/issue/34297.
    
    func defaultAllCodeGen() bool {
    	return os.Getenv("GO_BUILDER_NAME") == "linux-amd64"
    }
    
    
    	// Package-scoped variables that are initialized at the start of Test.
    	goTool       string
    
    	goos         string // Target GOOS
    	goarch       string // Target GOARCH
    
    	cgoEnabled   bool
    	goExperiment string
    
    	goDebug      string
    
    
    	// dirs are the directories to look for *.go files in.
    	// TODO(bradfitz): just use all directories?
    
    	dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "codegen", "runtime", "abi", "typeparam", "typeparam/mdempsky", "arenas"}
    
    // Test is the main entrypoint that runs tests in the GOROOT/test directory.
    
    // Each .go file test case in GOROOT/test is registered as a subtest with
    
    // a full name like "Test/fixedbugs/bug000.go" ('/'-separated relative path).
    
    	if *target != "" {
    		// When -target is set, propagate it to GOOS/GOARCH in our environment
    		// so that all commands run with the target GOOS/GOARCH.
    		//
    		// We do this before even calling "go env", because GOOS/GOARCH can
    		// affect other settings we get from go env (notably CGO_ENABLED).
    		goos, goarch, ok := strings.Cut(*target, "/")
    		if !ok {
    			t.Fatalf("bad -target flag %q, expected goos/goarch", *target)
    		}
    		t.Setenv("GOOS", goos)
    		t.Setenv("GOARCH", goarch)
    	}
    
    
    	goTool = testenv.GoToolPath(t)
    	cmd := exec.Command(goTool, "env", "-json")
    	stdout, err := cmd.StdoutPipe()
    
    		t.Fatal("StdoutPipe:", err)
    	}
    	if err := cmd.Start(); err != nil {
    		t.Fatal("Start:", err)
    	}
    	var env struct {
    		GOOS         string
    		GOARCH       string
    		GOEXPERIMENT string
    
    		GODEBUG      string
    
    		CGO_ENABLED  string
    	}
    	if err := json.NewDecoder(stdout).Decode(&env); err != nil {
    		t.Fatal("Decode:", err)
    	}
    	if err := cmd.Wait(); err != nil {
    		t.Fatal("Wait:", err)
    
    	goos = env.GOOS
    	goarch = env.GOARCH
    	cgoEnabled, _ = strconv.ParseBool(env.CGO_ENABLED)
    	goExperiment = env.GOEXPERIMENT
    
    	goDebug = env.GODEBUG
    
    	common := testCommon{
    		gorootTestDir: filepath.Join(testenv.GOROOT(t), "test"),
    		runoutputGate: make(chan bool, *runoutputLimit),
    	}
    
    	// cmd/distpack deletes GOROOT/test, so skip the test if it isn't present.
    	// cmd/distpack also requires GOROOT/VERSION to exist, so use that to
    	// suppress false-positive skips.
    	if _, err := os.Stat(common.gorootTestDir); os.IsNotExist(err) {
    		if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil {
    			t.Skipf("skipping: GOROOT/test not present")
    		}
    	}
    
    
    		for _, goFile := range goFiles(t, dir) {
    			test := test{testCommon: common, dir: dir, goFile: goFile}
    			t.Run(path.Join(dir, goFile), func(t *testing.T) {
    
    				testError := test.run()
    				wantError := test.expectFail() && !*force
    				if testError != nil {
    					if wantError {
    						t.Log(testError.Error() + " (expected)")
    
    						t.Fatal(testError)
    
    				} else if wantError {
    
    func shardMatch(name string) bool {
    
    		return true
    	}
    	h := fnv.New32()
    	io.WriteString(h, name)
    	return int(h.Sum32()%uint32(*shards)) == *shard
    }
    
    
    func goFiles(t *testing.T, dir string) []string {
    	f, err := os.Open(filepath.Join(testenv.GOROOT(t), "test", dir))
    
    Dmitri Shuralyov's avatar
    Dmitri Shuralyov committed
    	if err != nil {
    
    	dirnames, err := f.Readdirnames(-1)
    
    	f.Close()
    
    Dmitri Shuralyov's avatar
    Dmitri Shuralyov committed
    	if err != nil {
    
    	names := []string{}
    	for _, name := range dirnames {
    
    		if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && shardMatch(name) {
    
    			names = append(names, name)
    		}
    	}
    	sort.Strings(names)
    	return names
    }
    
    
    func compileFile(runcmd runCmd, longname string, flags []string) (out []byte, err error) {
    
    	cmd := []string{goTool, "tool", "compile", "-e", "-p=p", "-importcfg=" + stdlibImportcfgFile()}
    
    	if *linkshared {
    		cmd = append(cmd, "-dynlink", "-installsuffix=dynlink")
    	}
    	cmd = append(cmd, longname)
    	return runcmd(cmd...)
    
    func compileInDir(runcmd runCmd, dir string, flags []string, importcfg string, pkgname string, names ...string) (out []byte, err error) {
    	if importcfg == "" {
    		importcfg = stdlibImportcfgFile()
    	}
    
    	cmd := []string{goTool, "tool", "compile", "-e", "-D", "test", "-importcfg=" + importcfg}
    
    	if pkgname == "main" {
    		cmd = append(cmd, "-p=main")
    	} else {
    		pkgname = path.Join("test", strings.TrimSuffix(names[0], ".go"))
    		cmd = append(cmd, "-o", pkgname+".a", "-p", pkgname)
    
    	cmd = append(cmd, flags...)
    
    	if *linkshared {
    		cmd = append(cmd, "-dynlink", "-installsuffix=dynlink")
    	}
    
    	for _, name := range names {
    		cmd = append(cmd, filepath.Join(dir, name))
    	}
    	return runcmd(cmd...)
    
    var stdlibImportcfgStringOnce sync.Once // TODO(#56102): Use sync.OnceValue once available. Also below.
    
    var stdlibImportcfgString string
    
    func stdlibImportcfg() string {
    
    	stdlibImportcfgStringOnce.Do(func() {
    
    		cmd := exec.Command(goTool, "list", "-export", "-f", "{{if .Export}}packagefile {{.ImportPath}}={{.Export}}{{end}}", "std")
    		cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=")
    		output, err := cmd.Output()
    
    		if err != nil {
    			log.Fatal(err)
    		}
    		stdlibImportcfgString = string(output)
    	})
    	return stdlibImportcfgString
    }
    
    
    var stdlibImportcfgFilenameOnce sync.Once
    var stdlibImportcfgFilename string
    
    
    func stdlibImportcfgFile() string {
    
    	stdlibImportcfgFilenameOnce.Do(func() {
    
    		tmpdir, err := os.MkdirTemp("", "importcfg")
    		if err != nil {
    			log.Fatal(err)
    		}
    		filename := filepath.Join(tmpdir, "importcfg")
    
    		err = os.WriteFile(filename, []byte(stdlibImportcfg()), 0644)
    		if err != nil {
    			log.Fatal(err)
    		}
    
    		stdlibImportcfgFilename = filename
    	})
    	return stdlibImportcfgFilename
    }
    
    func linkFile(runcmd runCmd, goname string, importcfg string, ldflags []string) (err error) {
    	if importcfg == "" {
    		importcfg = stdlibImportcfgFile()
    	}
    
    	pfile := strings.Replace(goname, ".go", ".o", -1)
    
    	cmd := []string{goTool, "tool", "link", "-w", "-o", "a.exe", "-importcfg=" + importcfg}
    
    	if *linkshared {
    		cmd = append(cmd, "-linkshared", "-installsuffix=dynlink")
    	}
    
    	if ldflags != nil {
    		cmd = append(cmd, ldflags...)
    	}
    
    	cmd = append(cmd, pfile)
    	_, err = runcmd(cmd...)
    
    type testCommon struct {
    	// gorootTestDir is the GOROOT/test directory path.
    	gorootTestDir string
    
    	// runoutputGate controls the max number of runoutput tests
    	// executed in parallel as they can each consume a lot of memory.
    	runoutputGate chan bool
    }
    
    // test is a single test case in the GOROOT/test directory.
    
    	// dir and goFile identify the test case.
    	// For example, "fixedbugs", "bug000.go".
    	dir, goFile string
    
    // expectFail reports whether the (overall) test recipe is
    // expected to fail under the current build+test configuration.
    func (t test) expectFail() bool {
    
    	failureSets := []map[string]bool{types2Failures}
    
    	// Note: gccgo supports more 32-bit architectures than this, but
    	// hopefully the 32-bit failures are fixed before this matters.
    	switch goarch {
    	case "386", "arm", "mips", "mipsle":
    		failureSets = append(failureSets, types2Failures32Bit)
    	}
    
    	testName := path.Join(t.dir, t.goFile) // Test name is '/'-separated.
    
    
    	for _, set := range failureSets {
    
    		if set[testName] {
    			return true
    
    func (t test) goFileName() string {
    	return filepath.Join(t.dir, t.goFile)
    
    func (t test) goDirName() string {
    	return filepath.Join(t.dir, strings.Replace(t.goFile, ".go", ".dir", -1))
    
    // goDirFiles returns .go files in dir.
    func goDirFiles(dir string) (filter []fs.DirEntry, _ error) {
    	files, err := os.ReadDir(dir)
    	if err != nil {
    		return nil, err
    
    	for _, goFile := range files {
    		if filepath.Ext(goFile.Name()) == ".go" {
    			filter = append(filter, goFile)
    
    	return filter, nil
    
    var packageRE = regexp.MustCompile(`(?m)^package ([\p{Lu}\p{Ll}\w]+)`)
    
    func getPackageNameFromSource(fn string) (string, error) {
    
    	data, err := os.ReadFile(fn)
    
    	if err != nil {
    		return "", err
    	}
    	pkgname := packageRE.FindStringSubmatch(string(data))
    	if pkgname == nil {
    		return "", fmt.Errorf("cannot find package name in %s", fn)
    	}
    	return pkgname[1], nil
    }
    
    
    // goDirPkg represents a Go package in some directory.
    
    Russ Cox's avatar
    Russ Cox committed
    type goDirPkg struct {
    	name  string
    	files []string
    }
    
    
    // goDirPackages returns distinct Go packages in dir.
    
    // If singlefilepkgs is set, each file is considered a separate package
    // even if the package names are the same.
    
    func goDirPackages(t *testing.T, dir string, singlefilepkgs bool) []*goDirPkg {
    	files, err := goDirFiles(dir)
    
    Russ Cox's avatar
    Russ Cox committed
    	var pkgs []*goDirPkg
    	m := make(map[string]*goDirPkg)
    
    	for _, file := range files {
    		name := file.Name()
    
    		pkgname, err := getPackageNameFromSource(filepath.Join(dir, name))
    
    Dmitri Shuralyov's avatar
    Dmitri Shuralyov committed
    		if err != nil {
    
    Russ Cox's avatar
    Russ Cox committed
    		p, ok := m[pkgname]
    
    		if singlefilepkgs || !ok {
    
    Russ Cox's avatar
    Russ Cox committed
    			p = &goDirPkg{name: pkgname}
    			pkgs = append(pkgs, p)
    			m[pkgname] = p
    
    Russ Cox's avatar
    Russ Cox committed
    		p.files = append(p.files, name)
    
    type context struct {
    
    	GOOS       string
    	GOARCH     string
    	cgoEnabled bool
    	noOptEnv   bool
    
    // shouldTest looks for build tags in a source file and returns
    // whether the file should be used according to the tags.
    func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) {
    
    	if *runSkips {
    		return true, ""
    	}
    
    	for _, line := range strings.Split(src, "\n") {
    
    		if strings.HasPrefix(line, "package ") {
    			break
    
    		if expr, err := constraint.Parse(line); err == nil {
    			gcFlags := os.Getenv("GO_GCFLAGS")
    			ctxt := &context{
    				GOOS:       goos,
    				GOARCH:     goarch,
    				cgoEnabled: cgoEnabled,
    				noOptEnv:   strings.Contains(gcFlags, "-N") || strings.Contains(gcFlags, "-l"),
    
    
    			if !expr.Eval(ctxt.match) {
    
    				return false, line
    			}
    
    func (ctxt *context) match(name string) bool {
    	if name == "" {
    		return false
    	}
    
    	// Tags must be letters, digits, underscores or dots.
    	// Unlike in Go identifiers, all digits are fine (e.g., "386").
    	for _, c := range name {
    		if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
    			return false
    		}
    	}
    
    
    	if slices.Contains(build.Default.ReleaseTags, name) {
    		return true
    	}
    
    
    	if strings.HasPrefix(name, "goexperiment.") {
    
    		return slices.Contains(build.Default.ToolTags, name)
    
    	if name == "cgo" && ctxt.cgoEnabled {
    		return true
    	}
    
    
    	if name == ctxt.GOOS || name == ctxt.GOARCH || name == "gc" {
    
    	if ctxt.noOptEnv && name == "gcflags_noopt" {
    		return true
    	}
    
    
    	if name == "test_run" {
    		return true
    	}
    
    
    // goGcflags returns the -gcflags argument to use with go build / go run.
    
    // This must match the flags used for building the standard library,
    
    // or else the commands will rebuild any needed packages (like runtime)
    // over and over.
    
    func (test) goGcflags() string {
    
    	return "-gcflags=all=" + os.Getenv("GO_GCFLAGS")
    
    func (test) goGcflagsIsEmpty() bool {
    
    	return "" == os.Getenv("GO_GCFLAGS")
    
    var errTimeout = errors.New("command exceeded time limit")
    
    
    // run runs the test case.
    //
    // When there is a problem, run uses t.Fatal to signify that it's an unskippable
    // infrastructure error (such as failing to read an input file or the test recipe
    // being malformed), or it returns a non-nil error to signify a test case error.
    //
    // t.Error isn't used here to give the caller the opportunity to decide whether
    // the test case failing is expected before promoting it to a real test failure.
    // See expectFail and -f flag.
    func (t test) run() error {
    	srcBytes, err := os.ReadFile(filepath.Join(t.gorootTestDir, t.goFileName()))
    
    		t.Fatal("reading test case .go file:", err)
    
    	} else if bytes.HasPrefix(srcBytes, []byte{'\n'}) {
    
    		t.Fatal(".go file source starts with a newline")
    
    	// Execution recipe is contained in a comment in
    	// the first non-empty line that is not a build constraint.
    	var action string
    	for actionSrc := src; action == "" && actionSrc != ""; {
    		var line string
    		line, actionSrc, _ = strings.Cut(actionSrc, "\n")
    		if constraint.IsGoBuild(line) || constraint.IsPlusBuild(line) {
    			continue
    		}
    		action = strings.TrimSpace(strings.TrimPrefix(line, "//"))
    
    	if action == "" {
    		t.Fatalf("execution recipe not found in GOROOT/test/%s", t.goFileName())
    
    	// Check for build constraints only up to the actual code.
    
    	header, _, ok := strings.Cut(src, "\npackage")
    
    	if !ok {
    		header = action // some files are intentionally malformed
    
    	if ok, why := shouldTest(header, goos, goarch); !ok {
    
    	var args, flags, runenv []string
    
    	wantAuto := false
    
    	singlefilepkgs := false
    
    	f, err := splitQuoted(action)
    	if err != nil {
    
    		t.Fatal("invalid test recipe:", err)
    
    	if len(f) > 0 {
    		action = f[0]
    		args = f[1:]
    	}
    
    	// TODO: Clean up/simplify this switch statement.
    
    	case "compile", "compiledir", "build", "builddir", "buildrundir", "run", "buildrun", "runoutput", "rundir", "runindir", "asmcheck":
    
    		// nothing to do
    
    	case "errorcheckandrundir":
    		wantError = false // should be no error if also will run
    
    	case "errorcheckwithauto":
    		action = "errorcheck"
    		wantAuto = true
    		wantError = true
    
    	case "errorcheck", "errorcheckdir", "errorcheckoutput":
    
    	case "skip":
    
    		t.Fatalf("unknown pattern: %q", action)
    
    	goexp := goExperiment
    
    	godebug := goDebug
    
    	gomodvers := ""
    
    	// collect flags
    	for len(args) > 0 && strings.HasPrefix(args[0], "-") {
    		switch args[0] {
    
    		case "-0":
    			wantError = false
    		case "-s":
    			singlefilepkgs = true
    
    		case "-t": // timeout in seconds
    			args = args[1:]
    			var err error
    			tim, err = strconv.Atoi(args[0])
    			if err != nil {
    
    				t.Fatalf("need number of seconds for -t timeout, got %s instead", args[0])
    
    			if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
    				timeoutScale, err := strconv.Atoi(s)
    				if err != nil {
    
    					t.Fatalf("failed to parse $GO_TEST_TIMEOUT_SCALE = %q as integer: %v", s, err)
    
    		case "-goexperiment": // set GOEXPERIMENT environment
    			args = args[1:]
    
    			if goexp != "" {
    				goexp += ","
    			}
    			goexp += args[0]
    			runenv = append(runenv, "GOEXPERIMENT="+goexp)
    
    		case "-godebug": // set GODEBUG environment
    			args = args[1:]
    			if godebug != "" {
    				godebug += ","
    			}
    			godebug += args[0]
    			runenv = append(runenv, "GODEBUG="+godebug)
    
    
    		case "-gomodversion": // set the GoVersion in generated go.mod files (just runindir ATM)
    			args = args[1:]
    			gomodvers = args[0]
    
    
    		default:
    			flags = append(flags, args[0])
    		}
    		args = args[1:]
    	}
    
    	if action == "errorcheck" {
    		found := false
    		for i, f := range flags {
    			if strings.HasPrefix(f, "-d=") {
    				flags[i] = f + ",ssa/check/on"
    				found = true
    				break
    			}
    		}
    		if !found {
    			flags = append(flags, "-d=ssa/check/on")
    		}
    	}
    
    	tempDir := t.TempDir()
    	err = os.Mkdir(filepath.Join(tempDir, "test"), 0755)
    	if err != nil {
    		t.Fatal(err)
    
    	err = os.WriteFile(filepath.Join(tempDir, t.goFile), srcBytes, 0644)
    
    Dmitri Shuralyov's avatar
    Dmitri Shuralyov committed
    	if err != nil {
    
    		runInDir        = tempDir
    
    	runcmd := func(args ...string) ([]byte, error) {
    		cmd := exec.Command(args[0], args[1:]...)
    		var buf bytes.Buffer
    		cmd.Stdout = &buf
    		cmd.Stderr = &buf
    
    		cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=")
    		if runInDir != "" {
    			cmd.Dir = runInDir
    			// Set PWD to match Dir to speed up os.Getwd in the child process.
    			cmd.Env = append(cmd.Env, "PWD="+cmd.Dir)
    
    		} else {
    			// Default to running in the GOROOT/test directory.
    			cmd.Dir = t.gorootTestDir
    			// Set PWD to match Dir to speed up os.Getwd in the child process.
    			cmd.Env = append(cmd.Env, "PWD="+cmd.Dir)
    
    			cmd.Env = append(cmd.Env, "GOPATH="+tempDir)
    
    		cmd.Env = append(cmd.Env, "STDLIB_IMPORTCFG="+stdlibImportcfgFile())
    
    		cmd.Env = append(cmd.Env, runenv...)
    
    
    		var err error
    
    		if tim != 0 {
    			err = cmd.Start()
    			// This command-timeout code adapted from cmd/go/test.go
    
    			// Note: the Go command uses a more sophisticated timeout
    			// strategy, first sending SIGQUIT (if appropriate for the
    			// OS in question) to try to trigger a stack trace, then
    			// finally much later SIGKILL. If timeouts prove to be a
    			// common problem here, it would be worth porting over
    			// that code as well. See https://do.dev/issue/50973
    			// for more discussion.
    
    			if err == nil {
    				tick := time.NewTimer(time.Duration(tim) * time.Second)
    				done := make(chan error)
    				go func() {
    					done <- cmd.Wait()
    				}()
    				select {
    				case err = <-done:
    					// ok
    				case <-tick.C:
    
    					cmd.Process.Signal(os.Interrupt)
    					time.Sleep(1 * time.Second)
    
    					<-done
    					err = errTimeout
    
    		if err != nil && err != errTimeout {
    
    			err = fmt.Errorf("%s\n%s", err, buf.Bytes())
    		}
    
    		return buf.Bytes(), err
    
    	importcfg := func(pkgs []*goDirPkg) string {
    
    		cfg := stdlibImportcfg()
    		for _, pkg := range pkgs {
    			pkgpath := path.Join("test", strings.TrimSuffix(pkg.files[0], ".go"))
    
    			cfg += "\npackagefile " + pkgpath + "=" + filepath.Join(tempDir, pkgpath+".a")
    		}
    		filename := filepath.Join(tempDir, "importcfg")
    		err := os.WriteFile(filename, []byte(cfg), 0644)
    		if err != nil {
    			t.Fatal(err)
    
    	long := filepath.Join(t.gorootTestDir, t.goFileName())
    
    	switch action {
    
    	default:
    
    		t.Fatalf("unimplemented action %q", action)
    		panic("unreachable")
    
    		// Compile Go file and match the generated assembly
    		// against a set of regexps in comments.
    
    		ops := t.wantedAsmOpcodes(long)
    
    		self := runtime.GOOS + "/" + runtime.GOARCH
    
    		for _, env := range ops.Envs() {
    
    			// Only run checks relevant to the current GOOS/GOARCH,
    			// to avoid triggering a cross-compile of the runtime.
    			if string(env) != self && !strings.HasPrefix(string(env), self+"/") && !*allCodegen {
    				continue
    			}
    
    			// -S=2 forces outermost line numbers when disassembling inlined code.
    			cmdline := []string{"build", "-gcflags", "-S=2"}
    
    
    			// Append flags, but don't override -gcflags=-S=2; add to it instead.
    			for i := 0; i < len(flags); i++ {
    				flag := flags[i]
    				switch {
    				case strings.HasPrefix(flag, "-gcflags="):
    					cmdline[2] += " " + strings.TrimPrefix(flag, "-gcflags=")
    				case strings.HasPrefix(flag, "--gcflags="):
    					cmdline[2] += " " + strings.TrimPrefix(flag, "--gcflags=")
    				case flag == "-gcflags", flag == "--gcflags":
    					i++
    					if i < len(flags) {
    						cmdline[2] += " " + flags[i]
    					}
    				default:
    					cmdline = append(cmdline, flag)
    				}
    			}
    
    
    			cmdline = append(cmdline, long)
    
    			cmd := exec.Command(goTool, cmdline...)
    
    			cmd.Env = append(os.Environ(), env.Environ()...)
    
    			if len(flags) > 0 && flags[0] == "-race" {
    				cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
    			}
    
    
    			var buf bytes.Buffer
    			cmd.Stdout, cmd.Stderr = &buf, &buf
    			if err := cmd.Run(); err != nil {
    
    				t.Log(env, "\n", cmd.Stderr)
    				return err
    
    			err := t.asmCheck(buf.String(), long, env, ops[env])
    			if err != nil {
    				return err
    
    	case "errorcheck":
    
    		// Compile Go file.
    		// Fail if wantError is true and compilation was successful and vice versa.
    		// Match errors produced by gc against errors in comments.
    
    		// TODO(gri) remove need for -C (disable printing of columns in error messages)
    
    		cmdline := []string{goTool, "tool", "compile", "-p=p", "-d=panic", "-C", "-e", "-importcfg=" + stdlibImportcfgFile(), "-o", "a.o"}
    
    		// No need to add -dynlink even if linkshared if we're just checking for errors...
    
    		cmdline = append(cmdline, flags...)
    		cmdline = append(cmdline, long)
    		out, err := runcmd(cmdline...)
    		if wantError {
    			if err == nil {
    
    				return fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
    
    			if err == errTimeout {
    
    				return fmt.Errorf("compilation timed out")
    
    		if *updateErrors {
    			t.updateErrors(string(out), long)
    		}
    
    		return t.errorCheck(string(out), wantAuto, long, t.goFile)
    
    		// Compile Go file.
    
    		_, err := compileFile(runcmd, long, flags)
    		return err
    
    	case "compiledir":
    
    		// Compile all files in the directory as packages in lexicographic order.
    
    		longdir := filepath.Join(t.gorootTestDir, t.goDirName())
    		pkgs := goDirPackages(t.T, longdir, singlefilepkgs)
    		importcfgfile := importcfg(pkgs)
    
    Russ Cox's avatar
    Russ Cox committed
    		for _, pkg := range pkgs {
    
    			_, err := compileInDir(runcmd, longdir, flags, importcfgfile, pkg.name, pkg.files...)
    			if err != nil {
    				return err
    
    	case "errorcheckdir", "errorcheckandrundir":
    
    		flags = append(flags, "-d=panic")
    
    		// Compile and errorCheck all files in the directory as packages in lexicographic order.
    		// If errorcheckdir and wantError, compilation of the last package must fail.
    		// If errorcheckandrundir and wantError, compilation of the package prior the last must fail.
    
    		longdir := filepath.Join(t.gorootTestDir, t.goDirName())
    		pkgs := goDirPackages(t.T, longdir, singlefilepkgs)
    
    		errPkg := len(pkgs) - 1
    		if wantError && action == "errorcheckandrundir" {
    			// The last pkg should compiled successfully and will be run in next case.
    			// Preceding pkg must return an error from compileInDir.
    			errPkg--
    		}
    
    		importcfgfile := importcfg(pkgs)
    
    Russ Cox's avatar
    Russ Cox committed
    		for i, pkg := range pkgs {
    
    			out, err := compileInDir(runcmd, longdir, flags, importcfgfile, pkg.name, pkg.files...)
    
    					return fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
    
    Russ Cox's avatar
    Russ Cox committed
    			for _, name := range pkg.files {
    
    				fullshort = append(fullshort, filepath.Join(longdir, name), name)
    			}
    
    			err = t.errorCheck(string(out), wantAuto, fullshort...)
    			if err != nil {
    				return err
    
    		if action == "errorcheckdir" {
    
    		// Compile all files in the directory as packages in lexicographic order.
    		// In case of errorcheckandrundir, ignore failed compilation of the package before the last.
    		// Link as if the last file is the main package, run it.
    		// Verify the expected output.
    
    		longdir := filepath.Join(t.gorootTestDir, t.goDirName())
    		pkgs := goDirPackages(t.T, longdir, singlefilepkgs)
    
    		// Split flags into gcflags and ldflags
    		ldflags := []string{}
    		for i, fl := range flags {
    			if fl == "-ldflags" {
    				ldflags = flags[i+1:]
    				flags = flags[0:i]
    				break
    			}
    		}
    
    
    		importcfgfile := importcfg(pkgs)
    
    Russ Cox's avatar
    Russ Cox committed
    		for i, pkg := range pkgs {
    
    			_, err := compileInDir(runcmd, longdir, flags, importcfgfile, pkg.name, pkg.files...)
    
    			// Allow this package compilation fail based on conditions below;
    			// its errors were checked in previous case.
    			if err != nil && !(wantError && action == "errorcheckandrundir" && i == len(pkgs)-2) {
    
    			if i == len(pkgs)-1 {
    
    				err = linkFile(runcmd, pkg.files[0], importcfgfile, ldflags)
    
    				var cmd []string
    				cmd = append(cmd, findExecCmd()...)
    
    				cmd = append(cmd, filepath.Join(tempDir, "a.exe"))
    
    				cmd = append(cmd, args...)
    				out, err := runcmd(cmd...)
    
    		// Make a shallow copy of t.goDirName() in its own module and GOPATH, and
    		// run "go run ." in it. The module path (and hence import path prefix) of
    		// the copy is equal to the basename of the source directory.
    		//
    		// It's used when test a requires a full 'go build' in order to compile
    		// the sources, such as when importing multiple packages (issue29612.dir)
    		// or compiling a package containing assembly files (see issue15609.dir),
    		// but still needs to be run to verify the expected output.
    		tempDirIsGOPATH = true
    
    		srcDir := filepath.Join(t.gorootTestDir, t.goDirName())
    
    		gopathSrcDir := filepath.Join(tempDir, "src", modName)
    
    		runInDir = gopathSrcDir
    
    		if err := overlayDir(gopathSrcDir, srcDir); err != nil {
    
    		modVersion := gomodvers
    		if modVersion == "" {
    			modVersion = "1.14"
    		}
    		modFile := fmt.Sprintf("module %s\ngo %s\n", modName, modVersion)
    
    		if err := os.WriteFile(filepath.Join(gopathSrcDir, "go.mod"), []byte(modFile), 0666); err != nil {
    			t.Fatal(err)
    
    		cmd := []string{goTool, "run", t.goGcflags()}
    
    		if *linkshared {
    			cmd = append(cmd, "-linkshared")
    		}
    
    		cmd = append(cmd, flags...)
    
    		cmd = append(cmd, ".")
    		out, err := runcmd(cmd...)
    		if err != nil {
    
    		return t.checkExpectedOutput(out)
    
    	case "build":
    
    		// Build Go file.
    
    		cmd := []string{goTool, "build", t.goGcflags()}
    
    		cmd = append(cmd, flags...)
    		cmd = append(cmd, "-o", "a.exe", long)
    		_, err := runcmd(cmd...)
    
    	case "builddir", "buildrundir":
    
    		// Build an executable from all the .go and .s files in a subdirectory.
    
    		// Run it and verify its output in the buildrundir case.
    
    		longdir := filepath.Join(t.gorootTestDir, t.goDirName())
    		files, err := os.ReadDir(longdir)
    		if err != nil {
    			t.Fatal(err)
    
    		var gos []string
    		var asms []string
    
    		for _, file := range files {
    			switch filepath.Ext(file.Name()) {
    			case ".go":
    
    				gos = append(gos, filepath.Join(longdir, file.Name()))
    
    				asms = append(asms, filepath.Join(longdir, file.Name()))
    
    			emptyHdrFile := filepath.Join(tempDir, "go_asm.h")
    			if err := os.WriteFile(emptyHdrFile, nil, 0666); err != nil {
    				t.Fatalf("write empty go_asm.h: %v", err)
    
    			cmd := []string{goTool, "tool", "asm", "-p=main", "-gensymabis", "-o", "symabis"}
    
    			cmd = append(cmd, asms...)
    			_, err = runcmd(cmd...)
    			if err != nil {
    
    		cmd := []string{goTool, "tool", "compile", "-p=main", "-e", "-D", ".", "-importcfg=" + stdlibImportcfgFile(), "-o", "go.o"}
    
    			cmd = append(cmd, "-asmhdr", "go_asm.h", "-symabis", "symabis")
    
    		cmd = append(cmd, gos...)
    
    		_, err = runcmd(cmd...)
    
    		}
    		objs = append(objs, "go.o")
    		if len(asms) > 0 {
    
    			cmd = []string{goTool, "tool", "asm", "-p=main", "-e", "-I", ".", "-o", "asm.o"}
    
    			cmd = append(cmd, asms...)
    
    			_, err = runcmd(cmd...)
    			if err != nil {
    
    			}
    			objs = append(objs, "asm.o")
    		}
    
    		cmd = []string{goTool, "tool", "pack", "c", "all.a"}
    
    		cmd = append(cmd, objs...)
    		_, err = runcmd(cmd...)
    		if err != nil {
    
    		cmd = []string{goTool, "tool", "link", "-importcfg=" + stdlibImportcfgFile(), "-o", "a.exe", "all.a"}
    
    		_, err = runcmd(cmd...)
    		if err != nil {
    
    
    		if action == "builddir" {