1 // Copyright 2020 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.
5 package testinggoroutine
10 "golang.org/x/tools/go/analysis"
11 "golang.org/x/tools/go/analysis/passes/inspect"
12 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
13 "golang.org/x/tools/go/ast/inspector"
16 const Doc = `report calls to (*testing.T).Fatal from goroutines started by a test.
18 Functions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and
19 Skip{,f,Now} methods of *testing.T, must be called from the test goroutine itself.
20 This checker detects calls to these functions that occur within a goroutine
21 started by the test. For example:
23 func TestFoo(t *testing.T) {
25 t.Fatal("oops") // error: (*T).Fatal called from non-test goroutine
30 var Analyzer = &analysis.Analyzer{
31 Name: "testinggoroutine",
33 Requires: []*analysis.Analyzer{inspect.Analyzer},
37 var forbidden = map[string]bool{
46 func run(pass *analysis.Pass) (interface{}, error) {
47 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
49 if !analysisutil.Imports(pass.Pkg, "testing") {
53 // Filter out anything that isn't a function declaration.
54 onlyFuncs := []ast.Node{
58 inspect.Nodes(onlyFuncs, func(node ast.Node, push bool) bool {
59 fnDecl, ok := node.(*ast.FuncDecl)
64 if !hasBenchmarkOrTestParams(fnDecl) {
68 // Now traverse the benchmark/test's body and check that none of the
69 // forbidden methods are invoked in the goroutines within the body.
70 ast.Inspect(fnDecl, func(n ast.Node) bool {
71 goStmt, ok := n.(*ast.GoStmt)
76 checkGoStmt(pass, goStmt)
78 // No need to further traverse the GoStmt since right
79 // above we manually traversed it in the ast.Inspect(goStmt, ...)
89 func hasBenchmarkOrTestParams(fnDecl *ast.FuncDecl) bool {
90 // Check that the function's arguments include "*testing.T" or "*testing.B".
91 params := fnDecl.Type.Params.List
93 for _, param := range params {
94 if _, ok := typeIsTestingDotTOrB(param.Type); ok {
102 func typeIsTestingDotTOrB(expr ast.Expr) (string, bool) {
103 starExpr, ok := expr.(*ast.StarExpr)
107 selExpr, ok := starExpr.X.(*ast.SelectorExpr)
112 varPkg := selExpr.X.(*ast.Ident)
113 if varPkg.Name != "testing" {
117 varTypeName := selExpr.Sel.Name
118 ok = varTypeName == "B" || varTypeName == "T"
119 return varTypeName, ok
122 // checkGoStmt traverses the goroutine and checks for the
123 // use of the forbidden *testing.(B, T) methods.
124 func checkGoStmt(pass *analysis.Pass, goStmt *ast.GoStmt) {
125 // Otherwise examine the goroutine to check for the forbidden methods.
126 ast.Inspect(goStmt, func(n ast.Node) bool {
127 selExpr, ok := n.(*ast.SelectorExpr)
132 _, bad := forbidden[selExpr.Sel.Name]
137 // Now filter out false positives by the import-path/type.
138 ident, ok := selExpr.X.(*ast.Ident)
142 if ident.Obj == nil || ident.Obj.Decl == nil {
145 field, ok := ident.Obj.Decl.(*ast.Field)
149 if typeName, ok := typeIsTestingDotTOrB(field.Type); ok {
150 pass.ReportRangef(selExpr, "call to (*%s).%s from a non-test goroutine", typeName, selExpr.Sel)