Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make maxSleep and maxRetires configurable when building options #94

Merged
merged 3 commits into from Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions leaks.go
Expand Up @@ -56,6 +56,9 @@ func Find(options ...Option) error {
cur := stack.Current().ID()

opts := buildOpts(options...)
if err := opts.validate(); err != nil {
return err
}
if opts.cleanup != nil {
return errors.New("Cleanup can only be passed to VerifyNone or VerifyTestMain")
}
Expand Down
12 changes: 11 additions & 1 deletion leaks_test.go
Expand Up @@ -36,7 +36,7 @@ var _ = TestingT(testing.TB(nil))
// testOptions passes a shorter max sleep time, used so tests don't wait
// ~1 second in cases where we expect Find to error out.
func testOptions() Option {
return maxSleep(time.Millisecond)
return MaxSleep(time.Millisecond)
}

func TestFind(t *testing.T) {
Expand All @@ -60,6 +60,16 @@ func TestFind(t *testing.T) {
err := Find(Cleanup(func(int) { assert.Fail(t, "this should not be called") }))
require.Error(t, err, "Should exit with invalid option")
})

t.Run("Find should return error when maxRetries is less than 0", func(t *testing.T) {
err := Find(MaxRetries(-1))
require.Error(t, err, "maxRetries should be greater than 0")
})

t.Run("Find should return error when maxSleep is less than 0s", func(t *testing.T) {
err := Find(MaxSleep(time.Duration(-1)))
require.Error(t, err, "maxSleep should be greater than 0s")
})
}

func TestFindRetry(t *testing.T) {
Expand Down
49 changes: 40 additions & 9 deletions options.go
Expand Up @@ -21,6 +21,7 @@
package goleak

import (
"errors"
"strings"
"time"

Expand All @@ -32,10 +33,14 @@ type Option interface {
apply(*opts)
}

// We retry up to 20 times if we can't find the goroutine that
// we are looking for. In between each attempt, we will sleep for
// a short while to let any running goroutines complete.
const _defaultRetries = 20
const (
// We retry up to default 20 times if we can't find the goroutine that
// we are looking for.
_defaultRetries = 20
// In between each retry attempt, sleep for up to default 100 microseconds
// to let any running goroutine completes.
_defaultSleepTime = 100 * time.Microsecond
)

type opts struct {
filters []func(stack.Stack) bool
Expand All @@ -53,6 +58,27 @@ func (o *opts) apply(opts *opts) {
opts.cleanup = o.cleanup
}

// set the defaults when not configured.
func (o *opts) defaults() {
if o.maxRetries == 0 {
o.maxRetries = _defaultRetries
}
if o.maxSleep == time.Duration(0) {
o.maxSleep = _defaultSleepTime
}
}

// validate the options.
func (o *opts) validate() error {
if o.maxRetries < 0 {
return errors.New("maxRetries should be greater than 0")
}
if o.maxSleep < time.Duration(0) {
sywhang marked this conversation as resolved.
Show resolved Hide resolved
return errors.New("maxSleep should be greater than 0s")
}
return nil
}

// optionFunc lets us easily write options without a custom type.
type optionFunc func(*opts)

Expand Down Expand Up @@ -91,23 +117,26 @@ func IgnoreCurrent() Option {
})
}

kerthcet marked this conversation as resolved.
Show resolved Hide resolved
func maxSleep(d time.Duration) Option {
func MaxSleep(d time.Duration) Option {
return optionFunc(func(opts *opts) {
opts.maxSleep = d
})
}

func MaxRetries(num int) Option {
kerthcet marked this conversation as resolved.
Show resolved Hide resolved
return optionFunc(func(opts *opts) {
opts.maxRetries = num
})
}

func addFilter(f func(stack.Stack) bool) Option {
return optionFunc(func(opts *opts) {
opts.filters = append(opts.filters, f)
})
}

func buildOpts(options ...Option) *opts {
opts := &opts{
maxRetries: _defaultRetries,
maxSleep: 100 * time.Millisecond,
}
opts := &opts{}
opts.filters = append(opts.filters,
isTestStack,
isSyscallStack,
Expand All @@ -117,6 +146,8 @@ func buildOpts(options ...Option) *opts {
for _, option := range options {
option.apply(opts)
}

opts.defaults()
kerthcet marked this conversation as resolved.
Show resolved Hide resolved
return opts
}

Expand Down
16 changes: 13 additions & 3 deletions options_test.go
Expand Up @@ -65,10 +65,20 @@ func TestOptionsFilters(t *testing.T) {
require.Zero(t, countUnfiltered(), "blockedG should be filtered out. running: %v", stack.All())
}

func TestOptionsRetry(t *testing.T) {
func TestBuildOptions(t *testing.T) {
// With default options.
opts := buildOpts()
opts.maxRetries = 50 // initial attempt + 50 retries = 11
opts.maxSleep = time.Millisecond
assert.Equal(t, _defaultSleepTime, opts.maxSleep, "value of maxSleep not right")
assert.Equal(t, _defaultRetries, opts.maxRetries, "value of maxRetries not right")

// With customized options.
opts = buildOpts(MaxRetries(50), MaxSleep(time.Microsecond))
assert.Equal(t, time.Microsecond, opts.maxSleep, "value of maxSleep not right")
assert.Equal(t, 50, opts.maxRetries, "value of maxRetries not right")
}

func TestOptionsRetry(t *testing.T) {
opts := buildOpts(MaxSleep(time.Millisecond), MaxRetries(50)) // initial attempt + 50 retries = 51

for i := 0; i < 50; i++ {
assert.True(t, opts.retry(i), "Attempt %v/51 should allow retrying", i)
Expand Down