diff options
author | Pavlo Strokov <pstrokov@gitlab.com> | 2021-10-07 11:31:25 +0300 |
---|---|---|
committer | Pavlo Strokov <pstrokov@gitlab.com> | 2021-10-07 11:31:25 +0300 |
commit | 043bb6e66fa839296d76d5105756a005b2613808 (patch) | |
tree | c090b62ec8f4e2b1b1ae1b53f43975b24fb79093 | |
parent | efb14ad0a5a0b23ebd96d3216c07c135258fd24e (diff) | |
parent | 9ef46dc05bbe2ef39838ec973a2e8cf01252d919 (diff) |
Merge branch 'pks-testhelper-goroutine-leakage' into 'master'
testhelper: Enable Goroutine leak checks by default
Closes #3790
See merge request gitlab-org/gitaly!3909
140 files changed, 752 insertions, 966 deletions
@@ -238,22 +238,10 @@ importantly, this includes any calls to `log.Fatal()` and related functions. ### Common setup -When all tests require a common setup, we use the `TestMain()` function for -this. `TestMain()` must call `os.Exit()` to indicate whether any tests failed. -As this will cause deferred function calls to not be processed, we use the -following pattern: - -``` -func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() -} -``` +The `TestMain()` function shouldn't do any package-specific setup. Instead, all +tests are supposed to set up required state as part of the tests themselves. All +`TestMain()` functions must call `testhelper.Run()` though, which performs the +setup of global state required for tests. ## Black box and white box testing diff --git a/client/client_test.go b/client/client_test.go index 6414626c1..e8083a74e 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1,19 +1,11 @@ package client import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/cmd/gitaly-backup/create.go b/cmd/gitaly-backup/create.go index 9dae3d4a5..0ceac7d8e 100644 --- a/cmd/gitaly-backup/create.go +++ b/cmd/gitaly-backup/create.go @@ -9,6 +9,7 @@ import ( "runtime" log "github.com/sirupsen/logrus" + "gitlab.com/gitlab-org/gitaly/v14/client" "gitlab.com/gitlab-org/gitaly/v14/internal/backup" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/storage" "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb" @@ -46,7 +47,10 @@ func (cmd *createSubcommand) Run(ctx context.Context, stdin io.Reader, stdout io return fmt.Errorf("create: resolve locator: %w", err) } - manager := backup.NewManager(sink, locator) + pool := client.NewPool() + defer pool.Close() + + manager := backup.NewManager(sink, locator, pool) var pipeline backup.Pipeline pipeline = backup.NewLoggingPipeline(log.StandardLogger()) diff --git a/cmd/gitaly-backup/restore.go b/cmd/gitaly-backup/restore.go index 695e98cfb..6e119ba92 100644 --- a/cmd/gitaly-backup/restore.go +++ b/cmd/gitaly-backup/restore.go @@ -10,6 +10,7 @@ import ( "runtime" log "github.com/sirupsen/logrus" + "gitlab.com/gitlab-org/gitaly/v14/client" "gitlab.com/gitlab-org/gitaly/v14/internal/backup" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/storage" "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb" @@ -48,7 +49,10 @@ func (cmd *restoreSubcommand) Run(ctx context.Context, stdin io.Reader, stdout i return fmt.Errorf("restore: resolve locator: %w", err) } - manager := backup.NewManager(sink, locator) + pool := client.NewPool() + defer pool.Close() + + manager := backup.NewManager(sink, locator, pool) var pipeline backup.Pipeline pipeline = backup.NewLoggingPipeline(log.StandardLogger()) diff --git a/cmd/gitaly-backup/testhelper_test.go b/cmd/gitaly-backup/testhelper_test.go index 7fd4db4d4..64d8edae5 100644 --- a/cmd/gitaly-backup/testhelper_test.go +++ b/cmd/gitaly-backup/testhelper_test.go @@ -1,21 +1,11 @@ package main import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } diff --git a/cmd/gitaly-git2go/conflicts/conflicts_test.go b/cmd/gitaly-git2go/conflicts/conflicts_test.go index 34b28bdf7..13a981365 100644 --- a/cmd/gitaly-git2go/conflicts/conflicts_test.go +++ b/cmd/gitaly-git2go/conflicts/conflicts_test.go @@ -4,7 +4,6 @@ package conflicts import ( - "os" "testing" git "github.com/libgit2/git2go/v31" @@ -17,14 +16,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func TestConflicts(t *testing.T) { diff --git a/cmd/gitaly-git2go/main_test.go b/cmd/gitaly-git2go/main_test.go index 75cfb525c..2113b2335 100644 --- a/cmd/gitaly-git2go/main_test.go +++ b/cmd/gitaly-git2go/main_test.go @@ -4,19 +4,11 @@ package main import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/cmd/gitaly-hooks/hooks_test.go b/cmd/gitaly-hooks/hooks_test.go index 93d980bdd..811c6f6d8 100644 --- a/cmd/gitaly-hooks/hooks_test.go +++ b/cmd/gitaly-hooks/hooks_test.go @@ -96,14 +96,7 @@ func envForHooks(t testing.TB, ctx context.Context, cfg config.Cfg, repo *gitaly } func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func TestHooksPrePostWithSymlinkedStoragePath(t *testing.T) { diff --git a/cmd/gitaly-lfs-smudge/lfs_smudge_test.go b/cmd/gitaly-lfs-smudge/lfs_smudge_test.go index 5136587d9..88f4c661f 100644 --- a/cmd/gitaly-lfs-smudge/lfs_smudge_test.go +++ b/cmd/gitaly-lfs-smudge/lfs_smudge_test.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "net/http" - "os" "path/filepath" "strings" "testing" @@ -52,14 +51,7 @@ type mapConfig struct { } func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func (m *mapConfig) Get(key string) string { diff --git a/cmd/gitaly-ssh/testhelper_test.go b/cmd/gitaly-ssh/testhelper_test.go index 7fd4db4d4..64d8edae5 100644 --- a/cmd/gitaly-ssh/testhelper_test.go +++ b/cmd/gitaly-ssh/testhelper_test.go @@ -1,21 +1,11 @@ package main import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } diff --git a/cmd/praefect/main.go b/cmd/praefect/main.go index 8b56cab28..44a0981f0 100644 --- a/cmd/praefect/main.go +++ b/cmd/praefect/main.go @@ -340,6 +340,7 @@ func run(cfgs []starter.Config, conf config.Config) error { nodeManager = nodeMgr nodeMgr.Start(conf.Failover.BootstrapInterval.Duration(), conf.Failover.MonitorInterval.Duration()) + defer nodeMgr.Stop() } logger.Infof("election strategy: %q", conf.Failover.ElectionStrategy) @@ -401,6 +402,8 @@ func run(cfgs []starter.Config, conf config.Config) error { if err != nil { return fmt.Errorf("create gRPC server: %w", err) } + defer srv.Stop() + b.RegisterStarter(starter.New(cfg, srv)) } diff --git a/cmd/praefect/main_test.go b/cmd/praefect/main_test.go index 5fb0619e4..9a4e8949f 100644 --- a/cmd/praefect/main_test.go +++ b/cmd/praefect/main_test.go @@ -2,7 +2,6 @@ package main import ( "errors" - "os" "testing" "github.com/stretchr/testify/assert" @@ -13,14 +12,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func TestNoConfigFlag(t *testing.T) { diff --git a/cmd/praefect/subcmd_track_repository_test.go b/cmd/praefect/subcmd_track_repository_test.go index bdf29ce68..c03329eb1 100644 --- a/cmd/praefect/subcmd_track_repository_test.go +++ b/cmd/praefect/subcmd_track_repository_test.go @@ -154,6 +154,7 @@ func TestAddRepository_Exec(t *testing.T) { nodeMgr, err := nodes.NewManager(testhelper.DiscardTestEntry(t), addCmdConf, db.DB, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(0, time.Hour) + defer nodeMgr.Stop() relativePath := fmt.Sprintf("path/to/test/repo_%s", tn) repoDS := datastore.NewPostgresRepositoryStore(db, conf.StorageNames()) diff --git a/internal/backup/backup.go b/internal/backup/backup.go index c59591b59..9543a4483 100644 --- a/internal/backup/backup.go +++ b/internal/backup/backup.go @@ -122,10 +122,10 @@ type Manager struct { } // NewManager creates and returns initialized *Manager instance. -func NewManager(sink Sink, locator Locator) *Manager { +func NewManager(sink Sink, locator Locator, pool *client.Pool) *Manager { return &Manager{ sink: sink, - conns: client.NewPool(), + conns: pool, locator: locator, backupID: time.Now().UTC().Format("20060102150405"), } diff --git a/internal/backup/backup_test.go b/internal/backup/backup_test.go index ab8304965..059f9267c 100644 --- a/internal/backup/backup_test.go +++ b/internal/backup/backup_test.go @@ -89,7 +89,10 @@ func TestManager_Create(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() - fsBackup := NewManager(NewFilesystemSink(path), LegacyLocator{}) + pool := client.NewPool() + defer testhelper.MustClose(t, pool) + + fsBackup := NewManager(NewFilesystemSink(path), LegacyLocator{}, pool) err := fsBackup.Create(ctx, &CreateRequest{ Server: storage.ServerInfo{Address: gitalyAddr, Token: cfg.Auth.Token}, Repository: tc.repo, @@ -351,11 +354,14 @@ func testManagerRestore(t *testing.T, cfg config.Cfg, gitalyAddr string) { repo, bundles := tc.setup(t) repoPath := filepath.Join(cfg.Storages[0].Path, repo.RelativePath) + pool := client.NewPool() + defer testhelper.MustClose(t, pool) + sink := NewFilesystemSink(path) locator, err := ResolveLocator(locatorName, sink) require.NoError(t, err) - fsBackup := NewManager(sink, locator) + fsBackup := NewManager(sink, locator, pool) err = fsBackup.Restore(ctx, &RestoreRequest{ Server: storage.ServerInfo{Address: gitalyAddr, Token: cfg.Auth.Token}, Repository: repo, diff --git a/internal/backup/testhelper_test.go b/internal/backup/testhelper_test.go index 83b1e76cd..b900cb5d5 100644 --- a/internal/backup/testhelper_test.go +++ b/internal/backup/testhelper_test.go @@ -1,21 +1,13 @@ package backup import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + // gocloud.dev/blob leaks the HTTP connection even if we make sure to close all buckets. + //nolint:staticcheck + testhelper.Run(m, testhelper.WithDisabledGoroutineChecker()) } diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index ef414184d..95e43aa10 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -38,6 +38,7 @@ type upgrader interface { HasParent() bool Ready() error Upgrade() error + Stop() } // New performs tableflip initialization @@ -172,6 +173,7 @@ func (b *Bootstrap) Wait(gracefulTimeout time.Duration) error { err = fmt.Errorf("graceful upgrade: %v", waitError) case s := <-immediateShutdown: err = fmt.Errorf("received signal %q", s) + b.upgrader.Stop() case err = <-b.errChan: } diff --git a/internal/bootstrap/bootstrap_test.go b/internal/bootstrap/bootstrap_test.go index 8b870395c..1fb6c3485 100644 --- a/internal/bootstrap/bootstrap_test.go +++ b/internal/bootstrap/bootstrap_test.go @@ -26,6 +26,8 @@ func (m *mockUpgrader) Exit() <-chan struct{} { return m.exit } +func (m *mockUpgrader) Stop() {} + func (m *mockUpgrader) HasParent() bool { return m.hasParent } @@ -104,7 +106,10 @@ func waitWithTimeout(t *testing.T, waitCh <-chan error, timeout time.Duration) e } func TestImmediateTerminationOnSocketError(t *testing.T) { - b, server := makeBootstrap(t) + ctx, cancel := testhelper.Context() + defer cancel() + + b, server := makeBootstrap(t, ctx) waitCh := make(chan error) go func() { waitCh <- b.Wait(2 * time.Second) }() @@ -119,7 +124,10 @@ func TestImmediateTerminationOnSocketError(t *testing.T) { func TestImmediateTerminationOnSignal(t *testing.T) { for _, sig := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} { t.Run(sig.String(), func(t *testing.T) { - b, server := makeBootstrap(t) + ctx, cancel := testhelper.Context() + defer cancel() + + b, server := makeBootstrap(t, ctx) done := server.slowRequest(3 * time.Minute) @@ -146,7 +154,10 @@ func TestImmediateTerminationOnSignal(t *testing.T) { } func TestGracefulTerminationStuck(t *testing.T) { - b, server := makeBootstrap(t) + ctx, cancel := testhelper.Context() + defer cancel() + + b, server := makeBootstrap(t, ctx) err := testGracefulUpdate(t, server, b, 3*time.Second, 2*time.Second, nil) require.Contains(t, err.Error(), "grace period expired") @@ -158,7 +169,9 @@ func TestGracefulTerminationWithSignals(t *testing.T) { for _, sig := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} { t.Run(sig.String(), func(t *testing.T) { - b, server := makeBootstrap(t) + ctx, cancel := testhelper.Context() + defer cancel() + b, server := makeBootstrap(t, ctx) err := testGracefulUpdate(t, server, b, 1*time.Second, 2*time.Second, func() { require.NoError(t, self.Signal(sig)) @@ -169,7 +182,9 @@ func TestGracefulTerminationWithSignals(t *testing.T) { } func TestGracefulTerminationServerErrors(t *testing.T) { - b, server := makeBootstrap(t) + ctx, cancel := testhelper.Context() + defer cancel() + b, server := makeBootstrap(t, ctx) done := make(chan error, 1) // This is a simulation of receiving a listener error during waitGracePeriod @@ -192,7 +207,9 @@ func TestGracefulTerminationServerErrors(t *testing.T) { } func TestGracefulTermination(t *testing.T) { - b, server := makeBootstrap(t) + ctx, cancel := testhelper.Context() + defer cancel() + b, server := makeBootstrap(t, ctx) // Using server.Close we bypass the graceful shutdown faking a completed shutdown b.StopAction = func() { server.server.Close() } @@ -202,21 +219,21 @@ func TestGracefulTermination(t *testing.T) { } func TestPortReuse(t *testing.T) { - var addr string - b, err := New() require.NoError(t, err) l, err := b.listen("tcp", "localhost:") require.NoError(t, err, "failed to bind") - addr = l.Addr().String() + addr := l.Addr().String() _, port, err := net.SplitHostPort(addr) require.NoError(t, err) l, err = b.listen("tcp", "localhost:"+port) require.NoError(t, err, "failed to bind") require.NoError(t, l.Close()) + + b.upgrader.Stop() } func testGracefulUpdate(t *testing.T, server *testServer, b *Bootstrap, waitTimeout, gracefulWait time.Duration, duringGracePeriodCallback func()) error { @@ -251,7 +268,7 @@ func testGracefulUpdate(t *testing.T, server *testServer, b *Bootstrap, waitTime return waitErr } -func makeBootstrap(t *testing.T) (*Bootstrap, *testServer) { +func makeBootstrap(t *testing.T, ctx context.Context) (*Bootstrap, *testServer) { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(200) @@ -260,12 +277,16 @@ func makeBootstrap(t *testing.T) (*Bootstrap, *testServer) { sec, err := strconv.Atoi(r.URL.Query().Get("seconds")) require.NoError(t, err) - time.Sleep(time.Duration(sec) * time.Second) + select { + case <-ctx.Done(): + case <-time.After(time.Duration(sec) * time.Second): + } w.WriteHeader(200) }) s := http.Server{Handler: mux} + t.Cleanup(func() { testhelper.MustClose(t, &s) }) u := &mockUpgrader{exit: make(chan struct{})} b, err := _new(u, net.Listen, false) diff --git a/internal/bootstrap/testhelper_test.go b/internal/bootstrap/testhelper_test.go index baed8d01f..514236bd7 100644 --- a/internal/bootstrap/testhelper_test.go +++ b/internal/bootstrap/testhelper_test.go @@ -1,18 +1,11 @@ package bootstrap import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/cache/diskcache.go b/internal/cache/diskcache.go index 3fb5bf4bb..dffc38ca5 100644 --- a/internal/cache/diskcache.go +++ b/internal/cache/diskcache.go @@ -10,6 +10,7 @@ import ( "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "github.com/prometheus/client_golang/prometheus" + "gitlab.com/gitlab-org/gitaly/v14/internal/dontpanic" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/storage" "gitlab.com/gitlab-org/gitaly/v14/internal/safe" @@ -79,6 +80,9 @@ type DiskCache struct { af activeFiles cacheConfig cacheConfig + walkersDone chan struct{} + walkerLoops []*dontpanic.Forever + requestTotals prometheus.Counter missTotals prometheus.Counter bytesStoredtotals prometheus.Counter @@ -107,6 +111,7 @@ func New(cfg config.Cfg, locator storage.Locator, opts ...Option) *DiskCache { m: map[string]int{}, }, cacheConfig: cacheConfig, + walkersDone: make(chan struct{}), requestTotals: prometheus.NewCounter( prometheus.CounterOpts{ diff --git a/internal/cache/testhelper_test.go b/internal/cache/testhelper_test.go index ed9467aae..687ad7d6e 100644 --- a/internal/cache/testhelper_test.go +++ b/internal/cache/testhelper_test.go @@ -1,19 +1,11 @@ package cache import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/cache/walker.go b/internal/cache/walker.go index bfd5fba01..00d990c17 100644 --- a/internal/cache/walker.go +++ b/internal/cache/walker.go @@ -98,12 +98,27 @@ func (c *DiskCache) walkLoop(walkPath string) { logger.Infof("Starting file walker for %s", walkPath) walkTick := time.NewTicker(cleanWalkFrequency) - dontpanic.GoForever(time.Minute, func() { + + forever := dontpanic.NewForever(time.Minute) + forever.Go(func() { + select { + case <-c.walkersDone: + return + default: + } + if err := c.cleanWalk(walkPath); err != nil { logger.Error(err) } - <-walkTick.C + + select { + case <-c.walkersDone: + return + case <-walkTick.C: + } }) + + c.walkerLoops = append(c.walkerLoops, forever) } func (c *DiskCache) startCleanWalker(cacheDir, stateDir string) { @@ -199,3 +214,12 @@ func (c *DiskCache) StartWalkers() error { return nil } + +// StopWalkers stops all walkers started by StartWalkers. +func (c *DiskCache) StopWalkers() { + close(c.walkersDone) + for _, walkerLoop := range c.walkerLoops { + walkerLoop.Cancel() + } + c.walkerLoops = nil +} diff --git a/internal/cache/walker_test.go b/internal/cache/walker_test.go index 87e02d8fa..727e93d04 100644 --- a/internal/cache/walker_test.go +++ b/internal/cache/walker_test.go @@ -53,6 +53,7 @@ func TestDiskCacheObjectWalker(t *testing.T) { cache := New(cfg, locator, withDisabledMoveAndClear()) require.NoError(t, cache.StartWalkers()) + defer cache.StopWalkers() pollCountersUntil(t, cache, 4) @@ -78,6 +79,7 @@ func TestDiskCacheInitialClear(t *testing.T) { cache := New(cfg, locator, withDisabledWalker()) require.NoError(t, cache.StartWalkers()) + defer cache.StopWalkers() require.NoFileExists(t, canary) } diff --git a/internal/cgroups/cgroups_linux_test.go b/internal/cgroups/cgroups_linux_test.go index 7045a8484..9d8267bff 100644 --- a/internal/cgroups/cgroups_linux_test.go +++ b/internal/cgroups/cgroups_linux_test.go @@ -1,7 +1,6 @@ package cgroups import ( - "os" "testing" "github.com/stretchr/testify/require" @@ -10,16 +9,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } func TestNewManager(t *testing.T) { diff --git a/internal/command/command.go b/internal/command/command.go index 858c72642..2960487d6 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -15,6 +15,7 @@ import ( "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "github.com/opentracing/opentracing-go" "github.com/sirupsen/logrus" + "gitlab.com/gitlab-org/gitaly/v14/internal/command/commandcounter" "gitlab.com/gitlab-org/labkit/tracing" ) @@ -118,15 +119,6 @@ func (c *Command) Wait() error { return c.waitError } -var wg = &sync.WaitGroup{} - -// WaitAllDone waits for all commands started by the command package to -// finish. This can only be called once in the lifecycle of the current -// Go process. -func WaitAllDone() { - wg.Wait() -} - type contextWithoutDonePanic string // New creates a Command from an exec.Cmd. On success, the Command @@ -225,7 +217,7 @@ func New(ctx context.Context, cmd *exec.Cmd, stdin io.Reader, stdout, stderr io. // The goroutine below is responsible for terminating and reaping the // process when ctx is canceled. - wg.Add(1) + commandcounter.Increment() go func() { <-ctx.Done() @@ -277,14 +269,14 @@ func (c *Command) wait() { c.logProcessComplete() - // This is a bit out-of-place here given that the `wg.Add()` call is in `New()`. - // But in `New()`, we have to resort waiting on the context being finished until we + // This is a bit out-of-place here given that the `commandcounter.Increment()` call is in + // `New()`. But in `New()`, we have to resort waiting on the context being finished until we // would be able to decrement the number of in-flight commands. Given that in some // cases we detach processes from their initial context such that they run in the - // background, this would cause us to take longer than necessary to decrease the - // wait group counter again. So we instead do it here to accelerate the process, - // even though it's less idiomatic. - wg.Done() + // background, this would cause us to take longer than necessary to decrease the wait group + // counter again. So we instead do it here to accelerate the process, even though it's less + // idiomatic. + commandcounter.Decrement() } // ExitStatus will return the exit-code from an error returned by Wait(). diff --git a/internal/command/command_test.go b/internal/command/command_test.go index c29fba8ed..8f563f4c1 100644 --- a/internal/command/command_test.go +++ b/internal/command/command_test.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "io" - "os" "os/exec" "regexp" "strings" @@ -16,15 +15,15 @@ import ( "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uber.org/goleak" + "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - goleak.VerifyTestMain(m, goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start")) + testhelper.Run(m) } func TestNewCommandExtraEnv(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() extraVar := "FOOBAR=123456" @@ -38,7 +37,7 @@ func TestNewCommandExtraEnv(t *testing.T) { } func TestNewCommandExportedEnv(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() testCases := []struct { @@ -113,15 +112,8 @@ func TestNewCommandExportedEnv(t *testing.T) { for _, tc := range testCases { t.Run(tc.key, func(t *testing.T) { - oldValue, exists := os.LookupEnv(tc.key) - defer func() { - if !exists { - require.NoError(t, os.Unsetenv(tc.key)) - return - } - require.NoError(t, os.Setenv(tc.key, oldValue)) - }() - require.NoError(t, os.Setenv(tc.key, tc.value)) + cleanup := testhelper.ModifyEnvironment(t, tc.key, tc.value) + defer cleanup() buff := &bytes.Buffer{} cmd, err := New(ctx, exec.Command("/usr/bin/env"), nil, buff, nil) @@ -135,21 +127,12 @@ func TestNewCommandExportedEnv(t *testing.T) { } func TestNewCommandUnexportedEnv(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() unexportedEnvKey, unexportedEnvVal := "GITALY_UNEXPORTED_ENV", "foobar" - - oldValue, exists := os.LookupEnv(unexportedEnvKey) - defer func() { - if !exists { - require.NoError(t, os.Unsetenv(unexportedEnvKey)) - return - } - require.NoError(t, os.Setenv(unexportedEnvKey, oldValue)) - }() - - require.NoError(t, os.Setenv(unexportedEnvKey, unexportedEnvVal)) + cleanup := testhelper.ModifyEnvironment(t, unexportedEnvKey, unexportedEnvVal) + defer cleanup() buff := &bytes.Buffer{} cmd, err := New(ctx, exec.Command("/usr/bin/env"), nil, buff, nil) @@ -178,7 +161,7 @@ func TestRejectEmptyContextDone(t *testing.T) { } func TestNewCommandTimeout(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() defer func(ch chan struct{}, t time.Duration) { @@ -222,7 +205,7 @@ wait: } func TestCommand_Wait_interrupts_after_context_timeout(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() ctx, timeout := context.WithTimeout(ctx, time.Second) @@ -246,7 +229,7 @@ func TestCommand_Wait_interrupts_after_context_timeout(t *testing.T) { } func TestNewCommandWithSetupStdin(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() value := "Test value" @@ -267,7 +250,7 @@ func TestNewCommandWithSetupStdin(t *testing.T) { } func TestNewCommandNullInArg(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() _, err := New(ctx, exec.Command("sh", "-c", "hello\x00world"), nil, nil, nil) @@ -276,7 +259,7 @@ func TestNewCommandNullInArg(t *testing.T) { } func TestNewNonExistent(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() cmd, err := New(ctx, exec.Command("command-non-existent"), nil, nil, nil) @@ -285,7 +268,7 @@ func TestNewNonExistent(t *testing.T) { } func TestCommandStdErr(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() var stdout, stderr bytes.Buffer @@ -305,7 +288,7 @@ func TestCommandStdErr(t *testing.T) { } func TestCommandStdErrLargeOutput(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() var stdout, stderr bytes.Buffer @@ -325,7 +308,7 @@ func TestCommandStdErrLargeOutput(t *testing.T) { } func TestCommandStdErrBinaryNullBytes(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() var stdout, stderr bytes.Buffer @@ -345,7 +328,7 @@ func TestCommandStdErrBinaryNullBytes(t *testing.T) { } func TestCommandStdErrLongLine(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() var stdout, stderr bytes.Buffer @@ -364,7 +347,7 @@ func TestCommandStdErrLongLine(t *testing.T) { } func TestCommandStdErrMaxBytes(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() var stdout, stderr bytes.Buffer diff --git a/internal/command/commandcounter/counter.go b/internal/command/commandcounter/counter.go new file mode 100644 index 000000000..f2eb7935d --- /dev/null +++ b/internal/command/commandcounter/counter.go @@ -0,0 +1,25 @@ +package commandcounter + +import "sync" + +// inFlightCommands tracks the number of in-flight commands. This is hosted outside of the command +// package to avoid a cyclic dependency with the testhelper package. +var inFlightCommands = &sync.WaitGroup{} + +// Increment will add another process to the command counter. This may only be called by the +// command package. +func Increment() { + inFlightCommands.Add(1) +} + +// Decrement removes one process from the command counter. This may only be called by the command +// package. +func Decrement() { + inFlightCommands.Done() +} + +// WaitAllDone waits for all commands started by the command package to finish. This can only be +// called once in the lifecycle of the current Go process. +func WaitAllDone() { + inFlightCommands.Wait() +} diff --git a/internal/command/stats_test.go b/internal/command/stats_test.go index 98af29a51..12b29505c 100644 --- a/internal/command/stats_test.go +++ b/internal/command/stats_test.go @@ -1,15 +1,15 @@ package command import ( - "context" "testing" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestStatsFromContext_BackgroundContext(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() stats := StatsFromContext(ctx) @@ -17,7 +17,7 @@ func TestStatsFromContext_BackgroundContext(t *testing.T) { } func TestStatsFromContext_InitContext(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() ctx = InitContextStats(ctx) @@ -29,7 +29,7 @@ func TestStatsFromContext_InitContext(t *testing.T) { } func TestStatsFromContext_RecordSum(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() ctx = InitContextStats(ctx) @@ -44,7 +44,7 @@ func TestStatsFromContext_RecordSum(t *testing.T) { } func TestStatsFromContext_RecordSumByRef(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() ctx = InitContextStats(ctx) @@ -61,7 +61,7 @@ func TestStatsFromContext_RecordSumByRef(t *testing.T) { } func TestStatsFromContext_RecordMax(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testhelper.Context() defer cancel() ctx = InitContextStats(ctx) diff --git a/internal/dontpanic/retry.go b/internal/dontpanic/retry.go index bb8fa93bb..83eb007f5 100644 --- a/internal/dontpanic/retry.go +++ b/internal/dontpanic/retry.go @@ -8,6 +8,7 @@ package dontpanic import ( + "sync" "time" sentry "github.com/getsentry/sentry-go" @@ -56,24 +57,63 @@ func catchAndLog(fn func()) bool { return normal } -// GoForever will keep retrying a function fn in a goroutine forever in the -// background (until the process exits) while recovering from panics. Each -// time a closure panics, the recovered value will be sent to Sentry and -// logged at level error. The provided backoff will delay retries to enable +// Forever encapsulates logic to run a function forever. +type Forever struct { + backoff time.Duration + + cancelOnce sync.Once + cancelCh chan struct{} + doneCh chan struct{} +} + +// NewForever creates a new Forever struct. The given duration controls how long retry of a +// function should be delayed if the function were to thrown an error. +func NewForever(backoff time.Duration) *Forever { + return &Forever{ + backoff: backoff, + cancelCh: make(chan struct{}), + doneCh: make(chan struct{}), + } +} + +// Go will keep retrying a function fn in a goroutine forever in the background (until the process +// exits) while recovering from panics. Each time a closure panics, the recovered value will be +// sent to Sentry and logged at level error. The provided backoff will delay retries to enable // "breathing" room to prevent potentially worsening the situation. -func GoForever(backoff time.Duration, fn func()) { +func (f *Forever) Go(fn func()) { go func() { + defer close(f.doneCh) + for { + select { + case <-f.cancelCh: + return + default: + } + if Try(fn) { continue } - if backoff <= 0 { + if f.backoff <= 0 { continue } - logger.Infof("dontpanic: backing off %s before retrying", backoff) - time.Sleep(backoff) + logger.Infof("dontpanic: backing off %s before retrying", f.backoff) + + select { + case <-f.cancelCh: + return + case <-time.After(f.backoff): + } } }() } + +// Cancel cancels the walking loop. +func (f *Forever) Cancel() { + f.cancelOnce.Do(func() { + close(f.cancelCh) + <-f.doneCh + }) +} diff --git a/internal/dontpanic/retry_test.go b/internal/dontpanic/retry_test.go index d94ce3801..dcb683161 100644 --- a/internal/dontpanic/retry_test.go +++ b/internal/dontpanic/retry_test.go @@ -49,11 +49,13 @@ func TestGoForever(t *testing.T) { panic("don't panic") } - dontpanic.GoForever(time.Microsecond, fn) + forever := dontpanic.NewForever(time.Microsecond) + forever.Go(fn) var actualPanics int for range recoveredQ { actualPanics++ } require.Equal(t, expectPanics, actualPanics) + forever.Cancel() } diff --git a/internal/dontpanic/testhelper_test.go b/internal/dontpanic/testhelper_test.go index d4d8f7402..dd438042b 100644 --- a/internal/dontpanic/testhelper_test.go +++ b/internal/dontpanic/testhelper_test.go @@ -1,19 +1,11 @@ package dontpanic import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/git/catfile/testhelper_test.go b/internal/git/catfile/testhelper_test.go index 4479adb85..fa977e099 100644 --- a/internal/git/catfile/testhelper_test.go +++ b/internal/git/catfile/testhelper_test.go @@ -2,7 +2,6 @@ package catfile import ( "context" - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/command" @@ -13,18 +12,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer func() { - testhelper.MustHaveNoChildProcess() - testhelper.MustHaveNoGoroutines() - }() - - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } type repoExecutor struct { diff --git a/internal/git/gitpipe/testhelper_test.go b/internal/git/gitpipe/testhelper_test.go index b3683135f..8b2532840 100644 --- a/internal/git/gitpipe/testhelper_test.go +++ b/internal/git/gitpipe/testhelper_test.go @@ -1,21 +1,11 @@ package gitpipe import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } diff --git a/internal/git/gittest/commit_test.go b/internal/git/gittest/commit_test.go index e96520a14..c3ad815ce 100644 --- a/internal/git/gittest/commit_test.go +++ b/internal/git/gittest/commit_test.go @@ -19,6 +19,8 @@ func TestWriteCommit(t *testing.T) { defer cancel() batchCache := catfile.NewCache(cfg) + defer batchCache.Stop() + batch, err := batchCache.BatchProcess(ctx, repo) require.NoError(t, err) diff --git a/internal/git/gittest/testhelper_test.go b/internal/git/gittest/testhelper_test.go index 89d810164..a16e984f6 100644 --- a/internal/git/gittest/testhelper_test.go +++ b/internal/git/gittest/testhelper_test.go @@ -12,14 +12,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } // setup sets up a test configuration and repository. Ideally we'd use our central test helpers to diff --git a/internal/git/helper_test.go b/internal/git/helper_test.go index eb7e925fd..f76065121 100644 --- a/internal/git/helper_test.go +++ b/internal/git/helper_test.go @@ -1,7 +1,6 @@ package git import ( - "os" "testing" "github.com/stretchr/testify/require" @@ -9,14 +8,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func TestValidateRevision(t *testing.T) { diff --git a/internal/git/housekeeping/testhelper_test.go b/internal/git/housekeeping/testhelper_test.go index 05d4da7f7..f0afe0397 100644 --- a/internal/git/housekeeping/testhelper_test.go +++ b/internal/git/housekeeping/testhelper_test.go @@ -1,18 +1,11 @@ package housekeeping import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/git/localrepo/config_test.go b/internal/git/localrepo/config_test.go index 15c440524..7abc7be08 100644 --- a/internal/git/localrepo/config_test.go +++ b/internal/git/localrepo/config_test.go @@ -31,7 +31,10 @@ func setupRepoConfig(t *testing.T) (Config, string) { repoProto, repoPath := gittest.InitRepo(t, cfg, cfg.Storages[0]) gitCmdFactory := git.NewExecCommandFactory(cfg) - repo := New(gitCmdFactory, catfile.NewCache(cfg), repoProto, cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + + repo := New(gitCmdFactory, catfileCache, repoProto, cfg) return repo.Config(), repoPath } diff --git a/internal/git/localrepo/objects_test.go b/internal/git/localrepo/objects_test.go index d1d797955..53c420d6b 100644 --- a/internal/git/localrepo/objects_test.go +++ b/internal/git/localrepo/objects_test.go @@ -35,7 +35,9 @@ func setupRepo(t *testing.T, bare bool) (*Repo, string) { } gitCmdFactory := git.NewExecCommandFactory(cfg) - return New(gitCmdFactory, catfile.NewCache(cfg), repoProto, cfg), repoPath + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + return New(gitCmdFactory, catfileCache, repoProto, cfg), repoPath } type ReaderFunc func([]byte) (int, error) diff --git a/internal/git/localrepo/refs_test.go b/internal/git/localrepo/refs_test.go index 3d3fdcd32..6bdc9a7e1 100644 --- a/internal/git/localrepo/refs_test.go +++ b/internal/git/localrepo/refs_test.go @@ -242,9 +242,12 @@ func TestRepo_GetRemoteReferences(t *testing.T) { annotatedTagOID := text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", "annotated-tag")) gitCmdFactory := git.NewExecCommandFactory(cfg) + catfileCache := catfile.NewCache(cfg) + defer catfileCache.Stop() + repo := New( gitCmdFactory, - catfile.NewCache(cfg), + catfileCache, &gitalypb.Repository{StorageName: "default", RelativePath: filepath.Join(relativePath, ".git")}, cfg, ) diff --git a/internal/git/localrepo/remote_test.go b/internal/git/localrepo/remote_test.go index 6b165f0c9..547f0cf90 100644 --- a/internal/git/localrepo/remote_test.go +++ b/internal/git/localrepo/remote_test.go @@ -37,7 +37,10 @@ func setupRepoRemote(t *testing.T, bare bool) (Remote, string) { } gitCmdFactory := git.NewExecCommandFactory(cfg) - return New(gitCmdFactory, catfile.NewCache(cfg), repoProto, cfg).Remote(), repoPath + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + + return New(gitCmdFactory, catfileCache, repoProto, cfg).Remote(), repoPath } func TestRepo_Remote(t *testing.T) { diff --git a/internal/git/localrepo/repo.go b/internal/git/localrepo/repo.go index 08c996b18..af869a6bb 100644 --- a/internal/git/localrepo/repo.go +++ b/internal/git/localrepo/repo.go @@ -37,7 +37,9 @@ func New(gitCmdFactory git.CommandFactory, catfileCache catfile.Cache, repo repo // dependencies ad-hoc from the given config. func NewTestRepo(t testing.TB, cfg config.Cfg, repo repository.GitRepo) *Repo { gitCmdFactory := git.NewExecCommandFactory(cfg) - return New(gitCmdFactory, catfile.NewCache(cfg), repo, cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + return New(gitCmdFactory, catfileCache, repo, cfg) } // Path returns the on-disk path of the repository. diff --git a/internal/git/localrepo/repo_test.go b/internal/git/localrepo/repo_test.go index 8993471e0..c456e21b3 100644 --- a/internal/git/localrepo/repo_test.go +++ b/internal/git/localrepo/repo_test.go @@ -20,7 +20,9 @@ func TestRepo(t *testing.T) { gittest.TestRepository(t, cfg, func(t testing.TB, pbRepo *gitalypb.Repository) git.Repository { t.Helper() gitCmdFactory := git.NewExecCommandFactory(cfg) - return New(gitCmdFactory, catfile.NewCache(cfg), pbRepo, cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + return New(gitCmdFactory, catfileCache, pbRepo, cfg) }) } @@ -28,7 +30,9 @@ func TestRepo_Path(t *testing.T) { t.Run("valid repository", func(t *testing.T) { cfg, repoProto, repoPath := testcfg.BuildWithRepo(t) gitCmdFactory := git.NewExecCommandFactory(cfg) - repo := New(gitCmdFactory, catfile.NewCache(cfg), repoProto, cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + repo := New(gitCmdFactory, catfileCache, repoProto, cfg) path, err := repo.Path() require.NoError(t, err) @@ -38,7 +42,9 @@ func TestRepo_Path(t *testing.T) { t.Run("deleted repository", func(t *testing.T) { cfg, repoProto, repoPath := testcfg.BuildWithRepo(t) gitCmdFactory := git.NewExecCommandFactory(cfg) - repo := New(gitCmdFactory, catfile.NewCache(cfg), repoProto, cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + repo := New(gitCmdFactory, catfileCache, repoProto, cfg) require.NoError(t, os.RemoveAll(repoPath)) @@ -49,7 +55,9 @@ func TestRepo_Path(t *testing.T) { t.Run("non-git repository", func(t *testing.T) { cfg, repoProto, repoPath := testcfg.BuildWithRepo(t) gitCmdFactory := git.NewExecCommandFactory(cfg) - repo := New(gitCmdFactory, catfile.NewCache(cfg), repoProto, cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + repo := New(gitCmdFactory, catfileCache, repoProto, cfg) // Recreate the repository as a simple empty directory to simulate // that the repository is in a partially-created state. diff --git a/internal/git/localrepo/testhelper_test.go b/internal/git/localrepo/testhelper_test.go index 224d1566b..38486d705 100644 --- a/internal/git/localrepo/testhelper_test.go +++ b/internal/git/localrepo/testhelper_test.go @@ -1,19 +1,11 @@ package localrepo import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/git/objectpool/clone_test.go b/internal/git/objectpool/clone_test.go index 0720170a0..f3689ef24 100644 --- a/internal/git/objectpool/clone_test.go +++ b/internal/git/objectpool/clone_test.go @@ -23,11 +23,14 @@ func setupObjectPool(t *testing.T) (*ObjectPool, *gitalypb.Repository) { cfg, repo, _ := testcfg.BuildWithRepo(t) gitCommandFactory := git.NewExecCommandFactory(cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + pool, err := NewObjectPool( cfg, config.NewLocator(cfg), gitCommandFactory, - catfile.NewCache(cfg), + catfileCache, transaction.NewManager(cfg, backchannel.NewRegistry()), repo.GetStorageName(), gittest.NewObjectPoolName(t), diff --git a/internal/git/objectpool/testhelper_test.go b/internal/git/objectpool/testhelper_test.go index b252b37c1..bf79617d8 100644 --- a/internal/git/objectpool/testhelper_test.go +++ b/internal/git/objectpool/testhelper_test.go @@ -1,7 +1,6 @@ package objectpool import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/git/hooks" @@ -9,13 +8,8 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - hooks.Override = "/" - return m.Run() + testhelper.Run(m, testhelper.WithSetup(func() error { + hooks.Override = "/" + return nil + })) } diff --git a/internal/git/packfile/packfile_test.go b/internal/git/packfile/packfile_test.go index c8d412827..ecf56563b 100644 --- a/internal/git/packfile/packfile_test.go +++ b/internal/git/packfile/packfile_test.go @@ -1,7 +1,6 @@ package packfile_test import ( - "os" "path/filepath" "testing" @@ -13,14 +12,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func TestList(t *testing.T) { diff --git a/internal/git/quarantine/testhelper_test.go b/internal/git/quarantine/testhelper_test.go index 7ad131635..38d896d42 100644 --- a/internal/git/quarantine/testhelper_test.go +++ b/internal/git/quarantine/testhelper_test.go @@ -1,21 +1,11 @@ package quarantine import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } diff --git a/internal/git/remoterepo/helper_test.go b/internal/git/remoterepo/helper_test.go index 555b5445d..619ff5b16 100644 --- a/internal/git/remoterepo/helper_test.go +++ b/internal/git/remoterepo/helper_test.go @@ -1,19 +1,11 @@ package remoterepo import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/git/remoterepo/repository_test.go b/internal/git/remoterepo/repository_test.go index ddc63fb11..5bb233fdc 100644 --- a/internal/git/remoterepo/repository_test.go +++ b/internal/git/remoterepo/repository_test.go @@ -32,6 +32,7 @@ func TestRepository(t *testing.T) { deps.GetTxManager(), deps.GetGitCmdFactory(), deps.GetCatfileCache(), + deps.GetConnsPool(), )) gitalypb.RegisterCommitServiceServer(srv, commit.NewServer( deps.GetCfg(), diff --git a/internal/git/stats/testhelper_test.go b/internal/git/stats/testhelper_test.go index ce080ff27..b842dc949 100644 --- a/internal/git/stats/testhelper_test.go +++ b/internal/git/stats/testhelper_test.go @@ -1,19 +1,11 @@ package stats import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/git/updateref/updateref_test.go b/internal/git/updateref/updateref_test.go index c2cf6c0d6..17c87fd16 100644 --- a/internal/git/updateref/updateref_test.go +++ b/internal/git/updateref/updateref_test.go @@ -3,7 +3,6 @@ package updateref import ( "context" "fmt" - "os" "strings" "testing" @@ -17,15 +16,10 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - hooks.Override = "/" - return m.Run() + testhelper.Run(m, testhelper.WithSetup(func() error { + hooks.Override = "/" + return nil + })) } func setupUpdater(t *testing.T, ctx context.Context) (config.Cfg, *localrepo.Repo, *Updater) { diff --git a/internal/git2go/commit_test.go b/internal/git2go/commit_test.go index 04eb5334e..0f45235e2 100644 --- a/internal/git2go/commit_test.go +++ b/internal/git2go/commit_test.go @@ -5,7 +5,6 @@ import ( "context" "errors" "fmt" - "os" "strconv" "strings" "testing" @@ -21,14 +20,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } type commit struct { diff --git a/internal/gitaly/config/testhelper_test.go b/internal/gitaly/config/testhelper_test.go index 88f7044fa..de452320d 100644 --- a/internal/gitaly/config/testhelper_test.go +++ b/internal/gitaly/config/testhelper_test.go @@ -1,19 +1,11 @@ package config_test import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/gitaly/hook/testhelper_test.go b/internal/gitaly/hook/testhelper_test.go index 9ea5b4c4a..ed02a0c77 100644 --- a/internal/gitaly/hook/testhelper_test.go +++ b/internal/gitaly/hook/testhelper_test.go @@ -1,19 +1,11 @@ package hook import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/gitaly/linguist/linguist_test.go b/internal/gitaly/linguist/linguist_test.go index 3caea5606..ea662ef60 100644 --- a/internal/gitaly/linguist/linguist_test.go +++ b/internal/gitaly/linguist/linguist_test.go @@ -2,7 +2,6 @@ package linguist import ( "encoding/json" - "os" "path/filepath" "testing" @@ -13,14 +12,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func TestInstance_Stats_unmarshalJSONError(t *testing.T) { diff --git a/internal/gitaly/maintenance/main_test.go b/internal/gitaly/maintenance/main_test.go index f7b6eeaf1..2cf16c9b6 100644 --- a/internal/gitaly/maintenance/main_test.go +++ b/internal/gitaly/maintenance/main_test.go @@ -1,19 +1,11 @@ package maintenance import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/gitaly/maintenance/optimize_test.go b/internal/gitaly/maintenance/optimize_test.go index 19d6ee5ca..d764f59ee 100644 --- a/internal/gitaly/maintenance/optimize_test.go +++ b/internal/gitaly/maintenance/optimize_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/v14/client" "gitlab.com/gitlab-org/gitaly/v14/internal/backchannel" "gitlab.com/gitlab-org/gitaly/v14/internal/git" "gitlab.com/gitlab-org/gitaly/v14/internal/git/catfile" @@ -33,7 +34,18 @@ func (mo *mockOptimizer) OptimizeRepository(ctx context.Context, req *gitalypb.O l := config.NewLocator(mo.cfg) gitCmdFactory := git.NewExecCommandFactory(mo.cfg) catfileCache := catfile.NewCache(mo.cfg) - resp, err := repository.NewServer(mo.cfg, nil, l, transaction.NewManager(mo.cfg, backchannel.NewRegistry()), gitCmdFactory, catfileCache).OptimizeRepository(ctx, req) + mo.t.Cleanup(catfileCache.Stop) + + connsPool := client.NewPool() + mo.t.Cleanup(func() { testhelper.MustClose(mo.t, connsPool) }) + + resp, err := repository.NewServer(mo.cfg, nil, l, transaction.NewManager( + mo.cfg, + backchannel.NewRegistry()), + gitCmdFactory, + catfileCache, + connsPool, + ).OptimizeRepository(ctx, req) assert.NoError(mo.t, err) return resp, err } diff --git a/internal/gitaly/rubyserver/testhelper_test.go b/internal/gitaly/rubyserver/testhelper_test.go index 2d4c7deba..f304d96c2 100644 --- a/internal/gitaly/rubyserver/testhelper_test.go +++ b/internal/gitaly/rubyserver/testhelper_test.go @@ -1,21 +1,11 @@ package rubyserver import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } diff --git a/internal/gitaly/server/auth_test.go b/internal/gitaly/server/auth_test.go index 2dbde3056..adf2d6300 100644 --- a/internal/gitaly/server/auth_test.go +++ b/internal/gitaly/server/auth_test.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "net" - "os" "testing" "time" @@ -39,14 +38,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func TestSanity(t *testing.T) { @@ -202,6 +194,7 @@ func runServer(t *testing.T, cfg config.Cfg) string { ), cfg) gitCmdFactory := git.NewExecCommandFactory(cfg) catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) diskCache := cache.New(cfg, locator) srv, err := New(false, cfg, testhelper.DiscardTestEntry(t), registry, diskCache) @@ -255,6 +248,7 @@ func TestUnaryNoAuth(t *testing.T) { path := runServer(t, cfg) conn, err := grpc.Dial(path, grpc.WithInsecure()) require.NoError(t, err) + defer testhelper.MustClose(t, conn) ctx, cancel := testhelper.Context() defer cancel() diff --git a/internal/gitaly/server/server_factory_test.go b/internal/gitaly/server/server_factory_test.go index c851c32d7..369cf3775 100644 --- a/internal/gitaly/server/server_factory_test.go +++ b/internal/gitaly/server/server_factory_test.go @@ -239,8 +239,10 @@ func TestGitalyServerFactory_closeOrder(t *testing.T) { go server.Serve(ln) - *builder.conn, err = grpc.DialContext(ctx, ln.Addr().String(), grpc.WithInsecure()) + conn, err := grpc.DialContext(ctx, ln.Addr().String(), grpc.WithInsecure()) require.NoError(t, err) + t.Cleanup(func() { testhelper.MustClose(t, conn) }) + *builder.conn = conn } // both servers should be up and accepting RPCs diff --git a/internal/gitaly/service/blob/testhelper_test.go b/internal/gitaly/service/blob/testhelper_test.go index 5d760d761..6dfffa6a4 100644 --- a/internal/gitaly/service/blob/testhelper_test.go +++ b/internal/gitaly/service/blob/testhelper_test.go @@ -1,7 +1,6 @@ package blob import ( - "os" "testing" "github.com/stretchr/testify/require" @@ -16,16 +15,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } func setup(t *testing.T) (config.Cfg, *gitalypb.Repository, string, gitalypb.BlobServiceClient) { diff --git a/internal/gitaly/service/cleanup/testhelper_test.go b/internal/gitaly/service/cleanup/testhelper_test.go index 7cb00db68..220b46bc1 100644 --- a/internal/gitaly/service/cleanup/testhelper_test.go +++ b/internal/gitaly/service/cleanup/testhelper_test.go @@ -1,7 +1,6 @@ package cleanup import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config" @@ -15,14 +14,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func setupCleanupService(t *testing.T) (config.Cfg, *gitalypb.Repository, string, gitalypb.CleanupServiceClient) { diff --git a/internal/gitaly/service/commit/testhelper_test.go b/internal/gitaly/service/commit/testhelper_test.go index e6f2c31cf..ece194fb2 100644 --- a/internal/gitaly/service/commit/testhelper_test.go +++ b/internal/gitaly/service/commit/testhelper_test.go @@ -2,7 +2,6 @@ package commit import ( "io" - "os" "testing" "github.com/stretchr/testify/require" @@ -18,14 +17,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } // setupCommitService makes a basic configuration and starts the service with the client. diff --git a/internal/gitaly/service/conflicts/server.go b/internal/gitaly/service/conflicts/server.go index 63f1279ee..0bd08481c 100644 --- a/internal/gitaly/service/conflicts/server.go +++ b/internal/gitaly/service/conflicts/server.go @@ -27,19 +27,23 @@ type server struct { } // NewServer creates a new instance of a grpc ConflictsServer -func NewServer(cfg config.Cfg, hookManager hook.Manager, locator storage.Locator, gitCmdFactory git.CommandFactory, catfileCache catfile.Cache) gitalypb.ConflictsServiceServer { +func NewServer( + cfg config.Cfg, + hookManager hook.Manager, + locator storage.Locator, + gitCmdFactory git.CommandFactory, + catfileCache catfile.Cache, + connsPool *client.Pool, +) gitalypb.ConflictsServiceServer { return &server{ cfg: cfg, hookManager: hookManager, locator: locator, gitCmdFactory: gitCmdFactory, catfileCache: catfileCache, - pool: client.NewPoolWithOptions( - client.WithDialer(client.HealthCheckDialer(client.DialContext)), - client.WithDialOptions(client.FailOnNonTempDialError()...), - ), - updater: updateref.NewUpdaterWithHooks(cfg, hookManager, gitCmdFactory, catfileCache), - git2go: git2go.NewExecutor(cfg, locator), + pool: connsPool, + updater: updateref.NewUpdaterWithHooks(cfg, hookManager, gitCmdFactory, catfileCache), + git2go: git2go.NewExecutor(cfg, locator), } } diff --git a/internal/gitaly/service/conflicts/testhelper_test.go b/internal/gitaly/service/conflicts/testhelper_test.go index f85a2adc1..31792b01c 100644 --- a/internal/gitaly/service/conflicts/testhelper_test.go +++ b/internal/gitaly/service/conflicts/testhelper_test.go @@ -1,11 +1,8 @@ package conflicts import ( - "os" - "path/filepath" "testing" - log "github.com/sirupsen/logrus" "gitlab.com/gitlab-org/gitaly/v14/internal/git/gittest" "gitlab.com/gitlab-org/gitaly/v14/internal/git/hooks" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config" @@ -23,30 +20,10 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - tempDir, err := os.MkdirTemp("", "gitaly") - if err != nil { - log.Error(err) - return 1 - } - defer func() { - if err := os.RemoveAll(tempDir); err != nil { - log.Error(err) - } - }() - - defer func(old string) { hooks.Override = old }(hooks.Override) - hooks.Override = filepath.Join(tempDir, "hooks") - - return m.Run() + testhelper.Run(m, testhelper.WithSetup(func() error { + hooks.Override = "/" + return nil + })) } func SetupConfigAndRepo(t testing.TB, bare bool) (config.Cfg, *gitalypb.Repository, string) { @@ -87,6 +64,7 @@ func runConflictsServer(t testing.TB, cfg config.Cfg, hookManager hook.Manager) deps.GetLocator(), deps.GetGitCmdFactory(), deps.GetCatfileCache(), + deps.GetConnsPool(), )) gitalypb.RegisterRepositoryServiceServer(srv, repository.NewServer( deps.GetCfg(), @@ -95,6 +73,7 @@ func runConflictsServer(t testing.TB, cfg config.Cfg, hookManager hook.Manager) deps.GetTxManager(), deps.GetGitCmdFactory(), deps.GetCatfileCache(), + deps.GetConnsPool(), )) gitalypb.RegisterSSHServiceServer(srv, ssh.NewServer( deps.GetCfg(), diff --git a/internal/gitaly/service/diff/testhelper_test.go b/internal/gitaly/service/diff/testhelper_test.go index 1029cd3ed..06c12df5f 100644 --- a/internal/gitaly/service/diff/testhelper_test.go +++ b/internal/gitaly/service/diff/testhelper_test.go @@ -1,7 +1,6 @@ package diff import ( - "os" "testing" "github.com/stretchr/testify/require" @@ -15,14 +14,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func setupDiffService(t testing.TB, opt ...testserver.GitalyServerOpt) (config.Cfg, *gitalypb.Repository, string, gitalypb.DiffServiceClient) { diff --git a/internal/gitaly/service/hook/testhelper_test.go b/internal/gitaly/service/hook/testhelper_test.go index 35f7f416e..935a15d24 100644 --- a/internal/gitaly/service/hook/testhelper_test.go +++ b/internal/gitaly/service/hook/testhelper_test.go @@ -1,7 +1,6 @@ package hook import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config" @@ -15,14 +14,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func setupHookService(t testing.TB) (config.Cfg, *gitalypb.Repository, string, gitalypb.HookServiceClient) { diff --git a/internal/gitaly/service/internalgitaly/testhelper_test.go b/internal/gitaly/service/internalgitaly/testhelper_test.go index d768d2d80..b1ba1d050 100644 --- a/internal/gitaly/service/internalgitaly/testhelper_test.go +++ b/internal/gitaly/service/internalgitaly/testhelper_test.go @@ -1,7 +1,6 @@ package internalgitaly import ( - "os" "testing" "github.com/stretchr/testify/require" @@ -14,14 +13,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func setupInternalGitalyService(t *testing.T, cfg config.Cfg, internalService gitalypb.InternalGitalyServer) gitalypb.InternalGitalyClient { diff --git a/internal/gitaly/service/namespace/namespace_test.go b/internal/gitaly/service/namespace/namespace_test.go index 8adab217a..7657e3b28 100644 --- a/internal/gitaly/service/namespace/namespace_test.go +++ b/internal/gitaly/service/namespace/namespace_test.go @@ -15,16 +15,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } func TestNamespaceExists(t *testing.T) { diff --git a/internal/gitaly/service/objectpool/fetch_into_object_pool_test.go b/internal/gitaly/service/objectpool/fetch_into_object_pool_test.go index 9b68557e1..ba48dd00c 100644 --- a/internal/gitaly/service/objectpool/fetch_into_object_pool_test.go +++ b/internal/gitaly/service/objectpool/fetch_into_object_pool_test.go @@ -162,11 +162,14 @@ func TestFetchIntoObjectPool_Failure(t *testing.T) { locator := config.NewLocator(cfg) gitCmdFactory := git.NewExecCommandFactory(cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + server := NewServer( cfg, locator, gitCmdFactory, - catfile.NewCache(cfg), + catfileCache, transaction.NewManager(cfg, backchannel.NewRegistry()), ) diff --git a/internal/gitaly/service/objectpool/testhelper_test.go b/internal/gitaly/service/objectpool/testhelper_test.go index 8cdf1d180..86b85adcc 100644 --- a/internal/gitaly/service/objectpool/testhelper_test.go +++ b/internal/gitaly/service/objectpool/testhelper_test.go @@ -25,14 +25,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func setup(t *testing.T, opts ...testserver.GitalyServerOpt) (config.Cfg, *gitalypb.Repository, string, storage.Locator, gitalypb.ObjectPoolServiceClient) { @@ -76,12 +69,14 @@ func initObjectPool(t testing.TB, cfg config.Cfg, storage config.Storage) *objec relativePath := gittest.NewObjectPoolName(t) gittest.InitRepoDir(t, storage.Path, relativePath) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) pool, err := objectpool.NewObjectPool( cfg, config.NewLocator(cfg), git.NewExecCommandFactory(cfg), - catfile.NewCache(cfg), + catfileCache, transaction.NewManager(cfg, backchannel.NewRegistry()), storage.Name, relativePath, diff --git a/internal/gitaly/service/operations/apply_patch_test.go b/internal/gitaly/service/operations/apply_patch_test.go index bc128093c..ba6666b35 100644 --- a/internal/gitaly/service/operations/apply_patch_test.go +++ b/internal/gitaly/service/operations/apply_patch_test.go @@ -279,7 +279,10 @@ To restore the original branch and stop patching, run "git am --abort". t.Run(tc.desc, func(t *testing.T) { repoPb, repoPath := gittest.InitRepo(t, cfg, cfg.Storages[0]) - repo := localrepo.New(git.NewExecCommandFactory(cfg), catfile.NewCache(cfg), repoPb, cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + + repo := localrepo.New(git.NewExecCommandFactory(cfg), catfileCache, repoPb, cfg) executor := git2go.NewExecutor(cfg, config.NewLocator(cfg)) diff --git a/internal/gitaly/service/operations/testhelper_test.go b/internal/gitaly/service/operations/testhelper_test.go index b93c8b00e..3e8996740 100644 --- a/internal/gitaly/service/operations/testhelper_test.go +++ b/internal/gitaly/service/operations/testhelper_test.go @@ -2,7 +2,6 @@ package operations import ( "context" - "os" "testing" "github.com/stretchr/testify/require" @@ -32,21 +31,9 @@ var ( GitlabHooks []string ) -func init() { - GitlabHooks = append(GitlabHooks, append(gitlabPreHooks, gitlabPostHooks...)...) -} - func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + GitlabHooks = append(GitlabHooks, append(gitlabPreHooks, gitlabPostHooks...)...) + testhelper.Run(m) } func setupOperationsService(t testing.TB, ctx context.Context, options ...testserver.GitalyServerOpt) (context.Context, config.Cfg, *gitalypb.Repository, string, gitalypb.OperationServiceClient) { @@ -103,6 +90,7 @@ func runOperationServiceServer(t testing.TB, cfg config.Cfg, rubySrv *rubyserver deps.GetTxManager(), deps.GetGitCmdFactory(), deps.GetCatfileCache(), + deps.GetConnsPool(), )) gitalypb.RegisterRefServiceServer(srv, ref.NewServer( deps.GetCfg(), diff --git a/internal/gitaly/service/ref/find_all_tags_test.go b/internal/gitaly/service/ref/find_all_tags_test.go index 628edc948..86aebd495 100644 --- a/internal/gitaly/service/ref/find_all_tags_test.go +++ b/internal/gitaly/service/ref/find_all_tags_test.go @@ -399,6 +399,8 @@ func TestFindAllTags_nestedTags(t *testing.T) { testhelper.MustRunCommand(t, tags, "xargs", cfg.Git.BinPath, "-C", repoPath, "tag", "-d") catfileCache := catfile.NewCache(cfg) + defer catfileCache.Stop() + batch, err := catfileCache.BatchProcess(ctx, repo) require.NoError(t, err) @@ -506,7 +508,10 @@ func TestFindAllTags_sorted(t *testing.T) { repoProto, _ := gittest.CloneRepo(t, cfg, cfg.Storages[0]) - repo := localrepo.New(git.NewExecCommandFactory(cfg), catfile.NewCache(cfg), repoProto, cfg) + catfileCache := catfile.NewCache(cfg) + defer catfileCache.Stop() + + repo := localrepo.New(git.NewExecCommandFactory(cfg), catfileCache, repoProto, cfg) headCommit, err := repo.ReadCommit(ctx, "HEAD") require.NoError(t, err) annotatedTagID, err := repo.WriteTag(ctx, git.ObjectID(headCommit.Id), "commit", []byte("annotated"), []byte("message"), gittest.TestUser, time.Now()) diff --git a/internal/gitaly/service/ref/refs_test.go b/internal/gitaly/service/ref/refs_test.go index 381a368ec..d1540f1a0 100644 --- a/internal/gitaly/service/ref/refs_test.go +++ b/internal/gitaly/service/ref/refs_test.go @@ -1081,6 +1081,8 @@ func TestFindTagNestedTag(t *testing.T) { testhelper.MustRunCommand(t, tags, "xargs", cfg.Git.BinPath, "-C", repoPath, "tag", "-d") catfileCache := catfile.NewCache(cfg) + defer catfileCache.Stop() + batch, err := catfileCache.BatchProcess(ctx, repo) require.NoError(t, err) diff --git a/internal/gitaly/service/ref/testhelper_test.go b/internal/gitaly/service/ref/testhelper_test.go index 89f71285e..f530ca28a 100644 --- a/internal/gitaly/service/ref/testhelper_test.go +++ b/internal/gitaly/service/ref/testhelper_test.go @@ -2,7 +2,6 @@ package ref import ( "bytes" - "os" "testing" "github.com/stretchr/testify/require" @@ -26,20 +25,12 @@ var localBranches = map[string]*gitalypb.GitCommit{ } func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - // Force small messages to test that fragmenting the - // ref list works correctly - lines.ItemsPerMessage = 3 - - return m.Run() + testhelper.Run(m, testhelper.WithSetup(func() error { + // Force small messages to test that fragmenting the + // ref list works correctly + lines.ItemsPerMessage = 3 + return nil + })) } func setupRefService(t testing.TB) (config.Cfg, *gitalypb.Repository, string, gitalypb.RefServiceClient) { diff --git a/internal/gitaly/service/remote/server.go b/internal/gitaly/service/remote/server.go index d466af948..a44c96916 100644 --- a/internal/gitaly/service/remote/server.go +++ b/internal/gitaly/service/remote/server.go @@ -30,6 +30,7 @@ func NewServer( gitCmdFactory git.CommandFactory, catfileCache catfile.Cache, txManager transaction.Manager, + connsPool *client.Pool, ) gitalypb.RemoteServiceServer { return &server{ cfg: cfg, @@ -37,10 +38,7 @@ func NewServer( gitCmdFactory: gitCmdFactory, catfileCache: catfileCache, txManager: txManager, - conns: client.NewPoolWithOptions( - client.WithDialer(client.HealthCheckDialer(client.DialContext)), - client.WithDialOptions(client.FailOnNonTempDialError()...), - ), + conns: connsPool, } } diff --git a/internal/gitaly/service/remote/testhelper_test.go b/internal/gitaly/service/remote/testhelper_test.go index 35285044f..06eaa916e 100644 --- a/internal/gitaly/service/remote/testhelper_test.go +++ b/internal/gitaly/service/remote/testhelper_test.go @@ -1,7 +1,6 @@ package remote import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/git/gittest" @@ -15,16 +14,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } func setupRemoteService(t *testing.T, opts ...testserver.GitalyServerOpt) (config.Cfg, *gitalypb.Repository, string, gitalypb.RemoteServiceClient) { @@ -41,6 +31,7 @@ func setupRemoteService(t *testing.T, opts ...testserver.GitalyServerOpt) (confi deps.GetGitCmdFactory(), deps.GetCatfileCache(), deps.GetTxManager(), + deps.GetConnsPool(), )) }, opts...) cfg.SocketPath = addr diff --git a/internal/gitaly/service/remote/update_remote_mirror_test.go b/internal/gitaly/service/remote/update_remote_mirror_test.go index e56171609..7eeb8128f 100644 --- a/internal/gitaly/service/remote/update_remote_mirror_test.go +++ b/internal/gitaly/service/remote/update_remote_mirror_test.go @@ -564,6 +564,7 @@ func TestUpdateRemoteMirror(t *testing.T) { cmdFactory, deps.GetCatfileCache(), deps.GetTxManager(), + deps.GetConnsPool(), )) }) @@ -632,6 +633,7 @@ func TestSuccessfulUpdateRemoteMirrorRequest(t *testing.T) { deps.GetGitCmdFactory(), deps.GetCatfileCache(), deps.GetTxManager(), + deps.GetConnsPool(), )) }) @@ -734,6 +736,7 @@ func TestSuccessfulUpdateRemoteMirrorRequestWithWildcards(t *testing.T) { deps.GetGitCmdFactory(), deps.GetCatfileCache(), deps.GetTxManager(), + deps.GetConnsPool(), )) }) @@ -817,6 +820,7 @@ func TestUpdateRemoteMirrorInmemory(t *testing.T) { deps.GetGitCmdFactory(), deps.GetCatfileCache(), deps.GetTxManager(), + deps.GetConnsPool(), )) }) @@ -865,6 +869,7 @@ func TestSuccessfulUpdateRemoteMirrorRequestWithKeepDivergentRefs(t *testing.T) deps.GetGitCmdFactory(), deps.GetCatfileCache(), deps.GetTxManager(), + deps.GetConnsPool(), )) }) @@ -952,6 +957,7 @@ func TestFailedUpdateRemoteMirrorRequestDueToValidation(t *testing.T) { deps.GetGitCmdFactory(), deps.GetCatfileCache(), deps.GetTxManager(), + deps.GetConnsPool(), )) }) diff --git a/internal/gitaly/service/repository/apply_gitattributes_test.go b/internal/gitaly/service/repository/apply_gitattributes_test.go index f85b6f456..c71b48b80 100644 --- a/internal/gitaly/service/repository/apply_gitattributes_test.go +++ b/internal/gitaly/service/repository/apply_gitattributes_test.go @@ -105,6 +105,7 @@ func TestApplyGitattributesWithTransaction(t *testing.T) { deps.GetTxManager(), deps.GetGitCmdFactory(), deps.GetCatfileCache(), + deps.GetConnsPool(), )) }) diff --git a/internal/gitaly/service/repository/cleanup_test.go b/internal/gitaly/service/repository/cleanup_test.go index 3e8b2ce0b..c5d53b73f 100644 --- a/internal/gitaly/service/repository/cleanup_test.go +++ b/internal/gitaly/service/repository/cleanup_test.go @@ -153,7 +153,10 @@ func TestRemoveWorktree(t *testing.T) { cfg, repoProto, repoPath := testcfg.BuildWithRepo(t) gitCmdFactory := git.NewExecCommandFactory(cfg) - repo := localrepo.New(gitCmdFactory, catfile.NewCache(cfg), repoProto, cfg) + catfileCache := catfile.NewCache(cfg) + defer catfileCache.Stop() + + repo := localrepo.New(gitCmdFactory, catfileCache, repoProto, cfg) existingWorktreePath := filepath.Join(repoPath, worktreePrefix, "existing") gittest.AddWorktree(t, cfg, repoPath, existingWorktreePath) diff --git a/internal/gitaly/service/repository/clone_from_pool_internal_test.go b/internal/gitaly/service/repository/clone_from_pool_internal_test.go index 3a99242c7..72f17d230 100644 --- a/internal/gitaly/service/repository/clone_from_pool_internal_test.go +++ b/internal/gitaly/service/repository/clone_from_pool_internal_test.go @@ -26,12 +26,14 @@ func newTestObjectPool(t *testing.T, cfg config.Cfg) (*objectpool.ObjectPool, *g repo := gittest.InitRepoDir(t, cfg.Storages[0].Path, relativePath) gitCmdFactory := git.NewExecCommandFactory(cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) pool, err := objectpool.NewObjectPool( cfg, config.NewLocator(cfg), gitCmdFactory, - catfile.NewCache(cfg), + catfileCache, transaction.NewManager(cfg, backchannel.NewRegistry()), repo.GetStorageName(), relativePath, diff --git a/internal/gitaly/service/repository/clone_from_pool_test.go b/internal/gitaly/service/repository/clone_from_pool_test.go index 7a6623554..41de34186 100644 --- a/internal/gitaly/service/repository/clone_from_pool_test.go +++ b/internal/gitaly/service/repository/clone_from_pool_test.go @@ -37,7 +37,8 @@ func TestCloneFromPoolHTTP(t *testing.T) { defer forkRepoCleanup() authorizationHeader := "ABCefg0999182" - _, remoteURL := gittest.RemoteUploadPackServer(ctx, t, cfg.Git.BinPath, "my-repo", authorizationHeader, repoPath) + server, remoteURL := gittest.RemoteUploadPackServer(ctx, t, cfg.Git.BinPath, "my-repo", authorizationHeader, repoPath) + defer server.Close() req := &gitalypb.CloneFromPoolRequest{ Repository: forkedRepo, diff --git a/internal/gitaly/service/repository/fetch_bundle_test.go b/internal/gitaly/service/repository/fetch_bundle_test.go index a81889000..25b2d9961 100644 --- a/internal/gitaly/service/repository/fetch_bundle_test.go +++ b/internal/gitaly/service/repository/fetch_bundle_test.go @@ -83,6 +83,7 @@ func TestServer_FetchBundle_transaction(t *testing.T) { deps.GetTxManager(), deps.GetGitCmdFactory(), deps.GetCatfileCache(), + deps.GetConnsPool(), )) gitalypb.RegisterHookServiceServer(srv, hook.NewServer( deps.GetCfg(), diff --git a/internal/gitaly/service/repository/fetch_remote_test.go b/internal/gitaly/service/repository/fetch_remote_test.go index a64a51d87..85bcc3254 100644 --- a/internal/gitaly/service/repository/fetch_remote_test.go +++ b/internal/gitaly/service/repository/fetch_remote_test.go @@ -209,6 +209,7 @@ func TestFetchRemote_transaction(t *testing.T) { deps.GetTxManager(), deps.GetGitCmdFactory(), deps.GetCatfileCache(), + deps.GetConnsPool(), )) }, testserver.WithTransactionManager(txManager)) diff --git a/internal/gitaly/service/repository/fork_test.go b/internal/gitaly/service/repository/fork_test.go index dba63ffa2..092ec2e07 100644 --- a/internal/gitaly/service/repository/fork_test.go +++ b/internal/gitaly/service/repository/fork_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth" - gclient "gitlab.com/gitlab-org/gitaly/v14/client" + "gitlab.com/gitlab-org/gitaly/v14/client" "gitlab.com/gitlab-org/gitaly/v14/internal/backchannel" "gitlab.com/gitlab-org/gitaly/v14/internal/cache" "gitlab.com/gitlab-org/gitaly/v14/internal/git" @@ -129,7 +129,7 @@ func newSecureRepoClient(t testing.TB, addr, token string, pool *x509.CertPool) grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2(token)), } - conn, err := gclient.Dial(addr, connOpts) + conn, err := client.Dial(addr, connOpts) require.NoError(t, err) return gitalypb.NewRepositoryServiceClient(conn), conn @@ -226,10 +226,14 @@ func runSecureServer(t *testing.T, cfg config.Cfg, rubySrv *rubyserver.Server) s ), cfg) gitCmdFactory := git.NewExecCommandFactory(cfg) catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) - gitalypb.RegisterRepositoryServiceServer(server, NewServer(cfg, rubySrv, locator, txManager, gitCmdFactory, catfileCache)) + connsPool := client.NewPool() + t.Cleanup(func() { testhelper.MustClose(t, connsPool) }) + + gitalypb.RegisterRepositoryServiceServer(server, NewServer(cfg, rubySrv, locator, txManager, gitCmdFactory, catfileCache, connsPool)) gitalypb.RegisterHookServiceServer(server, hookservice.NewServer(cfg, hookManager, gitCmdFactory, nil)) - gitalypb.RegisterRemoteServiceServer(server, remote.NewServer(cfg, locator, gitCmdFactory, catfileCache, txManager)) + gitalypb.RegisterRemoteServiceServer(server, remote.NewServer(cfg, locator, gitCmdFactory, catfileCache, txManager, connsPool)) gitalypb.RegisterSSHServiceServer(server, ssh.NewServer(cfg, locator, gitCmdFactory, txManager)) gitalypb.RegisterRefServiceServer(server, ref.NewServer(cfg, locator, gitCmdFactory, txManager, catfileCache)) gitalypb.RegisterCommitServiceServer(server, commit.NewServer(cfg, locator, gitCmdFactory, nil, catfileCache)) diff --git a/internal/gitaly/service/repository/search_files_test.go b/internal/gitaly/service/repository/search_files_test.go index 096485610..25bd6c17c 100644 --- a/internal/gitaly/service/repository/search_files_test.go +++ b/internal/gitaly/service/repository/search_files_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/v14/client" "gitlab.com/gitlab-org/gitaly/v14/internal/backchannel" "gitlab.com/gitlab-org/gitaly/v14/internal/git" "gitlab.com/gitlab-org/gitaly/v14/internal/git/catfile" @@ -207,6 +208,11 @@ func TestSearchFilesByContentFailure(t *testing.T) { t.Parallel() cfg, repo, _ := testcfg.BuildWithRepo(t) gitCommandFactory := git.NewExecCommandFactory(cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + + connsPool := client.NewPool() + defer testhelper.MustClose(t, connsPool) server := NewServer( cfg, @@ -214,7 +220,8 @@ func TestSearchFilesByContentFailure(t *testing.T) { config.NewLocator(cfg), transaction.NewManager(cfg, backchannel.NewRegistry()), gitCommandFactory, - catfile.NewCache(cfg), + catfileCache, + connsPool, ) testCases := []struct { @@ -330,6 +337,11 @@ func TestSearchFilesByNameFailure(t *testing.T) { t.Parallel() cfg := testcfg.Build(t) gitCommandFactory := git.NewExecCommandFactory(cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) + + connsPool := client.NewPool() + defer testhelper.MustClose(t, connsPool) server := NewServer( cfg, @@ -337,7 +349,8 @@ func TestSearchFilesByNameFailure(t *testing.T) { config.NewLocator(cfg), transaction.NewManager(cfg, backchannel.NewRegistry()), gitCommandFactory, - catfile.NewCache(cfg), + catfileCache, + connsPool, ) testCases := []struct { diff --git a/internal/gitaly/service/repository/server.go b/internal/gitaly/service/repository/server.go index e248c70d7..d211325e5 100644 --- a/internal/gitaly/service/repository/server.go +++ b/internal/gitaly/service/repository/server.go @@ -36,21 +36,19 @@ func NewServer( txManager transaction.Manager, gitCmdFactory git.CommandFactory, catfileCache catfile.Cache, + connsPool *client.Pool, ) gitalypb.RepositoryServiceServer { return &server{ ruby: rs, locator: locator, txManager: txManager, gitCmdFactory: gitCmdFactory, - conns: client.NewPoolWithOptions( - client.WithDialer(client.HealthCheckDialer(client.DialContext)), - client.WithDialOptions(client.FailOnNonTempDialError()...), - ), - cfg: cfg, - binDir: cfg.BinDir, - loggingCfg: cfg.Logging, - catfileCache: catfileCache, - git2go: git2go.NewExecutor(cfg, locator), + conns: connsPool, + cfg: cfg, + binDir: cfg.BinDir, + loggingCfg: cfg.Logging, + catfileCache: catfileCache, + git2go: git2go.NewExecutor(cfg, locator), } } diff --git a/internal/gitaly/service/repository/snapshot_test.go b/internal/gitaly/service/repository/snapshot_test.go index 973eb93b6..a4727957a 100644 --- a/internal/gitaly/service/repository/snapshot_test.go +++ b/internal/gitaly/service/repository/snapshot_test.go @@ -133,6 +133,7 @@ func TestGetSnapshotWithDedupe(t *testing.T) { locator := config.NewLocator(cfg) catfileCache := catfile.NewCache(cfg) + defer catfileCache.Stop() // ensure commit cannot be found in current repository c, err := catfileCache.BatchProcess(ctx, repo) diff --git a/internal/gitaly/service/repository/testhelper_test.go b/internal/gitaly/service/repository/testhelper_test.go index a50e3a7da..1e33f5ff9 100644 --- a/internal/gitaly/service/repository/testhelper_test.go +++ b/internal/gitaly/service/repository/testhelper_test.go @@ -35,16 +35,7 @@ const testTimeString = "200601021504.05" var testTime = time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } func TestWithRubySidecar(t *testing.T) { @@ -124,6 +115,7 @@ func runRepositoryServerWithConfig(t testing.TB, cfg config.Cfg, rubySrv *rubyse deps.GetTxManager(), deps.GetGitCmdFactory(), deps.GetCatfileCache(), + deps.GetConnsPool(), )) gitalypb.RegisterHookServiceServer(srv, hookservice.NewServer(cfg, deps.GetHookManager(), deps.GetGitCmdFactory(), deps.GetPackObjectsCache())) gitalypb.RegisterRemoteServiceServer(srv, remote.NewServer( @@ -132,6 +124,7 @@ func runRepositoryServerWithConfig(t testing.TB, cfg config.Cfg, rubySrv *rubyse deps.GetGitCmdFactory(), deps.GetCatfileCache(), deps.GetTxManager(), + deps.GetConnsPool(), )) gitalypb.RegisterSSHServiceServer(srv, ssh.NewServer( cfg, diff --git a/internal/gitaly/service/server/server_test.go b/internal/gitaly/service/server/server_test.go index 95fca6a02..b2570c260 100644 --- a/internal/gitaly/service/server/server_test.go +++ b/internal/gitaly/service/server/server_test.go @@ -1,19 +1,11 @@ package server import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/gitaly/service/setup/register.go b/internal/gitaly/service/setup/register.go index 67286ffea..39010ff36 100644 --- a/internal/gitaly/service/setup/register.go +++ b/internal/gitaly/service/setup/register.go @@ -102,6 +102,7 @@ func RegisterAll(srv *grpc.Server, deps *service.Dependencies) { deps.GetTxManager(), deps.GetGitCmdFactory(), deps.GetCatfileCache(), + deps.GetConnsPool(), )) gitalypb.RegisterSSHServiceServer(srv, ssh.NewServer( deps.GetCfg(), @@ -124,6 +125,7 @@ func RegisterAll(srv *grpc.Server, deps *service.Dependencies) { deps.GetLocator(), deps.GetGitCmdFactory(), deps.GetCatfileCache(), + deps.GetConnsPool(), )) gitalypb.RegisterRemoteServiceServer(srv, remote.NewServer( deps.GetCfg(), @@ -131,6 +133,7 @@ func RegisterAll(srv *grpc.Server, deps *service.Dependencies) { deps.GetGitCmdFactory(), deps.GetCatfileCache(), deps.GetTxManager(), + deps.GetConnsPool(), )) gitalypb.RegisterServerServiceServer(srv, server.NewServer(deps.GetGitCmdFactory(), deps.GetCfg().Storages)) gitalypb.RegisterObjectPoolServiceServer(srv, objectpool.NewServer( diff --git a/internal/gitaly/service/smarthttp/inforefs_test.go b/internal/gitaly/service/smarthttp/inforefs_test.go index 1746aca5d..d2ccaeb37 100644 --- a/internal/gitaly/service/smarthttp/inforefs_test.go +++ b/internal/gitaly/service/smarthttp/inforefs_test.go @@ -121,7 +121,8 @@ func TestSuccessfulInfoRefsUploadPackWithGitProtocol(t *testing.T) { GitProtocol: git.ProtocolV2, } - client, _ := newSmartHTTPClient(t, serverSocketPath, cfg.Auth.Token) + client, conn := newSmartHTTPClient(t, serverSocketPath, cfg.Auth.Token) + defer testhelper.MustClose(t, conn) ctx, cancel := testhelper.Context() defer cancel() diff --git a/internal/gitaly/service/smarthttp/testhelper_test.go b/internal/gitaly/service/smarthttp/testhelper_test.go index 3b4ef7cfc..3a6d1b70d 100644 --- a/internal/gitaly/service/smarthttp/testhelper_test.go +++ b/internal/gitaly/service/smarthttp/testhelper_test.go @@ -2,7 +2,6 @@ package smarthttp import ( "context" - "os" "testing" "github.com/stretchr/testify/require" @@ -23,16 +22,7 @@ const ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } func startSmartHTTPServer(t *testing.T, cfg config.Cfg, serverOpts ...ServerOpt) testserver.GitalyServer { diff --git a/internal/gitaly/service/ssh/testhelper_test.go b/internal/gitaly/service/ssh/testhelper_test.go index 7c5349c32..45ab030bf 100644 --- a/internal/gitaly/service/ssh/testhelper_test.go +++ b/internal/gitaly/service/ssh/testhelper_test.go @@ -1,7 +1,6 @@ package ssh import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config" @@ -14,16 +13,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } func runSSHServer(t *testing.T, cfg config.Cfg, serverOpts ...testserver.GitalyServerOpt) string { diff --git a/internal/gitaly/service/wiki/testhelper_test.go b/internal/gitaly/service/wiki/testhelper_test.go index 9302d6b14..247fd9ac8 100644 --- a/internal/gitaly/service/wiki/testhelper_test.go +++ b/internal/gitaly/service/wiki/testhelper_test.go @@ -2,12 +2,10 @@ package wiki import ( "bytes" - "os" "reflect" "runtime" "testing" - log "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly/v14/internal/git" "gitlab.com/gitlab-org/gitaly/v14/internal/git/gittest" @@ -33,29 +31,10 @@ type createWikiPageOpts struct { var mockPageContent = bytes.Repeat([]byte("Mock wiki page content"), 10000) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - tempDir, err := os.MkdirTemp("", "gitaly") - if err != nil { - log.Error(err) - return 1 - } - defer func() { - if err := os.RemoveAll(tempDir); err != nil { - log.Error(err) - } - }() - - hooks.Override = tempDir + "/hooks" - - return m.Run() + testhelper.Run(m, testhelper.WithSetup(func() error { + hooks.Override = "/" + return nil + })) } func TestWithRubySidecar(t *testing.T) { diff --git a/internal/gitaly/transaction/testhelper_test.go b/internal/gitaly/transaction/testhelper_test.go index 314059345..3e17bd91e 100644 --- a/internal/gitaly/transaction/testhelper_test.go +++ b/internal/gitaly/transaction/testhelper_test.go @@ -1,21 +1,11 @@ package transaction import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } diff --git a/internal/gitalyssh/testhelper_test.go b/internal/gitalyssh/testhelper_test.go index 1f17e47a4..6af673482 100644 --- a/internal/gitalyssh/testhelper_test.go +++ b/internal/gitalyssh/testhelper_test.go @@ -1,19 +1,11 @@ package gitalyssh import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/gitlab/testhelper_test.go b/internal/gitlab/testhelper_test.go index f8a245a2d..69a859a86 100644 --- a/internal/gitlab/testhelper_test.go +++ b/internal/gitlab/testhelper_test.go @@ -1,15 +1,11 @@ package gitlab import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - os.Exit(m.Run()) + testhelper.Run(m) } diff --git a/internal/helper/chunk/chunker_test.go b/internal/helper/chunk/chunker_test.go index c7f91fd85..7f13050d8 100644 --- a/internal/helper/chunk/chunker_test.go +++ b/internal/helper/chunk/chunker_test.go @@ -3,7 +3,6 @@ package chunk import ( "io" "net" - "os" "testing" "github.com/stretchr/testify/require" @@ -15,14 +14,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } type testSender struct { diff --git a/internal/helper/repo_test.go b/internal/helper/repo_test.go index fb5961c52..c65665131 100644 --- a/internal/helper/repo_test.go +++ b/internal/helper/repo_test.go @@ -1,7 +1,6 @@ package helper import ( - "os" "testing" "github.com/stretchr/testify/assert" @@ -10,14 +9,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func TestRepoPathEqual(t *testing.T) { diff --git a/internal/middleware/commandstatshandler/commandstatshandler_test.go b/internal/middleware/commandstatshandler/commandstatshandler_test.go index 2d923095e..d474744bd 100644 --- a/internal/middleware/commandstatshandler/commandstatshandler_test.go +++ b/internal/middleware/commandstatshandler/commandstatshandler_test.go @@ -25,6 +25,10 @@ import ( "google.golang.org/grpc/test/bufconn" ) +func TestMain(m *testing.M) { + testhelper.Run(m) +} + func createNewServer(t *testing.T, cfg config.Cfg) *grpc.Server { logger := testhelper.NewTestLogger(t) logrusEntry := logrus.NewEntry(logger).WithField("test", t.Name()) @@ -47,13 +51,15 @@ func createNewServer(t *testing.T, cfg config.Cfg) *grpc.Server { server := grpc.NewServer(opts...) gitCommandFactory := git.NewExecCommandFactory(cfg) + catfileCache := catfile.NewCache(cfg) + t.Cleanup(catfileCache.Stop) gitalypb.RegisterRefServiceServer(server, ref.NewServer( cfg, config.NewLocator(cfg), gitCommandFactory, transaction.NewManager(cfg, backchannel.NewRegistry()), - catfile.NewCache(cfg), + catfileCache, )) return server @@ -66,9 +72,6 @@ func getBufDialer(listener *bufconn.Listener) func(context.Context, string) (net } func TestInterceptor(t *testing.T) { - cleanup := testhelper.Configure() - defer cleanup() - cfg, repo, _ := testcfg.BuildWithRepo(t) logBuffer := &bytes.Buffer{} diff --git a/internal/middleware/limithandler/limithandler_test.go b/internal/middleware/limithandler/limithandler_test.go index 743bb9d17..84df9f150 100644 --- a/internal/middleware/limithandler/limithandler_test.go +++ b/internal/middleware/limithandler/limithandler_test.go @@ -3,7 +3,6 @@ package limithandler_test import ( "context" "net" - "os" "sync" "testing" "time" @@ -17,14 +16,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } func fixedLockKey(ctx context.Context) string { diff --git a/internal/praefect/auth_test.go b/internal/praefect/auth_test.go index bafa7bfda..29c8a989b 100644 --- a/internal/praefect/auth_test.go +++ b/internal/praefect/auth_test.go @@ -153,6 +153,7 @@ func runServer(t *testing.T, token string, required bool) (*grpc.Server, string, nodeMgr, err := nodes.NewManager(logEntry, conf, nil, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) + defer nodeMgr.Stop() txMgr := transactions.NewManager(conf) diff --git a/internal/praefect/coordinator_test.go b/internal/praefect/coordinator_test.go index adf4c6262..664428fb9 100644 --- a/internal/praefect/coordinator_test.go +++ b/internal/praefect/coordinator_test.go @@ -311,6 +311,7 @@ func TestStreamDirectorMutator_StopTransaction(t *testing.T) { nodeMgr, err := nodes.NewManager(testhelper.DiscardTestEntry(t), conf, nil, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(0, time.Hour) + defer nodeMgr.Stop() ctx, cancel := testhelper.Context() defer cancel() @@ -442,6 +443,7 @@ func TestStreamDirectorAccessor(t *testing.T) { nodeMgr, err := nodes.NewManager(entry, conf, nil, rs, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(0, time.Minute) + defer nodeMgr.Stop() txMgr := transactions.NewManager(conf) @@ -553,6 +555,7 @@ func TestCoordinatorStreamDirector_distributesReads(t *testing.T) { nodeMgr, err := nodes.NewManager(entry, conf, nil, repoStore, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(0, time.Minute) + defer nodeMgr.Stop() txMgr := transactions.NewManager(conf) @@ -875,6 +878,7 @@ func TestStreamDirector_repo_creation(t *testing.T) { nodeMgr, err := nodes.NewManager(testhelper.DiscardTestEntry(t), conf, nil, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(0, time.Hour) + defer nodeMgr.Stop() router = NewNodeManagerRouter(nodeMgr, rs) for _, node := range nodeMgr.Nodes()["praefect"] { @@ -1077,6 +1081,7 @@ func TestAbsentCorrelationID(t *testing.T) { nodeMgr, err := nodes.NewManager(entry, conf, nil, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(0, time.Hour) + defer nodeMgr.Stop() txMgr := transactions.NewManager(conf) rs := datastore.MockRepositoryStore{} @@ -1211,6 +1216,7 @@ func TestStreamDirectorStorageScope(t *testing.T) { nodeMgr, err := nodes.NewManager(testhelper.DiscardTestEntry(t), conf, nil, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(0, time.Second) + defer nodeMgr.Stop() coordinator := NewCoordinator( nil, rs, diff --git a/internal/praefect/datastore/glsql/postgres.go b/internal/praefect/datastore/glsql/postgres.go index aa814af5c..f9f3eb951 100644 --- a/internal/praefect/datastore/glsql/postgres.go +++ b/internal/praefect/datastore/glsql/postgres.go @@ -20,6 +20,7 @@ func OpenDB(conf config.DB) (*sql.DB, error) { } if err := db.Ping(); err != nil { + db.Close() return nil, err } diff --git a/internal/praefect/grpc-proxy/proxy/handler_ext_test.go b/internal/praefect/grpc-proxy/proxy/handler_ext_test.go index 0aaea9cfe..35a11f150 100644 --- a/internal/praefect/grpc-proxy/proxy/handler_ext_test.go +++ b/internal/praefect/grpc-proxy/proxy/handler_ext_test.go @@ -12,7 +12,6 @@ import ( "net/http" "net/http/httptest" "net/url" - "os" "path/filepath" "testing" "time" @@ -49,18 +48,7 @@ const ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer func() { - testhelper.MustHaveNoChildProcess() - testhelper.MustHaveNoGoroutines() - }() - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } // asserting service is implemented on the server side and serves as a handler for stuff diff --git a/internal/praefect/helper_test.go b/internal/praefect/helper_test.go index 96e12c32b..597532f56 100644 --- a/internal/praefect/helper_test.go +++ b/internal/praefect/helper_test.go @@ -129,6 +129,7 @@ func defaultNodeMgr(t testing.TB, conf config.Config, rs datastore.RepositorySto nodeMgr, err := nodes.NewManager(testhelper.DiscardTestEntry(t), conf, nil, rs, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(0, time.Hour) + t.Cleanup(nodeMgr.Stop) return nodeMgr } diff --git a/internal/praefect/info_service_test.go b/internal/praefect/info_service_test.go index 7d1710ffd..36ea0f286 100644 --- a/internal/praefect/info_service_test.go +++ b/internal/praefect/info_service_test.go @@ -40,6 +40,7 @@ func TestInfoService_RepositoryReplicas(t *testing.T) { deps.GetTxManager(), deps.GetGitCmdFactory(), deps.GetCatfileCache(), + deps.GetConnsPool(), )) }, testserver.WithDisablePraefect()) cfgNodes = append(cfgNodes, &config.Node{ @@ -63,6 +64,7 @@ func TestInfoService_RepositoryReplicas(t *testing.T) { nodeManager, err := nodes.NewManager(testhelper.DiscardTestEntry(t), conf, nil, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeManager.Start(0, time.Hour) + defer nodeManager.Stop() cc, _, cleanup := runPraefectServer(t, ctx, conf, buildOptions{ withPrimaryGetter: nodeManager, withConnections: NodeSetFromNodeManager(nodeManager).Connections(), diff --git a/internal/praefect/middleware/errorhandler_test.go b/internal/praefect/middleware/errorhandler_test.go index fada40963..f85deea77 100644 --- a/internal/praefect/middleware/errorhandler_test.go +++ b/internal/praefect/middleware/errorhandler_test.go @@ -75,6 +75,7 @@ func TestStreamInterceptor(t *testing.T) { grpc.WithStreamInterceptor(StreamErrorHandler(registry, errTracker, nodeName)), ) require.NoError(t, err) + t.Cleanup(func() { testhelper.MustClose(t, cc) }) f, err := peeker.Peek() require.NoError(t, err) return proxy.NewStreamParameters(proxy.Destination{Conn: cc, Ctx: ctx, Msg: f}, nil, func() error { return nil }, nil), nil @@ -90,6 +91,7 @@ func TestStreamInterceptor(t *testing.T) { go praefectSrv.Serve(praefectLis) praefectCC, err := grpc.Dial("unix://"+praefectSocket, grpc.WithInsecure()) + defer testhelper.MustClose(t, praefectCC) require.NoError(t, err) simpleClient := mock.NewSimpleServiceClient(praefectCC) diff --git a/internal/praefect/middleware/helper_test.go b/internal/praefect/middleware/helper_test.go index e40f293dd..b733100c0 100644 --- a/internal/praefect/middleware/helper_test.go +++ b/internal/praefect/middleware/helper_test.go @@ -1,19 +1,11 @@ package middleware import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/praefect/nodes/disabled_elector.go b/internal/praefect/nodes/disabled_elector.go index 740632c47..047e41857 100644 --- a/internal/praefect/nodes/disabled_elector.go +++ b/internal/praefect/nodes/disabled_elector.go @@ -40,6 +40,9 @@ func (de *disabledElector) start(bootstrap, _ time.Duration) { de.updateMetrics() } +func (de *disabledElector) stop() { +} + func (de *disabledElector) updateMetrics() { metrics.PrimaryGauge.WithLabelValues(de.virtualStorage, de.shard.Primary.GetStorage()).Set(1) for _, n := range de.shard.Secondaries { diff --git a/internal/praefect/nodes/init_test.go b/internal/praefect/nodes/init_test.go index 678b275ea..1475680ed 100644 --- a/internal/praefect/nodes/init_test.go +++ b/internal/praefect/nodes/init_test.go @@ -1,19 +1,11 @@ package nodes import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) (code int) { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/praefect/nodes/local_elector.go b/internal/praefect/nodes/local_elector.go index 122ec5c16..bea1ef68b 100644 --- a/internal/praefect/nodes/local_elector.go +++ b/internal/praefect/nodes/local_elector.go @@ -19,6 +19,8 @@ type localElector struct { nodes []Node primaryNode Node log logrus.FieldLogger + + doneCh chan struct{} } func newLocalElector(name string, log logrus.FieldLogger, ns []*nodeStatus) *localElector { @@ -31,6 +33,7 @@ func newLocalElector(name string, log logrus.FieldLogger, ns []*nodeStatus) *loc log: log.WithField("virtual_storage", name), nodes: nodes[:], primaryNode: nodes[0], + doneCh: make(chan struct{}), } } @@ -61,7 +64,11 @@ func (s *localElector) monitor(d time.Duration) { ctx := context.Background() for { - <-ticker.C + select { + case <-s.doneCh: + return + case <-ticker.C: + } err := s.checkNodes(ctx) if err != nil { @@ -70,6 +77,10 @@ func (s *localElector) monitor(d time.Duration) { } } +func (s *localElector) stop() { + close(s.doneCh) +} + // checkNodes issues a gRPC health check for each node managed by the // shard. func (s *localElector) checkNodes(ctx context.Context) error { diff --git a/internal/praefect/nodes/local_elector_test.go b/internal/praefect/nodes/local_elector_test.go index 778e85613..2976c32ee 100644 --- a/internal/praefect/nodes/local_elector_test.go +++ b/internal/praefect/nodes/local_elector_test.go @@ -20,6 +20,7 @@ func setupElector(t *testing.T) (*localElector, []*nodeStatus, *grpc.ClientConn) "unix://"+socket, grpc.WithInsecure(), ) + t.Cleanup(func() { testhelper.MustClose(t, cc) }) require.NoError(t, err) diff --git a/internal/praefect/nodes/manager.go b/internal/praefect/nodes/manager.go index 2bfd9fb54..e40bf161e 100644 --- a/internal/praefect/nodes/manager.go +++ b/internal/praefect/nodes/manager.go @@ -107,6 +107,7 @@ type Mgr struct { // secondaries are managed. type leaderElectionStrategy interface { start(bootstrapInterval, monitorInterval time.Duration) + stop() checkNodes(context.Context) error GetShard(ctx context.Context) (Shard, error) } @@ -208,6 +209,19 @@ func (n *Mgr) Start(bootstrapInterval, monitorInterval time.Duration) { } } +// Stop will stop all monitoring processes and closes connections. Must only be called once. +func (n *Mgr) Stop() { + for _, strategy := range n.strategies { + strategy.stop() + } + + for _, nodes := range n.nodes { + for _, node := range nodes { + _ = node.GetConnection().Close() + } + } +} + // checkShards performs health checks on all the available shards. The // election strategy is responsible for determining the criteria for // when to elect a new primary and when a node is down. diff --git a/internal/praefect/nodes/manager_test.go b/internal/praefect/nodes/manager_test.go index 51e064ff5..2060c7fcc 100644 --- a/internal/praefect/nodes/manager_test.go +++ b/internal/praefect/nodes/manager_test.go @@ -63,6 +63,7 @@ func TestNodeStatus(t *testing.T) { "unix://"+socket, grpc.WithInsecure(), ) + defer testhelper.MustClose(t, cc) require.NoError(t, err) @@ -122,6 +123,7 @@ func TestManagerFailoverDisabledElectionStrategySQL(t *testing.T) { require.NoError(t, err) nm.Start(time.Millisecond, time.Millisecond) + defer nm.Stop() ctx, cancel := testhelper.Context() defer cancel() @@ -170,6 +172,7 @@ func TestDialWithUnhealthyNode(t *testing.T) { require.NoError(t, err) mgr.Start(1*time.Millisecond, 1*time.Millisecond) + defer mgr.Stop() ctx, cancel := testhelper.Context() defer cancel() @@ -222,7 +225,10 @@ func TestNodeManager(t *testing.T) { // monitoring period set to 1 hour as we execute health checks by hands in this test nm.Start(0, time.Hour) + defer nm.Stop() + nmWithoutFailover.Start(0, time.Hour) + defer nmWithoutFailover.Stop() ctx, cancel := testhelper.Context() defer cancel() @@ -339,6 +345,7 @@ func TestMgr_GetSyncedNode(t *testing.T) { healthSrvs[i].SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING) } nm.Start(0, time.Hour) + t.Cleanup(nm.Stop) return func(t *testing.T) { scenario(t, nm, rs) } } diff --git a/internal/praefect/nodes/sql_elector.go b/internal/praefect/nodes/sql_elector.go index 6d8b7bfef..a0007c855 100644 --- a/internal/praefect/nodes/sql_elector.go +++ b/internal/praefect/nodes/sql_elector.go @@ -85,6 +85,7 @@ type sqlElector struct { db *sql.DB log logrus.FieldLogger failoverTimeout time.Duration + doneCh chan struct{} } func newSQLElector(name string, c config.Config, db *sql.DB, log logrus.FieldLogger, ns []*nodeStatus) *sqlElector { @@ -107,6 +108,7 @@ func newSQLElector(name string, c config.Config, db *sql.DB, log logrus.FieldLog nodes: nodes, primaryNode: nodes[0], failoverTimeout: failoverTimeout, + doneCh: make(chan struct{}), } } @@ -137,6 +139,10 @@ func (s *sqlElector) start(bootstrapInterval, monitorInterval time.Duration) { go s.monitor(monitorInterval) } +func (s *sqlElector) stop() { + close(s.doneCh) +} + func (s *sqlElector) bootstrap(d time.Duration) { ctx := context.Background() s.checkNodes(ctx) @@ -149,7 +155,11 @@ func (s *sqlElector) monitor(d time.Duration) { ctx := context.Background() for { - <-ticker.C + select { + case <-s.doneCh: + return + case <-ticker.C: + } s.checkNodes(ctx) } } diff --git a/internal/praefect/nodes/sql_elector_test.go b/internal/praefect/nodes/sql_elector_test.go index f88369aa9..b86651e5b 100644 --- a/internal/praefect/nodes/sql_elector_test.go +++ b/internal/praefect/nodes/sql_elector_test.go @@ -47,6 +47,7 @@ func TestGetPrimaryAndSecondaries(t *testing.T) { "unix://"+internalSocket0, grpc.WithInsecure(), ) + defer testhelper.MustClose(t, cc0) require.NoError(t, err) storageName := "default" @@ -85,6 +86,7 @@ func TestSqlElector_slow_execution(t *testing.T) { "unix://"+gitalySocket, grpc.WithInsecure(), ) + defer testhelper.MustClose(t, gitalyConn) require.NoError(t, err) gitalyNodeStatus := newConnectionStatus(config.Node{Storage: "gitaly", Address: "gitaly-address"}, gitalyConn, logger, promtest.NewMockHistogramVec(), nil) @@ -128,6 +130,7 @@ func TestBasicFailover(t *testing.T) { addr0, grpc.WithInsecure(), ) + defer testhelper.MustClose(t, cc0) require.NoError(t, err) addr1 := "unix://" + internalSocket1 @@ -135,6 +138,7 @@ func TestBasicFailover(t *testing.T) { addr1, grpc.WithInsecure(), ) + defer testhelper.MustClose(t, cc1) require.NoError(t, err) @@ -483,6 +487,7 @@ func TestConnectionMultiplexing(t *testing.T) { nil, ) require.NoError(t, err) + defer mgr.Stop() // check the shard to get the primary in a healthy state mgr.checkShards() diff --git a/internal/praefect/replicator_pg_test.go b/internal/praefect/replicator_pg_test.go index d790a88c3..a1eccbed3 100644 --- a/internal/praefect/replicator_pg_test.go +++ b/internal/praefect/replicator_pg_test.go @@ -48,6 +48,7 @@ func TestReplicatorInvalidSourceRepository(t *testing.T) { targetCC, err := client.Dial(ln.Addr().Network()+":"+ln.Addr().String(), nil) require.NoError(t, err) + defer testhelper.MustClose(t, targetCC) rs := datastore.NewPostgresRepositoryStore(glsql.NewDB(t), nil) require.NoError(t, rs.SetGeneration(ctx, "virtual-storage-1", "relative-path-1", "gitaly-1", 0)) diff --git a/internal/praefect/replicator_test.go b/internal/praefect/replicator_test.go index bf6abf37d..35646c33a 100644 --- a/internal/praefect/replicator_test.go +++ b/internal/praefect/replicator_test.go @@ -2,7 +2,6 @@ package praefect import ( "context" - "os" "path/filepath" "strings" "sync" @@ -46,16 +45,7 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } func TestReplMgr_ProcessBacklog(t *testing.T) { @@ -127,6 +117,7 @@ func TestReplMgr_ProcessBacklog(t *testing.T) { nodeMgr, err := nodes.NewManager(entry, conf, nil, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(1*time.Millisecond, 5*time.Millisecond) + defer nodeMgr.Stop() shard, err := nodeMgr.GetShard(ctx, conf.VirtualStorages[0].Name) require.NoError(t, err) @@ -331,6 +322,7 @@ func TestReplicator_PropagateReplicationJob(t *testing.T) { nodeMgr, err := nodes.NewManager(logEntry, conf, nil, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(0, time.Hour) + defer nodeMgr.Stop() txMgr := transactions.NewManager(conf) @@ -710,6 +702,7 @@ func TestProcessBacklog_FailedJobs(t *testing.T) { nodeMgr, err := nodes.NewManager(logEntry, conf, nil, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(0, time.Hour) + defer nodeMgr.Stop() replMgr := NewReplMgr( logEntry, @@ -860,6 +853,7 @@ func TestProcessBacklog_Success(t *testing.T) { nodeMgr, err := nodes.NewManager(logEntry, conf, nil, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(0, time.Hour) + defer nodeMgr.Stop() replMgr := NewReplMgr( logEntry, diff --git a/internal/praefect/repocleaner/init_test.go b/internal/praefect/repocleaner/init_test.go index 011d4f2a4..7b5501eab 100644 --- a/internal/praefect/repocleaner/init_test.go +++ b/internal/praefect/repocleaner/init_test.go @@ -1,19 +1,11 @@ package repocleaner import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) (code int) { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/praefect/repository_exists_test.go b/internal/praefect/repository_exists_test.go index cf66210f2..39a25a71d 100644 --- a/internal/praefect/repository_exists_test.go +++ b/internal/praefect/repository_exists_test.go @@ -108,6 +108,7 @@ func TestRepositoryExistsHandler(t *testing.T) { clientConn, err := grpc.DialContext(ctx, "unix://"+ln.Addr().String(), grpc.WithInsecure()) require.NoError(t, err) + defer testhelper.MustClose(t, clientConn) client := gitalypb.NewRepositoryServiceClient(clientConn) _, err = client.RepositorySize(ctx, &gitalypb.RepositorySizeRequest{Repository: tc.repository}) diff --git a/internal/praefect/server_factory_test.go b/internal/praefect/server_factory_test.go index a6aea62a8..aba6ac118 100644 --- a/internal/praefect/server_factory_test.go +++ b/internal/praefect/server_factory_test.go @@ -80,6 +80,7 @@ func TestServerFactory(t *testing.T) { nodeMgr, err := nodes.NewManager(logger, conf, nil, rs, &promtest.MockHistogramVec{}, protoregistry.GitalyProtoPreregistered, nil, clientHandshaker, sidechannelRegistry) require.NoError(t, err) nodeMgr.Start(0, time.Second) + defer nodeMgr.Stop() registry := protoregistry.GitalyProtoPreregistered coordinator := NewCoordinator( diff --git a/internal/praefect/server_test.go b/internal/praefect/server_test.go index 163c13d4f..20933e5fe 100644 --- a/internal/praefect/server_test.go +++ b/internal/praefect/server_test.go @@ -546,6 +546,7 @@ func TestRemoveRepository(t *testing.T) { ) require.NoError(t, err) nodeMgr.Start(0, time.Hour) + defer nodeMgr.Stop() ctx, cancel := testhelper.Context() defer cancel() @@ -812,6 +813,7 @@ func TestProxyWrites(t *testing.T) { nodeMgr, err := nodes.NewManager(entry, conf, nil, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) require.NoError(t, err) nodeMgr.Start(0, time.Hour) + defer nodeMgr.Stop() ctx, cancel := testhelper.Context() defer cancel() @@ -978,6 +980,7 @@ func TestErrorThreshold(t *testing.T) { rs := datastore.MockRepositoryStore{} nodeMgr, err := nodes.NewManager(entry, conf, nil, rs, promtest.NewMockHistogramVec(), registry, errorTracker, nil, nil) require.NoError(t, err) + defer nodeMgr.Stop() coordinator := NewCoordinator( queue, @@ -1002,6 +1005,7 @@ func TestErrorThreshold(t *testing.T) { conn, err := dial("unix://"+socket, []grpc.DialOption{grpc.WithInsecure()}) require.NoError(t, err) + defer testhelper.MustClose(t, conn) cli := mock.NewSimpleServiceClient(conn) _, repo, _ := testcfg.BuildWithRepo(t) @@ -1045,6 +1049,7 @@ func newSmartHTTPClient(t *testing.T, serverSocketPath string) (gitalypb.SmartHT conn, err := grpc.Dial(serverSocketPath, grpc.WithInsecure()) require.NoError(t, err) + t.Cleanup(func() { testhelper.MustClose(t, conn) }) return gitalypb.NewSmartHTTPServiceClient(conn), conn } diff --git a/internal/safe/main_test.go b/internal/safe/main_test.go index 3a903c513..7158dca83 100644 --- a/internal/safe/main_test.go +++ b/internal/safe/main_test.go @@ -1,19 +1,11 @@ package safe_test import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - return m.Run() + testhelper.Run(m) } diff --git a/internal/streamcache/cache.go b/internal/streamcache/cache.go index ffeca32ce..0ea6ca3f0 100644 --- a/internal/streamcache/cache.go +++ b/internal/streamcache/cache.go @@ -122,18 +122,19 @@ type cache struct { stopOnce sync.Once logger logrus.FieldLogger dir string + sleepLoop *dontpanic.Forever } // New returns a new cache instance. func New(cfg config.StreamCacheConfig, logger logrus.FieldLogger) Cache { if cfg.Enabled { - return newCacheWithSleep(cfg.Dir, cfg.MaxAge.Duration(), time.Sleep, logger) + return newCacheWithSleep(cfg.Dir, cfg.MaxAge.Duration(), time.After, logger) } return NullCache{} } -func newCacheWithSleep(dir string, maxAge time.Duration, sleep func(time.Duration), logger logrus.FieldLogger) Cache { +func newCacheWithSleep(dir string, maxAge time.Duration, sleep func(time.Duration) <-chan time.Time, logger logrus.FieldLogger) Cache { fs := newFilestore(dir, maxAge, sleep, logger) c := &cache{ @@ -143,13 +144,15 @@ func newCacheWithSleep(dir string, maxAge time.Duration, sleep func(time.Duratio stop: make(chan struct{}), logger: logger, dir: dir, + sleepLoop: dontpanic.NewForever(time.Minute), } - dontpanic.GoForever(1*time.Minute, func() { + c.sleepLoop.Go(func() { sleepLoop(c.stop, c.maxAge, sleep, c.clean) }) go func() { <-c.stop + c.sleepLoop.Cancel() fs.Stop() }() @@ -353,19 +356,17 @@ func (w *waiter) Wait(ctx context.Context) error { } } -func sleepLoop(done chan struct{}, period time.Duration, sleep func(time.Duration), callback func()) { +func sleepLoop(done chan struct{}, period time.Duration, sleep func(time.Duration) <-chan time.Time, callback func()) { const maxPeriod = time.Minute if period <= 0 || period >= maxPeriod { period = maxPeriod } for { - sleep(period) - select { case <-done: return - default: + case <-sleep(period): } callback() diff --git a/internal/streamcache/cache_test.go b/internal/streamcache/cache_test.go index 708d09fd9..f562dad42 100644 --- a/internal/streamcache/cache_test.go +++ b/internal/streamcache/cache_test.go @@ -10,7 +10,6 @@ import ( "os" "path/filepath" "strings" - "sync" "testing" "time" @@ -221,35 +220,6 @@ func TestCache_scope(t *testing.T) { } } -type clock struct { - n int - sync.Mutex - *sync.Cond -} - -func newClock() *clock { - cl := &clock{} - cl.Cond = sync.NewCond(cl) - return cl -} - -func (cl *clock) wait() { - cl.Lock() - defer cl.Unlock() - - for old := cl.n; old == cl.n; { - cl.Cond.Wait() - } -} - -func (cl *clock) advance() { - cl.Lock() - defer cl.Unlock() - - cl.n++ - cl.Cond.Broadcast() -} - func TestCache_diskCleanup(t *testing.T) { tmp := testhelper.TempDir(t) @@ -257,8 +227,11 @@ func TestCache_diskCleanup(t *testing.T) { key = "test key" ) - cl := newClock() - c := newCacheWithSleep(tmp, 0, func(time.Duration) { cl.wait() }, log.Default()) + timerCh := make(chan time.Time) + + c := newCacheWithSleep(tmp, 0, func(time.Duration) <-chan time.Time { + return timerCh + }, log.Default()) defer c.Stop() content := func(i int) string { return fmt.Sprintf("content %d", i) } @@ -278,7 +251,7 @@ func TestCache_diskCleanup(t *testing.T) { requireCacheEntries(t, c, 1) // Unblock cleanup goroutines so they run exactly once - cl.advance() + close(timerCh) // Give them time to do their work time.Sleep(10 * time.Millisecond) diff --git a/internal/streamcache/filestore.go b/internal/streamcache/filestore.go index 67337c95f..8933bc127 100644 --- a/internal/streamcache/filestore.go +++ b/internal/streamcache/filestore.go @@ -56,16 +56,19 @@ type filestore struct { id []byte counter uint64 stop chan struct{} + + sleepLoop *dontpanic.Forever } -func newFilestore(dir string, maxAge time.Duration, sleep func(time.Duration), logger logrus.FieldLogger) *filestore { +func newFilestore(dir string, maxAge time.Duration, sleep func(time.Duration) <-chan time.Time, logger logrus.FieldLogger) *filestore { fs := &filestore{ - dir: dir, - maxAge: maxAge, - stop: make(chan struct{}), + dir: dir, + maxAge: maxAge, + stop: make(chan struct{}), + sleepLoop: dontpanic.NewForever(time.Minute), } - dontpanic.GoForever(1*time.Minute, func() { + fs.sleepLoop.Go(func() { sleepLoop(fs.stop, fs.maxAge, sleep, func() { diskUsageGauge.WithLabelValues(fs.dir).Set(fs.diskUsage()) @@ -146,6 +149,8 @@ func (fs *filestore) Stop() { default: close(fs.stop) } + + fs.sleepLoop.Cancel() } // cleanWalk removes files but not directories. This is to avoid races diff --git a/internal/streamcache/filestore_test.go b/internal/streamcache/filestore_test.go index b5c1f055e..cf8a29506 100644 --- a/internal/streamcache/filestore_test.go +++ b/internal/streamcache/filestore_test.go @@ -16,7 +16,7 @@ import ( func TestFilestoreCreate(t *testing.T) { tmp := testhelper.TempDir(t) - fs := newFilestore(tmp, 0, time.Sleep, log.Default()) + fs := newFilestore(tmp, 0, time.After, log.Default()) defer fs.Stop() f, err := fs.Create() @@ -39,7 +39,7 @@ func TestFilestoreCreate(t *testing.T) { func TestFilestoreCreate_concurrency(t *testing.T) { tmp := testhelper.TempDir(t) - fs := newFilestore(tmp, time.Hour, time.Sleep, log.Default()) + fs := newFilestore(tmp, time.Hour, time.After, log.Default()) defer fs.Stop() const N = 100 @@ -81,7 +81,7 @@ func TestFilestoreCreate_uniqueness(t *testing.T) { filenames := make(map[string]struct{}) for j := 0; j < M; j++ { - fs := newFilestore(tmp, time.Hour, time.Sleep, log.Default()) + fs := newFilestore(tmp, time.Hour, time.After, log.Default()) defer fs.Stop() for i := 0; i < N; i++ { @@ -101,7 +101,7 @@ func TestFilestoreCreate_uniqueness(t *testing.T) { func TestFilestoreCleanwalk(t *testing.T) { tmp := testhelper.TempDir(t) - fs := newFilestore(tmp, time.Hour, time.Sleep, log.Default()) + fs := newFilestore(tmp, time.Hour, time.After, log.Default()) defer fs.Stop() dir1 := filepath.Join(tmp, "dir1") diff --git a/internal/streamcache/testhelper_test.go b/internal/streamcache/testhelper_test.go index 1427567b4..03bcc2acf 100644 --- a/internal/streamcache/testhelper_test.go +++ b/internal/streamcache/testhelper_test.go @@ -1,15 +1,11 @@ package streamcache import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) -func TestMain(m *testing.M) { os.Exit(testMain(m)) } - -func testMain(m *testing.M) int { - defer testhelper.Configure()() - return m.Run() +func TestMain(m *testing.M) { + testhelper.Run(m) } diff --git a/internal/supervisor/monitor.go b/internal/supervisor/monitor.go index 08a112cd4..2e6a3dc71 100644 --- a/internal/supervisor/monitor.go +++ b/internal/supervisor/monitor.go @@ -93,6 +93,10 @@ func monitorHealth(f func() error, events chan<- Event, name string, shutdown <- return } - time.Sleep(15 * time.Second) + select { + case <-shutdown: + return + case <-time.After(15 * time.Second): + } } } diff --git a/internal/supervisor/supervisor_test.go b/internal/supervisor/supervisor_test.go index 9e2642d57..ccd8c1a93 100644 --- a/internal/supervisor/supervisor_test.go +++ b/internal/supervisor/supervisor_test.go @@ -12,7 +12,6 @@ import ( "testing" "time" - log "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) @@ -24,45 +23,32 @@ var ( ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() + testhelper.Run(m, testhelper.WithSetup(func() error { + var err error + testDir, err = os.MkdirTemp("", "gitaly-supervisor-test") + if err != nil { + return err + } - var err error - testDir, err = os.MkdirTemp("", "gitaly-supervisor-test") - if err != nil { - log.Error(err) - return 1 - } - defer func() { - if err := os.RemoveAll(testDir); err != nil { - log.Error(err) + scriptPath, err := filepath.Abs("test-scripts/pid-server.go") + if err != nil { + return err } - }() - scriptPath, err := filepath.Abs("test-scripts/pid-server.go") - if err != nil { - log.Error(err) - return 1 - } + testExe = filepath.Join(testDir, "pid-server") - testExe = filepath.Join(testDir, "pid-server") - buildCmd := exec.Command("go", "build", "-o", testExe, scriptPath) - buildCmd.Dir = filepath.Dir(scriptPath) - buildCmd.Stderr = os.Stderr - buildCmd.Stdout = os.Stdout - if err := buildCmd.Run(); err != nil { - log.Error(err) - return 1 - } + buildCmd := exec.Command("go", "build", "-o", testExe, scriptPath) + buildCmd.Dir = filepath.Dir(scriptPath) + buildCmd.Stderr = os.Stderr + buildCmd.Stdout = os.Stdout + if err := buildCmd.Run(); err != nil { + return err + } - socketPath = filepath.Join(testDir, "socket") + socketPath = filepath.Join(testDir, "socket") - return m.Run() + return nil + })) } func TestRespawnAfterCrashWithoutCircuitBreaker(t *testing.T) { diff --git a/internal/tempdir/testhelper_test.go b/internal/tempdir/testhelper_test.go index 6c4397185..cdc9dc00e 100644 --- a/internal/tempdir/testhelper_test.go +++ b/internal/tempdir/testhelper_test.go @@ -1,20 +1,11 @@ package tempdir import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } diff --git a/internal/testhelper/configure.go b/internal/testhelper/configure.go index ea993b678..f9319c42a 100644 --- a/internal/testhelper/configure.go +++ b/internal/testhelper/configure.go @@ -8,6 +8,7 @@ import ( "runtime" "strings" "sync" + "testing" log "github.com/sirupsen/logrus" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config" @@ -19,35 +20,102 @@ var ( testDirectory string ) -// Configure sets up the global test configuration. On failure, +// RunOption is an option that can be passed to Run. +type RunOption func(*runConfig) + +type runConfig struct { + setup func() error + disableGoroutineChecks bool +} + +// WithSetup allows the caller of Run to pass a setup function that will be called after global +// test state has been configured. +func WithSetup(setup func() error) RunOption { + return func(cfg *runConfig) { + cfg.setup = setup + } +} + +// WithDisabledGoroutineChecker disables checking for leaked Goroutines after tests have run. This +// should ideally only be used as a temporary measure until all Goroutine leaks have been fixed. +// +// Deprecated: This should not be used, but instead you should try to fix all Goroutine leakages. +func WithDisabledGoroutineChecker() RunOption { + return func(cfg *runConfig) { + cfg.disableGoroutineChecks = true + } +} + +// Run sets up required testing state and executes the given test suite. It can optionally receive a +// variable number of RunOptions. +func Run(m *testing.M, opts ...RunOption) { + // Run tests in a separate function such that we can use deferred statements and still + // (indirectly) call `os.Exit()` in case the test setup failed. + if err := func() error { + var cfg runConfig + for _, opt := range opts { + opt(&cfg) + } + + defer mustHaveNoChildProcess() + if !cfg.disableGoroutineChecks { + defer mustHaveNoGoroutines() + } + + cleanup, err := configure() + if err != nil { + return err + } + defer cleanup() + + if cfg.setup != nil { + if err := cfg.setup(); err != nil { + return fmt.Errorf("error calling setup function: %w", err) + } + } + + m.Run() + + return nil + }(); err != nil { + log.Fatalf("%v", err) + } +} + +// configure sets up the global test configuration. On failure, // terminates the program. -func Configure() func() { +func configure() (func(), error) { + var returnedErr error configureOnce.Do(func() { gitalylog.Configure(gitalylog.Loggers, "json", "panic") testDirectory = getTestTmpDir() for _, f := range []func() error{ - ConfigureGit, + configureGit, } { - if err := f(); err != nil { + if returnedErr = f(); returnedErr != nil { if err := os.RemoveAll(testDirectory); err != nil { log.Error(err) } - log.Fatalf("error configuring tests: %v", err) + + return } } }) + if returnedErr != nil { + return nil, returnedErr + } return func() { if err := os.RemoveAll(testDirectory); err != nil { - log.Fatalf("error removing test directory: %v", err) + log.Errorf("error removing test directory: %v", err) } - } + }, nil } -// ConfigureGit configures git for test purpose -func ConfigureGit() error { +// configureGit configures git for test purpose +func configureGit() error { // We cannot use gittest here given that we ain't got no config yet. We thus need to // manually resolve the git executable, which is either stored in below envvar if // executed via our Makefile, or else just git as resolved via PATH. @@ -108,7 +176,7 @@ func ConfigureRuby(cfg *config.Cfg) error { } if err := cfg.ConfigureRuby(); err != nil { - log.Fatalf("validate ruby config: %v", err) + return fmt.Errorf("validate ruby config: %w", err) } return nil diff --git a/internal/testhelper/leakage.go b/internal/testhelper/leakage.go new file mode 100644 index 000000000..946b867d2 --- /dev/null +++ b/internal/testhelper/leakage.go @@ -0,0 +1,99 @@ +package testhelper + +import ( + "fmt" + "os" + "os/exec" + "strings" + "syscall" + "time" + + "gitlab.com/gitlab-org/gitaly/v14/internal/command/commandcounter" + "gitlab.com/gitlab-org/gitaly/v14/internal/helper/text" + "go.uber.org/goleak" +) + +// mustHaveNoGoroutines panics if it finds any Goroutines running. +func mustHaveNoGoroutines() { + if err := goleak.Find( + // opencensus has a "defaultWorker" which is started by the package's + // `init()` function. There is no way to stop this worker, so it will leak + // whenever we import the package. + goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"), + // The Ruby server's load balancer is registered in the `init()` function + // of our "rubyserver/balancer" package. Ideally we'd clean this up + // eventually, but the pragmatic approach is to just wait until we remove + // the Ruby sidecar altogether. + goleak.IgnoreTopFunction("google.golang.org/grpc.(*ccBalancerWrapper).watcher"), + goleak.IgnoreTopFunction("gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/rubyserver/balancer.(*builder).monitor"), + // labkit's logger spawns a Goroutine which cannot be closed when calling + // `Initialize()`. + goleak.IgnoreTopFunction("gitlab.com/gitlab-org/labkit/log.listenForSignalHangup"), + // The backchannel code is somehow stock on closing its connections. I have no clue + // why that is, but we should investigate. + goleak.IgnoreTopFunction("gitlab.com/gitlab-org/gitaly/v14/internal/backchannel.clientHandshake.serve.func4"), + ); err != nil { + panic(fmt.Errorf("goroutines running: %w", err)) + } +} + +// mustHaveNoChildProcess panics if it finds a running or finished child +// process. It waits for 2 seconds for processes to be cleaned up by other +// goroutines. +func mustHaveNoChildProcess() { + waitDone := make(chan struct{}) + go func() { + commandcounter.WaitAllDone() + close(waitDone) + }() + + select { + case <-waitDone: + case <-time.After(2 * time.Second): + } + + if err := mustFindNoFinishedChildProcess(); err != nil { + panic(err) + } + + if err := mustFindNoRunningChildProcess(); err != nil { + panic(err) + } +} + +func mustFindNoFinishedChildProcess() error { + // Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, err error) + // + // We use pid -1 to wait for any child. We don't care about wstatus or + // rusage. Use WNOHANG to return immediately if there is no child waiting + // to be reaped. + wpid, err := syscall.Wait4(-1, nil, syscall.WNOHANG, nil) + if err == nil && wpid > 0 { + return fmt.Errorf("wait4 found child process %d", wpid) + } + + return nil +} + +func mustFindNoRunningChildProcess() error { + pgrep := exec.Command("pgrep", "-P", fmt.Sprintf("%d", os.Getpid())) + desc := fmt.Sprintf("%q", strings.Join(pgrep.Args, " ")) + + out, err := pgrep.Output() + if err == nil { + pidsComma := strings.Replace(text.ChompBytes(out), "\n", ",", -1) + psOut, _ := exec.Command("ps", "-o", "pid,args", "-p", pidsComma).Output() + return fmt.Errorf("found running child processes %s:\n%s", pidsComma, psOut) + } + + exitError, ok := err.(*exec.ExitError) + if !ok { + return fmt.Errorf("expected ExitError, got %T", err) + } + + if exitError.ExitCode() == 1 { + return nil + } + + return fmt.Errorf("%s: %w", desc, err) +} diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go index 30bd253ac..7a52ce3ee 100644 --- a/internal/testhelper/testhelper.go +++ b/internal/testhelper/testhelper.go @@ -16,8 +16,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" - "syscall" "testing" "time" @@ -25,12 +23,9 @@ import ( log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gitlab.com/gitlab-org/gitaly/v14/internal/command" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/storage" - "gitlab.com/gitlab-org/gitaly/v14/internal/helper/text" "gitlab.com/gitlab-org/gitaly/v14/internal/metadata/featureflag" - "go.uber.org/goleak" "google.golang.org/grpc/metadata" ) @@ -165,68 +160,6 @@ func GetLocalhostListener(t testing.TB) (net.Listener, string) { return l, addr } -// MustHaveNoGoroutines panics if it finds any Goroutines running. -func MustHaveNoGoroutines() { - if err := goleak.Find( - // opencensus has a "defaultWorker" which is started by the package's - // `init()` function. There is no way to stop this worker, so it will leak - // whenever we import the package. - goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"), - ); err != nil { - panic(fmt.Errorf("goroutines running: %w", err)) - } -} - -// MustHaveNoChildProcess panics if it finds a running or finished child -// process. It waits for 2 seconds for processes to be cleaned up by other -// goroutines. -func MustHaveNoChildProcess() { - waitDone := make(chan struct{}) - go func() { - command.WaitAllDone() - close(waitDone) - }() - - select { - case <-waitDone: - case <-time.After(2 * time.Second): - } - - mustFindNoFinishedChildProcess() - mustFindNoRunningChildProcess() -} - -func mustFindNoFinishedChildProcess() { - // Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, err error) - // - // We use pid -1 to wait for any child. We don't care about wstatus or - // rusage. Use WNOHANG to return immediately if there is no child waiting - // to be reaped. - wpid, err := syscall.Wait4(-1, nil, syscall.WNOHANG, nil) - if err == nil && wpid > 0 { - panic(fmt.Errorf("wait4 found child process %d", wpid)) - } -} - -func mustFindNoRunningChildProcess() { - pgrep := exec.Command("pgrep", "-P", fmt.Sprintf("%d", os.Getpid())) - desc := fmt.Sprintf("%q", strings.Join(pgrep.Args, " ")) - - out, err := pgrep.Output() - if err == nil { - pidsComma := strings.Replace(text.ChompBytes(out), "\n", ",", -1) - psOut, _ := exec.Command("ps", "-o", "pid,args", "-p", pidsComma).Output() - panic(fmt.Errorf("found running child processes %s:\n%s", pidsComma, psOut)) - } - - if status, ok := command.ExitStatus(err); ok && status == 1 { - // Exit status 1 means no processes were found - return - } - - panic(fmt.Errorf("%s: %w", desc, err)) -} - // ContextOpt returns a new context instance with the new additions to it. type ContextOpt func(context.Context) (context.Context, func()) diff --git a/internal/testhelper/testserver/gitaly.go b/internal/testhelper/testserver/gitaly.go index 7dac962f3..cf7bf7b04 100644 --- a/internal/testhelper/testserver/gitaly.go +++ b/internal/testhelper/testserver/gitaly.go @@ -350,6 +350,7 @@ func (gsd *gitalyServerDeps) createDependencies(t testing.TB, cfg config.Cfg, ru if gsd.packObjectsCache == nil { gsd.packObjectsCache = streamcache.New(cfg.PackObjectsCache, gsd.logger) + t.Cleanup(gsd.packObjectsCache.Stop) } return &service.Dependencies{ diff --git a/internal/transaction/voting/testhelper_test.go b/internal/transaction/voting/testhelper_test.go index 0c8359afd..59ee6c4a8 100644 --- a/internal/transaction/voting/testhelper_test.go +++ b/internal/transaction/voting/testhelper_test.go @@ -1,21 +1,11 @@ package voting import ( - "os" "testing" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" ) func TestMain(m *testing.M) { - os.Exit(testMain(m)) -} - -func testMain(m *testing.M) int { - defer testhelper.MustHaveNoChildProcess() - - cleanup := testhelper.Configure() - defer cleanup() - - return m.Run() + testhelper.Run(m) } |