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