mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-27 02:37:38 +00:00
tstest: simplify goroutine leak tests
Use tb.Cleanup to simplify both the API and the implementation. One behavior change: When the number of goroutines shrinks, don't log. I've never found these logs to be useful, and they frequently add noise. Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
This commit is contained in:
parent
9a70789853
commit
e8cd7bb66f
@ -16,9 +16,7 @@ import (
|
|||||||
|
|
||||||
func TestReadWrite(t *testing.T) {
|
func TestReadWrite(t *testing.T) {
|
||||||
tstest.PanicOnLog()
|
tstest.PanicOnLog()
|
||||||
|
tstest.ResourceCheck(t)
|
||||||
rc := tstest.NewResourceCheck()
|
|
||||||
defer rc.Assert(t)
|
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
err := WriteMsg(&buf, []byte("Test string1"))
|
err := WriteMsg(&buf, []byte("Test string1"))
|
||||||
@ -64,9 +62,7 @@ func TestReadWrite(t *testing.T) {
|
|||||||
|
|
||||||
func TestClientServer(t *testing.T) {
|
func TestClientServer(t *testing.T) {
|
||||||
tstest.PanicOnLog()
|
tstest.PanicOnLog()
|
||||||
|
tstest.ResourceCheck(t)
|
||||||
rc := tstest.NewResourceCheck()
|
|
||||||
defer rc.Assert(t)
|
|
||||||
|
|
||||||
b := &FakeBackend{}
|
b := &FakeBackend{}
|
||||||
var bs *BackendServer
|
var bs *BackendServer
|
||||||
|
@ -12,8 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGetList(t *testing.T) {
|
func TestGetList(t *testing.T) {
|
||||||
rc := tstest.NewResourceCheck()
|
tstest.ResourceCheck(t)
|
||||||
defer rc.Assert(t)
|
|
||||||
|
|
||||||
pl, err := GetList(nil)
|
pl, err := GetList(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -26,8 +25,7 @@ func TestGetList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIgnoreLocallyBoundPorts(t *testing.T) {
|
func TestIgnoreLocallyBoundPorts(t *testing.T) {
|
||||||
rc := tstest.NewResourceCheck()
|
tstest.ResourceCheck(t)
|
||||||
defer rc.Assert(t)
|
|
||||||
|
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -14,64 +14,30 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResourceCheck struct {
|
func ResourceCheck(tb testing.TB) {
|
||||||
startNumRoutines int
|
startN, startStacks := goroutines()
|
||||||
startDump string
|
tb.Cleanup(func() {
|
||||||
}
|
if tb.Failed() {
|
||||||
|
// Something else went wrong.
|
||||||
func NewResourceCheck() *ResourceCheck {
|
return
|
||||||
// NOTE(apenwarr): I'd rather not pre-generate a goroutine dump here.
|
}
|
||||||
// However, it turns out to be tricky to debug when eg. the initial
|
tb.Helper()
|
||||||
// goroutine count > the ending goroutine count, because of course
|
// Goroutines might be still exiting.
|
||||||
// the missing ones are not in the final dump. Also, we have to
|
|
||||||
// render the profile as a string right away, because the
|
|
||||||
// pprof.Profile object doesn't stay stable over time. Every time
|
|
||||||
// you render the string, you might get a different answer.
|
|
||||||
r := &ResourceCheck{}
|
|
||||||
r.startNumRoutines, r.startDump = goroutineDump()
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func goroutineDump() (int, string) {
|
|
||||||
p := pprof.Lookup("goroutine")
|
|
||||||
b := &bytes.Buffer{}
|
|
||||||
p.WriteTo(b, 1)
|
|
||||||
return p.Count(), b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResourceCheck) Assert(t testing.TB) {
|
|
||||||
if t.Failed() {
|
|
||||||
// Something else went wrong.
|
|
||||||
// Assume that that is responsible for the leak
|
|
||||||
// and don't pile on a bunch of extra of output.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Helper()
|
|
||||||
want := r.startNumRoutines
|
|
||||||
|
|
||||||
// Some goroutines might be still exiting, so give them a chance
|
|
||||||
got := runtime.NumGoroutine()
|
|
||||||
if want != got {
|
|
||||||
_, dump := goroutineDump()
|
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
got = runtime.NumGoroutine()
|
if runtime.NumGoroutine() <= startN {
|
||||||
if want == got {
|
return
|
||||||
break
|
|
||||||
}
|
}
|
||||||
time.Sleep(1 * time.Millisecond)
|
time.Sleep(1 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
endN, endStacks := goroutines()
|
||||||
// If the count is *still* wrong, that's a failure.
|
tb.Logf("goroutine diff:\n%v\n", cmp.Diff(startStacks, endStacks))
|
||||||
if want != got {
|
tb.Fatalf("goroutine count: expected %d, got %d\n", startN, endN)
|
||||||
t.Logf("goroutine diff:\n%v\n", cmp.Diff(r.startDump, dump))
|
})
|
||||||
t.Logf("goroutine count: expected %d, got %d\n", want, got)
|
}
|
||||||
// Don't fail if there are *fewer* goroutines than
|
|
||||||
// expected. That just might be some leftover ones
|
func goroutines() (int, []byte) {
|
||||||
// from the previous test, which are pretty hard to
|
p := pprof.Lookup("goroutine")
|
||||||
// eliminate.
|
b := new(bytes.Buffer)
|
||||||
if want < got {
|
p.WriteTo(b, 1)
|
||||||
t.Fatalf("ResourceCheck: goroutine count: expected %d, got %d\n", want, got)
|
return p.Count(), b.Bytes()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -331,8 +331,7 @@ func meshStacks(logf logger.Logf, ms []*magicStack) (cleanup func()) {
|
|||||||
|
|
||||||
func TestNewConn(t *testing.T) {
|
func TestNewConn(t *testing.T) {
|
||||||
tstest.PanicOnLog()
|
tstest.PanicOnLog()
|
||||||
rc := tstest.NewResourceCheck()
|
tstest.ResourceCheck(t)
|
||||||
defer rc.Assert(t)
|
|
||||||
|
|
||||||
epCh := make(chan string, 16)
|
epCh := make(chan string, 16)
|
||||||
epFunc := func(endpoints []string) {
|
epFunc := func(endpoints []string) {
|
||||||
@ -398,8 +397,7 @@ func pickPort(t testing.TB) uint16 {
|
|||||||
|
|
||||||
func TestDerpIPConstant(t *testing.T) {
|
func TestDerpIPConstant(t *testing.T) {
|
||||||
tstest.PanicOnLog()
|
tstest.PanicOnLog()
|
||||||
rc := tstest.NewResourceCheck()
|
tstest.ResourceCheck(t)
|
||||||
defer rc.Assert(t)
|
|
||||||
|
|
||||||
if DerpMagicIP != derpMagicIP.String() {
|
if DerpMagicIP != derpMagicIP.String() {
|
||||||
t.Errorf("str %q != IP %v", DerpMagicIP, derpMagicIP)
|
t.Errorf("str %q != IP %v", DerpMagicIP, derpMagicIP)
|
||||||
@ -411,8 +409,7 @@ func TestDerpIPConstant(t *testing.T) {
|
|||||||
|
|
||||||
func TestPickDERPFallback(t *testing.T) {
|
func TestPickDERPFallback(t *testing.T) {
|
||||||
tstest.PanicOnLog()
|
tstest.PanicOnLog()
|
||||||
rc := tstest.NewResourceCheck()
|
tstest.ResourceCheck(t)
|
||||||
defer rc.Assert(t)
|
|
||||||
|
|
||||||
c := newConn()
|
c := newConn()
|
||||||
c.derpMap = derpmap.Prod()
|
c.derpMap = derpmap.Prod()
|
||||||
@ -519,8 +516,7 @@ func parseCIDR(t *testing.T, addr string) netaddr.IPPrefix {
|
|||||||
// -count=10000 to be sure.
|
// -count=10000 to be sure.
|
||||||
func TestDeviceStartStop(t *testing.T) {
|
func TestDeviceStartStop(t *testing.T) {
|
||||||
tstest.PanicOnLog()
|
tstest.PanicOnLog()
|
||||||
rc := tstest.NewResourceCheck()
|
tstest.ResourceCheck(t)
|
||||||
defer rc.Assert(t)
|
|
||||||
|
|
||||||
conn, err := NewConn(Options{
|
conn, err := NewConn(Options{
|
||||||
EndpointsFunc: func(eps []string) {},
|
EndpointsFunc: func(eps []string) {},
|
||||||
@ -838,8 +834,7 @@ func newPinger(t *testing.T, logf logger.Logf, src, dst *magicStack) (cleanup fu
|
|||||||
// get exercised.
|
// get exercised.
|
||||||
func testActiveDiscovery(t *testing.T, d *devices) {
|
func testActiveDiscovery(t *testing.T, d *devices) {
|
||||||
tstest.PanicOnLog()
|
tstest.PanicOnLog()
|
||||||
rc := tstest.NewResourceCheck()
|
tstest.ResourceCheck(t)
|
||||||
defer rc.Assert(t)
|
|
||||||
|
|
||||||
tlogf, setT := makeNestable(t)
|
tlogf, setT := makeNestable(t)
|
||||||
setT(t)
|
setT(t)
|
||||||
@ -900,8 +895,7 @@ func testActiveDiscovery(t *testing.T, d *devices) {
|
|||||||
|
|
||||||
func testTwoDevicePing(t *testing.T, d *devices) {
|
func testTwoDevicePing(t *testing.T, d *devices) {
|
||||||
tstest.PanicOnLog()
|
tstest.PanicOnLog()
|
||||||
rc := tstest.NewResourceCheck()
|
tstest.ResourceCheck(t)
|
||||||
defer rc.Assert(t)
|
|
||||||
|
|
||||||
// This gets reassigned inside every test, so that the connections
|
// This gets reassigned inside every test, so that the connections
|
||||||
// all log using the "current" t.Logf function. Sigh.
|
// all log using the "current" t.Logf function. Sigh.
|
||||||
@ -1145,8 +1139,7 @@ func testTwoDevicePing(t *testing.T, d *devices) {
|
|||||||
// TestAddrSet tests addrSet appendDests and updateDst.
|
// TestAddrSet tests addrSet appendDests and updateDst.
|
||||||
func TestAddrSet(t *testing.T) {
|
func TestAddrSet(t *testing.T) {
|
||||||
tstest.PanicOnLog()
|
tstest.PanicOnLog()
|
||||||
rc := tstest.NewResourceCheck()
|
tstest.ResourceCheck(t)
|
||||||
defer rc.Assert(t)
|
|
||||||
|
|
||||||
mustIPPortPtr := func(s string) *netaddr.IPPort {
|
mustIPPortPtr := func(s string) *netaddr.IPPort {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
@ -275,8 +275,7 @@ func TestResolveReverse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDelegate(t *testing.T) {
|
func TestDelegate(t *testing.T) {
|
||||||
rc := tstest.NewResourceCheck()
|
tstest.ResourceCheck(t)
|
||||||
defer rc.Assert(t)
|
|
||||||
|
|
||||||
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
|
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
|
||||||
dnsHandleFunc("nxdomain.site.", resolveToNXDOMAIN)
|
dnsHandleFunc("nxdomain.site.", resolveToNXDOMAIN)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user