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