ResourceCheck takes a snapshot of the current goroutines and registers a cleanup on tb to verify that after the rest, all goroutines created by the test go away. (well, at least that the count matches. Maybe in the future it can look at specific routines). It panics if called from a parallel test.
(tb testing.TB)
| 20 | // |
| 21 | // It panics if called from a parallel test. |
| 22 | func ResourceCheck(tb testing.TB) { |
| 23 | tb.Helper() |
| 24 | |
| 25 | // Set an environment variable (anything at all) just for the |
| 26 | // side effect of tb.Setenv panicking if we're in a parallel test. |
| 27 | tb.Setenv("TS_CHECKING_RESOURCES", "1") |
| 28 | |
| 29 | startN, startStacks := goroutines() |
| 30 | tb.Cleanup(func() { |
| 31 | if tb.Failed() { |
| 32 | // Test has failed - but this doesn't catch panics due to |
| 33 | // https://github.com/golang/go/issues/49929. |
| 34 | return |
| 35 | } |
| 36 | // Goroutines might be still exiting. |
| 37 | for range 300 { |
| 38 | if runtime.NumGoroutine() <= startN { |
| 39 | return |
| 40 | } |
| 41 | time.Sleep(10 * time.Millisecond) |
| 42 | } |
| 43 | endN, endStacks := goroutines() |
| 44 | if endN <= startN { |
| 45 | return |
| 46 | } |
| 47 | |
| 48 | // Parse and print goroutines. |
| 49 | start := parseGoroutines(startStacks) |
| 50 | end := parseGoroutines(endStacks) |
| 51 | if testing.Verbose() { |
| 52 | tb.Logf("goroutines start:\n%s", printGoroutines(start)) |
| 53 | tb.Logf("goroutines end:\n%s", printGoroutines(end)) |
| 54 | } |
| 55 | |
| 56 | // Print goroutine diff, omitting tstest.ResourceCheck goroutines. |
| 57 | self := func(g goroutine) bool { return bytes.Contains(g.stack, []byte("\ttailscale.com/tstest.goroutines+")) } |
| 58 | start.goroutines = slices.DeleteFunc(start.goroutines, self) |
| 59 | end.goroutines = slices.DeleteFunc(end.goroutines, self) |
| 60 | tb.Logf("goroutine diff (-start +end):\n%s", diffGoroutines(start, end)) |
| 61 | |
| 62 | // tb.Failed() above won't report on panics, so we shouldn't call Fatal |
| 63 | // here or we risk suppressing reporting of the panic. |
| 64 | tb.Errorf("goroutine count: expected %d, got %d\n", startN, endN) |
| 65 | }) |
| 66 | } |
| 67 | |
| 68 | func goroutines() (int, []byte) { |
| 69 | p := pprof.Lookup("goroutine") |
searching dependent graphs…