github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/tests/gorepo/run.go (about)

     1  //go:build ignore
     2  // +build ignore
     3  
     4  // skip
     5  
     6  // Copyright 2012 The Go Authors.  All rights reserved.
     7  // Use of this source code is governed by a BSD-style
     8  // license that can be found in the LICENSE file.
     9  
    10  // Run runs tests in the test directory.
    11  //
    12  // To run manually with summary, verbose output, and full stack traces of of known failures:
    13  //
    14  //	go run run.go -summary -v -show_known_fails
    15  //
    16  // TODO(bradfitz): docs of some sort, once we figure out how we're changing
    17  // headers of files
    18  package main
    19  
    20  import (
    21  	"bytes"
    22  	"errors"
    23  	"flag"
    24  	"fmt"
    25  	"go/build/constraint"
    26  	"hash/fnv"
    27  	"io"
    28  	"log"
    29  	"os"
    30  	"os/exec"
    31  	"path"
    32  	"path/filepath"
    33  	"regexp"
    34  	"runtime"
    35  	"sort"
    36  	"strconv"
    37  	"strings"
    38  	"time"
    39  	"unicode"
    40  
    41  	gbuild "github.com/gopherjs/gopherjs/build"
    42  )
    43  
    44  // -----------------------------------------------------------------------------
    45  // GOPHERJS: Known test fails for GopherJS compiler.
    46  //
    47  // TODO: Reduce these to zero or as close as possible.
    48  var knownFails = map[string]failReason{
    49  	"fixedbugs/bug114.go":     {desc: "fixedbugs/bug114.go:15:27: B32 (untyped int constant 4294967295) overflows int"},
    50  	"fixedbugs/bug242.go":     {desc: "bad map check 13 false false Error: fail"},
    51  	"fixedbugs/bug260.go":     {desc: "maybe unsupportedFeature, pointer arithm"},
    52  	"fixedbugs/bug262.go":     {desc: "Error: fail"},
    53  	"fixedbugs/bug273.go":     {desc: "BUG: didn't crash:  badcap1"},
    54  	"fixedbugs/bug328.go":     {desc: "incorrect output"},
    55  	"fixedbugs/bug347.go":     {desc: "BUG: bug347: cannot find caller"},
    56  	"fixedbugs/bug348.go":     {desc: "BUG: bug348: cannot find caller"},
    57  	"fixedbugs/bug352.go":     {desc: "BUG: bug352 struct{}"},
    58  	"fixedbugs/bug409.go":     {desc: "1 2 3 4"},
    59  	"fixedbugs/bug433.go":     {desc: "Error: [object Object]"},
    60  	"fixedbugs/issue11656.go": {desc: "Error: Native function not implemented: runtime/debug.setPanicOnFault"},
    61  	"fixedbugs/issue4085b.go": {desc: "Error: got panic JavaScript error: Invalid typed array length, want len out of range"},
    62  	"fixedbugs/issue4316.go":  {desc: "Error: runtime error: invalid memory address or nil pointer dereference"},
    63  	"fixedbugs/issue4388.go":  {desc: "Error: expected <autogenerated>:1 have anonymous function:0"},
    64  	"fixedbugs/issue4562.go":  {desc: "Error: cannot find issue4562.go on stack"},
    65  	"fixedbugs/issue4620.go":  {desc: "map[0:1 1:2], Error: m[i] != 2"},
    66  	"fixedbugs/issue5856.go":  {category: requiresSourceMapSupport},
    67  	"fixedbugs/issue6899.go":  {desc: "incorrect output -0"},
    68  	"fixedbugs/issue7550.go":  {category: neverTerminates, desc: "FATAL ERROR: invalid table size Allocation failed - process out of memory"},
    69  	"fixedbugs/issue7690.go":  {desc: "Error: runtime error: slice bounds out of range"},
    70  	"fixedbugs/issue8047b.go": {desc: "Error: [object Object]"},
    71  
    72  	// Failing due to use of os/exec.Command, which is unsupported. Now skipped via !nacl build tag.
    73  	/*"fixedbugs/bug248.go":        {desc: "os/exec.Command unsupported"},
    74  	"fixedbugs/bug302.go":        {desc: "os/exec.Command unsupported"},
    75  	"fixedbugs/bug345.go":        {desc: "os/exec.Command unsupported"},
    76  	"fixedbugs/bug369.go":        {desc: "os/exec.Command unsupported"},
    77  	"fixedbugs/bug429_run.go":    {desc: "os/exec.Command unsupported"},
    78  	"fixedbugs/issue9862_run.go": {desc: "os/exec.Command unsupported"},*/
    79  	"fixedbugs/issue10607.go": {desc: "os/exec.Command unsupported"},
    80  	"fixedbugs/issue13268.go": {desc: "os/exec.Command unsupported"},
    81  	"fixedbugs/issue14636.go": {desc: "os/exec.Command unsupported"},
    82  
    83  	// These are new tests in Go 1.7.
    84  	"fixedbugs/issue14646.go": {category: unsureIfGopherJSSupportsThisFeature, desc: "tests runtime.Caller behavior in a deferred func in SSA backend... does GopherJS even support runtime.Caller?"},
    85  	"fixedbugs/issue15039.go": {desc: "valid bug but deal with after Go 1.7 support is out? it's likely not a regression"},
    86  	"fixedbugs/issue15281.go": {desc: "also looks valid but deal with after Go 1.7 support is out? it's likely not a regression"},
    87  
    88  	// These are new tests in Go 1.8.
    89  	"fixedbugs/issue17381.go": {category: unsureIfGopherJSSupportsThisFeature, desc: "tests runtime.{Callers,FuncForPC} behavior in a deferred func with garbage on stack... does GopherJS even support runtime.{Callers,FuncForPC}?"},
    90  	"fixedbugs/issue18149.go": {desc: "//line directives with filenames are not correctly parsed, see https://github.com/gopherjs/gopherjs/issues/553."},
    91  
    92  	// These are new tests in Go 1.9.
    93  	"fixedbugs/issue19182.go": {category: neverTerminates, desc: "needs GOMAXPROCS=2"},
    94  	"fixedbugs/issue19040.go": {desc: `panicwrap error text:
    95  			"runtime error: invalid memory address or nil pointer dereference"
    96  		want:
    97  			"value method main.T.F called using nil *T pointer"`},
    98  	"fixedbugs/issue19246.go": {desc: "expected nil pointer dereference panic"}, // Issue https://golang.org/issues/19246: Failed to evaluate some zero-sized values when converting them to interfaces.
    99  
   100  	// These are new tests in Go 1.10.
   101  	"fixedbugs/issue21879.go": {desc: "incorrect output related to runtime.Callers, runtime.CallersFrames, etc."},
   102  	"fixedbugs/issue21887.go": {desc: "incorrect output (although within spec, not worth fixing) for println(^uint64(0)). got: { '$high': 4294967295, '$low': 4294967295, '$val': [Circular] } want: 18446744073709551615"},
   103  	"fixedbugs/issue22660.go": {category: notApplicable, desc: "test of gc compiler, uses os/exec.Command"},
   104  	"fixedbugs/issue23305.go": {desc: "GopherJS fails to compile println(0xffffffff), maybe because 32-bit arch"},
   105  
   106  	// These are new tests in Go 1.11.
   107  	"fixedbugs/issue21221.go":  {category: usesUnsupportedPackage, desc: "uses unsafe package and compares nil pointers"},
   108  	"fixedbugs/issue22662.go":  {desc: "line directives not fully working. Error: got /private/var/folders/b8/66r1c5856mqds1mrf2tjtq8w0000gn/T:1; want ??:1"},
   109  	"fixedbugs/issue22662b.go": {category: usesUnsupportedPackage, desc: "os/exec.Command unsupported"},
   110  	"fixedbugs/issue23188.go":  {desc: "incorrect order of evaluation of index operations"},
   111  	"fixedbugs/issue24547.go":  {desc: "incorrect computing method sets with shadowed methods"},
   112  
   113  	// These are new tests in Go 1.12.
   114  	"fixedbugs/issue23837.go":  {desc: "missing panic on nil pointer-to-empty-struct dereference"},
   115  	"fixedbugs/issue27201.go":  {desc: "incorrect stack trace for nil dereference in inlined function"},
   116  	"fixedbugs/issue27518b.go": {desc: "sigpanic can make dead pointer live again"},
   117  	"fixedbugs/issue29190.go":  {desc: "append does not fail when length overflows", category: neverTerminates},
   118  
   119  	// These are new tests in Go 1.12.9.
   120  	"fixedbugs/issue30977.go": {category: neverTerminates, desc: "does for { runtime.GC() }"},
   121  	"fixedbugs/issue32477.go": {category: notApplicable, desc: "uses runtime.SetFinalizer and runtime.GC"},
   122  
   123  	// These are new tests in Go 1.13-1.16.
   124  	"fixedbugs/issue19113.go":  {category: lowLevelRuntimeDifference, desc: "JavaScript bit shifts by negative amount don't cause an exception"},
   125  	"fixedbugs/issue24491a.go": {category: notApplicable, desc: "tests interaction between unsafe and GC; uses runtime.SetFinalizer()"},
   126  	"fixedbugs/issue24491b.go": {category: notApplicable, desc: "tests interaction between unsafe and GC; uses runtime.SetFinalizer()"},
   127  	"fixedbugs/issue29504.go":  {category: notApplicable, desc: "requires source map support beyond what GopherJS currently provides"},
   128  	// This test incorrectly passes because main function's name is returned as "main" and not "main.main". Even number of bugs cancel each other out ¯\_(ツ)_/¯
   129  	// "fixedbugs/issue29735.go":  {category: usesUnsupportedPackage, desc: "GopherJS only supports runtime.FuncForPC() with position counters previously returned by runtime.Callers() or runtime.Caller()"},
   130  	"fixedbugs/issue30116.go":  {desc: "GopherJS doesn't specify the array/slice index selector in the out-of-bounds message"},
   131  	"fixedbugs/issue30116u.go": {desc: "GopherJS doesn't specify the array/slice index selector in the out-of-bounds message"},
   132  	"fixedbugs/issue34395.go":  {category: neverTerminates, desc: "https://github.com/gopherjs/gopherjs/issues/1007"},
   133  	"fixedbugs/issue35027.go":  {category: usesUnsupportedPackage, desc: "uses unsupported conversion to reflect.SliceHeader and -gcflags=-d=checkptr"},
   134  	"fixedbugs/issue35576.go":  {category: lowLevelRuntimeDifference, desc: "GopherJS print/println format for floats differs from Go's"},
   135  	"fixedbugs/issue40917.go":  {category: notApplicable, desc: "uses pointer arithmetic and unsupported flag -gcflags=-d=checkptr"},
   136  
   137  	// These are new tests in Go 1.17
   138  	"fixedbugs/issue45045.go": {category: notApplicable, desc: "GC related, not relevant to GopherJS"},
   139  	"fixedbugs/issue5493.go":  {category: notApplicable, desc: "GC related, not relevant to GopherJS"},
   140  	"fixedbugs/issue46725.go": {category: notApplicable, desc: "GC related, not relevant to GopherJS"},
   141  	"fixedbugs/issue43444.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format is different from Go's"},
   142  	"fixedbugs/issue23017.go": {desc: "https://github.com/gopherjs/gopherjs/issues/1063"},
   143  
   144  	// These are new tests in Go 1.17.8
   145  	"fixedbugs/issue50854.go": {category: lowLevelRuntimeDifference, desc: "negative int32 overflow behaves differently in JS"},
   146  
   147  	// These are new tests in Go 1.18
   148  	"fixedbugs/issue47928.go":  {category: notApplicable, desc: "//go:nointerface is a part of GOEXPERIMENT=fieldtrack and is not supported by GopherJS"},
   149  	"fixedbugs/issue48536.go":  {category: usesUnsupportedPackage, desc: "https://github.com/gopherjs/gopherjs/issues/1130"},
   150  	"fixedbugs/issue48898.go":  {category: other, desc: "https://github.com/gopherjs/gopherjs/issues/1128"},
   151  	"fixedbugs/issue53600.go":  {category: lowLevelRuntimeDifference, desc: "GopherJS println format is different from Go's"},
   152  	"typeparam/chans.go":       {category: neverTerminates, desc: "uses runtime.SetFinalizer() and runtime.GC()."},
   153  	"typeparam/issue51733.go":  {category: usesUnsupportedPackage, desc: "unsafe: uintptr to struct pointer conversion is unsupported"},
   154  	"typeparam/typeswitch5.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format is different from Go's"},
   155  
   156  	// Failures related to the lack of generics support. Ideally, this section
   157  	// should be emptied once https://github.com/gopherjs/gopherjs/issues/1013 is
   158  	// fixed.
   159  	"typeparam/nested.go": {category: usesUnsupportedGenerics, desc: "incomplete support for generic types inside generic functions"},
   160  
   161  	// These are new tests in Go 1.19
   162  	"typeparam/issue51521.go": {category: lowLevelRuntimeDifference, desc: "different panic message when calling a method on nil interface"},
   163  	"fixedbugs/issue50672.go": {category: other, desc: "https://github.com/gopherjs/gopherjs/issues/1271"},
   164  	"fixedbugs/issue53653.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format of int64 is different from Go's"},
   165  }
   166  
   167  type failCategory uint8
   168  
   169  const (
   170  	other                    failCategory = iota
   171  	neverTerminates                       // Test never terminates (so avoid starting it).
   172  	usesUnsupportedPackage                // Test fails because it imports an unsupported package, e.g., "unsafe".
   173  	requiresSourceMapSupport              // Test fails without source map support (as configured in CI), because it tries to check filename/line number via runtime.Caller.
   174  	usesUnsupportedGenerics               // Test uses generics (type parameters) that are not currently supported.
   175  	compilerPanic
   176  	unsureIfGopherJSSupportsThisFeature
   177  	lowLevelRuntimeDifference // JavaScript runtime behaves differently from Go in ways that are difficult to work around.
   178  	notApplicable             // Test that doesn't need to run under GopherJS; it doesn't apply to the Go language in a general way.
   179  )
   180  
   181  type failReason struct {
   182  	category failCategory
   183  	desc     string
   184  }
   185  
   186  // -----------------------------------------------------------------------------
   187  
   188  var (
   189  	verbose        = flag.Bool("v", false, "verbose. if set, parallelism is set to 1.")
   190  	numParallel    = flag.Int("n", runtime.NumCPU(), "number of parallel tests to run")
   191  	summary        = flag.Bool("summary", false, "show summary of results")
   192  	showSkips      = flag.Bool("show_skips", false, "show skipped tests")
   193  	showKnownFails = flag.Bool("show_known_fails", false, "show full error output of known fails")
   194  	updateErrors   = flag.Bool("update_errors", false, "update error messages in test file based on compiler output")
   195  	runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run")
   196  
   197  	shard  = flag.Int("shard", 0, "shard index to run. Only applicable if -shards is non-zero.")
   198  	shards = flag.Int("shards", 0, "number of shards. If 0, all tests are run. This is used by the continuous build.")
   199  )
   200  
   201  var (
   202  	goos, goarch string
   203  
   204  	// dirs are the directories to look for *.go files in.
   205  	// TODO(bradfitz): just use all directories?
   206  	dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "typeparam"}
   207  
   208  	// ratec controls the max number of tests running at a time.
   209  	ratec chan bool
   210  
   211  	// toRun is the channel of tests to run.
   212  	// It is nil until the first test is started.
   213  	toRun chan *test
   214  
   215  	// rungatec controls the max number of runoutput tests
   216  	// executed in parallel as they can each consume a lot of memory.
   217  	rungatec chan bool
   218  )
   219  
   220  // maxTests is an upper bound on the total number of tests.
   221  // It is used as a channel buffer size to make sure sends don't block.
   222  const maxTests = 5000
   223  
   224  func main() {
   225  	flag.Parse()
   226  
   227  	// GOPHERJS.
   228  	err := os.Chdir(filepath.Join(gbuild.DefaultGOROOT, "test"))
   229  	if err != nil {
   230  		log.Fatalln(err)
   231  	}
   232  
   233  	// GOPHERJS: We're running this script natively, but the tests are executed with js architecture.
   234  	goos = getenv("GOOS", "js")
   235  	goarch = getenv("GOARCH", "ecmascript")
   236  
   237  	findExecCmd()
   238  
   239  	// Disable parallelism if using a simulator.
   240  	// Do not disable parallelism in verbose mode, since Go's file IO had internal
   241  	// r/w locking, which should make significant output garbling very unlikely.
   242  	// GopherJS CI setup runs these tests in verbose mode, but it can benefit from
   243  	// parallelism a lot.
   244  	if len(findExecCmd()) > 0 {
   245  		*numParallel = 1
   246  	}
   247  
   248  	if *verbose {
   249  		fmt.Printf("goos: %q, goarch: %q\n", goos, goarch)
   250  		fmt.Printf("parallel: %d\n", *numParallel)
   251  	}
   252  
   253  	ratec = make(chan bool, *numParallel)
   254  	rungatec = make(chan bool, *runoutputLimit)
   255  
   256  	var tests []*test
   257  	if flag.NArg() > 0 {
   258  		for _, arg := range flag.Args() {
   259  			if arg == "-" || arg == "--" {
   260  				// Permit running:
   261  				// $ go run run.go - env.go
   262  				// $ go run run.go -- env.go
   263  				// $ go run run.go - ./fixedbugs
   264  				// $ go run run.go -- ./fixedbugs
   265  				continue
   266  			}
   267  			if fi, err := os.Stat(arg); err == nil && fi.IsDir() {
   268  				for _, baseGoFile := range goFiles(arg) {
   269  					tests = append(tests, startTest(arg, baseGoFile))
   270  				}
   271  			} else if strings.HasSuffix(arg, ".go") {
   272  				dir, file := filepath.Split(arg)
   273  				tests = append(tests, startTest(dir, file))
   274  			} else {
   275  				log.Fatalf("can't yet deal with non-directory and non-go file %q", arg)
   276  			}
   277  		}
   278  	} else {
   279  		for _, dir := range dirs {
   280  			for _, baseGoFile := range goFiles(dir) {
   281  				tests = append(tests, startTest(dir, baseGoFile))
   282  			}
   283  		}
   284  	}
   285  
   286  	failed := false
   287  	resCount := map[string]int{}
   288  	for _, test := range tests {
   289  		<-test.donec
   290  		// GOPHERJS.
   291  		if test.action == "skip" && !*showSkips {
   292  			continue
   293  		}
   294  		status := "ok  "
   295  		errStr := ""
   296  		// GOPHERJS.
   297  		if _, ok := knownFails[filepath.ToSlash(test.goFileName())]; ok && test.err != nil {
   298  			errStr = test.err.Error()
   299  			test.err = nil
   300  			status = "knfl" // knfl means known failure. Expect test to fail.
   301  		} else if ok && test.err == nil {
   302  			// unok means unexpected okay. Test was expected to fail, but it unexpectedly succeeded.
   303  			// If this is not an accident, it should be removed from knownFails map.
   304  			status = "unok"
   305  		}
   306  		if _, isSkip := test.err.(skipError); isSkip {
   307  			test.err = nil
   308  			errStr = "unexpected skip for " + path.Join(test.dir, test.gofile) + ": " + errStr
   309  			status = "FAIL"
   310  		}
   311  		if test.err != nil {
   312  			status = "FAIL"
   313  			errStr = test.err.Error()
   314  		}
   315  		if status == "FAIL" {
   316  			failed = true
   317  		}
   318  		// GOPHERJS.
   319  		if status == "unok" {
   320  			failed = true
   321  		}
   322  		resCount[status]++
   323  		if status == "skip" && !*verbose && !*showSkips {
   324  			continue
   325  		}
   326  		dt := fmt.Sprintf("%.3fs", test.dt.Seconds())
   327  		if status == "FAIL" {
   328  			fmt.Printf("# go run run.go -- %s\n%s\nFAIL\t%s\t%s\n",
   329  				path.Join(test.dir, test.gofile),
   330  				errStr, test.goFileName(), dt)
   331  			continue
   332  		}
   333  		// GOPHERJS.
   334  		if status == "knfl" && *showKnownFails {
   335  			fmt.Printf("# go run run.go -show_known_fails -- %s\n%s\nknfl\t%s\t%s\n",
   336  				path.Join(test.dir, test.gofile),
   337  				errStr, test.goFileName(), dt)
   338  			continue
   339  		}
   340  		if !*verbose && status != "unok" {
   341  			continue
   342  		}
   343  		fmt.Printf("%s\t%s\t%s\n", status, test.goFileName(), dt)
   344  	}
   345  
   346  	if *summary {
   347  		for k, v := range resCount {
   348  			fmt.Printf("%5d %s\n", v, k)
   349  		}
   350  	}
   351  
   352  	if failed {
   353  		os.Exit(1)
   354  	}
   355  }
   356  
   357  func shardMatch(name string) bool {
   358  	if *shards == 0 {
   359  		return true
   360  	}
   361  	h := fnv.New32()
   362  	io.WriteString(h, name)
   363  	return int(h.Sum32()%uint32(*shards)) == *shard
   364  }
   365  
   366  func goFiles(dir string) []string {
   367  	f, err := os.Open(dir)
   368  	check(err)
   369  	dirnames, err := f.Readdirnames(-1)
   370  	f.Close()
   371  	check(err)
   372  	names := []string{}
   373  	for _, name := range dirnames {
   374  		if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && shardMatch(name) {
   375  			names = append(names, name)
   376  		}
   377  	}
   378  	sort.Strings(names)
   379  	return names
   380  }
   381  
   382  type runCmd func(...string) ([]byte, error)
   383  
   384  func compileFile(runcmd runCmd, longname string) (out []byte, err error) {
   385  	return runcmd("go", "tool", "compile", "-e", longname)
   386  }
   387  
   388  func compileInDir(runcmd runCmd, dir string, names ...string) (out []byte, err error) {
   389  	cmd := []string{"go", "tool", "compile", "-e", "-D", ".", "-I", "."}
   390  	for _, name := range names {
   391  		cmd = append(cmd, filepath.Join(dir, name))
   392  	}
   393  	return runcmd(cmd...)
   394  }
   395  
   396  func linkFile(runcmd runCmd, goname string) (err error) {
   397  	pfile := strings.Replace(goname, ".go", ".o", -1)
   398  	_, err = runcmd("go", "tool", "link", "-w", "-o", "a.exe", "-L", ".", pfile)
   399  	return
   400  }
   401  
   402  // skipError describes why a test was skipped.
   403  type skipError string
   404  
   405  func (s skipError) Error() string { return string(s) }
   406  
   407  func check(err error) {
   408  	if err != nil {
   409  		log.Fatal(err)
   410  	}
   411  }
   412  
   413  // test holds the state of a test.
   414  type test struct {
   415  	dir, gofile string
   416  	donec       chan bool // closed when done
   417  	dt          time.Duration
   418  
   419  	src    string
   420  	action string // "compile", "build", etc.
   421  
   422  	tempDir string
   423  	err     error
   424  }
   425  
   426  // startTest
   427  func startTest(dir, gofile string) *test {
   428  	t := &test{
   429  		dir:    dir,
   430  		gofile: gofile,
   431  		donec:  make(chan bool, 1),
   432  	}
   433  	if toRun == nil {
   434  		toRun = make(chan *test, maxTests)
   435  		go runTests()
   436  	}
   437  	select {
   438  	case toRun <- t:
   439  	default:
   440  		panic("toRun buffer size (maxTests) is too small")
   441  	}
   442  	return t
   443  }
   444  
   445  // runTests runs tests in parallel, but respecting the order they
   446  // were enqueued on the toRun channel.
   447  func runTests() {
   448  	for {
   449  		ratec <- true
   450  		t := <-toRun
   451  		go func() {
   452  			t.run()
   453  			<-ratec
   454  		}()
   455  	}
   456  }
   457  
   458  var cwd, _ = os.Getwd()
   459  
   460  func (t *test) goFileName() string {
   461  	return filepath.Join(t.dir, t.gofile)
   462  }
   463  
   464  func (t *test) goDirName() string {
   465  	return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1))
   466  }
   467  
   468  func goDirFiles(longdir string) (filter []os.DirEntry, err error) {
   469  	files, dirErr := os.ReadDir(longdir)
   470  	if dirErr != nil {
   471  		return nil, dirErr
   472  	}
   473  	for _, gofile := range files {
   474  		if filepath.Ext(gofile.Name()) == ".go" {
   475  			filter = append(filter, gofile)
   476  		}
   477  	}
   478  	return
   479  }
   480  
   481  var packageRE = regexp.MustCompile(`(?m)^package (\w+)`)
   482  
   483  func goDirPackages(longdir string) ([][]string, error) {
   484  	files, err := goDirFiles(longdir)
   485  	if err != nil {
   486  		return nil, err
   487  	}
   488  	var pkgs [][]string
   489  	m := make(map[string]int)
   490  	for _, file := range files {
   491  		name := file.Name()
   492  		data, err := os.ReadFile(filepath.Join(longdir, name))
   493  		if err != nil {
   494  			return nil, err
   495  		}
   496  		pkgname := packageRE.FindStringSubmatch(string(data))
   497  		if pkgname == nil {
   498  			return nil, fmt.Errorf("cannot find package name in %s", name)
   499  		}
   500  		i, ok := m[pkgname[1]]
   501  		if !ok {
   502  			i = len(pkgs)
   503  			pkgs = append(pkgs, nil)
   504  			m[pkgname[1]] = i
   505  		}
   506  		pkgs[i] = append(pkgs[i], name)
   507  	}
   508  	return pkgs, nil
   509  }
   510  
   511  type context struct {
   512  	GOOS   string
   513  	GOARCH string
   514  }
   515  
   516  // shouldTest looks for build tags in a source file and returns
   517  // whether the file should be used according to the tags.
   518  func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) {
   519  	// Custom rule, treat js as equivalent to nacl.
   520  	if goarch == "js" {
   521  		goarch = "nacl"
   522  	}
   523  
   524  	for _, line := range strings.Split(src, "\n") {
   525  		if strings.HasPrefix(line, "package ") {
   526  			break
   527  		}
   528  		if expr, err := constraint.Parse(line); err == nil {
   529  			ctxt := &context{
   530  				GOOS:   goos,
   531  				GOARCH: goarch,
   532  			}
   533  			if !expr.Eval(ctxt.match) {
   534  				return false, line
   535  			}
   536  		}
   537  	}
   538  	return true, ""
   539  }
   540  
   541  func (ctxt *context) match(name string) bool {
   542  	if name == "" {
   543  		return false
   544  	}
   545  
   546  	// Tags must be letters, digits, underscores or dots.
   547  	// Unlike in Go identifiers, all digits are fine (e.g., "386").
   548  	for _, c := range name {
   549  		if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
   550  			return false
   551  		}
   552  	}
   553  
   554  	// GOPHERJS: Ignore "goexperiment." for now
   555  	// GOPHERJS: Don't match "cgo" since not supported
   556  	// GOPHERJS: Don't match "gc"
   557  	if name == ctxt.GOOS || name == ctxt.GOARCH {
   558  		return true
   559  	}
   560  
   561  	// GOPHERJS: Don't match "gcflags_noopt"
   562  	if name == "test_run" {
   563  		return true
   564  	}
   565  
   566  	return false
   567  }
   568  
   569  func init() { checkShouldTest() }
   570  
   571  // run runs a test.
   572  func (t *test) run() {
   573  	start := time.Now()
   574  	defer func() {
   575  		t.dt = time.Since(start)
   576  		close(t.donec)
   577  	}()
   578  
   579  	// GOPHERJS: Some tests may never terminate once started. Avoid starting them.
   580  	if kf, ok := knownFails[filepath.ToSlash(t.goFileName())]; ok && kf.category == neverTerminates {
   581  		t.err = skipError("skipping because it doesn't terminate")
   582  		return
   583  	}
   584  
   585  	srcBytes, err := os.ReadFile(t.goFileName())
   586  	if err != nil {
   587  		t.err = err
   588  		return
   589  	}
   590  	t.src = string(srcBytes)
   591  	if t.src[0] == '\n' {
   592  		t.err = skipError("starts with newline")
   593  		return
   594  	}
   595  
   596  	// Execution recipe stops at first blank line.
   597  	action, _, ok := strings.Cut(t.src, "\n\n")
   598  	if !ok {
   599  		t.err = fmt.Errorf("double newline ending execution recipe not found in %s", t.goFileName())
   600  		return
   601  	}
   602  	if firstLine, rest, ok := strings.Cut(action, "\n"); ok && strings.Contains(firstLine, "+build") {
   603  		// skip first line
   604  		action = rest
   605  	}
   606  	action = strings.TrimPrefix(action, "//")
   607  
   608  	// Check for build constraints only up to the actual code.
   609  	header, _, ok := strings.Cut(t.src, "\npackage")
   610  	if !ok {
   611  		header = action // some files are intentionally malformed
   612  	}
   613  	if ok, why := shouldTest(header, goos, goarch); !ok {
   614  		t.action = "skip"
   615  		if *showSkips {
   616  			fmt.Printf("%-20s %-20s: %s\n", t.action, t.goFileName(), why)
   617  		}
   618  		return
   619  	}
   620  
   621  	var args, flags []string
   622  	wantError := false
   623  	f, err := splitQuoted(action)
   624  	if err != nil {
   625  		t.err = fmt.Errorf("invalid test recipe: %v", err)
   626  		return
   627  	}
   628  	if len(f) > 0 {
   629  		action = f[0]
   630  		args = f[1:]
   631  	}
   632  
   633  	// GOPHERJS: For now, only run with "run", "cmpout" actions, in "fixedbugs" and "typeparam" dirs. Skip all others.
   634  	switch action {
   635  	case "run", "cmpout":
   636  		if d := filepath.Clean(t.dir); d != "fixedbugs" && d != "typeparam" {
   637  			action = "skip"
   638  		}
   639  	default:
   640  		action = "skip"
   641  	}
   642  
   643  	switch action {
   644  	case "rundircmpout":
   645  		action = "rundir"
   646  		t.action = "rundir"
   647  	case "cmpout":
   648  		action = "run" // the run case already looks for <dir>/<test>.out files
   649  		fallthrough
   650  	case "compile", "compiledir", "build", "run", "runoutput", "rundir":
   651  		t.action = action
   652  	case "errorcheck", "errorcheckdir", "errorcheckoutput":
   653  		t.action = action
   654  		wantError = true
   655  		for len(args) > 0 && strings.HasPrefix(args[0], "-") {
   656  			if args[0] == "-0" {
   657  				wantError = false
   658  			} else {
   659  				flags = append(flags, args[0])
   660  			}
   661  			args = args[1:]
   662  		}
   663  	case "skip":
   664  		t.action = "skip"
   665  		return
   666  	default:
   667  		t.err = skipError("skipped; unknown pattern: " + action)
   668  		t.action = "??"
   669  		return
   670  	}
   671  
   672  	t.makeTempDir()
   673  	defer os.RemoveAll(t.tempDir)
   674  
   675  	err = os.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0o644)
   676  	check(err)
   677  
   678  	// A few tests (of things like the environment) require these to be set.
   679  	if os.Getenv("GOOS") == "" {
   680  		os.Setenv("GOOS", goos)
   681  	}
   682  	if os.Getenv("GOARCH") == "" {
   683  		os.Setenv("GOARCH", goarch)
   684  	}
   685  
   686  	{
   687  		// GopherJS: we don't support any of -gcflags, but for the most part they
   688  		// are not too relevant to the outcome of the test.
   689  		supportedArgs := []string{}
   690  		for _, a := range args {
   691  			if strings.HasPrefix(a, "-gcflags") {
   692  				continue
   693  			}
   694  			supportedArgs = append(supportedArgs, a)
   695  		}
   696  		args = supportedArgs
   697  	}
   698  
   699  	useTmp := true
   700  	runcmd := func(args ...string) ([]byte, error) {
   701  		cmd := exec.Command(args[0], args[1:]...)
   702  		var buf bytes.Buffer
   703  		cmd.Stdout = &buf
   704  		cmd.Stderr = &buf
   705  		if useTmp {
   706  			cmd.Dir = t.tempDir
   707  			cmd.Env = envForDir(cmd.Dir)
   708  		}
   709  		err := cmd.Run()
   710  		if err != nil {
   711  			err = fmt.Errorf("%s\n%s", err, buf.Bytes())
   712  		}
   713  		return buf.Bytes(), err
   714  	}
   715  
   716  	long := filepath.Join(cwd, t.goFileName())
   717  	switch action {
   718  	default:
   719  		t.err = fmt.Errorf("unimplemented action %q", action)
   720  
   721  	case "errorcheck":
   722  		cmdline := []string{"go", "tool", "compile", "-e", "-o", "a.o"}
   723  		cmdline = append(cmdline, flags...)
   724  		cmdline = append(cmdline, long)
   725  		out, err := runcmd(cmdline...)
   726  		if wantError {
   727  			if err == nil {
   728  				t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
   729  				return
   730  			}
   731  		} else {
   732  			if err != nil {
   733  				t.err = err
   734  				return
   735  			}
   736  		}
   737  		if *updateErrors {
   738  			t.updateErrors(string(out), long)
   739  		}
   740  		t.err = t.errorCheck(string(out), long, t.gofile)
   741  		return
   742  
   743  	case "compile":
   744  		_, t.err = compileFile(runcmd, long)
   745  
   746  	case "compiledir":
   747  		// Compile all files in the directory in lexicographic order.
   748  		longdir := filepath.Join(cwd, t.goDirName())
   749  		pkgs, err := goDirPackages(longdir)
   750  		if err != nil {
   751  			t.err = err
   752  			return
   753  		}
   754  		for _, gofiles := range pkgs {
   755  			_, t.err = compileInDir(runcmd, longdir, gofiles...)
   756  			if t.err != nil {
   757  				return
   758  			}
   759  		}
   760  
   761  	case "errorcheckdir":
   762  		// errorcheck all files in lexicographic order
   763  		// useful for finding importing errors
   764  		longdir := filepath.Join(cwd, t.goDirName())
   765  		pkgs, err := goDirPackages(longdir)
   766  		if err != nil {
   767  			t.err = err
   768  			return
   769  		}
   770  		for i, gofiles := range pkgs {
   771  			out, err := compileInDir(runcmd, longdir, gofiles...)
   772  			if i == len(pkgs)-1 {
   773  				if wantError && err == nil {
   774  					t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
   775  					return
   776  				} else if !wantError && err != nil {
   777  					t.err = err
   778  					return
   779  				}
   780  			} else if err != nil {
   781  				t.err = err
   782  				return
   783  			}
   784  			var fullshort []string
   785  			for _, name := range gofiles {
   786  				fullshort = append(fullshort, filepath.Join(longdir, name), name)
   787  			}
   788  			t.err = t.errorCheck(string(out), fullshort...)
   789  			if t.err != nil {
   790  				break
   791  			}
   792  		}
   793  
   794  	case "rundir":
   795  		// Compile all files in the directory in lexicographic order.
   796  		// then link as if the last file is the main package and run it
   797  		longdir := filepath.Join(cwd, t.goDirName())
   798  		pkgs, err := goDirPackages(longdir)
   799  		if err != nil {
   800  			t.err = err
   801  			return
   802  		}
   803  		for i, gofiles := range pkgs {
   804  			_, err := compileInDir(runcmd, longdir, gofiles...)
   805  			if err != nil {
   806  				t.err = err
   807  				return
   808  			}
   809  			if i == len(pkgs)-1 {
   810  				err = linkFile(runcmd, gofiles[0])
   811  				if err != nil {
   812  					t.err = err
   813  					return
   814  				}
   815  				var cmd []string
   816  				cmd = append(cmd, findExecCmd()...)
   817  				cmd = append(cmd, filepath.Join(t.tempDir, "a.exe"))
   818  				cmd = append(cmd, args...)
   819  				out, err := runcmd(cmd...)
   820  				if err != nil {
   821  					t.err = err
   822  					return
   823  				}
   824  				if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() {
   825  					t.err = fmt.Errorf("incorrect output\n%s", out)
   826  				}
   827  			}
   828  		}
   829  
   830  	case "build":
   831  		_, err := runcmd("go", "build", "-o", "a.exe", long)
   832  		if err != nil {
   833  			t.err = err
   834  		}
   835  
   836  	case "run":
   837  		useTmp = false
   838  		// GOPHERJS.
   839  		out, err := runcmd(append([]string{"gopherjs", "run", t.goFileName()}, args...)...)
   840  		if err != nil {
   841  			t.err = err
   842  			return
   843  		}
   844  		if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() {
   845  			t.err = fmt.Errorf("incorrect output\n%s", out)
   846  		}
   847  
   848  	case "runoutput":
   849  		rungatec <- true
   850  		defer func() {
   851  			<-rungatec
   852  		}()
   853  		useTmp = false
   854  		out, err := runcmd(append([]string{"go", "run", t.goFileName()}, args...)...)
   855  		if err != nil {
   856  			t.err = err
   857  			return
   858  		}
   859  		tfile := filepath.Join(t.tempDir, "tmp__.go")
   860  		if err := os.WriteFile(tfile, out, 0o666); err != nil {
   861  			t.err = fmt.Errorf("write tempfile:%s", err)
   862  			return
   863  		}
   864  		out, err = runcmd("go", "run", tfile)
   865  		if err != nil {
   866  			t.err = err
   867  			return
   868  		}
   869  		if string(out) != t.expectedOutput() {
   870  			t.err = fmt.Errorf("incorrect output\n%s", out)
   871  		}
   872  
   873  	case "errorcheckoutput":
   874  		useTmp = false
   875  		out, err := runcmd(append([]string{"go", "run", t.goFileName()}, args...)...)
   876  		if err != nil {
   877  			t.err = err
   878  			return
   879  		}
   880  		tfile := filepath.Join(t.tempDir, "tmp__.go")
   881  		err = os.WriteFile(tfile, out, 0o666)
   882  		if err != nil {
   883  			t.err = fmt.Errorf("write tempfile:%s", err)
   884  			return
   885  		}
   886  		cmdline := []string{"go", "tool", "compile", "-e", "-o", "a.o"}
   887  		cmdline = append(cmdline, flags...)
   888  		cmdline = append(cmdline, tfile)
   889  		out, err = runcmd(cmdline...)
   890  		if wantError {
   891  			if err == nil {
   892  				t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
   893  				return
   894  			}
   895  		} else {
   896  			if err != nil {
   897  				t.err = err
   898  				return
   899  			}
   900  		}
   901  		t.err = t.errorCheck(string(out), tfile, "tmp__.go")
   902  		return
   903  	}
   904  }
   905  
   906  var execCmd []string
   907  
   908  func findExecCmd() []string {
   909  	if execCmd != nil {
   910  		return execCmd
   911  	}
   912  	execCmd = []string{} // avoid work the second time
   913  	if goos == runtime.GOOS && goarch == runtime.GOARCH {
   914  		return execCmd
   915  	}
   916  	path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch))
   917  	if err == nil {
   918  		execCmd = []string{path}
   919  	}
   920  	return execCmd
   921  }
   922  
   923  func (t *test) String() string {
   924  	return filepath.Join(t.dir, t.gofile)
   925  }
   926  
   927  func (t *test) makeTempDir() {
   928  	var err error
   929  	t.tempDir, err = os.MkdirTemp("", "")
   930  	check(err)
   931  }
   932  
   933  func (t *test) expectedOutput() string {
   934  	filename := filepath.Join(t.dir, t.gofile)
   935  	filename = filename[:len(filename)-len(".go")]
   936  	filename += ".out"
   937  	b, _ := os.ReadFile(filename)
   938  	return string(b)
   939  }
   940  
   941  func splitOutput(out string) []string {
   942  	// gc error messages continue onto additional lines with leading tabs.
   943  	// Split the output at the beginning of each line that doesn't begin with a tab.
   944  	// <autogenerated> lines are impossible to match so those are filtered out.
   945  	var res []string
   946  	for _, line := range strings.Split(out, "\n") {
   947  		if strings.HasSuffix(line, "\r") { // remove '\r', output by compiler on windows
   948  			line = line[:len(line)-1]
   949  		}
   950  		if strings.HasPrefix(line, "\t") {
   951  			res[len(res)-1] += "\n" + line
   952  		} else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "<autogenerated>") {
   953  			continue
   954  		} else if strings.TrimSpace(line) != "" {
   955  			res = append(res, line)
   956  		}
   957  	}
   958  	return res
   959  }
   960  
   961  func (t *test) errorCheck(outStr string, fullshort ...string) (err error) {
   962  	defer func() {
   963  		if *verbose && err != nil {
   964  			log.Printf("%s gc output:\n%s", t, outStr)
   965  		}
   966  	}()
   967  	var errs []error
   968  	out := splitOutput(outStr)
   969  
   970  	// Cut directory name.
   971  	for i := range out {
   972  		for j := 0; j < len(fullshort); j += 2 {
   973  			full, short := fullshort[j], fullshort[j+1]
   974  			out[i] = strings.Replace(out[i], full, short, -1)
   975  		}
   976  	}
   977  
   978  	var want []wantedError
   979  	for j := 0; j < len(fullshort); j += 2 {
   980  		full, short := fullshort[j], fullshort[j+1]
   981  		want = append(want, t.wantedErrors(full, short)...)
   982  	}
   983  
   984  	for _, we := range want {
   985  		var errmsgs []string
   986  		errmsgs, out = partitionStrings(we.prefix, out)
   987  		if len(errmsgs) == 0 {
   988  			errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr))
   989  			continue
   990  		}
   991  		matched := false
   992  		n := len(out)
   993  		for _, errmsg := range errmsgs {
   994  			if we.re.MatchString(errmsg) {
   995  				matched = true
   996  			} else {
   997  				out = append(out, errmsg)
   998  			}
   999  		}
  1000  		if !matched {
  1001  			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")))
  1002  			continue
  1003  		}
  1004  	}
  1005  
  1006  	if len(out) > 0 {
  1007  		errs = append(errs, fmt.Errorf("Unmatched Errors:"))
  1008  		for _, errLine := range out {
  1009  			errs = append(errs, fmt.Errorf("%s", errLine))
  1010  		}
  1011  	}
  1012  
  1013  	if len(errs) == 0 {
  1014  		return nil
  1015  	}
  1016  	if len(errs) == 1 {
  1017  		return errs[0]
  1018  	}
  1019  	var buf bytes.Buffer
  1020  	fmt.Fprintf(&buf, "\n")
  1021  	for _, err := range errs {
  1022  		fmt.Fprintf(&buf, "%s\n", err.Error())
  1023  	}
  1024  	return errors.New(buf.String())
  1025  }
  1026  
  1027  func (t *test) updateErrors(out string, file string) {
  1028  	// Read in source file.
  1029  	src, err := os.ReadFile(file)
  1030  	if err != nil {
  1031  		fmt.Fprintln(os.Stderr, err)
  1032  		return
  1033  	}
  1034  	lines := strings.Split(string(src), "\n")
  1035  	// Remove old errors.
  1036  	for i, ln := range lines {
  1037  		pos := strings.Index(ln, " // ERROR ")
  1038  		if pos >= 0 {
  1039  			lines[i] = ln[:pos]
  1040  		}
  1041  	}
  1042  	// Parse new errors.
  1043  	errors := make(map[int]map[string]bool)
  1044  	tmpRe := regexp.MustCompile(`autotmp_[0-9]+`)
  1045  	for _, errStr := range splitOutput(out) {
  1046  		colon1 := strings.Index(errStr, ":")
  1047  		if colon1 < 0 || errStr[:colon1] != file {
  1048  			continue
  1049  		}
  1050  		colon2 := strings.Index(errStr[colon1+1:], ":")
  1051  		if colon2 < 0 {
  1052  			continue
  1053  		}
  1054  		colon2 += colon1 + 1
  1055  		line, err := strconv.Atoi(errStr[colon1+1 : colon2])
  1056  		line--
  1057  		if err != nil || line < 0 || line >= len(lines) {
  1058  			continue
  1059  		}
  1060  		msg := errStr[colon2+2:]
  1061  		for _, r := range []string{`\`, `*`, `+`, `[`, `]`, `(`, `)`} {
  1062  			msg = strings.Replace(msg, r, `\`+r, -1)
  1063  		}
  1064  		msg = strings.Replace(msg, `"`, `.`, -1)
  1065  		msg = tmpRe.ReplaceAllLiteralString(msg, `autotmp_[0-9]+`)
  1066  		if errors[line] == nil {
  1067  			errors[line] = make(map[string]bool)
  1068  		}
  1069  		errors[line][msg] = true
  1070  	}
  1071  	// Add new errors.
  1072  	for line, errs := range errors {
  1073  		var sorted []string
  1074  		for e := range errs {
  1075  			sorted = append(sorted, e)
  1076  		}
  1077  		sort.Strings(sorted)
  1078  		lines[line] += " // ERROR"
  1079  		for _, e := range sorted {
  1080  			lines[line] += fmt.Sprintf(` "%s$"`, e)
  1081  		}
  1082  	}
  1083  	// Write new file.
  1084  	err = os.WriteFile(file, []byte(strings.Join(lines, "\n")), 0o640)
  1085  	if err != nil {
  1086  		fmt.Fprintln(os.Stderr, err)
  1087  		return
  1088  	}
  1089  	// Polish.
  1090  	exec.Command("go", "fmt", file).CombinedOutput()
  1091  }
  1092  
  1093  // matchPrefix reports whether s is of the form ^(.*/)?prefix(:|[),
  1094  // That is, it needs the file name prefix followed by a : or a [,
  1095  // and possibly preceded by a directory name.
  1096  func matchPrefix(s, prefix string) bool {
  1097  	i := strings.Index(s, ":")
  1098  	if i < 0 {
  1099  		return false
  1100  	}
  1101  	j := strings.LastIndex(s[:i], "/")
  1102  	s = s[j+1:]
  1103  	if len(s) <= len(prefix) || s[:len(prefix)] != prefix {
  1104  		return false
  1105  	}
  1106  	switch s[len(prefix)] {
  1107  	case '[', ':':
  1108  		return true
  1109  	}
  1110  	return false
  1111  }
  1112  
  1113  func partitionStrings(prefix string, strs []string) (matched, unmatched []string) {
  1114  	for _, s := range strs {
  1115  		if matchPrefix(s, prefix) {
  1116  			matched = append(matched, s)
  1117  		} else {
  1118  			unmatched = append(unmatched, s)
  1119  		}
  1120  	}
  1121  	return
  1122  }
  1123  
  1124  type wantedError struct {
  1125  	reStr   string
  1126  	re      *regexp.Regexp
  1127  	lineNum int
  1128  	file    string
  1129  	prefix  string
  1130  }
  1131  
  1132  var (
  1133  	errRx       = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
  1134  	errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
  1135  	lineRx      = regexp.MustCompile(`LINE(([+-])([0-9]+))?`)
  1136  )
  1137  
  1138  func (t *test) wantedErrors(file, short string) (errs []wantedError) {
  1139  	cache := make(map[string]*regexp.Regexp)
  1140  
  1141  	src, _ := os.ReadFile(file)
  1142  	for i, line := range strings.Split(string(src), "\n") {
  1143  		lineNum := i + 1
  1144  		if strings.Contains(line, "////") {
  1145  			// double comment disables ERROR
  1146  			continue
  1147  		}
  1148  		m := errRx.FindStringSubmatch(line)
  1149  		if m == nil {
  1150  			continue
  1151  		}
  1152  		all := m[1]
  1153  		mm := errQuotesRx.FindAllStringSubmatch(all, -1)
  1154  		if mm == nil {
  1155  			log.Fatalf("%s:%d: invalid errchk line: %s", t.goFileName(), lineNum, line)
  1156  		}
  1157  		for _, m := range mm {
  1158  			rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
  1159  				n := lineNum
  1160  				if strings.HasPrefix(m, "LINE+") {
  1161  					delta, _ := strconv.Atoi(m[5:])
  1162  					n += delta
  1163  				} else if strings.HasPrefix(m, "LINE-") {
  1164  					delta, _ := strconv.Atoi(m[5:])
  1165  					n -= delta
  1166  				}
  1167  				return fmt.Sprintf("%s:%d", short, n)
  1168  			})
  1169  			re := cache[rx]
  1170  			if re == nil {
  1171  				var err error
  1172  				re, err = regexp.Compile(rx)
  1173  				if err != nil {
  1174  					log.Fatalf("%s:%d: invalid regexp \"%s\" in ERROR line: %v", t.goFileName(), lineNum, rx, err)
  1175  				}
  1176  				cache[rx] = re
  1177  			}
  1178  			prefix := fmt.Sprintf("%s:%d", short, lineNum)
  1179  			errs = append(errs, wantedError{
  1180  				reStr:   rx,
  1181  				re:      re,
  1182  				prefix:  prefix,
  1183  				lineNum: lineNum,
  1184  				file:    short,
  1185  			})
  1186  		}
  1187  	}
  1188  
  1189  	return
  1190  }
  1191  
  1192  // defaultRunOutputLimit returns the number of runoutput tests that
  1193  // can be executed in parallel.
  1194  func defaultRunOutputLimit() int {
  1195  	const maxArmCPU = 2
  1196  
  1197  	cpu := runtime.NumCPU()
  1198  	if runtime.GOARCH == "arm" && cpu > maxArmCPU {
  1199  		cpu = maxArmCPU
  1200  	}
  1201  	return cpu
  1202  }
  1203  
  1204  // checkShouldTest runs sanity checks on the shouldTest function.
  1205  func checkShouldTest() {
  1206  	assert := func(ok bool, _ string) {
  1207  		if !ok {
  1208  			panic("fail")
  1209  		}
  1210  	}
  1211  	assertNot := func(ok bool, _ string) { assert(!ok, "") }
  1212  
  1213  	// Simple tests.
  1214  	assert(shouldTest("// +build linux", "linux", "arm"))
  1215  	assert(shouldTest("// +build !windows", "linux", "arm"))
  1216  	assertNot(shouldTest("// +build !windows", "windows", "amd64"))
  1217  
  1218  	// A file with no build tags will always be tested.
  1219  	assert(shouldTest("// This is a test.", "os", "arch"))
  1220  
  1221  	// Build tags separated by a space are OR-ed together.
  1222  	assertNot(shouldTest("// +build arm 386", "linux", "amd64"))
  1223  
  1224  	// Build tags separated by a comma are AND-ed together.
  1225  	assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64"))
  1226  	assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386"))
  1227  
  1228  	// Build tags on multiple lines are AND-ed together.
  1229  	assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64"))
  1230  	assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64"))
  1231  
  1232  	// Test that (!a OR !b) matches anything.
  1233  	assert(shouldTest("// +build !windows !plan9", "windows", "amd64"))
  1234  
  1235  	// GOPHERJS: Custom rule, test that don't run on nacl should also not run on js.
  1236  	assertNot(shouldTest("// +build !nacl,!plan9,!windows", "darwin", "js"))
  1237  }
  1238  
  1239  // envForDir returns a copy of the environment
  1240  // suitable for running in the given directory.
  1241  // The environment is the current process's environment
  1242  // but with an updated $PWD, so that an os.Getwd in the
  1243  // child will be faster.
  1244  func envForDir(dir string) []string {
  1245  	env := os.Environ()
  1246  	for i, kv := range env {
  1247  		if strings.HasPrefix(kv, "PWD=") {
  1248  			env[i] = "PWD=" + dir
  1249  			return env
  1250  		}
  1251  	}
  1252  	env = append(env, "PWD="+dir)
  1253  	return env
  1254  }
  1255  
  1256  func getenv(key, def string) string {
  1257  	value := os.Getenv(key)
  1258  	if value != "" {
  1259  		return value
  1260  	}
  1261  	return def
  1262  }
  1263  
  1264  // splitQuoted splits the string s around each instance of one or more consecutive
  1265  // white space characters while taking into account quotes and escaping, and
  1266  // returns an array of substrings of s or an empty list if s contains only white space.
  1267  // Single quotes and double quotes are recognized to prevent splitting within the
  1268  // quoted region, and are removed from the resulting substrings. If a quote in s
  1269  // isn't closed err will be set and r will have the unclosed argument as the
  1270  // last element. The backslash is used for escaping.
  1271  //
  1272  // For example, the following string:
  1273  //
  1274  //	a b:"c d" 'e''f'  "g\""
  1275  //
  1276  // Would be parsed as:
  1277  //
  1278  //	[]string{"a", "b:c d", "ef", `g"`}
  1279  //
  1280  // [copied from src/go/build/build.go]
  1281  func splitQuoted(s string) (r []string, err error) {
  1282  	var args []string
  1283  	arg := make([]rune, len(s))
  1284  	escaped := false
  1285  	quoted := false
  1286  	quote := '\x00'
  1287  	i := 0
  1288  	for _, rune := range s {
  1289  		switch {
  1290  		case escaped:
  1291  			escaped = false
  1292  		case rune == '\\':
  1293  			escaped = true
  1294  			continue
  1295  		case quote != '\x00':
  1296  			if rune == quote {
  1297  				quote = '\x00'
  1298  				continue
  1299  			}
  1300  		case rune == '"' || rune == '\'':
  1301  			quoted = true
  1302  			quote = rune
  1303  			continue
  1304  		case unicode.IsSpace(rune):
  1305  			if quoted || i > 0 {
  1306  				quoted = false
  1307  				args = append(args, string(arg[:i]))
  1308  				i = 0
  1309  			}
  1310  			continue
  1311  		}
  1312  		arg[i] = rune
  1313  		i++
  1314  	}
  1315  	if quoted || i > 0 {
  1316  		args = append(args, string(arg[:i]))
  1317  	}
  1318  	if quote != 0 {
  1319  		err = errors.New("unclosed quote")
  1320  	} else if escaped {
  1321  		err = errors.New("unfinished escaping")
  1322  	}
  1323  	return args, err
  1324  }