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.
7 // Run runs tests in the test directory.
9 // TODO(bradfitz): docs of some sort, once we figure out how we're changing
35 verbose
= flag
.Bool("v", false, "verbose. if set, parallelism is set to 1.")
36 numParallel
= flag
.Int("n", runtime
.NumCPU(), "number of parallel tests to run")
37 summary
= flag
.Bool("summary", false, "show summary of results")
38 showSkips
= flag
.Bool("show_skips", false, "show skipped tests")
39 runoutputLimit
= flag
.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run")
43 // gc and ld are [568][gl].
46 // letter is the build.ArchChar
49 // dirs are the directories to look for *.go files in.
50 // TODO(bradfitz): just use all directories?
51 dirs
= []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "bugs"}
53 // ratec controls the max number of tests running at a time.
56 // toRun is the channel of tests to run.
57 // It is nil until the first test is started.
60 // rungatec controls the max number of runoutput tests
61 // executed in parallel as they can each consume a lot of memory.
65 // maxTests is an upper bound on the total number of tests.
66 // It is used as a channel buffer size to make sure sends don't block.
72 // Disable parallelism if printing
77 ratec
= make(chan bool, *numParallel
)
78 rungatec
= make(chan bool, *runoutputLimit
)
80 letter
, err
= build
.ArchChar(build
.Default
.GOARCH
)
87 for _
, arg
:= range flag
.Args() {
88 if arg
== "-" || arg
== "--" {
90 // $ go run run.go - env.go
91 // $ go run run.go -- env.go
92 // $ go run run.go - ./fixedbugs
93 // $ go run run.go -- ./fixedbugs
96 if fi
, err
:= os
.Stat(arg
); err
== nil && fi
.IsDir() {
97 for _
, baseGoFile
:= range goFiles(arg
) {
98 tests
= append(tests
, startTest(arg
, baseGoFile
))
100 } else if strings
.HasSuffix(arg
, ".go") {
101 dir
, file
:= filepath
.Split(arg
)
102 tests
= append(tests
, startTest(dir
, file
))
104 log
.Fatalf("can't yet deal with non-directory and non-go file %q", arg
)
108 for _
, dir
:= range dirs
{
109 for _
, baseGoFile
:= range goFiles(dir
) {
110 tests
= append(tests
, startTest(dir
, baseGoFile
))
116 resCount
:= map[string]int{}
117 for _
, test
:= range tests
{
121 if _
, isSkip
:= test
.err
.(skipError
); isSkip
{
124 if !skipOkay
[path
.Join(test
.dir
, test
.gofile
)] {
125 errStr
= "unexpected skip for " + path
.Join(test
.dir
, test
.gofile
) + ": " + errStr
131 errStr
= test
.err
.Error()
133 if status
== "FAIL" {
137 if status
== "skip" && !*verbose
&& !*showSkips
{
140 dt
:= fmt
.Sprintf("%.3fs", test
.dt
.Seconds())
141 if status
== "FAIL" {
142 fmt
.Printf("# go run run.go -- %s\n%s\nFAIL\t%s\t%s\n",
143 path
.Join(test
.dir
, test
.gofile
),
144 errStr
, test
.goFileName(), dt
)
150 fmt
.Printf("%s\t%s\t%s\n", status
, test
.goFileName(), dt
)
154 for k
, v
:= range resCount
{
155 fmt
.Printf("%5d %s\n", v
, k
)
164 func toolPath(name
string) string {
165 p
:= filepath
.Join(os
.Getenv("GOROOT"), "bin", "tool", name
)
166 if _
, err
:= os
.Stat(p
); err
!= nil {
167 log
.Fatalf("didn't find binary at %s", p
)
172 func goFiles(dir
string) []string {
173 f
, err
:= os
.Open(dir
)
175 dirnames
, err
:= f
.Readdirnames(-1)
178 for _
, name
:= range dirnames
{
179 if !strings
.HasPrefix(name
, ".") && strings
.HasSuffix(name
, ".go") {
180 names
= append(names
, name
)
187 type runCmd
func(...string) ([]byte, error
)
189 func compileFile(runcmd runCmd
, longname
string) (out
[]byte, err error
) {
190 return runcmd("go", "tool", gc
, "-e", longname
)
193 func compileInDir(runcmd runCmd
, dir
string, names
...string) (out
[]byte, err error
) {
194 cmd
:= []string{"go", "tool", gc
, "-e", "-D", ".", "-I", "."}
195 for _
, name
:= range names
{
196 cmd
= append(cmd
, filepath
.Join(dir
, name
))
198 return runcmd(cmd
...)
201 func linkFile(runcmd runCmd
, goname
string) (err error
) {
202 pfile
:= strings
.Replace(goname
, ".go", "."+letter
, -1)
203 _
, err
= runcmd("go", "tool", ld
, "-o", "a.exe", "-L", ".", pfile
)
207 // skipError describes why a test was skipped.
208 type skipError
string
210 func (s skipError
) Error() string { return string(s
) }
212 func check(err error
) {
218 // test holds the state of a test.
221 donec
chan bool // closed when done
225 action
string // "compile", "build", etc.
232 func startTest(dir
, gofile
string) *test
{
236 donec
: make(chan bool, 1),
239 toRun
= make(chan *test
, maxTests
)
245 panic("toRun buffer size (maxTests) is too small")
250 // runTests runs tests in parallel, but respecting the order they
251 // were enqueued on the toRun channel.
263 var cwd
, _
= os
.Getwd()
265 func (t
*test
) goFileName() string {
266 return filepath
.Join(t
.dir
, t
.gofile
)
269 func (t
*test
) goDirName() string {
270 return filepath
.Join(t
.dir
, strings
.Replace(t
.gofile
, ".go", ".dir", -1))
273 func goDirFiles(longdir
string) (filter
[]os
.FileInfo
, err error
) {
274 files
, dirErr
:= ioutil
.ReadDir(longdir
)
278 for _
, gofile
:= range files
{
279 if filepath
.Ext(gofile
.Name()) == ".go" {
280 filter
= append(filter
, gofile
)
286 var packageRE
= regexp
.MustCompile(`(?m)^package (\w+)`)
288 func goDirPackages(longdir
string) ([][]string, error
) {
289 files
, err
:= goDirFiles(longdir
)
294 m
:= make(map[string]int)
295 for _
, file
:= range files
{
297 data
, err
:= ioutil
.ReadFile(filepath
.Join(longdir
, name
))
301 pkgname
:= packageRE
.FindStringSubmatch(string(data
))
303 return nil, fmt
.Errorf("cannot find package name in %s", name
)
305 i
, ok
:= m
[pkgname
[1]]
308 pkgs
= append(pkgs
, nil)
311 pkgs
[i
] = append(pkgs
[i
], name
)
316 type context
struct {
321 // shouldTest looks for build tags in a source file and returns
322 // whether the file should be used according to the tags.
323 func shouldTest(src
string, goos
, goarch
string) (ok
bool, whyNot
string) {
324 if idx
:= strings
.Index(src
, "\npackage"); idx
>= 0 {
327 for _
, line
:= range strings
.Split(src
, "\n") {
328 line
= strings
.TrimSpace(line
)
329 if strings
.HasPrefix(line
, "//") {
334 line
= strings
.TrimSpace(line
)
335 if len(line
) == 0 || line
[0] != '+' {
342 words
:= strings
.Fields(line
)
343 if words
[0] == "+build" {
345 for _
, word
:= range words
[1:] {
346 if ctxt
.match(word
) {
352 // no matching tag found.
361 func (ctxt
*context
) match(name
string) bool {
365 if i
:= strings
.Index(name
, ","); i
>= 0 {
366 // comma-separated list
367 return ctxt
.match(name
[:i
]) && ctxt
.match(name
[i
+1:])
369 if strings
.HasPrefix(name
, "!!") { // bad syntax, reject always
372 if strings
.HasPrefix(name
, "!") { // negation
373 return len(name
) > 1 && !ctxt
.match(name
[1:])
376 // Tags must be letters, digits, underscores or dots.
377 // Unlike in Go identifiers, all digits are fine (e.g., "386").
378 for _
, c
:= range name
{
379 if !unicode
.IsLetter(c
) && !unicode
.IsDigit(c
) && c
!= '_' && c
!= '.' {
384 if name
== ctxt
.GOOS || name
== ctxt
.GOARCH
{
391 func init() { checkShouldTest() }
394 func (t
*test
) run() {
397 t
.dt
= time
.Since(start
)
401 srcBytes
, err
:= ioutil
.ReadFile(t
.goFileName())
406 t
.src
= string(srcBytes
)
407 if t
.src
[0] == '\n' {
408 t
.err
= skipError("starts with newline")
411 pos
:= strings
.Index(t
.src
, "\n\n")
413 t
.err
= errors
.New("double newline not found")
416 if ok
, why
:= shouldTest(t
.src
, runtime
.GOOS
, runtime
.GOARCH
); !ok
{
419 fmt
.Printf("%-20s %-20s: %s\n", t
.action
, t
.goFileName(), why
)
423 action
:= t
.src
[:pos
]
424 if nl
:= strings
.Index(action
, "\n"); nl
>= 0 && strings
.Contains(action
[:nl
], "+build") {
426 action
= action
[nl
+1:]
428 if strings
.HasPrefix(action
, "//") {
432 var args
, flags
[]string
434 f
:= strings
.Fields(action
)
445 action
= "run" // the run case already looks for <dir>/<test>.out files
447 case "compile", "compiledir", "build", "run", "runoutput", "rundir":
449 case "errorcheck", "errorcheckdir", "errorcheckoutput":
452 for len(args
) > 0 && strings
.HasPrefix(args
[0], "-") {
456 flags
= append(flags
, args
[0])
464 t
.err
= skipError("skipped; unknown pattern: " + action
)
470 defer os
.RemoveAll(t
.tempDir
)
472 err
= ioutil
.WriteFile(filepath
.Join(t
.tempDir
, t
.gofile
), srcBytes
, 0644)
475 // A few tests (of things like the environment) require these to be set.
476 os
.Setenv("GOOS", runtime
.GOOS
)
477 os
.Setenv("GOARCH", runtime
.GOARCH
)
480 runcmd
:= func(args
...string) ([]byte, error
) {
481 cmd
:= exec
.Command(args
[0], args
[1:]...)
487 cmd
.Env
= envForDir(cmd
.Dir
)
491 err
= fmt
.Errorf("%s\n%s", err
, buf
.Bytes())
493 return buf
.Bytes(), err
496 long
:= filepath
.Join(cwd
, t
.goFileName())
499 t
.err
= fmt
.Errorf("unimplemented action %q", action
)
502 cmdline
:= []string{"go", "tool", gc
, "-e", "-o", "a." + letter
}
503 cmdline
= append(cmdline
, flags
...)
504 cmdline
= append(cmdline
, long
)
505 out
, err
:= runcmd(cmdline
...)
508 t
.err
= fmt
.Errorf("compilation succeeded unexpectedly\n%s", out
)
517 t
.err
= t
.errorCheck(string(out
), long
, t
.gofile
)
521 _
, t
.err
= compileFile(runcmd
, long
)
524 // Compile all files in the directory in lexicographic order.
525 longdir
:= filepath
.Join(cwd
, t
.goDirName())
526 pkgs
, err
:= goDirPackages(longdir
)
531 for _
, gofiles
:= range pkgs
{
532 _
, t
.err
= compileInDir(runcmd
, longdir
, gofiles
...)
538 case "errorcheckdir":
539 // errorcheck all files in lexicographic order
540 // useful for finding importing errors
541 longdir
:= filepath
.Join(cwd
, t
.goDirName())
542 pkgs
, err
:= goDirPackages(longdir
)
547 for i
, gofiles
:= range pkgs
{
548 out
, err
:= compileInDir(runcmd
, longdir
, gofiles
...)
549 if i
== len(pkgs
)-1 {
550 if wantError
&& err
== nil {
551 t
.err
= fmt
.Errorf("compilation succeeded unexpectedly\n%s", out
)
553 } else if !wantError
&& err
!= nil {
557 } else if err
!= nil {
561 var fullshort
[]string
562 for _
, name
:= range gofiles
{
563 fullshort
= append(fullshort
, filepath
.Join(longdir
, name
), name
)
565 t
.err
= t
.errorCheck(string(out
), fullshort
...)
572 // Compile all files in the directory in lexicographic order.
573 // then link as if the last file is the main package and run it
574 longdir
:= filepath
.Join(cwd
, t
.goDirName())
575 pkgs
, err
:= goDirPackages(longdir
)
580 for i
, gofiles
:= range pkgs
{
581 _
, err
:= compileInDir(runcmd
, longdir
, gofiles
...)
586 if i
== len(pkgs
)-1 {
587 err
= linkFile(runcmd
, gofiles
[0])
592 out
, err
:= runcmd(append([]string{filepath
.Join(t
.tempDir
, "a.exe")}, args
...)...)
597 if strings
.Replace(string(out
), "\r\n", "\n", -1) != t
.expectedOutput() {
598 t
.err
= fmt
.Errorf("incorrect output\n%s", out
)
604 _
, err
:= runcmd("go", "build", "-o", "a.exe", long
)
611 out
, err
:= runcmd(append([]string{"go", "run", t
.goFileName()}, args
...)...)
615 if strings
.Replace(string(out
), "\r\n", "\n", -1) != t
.expectedOutput() {
616 t
.err
= fmt
.Errorf("incorrect output\n%s", out
)
625 out
, err
:= runcmd(append([]string{"go", "run", t
.goFileName()}, args
...)...)
629 tfile
:= filepath
.Join(t
.tempDir
, "tmp__.go")
630 if err
:= ioutil
.WriteFile(tfile
, out
, 0666); err
!= nil {
631 t
.err
= fmt
.Errorf("write tempfile:%s", err
)
634 out
, err
= runcmd("go", "run", tfile
)
638 if string(out
) != t
.expectedOutput() {
639 t
.err
= fmt
.Errorf("incorrect output\n%s", out
)
642 case "errorcheckoutput":
644 out
, err
:= runcmd(append([]string{"go", "run", t
.goFileName()}, args
...)...)
648 tfile
:= filepath
.Join(t
.tempDir
, "tmp__.go")
649 err
= ioutil
.WriteFile(tfile
, out
, 0666)
651 t
.err
= fmt
.Errorf("write tempfile:%s", err
)
654 cmdline
:= []string{"go", "tool", gc
, "-e", "-o", "a." + letter
}
655 cmdline
= append(cmdline
, flags
...)
656 cmdline
= append(cmdline
, tfile
)
657 out
, err
= runcmd(cmdline
...)
660 t
.err
= fmt
.Errorf("compilation succeeded unexpectedly\n%s", out
)
669 t
.err
= t
.errorCheck(string(out
), tfile
, "tmp__.go")
674 func (t
*test
) String() string {
675 return filepath
.Join(t
.dir
, t
.gofile
)
678 func (t
*test
) makeTempDir() {
680 t
.tempDir
, err
= ioutil
.TempDir("", "")
684 func (t
*test
) expectedOutput() string {
685 filename
:= filepath
.Join(t
.dir
, t
.gofile
)
686 filename
= filename
[:len(filename
)-len(".go")]
688 b
, _
:= ioutil
.ReadFile(filename
)
692 func (t
*test
) errorCheck(outStr
string, fullshort
...string) (err error
) {
694 if *verbose
&& err
!= nil {
695 log
.Printf("%s gc output:\n%s", t
, outStr
)
701 // 6g error messages continue onto additional lines with leading tabs.
702 // Split the output at the beginning of each line that doesn't begin with a tab.
703 for _
, line
:= range strings
.Split(outStr
, "\n") {
704 if strings
.HasSuffix(line
, "\r") { // remove '\r', output by compiler on windows
705 line
= line
[:len(line
)-1]
707 if strings
.HasPrefix(line
, "\t") {
708 out
[len(out
)-1] += "\n" + line
709 } else if strings
.HasPrefix(line
, "go tool") {
711 } else if strings
.TrimSpace(line
) != "" {
712 out
= append(out
, line
)
716 // Cut directory name.
718 for j
:= 0; j
< len(fullshort
); j
+= 2 {
719 full
, short
:= fullshort
[j
], fullshort
[j
+1]
720 out
[i
] = strings
.Replace(out
[i
], full
, short
, -1)
724 var want
[]wantedError
725 for j
:= 0; j
< len(fullshort
); j
+= 2 {
726 full
, short
:= fullshort
[j
], fullshort
[j
+1]
727 want
= append(want
, t
.wantedErrors(full
, short
)...)
730 for _
, we
:= range want
{
732 errmsgs
, out
= partitionStrings(we
.filterRe
, out
)
733 if len(errmsgs
) == 0 {
734 errs
= append(errs
, fmt
.Errorf("%s:%d: missing error %q", we
.file
, we
.lineNum
, we
.reStr
))
739 for _
, errmsg
:= range errmsgs
{
740 if we
.re
.MatchString(errmsg
) {
743 out
= append(out
, errmsg
)
747 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")))
753 errs
= append(errs
, fmt
.Errorf("Unmatched Errors:"))
754 for _
, errLine
:= range out
{
755 errs
= append(errs
, fmt
.Errorf("%s", errLine
))
766 fmt
.Fprintf(&buf
, "\n")
767 for _
, err
:= range errs
{
768 fmt
.Fprintf(&buf
, "%s\n", err
.Error())
770 return errors
.New(buf
.String())
774 func partitionStrings(rx
*regexp
.Regexp
, strs
[]string) (matched
, unmatched
[]string) {
775 for _
, s
:= range strs
{
776 if rx
.MatchString(s
) {
777 matched
= append(matched
, s
)
779 unmatched
= append(unmatched
, s
)
785 type wantedError
struct {
790 filterRe
*regexp
.Regexp
// /^file:linenum\b/m
794 errRx
= regexp
.MustCompile(`// (?:GC_)?ERROR (.*)`)
795 errQuotesRx
= regexp
.MustCompile(`"([^"]*)"`)
796 lineRx
= regexp
.MustCompile(`LINE(([+-])([0-9]+))?`)
799 func (t
*test
) wantedErrors(file
, short
string) (errs
[]wantedError
) {
800 src
, _
:= ioutil
.ReadFile(file
)
801 for i
, line
:= range strings
.Split(string(src
), "\n") {
803 if strings
.Contains(line
, "////") {
804 // double comment disables ERROR
807 m
:= errRx
.FindStringSubmatch(line
)
812 mm
:= errQuotesRx
.FindAllStringSubmatch(all
, -1)
814 log
.Fatalf("%s:%d: invalid errchk line: %s", t
.goFileName(), lineNum
, line
)
816 for _
, m
:= range mm
{
817 rx
:= lineRx
.ReplaceAllStringFunc(m
[1], func(m
string) string {
819 if strings
.HasPrefix(m
, "LINE+") {
820 delta
, _
:= strconv
.Atoi(m
[5:])
822 } else if strings
.HasPrefix(m
, "LINE-") {
823 delta
, _
:= strconv
.Atoi(m
[5:])
826 return fmt
.Sprintf("%s:%d", short
, n
)
828 re
, err
:= regexp
.Compile(rx
)
830 log
.Fatalf("%s:%d: invalid regexp in ERROR line: %v", t
.goFileName(), lineNum
, err
)
832 filterPattern
:= fmt
.Sprintf(`^(\w+/)?%s:%d[:[]`, regexp
.QuoteMeta(short
), lineNum
)
833 errs
= append(errs
, wantedError
{
836 filterRe
: regexp
.MustCompile(filterPattern
),
846 var skipOkay
= map[string]bool{
847 "linkx.go": true, // like "run" but wants linker flags
849 "fixedbugs/bug248.go": true, // combines errorcheckdir and rundir in the same dir.
850 "fixedbugs/bug302.go": true, // tests both .$O and .a imports.
851 "fixedbugs/bug345.go": true, // needs the appropriate flags in gc invocation.
852 "fixedbugs/bug369.go": true, // needs compiler flags.
853 "fixedbugs/bug429.go": true, // like "run" but program should fail
854 "bugs/bug395.go": true,
857 // defaultRunOutputLimit returns the number of runoutput tests that
858 // can be executed in parallel.
859 func defaultRunOutputLimit() int {
862 cpu
:= runtime
.NumCPU()
863 if runtime
.GOARCH
== "arm" && cpu
> maxArmCPU
{
869 // checkShouldTest runs sanity checks on the shouldTest function.
870 func checkShouldTest() {
871 assert
:= func(ok
bool, _
string) {
876 assertNot
:= func(ok
bool, _
string) { assert(!ok
, "") }
879 assert(shouldTest("// +build linux", "linux", "arm"))
880 assert(shouldTest("// +build !windows", "linux", "arm"))
881 assertNot(shouldTest("// +build !windows", "windows", "amd64"))
883 // A file with no build tags will always be tested.
884 assert(shouldTest("// This is a test.", "os", "arch"))
886 // Build tags separated by a space are OR-ed together.
887 assertNot(shouldTest("// +build arm 386", "linux", "amd64"))
889 // Build tags seperated by a comma are AND-ed together.
890 assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64"))
891 assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386"))
893 // Build tags on multiple lines are AND-ed together.
894 assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64"))
895 assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64"))
897 // Test that (!a OR !b) matches anything.
898 assert(shouldTest("// +build !windows !plan9", "windows", "amd64"))
901 // envForDir returns a copy of the environment
902 // suitable for running in the given directory.
903 // The environment is the current process's environment
904 // but with an updated $PWD, so that an os.Getwd in the
905 // child will be faster.
906 func envForDir(dir
string) []string {
908 for i
, kv
:= range env
{
909 if strings
.HasPrefix(kv
, "PWD=") {
910 env
[i
] = "PWD=" + dir
914 env
= append(env
, "PWD="+dir
)