1
2
3
4
5 package testinggoroutine
6
7 import (
8 "go/ast"
9
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"
14 "golang.org/x/tools/internal/typeparams"
15 )
16
17 const Doc = `report calls to (*testing.T).Fatal from goroutines started by a test.
18
19 Functions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and
20 Skip{,f,Now} methods of *testing.T, must be called from the test goroutine itself.
21 This checker detects calls to these functions that occur within a goroutine
22 started by the test. For example:
23
24 func TestFoo(t *testing.T) {
25 go func() {
26 t.Fatal("oops") // error: (*T).Fatal called from non-test goroutine
27 }()
28 }
29 `
30
31 var Analyzer = &analysis.Analyzer{
32 Name: "testinggoroutine",
33 Doc: Doc,
34 Requires: []*analysis.Analyzer{inspect.Analyzer},
35 Run: run,
36 }
37
38 var forbidden = map[string]bool{
39 "FailNow": true,
40 "Fatal": true,
41 "Fatalf": true,
42 "Skip": true,
43 "Skipf": true,
44 "SkipNow": true,
45 }
46
47 func run(pass *analysis.Pass) (interface{}, error) {
48 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
49
50 if !analysisutil.Imports(pass.Pkg, "testing") {
51 return nil, nil
52 }
53
54
55 onlyFuncs := []ast.Node{
56 (*ast.FuncDecl)(nil),
57 }
58
59 inspect.Nodes(onlyFuncs, func(node ast.Node, push bool) bool {
60 fnDecl, ok := node.(*ast.FuncDecl)
61 if !ok {
62 return false
63 }
64
65 if !hasBenchmarkOrTestParams(fnDecl) {
66 return false
67 }
68
69
70
71 ast.Inspect(fnDecl, func(n ast.Node) bool {
72 goStmt, ok := n.(*ast.GoStmt)
73 if !ok {
74 return true
75 }
76
77 checkGoStmt(pass, goStmt)
78
79
80
81 return false
82 })
83
84 return false
85 })
86
87 return nil, nil
88 }
89
90 func hasBenchmarkOrTestParams(fnDecl *ast.FuncDecl) bool {
91
92 params := fnDecl.Type.Params.List
93
94 for _, param := range params {
95 if _, ok := typeIsTestingDotTOrB(param.Type); ok {
96 return true
97 }
98 }
99
100 return false
101 }
102
103 func typeIsTestingDotTOrB(expr ast.Expr) (string, bool) {
104 starExpr, ok := expr.(*ast.StarExpr)
105 if !ok {
106 return "", false
107 }
108 selExpr, ok := starExpr.X.(*ast.SelectorExpr)
109 if !ok {
110 return "", false
111 }
112
113 varPkg := selExpr.X.(*ast.Ident)
114 if varPkg.Name != "testing" {
115 return "", false
116 }
117
118 varTypeName := selExpr.Sel.Name
119 ok = varTypeName == "B" || varTypeName == "T"
120 return varTypeName, ok
121 }
122
123
124
125
126
127 func goStmtFun(goStmt *ast.GoStmt) ast.Node {
128 switch fun := goStmt.Call.Fun.(type) {
129 case *ast.IndexExpr, *typeparams.IndexListExpr:
130 x, _, _, _ := typeparams.UnpackIndexExpr(fun)
131 id, _ := x.(*ast.Ident)
132 if id == nil {
133 break
134 }
135 if id.Obj == nil {
136 break
137 }
138 if funDecl, ok := id.Obj.Decl.(ast.Node); ok {
139 return funDecl
140 }
141 case *ast.Ident:
142
143 if fun.Obj == nil {
144 break
145 }
146 if funDecl, ok := fun.Obj.Decl.(ast.Node); ok {
147 return funDecl
148 }
149 case *ast.FuncLit:
150 return goStmt.Call.Fun
151 }
152 return goStmt.Call
153 }
154
155
156
157 func checkGoStmt(pass *analysis.Pass, goStmt *ast.GoStmt) {
158 fn := goStmtFun(goStmt)
159
160 ast.Inspect(fn, func(n ast.Node) bool {
161 selExpr, ok := n.(*ast.SelectorExpr)
162 if !ok {
163 return true
164 }
165
166 _, bad := forbidden[selExpr.Sel.Name]
167 if !bad {
168 return true
169 }
170
171
172 ident, ok := selExpr.X.(*ast.Ident)
173 if !ok {
174 return true
175 }
176 if ident.Obj == nil || ident.Obj.Decl == nil {
177 return true
178 }
179 field, ok := ident.Obj.Decl.(*ast.Field)
180 if !ok {
181 return true
182 }
183 if typeName, ok := typeIsTestingDotTOrB(field.Type); ok {
184 var fnRange analysis.Range = goStmt
185 if _, ok := fn.(*ast.FuncLit); ok {
186 fnRange = selExpr
187 }
188 pass.ReportRangef(fnRange, "call to (*%s).%s from a non-test goroutine", typeName, selExpr.Sel)
189 }
190 return true
191 })
192 }
193
View as plain text