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  }