1// skip
2
3// Copyright 2012 The Go Authors.  All rights reserved.
4// Use of this source code is governed by a BSD-style
5// license that can be found in the LICENSE file.
6
7// Run runs tests in the test directory.
8//
9// TODO(bradfitz): docs of some sort, once we figure out how we're changing
10// headers of files
11package main
12
13import (
14	"bytes"
15	"errors"
16	"flag"
17	"fmt"
18	"go/build"
19	"io/ioutil"
20	"log"
21	"os"
22	"os/exec"
23	"path"
24	"path/filepath"
25	"regexp"
26	"runtime"
27	"sort"
28	"strconv"
29	"strings"
30	"time"
31	"unicode"
32)
33
34var (
35	verbose        = flag.Bool("v", false, "verbose. if set, parallelism is set to 1.")
36	numParallel    = flag.Int("n", runtime.NumCPU(), "number of parallel tests to run")
37	summary        = flag.Bool("summary", false, "show summary of results")
38	showSkips      = flag.Bool("show_skips", false, "show skipped tests")
39	runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run")
40)
41
42var (
43	// gc and ld are [568][gl].
44	gc, ld string
45
46	// letter is the build.ArchChar
47	letter string
48
49	// dirs are the directories to look for *.go files in.
50	// TODO(bradfitz): just use all directories?
51	dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "bugs"}
52
53	// ratec controls the max number of tests running at a time.
54	ratec chan bool
55
56	// toRun is the channel of tests to run.
57	// It is nil until the first test is started.
58	toRun chan *test
59
60	// rungatec controls the max number of runoutput tests
61	// executed in parallel as they can each consume a lot of memory.
62	rungatec chan bool
63)
64
65// maxTests is an upper bound on the total number of tests.
66// It is used as a channel buffer size to make sure sends don't block.
67const maxTests = 5000
68
69func main() {
70	flag.Parse()
71
72	// Disable parallelism if printing
73	if *verbose {
74		*numParallel = 1
75	}
76
77	ratec = make(chan bool, *numParallel)
78	rungatec = make(chan bool, *runoutputLimit)
79	var err error
80	letter, err = build.ArchChar(build.Default.GOARCH)
81	check(err)
82	gc = letter + "g"
83	ld = letter + "l"
84
85	var tests []*test
86	if flag.NArg() > 0 {
87		for _, arg := range flag.Args() {
88			if arg == "-" || arg == "--" {
89				// Permit running:
90				// $ go run run.go - env.go
91				// $ go run run.go -- env.go
92				// $ go run run.go - ./fixedbugs
93				// $ go run run.go -- ./fixedbugs
94				continue
95			}
96			if fi, err := os.Stat(arg); err == nil && fi.IsDir() {
97				for _, baseGoFile := range goFiles(arg) {
98					tests = append(tests, startTest(arg, baseGoFile))
99				}
100			} else if strings.HasSuffix(arg, ".go") {
101				dir, file := filepath.Split(arg)
102				tests = append(tests, startTest(dir, file))
103			} else {
104				log.Fatalf("can't yet deal with non-directory and non-go file %q", arg)
105			}
106		}
107	} else {
108		for _, dir := range dirs {
109			for _, baseGoFile := range goFiles(dir) {
110				tests = append(tests, startTest(dir, baseGoFile))
111			}
112		}
113	}
114
115	failed := false
116	resCount := map[string]int{}
117	for _, test := range tests {
118		<-test.donec
119		status := "ok  "
120		errStr := ""
121		if _, isSkip := test.err.(skipError); isSkip {
122			status = "skip"
123			test.err = nil
124			if !skipOkay[path.Join(test.dir, test.gofile)] {
125				errStr = "unexpected skip for " + path.Join(test.dir, test.gofile) + ": " + errStr
126				status = "FAIL"
127			}
128		}
129		if test.err != nil {
130			status = "FAIL"
131			errStr = test.err.Error()
132		}
133		if status == "FAIL" {
134			failed = true
135		}
136		resCount[status]++
137		if status == "skip" && !*verbose && !*showSkips {
138			continue
139		}
140		dt := fmt.Sprintf("%.3fs", test.dt.Seconds())
141		if status == "FAIL" {
142			fmt.Printf("# go run run.go -- %s\n%s\nFAIL\t%s\t%s\n",
143				path.Join(test.dir, test.gofile),
144				errStr, test.goFileName(), dt)
145			continue
146		}
147		if !*verbose {
148			continue
149		}
150		fmt.Printf("%s\t%s\t%s\n", status, test.goFileName(), dt)
151	}
152
153	if *summary {
154		for k, v := range resCount {
155			fmt.Printf("%5d %s\n", v, k)
156		}
157	}
158
159	if failed {
160		os.Exit(1)
161	}
162}
163
164func toolPath(name string) string {
165	p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name)
166	if _, err := os.Stat(p); err != nil {
167		log.Fatalf("didn't find binary at %s", p)
168	}
169	return p
170}
171
172func goFiles(dir string) []string {
173	f, err := os.Open(dir)
174	check(err)
175	dirnames, err := f.Readdirnames(-1)
176	check(err)
177	names := []string{}
178	for _, name := range dirnames {
179		if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") {
180			names = append(names, name)
181		}
182	}
183	sort.Strings(names)
184	return names
185}
186
187type runCmd func(...string) ([]byte, error)
188
189func compileFile(runcmd runCmd, longname string) (out []byte, err error) {
190	return runcmd("go", "tool", gc, "-e", longname)
191}
192
193func compileInDir(runcmd runCmd, dir string, names ...string) (out []byte, err error) {
194	cmd := []string{"go", "tool", gc, "-e", "-D", ".", "-I", "."}
195	for _, name := range names {
196		cmd = append(cmd, filepath.Join(dir, name))
197	}
198	return runcmd(cmd...)
199}
200
201func linkFile(runcmd runCmd, goname string) (err error) {
202	pfile := strings.Replace(goname, ".go", "."+letter, -1)
203	_, err = runcmd("go", "tool", ld, "-o", "a.exe", "-L", ".", pfile)
204	return
205}
206
207// skipError describes why a test was skipped.
208type skipError string
209
210func (s skipError) Error() string { return string(s) }
211
212func check(err error) {
213	if err != nil {
214		log.Fatal(err)
215	}
216}
217
218// test holds the state of a test.
219type test struct {
220	dir, gofile string
221	donec       chan bool // closed when done
222	dt time.Duration
223
224	src    string
225	action string // "compile", "build", etc.
226
227	tempDir string
228	err     error
229}
230
231// startTest
232func startTest(dir, gofile string) *test {
233	t := &test{
234		dir:    dir,
235		gofile: gofile,
236		donec:  make(chan bool, 1),
237	}
238	if toRun == nil {
239		toRun = make(chan *test, maxTests)
240		go runTests()
241	}
242	select {
243	case toRun <- t:
244	default:
245		panic("toRun buffer size (maxTests) is too small")
246	}
247	return t
248}
249
250// runTests runs tests in parallel, but respecting the order they
251// were enqueued on the toRun channel.
252func runTests() {
253	for {
254		ratec <- true
255		t := <-toRun
256		go func() {
257			t.run()
258			<-ratec
259		}()
260	}
261}
262
263var cwd, _ = os.Getwd()
264
265func (t *test) goFileName() string {
266	return filepath.Join(t.dir, t.gofile)
267}
268
269func (t *test) goDirName() string {
270	return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1))
271}
272
273func goDirFiles(longdir string) (filter []os.FileInfo, err error) {
274	files, dirErr := ioutil.ReadDir(longdir)
275	if dirErr != nil {
276		return nil, dirErr
277	}
278	for _, gofile := range files {
279		if filepath.Ext(gofile.Name()) == ".go" {
280			filter = append(filter, gofile)
281		}
282	}
283	return
284}
285
286var packageRE = regexp.MustCompile(`(?m)^package (\w+)`)
287
288func goDirPackages(longdir string) ([][]string, error) {
289	files, err := goDirFiles(longdir)
290	if err != nil {
291		return nil, err
292	}
293	var pkgs [][]string
294	m := make(map[string]int)
295	for _, file := range files {
296		name := file.Name()
297		data, err := ioutil.ReadFile(filepath.Join(longdir, name))
298		if err != nil {
299			return nil, err
300		}
301		pkgname := packageRE.FindStringSubmatch(string(data))
302		if pkgname == nil {
303			return nil, fmt.Errorf("cannot find package name in %s", name)
304		}
305		i, ok := m[pkgname[1]]
306		if !ok {
307			i = len(pkgs)
308			pkgs = append(pkgs, nil)
309			m[pkgname[1]] = i
310		}
311		pkgs[i] = append(pkgs[i], name)
312	}
313	return pkgs, nil
314}
315
316type context struct {
317	GOOS   string
318	GOARCH string
319}
320
321// shouldTest looks for build tags in a source file and returns
322// whether the file should be used according to the tags.
323func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) {
324	if idx := strings.Index(src, "\npackage"); idx >= 0 {
325		src = src[:idx]
326	}
327	for _, line := range strings.Split(src, "\n") {
328		line = strings.TrimSpace(line)
329		if strings.HasPrefix(line, "//") {
330			line = line[2:]
331		} else {
332			continue
333		}
334		line = strings.TrimSpace(line)
335		if len(line) == 0 || line[0] != '+' {
336			continue
337		}
338		ctxt := &context{
339			GOOS:   goos,
340			GOARCH: goarch,
341		}
342		words := strings.Fields(line)
343		if words[0] == "+build" {
344			ok := false
345			for _, word := range words[1:] {
346				if ctxt.match(word) {
347					ok = true
348					break
349				}
350			}
351			if !ok {
352				// no matching tag found.
353				return false, line
354			}
355		}
356	}
357	// no build tags
358	return true, ""
359}
360
361func (ctxt *context) match(name string) bool {
362	if name == "" {
363		return false
364	}
365	if i := strings.Index(name, ","); i >= 0 {
366		// comma-separated list
367		return ctxt.match(name[:i]) && ctxt.match(name[i+1:])
368	}
369	if strings.HasPrefix(name, "!!") { // bad syntax, reject always
370		return false
371	}
372	if strings.HasPrefix(name, "!") { // negation
373		return len(name) > 1 && !ctxt.match(name[1:])
374	}
375
376	// Tags must be letters, digits, underscores or dots.
377	// Unlike in Go identifiers, all digits are fine (e.g., "386").
378	for _, c := range name {
379		if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
380			return false
381		}
382	}
383
384	if name == ctxt.GOOS || name == ctxt.GOARCH {
385		return true
386	}
387
388	return false
389}
390
391func init() { checkShouldTest() }
392
393// run runs a test.
394func (t *test) run() {
395	start := time.Now()
396	defer func() {
397		t.dt = time.Since(start)
398		close(t.donec)
399	}()
400
401	srcBytes, err := ioutil.ReadFile(t.goFileName())
402	if err != nil {
403		t.err = err
404		return
405	}
406	t.src = string(srcBytes)
407	if t.src[0] == '\n' {
408		t.err = skipError("starts with newline")
409		return
410	}
411	pos := strings.Index(t.src, "\n\n")
412	if pos == -1 {
413		t.err = errors.New("double newline not found")
414		return
415	}
416	if ok, why := shouldTest(t.src, runtime.GOOS, runtime.GOARCH); !ok {
417		t.action = "skip"
418		if *showSkips {
419			fmt.Printf("%-20s %-20s: %s\n", t.action, t.goFileName(), why)
420		}
421		return
422	}
423	action := t.src[:pos]
424	if nl := strings.Index(action, "\n"); nl >= 0 && strings.Contains(action[:nl], "+build") {
425		// skip first line
426		action = action[nl+1:]
427	}
428	if strings.HasPrefix(action, "//") {
429		action = action[2:]
430	}
431
432	var args, flags []string
433	wantError := false
434	f := strings.Fields(action)
435	if len(f) > 0 {
436		action = f[0]
437		args = f[1:]
438	}
439
440	switch action {
441	case "rundircmpout":
442		action = "rundir"
443		t.action = "rundir"
444	case "cmpout":
445		action = "run" // the run case already looks for <dir>/<test>.out files
446		fallthrough
447	case "compile", "compiledir", "build", "run", "runoutput", "rundir":
448		t.action = action
449	case "errorcheck", "errorcheckdir", "errorcheckoutput":
450		t.action = action
451		wantError = true
452		for len(args) > 0 && strings.HasPrefix(args[0], "-") {
453			if args[0] == "-0" {
454				wantError = false
455			} else {
456				flags = append(flags, args[0])
457			}
458			args = args[1:]
459		}
460	case "skip":
461		t.action = "skip"
462		return
463	default:
464		t.err = skipError("skipped; unknown pattern: " + action)
465		t.action = "??"
466		return
467	}
468
469	t.makeTempDir()
470	defer os.RemoveAll(t.tempDir)
471
472	err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644)
473	check(err)
474
475	// A few tests (of things like the environment) require these to be set.
476	os.Setenv("GOOS", runtime.GOOS)
477	os.Setenv("GOARCH", runtime.GOARCH)
478
479	useTmp := true
480	runcmd := func(args ...string) ([]byte, error) {
481		cmd := exec.Command(args[0], args[1:]...)
482		var buf bytes.Buffer
483		cmd.Stdout = &buf
484		cmd.Stderr = &buf
485		if useTmp {
486			cmd.Dir = t.tempDir
487			cmd.Env = envForDir(cmd.Dir)
488		}
489		err := cmd.Run()
490		if err != nil {
491			err = fmt.Errorf("%s\n%s", err, buf.Bytes())
492		}
493		return buf.Bytes(), err
494	}
495
496	long := filepath.Join(cwd, t.goFileName())
497	switch action {
498	default:
499		t.err = fmt.Errorf("unimplemented action %q", action)
500
501	case "errorcheck":
502		cmdline := []string{"go", "tool", gc, "-e", "-o", "a." + letter}
503		cmdline = append(cmdline, flags...)
504		cmdline = append(cmdline, long)
505		out, err := runcmd(cmdline...)
506		if wantError {
507			if err == nil {
508				t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
509				return
510			}
511		} else {
512			if err != nil {
513				t.err = err
514				return
515			}
516		}
517		t.err = t.errorCheck(string(out), long, t.gofile)
518		return
519
520	case "compile":
521		_, t.err = compileFile(runcmd, long)
522
523	case "compiledir":
524		// Compile all files in the directory in lexicographic order.
525		longdir := filepath.Join(cwd, t.goDirName())
526		pkgs, err := goDirPackages(longdir)
527		if err != nil {
528			t.err = err
529			return
530		}
531		for _, gofiles := range pkgs {
532			_, t.err = compileInDir(runcmd, longdir, gofiles...)
533			if t.err != nil {
534				return
535			}
536		}
537
538	case "errorcheckdir":
539		// errorcheck all files in lexicographic order
540		// useful for finding importing errors
541		longdir := filepath.Join(cwd, t.goDirName())
542		pkgs, err := goDirPackages(longdir)
543		if err != nil {
544			t.err = err
545			return
546		}
547		for i, gofiles := range pkgs {
548			out, err := compileInDir(runcmd, longdir, gofiles...)
549			if i == len(pkgs)-1 {
550				if wantError && err == nil {
551					t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
552					return
553				} else if !wantError && err != nil {
554					t.err = err
555					return
556				}
557			} else if err != nil {
558				t.err = err
559				return
560			}
561			var fullshort []string
562			for _, name := range gofiles {
563				fullshort = append(fullshort, filepath.Join(longdir, name), name)
564			}
565			t.err = t.errorCheck(string(out), fullshort...)
566			if t.err != nil {
567				break
568			}
569		}
570
571	case "rundir":
572		// Compile all files in the directory in lexicographic order.
573		// then link as if the last file is the main package and run it
574		longdir := filepath.Join(cwd, t.goDirName())
575		pkgs, err := goDirPackages(longdir)
576		if err != nil {
577			t.err = err
578			return
579		}
580		for i, gofiles := range pkgs {
581			_, err := compileInDir(runcmd, longdir, gofiles...)
582			if err != nil {
583				t.err = err
584				return
585			}
586			if i == len(pkgs)-1 {
587				err = linkFile(runcmd, gofiles[0])
588				if err != nil {
589					t.err = err
590					return
591				}
592				out, err := runcmd(append([]string{filepath.Join(t.tempDir, "a.exe")}, args...)...)
593				if err != nil {
594					t.err = err
595					return
596				}
597				if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() {
598					t.err = fmt.Errorf("incorrect output\n%s", out)
599				}
600			}
601		}
602
603	case "build":
604		_, err := runcmd("go", "build", "-o", "a.exe", long)
605		if err != nil {
606			t.err = err
607		}
608
609	case "run":
610		useTmp = false
611		out, err := runcmd(append([]string{"go", "run", t.goFileName()}, args...)...)
612		if err != nil {
613			t.err = err
614		}
615		if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() {
616			t.err = fmt.Errorf("incorrect output\n%s", out)
617		}
618
619	case "runoutput":
620		rungatec <- true
621		defer func() {
622			<-rungatec
623		}()
624		useTmp = false
625		out, err := runcmd(append([]string{"go", "run", t.goFileName()}, args...)...)
626		if err != nil {
627			t.err = err
628		}
629		tfile := filepath.Join(t.tempDir, "tmp__.go")
630		if err := ioutil.WriteFile(tfile, out, 0666); err != nil {
631			t.err = fmt.Errorf("write tempfile:%s", err)
632			return
633		}
634		out, err = runcmd("go", "run", tfile)
635		if err != nil {
636			t.err = err
637		}
638		if string(out) != t.expectedOutput() {
639			t.err = fmt.Errorf("incorrect output\n%s", out)
640		}
641
642	case "errorcheckoutput":
643		useTmp = false
644		out, err := runcmd(append([]string{"go", "run", t.goFileName()}, args...)...)
645		if err != nil {
646			t.err = err
647		}
648		tfile := filepath.Join(t.tempDir, "tmp__.go")
649		err = ioutil.WriteFile(tfile, out, 0666)
650		if err != nil {
651			t.err = fmt.Errorf("write tempfile:%s", err)
652			return
653		}
654		cmdline := []string{"go", "tool", gc, "-e", "-o", "a." + letter}
655		cmdline = append(cmdline, flags...)
656		cmdline = append(cmdline, tfile)
657		out, err = runcmd(cmdline...)
658		if wantError {
659			if err == nil {
660				t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
661				return
662			}
663		} else {
664			if err != nil {
665				t.err = err
666				return
667			}
668		}
669		t.err = t.errorCheck(string(out), tfile, "tmp__.go")
670		return
671	}
672}
673
674func (t *test) String() string {
675	return filepath.Join(t.dir, t.gofile)
676}
677
678func (t *test) makeTempDir() {
679	var err error
680	t.tempDir, err = ioutil.TempDir("", "")
681	check(err)
682}
683
684func (t *test) expectedOutput() string {
685	filename := filepath.Join(t.dir, t.gofile)
686	filename = filename[:len(filename)-len(".go")]
687	filename += ".out"
688	b, _ := ioutil.ReadFile(filename)
689	return string(b)
690}
691
692func (t *test) errorCheck(outStr string, fullshort ...string) (err error) {
693	defer func() {
694		if *verbose && err != nil {
695			log.Printf("%s gc output:\n%s", t, outStr)
696		}
697	}()
698	var errs []error
699
700	var out []string
701	// 6g error messages continue onto additional lines with leading tabs.
702	// Split the output at the beginning of each line that doesn't begin with a tab.
703	for _, line := range strings.Split(outStr, "\n") {
704		if strings.HasSuffix(line, "\r") { // remove '\r', output by compiler on windows
705			line = line[:len(line)-1]
706		}
707		if strings.HasPrefix(line, "\t") {
708			out[len(out)-1] += "\n" + line
709		} else if strings.HasPrefix(line, "go tool") {
710			continue
711		} else if strings.TrimSpace(line) != "" {
712			out = append(out, line)
713		}
714	}
715
716	// Cut directory name.
717	for i := range out {
718		for j := 0; j < len(fullshort); j += 2 {
719			full, short := fullshort[j], fullshort[j+1]
720			out[i] = strings.Replace(out[i], full, short, -1)
721		}
722	}
723
724	var want []wantedError
725	for j := 0; j < len(fullshort); j += 2 {
726		full, short := fullshort[j], fullshort[j+1]
727		want = append(want, t.wantedErrors(full, short)...)
728	}
729
730	for _, we := range want {
731		var errmsgs []string
732		errmsgs, out = partitionStrings(we.filterRe, out)
733		if len(errmsgs) == 0 {
734			errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr))
735			continue
736		}
737		matched := false
738		n := len(out)
739		for _, errmsg := range errmsgs {
740			if we.re.MatchString(errmsg) {
741				matched = true
742			} else {
743				out = append(out, errmsg)
744			}
745		}
746		if !matched {
747			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")))
748			continue
749		}
750	}
751
752	if len(out) > 0 {
753		errs = append(errs, fmt.Errorf("Unmatched Errors:"))
754		for _, errLine := range out {
755			errs = append(errs, fmt.Errorf("%s", errLine))
756		}
757	}
758
759	if len(errs) == 0 {
760		return nil
761	}
762	if len(errs) == 1 {
763		return errs[0]
764	}
765	var buf bytes.Buffer
766	fmt.Fprintf(&buf, "\n")
767	for _, err := range errs {
768		fmt.Fprintf(&buf, "%s\n", err.Error())
769	}
770	return errors.New(buf.String())
771
772}
773
774func partitionStrings(rx *regexp.Regexp, strs []string) (matched, unmatched []string) {
775	for _, s := range strs {
776		if rx.MatchString(s) {
777			matched = append(matched, s)
778		} else {
779			unmatched = append(unmatched, s)
780		}
781	}
782	return
783}
784
785type wantedError struct {
786	reStr    string
787	re       *regexp.Regexp
788	lineNum  int
789	file     string
790	filterRe *regexp.Regexp // /^file:linenum\b/m
791}
792
793var (
794	errRx       = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
795	errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
796	lineRx      = regexp.MustCompile(`LINE(([+-])([0-9]+))?`)
797)
798
799func (t *test) wantedErrors(file, short string) (errs []wantedError) {
800	src, _ := ioutil.ReadFile(file)
801	for i, line := range strings.Split(string(src), "\n") {
802		lineNum := i + 1
803		if strings.Contains(line, "////") {
804			// double comment disables ERROR
805			continue
806		}
807		m := errRx.FindStringSubmatch(line)
808		if m == nil {
809			continue
810		}
811		all := m[1]
812		mm := errQuotesRx.FindAllStringSubmatch(all, -1)
813		if mm == nil {
814			log.Fatalf("%s:%d: invalid errchk line: %s", t.goFileName(), lineNum, line)
815		}
816		for _, m := range mm {
817			rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
818				n := lineNum
819				if strings.HasPrefix(m, "LINE+") {
820					delta, _ := strconv.Atoi(m[5:])
821					n += delta
822				} else if strings.HasPrefix(m, "LINE-") {
823					delta, _ := strconv.Atoi(m[5:])
824					n -= delta
825				}
826				return fmt.Sprintf("%s:%d", short, n)
827			})
828			re, err := regexp.Compile(rx)
829			if err != nil {
830				log.Fatalf("%s:%d: invalid regexp in ERROR line: %v", t.goFileName(), lineNum, err)
831			}
832			filterPattern := fmt.Sprintf(`^(\w+/)?%s:%d[:[]`, regexp.QuoteMeta(short), lineNum)
833			errs = append(errs, wantedError{
834				reStr:    rx,
835				re:       re,
836				filterRe: regexp.MustCompile(filterPattern),
837				lineNum:  lineNum,
838				file:     short,
839			})
840		}
841	}
842
843	return
844}
845
846var skipOkay = map[string]bool{
847	"linkx.go":            true, // like "run" but wants linker flags
848	"sinit.go":            true,
849	"fixedbugs/bug248.go": true, // combines errorcheckdir and rundir in the same dir.
850	"fixedbugs/bug302.go": true, // tests both .$O and .a imports.
851	"fixedbugs/bug345.go": true, // needs the appropriate flags in gc invocation.
852	"fixedbugs/bug369.go": true, // needs compiler flags.
853	"fixedbugs/bug429.go": true, // like "run" but program should fail
854	"bugs/bug395.go":      true,
855}
856
857// defaultRunOutputLimit returns the number of runoutput tests that
858// can be executed in parallel.
859func defaultRunOutputLimit() int {
860	const maxArmCPU = 2
861
862	cpu := runtime.NumCPU()
863	if runtime.GOARCH == "arm" && cpu > maxArmCPU {
864		cpu = maxArmCPU
865	}
866	return cpu
867}
868
869// checkShouldTest runs sanity checks on the shouldTest function.
870func checkShouldTest() {
871	assert := func(ok bool, _ string) {
872		if !ok {
873			panic("fail")
874		}
875	}
876	assertNot := func(ok bool, _ string) { assert(!ok, "") }
877
878	// Simple tests.
879	assert(shouldTest("// +build linux", "linux", "arm"))
880	assert(shouldTest("// +build !windows", "linux", "arm"))
881	assertNot(shouldTest("// +build !windows", "windows", "amd64"))
882
883	// A file with no build tags will always be tested.
884	assert(shouldTest("// This is a test.", "os", "arch"))
885
886	// Build tags separated by a space are OR-ed together.
887	assertNot(shouldTest("// +build arm 386", "linux", "amd64"))
888
889	// Build tags seperated by a comma are AND-ed together.
890	assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64"))
891	assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386"))
892
893	// Build tags on multiple lines are AND-ed together.
894	assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64"))
895	assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64"))
896
897	// Test that (!a OR !b) matches anything.
898	assert(shouldTest("// +build !windows !plan9", "windows", "amd64"))
899}
900
901// envForDir returns a copy of the environment
902// suitable for running in the given directory.
903// The environment is the current process's environment
904// but with an updated $PWD, so that an os.Getwd in the
905// child will be faster.
906func envForDir(dir string) []string {
907	env := os.Environ()
908	for i, kv := range env {
909		if strings.HasPrefix(kv, "PWD=") {
910			env[i] = "PWD=" + dir
911			return env
912		}
913	}
914	env = append(env, "PWD="+dir)
915	return env
916}
917