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))