github.com/gocuntian/go@v0.0.0-20160610041250-fee02d270bf8/src/cmd/vet/tests.go (about) 1 // Copyright 2015 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 5 package main 6 7 import ( 8 "go/ast" 9 "go/types" 10 "strings" 11 "unicode" 12 "unicode/utf8" 13 ) 14 15 func init() { 16 register("tests", 17 "check for common mistaken usages of tests/documentation examples", 18 checkTestFunctions, 19 funcDecl) 20 } 21 22 func isExampleSuffix(s string) bool { 23 r, size := utf8.DecodeRuneInString(s) 24 return size > 0 && unicode.IsLower(r) 25 } 26 27 func isTestSuffix(name string) bool { 28 if len(name) == 0 { 29 // "Test" is ok. 30 return true 31 } 32 r, _ := utf8.DecodeRuneInString(name) 33 return !unicode.IsLower(r) 34 } 35 36 func isTestParam(typ ast.Expr, wantType string) bool { 37 ptr, ok := typ.(*ast.StarExpr) 38 if !ok { 39 // Not a pointer. 40 return false 41 } 42 // No easy way of making sure it's a *testing.T or *testing.B: 43 // ensure the name of the type matches. 44 if name, ok := ptr.X.(*ast.Ident); ok { 45 return name.Name == wantType 46 } 47 if sel, ok := ptr.X.(*ast.SelectorExpr); ok { 48 return sel.Sel.Name == wantType 49 } 50 return false 51 } 52 53 func lookup(name string, scopes []*types.Scope) types.Object { 54 for _, scope := range scopes { 55 if o := scope.Lookup(name); o != nil { 56 return o 57 } 58 } 59 return nil 60 } 61 62 func extendedScope(pkg *Package) []*types.Scope { 63 scopes := []*types.Scope{pkg.typesPkg.Scope()} 64 65 pkgName := pkg.typesPkg.Name() 66 if strings.HasPrefix(pkgName, "_test") { 67 basePkg := strings.TrimSuffix(pkgName, "_test") 68 for _, p := range pkg.typesPkg.Imports() { 69 if p.Name() == basePkg { 70 scopes = append(scopes, p.Scope()) 71 break 72 } 73 } 74 } 75 return scopes 76 } 77 78 func checkExample(fn *ast.FuncDecl, pkg *Package, report reporter) { 79 fnName := fn.Name.Name 80 if params := fn.Type.Params; len(params.List) != 0 { 81 report("%s should be niladic", fnName) 82 } 83 if results := fn.Type.Results; results != nil && len(results.List) != 0 { 84 report("%s should return nothing", fnName) 85 } 86 87 if filesRun && !includesNonTest { 88 // The coherence checks between a test and the package it tests 89 // will report false positives if no non-test files have 90 // been provided. 91 return 92 } 93 94 if fnName == "Example" { 95 // Nothing more to do. 96 return 97 } 98 99 var ( 100 exName = strings.TrimPrefix(fnName, "Example") 101 elems = strings.SplitN(exName, "_", 3) 102 ident = elems[0] 103 obj = lookup(ident, extendedScope(pkg)) 104 ) 105 if ident != "" && obj == nil { 106 // Check ExampleFoo and ExampleBadFoo. 107 report("%s refers to unknown identifier: %s", fnName, ident) 108 // Abort since obj is absent and no subsequent checks can be performed. 109 return 110 } 111 if len(elems) < 2 { 112 // Nothing more to do. 113 return 114 } 115 116 if ident == "" { 117 // Check Example_suffix and Example_BadSuffix. 118 if residual := strings.TrimPrefix(exName, "_"); !isExampleSuffix(residual) { 119 report("%s has malformed example suffix: %s", fnName, residual) 120 } 121 return 122 } 123 124 mmbr := elems[1] 125 if !isExampleSuffix(mmbr) { 126 // Check ExampleFoo_Method and ExampleFoo_BadMethod. 127 if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj == nil { 128 report("%s refers to unknown field or method: %s.%s", fnName, ident, mmbr) 129 } 130 } 131 if len(elems) == 3 && !isExampleSuffix(elems[2]) { 132 // Check ExampleFoo_Method_suffix and ExampleFoo_Method_Badsuffix. 133 report("%s has malformed example suffix: %s", fnName, elems[2]) 134 } 135 } 136 137 func checkTest(fn *ast.FuncDecl, prefix string, report reporter) { 138 // Want functions with 0 results and 1 parameter. 139 if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 || 140 fn.Type.Params == nil || 141 len(fn.Type.Params.List) != 1 || 142 len(fn.Type.Params.List[0].Names) > 1 { 143 return 144 } 145 146 // The param must look like a *testing.T or *testing.B. 147 if !isTestParam(fn.Type.Params.List[0].Type, prefix[:1]) { 148 return 149 } 150 151 if !isTestSuffix(fn.Name.Name[len(prefix):]) { 152 report("%s has malformed name: first letter after '%s' must not be lowercase", fn.Name.Name, prefix) 153 } 154 } 155 156 type reporter func(format string, args ...interface{}) 157 158 // checkTestFunctions walks Test, Benchmark and Example functions checking 159 // malformed names, wrong signatures and examples documenting inexistent 160 // identifiers. 161 func checkTestFunctions(f *File, node ast.Node) { 162 if !strings.HasSuffix(f.name, "_test.go") { 163 return 164 } 165 166 fn, ok := node.(*ast.FuncDecl) 167 if !ok || fn.Recv != nil { 168 // Ignore non-functions or functions with receivers. 169 return 170 } 171 172 report := func(format string, args ...interface{}) { f.Badf(node.Pos(), format, args...) } 173 174 switch { 175 case strings.HasPrefix(fn.Name.Name, "Example"): 176 checkExample(fn, f.pkg, report) 177 case strings.HasPrefix(fn.Name.Name, "Test"): 178 checkTest(fn, "Test", report) 179 case strings.HasPrefix(fn.Name.Name, "Benchmark"): 180 checkTest(fn, "Benchmark", report) 181 } 182 }