Skip to content

Commit 1733061

Browse files
committed
go/analysis/passes/testinggoroutine: report by enclosing regions
This adds support for t.Run() statements. The new version performs two passes. The first pass collects all of the `go callee` and `t.Run(name, callee)` statements. The second pass inspects each callee FuncDecl or FuncLit for a call to a tb.Forbidden() function. When the enclosing callee function called from an t.Run() statement, it reports when tb is declared outside of the body of the enclosing function. Fixes golang/go#63799 Updates golang/go#63849 Updates golang/go#48124 Change-Id: I10d5f2a0af9b985126dbfcc98eaab97757a7f71d Reviewed-on: https://go-review.googlesource.com/c/tools/+/541155 TryBot-Result: Gopher Robot <gobot@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Findley <rfindley@google.com> Run-TryBot: Tim King <taking@google.com>
1 parent b19be0f commit 1733061

File tree

8 files changed

+503
-102
lines changed

8 files changed

+503
-102
lines changed

go/analysis/passes/testinggoroutine/doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//
88
// # Analyzer testinggoroutine
99
//
10-
// testinggoroutine: report calls to (*testing.T).Fatal from goroutines started by a test.
10+
// testinggoroutine: report calls to (*testing.T).Fatal from goroutines started by a test
1111
//
1212
// Functions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and
1313
// Skip{,f,Now} methods of *testing.T, must be called from the test goroutine itself.

go/analysis/passes/testinggoroutine/testdata/src/a/a.go

Lines changed: 207 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,212 @@ func TestWithCustomType(t *testing.T) {
275275
}
276276
}
277277

