Skip to content

Commit 6276826

Browse files
committed
feat(safety): added NoGoRoutineLeak assertion
Signed-off-by: Frederic BIDON <[email protected]>
1 parent de7f52f commit 6276826

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

internal/assertions/safety.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package assertions
5+
6+
import (
7+
"context"
8+
9+
"github.com/go-openapi/testify/v2/internal/leak"
10+
)
11+
12+
// NoGoRoutineLeak ensures that no goroutine did leak from inside the tested function.
13+
//
14+
// NOTE: only the go routines spawned from inside the tested function are checked for leaks.
15+
// No filter or configuration is needed to exclude "known go routines".
16+
//
17+
// Resource cleanup should be done inside the tested function, and not using [testing.T.Cleanup],
18+
// as t.Cleanup is called after the leak check.
19+
//
20+
// # Edge cases
21+
//
22+
// - if the tested function panics leaving behind leaked goroutines, these are detected.
23+
// - if the tested function calls runtime.Goexit (e.g. from [testing.T.FailNow]) leaving behind leaked goroutines,
24+
// these are detected.
25+
// - if a panic occurs in one of the leaked go routines, it cannot be recovered with certainty and
26+
// the calling program will usually panic.
27+
//
28+
// # Concurrency
29+
//
30+
// [NoGoRoutineLeak] may be used safely in parallel tests.
31+
//
32+
// # Usage
33+
//
34+
// NoGoRoutineLeak(t, func() {
35+
// ...
36+
// },
37+
// "should not leak any go routine",
38+
// )
39+
//
40+
// # Examples
41+
//
42+
// success: func() {}
43+
func NoGoRoutineLeak(t T, tested func(), msgAndArgs ...any) bool {
44+
// Domain: safety
45+
if h, ok := t.(H); ok {
46+
h.Helper()
47+
}
48+
49+
var ctx context.Context
50+
c, ok := t.(contextualizer)
51+
if ok {
52+
ctx = c.Context()
53+
}
54+
if ctx == nil {
55+
ctx = context.Background()
56+
}
57+
58+
signature := leak.Leaked(ctx, tested)
59+
if signature == "" {
60+
return true
61+
}
62+
63+
return Fail(t, "found leaked go routines: "+signature, msgAndArgs...)
64+
}

internal/assertions/safety_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package assertions
5+
6+
import (
7+
"sync"
8+
"testing"
9+
)
10+
11+
func TestNoGoRoutineLeak_Success(t *testing.T) {
12+
mockT := new(mockT)
13+
14+
result := NoGoRoutineLeak(mockT, func() {
15+
// Clean function — no goroutines spawned.
16+
})
17+
18+
if !result {
19+
t.Error("expected NoGoRoutineLeak to return true for clean function")
20+
}
21+
if mockT.failed {
22+
t.Error("expected no failure for clean function")
23+
}
24+
}
25+
26+
func TestNoGoRoutineLeak_Failure(t *testing.T) {
27+
blocker := make(chan struct{})
28+
var wg sync.WaitGroup
29+
30+
t.Cleanup(func() {
31+
close(blocker)
32+
wg.Wait()
33+
})
34+
35+
mockT := new(mockT)
36+
37+
wg.Add(1)
38+
result := NoGoRoutineLeak(mockT, func() {
39+
go func() {
40+
defer wg.Done()
41+
<-blocker // leaked: blocks until cleanup
42+
}()
43+
})
44+
45+
if result {
46+
t.Error("expected NoGoRoutineLeak to return false for leaking function")
47+
}
48+
if !mockT.failed {
49+
t.Error("expected failure to be reported for leaking function")
50+
}
51+
}

0 commit comments

Comments
 (0)