// #ignore // Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Run runs tests in the test directory. // // TODO(bradfitz): docs of some sort, once we figure out how we're changing // headers of files package main import ( "bytes" "errors" "flag" "fmt" "go/build" "io/ioutil" "log" "os" "os/exec" "path/filepath" "regexp" "runtime" "sort" "strconv" "strings" ) var ( verbose = flag.Bool("v", false, "verbose. if set, parallelism is set to 1.") numParallel = flag.Int("n", 8, "number of parallel tests to run") summary = flag.Bool("summary", false, "show summary of results") showSkips = flag.Bool("show_skips", false, "show skipped tests") ) var ( // gc and ld are [568][gl]. gc, ld string // letter is the build.ArchChar letter string // dirs are the directories to look for *.go files in. // TODO(bradfitz): just use all directories? dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "bugs"} // ratec controls the max number of tests running at a time. ratec chan bool // toRun is the channel of tests to run. // It is nil until the first test is started. toRun chan *test ) // maxTests is an upper bound on the total number of tests. // It is used as a channel buffer size to make sure sends don't block. const maxTests = 5000 func main() { flag.Parse() if *verbose { *numParallel = 1 } ratec = make(chan bool, *numParallel) var err error letter, err = build.ArchChar(build.DefaultContext.GOARCH) check(err) gc = letter + "g" ld = letter + "l" var tests []*test if flag.NArg() > 0 { for _, arg := range flag.Args() { if arg == "-" || arg == "--" { // Permit running either: // $ go run run.go - env.go // $ go run run.go -- env.go continue } if !strings.HasSuffix(arg, ".go") { log.Fatalf("can't yet deal with non-go file %q", arg) } dir, file := filepath.Split(arg) tests = append(tests, startTest(dir, file)) } } else { for _, dir := range dirs { for _, baseGoFile := range goFiles(dir) { tests = append(tests, startTest(dir, baseGoFile)) } } } failed := false resCount := map[string]int{} for _, test := range tests { <-test.donec _, isSkip := test.err.(skipError) errStr := "pass" if isSkip { errStr = "skip" } if test.err != nil { errStr = test.err.Error() if !isSkip { failed = true } } resCount[errStr]++ if isSkip && !*verbose && !*showSkips { continue } if !*verbose && test.err == nil { continue } fmt.Printf("%-10s %-20s: %s\n", test.action, test.goFileName(), errStr) } if *summary { for k, v := range resCount { fmt.Printf("%5d %s\n", v, k) } } if failed { os.Exit(1) } } func toolPath(name string) string { p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name) if _, err := os.Stat(p); err != nil { log.Fatalf("didn't find binary at %s", p) } return p } func goFiles(dir string) []string { f, err := os.Open(dir) check(err) dirnames, err := f.Readdirnames(-1) check(err) names := []string{} for _, name := range dirnames { if strings.HasSuffix(name, ".go") { names = append(names, name) } } sort.Strings(names) return names } // skipError describes why a test was skipped. type skipError string func (s skipError) Error() string { return string(s) } func check(err error) { if err != nil { log.Fatal(err) } } // test holds the state of a test. type test struct { dir, gofile string donec chan bool // closed when done src string action string // "compile", "build", "run", "errorcheck" tempDir string err error } // startTest func startTest(dir, gofile string) *test { t := &test{ dir: dir, gofile: gofile, donec: make(chan bool, 1), } if toRun == nil { toRun = make(chan *test, maxTests) go runTests() } select { case toRun <- t: default: panic("toRun buffer size (maxTests) is too small") } return t } // runTests runs tests in parallel, but respecting the order they // were enqueued on the toRun channel. func runTests() { for { ratec <- true t := <-toRun go func() { t.run() <-ratec }() } } func (t *test) goFileName() string { return filepath.Join(t.dir, t.gofile) } // run runs a test. func (t *test) run() { defer close(t.donec) srcBytes, err := ioutil.ReadFile(t.goFileName()) if err != nil { t.err = err return } t.src = string(srcBytes) if t.src[0] == '\n' { t.err = skipError("starts with newline") return } pos := strings.Index(t.src, "\n\n") if pos == -1 { t.err = errors.New("double newline not found") return } action := t.src[:pos] if strings.HasPrefix(action, "//") { action = action[2:] } action = strings.TrimSpace(action) switch action { case "cmpout": action = "run" // the run case already looks for