github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/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  }