You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
8.0 KiB

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