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