99 "fmt"
1010 "net"
1111 "os"
12- "runtime"
1312 "sync/atomic"
1413 "testing"
1514 "time"
@@ -27,6 +26,7 @@ import (
2726 "github.com/marcopolo/simnet"
2827 "github.com/multiformats/go-multiaddr"
2928 "github.com/stretchr/testify/require"
29+ "go.uber.org/goleak"
3030 "go.uber.org/zap/zapcore"
3131 "golang.zx2c4.com/wireguard/tun"
3232
@@ -39,7 +39,6 @@ import (
3939
4040const TestTUNBatchSize = 100
4141
42- // TODO: add support for goleak in TestSuite
4342type TestSuite struct {
4443 * require.Assertions
4544
@@ -50,10 +49,28 @@ type TestSuite struct {
5049}
5150
5251func NewTestSuite (t testing.TB ) * TestSuite {
53- // TODO: fix
54- if os .Getenv ("CI" ) != "" && runtime .GOOS == "linux" {
55- t .Skip ("doesn't work on linux in CI, flaky ensurePeersAvailableInDHT can't find peers" )
56- }
52+ // Snapshot goroutine state before anything starts. Because t.Cleanup is LIFO,
53+ // registering here means this check runs last — after all peers and bootstrap
54+ // nodes have been closed — so it reliably detects goroutine leaks.
55+ //
56+ // libp2p.NATPortMap() spawns a short-lived UPnP SSDP discovery goroutine
57+ // (koron/go-ssdp.Search) with a 5-second timeout. It exits on its own and
58+ // is not a real leak, so we filter it out.
59+ ignoreCurrent := goleak .IgnoreCurrent ()
60+ t .Cleanup (func () {
61+ goleak .VerifyNone (t , ignoreCurrent ,
62+ // UPnP/NAT discovery: spawned by libp2p.NATPortMap(), self-terminates
63+ // after its timeout (~5s). koron/go-ssdp#6 (closed invalid) rejected adding
64+ // context.Context to Search(), so there is no way to cancel it earlier.
65+ goleak .IgnoreAnyFunction ("github.com/koron/go-ssdp.Search" ),
66+ // go-flow-metrics sweeper: package-level singleton started lazily by
67+ // libp2p bandwidth tracking; intentionally lives for the process lifetime.
68+ // No upstream issue exists for making it stoppable (the closest is
69+ // libp2p/go-flow-metrics#23 which asks for a customizable sweeper but
70+ // does not address stopping it).
71+ goleak .IgnoreAnyFunction ("github.com/libp2p/go-flow-metrics.(*sweeper).runActive" ),
72+ )
73+ })
5774
5875 ts := & TestSuite {t : t , Assertions : require .New (t )}
5976 ts .initBootstrapNode ()
@@ -371,7 +388,11 @@ func (t *testTun) Read(bufs [][]byte, sizes []int, offset int) (n int, err error
371388 return 0 , os .ErrClosed
372389 }
373390 if len (t .t .outboundBuf ) == 0 {
374- t .t .outboundBuf = <- t .t .Outbound
391+ var ok bool
392+ t .t .outboundBuf , ok = <- t .t .Outbound
393+ if ! ok {
394+ return 0 , os .ErrClosed
395+ }
375396 }
376397
377398 for i := range min (len (bufs ), len (t .t .outboundBuf )) {
@@ -414,7 +435,10 @@ func (t *testTun) MTU() (int, error) { return vpn.InterfaceMTU, nil }
414435func (t * testTun ) Name () (string , error ) { return "testTun" , nil }
415436func (t * testTun ) Events () <- chan tun.Event { return t .t .events }
416437func (t * testTun ) Close () error {
417- t .t .isClosed .Store (true )
438+ if t .t .isClosed .Swap (true ) {
439+ return nil
440+ }
441+ close (t .t .Outbound )
418442 close (t .t .events )
419443 return nil
420444}
0 commit comments