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  }