blob: e1d97e9eef771aa0d1fd7582ce6df36ab7a237b1 [file] [log] [blame]
Shenghou Mae2662832012-03-22 02:14:44 +08001// skip
Brad Fitzpatrickce837b32012-02-21 14:28:49 +11002
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
11package main
12
13import (
14 "bytes"
15 "errors"
16 "flag"
17 "fmt"
18 "go/build"
19 "io/ioutil"
20 "log"
21 "os"
22 "os/exec"
23 "path/filepath"
24 "regexp"
25 "runtime"
26 "sort"
27 "strconv"
28 "strings"
29)
30
31var (
32 verbose = flag.Bool("v", false, "verbose. if set, parallelism is set to 1.")
Shenghou Ma47ee9822012-03-07 12:43:25 +080033 numParallel = flag.Int("n", runtime.NumCPU(), "number of parallel tests to run")
Brad Fitzpatrickce837b32012-02-21 14:28:49 +110034 summary = flag.Bool("summary", false, "show summary of results")
Brad Fitzpatricka55a5c82012-02-24 12:52:15 +110035 showSkips = flag.Bool("show_skips", false, "show skipped tests")
Brad Fitzpatrickce837b32012-02-21 14:28:49 +110036)
37
38var (
39 // gc and ld are [568][gl].
40 gc, ld string
41
42 // letter is the build.ArchChar
43 letter string
44
45 // dirs are the directories to look for *.go files in.
46 // TODO(bradfitz): just use all directories?
47 dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "bugs"}
48
49 // ratec controls the max number of tests running at a time.
50 ratec chan bool
51
52 // toRun is the channel of tests to run.
53 // It is nil until the first test is started.
54 toRun chan *test
55)
56
57// maxTests is an upper bound on the total number of tests.
58// It is used as a channel buffer size to make sure sends don't block.
59const maxTests = 5000
60
61func main() {
62 flag.Parse()
Shenghou Ma47ee9822012-03-07 12:43:25 +080063
64 // Disable parallelism if printing
65 if *verbose {
Brad Fitzpatrickce837b32012-02-21 14:28:49 +110066 *numParallel = 1
67 }
68
69 ratec = make(chan bool, *numParallel)
70 var err error
Shenghou Maeb5af842012-03-06 03:34:53 +080071 letter, err = build.ArchChar(build.Default.GOARCH)
Brad Fitzpatrickce837b32012-02-21 14:28:49 +110072 check(err)
73 gc = letter + "g"
74 ld = letter + "l"
75
76 var tests []*test
77 if flag.NArg() > 0 {
78 for _, arg := range flag.Args() {
79 if arg == "-" || arg == "--" {
80 // Permit running either:
81 // $ go run run.go - env.go
82 // $ go run run.go -- env.go
83 continue
84 }
85 if !strings.HasSuffix(arg, ".go") {
86 log.Fatalf("can't yet deal with non-go file %q", arg)
87 }
88 dir, file := filepath.Split(arg)
89 tests = append(tests, startTest(dir, file))
90 }
91 } else {
92 for _, dir := range dirs {
93 for _, baseGoFile := range goFiles(dir) {
94 tests = append(tests, startTest(dir, baseGoFile))
95 }
96 }
97 }
98
99 failed := false
100 resCount := map[string]int{}
101 for _, test := range tests {
102 <-test.donec
103 _, isSkip := test.err.(skipError)
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100104 errStr := "pass"
Brad Fitzpatricka55a5c82012-02-24 12:52:15 +1100105 if isSkip {
106 errStr = "skip"
107 }
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100108 if test.err != nil {
109 errStr = test.err.Error()
110 if !isSkip {
111 failed = true
112 }
113 }
114 resCount[errStr]++
Brad Fitzpatricka55a5c82012-02-24 12:52:15 +1100115 if isSkip && !*verbose && !*showSkips {
116 continue
117 }
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100118 if !*verbose && test.err == nil {
119 continue
Brad Fitzpatricka55a5c82012-02-24 12:52:15 +1100120 }
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100121 fmt.Printf("%-10s %-20s: %s\n", test.action, test.goFileName(), errStr)
122 }
123
124 if *summary {
125 for k, v := range resCount {
126 fmt.Printf("%5d %s\n", v, k)
127 }
128 }
129
130 if failed {
131 os.Exit(1)
132 }
133}
134
135func toolPath(name string) string {
136 p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name)
137 if _, err := os.Stat(p); err != nil {
138 log.Fatalf("didn't find binary at %s", p)
139 }
140 return p
141}
142
143func goFiles(dir string) []string {
144 f, err := os.Open(dir)
145 check(err)
146 dirnames, err := f.Readdirnames(-1)
147 check(err)
148 names := []string{}
149 for _, name := range dirnames {
Russ Coxc978a5a2012-03-08 14:03:40 -0500150 if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") {
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100151 names = append(names, name)
152 }
153 }
154 sort.Strings(names)
155 return names
156}
157
158// skipError describes why a test was skipped.
159type skipError string
160
161func (s skipError) Error() string { return string(s) }
162
163func check(err error) {
164 if err != nil {
165 log.Fatal(err)
166 }
167}
168
169// test holds the state of a test.
170type test struct {
171 dir, gofile string
172 donec chan bool // closed when done
173
174 src string
Shenghou Madda6d6a2012-04-20 23:45:43 +0800175 action string // "compile", "build", "run", "errorcheck", "skip", "runoutput"
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100176
177 tempDir string
178 err error
179}
180
181// startTest
182func startTest(dir, gofile string) *test {
183 t := &test{
184 dir: dir,
185 gofile: gofile,
186 donec: make(chan bool, 1),
187 }
188 if toRun == nil {
189 toRun = make(chan *test, maxTests)
190 go runTests()
191 }
192 select {
193 case toRun <- t:
194 default:
195 panic("toRun buffer size (maxTests) is too small")
196 }
197 return t
198}
199
200// runTests runs tests in parallel, but respecting the order they
201// were enqueued on the toRun channel.
202func runTests() {
203 for {
204 ratec <- true
205 t := <-toRun
206 go func() {
207 t.run()
208 <-ratec
209 }()
210 }
211}
212
Russ Cox105c5fa2012-03-07 01:54:39 -0500213var cwd, _ = os.Getwd()
214
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100215func (t *test) goFileName() string {
216 return filepath.Join(t.dir, t.gofile)
217}
218
Rémy Oudomphengadc93372012-07-30 21:12:05 +0200219func (t *test) goDirName() string {
220 return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1))
221}
222
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100223// run runs a test.
224func (t *test) run() {
225 defer close(t.donec)
226
227 srcBytes, err := ioutil.ReadFile(t.goFileName())
228 if err != nil {
229 t.err = err
230 return
231 }
232 t.src = string(srcBytes)
233 if t.src[0] == '\n' {
234 t.err = skipError("starts with newline")
235 return
236 }
237 pos := strings.Index(t.src, "\n\n")
238 if pos == -1 {
239 t.err = errors.New("double newline not found")
240 return
241 }
242 action := t.src[:pos]
243 if strings.HasPrefix(action, "//") {
244 action = action[2:]
245 }
Russ Coxc978a5a2012-03-08 14:03:40 -0500246
Russ Cox105c5fa2012-03-07 01:54:39 -0500247 var args []string
248 f := strings.Fields(action)
249 if len(f) > 0 {
250 action = f[0]
251 args = f[1:]
252 }
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100253
254 switch action {
Brad Fitzpatricke014cf02012-02-24 13:17:26 +1100255 case "cmpout":
256 action = "run" // the run case already looks for <dir>/<test>.out files
257 fallthrough
Rémy Oudomphengadc93372012-07-30 21:12:05 +0200258 case "compile", "compiledir", "build", "run", "errorcheck", "runoutput":
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100259 t.action = action
Shenghou Mae2662832012-03-22 02:14:44 +0800260 case "skip":
261 t.action = "skip"
262 return
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100263 default:
264 t.err = skipError("skipped; unknown pattern: " + action)
265 t.action = "??"
266 return
267 }
268
269 t.makeTempDir()
270 defer os.RemoveAll(t.tempDir)
271
272 err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644)
273 check(err)
Russ Coxc978a5a2012-03-08 14:03:40 -0500274
Russ Cox71247362012-03-07 02:22:08 -0500275 // A few tests (of things like the environment) require these to be set.
276 os.Setenv("GOOS", runtime.GOOS)
277 os.Setenv("GOARCH", runtime.GOARCH)
278
Russ Cox105c5fa2012-03-07 01:54:39 -0500279 useTmp := true
280 runcmd := func(args ...string) ([]byte, error) {
281 cmd := exec.Command(args[0], args[1:]...)
282 var buf bytes.Buffer
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100283 cmd.Stdout = &buf
284 cmd.Stderr = &buf
Russ Cox105c5fa2012-03-07 01:54:39 -0500285 if useTmp {
286 cmd.Dir = t.tempDir
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100287 }
Russ Cox105c5fa2012-03-07 01:54:39 -0500288 err := cmd.Run()
289 return buf.Bytes(), err
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100290 }
291
Russ Cox105c5fa2012-03-07 01:54:39 -0500292 long := filepath.Join(cwd, t.goFileName())
Russ Coxc978a5a2012-03-08 14:03:40 -0500293 switch action {
Russ Cox105c5fa2012-03-07 01:54:39 -0500294 default:
295 t.err = fmt.Errorf("unimplemented action %q", action)
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100296
Russ Cox105c5fa2012-03-07 01:54:39 -0500297 case "errorcheck":
298 out, _ := runcmd("go", "tool", gc, "-e", "-o", "a."+letter, long)
299 t.err = t.errorCheck(string(out), long, t.gofile)
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100300 return
Russ Coxc978a5a2012-03-08 14:03:40 -0500301
Russ Cox105c5fa2012-03-07 01:54:39 -0500302 case "compile":
303 out, err := runcmd("go", "tool", gc, "-e", "-o", "a."+letter, long)
304 if err != nil {
305 t.err = fmt.Errorf("%s\n%s", err, out)
306 }
Russ Coxc978a5a2012-03-08 14:03:40 -0500307
Rémy Oudomphengadc93372012-07-30 21:12:05 +0200308 case "compiledir":
309 // Compile all files in the directory in lexicographic order.
310 longdir := filepath.Join(cwd, t.goDirName())
311 files, dirErr := ioutil.ReadDir(longdir)
312 if dirErr != nil {
313 t.err = dirErr
314 return
315 }
316 for _, gofile := range files {
317 afile := strings.Replace(gofile.Name(), ".go", "."+letter, -1)
318 out, err := runcmd("go", "tool", gc, "-e", "-o", afile, filepath.Join(longdir, gofile.Name()))
319 if err != nil {
320 t.err = fmt.Errorf("%s\n%s", err, out)
321 break
322 }
323 }
324
Russ Cox105c5fa2012-03-07 01:54:39 -0500325 case "build":
326 out, err := runcmd("go", "build", "-o", "a.exe", long)
327 if err != nil {
328 t.err = fmt.Errorf("%s\n%s", err, out)
329 }
Russ Coxc978a5a2012-03-08 14:03:40 -0500330
Russ Cox105c5fa2012-03-07 01:54:39 -0500331 case "run":
332 useTmp = false
333 out, err := runcmd(append([]string{"go", "run", t.goFileName()}, args...)...)
334 if err != nil {
335 t.err = fmt.Errorf("%s\n%s", err, out)
336 }
337 if string(out) != t.expectedOutput() {
338 t.err = fmt.Errorf("incorrect output\n%s", out)
339 }
Shenghou Madda6d6a2012-04-20 23:45:43 +0800340
341 case "runoutput":
342 useTmp = false
343 out, err := runcmd("go", "run", t.goFileName())
344 if err != nil {
345 t.err = fmt.Errorf("%s\n%s", err, out)
346 }
347 tfile := filepath.Join(t.tempDir, "tmp__.go")
348 err = ioutil.WriteFile(tfile, out, 0666)
349 if err != nil {
350 t.err = fmt.Errorf("write tempfile:%s", err)
351 return
352 }
353 out, err = runcmd("go", "run", tfile)
354 if err != nil {
355 t.err = fmt.Errorf("%s\n%s", err, out)
356 }
357 if string(out) != t.expectedOutput() {
358 t.err = fmt.Errorf("incorrect output\n%s", out)
359 }
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100360 }
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100361}
362
363func (t *test) String() string {
364 return filepath.Join(t.dir, t.gofile)
365}
366
367func (t *test) makeTempDir() {
368 var err error
369 t.tempDir, err = ioutil.TempDir("", "")
370 check(err)
371}
372
373func (t *test) expectedOutput() string {
374 filename := filepath.Join(t.dir, t.gofile)
375 filename = filename[:len(filename)-len(".go")]
376 filename += ".out"
377 b, _ := ioutil.ReadFile(filename)
378 return string(b)
379}
380
Russ Cox105c5fa2012-03-07 01:54:39 -0500381func (t *test) errorCheck(outStr string, full, short string) (err error) {
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100382 defer func() {
383 if *verbose && err != nil {
384 log.Printf("%s gc output:\n%s", t, outStr)
385 }
386 }()
387 var errs []error
388
389 var out []string
390 // 6g error messages continue onto additional lines with leading tabs.
391 // Split the output at the beginning of each line that doesn't begin with a tab.
392 for _, line := range strings.Split(outStr, "\n") {
393 if strings.HasPrefix(line, "\t") {
394 out[len(out)-1] += "\n" + line
395 } else {
396 out = append(out, line)
397 }
398 }
399
Russ Cox105c5fa2012-03-07 01:54:39 -0500400 // Cut directory name.
401 for i := range out {
402 out[i] = strings.Replace(out[i], full, short, -1)
403 }
404
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100405 for _, we := range t.wantedErrors() {
406 var errmsgs []string
407 errmsgs, out = partitionStrings(we.filterRe, out)
408 if len(errmsgs) == 0 {
Russ Cox105c5fa2012-03-07 01:54:39 -0500409 errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr))
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100410 continue
411 }
412 matched := false
413 for _, errmsg := range errmsgs {
414 if we.re.MatchString(errmsg) {
415 matched = true
416 } else {
417 out = append(out, errmsg)
418 }
419 }
420 if !matched {
Russ Cox105c5fa2012-03-07 01:54:39 -0500421 errs = append(errs, fmt.Errorf("%s:%d: no match for %q in%s", we.file, we.lineNum, we.reStr, strings.Join(out, "\n")))
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100422 continue
423 }
424 }
425
426 if len(errs) == 0 {
427 return nil
428 }
429 if len(errs) == 1 {
430 return errs[0]
431 }
432 var buf bytes.Buffer
Russ Cox105c5fa2012-03-07 01:54:39 -0500433 fmt.Fprintf(&buf, "\n")
Brad Fitzpatrickce837b32012-02-21 14:28:49 +1100434 for _, err := range errs {
435 fmt.Fprintf(&buf, "%s\n", err.Error())
436 }
437 return errors.New(buf.String())
438
439}
440
441func partitionStrings(rx *regexp.Regexp, strs []string) (matched, unmatched []string) {
442 for _, s := range strs {
443 if rx.MatchString(s) {
444 matched = append(matched, s)
445 } else {
446 unmatched = append(unmatched, s)
447 }
448 }
449 return
450}
451
452type wantedError struct {
453 reStr string
454 re *regexp.Regexp
455 lineNum int
456 file string
457 filterRe *regexp.Regexp // /^file:linenum\b/m
458}
459
460var (
461 errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
462 errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
463 lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`)
464)
465
466func (t *test) wantedErrors() (errs []wantedError) {
467 for i, line := range strings.Split(t.src, "\n") {
468 lineNum := i + 1
469 if strings.Contains(line, "////") {
470 // double comment disables ERROR
471 continue
472 }
473 m := errRx.FindStringSubmatch(line)
474 if m == nil {
475 continue
476 }
477 all := m[1]
478 mm := errQuotesRx.FindAllStringSubmatch(all, -1)
479 if mm == nil {
480 log.Fatalf("invalid errchk line in %s: %s", t.goFileName(), line)
481 }
482 for _, m := range mm {
483 rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
484 n := lineNum
485 if strings.HasPrefix(m, "LINE+") {
486 delta, _ := strconv.Atoi(m[5:])
487 n += delta
488 } else if strings.HasPrefix(m, "LINE-") {
489 delta, _ := strconv.Atoi(m[5:])
490 n -= delta
491 }
492 return fmt.Sprintf("%s:%d", t.gofile, n)
493 })
494 filterPattern := fmt.Sprintf(`^(\w+/)?%s:%d[:[]`, t.gofile, lineNum)
495 errs = append(errs, wantedError{
496 reStr: rx,
497 re: regexp.MustCompile(rx),
498 filterRe: regexp.MustCompile(filterPattern),
499 lineNum: lineNum,
500 file: t.gofile,
501 })
502 }
503 }
504
505 return
506}