summaryrefslogtreecommitdiffhomepage
path: root/device/device_test.go
diff options
context:
space:
mode:
authorJosh Bleecher Snyder <josh@tailscale.com>2021-02-02 10:41:20 -0800
committerJason A. Donenfeld <Jason@zx2c4.com>2021-02-03 00:57:57 +0100
commitfd63a233c9fedad2be4e26edbc540e11d9ea5968 (patch)
tree277e0fdd5624b0b191d118fac824dedcd5bb70c0 /device/device_test.go
parent8a374a35a0fe62dfd86df2c16166d2bb84115b93 (diff)
device: test that we do not leak goroutines
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
Diffstat (limited to 'device/device_test.go')
-rw-r--r--device/device_test.go31
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)
+ }
+ })
+}