Source file test/run.go

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

View as plain text