278+
func helpTB(tb testing.TB) {
279+
tb.FailNow()
280+
}
281+
282+
func TestTB(t *testing.T) {
283+
go helpTB(t) // want "call to .+TB.+FailNow from a non-test goroutine"
284+
}
285+
278286
func TestIssue48124(t *testing.T) {
279-
go h()
287+
go helper(t) // want "call to .+T.+Skip from a non-test goroutine"
288+
}
289+
290+
func TestEachCall(t *testing.T) {
291+
go helper(t) // want "call to .+T.+Skip from a non-test goroutine"
292+
go helper(t) // want "call to .+T.+Skip from a non-test goroutine"
293+
}
294+
295+
func TestWithSubtest(t *testing.T) {
296+
t.Run("name", func(t2 *testing.T) {
297+
t.FailNow() // want "call to .+T.+FailNow on t defined outside of the subtest"
298+
t2.Fatal()
299+
})
300+
301+
f := func(t3 *testing.T) {
302+
t.FailNow()
303+
t3.Fatal()
304+
}
305+
t.Run("name", f) // want "call to .+T.+FailNow on t defined outside of the subtest"
306+
307+
g := func(t4 *testing.T) {
308+
t.FailNow()
309+
t4.Fatal()
310+
}
311+
g(t)
312+
313+
t.Run("name", helper)
314+
315+
go t.Run("name", func(t2 *testing.T) {
316+
t.FailNow() // want "call to .+T.+FailNow on t defined outside of the subtest"
317+
t2.Fatal()
318+
})
319+
}
320+
321+
func TestMultipleVariables(t *testing.T) {
322+
{ // short decl
323+
f, g := func(t1 *testing.T) {
324+
t1.Fatal()
325+
}, func(t2 *testing.T) {
326+
t2.Error()
327+
}
328+
329+
go f(t) // want "call to .+T.+Fatal from a non-test goroutine"
330+
go g(t)
331+
332+
t.Run("name", f)
333+
t.Run("name", g)
334+
}
335+
336+
{ // var decl
337+
var f, g = func(t1 *testing.T) {
338+
t1.Fatal()
339+
}, func(t2 *testing.T) {
340+
t2.Error()
341+
}
342+
343+
go f(t) // want "call to .+T.+Fatal from a non-test goroutine"
344+
go g(t)
345+
346+
t.Run("name", f)
347+
t.Run("name", g)
348+
}
349+
}
350+
351+
func BadIgnoresMultipleAssignments(t *testing.T) {
352+
{
353+
f := func(t1 *testing.T) {
354+
t1.Fatal()
355+
}
356+
go f(t) // want "call to .+T.+Fatal from a non-test goroutine"
357+
358+
f = func(t2 *testing.T) {
359+
t2.Error()
360+
}
361+
go f(t) // want "call to .+T.+Fatal from a non-test goroutine"
362+
}
363+
{
364+
f := func(t1 *testing.T) {
365+
t1.Error()
366+
}
367+
go f(t)
368+
369+
f = func(t2 *testing.T) {
370+
t2.FailNow()
371+
}
372+
go f(t) // false negative
373+
}
374+
}
375+
376+
func TestGoDoesNotDescendIntoSubtest(t *testing.T) {
377+
f := func(t2 *testing.T) {
378+
g := func(t3 *testing.T) {
379+
t3.Fatal() // fine
380+
}
381+
t2.Run("name", g)
382+
t2.FailNow() // bad
383+
}
384+
go f(t) // want "call to .+T.+FailNow from a non-test goroutine"
385+
}
386+
387+
func TestFreeVariableAssignedWithinEnclosing(t *testing.T) {
388+
f := func(t2 *testing.T) {
389+
inner := t
390+
inner.FailNow()
391+
}
392+
393+
go f(nil) // want "call to .+T.+FailNow from a non-test goroutine"
394+
395+
t.Run("name", func(t3 *testing.T) {
396+
go f(nil) // want "call to .+T.+FailNow from a non-test goroutine"
397+
})
398+
399+
// Without pointer analysis we cannot tell if inner is t or t2.
400+
// So we accept a false negatives on the following examples.
401+
t.Run("name", f)
402+
403+
go func(_ *testing.T) {
404+
t.Run("name", f)
405+
}(nil)
406+
407+
go t.Run("name", f)
408+
}
409+
410+
func TestWithUnusedSelection(t *testing.T) {
411+
go func() {
412+
_ = t.FailNow
413+
}()
414+
t.Run("name", func(t2 *testing.T) {
415+
_ = t.FailNow
416+
})
417+
}
418+
419+
func TestMethodExprsAreIgnored(t *testing.T) {
420+
go func() {
421+
(*testing.T).FailNow(t)
422+
}()
423+
}
424+
425+
func TestRecursive(t *testing.T) {
426+
t.SkipNow()
427+
428+
go TestRecursive(t) // want "call to .+T.+SkipNow from a non-test goroutine"
429+
430+
t.Run("name", TestRecursive)
431+
}
432+
433+
func TestMethodSelection(t *testing.T) {
434+
var h helperType
435+
436+
go h.help(t) // want "call to .+T.+SkipNow from a non-test goroutine"
437+
t.Run("name", h.help)
438+
}
439+
440+
type helperType struct{}
441+
442+
func (h *helperType) help(t *testing.T) { t.SkipNow() }
443+
444+
func TestIssue63799a(t *testing.T) {
445+
done := make(chan struct{})
446+
go func() {
447+
defer close(done)
448+
t.Run("", func(t *testing.T) {
449+
t.Fatal() // No warning. This is in a subtest.
450+
})
451+
}()
452+
<-done
453+
}
454+
455+
func TestIssue63799b(t *testing.T) {
456+
// Simplified from go.dev/cl/538698
457+
458+
// nondet is some unspecified boolean placeholder.
459+
var nondet func() bool
460+
461+
t.Run("nohup", func(t *testing.T) {
462+
if nondet() {
463+
t.Skip("ignored")
464+
}
465+
466+
go t.Run("nohup-i", func(t *testing.T) {
467+
t.Parallel()
468+
if nondet() {
469+
if nondet() {
470+
t.Skip("go.dev/cl/538698 wanted to have skip here")
471+
}
472+
473+
t.Error("ignored")
474+
} else {
475+
t.Log("ignored")
476+
}
477+
})
478+
})
479+
}
480+
481+
func TestIssue63849(t *testing.T) {
482+
go func() {
483+
helper(t) // False negative. We do not do an actual interprodecural reachability analysis.
484+
}()
485+
go helper(t) // want "call to .+T.+Skip from a non-test goroutine"
280486
}

go/analysis/passes/testinggoroutine/testdata/src/a/b.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@
44

55
package a
66

7-
func h() {}
7+
import "testing"
8+
9+
func helper(t *testing.T) {
10+
t.Skip()
11+
}

0 commit comments

Comments
 (0)