|  | // 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") | 
|  | } | 
|  | } |