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