Skip to content

Commit

Permalink
Add DefaultIgnoreFunctionSet
Browse files Browse the repository at this point in the history
This exposes a variable `DefaultIgnoreFunctionSet`, which can be used
to set a default list of functions to ignore for goleak.

Note that this mechanism is intended for library or tool authors who
cannot directly call Ignore* Options to set up how their consumers are
running the tests. They should be able to use linkname to set this
variable to some well-known list of goroutines that leak if they really
want to exempt their goroutines from getting continously flagged by
goleak.

Fixes uber-go#119.
  • Loading branch information
sywhang committed Jul 24, 2024
1 parent 898a938 commit c996550
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 0 deletions.
28 changes: 28 additions & 0 deletions leaks.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,23 @@ package goleak
import (
"errors"
"fmt"
"strings"

"go.uber.org/goleak/internal/stack"
)

// DefaultIgnoreFunctionSet can be set to ignore a comma-separated list
// of functions that may leak and should be ignored by goleak.
// The registered set of functions will be then used similar to parameters
// passed to [IgnoreAnyFunction]: any goroutine leaks with the given function
// name(s) will be ignored by goleak.
// This is helpful for library or tool authors that "owns" the leaking
// goroutine and do not have explicit control over how the tests are run
// by their consumers.
// Unless you are in such a situation, you should be using [IgnoreAnyFunction],
// [IgnoreCurrent], or [IgnoreTopFunction] instead.
var DefaultIgnoreFunctionSet string

// TestingT is the minimal subset of testing.TB that we use.
type TestingT interface {
Error(...interface{})
Expand All @@ -50,15 +63,30 @@ func filterStacks(stacks []stack.Stack, skipID int, opts *opts) []stack.Stack {
return filtered
}

func parseDefaultIgnoreFunctions() []Option {
// parse DefaultIgnoreFunctionSet and add it to filters
funcs := strings.Split(DefaultIgnoreFunctionSet, ",")
opts := make([]Option, 0, len(funcs))
for _, f := range funcs {
if f != "" {
opts = append(opts, IgnoreAnyFunction(f))
}
}
return opts
}

// Find looks for extra goroutines, and returns a descriptive error if
// any are found.
func Find(options ...Option) error {
cur := stack.Current().ID()

options = append(options, parseDefaultIgnoreFunctions()...)

opts := buildOpts(options...)
if opts.cleanup != nil {
return errors.New("Cleanup can only be passed to VerifyNone or VerifyTestMain")
}

var stacks []stack.Stack
retry := true
for i := 0; retry; i++ {
Expand Down
47 changes: 47 additions & 0 deletions leaks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,50 @@ func TestVerifyParallel(t *testing.T) {
VerifyNone(t)
})
}

func TestDefaultIgnoreFunctionSet(t *testing.T) {
t.Run("single function", func(t *testing.T) {
done := make(chan struct{})
go func() {
<-done
}()
require.Error(t, Find(), "expected the leaking goroutine to get flagged")

DefaultIgnoreFunctionSet = "go.uber.org/goleak.TestDefaultIgnoreFunctionSet.func1.1"
assert.Equal(t, 1, len(parseDefaultIgnoreFunctions()))
assert.NoError(t, Find(), "expected the goroutine to get ignored after setting DefaultIgnoreFunctionSet")

DefaultIgnoreFunctionSet = ""
assert.Error(t, Find(), "expected the leaking goroutine to get flagged again after resetting DefaultIgnoreFunctionSet")

close(done)
assert.NoError(t, Find())
})

t.Run("many functions", func(t *testing.T) {
bg := startBlockedG()
bg2 := blockedG2()

require.Error(t, Find(), "expected the leaking goroutine to get flagged")

DefaultIgnoreFunctionSet = "go.uber.org/goleak.(*blockedG).block,go.uber.org/goleak.blockedG2.func1"

assert.Equal(t, 2, len(parseDefaultIgnoreFunctions()), "expected 2 filters to be added with DefaultIgnoreFunctionSet")
assert.NoError(t, Find(), "expected the goroutine to get ignored after setting DefaultIgnoreFunctionSet")

DefaultIgnoreFunctionSet = ""
assert.Error(t, Find(), "expected the leaking goroutine to get flagged again after resetting DefaultIgnoreFunctionSet")

bg.unblock()
close(bg2)
assert.NoError(t, Find())
})
}

func blockedG2() chan struct{} {
ch := make(chan struct{})
go func() {
<-ch
}()
return ch
}

0 comments on commit c996550

Please sign in to comment.