| // Copyright 2015 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 the docs tests found in this directory. | |
| package main | |
| import ( | |
| "bytes" | |
| "flag" | |
| "fmt" | |
| "io/ioutil" | |
| "os" | |
| "os/exec" | |
| "path/filepath" | |
| "regexp" | |
| "runtime" | |
| "strings" | |
| "time" | |
| ) | |
| const usage = `go run run.go [tests] | |
| run.go runs the docs tests in this directory. | |
| If no tests are provided, it runs all tests. | |
| Tests may be specified without their .go suffix. | |
| ` | |
| func main() { | |
| start := time.Now() | |
| flag.Usage = func() { | |
| fmt.Fprintf(os.Stderr, usage) | |
| flag.PrintDefaults() | |
| os.Exit(2) | |
| } | |
| flag.Parse() | |
| if flag.NArg() == 0 { | |
| // run all tests | |
| fixcgo() | |
| } else { | |
| // run specified tests | |
| onlyTest(flag.Args()...) | |
| } | |
| tmpdir, err := ioutil.TempDir("", "go-progs") | |
| if err != nil { | |
| fmt.Fprintln(os.Stderr, err) | |
| os.Exit(1) | |
| } | |
| // ratec limits the number of tests running concurrently. | |
| // None of the tests are intensive, so don't bother | |
| // trying to manually adjust for slow builders. | |
| ratec := make(chan bool, runtime.NumCPU()) | |
| errc := make(chan error, len(tests)) | |
| for _, tt := range tests { | |
| tt := tt | |
| ratec <- true | |
| go func() { | |
| errc <- test(tmpdir, tt.file, tt.want) | |
| <-ratec | |
| }() | |
| } | |
| var rc int | |
| for range tests { | |
| if err := <-errc; err != nil { | |
| fmt.Fprintln(os.Stderr, err) | |
| rc = 1 | |
| } | |
| } | |
| os.Remove(tmpdir) | |
| if rc == 0 { | |
| fmt.Printf("ok\t%s\t%s\n", filepath.Base(os.Args[0]), time.Since(start).Round(time.Millisecond)) | |
| } | |
| os.Exit(rc) | |
| } | |
| // test builds the test in the given file. | |
| // If want is non-empty, test also runs the test | |
| // and checks that the output matches the regexp want. | |
| func test(tmpdir, file, want string) error { | |
| // Build the program. | |
| prog := filepath.Join(tmpdir, file+".exe") | |
| cmd := exec.Command("go", "build", "-o", prog, file+".go") | |
| out, err := cmd.CombinedOutput() | |
| if err != nil { | |
| return fmt.Errorf("go build %s.go failed: %v\nOutput:\n%s", file, err, out) | |
| } | |
| defer os.Remove(prog) | |
| // Only run the test if we have output to check. | |
| if want == "" { | |
| return nil | |
| } | |
| cmd = exec.Command(prog) | |
| out, err = cmd.CombinedOutput() | |
| if err != nil { | |
| return fmt.Errorf("%s failed: %v\nOutput:\n%s", file, err, out) | |
| } | |
| // Canonicalize output. | |
| out = bytes.TrimRight(out, "\n") | |
| out = bytes.ReplaceAll(out, []byte{'\n'}, []byte{' '}) | |
| // Check the result. | |
| match, err := regexp.Match(want, out) | |
| if err != nil { | |
| return fmt.Errorf("failed to parse regexp %q: %v", want, err) | |
| } | |
| if !match { | |
| return fmt.Errorf("%s.go:\n%q\ndoes not match %s", file, out, want) | |
| } | |
| return nil | |
| } | |
| type testcase struct { | |
| file string | |
| want string | |
| } | |
| var tests = []testcase{ | |
| // defer_panic_recover | |
| {"defer", `^0 3210 2$`}, | |
| {"defer2", `^Calling g. Printing in g 0 Printing in g 1 Printing in g 2 Printing in g 3 Panicking! Defer in g 3 Defer in g 2 Defer in g 1 Defer in g 0 Recovered in f 4 Returned normally from f.$`}, | |
| // effective_go | |
| {"eff_bytesize", `^1.00YB 9.09TB$`}, | |
| {"eff_qr", ""}, | |
| {"eff_sequence", `^\[-1 2 6 16 44\]$`}, | |
| {"eff_unused2", ""}, | |
| // error_handling | |
| {"error", ""}, | |
| {"error2", ""}, | |
| {"error3", ""}, | |
| {"error4", ""}, | |
| // law_of_reflection | |
| {"interface", ""}, | |
| {"interface2", `^type: float64$`}, | |
| // c_go_cgo | |
| {"cgo1", ""}, | |
| {"cgo2", ""}, | |
| {"cgo3", ""}, | |
| {"cgo4", ""}, | |
| // timeout | |
| {"timeout1", ""}, | |
| {"timeout2", ""}, | |
| // gobs | |
| {"gobs1", ""}, | |
| {"gobs2", ""}, | |
| // json | |
| {"json1", `^$`}, | |
| {"json2", `the reciprocal of i is`}, | |
| {"json3", `Age is int 6`}, | |
| {"json4", `^$`}, | |
| {"json5", ""}, | |
| // image_package | |
| {"image_package1", `^X is 2 Y is 1$`}, | |
| {"image_package2", `^3 4 false$`}, | |
| {"image_package3", `^3 4 true$`}, | |
| {"image_package4", `^image.Point{X:2, Y:1}$`}, | |
| {"image_package5", `^{255 0 0 255}$`}, | |
| {"image_package6", `^8 4 true$`}, | |
| // other | |
| {"go1", `^Christmas is a holiday: true .*go1.go already exists$`}, | |
| {"slices", ""}, | |
| } | |
| func onlyTest(files ...string) { | |
| var new []testcase | |
| NextFile: | |
| for _, file := range files { | |
| file = strings.TrimSuffix(file, ".go") | |
| for _, tt := range tests { | |
| if tt.file == file { | |
| new = append(new, tt) | |
| continue NextFile | |
| } | |
| } | |
| fmt.Fprintf(os.Stderr, "test %s.go not found\n", file) | |
| os.Exit(1) | |
| } | |
| tests = new | |
| } | |
| func skipTest(file string) { | |
| for i, tt := range tests { | |
| if tt.file == file { | |
| copy(tests[i:], tests[i+1:]) | |
| tests = tests[:len(tests)-1] | |
| return | |
| } | |
| } | |
| panic("delete(" + file + "): not found") | |
| } | |
| func fixcgo() { | |
| if os.Getenv("CGO_ENABLED") != "1" { | |
| skipTest("cgo1") | |
| skipTest("cgo2") | |
| skipTest("cgo3") | |
| skipTest("cgo4") | |
| return | |
| } | |
| switch runtime.GOOS { | |
| case "freebsd": | |
| // cgo1 and cgo2 don't run on freebsd, srandom has a different signature | |
| skipTest("cgo1") | |
| skipTest("cgo2") | |
| case "netbsd": | |
| // cgo1 and cgo2 don't run on netbsd, srandom has a different signature | |
| skipTest("cgo1") | |
| skipTest("cgo2") | |
| } | |
| } |