From 7b4998fbaa50c54bbb8516b02e0870175cee73b1 Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Mon, 23 Oct 2023 07:52:10 -0700 Subject: [PATCH] options: Add IgnoreAnyFunction (#113) Adds a new IgnoreAnyFunction option to ignore stacks that have the provided function anywhere in the stack, not just the top. To test this better, the helper blockedG.run function was split into two. Supersedes #80 Depends on #111 --- CHANGELOG.md | 3 +++ options.go | 16 ++++++++++++++++ options_test.go | 25 ++++++++++++++++++++++++- utils_test.go | 4 ++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e589c8d..a2db308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Built-in ignores now match function names more accurately. They will no longer ignore stacks because of file names that look similar to function names. +### Added +- Add an `IgnoreAnyFunction` option to ignore stack traces + that have the provided function anywhere in the stack. ## [1.2.1] ### Changed diff --git a/options.go b/options.go index 31b0c18..ad5e77b 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..bb2fe06 100644 --- a/options_test.go +++ b/options_test.go @@ -61,8 +61,31 @@ 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()) + + // If we ignore startBlockedG, that should not ignore the blockedG goroutine + // because startBlockedG should be the "created by" function in the stack. + opts = buildOpts(IgnoreAnyFunction("go.uber.org/goleak.startBlockedG")) + require.Equal(t, 1, countUnfiltered(), + "startBlockedG should not 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) { 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 }