github.com/varialus/godfly@v0.0.0-20130904042352-1934f9f095ab/test/run.go (about)

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