diff --git a/options.go b/options.go index 345eab1..21d237d 100644 --- a/options.go +++ b/options.go @@ -83,6 +83,22 @@ func IgnoreTopFunction(f string) Option { }) } +// IgnoreAnyFunction ignores goroutines where the specified function +// is present anywhere in the stack. +// +// The function name must be fully qualified, e.g., +// +// go.uber.org/goleak.IgnoreAnyFunction +// +// For methods, the fully qualified form looks like: +// +// go.uber.org/goleak.(*MyType).MyMethod +func IgnoreAnyFunction(f string) Option { + return addFilter(func(s stack.Stack) bool { + return s.HasFunction(f) + }) +} + // Cleanup sets up a cleanup function that will be executed at the // end of the leak check. // When passed to [VerifyTestMain], the exit code passed to cleanupFunc diff --git a/options_test.go b/options_test.go index 11438da..16c5f19 100644 --- a/options_test.go +++ b/options_test.go @@ -61,10 +61,27 @@ func TestOptionsFilters(t *testing.T) { require.Equal(t, 1, countUnfiltered(), "Expected blockedG goroutine to not match any filter") // If we add an extra filter to ignore blockTill, it shouldn't match. - opts = buildOpts(IgnoreTopFunction("go.uber.org/goleak.(*blockedG).run")) + opts = buildOpts(IgnoreTopFunction("go.uber.org/goleak.(*blockedG).block")) require.Zero(t, countUnfiltered(), "blockedG should be filtered out. running: %v", stack.All()) } +func TestOptionsIgnoreAnyFunction(t *testing.T) { + cur := stack.Current() + opts := buildOpts(IgnoreAnyFunction("go.uber.org/goleak.(*blockedG).run")) + + for _, s := range stack.All() { + if s.ID() == cur.ID() { + continue + } + + if opts.filter(s) { + continue + } + + t.Errorf("Unexpected goroutine: %v", s) + } +} + func TestBuildOptions(t *testing.T) { // With default options. opts := buildOpts() diff --git a/utils_test.go b/utils_test.go index 7d6b0f1..5504774 100644 --- a/utils_test.go +++ b/utils_test.go @@ -45,6 +45,10 @@ func startBlockedG() *blockedG { func (bg *blockedG) run() { close(bg.started) + bg.block() +} + +func (bg *blockedG) block() { <-bg.wait }