gitee.com/wgliang/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/simpler/ssa/testmain.go (about) 1 // Copyright 2013 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 // +build go1.5 6 7 package ssa 8 9 // CreateTestMainPackage synthesizes a main package that runs all the 10 // tests of the supplied packages. 11 // It is closely coupled to $GOROOT/src/cmd/go/test.go and $GOROOT/src/testing. 12 // 13 // TODO(adonovan): this file no longer needs to live in the ssa package. 14 // Move it to ssautil. 15 16 import ( 17 "bytes" 18 "fmt" 19 "go/ast" 20 "go/parser" 21 "go/types" 22 "log" 23 "os" 24 "strings" 25 "text/template" 26 ) 27 28 // FindTests returns the Test, Benchmark, and Example functions 29 // (as defined by "go test") defined in the specified package, 30 // and its TestMain function, if any. 31 func FindTests(pkg *Package) (tests, benchmarks, examples []*Function, main *Function) { 32 prog := pkg.Prog 33 34 // The first two of these may be nil: if the program doesn't import "testing", 35 // it can't contain any tests, but it may yet contain Examples. 36 var testSig *types.Signature // func(*testing.T) 37 var benchmarkSig *types.Signature // func(*testing.B) 38 var exampleSig = types.NewSignature(nil, nil, nil, false) // func() 39 40 // Obtain the types from the parameters of testing.MainStart. 41 if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil { 42 mainStart := testingPkg.Func("MainStart") 43 params := mainStart.Signature.Params() 44 testSig = funcField(params.At(1).Type()) 45 benchmarkSig = funcField(params.At(2).Type()) 46 47 // Does the package define this function? 48 // func TestMain(*testing.M) 49 if f := pkg.Func("TestMain"); f != nil { 50 sig := f.Type().(*types.Signature) 51 starM := mainStart.Signature.Results().At(0).Type() // *testing.M 52 if sig.Results().Len() == 0 && 53 sig.Params().Len() == 1 && 54 types.Identical(sig.Params().At(0).Type(), starM) { 55 main = f 56 } 57 } 58 } 59 60 // TODO(adonovan): use a stable order, e.g. lexical. 61 for _, mem := range pkg.Members { 62 if f, ok := mem.(*Function); ok && 63 ast.IsExported(f.Name()) && 64 strings.HasSuffix(prog.Fset.Position(f.Pos()).Filename, "_test.go") { 65 66 switch { 67 case testSig != nil && isTestSig(f, "Test", testSig): 68 tests = append(tests, f) 69 case benchmarkSig != nil && isTestSig(f, "Benchmark", benchmarkSig): 70 benchmarks = append(benchmarks, f) 71 case isTestSig(f, "Example", exampleSig): 72 examples = append(examples, f) 73 default: 74 continue 75 } 76 } 77 } 78 return 79 } 80 81 // Like isTest, but checks the signature too. 82 func isTestSig(f *Function, prefix string, sig *types.Signature) bool { 83 return isTest(f.Name(), prefix) && types.Identical(f.Signature, sig) 84 } 85 86 // Given the type of one of the three slice parameters of testing.Main, 87 // returns the function type. 88 func funcField(slice types.Type) *types.Signature { 89 return slice.(*types.Slice).Elem().Underlying().(*types.Struct).Field(1).Type().(*types.Signature) 90 } 91 92 // isTest tells whether name looks like a test (or benchmark, according to prefix). 93 // It is a Test (say) if there is a character after Test that is not a lower-case letter. 94 // We don't want TesticularCancer. 95 // Plundered from $GOROOT/src/cmd/go/test.go 96 func isTest(name, prefix string) bool { 97 if !strings.HasPrefix(name, prefix) { 98 return false 99 } 100 if len(name) == len(prefix) { // "Test" is ok 101 return true 102 } 103 return ast.IsExported(name[len(prefix):]) 104 } 105 106 // CreateTestMainPackage creates and returns a synthetic "testmain" 107 // package for the specified package if it defines tests, benchmarks or 108 // executable examples, or nil otherwise. The new package is named 109 // "main" and provides a function named "main" that runs the tests, 110 // similar to the one that would be created by the 'go test' tool. 111 // 112 // Subsequent calls to prog.AllPackages include the new package. 113 // The package pkg must belong to the program prog. 114 func (prog *Program) CreateTestMainPackage(pkg *Package) *Package { 115 if pkg.Prog != prog { 116 log.Fatal("Package does not belong to Program") 117 } 118 119 // Template data 120 var data struct { 121 Pkg *Package 122 Tests, Benchmarks, Examples []*Function 123 Main *Function 124 Go18 bool 125 } 126 data.Pkg = pkg 127 128 // Enumerate tests. 129 data.Tests, data.Benchmarks, data.Examples, data.Main = FindTests(pkg) 130 if data.Main == nil && 131 data.Tests == nil && data.Benchmarks == nil && data.Examples == nil { 132 return nil 133 } 134 135 // Synthesize source for testmain package. 136 path := pkg.Pkg.Path() + "$testmain" 137 tmpl := testmainTmpl 138 if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil { 139 // In Go 1.8, testing.MainStart's first argument is an interface, not a func. 140 data.Go18 = types.IsInterface(testingPkg.Func("MainStart").Signature.Params().At(0).Type()) 141 } else { 142 // The program does not import "testing", but FindTests 143 // returned non-nil, which must mean there were Examples 144 // but no Test, Benchmark, or TestMain functions. 145 146 // We'll simply call them from testmain.main; this will 147 // ensure they don't panic, but will not check any 148 // "Output:" comments. 149 // (We should not execute an Example that has no 150 // "Output:" comment, but it's impossible to tell here.) 151 tmpl = examplesOnlyTmpl 152 } 153 var buf bytes.Buffer 154 if err := tmpl.Execute(&buf, data); err != nil { 155 log.Fatalf("internal error expanding template for %s: %v", path, err) 156 } 157 if false { // debugging 158 fmt.Fprintln(os.Stderr, buf.String()) 159 } 160 161 // Parse and type-check the testmain package. 162 f, err := parser.ParseFile(prog.Fset, path+".go", &buf, parser.Mode(0)) 163 if err != nil { 164 log.Fatalf("internal error parsing %s: %v", path, err) 165 } 166 conf := types.Config{ 167 DisableUnusedImportCheck: true, 168 Importer: importer{pkg}, 169 } 170 files := []*ast.File{f} 171 info := &types.Info{ 172 Types: make(map[ast.Expr]types.TypeAndValue), 173 Defs: make(map[*ast.Ident]types.Object), 174 Uses: make(map[*ast.Ident]types.Object), 175 Implicits: make(map[ast.Node]types.Object), 176 Scopes: make(map[ast.Node]*types.Scope), 177 Selections: make(map[*ast.SelectorExpr]*types.Selection), 178 } 179 testmainPkg, err := conf.Check(path, prog.Fset, files, info) 180 if err != nil { 181 log.Fatalf("internal error type-checking %s: %v", path, err) 182 } 183 184 // Create and build SSA code. 185 testmain := prog.CreatePackage(testmainPkg, files, info, false) 186 testmain.SetDebugMode(false) 187 testmain.Build() 188 testmain.Func("main").Synthetic = "test main function" 189 testmain.Func("init").Synthetic = "package initializer" 190 return testmain 191 } 192 193 // An implementation of types.Importer for an already loaded SSA program. 194 type importer struct { 195 pkg *Package // package under test; may be non-importable 196 } 197 198 func (imp importer) Import(path string) (*types.Package, error) { 199 if p := imp.pkg.Prog.ImportedPackage(path); p != nil { 200 return p.Pkg, nil 201 } 202 if path == imp.pkg.Pkg.Path() { 203 return imp.pkg.Pkg, nil 204 } 205 return nil, fmt.Errorf("not found") // can't happen 206 } 207 208 var testmainTmpl = template.Must(template.New("testmain").Parse(` 209 package main 210 211 import "io" 212 import "os" 213 import "testing" 214 import p {{printf "%q" .Pkg.Pkg.Path}} 215 216 {{if .Go18}} 217 type deps struct{} 218 219 func (deps) MatchString(pat, str string) (bool, error) { return true, nil } 220 func (deps) StartCPUProfile(io.Writer) error { return nil } 221 func (deps) StopCPUProfile() {} 222 func (deps) WriteHeapProfile(io.Writer) error { return nil } 223 func (deps) WriteProfileTo(string, io.Writer, int) error { return nil } 224 225 var match deps 226 {{else}} 227 func match(_, _ string) (bool, error) { return true, nil } 228 {{end}} 229 230 func main() { 231 tests := []testing.InternalTest{ 232 {{range .Tests}} 233 { {{printf "%q" .Name}}, p.{{.Name}} }, 234 {{end}} 235 } 236 benchmarks := []testing.InternalBenchmark{ 237 {{range .Benchmarks}} 238 { {{printf "%q" .Name}}, p.{{.Name}} }, 239 {{end}} 240 } 241 examples := []testing.InternalExample{ 242 {{range .Examples}} 243 {Name: {{printf "%q" .Name}}, F: p.{{.Name}}}, 244 {{end}} 245 } 246 m := testing.MainStart(match, tests, benchmarks, examples) 247 {{with .Main}} 248 p.{{.Name}}(m) 249 {{else}} 250 os.Exit(m.Run()) 251 {{end}} 252 } 253 254 `)) 255 256 var examplesOnlyTmpl = template.Must(template.New("examples").Parse(` 257 package main 258 259 import p {{printf "%q" .Pkg.Pkg.Path}} 260 261 func main() { 262 {{range .Examples}} 263 p.{{.Name}}() 264 {{end}} 265 } 266 `))