github.com/goplus/igop@v0.25.0/load/test.go (about)

     1  /*
     2   * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package load
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"go/ast"
    23  	"go/build"
    24  	"go/doc"
    25  	"go/parser"
    26  	"go/token"
    27  	"os"
    28  	"path/filepath"
    29  	"sort"
    30  	"strings"
    31  	"text/template"
    32  	"unicode"
    33  	"unicode/utf8"
    34  )
    35  
    36  // TestMain create testmain data form package
    37  func TestMain(bp *build.Package) ([]byte, error) {
    38  	t, err := loadTestFuncs(bp)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	return formatTestmain(t)
    43  }
    44  
    45  // TestCover not used
    46  type TestCover struct {
    47  	Mode     string
    48  	Local    bool
    49  	Pkgs     []*build.Package
    50  	Paths    []string
    51  	Vars     []coverInfo
    52  	DeclVars func(*build.Package, ...string) map[string]*CoverVar
    53  }
    54  
    55  // isTestFunc tells whether fn has the type of a testing function. arg
    56  // specifies the parameter type we look for: B, M or T.
    57  func isTestFunc(fn *ast.FuncDecl, arg string) bool {
    58  	if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
    59  		fn.Type.Params.List == nil ||
    60  		len(fn.Type.Params.List) != 1 ||
    61  		len(fn.Type.Params.List[0].Names) > 1 {
    62  		return false
    63  	}
    64  	ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
    65  	if !ok {
    66  		return false
    67  	}
    68  	// We can't easily check that the type is *testing.M
    69  	// because we don't know how testing has been imported,
    70  	// but at least check that it's *M or *something.M.
    71  	// Same applies for B and T.
    72  	if name, ok := ptr.X.(*ast.Ident); ok && name.Name == arg {
    73  		return true
    74  	}
    75  	if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == arg {
    76  		return true
    77  	}
    78  	return false
    79  }
    80  
    81  // isTest tells whether name looks like a test (or benchmark, according to prefix).
    82  // It is a Test (say) if there is a character after Test that is not a lower-case letter.
    83  // We don't want TesticularCancer.
    84  func isTest(name, prefix string) bool {
    85  	if !strings.HasPrefix(name, prefix) {
    86  		return false
    87  	}
    88  	if len(name) == len(prefix) { // "Test" is ok
    89  		return true
    90  	}
    91  	rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
    92  	return !unicode.IsLower(rune)
    93  }
    94  
    95  // CoverVar holds the name of the generated coverage variables targeting the named file.
    96  type CoverVar struct {
    97  	File string // local file name
    98  	Var  string // name of count struct
    99  }
   100  
   101  type coverInfo struct {
   102  	Package *build.Package
   103  	Vars    map[string]*CoverVar
   104  }
   105  
   106  // loadTestFuncs returns the testFuncs describing the tests that will be run.
   107  // The returned testFuncs is always non-nil, even if an error occurred while
   108  // processing test files.
   109  func loadTestFuncs(bp *build.Package) (*testFuncs, error) {
   110  	t := &testFuncs{
   111  		Package: bp,
   112  	}
   113  	var err error
   114  	for _, file := range bp.TestGoFiles {
   115  		if lerr := t.load(filepath.Join(bp.Dir, file), "_test", &t.ImportTest, &t.NeedTest); lerr != nil && err == nil {
   116  			err = lerr
   117  		}
   118  	}
   119  	for _, file := range bp.XTestGoFiles {
   120  		if lerr := t.load(filepath.Join(bp.Dir, file), "_xtest", &t.ImportXtest, &t.NeedXtest); lerr != nil && err == nil {
   121  			err = lerr
   122  		}
   123  	}
   124  	return t, err
   125  }
   126  
   127  // formatTestmain returns the content of the _testmain.go file for t.
   128  func formatTestmain(t *testFuncs) ([]byte, error) {
   129  	var buf bytes.Buffer
   130  	if err := testmainTmpl.Execute(&buf, t); err != nil {
   131  		return nil, err
   132  	}
   133  	return buf.Bytes(), nil
   134  }
   135  
   136  type testFuncs struct {
   137  	Tests       []testFunc
   138  	Benchmarks  []testFunc
   139  	FuzzTargets []testFunc
   140  	Examples    []testFunc
   141  	TestMain    *testFunc
   142  	Package     *build.Package
   143  	ImportTest  bool
   144  	NeedTest    bool
   145  	ImportXtest bool
   146  	NeedXtest   bool
   147  	Cover       *TestCover
   148  }
   149  
   150  // ImportPath returns the import path of the package being tested, if it is within GOPATH.
   151  // This is printed by the testing package when running benchmarks.
   152  func (t *testFuncs) ImportPath() string {
   153  	pkg := t.Package.ImportPath
   154  	if strings.HasPrefix(pkg, "_/") {
   155  		return ""
   156  	}
   157  	if pkg == "command-line-arguments" {
   158  		return ""
   159  	}
   160  	return pkg
   161  }
   162  
   163  // Covered returns a string describing which packages are being tested for coverage.
   164  // If the covered package is the same as the tested package, it returns the empty string.
   165  // Otherwise it is a comma-separated human-readable list of packages beginning with
   166  // " in", ready for use in the coverage message.
   167  func (t *testFuncs) Covered() string {
   168  	if t.Cover == nil || t.Cover.Paths == nil {
   169  		return ""
   170  	}
   171  	return " in " + strings.Join(t.Cover.Paths, ", ")
   172  }
   173  
   174  // Tested returns the name of the package being tested.
   175  func (t *testFuncs) Tested() string {
   176  	return t.Package.Name
   177  }
   178  
   179  type testFunc struct {
   180  	Package   string // imported package name (_test or _xtest)
   181  	Name      string // function name
   182  	Output    string // output, for examples
   183  	Unordered bool   // output is allowed to be unordered.
   184  }
   185  
   186  var testFileSet = token.NewFileSet()
   187  
   188  func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
   189  	// Pass in the overlaid source if we have an overlay for this file.
   190  	src, err := os.Open(filename)
   191  	if err != nil {
   192  		return err
   193  	}
   194  	defer src.Close()
   195  	f, err := parser.ParseFile(testFileSet, filename, src, parser.ParseComments)
   196  	if err != nil {
   197  		return err
   198  	}
   199  	for _, d := range f.Decls {
   200  		n, ok := d.(*ast.FuncDecl)
   201  		if !ok {
   202  			continue
   203  		}
   204  		if n.Recv != nil {
   205  			continue
   206  		}
   207  		name := n.Name.String()
   208  		switch {
   209  		case name == "TestMain":
   210  			if isTestFunc(n, "T") {
   211  				t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
   212  				*doImport, *seen = true, true
   213  				continue
   214  			}
   215  			err := checkTestFunc(n, "M")
   216  			if err != nil {
   217  				return err
   218  			}
   219  			if t.TestMain != nil {
   220  				return errors.New("multiple definitions of TestMain")
   221  			}
   222  			t.TestMain = &testFunc{pkg, name, "", false}
   223  			*doImport, *seen = true, true
   224  		case isTest(name, "Test"):
   225  			err := checkTestFunc(n, "T")
   226  			if err != nil {
   227  				return err
   228  			}
   229  			t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
   230  			*doImport, *seen = true, true
   231  		case isTest(name, "Benchmark"):
   232  			err := checkTestFunc(n, "B")
   233  			if err != nil {
   234  				return err
   235  			}
   236  			t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
   237  			*doImport, *seen = true, true
   238  		case isTest(name, "Fuzz"):
   239  			err := checkTestFunc(n, "F")
   240  			if err != nil {
   241  				return err
   242  			}
   243  			t.FuzzTargets = append(t.FuzzTargets, testFunc{pkg, name, "", false})
   244  			*doImport, *seen = true, true
   245  		}
   246  	}
   247  	ex := doc.Examples(f)
   248  	sort.Slice(ex, func(i, j int) bool { return ex[i].Order < ex[j].Order })
   249  	for _, e := range ex {
   250  		*doImport = true // import test file whether executed or not
   251  		if e.Output == "" && !e.EmptyOutput {
   252  			// Don't run examples with no output.
   253  			continue
   254  		}
   255  		t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered})
   256  		*seen = true
   257  	}
   258  	return nil
   259  }
   260  
   261  var testmainTmpl = template.Must(template.New("main").Parse(testmainData))