github.com/karrick/go@v0.0.0-20170817181416-d5b0ec858b37/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(f *File) []*types.Scope { 63 scopes := []*types.Scope{f.pkg.typesPkg.Scope()} 64 if f.basePkg != nil { 65 scopes = append(scopes, f.basePkg.typesPkg.Scope()) 66 } else { 67 // If basePkg is not specified (e.g. when checking a single file) try to 68 // find it among imports. 69 pkgName := f.pkg.typesPkg.Name() 70 if strings.HasSuffix(pkgName, "_test") { 71 basePkgName := strings.TrimSuffix(pkgName, "_test") 72 for _, p := range f.pkg.typesPkg.Imports() { 73 if p.Name() == basePkgName { 74 scopes = append(scopes, p.Scope()) 75 break 76 } 77 } 78 } 79 } 80 return scopes 81 } 82 83 func checkExample(fn *ast.FuncDecl, f *File, report reporter) { 84 fnName := fn.Name.Name 85 if params := fn.Type.Params; len(params.List) != 0 { 86 report("%s should be niladic", fnName) 87 } 88 if results := fn.Type.Results; results != nil && len(results.List) != 0 { 89 report("%s should return nothing", fnName) 90 } 91 92 if filesRun && !includesNonTest { 93 // The coherence checks between a test and the package it tests 94 // will report false positives if no non-test files have 95 // been provided. 96 return 97 } 98 99 if fnName == "Example" { 100 // Nothing more to do. 101 return 102 } 103 104 var ( 105 exName = strings.TrimPrefix(fnName, "Example") 106 elems = strings.SplitN(exName, "_", 3) 107 ident = elems[0] 108 obj = lookup(ident, extendedScope(f)) 109 ) 110 if ident != "" && obj == nil { 111 // Check ExampleFoo and ExampleBadFoo. 112 report("%s refers to unknown identifier: %s", fnName, ident) 113 // Abort since obj is absent and no subsequent checks can be performed. 114 return 115 } 116 if len(elems) < 2 { 117 // Nothing more to do. 118 return 119 } 120 121 if ident == "" { 122 // Check Example_suffix and Example_BadSuffix. 123 if residual := strings.TrimPrefix(exName, "_"); !isExampleSuffix(residual) { 124 report("%s has malformed example suffix: %s", fnName, residual) 125 } 126 return 127 } 128 129 mmbr := elems[1] 130 if !isExampleSuffix(mmbr) { 131 // Check ExampleFoo_Method and ExampleFoo_BadMethod. 132 if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj == nil { 133 report("%s refers to unknown field or method: %s.%s", fnName, ident, mmbr) 134 } 135 } 136 if len(elems) == 3 && !isExampleSuffix(elems[2]) { 137 // Check ExampleFoo_Method_suffix and ExampleFoo_Method_Badsuffix. 138 report("%s has malformed example suffix: %s", fnName, elems[2]) 139 } 140 } 141 142 func checkTest(fn *ast.FuncDecl, prefix string, report reporter) { 143 // Want functions with 0 results and 1 parameter. 144 if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 || 145 fn.Type.Params == nil || 146 len(fn.Type.Params.List) != 1 || 147 len(fn.Type.Params.List[0].Names) > 1 { 148 return 149 } 150 151 // The param must look like a *testing.T or *testing.B. 152 if !isTestParam(fn.Type.Params.List[0].Type, prefix[:1]) { 153 return 154 } 155 156 if !isTestSuffix(fn.Name.Name[len(prefix):]) { 157 report("%s has malformed name: first letter after '%s' must not be lowercase", fn.Name.Name, prefix) 158 } 159 } 160 161 type reporter func(format string, args ...interface{}) 162 163 // checkTestFunctions walks Test, Benchmark and Example functions checking 164 // malformed names, wrong signatures and examples documenting inexistent 165 // identifiers. 166 func checkTestFunctions(f *File, node ast.Node) { 167 if !strings.HasSuffix(f.name, "_test.go") { 168 return 169 } 170 171 fn, ok := node.(*ast.FuncDecl) 172 if !ok || fn.Recv != nil { 173 // Ignore non-functions or functions with receivers. 174 return 175 } 176 177 report := func(format string, args ...interface{}) { f.Badf(node.Pos(), format, args...) } 178 179 switch { 180 case strings.HasPrefix(fn.Name.Name, "Example"): 181 checkExample(fn, f, report) 182 case strings.HasPrefix(fn.Name.Name, "Test"): 183 checkTest(fn, "Test", report) 184 case strings.HasPrefix(fn.Name.Name, "Benchmark"): 185 checkTest(fn, "Benchmark", report) 186 } 187 }