From bd68ec158ac35cf7bf9ea3e96e5da63eed88f58f Mon Sep 17 00:00:00 2001 From: Karel Bilek Date: Fri, 17 May 2024 15:25:25 +0200 Subject: [PATCH] Fixing all goroutine leaks with client closings Also adds uber's goroutine leaks checker to almost all tests. The biggest change here is adding context to sidecar pull, which is then cancelled if the container creation fails. This prevents the sidecar goroutine from leaking. The same for making the sidecar channel buffered; the goroutine is not stuck when nobody reads from it. (It is not a memory leak either, GC will remove the channel.) Other changes are just annoying work; putting client.Close() everywhere, db.Close() when Ping() is not successful, client closes in tests/presets, etc. internal/gnomockd/ tests still show goroutine leaks for http transport, but I have no idea what is gnomockd even doing, let alone the tests, so I let that be. --- .golangci.yml | 2 ++ docker.go | 20 ++++++++++++++------ gnomock.go | 5 +++++ gnomock_internal_test.go | 11 +++++++++++ gnomock_test.go | 1 + go.mod | 8 +++----- go.sum | 3 ++- internal/cleaner/cleaner_test.go | 8 ++++++++ internal/errors/errors_test.go | 6 ++++++ internal/health/health_test.go | 6 ++++++ internal/registry/registry_test.go | 5 +++++ preset/azurite/preset_test.go | 5 +++++ preset/cassandra/preset_test.go | 6 ++++++ preset/cockroachdb/preset.go | 5 +++++ preset/cockroachdb/preset_test.go | 7 +++++++ preset/elastic/preset_test.go | 6 ++++++ preset/influxdb/preset_test.go | 7 +++++++ preset/k3s/preset_test.go | 6 ++++++ preset/kafka/preset.go | 3 ++- preset/kafka/preset_test.go | 6 ++++++ preset/localstack/preset_test.go | 6 ++++++ preset/mariadb/preset.go | 4 ++++ preset/mariadb/preset_test.go | 8 ++++++++ preset/memcached/preset.go | 2 ++ preset/memcached/preset_test.go | 10 ++++++++++ preset/mongo/preset.go | 4 ++++ preset/mongo/preset_test.go | 8 ++++++++ preset/mssql/preset.go | 7 +++++-- preset/mssql/preset_test.go | 7 +++++++ preset/mysql/preset.go | 4 ++++ preset/mysql/preset_test.go | 8 ++++++++ preset/postgres/preset.go | 8 ++++++-- preset/postgres/preset_test.go | 8 ++++++++ preset/rabbitmq/preset_test.go | 6 ++++++ preset/redis/preset.go | 5 +++++ preset/redis/preset_test.go | 8 ++++++++ preset/splunk/preset.go | 3 ++- preset/splunk/preset_test.go | 9 ++++++++- preset/vault/preset_test.go | 6 ++++++ preset_test.go | 8 ++++++++ 40 files changed, 236 insertions(+), 19 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 21361b4f..74a9c0fd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -37,6 +37,8 @@ linters: - wsl issues: + include: + - EXC0001 # require error check on Close() exclude-rules: - path: _test\.go linters: diff --git a/docker.go b/docker.go index cb10008a..1a3eae34 100644 --- a/docker.go +++ b/docker.go @@ -137,15 +137,17 @@ func (d *docker) startContainer(ctx context.Context, image string, ports NamedPo return nil, fmt.Errorf("can't prepare container: %w", err) } - sidecarChan := d.setupContainerCleanup(resp.ID, cfg) + sidecarChan, cleanupCancel := d.setupContainerCleanup(resp.ID, cfg) err = d.client.ContainerStart(ctx, resp.ID, container.StartOptions{}) if err != nil { + cleanupCancel() return nil, fmt.Errorf("can't start container %s: %w", resp.ID, err) } container, err := d.waitForContainerNetwork(ctx, resp.ID, ports) if err != nil { + cleanupCancel() return nil, fmt.Errorf("container network isn't ready: %w", err) } @@ -158,8 +160,9 @@ func (d *docker) startContainer(ctx context.Context, image string, ports NamedPo return container, nil } -func (d *docker) setupContainerCleanup(id string, cfg *Options) chan string { - sidecarChan := make(chan string) +func (d *docker) setupContainerCleanup(id string, cfg *Options) (chan string, context.CancelFunc) { + sidecarChan := make(chan string, 1) + bctx, bcancel := context.WithCancel(context.Background()) go func() { defer close(sidecarChan) @@ -174,9 +177,10 @@ func (d *docker) setupContainerCleanup(id string, cfg *Options) chan string { WithHealthCheck(func(ctx context.Context, c *Container) error { return health.HTTPGet(ctx, c.DefaultAddress()) }), - WithInit(func(ctx context.Context, c *Container) error { - return cleaner.Notify(ctx, c.DefaultAddress(), id) + WithInit(func(_ context.Context, c *Container) error { + return cleaner.Notify(bctx, c.DefaultAddress(), id) }), + WithContext(bctx), } if cfg.UseLocalImagesFirst { opts = append(opts, WithUseLocalImagesFirst()) @@ -190,7 +194,7 @@ func (d *docker) setupContainerCleanup(id string, cfg *Options) chan string { } }() - return sidecarChan + return sidecarChan, bcancel } func (d *docker) prepareContainer( @@ -453,6 +457,10 @@ func (d *docker) stopContainer(ctx context.Context, id string) error { return nil } +func (d *docker) stopClient() error { + return d.client.Close() +} + func (d *docker) removeContainer(ctx context.Context, id string) error { d.lock.Lock() defer d.lock.Unlock() diff --git a/gnomock.go b/gnomock.go index 7a4aeac5..ec595bc6 100644 --- a/gnomock.go +++ b/gnomock.go @@ -114,6 +114,8 @@ func newContainer(g *g, image string, ports NamedPorts, config *Options) (c *Con return nil, fmt.Errorf("can't create docker client: %w", err) } + defer func() { _ = cli.stopClient() }() + c, err = cli.startContainer(ctx, image, ports, config) if err != nil { return nil, fmt.Errorf("can't start container: %w", err) @@ -230,6 +232,8 @@ func (g *g) stop(c *Container) error { return fmt.Errorf("can't create docker client: %w", err) } + defer func() { _ = cli.stopClient() }() + id, sidecar := parseID(c.ID) if sidecar != "" { go func() { @@ -237,6 +241,7 @@ func (g *g) stop(c *Container) error { // error in this case won't matter, the container has a self-destruct // timer _ = cli.stopContainer(context.Background(), sidecar) + _ = cli.stopClient() }() } diff --git a/gnomock_internal_test.go b/gnomock_internal_test.go index 6942cefb..2f35ad56 100644 --- a/gnomock_internal_test.go +++ b/gnomock_internal_test.go @@ -8,10 +8,15 @@ import ( "time" "github.com/stretchr/testify/require" + "go.uber.org/goleak" ) const testImage = "docker.io/orlangure/gnomock-test-image" +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestWaitForContainerNetwork(t *testing.T) { t.Parallel() @@ -36,6 +41,10 @@ func TestWaitForContainerNetwork(t *testing.T) { d, err := gg.dockerConnect() require.NoError(t, err) + defer func() { + require.NoError(t, d.stopClient()) + }() + ctx := context.Background() t.Run("fails after context cancellation", func(t *testing.T) { @@ -57,6 +66,8 @@ func TestWaitForContainerNetwork(t *testing.T) { require.Error(t, err) require.True(t, errors.Is(err, ErrPortNotFound), err.Error()) }) + + require.NoError(t, Stop(container)) } func TestEnvAwareClone(t *testing.T) { diff --git a/gnomock_test.go b/gnomock_test.go index b0d589d6..03e0b981 100644 --- a/gnomock_test.go +++ b/gnomock_test.go @@ -167,6 +167,7 @@ func TestGnomock_withDebugMode(t *testing.T) { containerList, err = testutil.ListContainerByID(cli, container.ID) require.NoError(t, err) require.Len(t, containerList, 0) + require.NoError(t, cli.Close()) } func TestGnomock_withLogWriter(t *testing.T) { diff --git a/go.mod b/go.mod index f3eae244..8ba92afa 100644 --- a/go.mod +++ b/go.mod @@ -26,22 +26,20 @@ require ( github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/rabbitmq/amqp091-go v1.9.0 github.com/segmentio/kafka-go v0.4.47 github.com/stretchr/testify v1.9.0 go.mongodb.org/mongo-driver v1.15.0 + go.uber.org/goleak v1.3.0 go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.26.0 + golang.org/x/mod v0.14.0 golang.org/x/sync v0.6.0 k8s.io/api v0.29.1 k8s.io/apimachinery v0.29.1 k8s.io/client-go v0.29.1 ) -require ( - github.com/rabbitmq/amqp091-go v1.9.0 - golang.org/x/mod v0.14.0 -) - require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // indirect diff --git a/go.sum b/go.sum index 99ad835b..ff99cb35 100644 --- a/go.sum +++ b/go.sum @@ -323,8 +323,9 @@ go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1 go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= diff --git a/internal/cleaner/cleaner_test.go b/internal/cleaner/cleaner_test.go index c1cf2e1b..398020c6 100644 --- a/internal/cleaner/cleaner_test.go +++ b/internal/cleaner/cleaner_test.go @@ -11,8 +11,14 @@ import ( "github.com/orlangure/gnomock/internal/health" "github.com/orlangure/gnomock/internal/testutil" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestCleaner(t *testing.T) { t.Parallel() @@ -53,6 +59,8 @@ func TestCleaner(t *testing.T) { containerList, err = testutil.ListContainerByID(cli, cleanerContainer.ID) require.NoError(t, err) require.Len(t, containerList, 0) + + require.NoError(t, cli.Close()) } func TestCleaner_wrongRequest(t *testing.T) { diff --git a/internal/errors/errors_test.go b/internal/errors/errors_test.go index 18c8ed8e..4093beac 100644 --- a/internal/errors/errors_test.go +++ b/internal/errors/errors_test.go @@ -8,8 +8,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/internal/errors" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPresetNotFoundError(t *testing.T) { err := errors.NewPresetNotFoundError("invalid") require.Equal(t, "preset 'invalid' not found", err.Error()) diff --git a/internal/health/health_test.go b/internal/health/health_test.go index e4553af0..7b734daa 100644 --- a/internal/health/health_test.go +++ b/internal/health/health_test.go @@ -8,8 +8,14 @@ import ( "github.com/orlangure/gnomock/internal/health" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestHTTPGet(t *testing.T) { ctx := context.Background() diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go index 0a5ead10..299a1c31 100644 --- a/internal/registry/registry_test.go +++ b/internal/registry/registry_test.go @@ -6,8 +6,13 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/internal/registry" "github.com/stretchr/testify/require" + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + var p gnomock.Preset func TestRegistry(t *testing.T) { diff --git a/preset/azurite/preset_test.go b/preset/azurite/preset_test.go index c297e403..5e8ba292 100644 --- a/preset/azurite/preset_test.go +++ b/preset/azurite/preset_test.go @@ -8,10 +8,15 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/orlangure/gnomock/preset/azurite" "github.com/stretchr/testify/require" + "go.uber.org/goleak" "github.com/orlangure/gnomock" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset_Blobstorage(t *testing.T) { t.Parallel() diff --git a/preset/cassandra/preset_test.go b/preset/cassandra/preset_test.go index 887b8ba3..4ab16aa1 100644 --- a/preset/cassandra/preset_test.go +++ b/preset/cassandra/preset_test.go @@ -7,8 +7,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/cassandra" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { t.Parallel() diff --git a/preset/cockroachdb/preset.go b/preset/cockroachdb/preset.go index 9f0a9f9e..40e61626 100644 --- a/preset/cockroachdb/preset.go +++ b/preset/cockroachdb/preset.go @@ -87,6 +87,10 @@ func (p *P) setDefaults() { func healthcheck(_ context.Context, c *gnomock.Container) error { db, err := connect(c, "") if err != nil { + if db != nil { + _ = db.Close() + } + return err } @@ -108,6 +112,7 @@ func (p *P) initf() gnomock.InitFunc { _, err = db.Exec("create database " + p.DB) if err != nil { + _ = db.Close() return err } diff --git a/preset/cockroachdb/preset_test.go b/preset/cockroachdb/preset_test.go index 39f7dbea..e6671c8e 100644 --- a/preset/cockroachdb/preset_test.go +++ b/preset/cockroachdb/preset_test.go @@ -8,8 +8,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/cockroachdb" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { t.Parallel() @@ -54,6 +60,7 @@ func testPreset(version string) func(t *testing.T) { require.Equal(t, float64(2), avg) require.Equal(t, float64(1), min) require.Equal(t, float64(3), count) + require.NoError(t, db.Close()) } } diff --git a/preset/elastic/preset_test.go b/preset/elastic/preset_test.go index 12d6d4df..42a6f363 100644 --- a/preset/elastic/preset_test.go +++ b/preset/elastic/preset_test.go @@ -9,8 +9,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/elastic" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + // these tests have trouble running in parallel, probably due to limited // resources diff --git a/preset/influxdb/preset_test.go b/preset/influxdb/preset_test.go index 3fc018d6..30239224 100644 --- a/preset/influxdb/preset_test.go +++ b/preset/influxdb/preset_test.go @@ -10,8 +10,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/influxdb" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { for _, version := range []string{"alpine"} { t.Run("version", func(t *testing.T) { @@ -96,5 +102,6 @@ func testPreset(version string, useDefaults bool) func(t *testing.T) { } require.Contains(t, orgNames, org) + client.Close() } } diff --git a/preset/k3s/preset_test.go b/preset/k3s/preset_test.go index 56a96168..ea3db025 100644 --- a/preset/k3s/preset_test.go +++ b/preset/k3s/preset_test.go @@ -11,8 +11,14 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { t.Parallel() diff --git a/preset/kafka/preset.go b/preset/kafka/preset.go index a7efa996..ed65d317 100644 --- a/preset/kafka/preset.go +++ b/preset/kafka/preset.go @@ -146,7 +146,8 @@ func (p *P) healthcheck(ctx context.Context, c *gnomock.Container) (err error) { if err != nil { return fmt.Errorf("can't create consumer group: %w", err) } - defer group.Close() + + defer func() { _ = group.Close() }() if _, err := group.Next(ctx); err != nil { return fmt.Errorf("can't read next consumer group: %w", err) diff --git a/preset/kafka/preset_test.go b/preset/kafka/preset_test.go index c3514547..b7f1720d 100644 --- a/preset/kafka/preset_test.go +++ b/preset/kafka/preset_test.go @@ -10,8 +10,14 @@ import ( "github.com/orlangure/gnomock/preset/kafka" kafkaclient "github.com/segmentio/kafka-go" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { versions := []string{"3.3.1-L0", "3.6.1-L0"} diff --git a/preset/localstack/preset_test.go b/preset/localstack/preset_test.go index 0a8b4ef4..75712a5e 100644 --- a/preset/localstack/preset_test.go +++ b/preset/localstack/preset_test.go @@ -16,8 +16,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/localstack" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset_s3(t *testing.T) { t.Parallel() diff --git a/preset/mariadb/preset.go b/preset/mariadb/preset.go index 517edf65..48ea734a 100644 --- a/preset/mariadb/preset.go +++ b/preset/mariadb/preset.go @@ -91,6 +91,10 @@ func (p *P) healthcheck(_ context.Context, c *gnomock.Container) error { db, err := p.connect(addr) if err != nil { + if db != nil { + _ = db.Close() + } + return err } diff --git a/preset/mariadb/preset_test.go b/preset/mariadb/preset_test.go index 8b1e0ae1..f0309b83 100644 --- a/preset/mariadb/preset_test.go +++ b/preset/mariadb/preset_test.go @@ -8,8 +8,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/mariadb" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { t.Parallel() @@ -61,6 +67,8 @@ func testPreset(version string) func(t *testing.T) { require.Equal(t, float64(2), avg) require.Equal(t, float64(1), min) require.Equal(t, float64(3), count) + + require.NoError(t, db.Close()) } } diff --git a/preset/memcached/preset.go b/preset/memcached/preset.go index 7c0bf2d3..33b22f80 100644 --- a/preset/memcached/preset.go +++ b/preset/memcached/preset.go @@ -98,5 +98,7 @@ func healthcheck(_ context.Context, c *gnomock.Container) error { addr := c.Address(gnomock.DefaultPort) client := memcache.New(addr) + defer func() { _ = client.Close() }() + return client.Ping() } diff --git a/preset/memcached/preset_test.go b/preset/memcached/preset_test.go index 55dc62bb..190c9efd 100644 --- a/preset/memcached/preset_test.go +++ b/preset/memcached/preset_test.go @@ -9,8 +9,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/memcached" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { t.Parallel() @@ -69,6 +75,8 @@ func testPreset(version string) func(t *testing.T) { itemD, err := client.Get("d") require.NoError(t, err) require.Equal(t, "foo", string(itemD.Value)) + + require.NoError(t, client.Close()) } } @@ -85,4 +93,6 @@ func TestPreset_withDefaults(t *testing.T) { addr := container.DefaultAddress() client := memcachedclient.New(addr) require.NoError(t, client.Ping()) + + require.NoError(t, client.Close()) } diff --git a/preset/mongo/preset.go b/preset/mongo/preset.go index 1abaab14..7e9ccd65 100644 --- a/preset/mongo/preset.go +++ b/preset/mongo/preset.go @@ -103,6 +103,8 @@ func (p *P) initf(ctx context.Context, c *gnomock.Container) error { return fmt.Errorf("can't create mongo client: %w", err) } + defer func() { _ = client.Disconnect(ctx) }() + topLevelDirs, err := os.ReadDir(p.DataPath) if err != nil { return fmt.Errorf("can't read test data path: %w", err) @@ -196,5 +198,7 @@ func healthcheck(ctx context.Context, c *gnomock.Container) error { return fmt.Errorf("can't create mongo client: %w", err) } + defer func() { _ = client.Disconnect(ctx) }() + return client.Ping(ctx, nil) } diff --git a/preset/mongo/preset_test.go b/preset/mongo/preset_test.go index c18cc9f3..abd748c1 100644 --- a/preset/mongo/preset_test.go +++ b/preset/mongo/preset_test.go @@ -12,8 +12,14 @@ import ( "go.mongodb.org/mongo-driver/bson" mongodb "go.mongodb.org/mongo-driver/mongo" mongooptions "go.mongodb.org/mongo-driver/mongo/options" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { t.Parallel() @@ -55,6 +61,8 @@ func testPreset(version string) func(t *testing.T) { count, err = client.Database("db2").Collection("countries").CountDocuments(ctx, bson.D{}) require.NoError(t, err) require.Equal(t, int64(3), count) + + require.NoError(t, client.Disconnect(ctx)) } } diff --git a/preset/mssql/preset.go b/preset/mssql/preset.go index 1be3bc5d..fc971a85 100644 --- a/preset/mssql/preset.go +++ b/preset/mssql/preset.go @@ -104,18 +104,21 @@ func (p *P) initf() gnomock.InitFunc { return err } - defer func() { _ = db.Close() }() - _, err = db.Exec("create database " + p.DB) if err != nil { + _ = db.Close() return fmt.Errorf("can't create database '%s': %w", p.DB, err) } + _ = db.Close() + db, err = p.connect(addr, p.DB) if err != nil { return err } + defer func() { _ = db.Close() }() + if len(p.QueriesFiles) > 0 { for _, f := range p.QueriesFiles { bs, err := os.ReadFile(f) // nolint:gosec diff --git a/preset/mssql/preset_test.go b/preset/mssql/preset_test.go index 17f4826f..81f49cb8 100644 --- a/preset/mssql/preset_test.go +++ b/preset/mssql/preset_test.go @@ -10,8 +10,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/mssql" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { t.Parallel() @@ -63,6 +69,7 @@ func testPreset(version string) func(t *testing.T) { require.Equal(t, float64(2), avg) require.Equal(t, float64(1), min) require.Equal(t, float64(3), count) + require.NoError(t, db.Close()) } } diff --git a/preset/mysql/preset.go b/preset/mysql/preset.go index 043aaead..65a334d0 100644 --- a/preset/mysql/preset.go +++ b/preset/mysql/preset.go @@ -93,6 +93,10 @@ func (p *P) healthcheck(_ context.Context, c *gnomock.Container) error { db, err := p.connect(addr) if err != nil { + if db != nil { + _ = db.Close() + } + return err } diff --git a/preset/mysql/preset_test.go b/preset/mysql/preset_test.go index 89c46568..50f61e87 100644 --- a/preset/mysql/preset_test.go +++ b/preset/mysql/preset_test.go @@ -8,8 +8,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/mysql" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { t.Parallel() @@ -59,6 +65,8 @@ func testPreset(version string) func(t *testing.T) { require.Equal(t, float64(2), avg) require.Equal(t, float64(1), min) require.Equal(t, float64(3), count) + + require.NoError(t, db.Close()) } } diff --git a/preset/postgres/preset.go b/preset/postgres/preset.go index 6e1a0156..ef4ec879 100644 --- a/preset/postgres/preset.go +++ b/preset/postgres/preset.go @@ -93,6 +93,10 @@ func (p *P) Options() []gnomock.Option { func (p *P) healthcheck(_ context.Context, c *gnomock.Container) error { db, err := connect(c, defaultDatabase) if err != nil { + if db != nil { + _ = db.Close() + } + return err } @@ -113,6 +117,8 @@ func (p *P) initf() gnomock.InitFunc { return err } + defer func() { _ = db.Close() }() + _, err = db.Exec("create database " + p.DB) if err != nil { isDuplicateDB := strings.Contains(err.Error(), fmt.Sprintf(`pq: database "%s" already exists`, p.DB)) @@ -120,8 +126,6 @@ func (p *P) initf() gnomock.InitFunc { return err } } - - _ = db.Close() } db, err := connect(c, p.DB) diff --git a/preset/postgres/preset_test.go b/preset/postgres/preset_test.go index be96f3f3..cd1f4598 100644 --- a/preset/postgres/preset_test.go +++ b/preset/postgres/preset_test.go @@ -8,8 +8,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/postgres" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { t.Parallel() @@ -63,6 +69,8 @@ func testPreset(version string) func(t *testing.T) { timezoneRow := db.QueryRow("show timezone") require.NoError(t, timezoneRow.Scan(&timezone)) require.Equal(t, "Europe/Paris", timezone) + + require.NoError(t, db.Close()) } } diff --git a/preset/rabbitmq/preset_test.go b/preset/rabbitmq/preset_test.go index 7d32494e..2d458e4e 100644 --- a/preset/rabbitmq/preset_test.go +++ b/preset/rabbitmq/preset_test.go @@ -12,8 +12,14 @@ import ( "github.com/orlangure/gnomock/preset/rabbitmq" amqp "github.com/rabbitmq/amqp091-go" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { versions := []string{ "3.8.9-alpine", diff --git a/preset/redis/preset.go b/preset/redis/preset.go index 241c4451..f084d241 100644 --- a/preset/redis/preset.go +++ b/preset/redis/preset.go @@ -60,6 +60,8 @@ func (p *P) Options() []gnomock.Option { addr := c.Address(gnomock.DefaultPort) client := redisclient.NewClient(&redisclient.Options{Addr: addr}) + defer func() { _ = client.Close() }() + for k, v := range p.Values { err := client.Set(k, v, 0).Err() if err != nil { @@ -85,6 +87,9 @@ func (p *P) setDefaults() { func healthcheck(_ context.Context, c *gnomock.Container) error { addr := c.Address(gnomock.DefaultPort) client := redisclient.NewClient(&redisclient.Options{Addr: addr}) + + defer func() { _ = client.Close() }() + _, err := client.Ping().Result() return err diff --git a/preset/redis/preset_test.go b/preset/redis/preset_test.go index bc70cd7e..23d70ec7 100644 --- a/preset/redis/preset_test.go +++ b/preset/redis/preset_test.go @@ -7,8 +7,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/redis" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { t.Parallel() @@ -52,6 +58,8 @@ func testPreset(version string) func(t *testing.T) { require.NoError(t, client.Get("c").Scan(&flag)) require.True(t, flag) + + require.NoError(t, client.Close()) } } diff --git a/preset/splunk/preset.go b/preset/splunk/preset.go index b834de57..896ec925 100644 --- a/preset/splunk/preset.go +++ b/preset/splunk/preset.go @@ -144,7 +144,8 @@ func checkHEC(ctx context.Context, c *gnomock.Container) error { func insecureClient() http.Client { return http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec + DisableKeepAlives: true, }, } } diff --git a/preset/splunk/preset_test.go b/preset/splunk/preset_test.go index edea18b3..ee1dfd28 100644 --- a/preset/splunk/preset_test.go +++ b/preset/splunk/preset_test.go @@ -16,8 +16,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/splunk" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { events := make([]splunk.Event, 1000) @@ -65,7 +71,8 @@ func TestPreset(t *testing.T) { t.Run("initial values ingested", func(t *testing.T) { client := &http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DisableKeepAlives: true, }, } diff --git a/preset/vault/preset_test.go b/preset/vault/preset_test.go index e16850eb..1e361d22 100644 --- a/preset/vault/preset_test.go +++ b/preset/vault/preset_test.go @@ -8,8 +8,14 @@ import ( "github.com/orlangure/gnomock" "github.com/orlangure/gnomock/preset/vault" "github.com/stretchr/testify/require" + + "go.uber.org/goleak" ) +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + func TestPreset(t *testing.T) { t.Parallel() diff --git a/preset_test.go b/preset_test.go index b5829400..ad267cf6 100644 --- a/preset_test.go +++ b/preset_test.go @@ -34,6 +34,10 @@ func TestPreset_parallel(t *testing.T) { require.NoError(t, health.HTTPGet(ctx, c.Address("web80"))) require.NoError(t, health.HTTPGet(ctx, c.Address("web8080"))) } + + for _, c := range containers { + require.NoError(t, gnomock.Stop(c)) + } } func TestPreset(t *testing.T) { @@ -77,6 +81,8 @@ func TestPreset_containerRemainsIfDebug(t *testing.T) { containerList, err := testutil.ListContainerByID(cli, container.ID) require.NoError(t, err) require.Len(t, containerList, 0) + + require.NoError(t, cli.Close()) } func TestPreset_duplicateContainerName(t *testing.T) { @@ -106,6 +112,8 @@ func TestPreset_duplicateContainerName(t *testing.T) { require.NoError(t, err) require.Len(t, containerList, 0) require.NoError(t, gnomock.Stop(newContainer)) + + require.NoError(t, cli.Close()) } func TestPreset_reusableContainerSucceeds(t *testing.T) {