diff options
author | Josh Bleecher Snyder <josh@tailscale.com> | 2021-02-02 10:41:20 -0800 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2021-02-03 00:57:57 +0100 |
commit | fd63a233c9fedad2be4e26edbc540e11d9ea5968 (patch) | |
tree | 277e0fdd5624b0b191d118fac824dedcd5bb70c0 | |
parent | 8a374a35a0fe62dfd86df2c16166d2bb84115b93 (diff) |
device: test that we do not leak goroutines
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
-rw-r--r-- | device/device_test.go | 31 |
1 files changed, 31 insertions, 0 deletions
diff --git a/device/device_test.go b/device/device_test.go index d1af225..9290ff0 100644 --- a/device/device_test.go +++ b/device/device_test.go @@ -12,6 +12,8 @@ import ( "io" "io/ioutil" "net" + "runtime" + "runtime/pprof" "sync" "sync/atomic" "testing" @@ -163,6 +165,7 @@ NextAttempt: // If there's something permanent wrong, // we'll see that when we run out of attempts. tb.Logf("failed to configure device %d: %v", i, err) + p.dev.Close() continue NextAttempt } // The device might still not be up, e.g. due to an error @@ -170,6 +173,7 @@ NextAttempt: // Assume it's due to a transient error (port in use), and retry. if !p.dev.isUp.Get() { tb.Logf("device %d did not come up, trying again", i) + p.dev.Close() continue NextAttempt } // The device is up. Close it when the test completes. @@ -183,6 +187,7 @@ NextAttempt: } func TestTwoDevicePing(t *testing.T) { + goroutineLeakCheck(t) pair := genTestPair(t) t.Run("ping 1.0.0.1", func(t *testing.T) { pair.Send(t, Ping, nil) @@ -352,3 +357,29 @@ func BenchmarkUAPIGet(b *testing.B) { pair[0].dev.IpcGetOperation(ioutil.Discard) } } + +func goroutineLeakCheck(t *testing.T) { + goroutines := func() (int, []byte) { + p := pprof.Lookup("goroutine") + b := new(bytes.Buffer) + p.WriteTo(b, 1) + return p.Count(), b.Bytes() + } + + startGoroutines, startStacks := goroutines() + t.Cleanup(func() { + if t.Failed() { + return + } + // Give goroutines time to exit, if they need it. + for i := 0; i < 1000 && startGoroutines < runtime.NumGoroutine(); i++ { + time.Sleep(10 * time.Millisecond) + } + if got := runtime.NumGoroutine(); startGoroutines < got { + _, endStacks := goroutines() + t.Logf("starting stacks:\n%s\n", startStacks) + t.Logf("ending stacks:\n%s\n", endStacks) + t.Fatalf("expected %d goroutines, got %d, leak?", startGoroutines, got) + } + }) +} |