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