Home | History | Annotate | Download | only in progs
      1 // Copyright 2015 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // run runs the docs tests found in this directory.
      6 package main
      7 
      8 import (
      9 	"bytes"
     10 	"flag"
     11 	"fmt"
     12 	"io/ioutil"
     13 	"os"
     14 	"os/exec"
     15 	"path/filepath"
     16 	"regexp"
     17 	"runtime"
     18 	"strings"
     19 )
     20 
     21 const usage = `go run run.go [tests]
     22 
     23 run.go runs the docs tests in this directory.
     24 If no tests are provided, it runs all tests.
     25 Tests may be specified without their .go suffix.
     26 `
     27 
     28 func main() {
     29 	flag.Usage = func() {
     30 		fmt.Fprintf(os.Stderr, usage)
     31 		flag.PrintDefaults()
     32 		os.Exit(2)
     33 	}
     34 
     35 	flag.Parse()
     36 	if flag.NArg() == 0 {
     37 		// run all tests
     38 		fixcgo()
     39 	} else {
     40 		// run specified tests
     41 		onlyTest(flag.Args()...)
     42 	}
     43 
     44 	tmpdir, err := ioutil.TempDir("", "go-progs")
     45 	if err != nil {
     46 		fmt.Fprintln(os.Stderr, err)
     47 		os.Exit(1)
     48 	}
     49 
     50 	// ratec limits the number of tests running concurrently.
     51 	// None of the tests are intensive, so don't bother
     52 	// trying to manually adjust for slow builders.
     53 	ratec := make(chan bool, runtime.NumCPU())
     54 	errc := make(chan error, len(tests))
     55 
     56 	for _, tt := range tests {
     57 		tt := tt
     58 		ratec <- true
     59 		go func() {
     60 			errc <- test(tmpdir, tt.file, tt.want)
     61 			<-ratec
     62 		}()
     63 	}
     64 
     65 	var rc int
     66 	for range tests {
     67 		if err := <-errc; err != nil {
     68 			fmt.Fprintln(os.Stderr, err)
     69 			rc = 1
     70 		}
     71 	}
     72 	os.Remove(tmpdir)
     73 	os.Exit(rc)
     74 }
     75 
     76 // test builds the test in the given file.
     77 // If want is non-empty, test also runs the test
     78 // and checks that the output matches the regexp want.
     79 func test(tmpdir, file, want string) error {
     80 	// Build the program.
     81 	prog := filepath.Join(tmpdir, file)
     82 	cmd := exec.Command("go", "build", "-o", prog, file+".go")
     83 	out, err := cmd.CombinedOutput()
     84 	if err != nil {
     85 		return fmt.Errorf("go build %s.go failed: %v\nOutput:\n%s", file, err, out)
     86 	}
     87 	defer os.Remove(prog)
     88 
     89 	// Only run the test if we have output to check.
     90 	if want == "" {
     91 		return nil
     92 	}
     93 
     94 	cmd = exec.Command(prog)
     95 	out, err = cmd.CombinedOutput()
     96 	if err != nil {
     97 		return fmt.Errorf("%s failed: %v\nOutput:\n%s", file, err, out)
     98 	}
     99 
    100 	// Canonicalize output.
    101 	out = bytes.TrimRight(out, "\n")
    102 	out = bytes.Replace(out, []byte{'\n'}, []byte{' '}, -1)
    103 
    104 	// Check the result.
    105 	match, err := regexp.Match(want, out)
    106 	if err != nil {
    107 		return fmt.Errorf("failed to parse regexp %q: %v", want, err)
    108 	}
    109 	if !match {
    110 		return fmt.Errorf("%s.go:\n%q\ndoes not match %s", file, out, want)
    111 	}
    112 
    113 	return nil
    114 }
    115 
    116 type testcase struct {
    117 	file string
    118 	want string
    119 }
    120 
    121 var tests = []testcase{
    122 	// defer_panic_recover
    123 	{"defer", `^0 3210 2$`},
    124 	{"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.$`},
    125 
    126 	// effective_go
    127 	{"eff_bytesize", `^1.00YB 9.09TB$`},
    128 	{"eff_qr", ""},
    129 	{"eff_sequence", `^\[-1 2 6 16 44\]$`},
    130 	{"eff_unused2", ""},
    131 
    132 	// error_handling
    133 	{"error", ""},
    134 	{"error2", ""},
    135 	{"error3", ""},
    136 	{"error4", ""},
    137 
    138 	// law_of_reflection
    139 	{"interface", ""},
    140 	{"interface2", `^type: float64$`},
    141 
    142 	// c_go_cgo
    143 	{"cgo1", ""},
    144 	{"cgo2", ""},
    145 	{"cgo3", ""},
    146 	{"cgo4", ""},
    147 
    148 	// timeout
    149 	{"timeout1", ""},
    150 	{"timeout2", ""},
    151 
    152 	// gobs
    153 	{"gobs1", ""},
    154 	{"gobs2", ""},
    155 
    156 	// json
    157 	{"json1", `^$`},
    158 	{"json2", `the reciprocal of i is`},
    159 	{"json3", `Age is int 6`},
    160 	{"json4", `^$`},
    161 	{"json5", ""},
    162 
    163 	// image_package
    164 	{"image_package1", `^X is 2 Y is 1$`},
    165 	{"image_package2", `^3 4 false$`},
    166 	{"image_package3", `^3 4 true$`},
    167 	{"image_package4", `^image.Point{X:2, Y:1}$`},
    168 	{"image_package5", `^{255 0 0 255}$`},
    169 	{"image_package6", `^8 4 true$`},
    170 
    171 	// other
    172 	{"go1", `^Christmas is a holiday: true .*go1.go already exists$`},
    173 	{"slices", ""},
    174 }
    175 
    176 func onlyTest(files ...string) {
    177 	var new []testcase
    178 NextFile:
    179 	for _, file := range files {
    180 		file = strings.TrimSuffix(file, ".go")
    181 		for _, tt := range tests {
    182 			if tt.file == file {
    183 				new = append(new, tt)
    184 				continue NextFile
    185 			}
    186 		}
    187 		fmt.Fprintf(os.Stderr, "test %s.go not found\n", file)
    188 		os.Exit(1)
    189 	}
    190 	tests = new
    191 }
    192 
    193 func skipTest(file string) {
    194 	for i, tt := range tests {
    195 		if tt.file == file {
    196 			copy(tests[i:], tests[i+1:])
    197 			tests = tests[:len(tests)-1]
    198 			return
    199 		}
    200 	}
    201 	panic("delete(" + file + "): not found")
    202 }
    203 
    204 func fixcgo() {
    205 	if os.Getenv("CGO_ENABLED") != "1" {
    206 		skipTest("cgo1")
    207 		skipTest("cgo2")
    208 		skipTest("cgo3")
    209 		skipTest("cgo4")
    210 		return
    211 	}
    212 
    213 	switch runtime.GOOS {
    214 	case "freebsd":
    215 		// cgo1 and cgo2 don't run on freebsd, srandom has a different signature
    216 		skipTest("cgo1")
    217 		skipTest("cgo2")
    218 	case "netbsd":
    219 		// cgo1 and cgo2 don't run on netbsd, srandom has a different signature
    220 		skipTest("cgo1")
    221 		skipTest("cgo2")
    222 	}
    223 }
    224