...

Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go

     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 tests defines an Analyzer that checks for common mistaken
     6	// usages of tests and examples.
     7	package tests
     8	
     9	import (
    10		"go/ast"
    11		"go/types"
    12		"strings"
    13		"unicode"
    14		"unicode/utf8"
    15	
    16		"golang.org/x/tools/go/analysis"
    17	)
    18	
    19	const Doc = `check for common mistaken usages of tests and examples
    20	
    21	The tests checker walks Test, Benchmark and Example functions checking
    22	malformed names, wrong signatures and examples documenting non-existent
    23	identifiers.
    24	
    25	Please see the documentation for package testing in golang.org/pkg/testing
    26	for the conventions that are enforced for Tests, Benchmarks, and Examples.`
    27	
    28	var Analyzer = &analysis.Analyzer{
    29		Name: "tests",
    30		Doc:  Doc,
    31		Run:  run,
    32	}
    33	
    34	func run(pass *analysis.Pass) (interface{}, error) {
    35		for _, f := range pass.Files {
    36			if !strings.HasSuffix(pass.Fset.File(f.Pos()).Name(), "_test.go") {
    37				continue
    38			}
    39			for _, decl := range f.Decls {
    40				fn, ok := decl.(*ast.FuncDecl)
    41				if !ok || fn.Recv != nil {
    42					// Ignore non-functions or functions with receivers.
    43					continue
    44				}
    45	
    46				switch {
    47				case strings.HasPrefix(fn.Name.Name, "Example"):
    48					checkExample(pass, fn)
    49				case strings.HasPrefix(fn.Name.Name, "Test"):
    50					checkTest(pass, fn, "Test")
    51				case strings.HasPrefix(fn.Name.Name, "Benchmark"):
    52					checkTest(pass, fn, "Benchmark")
    53				}
    54			}
    55		}
    56		return nil, nil
    57	}
    58	
    59	func isExampleSuffix(s string) bool {
    60		r, size := utf8.DecodeRuneInString(s)
    61		return size > 0 && unicode.IsLower(r)
    62	}
    63	
    64	func isTestSuffix(name string) bool {
    65		if len(name) == 0 {
    66			// "Test" is ok.
    67			return true
    68		}
    69		r, _ := utf8.DecodeRuneInString(name)
    70		return !unicode.IsLower(r)
    71	}
    72	
    73	func isTestParam(typ ast.Expr, wantType string) bool {
    74		ptr, ok := typ.(*ast.StarExpr)
    75		if !ok {
    76			// Not a pointer.
    77			return false
    78		}
    79		// No easy way of making sure it's a *testing.T or *testing.B:
    80		// ensure the name of the type matches.
    81		if name, ok := ptr.X.(*ast.Ident); ok {
    82			return name.Name == wantType
    83		}
    84		if sel, ok := ptr.X.(*ast.SelectorExpr); ok {
    85			return sel.Sel.Name == wantType
    86		}
    87		return false
    88	}
    89	
    90	func lookup(pkg *types.Package, name string) []types.Object {
    91		if o := pkg.Scope().Lookup(name); o != nil {
    92			return []types.Object{o}
    93		}
    94	
    95		var ret []types.Object
    96		// Search through the imports to see if any of them define name.
    97		// It's hard to tell in general which package is being tested, so
    98		// for the purposes of the analysis, allow the object to appear
    99		// in any of the imports. This guarantees there are no false positives
   100		// because the example needs to use the object so it must be defined
   101		// in the package or one if its imports. On the other hand, false
   102		// negatives are possible, but should be rare.
   103		for _, imp := range pkg.Imports() {
   104			if obj := imp.Scope().Lookup(name); obj != nil {
   105				ret = append(ret, obj)
   106			}
   107		}
   108		return ret
   109	}
   110	
   111	func checkExample(pass *analysis.Pass, fn *ast.FuncDecl) {
   112		fnName := fn.Name.Name
   113		if params := fn.Type.Params; len(params.List) != 0 {
   114			pass.Reportf(fn.Pos(), "%s should be niladic", fnName)
   115		}
   116		if results := fn.Type.Results; results != nil && len(results.List) != 0 {
   117			pass.Reportf(fn.Pos(), "%s should return nothing", fnName)
   118		}
   119	
   120		if fnName == "Example" {
   121			// Nothing more to do.
   122			return
   123		}
   124	
   125		var (
   126			exName = strings.TrimPrefix(fnName, "Example")
   127			elems  = strings.SplitN(exName, "_", 3)
   128			ident  = elems[0]
   129			objs   = lookup(pass.Pkg, ident)
   130		)
   131		if ident != "" && len(objs) == 0 {
   132			// Check ExampleFoo and ExampleBadFoo.
   133			pass.Reportf(fn.Pos(), "%s refers to unknown identifier: %s", fnName, ident)
   134			// Abort since obj is absent and no subsequent checks can be performed.
   135			return
   136		}
   137		if len(elems) < 2 {
   138			// Nothing more to do.
   139			return
   140		}
   141	
   142		if ident == "" {
   143			// Check Example_suffix and Example_BadSuffix.
   144			if residual := strings.TrimPrefix(exName, "_"); !isExampleSuffix(residual) {
   145				pass.Reportf(fn.Pos(), "%s has malformed example suffix: %s", fnName, residual)
   146			}
   147			return
   148		}
   149	
   150		mmbr := elems[1]
   151		if !isExampleSuffix(mmbr) {
   152			// Check ExampleFoo_Method and ExampleFoo_BadMethod.
   153			found := false
   154			// Check if Foo.Method exists in this package or its imports.
   155			for _, obj := range objs {
   156				if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj != nil {
   157					found = true
   158					break
   159				}
   160			}
   161			if !found {
   162				pass.Reportf(fn.Pos(), "%s refers to unknown field or method: %s.%s", fnName, ident, mmbr)
   163			}
   164		}
   165		if len(elems) == 3 && !isExampleSuffix(elems[2]) {
   166			// Check ExampleFoo_Method_suffix and ExampleFoo_Method_Badsuffix.
   167			pass.Reportf(fn.Pos(), "%s has malformed example suffix: %s", fnName, elems[2])
   168		}
   169	}
   170	
   171	func checkTest(pass *analysis.Pass, fn *ast.FuncDecl, prefix string) {
   172		// Want functions with 0 results and 1 parameter.
   173		if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
   174			fn.Type.Params == nil ||
   175			len(fn.Type.Params.List) != 1 ||
   176			len(fn.Type.Params.List[0].Names) > 1 {
   177			return
   178		}
   179	
   180		// The param must look like a *testing.T or *testing.B.
   181		if !isTestParam(fn.Type.Params.List[0].Type, prefix[:1]) {
   182			return
   183		}
   184	
   185		if !isTestSuffix(fn.Name.Name[len(prefix):]) {
   186			pass.Reportf(fn.Pos(), "%s has malformed name: first letter after '%s' must not be lowercase", fn.Name.Name, prefix)
   187		}
   188	}
   189	

View as plain text