Skip to content

goleak finds goroutine leak in not enabled test case #83

@szuecs

Description

@szuecs

If you have a module with a leak in func leak():

package foo

import (
	"sync"
	"time"
)

type O struct {
	once *sync.Once
	quit chan struct{}
}

func NewO() *O {
	o := &O{
		once: &sync.Once{},
		quit: make(chan struct{}),
	}
	return o
}

func (o *O) leak() {
	go func() {
		d := 100 * time.Millisecond
		for {
			select {
			case <-o.quit:
				return
			case <-time.After(2 * d):
				time.Sleep(d)
			}

		}

	}()
}

func (o *O) close() {
	o.once.Do(func() {
		close(o.quit)
	})
}

func (*O) run(d time.Duration) {
	time.Sleep(d)
}

and tests checking for leaks by running defer goleak.VerifyNone(t), that don't run leak(), it will show a leak in TestC, even if the leak is in TestB without defer goleak.VerifyNone(t):

package foo

import (
	"testing"
	"time"

	"go.uber.org/goleak"
)

func TestA(t *testing.T) {
	defer goleak.VerifyNone(t)

	o := NewO()
	defer o.close()
	o.run(time.Second)
}

func TestB(t *testing.T) {
	o := NewO()
	o.leak()
	o.run(time.Second)
}

func TestC(t *testing.T) {
	defer goleak.VerifyNone(t)

	o := NewO()
	defer o.close()
	o.run(time.Second)
}

It depends on order of execution of tests.

Here a test run:

% go test .
--- FAIL: TestC (1.45s)
    foo_test.go:30: found unexpected goroutines:
        [Goroutine 6 in state select, with foo.(*O).leak.func1 on top of the stack:
        goroutine 6 [select]:
        foo.(*O).leak.func1()
                /tmp/go/goleak/foo.go:25 +0x85
        created by foo.(*O).leak
                /tmp/go/goleak/foo.go:22 +0x56
        ]
FAIL
FAIL    foo     3.466s
FAIL

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions