Home | History | Annotate | Download | only in vet
      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 nonexistent
    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 }
    188