github.com/goplus/gossa@v0.3.25/testmain.go (about) 1 package gossa 2 3 import ( 4 "bytes" 5 "fmt" 6 "go/ast" 7 "go/parser" 8 "go/types" 9 "log" 10 "os" 11 "sort" 12 "strings" 13 "text/template" 14 15 "golang.org/x/tools/go/ssa" 16 ) 17 18 // CreateTestMainPackage creates and returns a synthetic "testmain" 19 // package for the specified package if it defines tests, benchmarks or 20 // executable examples, or nil otherwise. The new package is named 21 // "main" and provides a function named "main" that runs the tests, 22 // similar to the one that would be created by the 'go test' tool. 23 // 24 // Subsequent calls to prog.AllPackages include the new package. 25 // The package pkg must belong to the program prog. 26 // 27 // Deprecated: Use golang.org/x/tools/go/packages to access synthetic 28 // testmain packages. 29 func CreateTestMainPackage(pkg *ssa.Package) (*ssa.Package, error) { 30 prog := pkg.Prog 31 32 // Template data 33 var data struct { 34 Pkg *ssa.Package 35 Tests, Benchmarks, Examples []*ssa.Function 36 FuzzTargets []*ssa.Function 37 Main *ssa.Function 38 } 39 data.Pkg = pkg 40 41 // Enumerate tests. 42 data.Tests, data.Benchmarks, data.Examples, data.Main = FindTests(pkg) 43 if data.Main == nil && 44 data.Tests == nil && data.Benchmarks == nil && data.Examples == nil { 45 return nil, nil 46 } 47 sort.Slice(data.Tests, func(i, j int) bool { 48 return data.Tests[i].Pos() < data.Tests[j].Pos() 49 }) 50 sort.Slice(data.Benchmarks, func(i, j int) bool { 51 return data.Benchmarks[i].Pos() < data.Benchmarks[j].Pos() 52 }) 53 sort.Slice(data.Examples, func(i, j int) bool { 54 return data.Examples[i].Pos() < data.Examples[j].Pos() 55 }) 56 57 // Synthesize source for testmain package. 58 path := pkg.Pkg.Path() + "$testmain" 59 tmpl := testmainTmpl 60 if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil { 61 if testingPkg.Type("InternalFuzzTarget") != nil { 62 tmpl = testmainTmpl_go118 63 } 64 // In Go 1.8, testing.MainStart's first argument is an interface, not a func. 65 // data.Go18 = types.IsInterface(testingPkg.Func("MainStart").Signature.Params().At(0).Type()) 66 } else { 67 // The program does not import "testing", but FindTests 68 // returned non-nil, which must mean there were Examples 69 // but no Test, Benchmark, or TestMain functions. 70 71 // We'll simply call them from testmain.main; this will 72 // ensure they don't panic, but will not check any 73 // "Output:" comments. 74 // (We should not execute an Example that has no 75 // "Output:" comment, but it's impossible to tell here.) 76 tmpl = examplesOnlyTmpl 77 } 78 var buf bytes.Buffer 79 if err := tmpl.Execute(&buf, data); err != nil { 80 log.Fatalf("internal error expanding template for %s: %v", path, err) 81 } 82 83 if false { // debugging 84 fmt.Fprintln(os.Stderr, buf.String()) 85 } 86 87 // Parse and type-check the testmain package. 88 f, err := parser.ParseFile(prog.Fset, path+".go", &buf, parser.Mode(0)) 89 if err != nil { 90 return nil, err 91 } 92 conf := types.Config{ 93 DisableUnusedImportCheck: true, 94 Importer: testImporter{pkg}, 95 } 96 files := []*ast.File{f} 97 info := &types.Info{ 98 Types: make(map[ast.Expr]types.TypeAndValue), 99 Defs: make(map[*ast.Ident]types.Object), 100 Uses: make(map[*ast.Ident]types.Object), 101 Implicits: make(map[ast.Node]types.Object), 102 Scopes: make(map[ast.Node]*types.Scope), 103 Selections: make(map[*ast.SelectorExpr]*types.Selection), 104 } 105 testmainPkg, err := conf.Check(path, prog.Fset, files, info) 106 if err != nil { 107 return nil, err 108 } 109 110 // Create and build SSA code. 111 testmain := prog.CreatePackage(testmainPkg, files, info, false) 112 testmain.SetDebugMode(false) 113 testmain.Build() 114 testmain.Func("main").Synthetic = "test main function" 115 testmain.Func("init").Synthetic = "package initializer" 116 return testmain, nil 117 } 118 119 // An implementation of types.Importer for an already loaded SSA program. 120 type testImporter struct { 121 pkg *ssa.Package // package under test; may be non-importable 122 } 123 124 func (imp testImporter) Import(path string) (*types.Package, error) { 125 if p := imp.pkg.Prog.ImportedPackage(path); p != nil { 126 return p.Pkg, nil 127 } 128 if path == imp.pkg.Pkg.Path() { 129 return imp.pkg.Pkg, nil 130 } 131 return nil, ErrNotFoundPackage 132 } 133 134 var testmainTmpl = template.Must(template.New("testmain").Parse(` 135 package main 136 137 import ( 138 "io" 139 "os" 140 "regexp" 141 "testing" 142 _test {{printf "%q" .Pkg.Pkg.Path}} 143 ) 144 145 type deps struct{} 146 147 func (deps) ImportPath() string { return "" } 148 func (deps) SetPanicOnExit0(bool) {} 149 func (deps) StartCPUProfile(io.Writer) error { return nil } 150 func (deps) StartTestLog(io.Writer) {} 151 func (deps) StopCPUProfile() {} 152 func (deps) StopTestLog() error { return nil } 153 func (deps) WriteHeapProfile(io.Writer) error { return nil } 154 func (deps) WriteProfileTo(string, io.Writer, int) error { return nil } 155 156 var matchPat string 157 var matchRe *regexp.Regexp 158 159 func (deps) MatchString(pat, str string) (result bool, err error) { 160 if matchRe == nil || matchPat != pat { 161 matchPat = pat 162 matchRe, err = regexp.Compile(matchPat) 163 if err != nil { 164 return 165 } 166 } 167 return matchRe.MatchString(str), nil 168 } 169 170 var tests = []testing.InternalTest{ 171 {{range .Tests}} 172 { {{printf "%q" .Name}}, _test.{{.Name}} }, 173 {{end}} 174 } 175 176 var benchmarks = []testing.InternalBenchmark{ 177 {{range .Benchmarks}} 178 { {{printf "%q" .Name}}, _test.{{.Name}} }, 179 {{end}} 180 } 181 182 var examples = []testing.InternalExample{ 183 {{range .Examples}} 184 {Name: {{printf "%q" .Name}}, F: _test.{{.Name}}}, 185 {{end}} 186 } 187 188 func main() { 189 m := testing.MainStart(deps{}, tests, benchmarks, examples) 190 {{with .Main}} 191 _test.{{.Name}}(m) 192 {{else}} 193 os.Exit(m.Run()) 194 {{end}} 195 } 196 197 `)) 198 199 var testmainTmpl_go118 = template.Must(template.New("testmain").Parse(` 200 package main 201 202 import ( 203 "io" 204 "os" 205 "regexp" 206 "testing" 207 "time" 208 "reflect" 209 _test {{printf "%q" .Pkg.Pkg.Path}} 210 ) 211 212 type deps struct{} 213 214 func (deps) ImportPath() string { return "" } 215 func (deps) SetPanicOnExit0(bool) {} 216 func (deps) StartCPUProfile(io.Writer) error { return nil } 217 func (deps) StartTestLog(io.Writer) {} 218 func (deps) StopCPUProfile() {} 219 func (deps) StopTestLog() error { return nil } 220 func (deps) WriteHeapProfile(io.Writer) error { return nil } 221 func (deps) WriteProfileTo(string, io.Writer, int) error { return nil } 222 223 type corpusEntry = struct { 224 Parent string 225 Path string 226 Data []byte 227 Values []any 228 Generation int 229 IsSeed bool 230 } 231 232 func (deps) CoordinateFuzzing(time.Duration, int64, time.Duration, int64, int, []corpusEntry, []reflect.Type, string, string) error { 233 return nil 234 } 235 func (deps) RunFuzzWorker(func(corpusEntry) error) error { return nil } 236 func (deps) ReadCorpus(string, []reflect.Type) ([]corpusEntry, error) { 237 return nil, nil 238 } 239 func (f deps) CheckCorpus([]any, []reflect.Type) error { return nil } 240 func (f deps) ResetCoverage() {} 241 func (f deps) SnapshotCoverage() {} 242 243 var matchPat string 244 var matchRe *regexp.Regexp 245 246 func (deps) MatchString(pat, str string) (result bool, err error) { 247 if matchRe == nil || matchPat != pat { 248 matchPat = pat 249 matchRe, err = regexp.Compile(matchPat) 250 if err != nil { 251 return 252 } 253 } 254 return matchRe.MatchString(str), nil 255 } 256 257 var tests = []testing.InternalTest{ 258 {{range .Tests}} 259 { {{printf "%q" .Name}}, _test.{{.Name}} }, 260 {{end}} 261 } 262 263 var benchmarks = []testing.InternalBenchmark{ 264 {{range .Benchmarks}} 265 { {{printf "%q" .Name}}, _test.{{.Name}} }, 266 {{end}} 267 } 268 269 var fuzzTargets = []testing.InternalFuzzTarget{ 270 {{range .FuzzTargets}} 271 { {{printf "%q" .Name}}, _test.{{.Name}} }, 272 {{end}} 273 } 274 275 var examples = []testing.InternalExample{ 276 {{range .Examples}} 277 {Name: {{printf "%q" .Name}}, F: _test.{{.Name}}}, 278 {{end}} 279 } 280 281 func main() { 282 m := testing.MainStart(deps{}, tests, benchmarks, fuzzTargets, examples) 283 {{with .Main}} 284 _test.{{.Name}}(m) 285 {{else}} 286 os.Exit(m.Run()) 287 {{end}} 288 } 289 290 `)) 291 292 var examplesOnlyTmpl = template.Must(template.New("examples").Parse(` 293 package main 294 295 import p {{printf "%q" .Pkg.Pkg.Path}} 296 297 func main() { 298 {{range .Examples}} 299 p.{{.Name}}() 300 {{end}} 301 } 302 `)) 303 304 // FindTests returns the Test, Benchmark, and Example functions 305 // (as defined by "go test") defined in the specified package, 306 // and its TestMain function, if any. 307 // 308 // Deprecated: Use golang.org/x/tools/go/packages to access synthetic 309 // testmain packages. 310 func FindTests(pkg *ssa.Package) (tests, benchmarks, examples []*ssa.Function, main *ssa.Function) { 311 prog := pkg.Prog 312 313 // The first two of these may be nil: if the program doesn't import "testing", 314 // it can't contain any tests, but it may yet contain Examples. 315 var testSig *types.Signature // func(*testing.T) 316 var benchmarkSig *types.Signature // func(*testing.B) 317 var exampleSig = types.NewSignature(nil, nil, nil, false) // func() 318 319 // Obtain the types from the parameters of testing.MainStart. 320 if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil { 321 mainStart := testingPkg.Func("MainStart") 322 params := mainStart.Signature.Params() 323 testSig = funcField(params.At(1).Type()) 324 benchmarkSig = funcField(params.At(2).Type()) 325 326 // Does the package define this function? 327 // func TestMain(*testing.M) 328 if f := pkg.Func("TestMain"); f != nil { 329 sig := f.Type().(*types.Signature) 330 starM := mainStart.Signature.Results().At(0).Type() // *testing.M 331 if sig.Results().Len() == 0 && 332 sig.Params().Len() == 1 && 333 types.Identical(sig.Params().At(0).Type(), starM) { 334 main = f 335 } 336 } 337 } 338 339 // TODO(adonovan): use a stable order, e.g. lexical. 340 for _, mem := range pkg.Members { 341 if f, ok := mem.(*ssa.Function); ok && 342 ast.IsExported(f.Name()) && 343 strings.HasSuffix(prog.Fset.Position(f.Pos()).Filename, "_test.go") { 344 345 switch { 346 case testSig != nil && isTestSig(f, "Test", testSig): 347 tests = append(tests, f) 348 case benchmarkSig != nil && isTestSig(f, "Benchmark", benchmarkSig): 349 benchmarks = append(benchmarks, f) 350 case isTestSig(f, "Example", exampleSig): 351 examples = append(examples, f) 352 default: 353 continue 354 } 355 } 356 } 357 return 358 } 359 360 // Like isTest, but checks the signature too. 361 func isTestSig(f *ssa.Function, prefix string, sig *types.Signature) bool { 362 return isTest(f.Name(), prefix) && types.Identical(f.Signature, sig) 363 } 364 365 // Given the type of one of the three slice parameters of testing.Main, 366 // returns the function type. 367 func funcField(slice types.Type) *types.Signature { 368 return slice.(*types.Slice).Elem().Underlying().(*types.Struct).Field(1).Type().(*types.Signature) 369 } 370 371 // isTest tells whether name looks like a test (or benchmark, according to prefix). 372 // It is a Test (say) if there is a character after Test that is not a lower-case letter. 373 // We don't want TesticularCancer. 374 // Plundered from $GOROOT/src/cmd/go/test.go 375 func isTest(name, prefix string) bool { 376 if !strings.HasPrefix(name, prefix) { 377 return false 378 } 379 if len(name) == len(prefix) { // "Test" is ok 380 return true 381 } 382 return ast.IsExported(name[len(prefix):]) 383 }