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