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