Skip to content

Commit

Permalink
Merge branch 'master' into thomshutt/master
Browse files Browse the repository at this point in the history
  • Loading branch information
abhinav committed Sep 7, 2022
2 parents 6319030 + 5c9bf00 commit 383b130
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 93 deletions.
23 changes: 20 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,48 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [1.2.0]
### Added
- Add Cleanup option that can be used for registering cleanup callbacks. (#78)

### Changed
- Mark VerifyNone as a test helper. (#75)

Thanks to @tallclair for their contribution to this release.

[1.2.0]: https://github.com/uber-go/goleak/compare/v1.1.12...v1.2.0

## [1.1.12]
### Fixed
- Fixed logic for ignoring trace related goroutines on Go versions 1.16 and above.

[1.1.12]: https://github.com/uber-go/goleak/compare/v1.1.11...v1.1.12

## [1.1.11]
### Fixed
- Documentation fix on how to test.
- Update dependency on stretchr/testify to v1.7.0. (#59)
- Update dependency on golang.org/x/tools to address CVE-2020-14040. (#62)

[1.1.11]: https://github.com/uber-go/goleak/compare/v1.1.10...v1.1.11

## [1.1.10]
### Added
- [#49]: Add option to ignore current goroutines, which checks for any additional leaks and allows for incremental adoption of goleak in larger projects.

Thanks to @denis-tingajkin for their contributions to this release.

[#49]: https://github.com/uber-go/goleak/pull/49
[1.1.10]: https://github.com/uber-go/goleak/compare/v1.0.0...v1.1.10

## [1.0.0]
### Changed
- Migrate to Go modules.

### Fixed
- Ignore trace related goroutines that cause false positives with -trace.

[1.0.0]: https://github.com/uber-go/goleak/compare/v0.10.0...v1.0.0

## 0.10.0
- Initial release.

[1.0.0]: https://github.com/uber-go/goleak/compare/v0.10.0...v1.0.0
[#49]: https://github.com/uber-go/goleak/pull/49
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
module go.uber.org/goleak

go 1.17
go 1.18

require (
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.8.0
golang.org/x/lint v0.0.0-20190930215403-16217165b5de
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/tools v0.1.5 // indirect
Expand Down
28 changes: 6 additions & 22 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
Expand All @@ -8,37 +9,20 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
14 changes: 13 additions & 1 deletion leaks.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package goleak

import (
"errors"
"fmt"

"go.uber.org/goleak/internal/stack"
Expand Down Expand Up @@ -55,6 +56,9 @@ func Find(options ...Option) error {
cur := stack.Current().ID()

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 All @@ -79,12 +83,20 @@ type testHelper interface {
//
// defer VerifyNone(t)
func VerifyNone(t TestingT, options ...Option) {
opts := buildOpts(options...)
var cleanup func(int)
cleanup, opts.cleanup = opts.cleanup, nil

if h, ok := t.(testHelper); ok {
// Mark this function as a test helper, if available.
h.Helper()
}

if err := Find(options...); err != nil {
if err := Find(opts); err != nil {
t.Error(err)
}

if cleanup != nil {
cleanup(0)
}
}
55 changes: 38 additions & 17 deletions leaks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,26 @@ func testOptions() Option {
}

func TestFind(t *testing.T) {
require.NoError(t, Find(), "Should find no leaks by default")
t.Run("Should find no leaks by default", func(t *testing.T) {
require.NoError(t, Find())
})

bg := startBlockedG()
err := Find(testOptions())
require.Error(t, err, "Should find leaks with leaked goroutine")
assert.Contains(t, err.Error(), "blockedG")
assert.Contains(t, err.Error(), "created by go.uber.org/goleak.startBlockedG")

// Once we unblock the goroutine, we shouldn't have leaks.
bg.unblock()
require.NoError(t, Find(), "Should find no leaks by default")
t.Run("Find leaks with leaked goroutine", func(t *testing.T) {
bg := startBlockedG()
err := Find(testOptions())
require.Error(t, err, "Should find leaks with leaked goroutine")
assert.ErrorContains(t, err, "blockedG")
assert.ErrorContains(t, err, "created by go.uber.org/goleak.startBlockedG")

// Once we unblock the goroutine, we shouldn't have leaks.
bg.unblock()
require.NoError(t, Find(), "Should find no leaks by default")
})

t.Run("Find can't take in Cleanup option", func(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")
})
}

func TestFindRetry(t *testing.T) {
Expand All @@ -74,14 +83,26 @@ func (ft *fakeT) Error(args ...interface{}) {
}

func TestVerifyNone(t *testing.T) {
ft := &fakeT{}
VerifyNone(ft)
require.Empty(t, ft.errors, "Expect no errors from VerifyNone")
t.Run("VerifyNone finds leaks", func(t *testing.T) {
ft := &fakeT{}
VerifyNone(ft)
require.Empty(t, ft.errors, "Expect no errors from VerifyNone")

bg := startBlockedG()
VerifyNone(ft, testOptions())
require.NotEmpty(t, ft.errors, "Expect errors from VerifyNone on leaked goroutine")
bg.unblock()
})

bg := startBlockedG()
VerifyNone(ft, testOptions())
require.NotEmpty(t, ft.errors, "Expect errors from VerifyNone on leaked goroutine")
bg.unblock()
t.Run("cleanup registered callback should be called", func(t *testing.T) {
ft := &fakeT{}
cleanupCalled := false
VerifyNone(ft, Cleanup(func(c int) {
assert.Equal(t, 0, c)
cleanupCalled = true
}))
require.True(t, cleanupCalled, "expect cleanup registered callback to be called")
})
}

func TestIgnoreCurrent(t *testing.T) {
Expand Down
34 changes: 28 additions & 6 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ type opts struct {
filters []func(stack.Stack) bool
maxRetries int
maxSleep time.Duration
cleanup func(int)
}

// implement apply so that opts struct itself can be used as
// an Option.
func (o *opts) apply(opts *opts) {
opts.filters = o.filters
opts.maxRetries = o.maxRetries
opts.maxSleep = o.maxSleep
opts.cleanup = o.cleanup
}

// optionFunc lets us easily write options without a custom type.
Expand All @@ -57,6 +67,18 @@ func IgnoreTopFunction(f string) Option {
})
}

// 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
// will be set to the exit code of TestMain.
// When passed to [VerifyNone], the exit code will be set to 0.
// This cannot be passed to [Find].
func Cleanup(cleanupFunc func(exitCode int)) Option {
return optionFunc(func(opts *opts) {
opts.cleanup = cleanupFunc
})
}

// IgnoreAnyFunction ignores any goroutines where the specified function
// is anywhere in the stack. The function name should be fully qualified,
// e.g., go.uber.org/goleak.IgnoreTopFunction
Expand Down Expand Up @@ -107,23 +129,23 @@ func buildOpts(options ...Option) *opts {
return opts
}

func (vo *opts) filter(s stack.Stack) bool {
for _, filter := range vo.filters {
func (o *opts) filter(s stack.Stack) bool {
for _, filter := range o.filters {
if filter(s) {
return true
}
}
return false
}

func (vo *opts) retry(i int) bool {
if i >= vo.maxRetries {
func (o *opts) retry(i int) bool {
if i >= o.maxRetries {
return false
}

d := time.Duration(int(time.Microsecond) << uint(i))
if d > vo.maxSleep {
d = vo.maxSleep
if d > o.maxSleep {
d = o.maxSleep
}
time.Sleep(d)
return true
Expand Down
12 changes: 9 additions & 3 deletions testmain.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,19 @@ type TestingM interface {
// for any goroutine leaks and fail the tests if any leaks were found.
func VerifyTestMain(m TestingM, options ...Option) {
exitCode := m.Run()
opts := buildOpts(options...)

var cleanup func(int)
cleanup, opts.cleanup = opts.cleanup, nil
if cleanup == nil {
cleanup = _osExit
}
defer func() { cleanup(exitCode) }()

if exitCode == 0 {
if err := Find(options...); err != nil {
if err := Find(opts); err != nil {
fmt.Fprintf(_osStderr, "goleak: Errors on successful test run: %v\n", err)
exitCode = 1
}
}

_osExit(exitCode)
}
9 changes: 9 additions & 0 deletions testmain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,13 @@ func TestVerifyTestMain(t *testing.T) {
VerifyTestMain(dummyTestMain(0))
assert.Equal(t, 0, <-exitCode, "Expect no errors without leaks")
assert.NotContains(t, <-stderr, "goleak: Errors", "No errors on successful run without leaks")

cleanupCalled := false
cleanupExitcode := 0
VerifyTestMain(dummyTestMain(3), Cleanup(func(ec int) {
cleanupCalled = true
cleanupExitcode = ec
}))
assert.True(t, cleanupCalled)
assert.Equal(t, 3, cleanupExitcode)
}
38 changes: 0 additions & 38 deletions tracestack_old.go

This file was deleted.

0 comments on commit 383b130

Please sign in to comment.