Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions core/repomanager/repo_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ type Params struct {
// appCtx is the application-lifetime context. Cancel it when the process is
// shutting down (e.g. wire it to SIGTERM/SIGINT in main) to abort any
// background goroutines the manager spawns.
func NewRepoManager(appCtx context.Context, p Params) RepoManager {
func NewRepoManager(appCtx context.Context, p Params) (RepoManager, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, since this signature's changed, all the callers have to be updated too.

if p.PoolSize <= 0 {
return nil, fmt.Errorf("pool size must be > 0, got %d", p.PoolSize)
}
return &repoManager{
git: p.Git,
repoManagerClonePath: p.RepoManagerClonePath,
Expand All @@ -95,7 +98,7 @@ func NewRepoManager(appCtx context.Context, p Params) RepoManager {
poolSize: p.PoolSize,
pools: make(map[string]*workerPool),
appCtx: appCtx,
}
}, nil
}

func (r *repoManager) poolFor(repo string) *workerPool {
Expand All @@ -105,10 +108,6 @@ func (r *repoManager) poolFor(repo string) *workerPool {
if pool, ok := r.pools[repo]; ok {
return pool
}
// default to 1 worker workspace per repo
if r.poolSize <= 0 {
r.poolSize = 1
}
pool := &workerPool{
originDir: filepath.Join(r.repoManagerClonePath, repo),
avail: make(chan *workerSlot, r.poolSize),
Expand Down
38 changes: 27 additions & 11 deletions core/repomanager/repo_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ import (
"go.uber.org/zap"
)

func newTestRepoManager(t *testing.T, appCtx context.Context, p Params) RepoManager {
t.Helper()
rm, err := NewRepoManager(appCtx, p)
require.NoError(t, err)
return rm
}

func TestNewRepoManager_InvalidPoolSize(t *testing.T) {
t.Parallel()
_, err := NewRepoManager(context.Background(), Params{
Logger: zap.NewNop().Sugar(),
PoolSize: 0,
})
require.Error(t, err, "expected setting invalid poolsize to error out")
}

func TestLease_ClonesOriginAndCreatesWorker(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
Expand All @@ -43,7 +59,7 @@ func TestLease_ClonesOriginAndCreatesWorker(t *testing.T) {
g.EXPECT().Clone(gomock.Any(), remote, originDir, "-c", "gc.auto=0").Return(nil)
g.EXPECT().Clone(gomock.Any(), originDir, workerDir, "--local", "-c", "gc.auto=0").Return(nil)

rm := NewRepoManager(context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
rm := newTestRepoManager(t, context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
ws, err := rm.Lease(context.Background(), tangopb.BuildDescription{Remote: remote})
require.NoError(t, err)
assert.Equal(t, workerDir, ws.Path())
Expand All @@ -65,7 +81,7 @@ func TestLease_SkipsOriginClone_WhenExists(t *testing.T) {
// Only worker clone expected
g.EXPECT().Clone(gomock.Any(), originDir, workerDir, "--local", "-c", "gc.auto=0").Return(nil)

rm := NewRepoManager(context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
rm := newTestRepoManager(t, context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
ws, err := rm.Lease(context.Background(), tangopb.BuildDescription{Remote: remote})
require.NoError(t, err)
assert.Equal(t, workerDir, ws.Path())
Expand All @@ -86,7 +102,7 @@ func TestLease_ReusesWorker_AfterRelease(t *testing.T) {
g.EXPECT().Clone(gomock.Any(), remote, originDir, "-c", "gc.auto=0").Return(nil)
g.EXPECT().Clone(gomock.Any(), originDir, workerDir, "--local", "-c", "gc.auto=0").Return(nil)

rm := NewRepoManager(context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
rm := newTestRepoManager(t, context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
ctx := context.Background()

ws1, err := rm.Lease(ctx, tangopb.BuildDescription{Remote: remote})
Expand Down Expand Up @@ -115,7 +131,7 @@ func TestLease_CreatesMultipleWorkers(t *testing.T) {
g.EXPECT().Clone(gomock.Any(), originDir, dir, "--local", "-c", "gc.auto=0").Return(nil)
}

rm := NewRepoManager(context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 2})
rm := newTestRepoManager(t, context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 2})
ctx := context.Background()

ws1, err := rm.Lease(ctx, tangopb.BuildDescription{Remote: remote})
Expand All @@ -141,7 +157,7 @@ func TestLease_BlocksUntilReturn(t *testing.T) {
g.EXPECT().Clone(gomock.Any(), remote, originDir, "-c", "gc.auto=0").Return(nil)
g.EXPECT().Clone(gomock.Any(), originDir, workerDir, "--local", "-c", "gc.auto=0").Return(nil)

rm := NewRepoManager(context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
rm := newTestRepoManager(t, context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
ctx := context.Background()

ws1, err := rm.Lease(ctx, tangopb.BuildDescription{Remote: remote})
Expand Down Expand Up @@ -187,7 +203,7 @@ func TestLease_CtxCanceled(t *testing.T) {
g.EXPECT().Clone(gomock.Any(), remote, originDir, "-c", "gc.auto=0").Return(nil)
g.EXPECT().Clone(gomock.Any(), originDir, workerDir, "--local", "-c", "gc.auto=0").Return(nil)

rm := NewRepoManager(context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
rm := newTestRepoManager(t, context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})

ws1, err := rm.Lease(context.Background(), tangopb.BuildDescription{Remote: remote})
require.NoError(t, err)
Expand All @@ -209,7 +225,7 @@ func TestLease_OriginCloneFails(t *testing.T) {
remote := "git@github.com:org/repo"
g.EXPECT().Clone(gomock.Any(), remote, filepath.Join(root, "org/repo"), "-c", "gc.auto=0").Return(assert.AnError)

rm := NewRepoManager(context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
rm := newTestRepoManager(t, context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
_, err := rm.Lease(context.Background(), tangopb.BuildDescription{Remote: remote})
require.Error(t, err)
assert.Contains(t, err.Error(), "clone origin")
Expand All @@ -228,7 +244,7 @@ func TestLease_WorkerCloneFails(t *testing.T) {
g.EXPECT().Clone(gomock.Any(), remote, originDir, "-c", "gc.auto=0").Return(nil)
g.EXPECT().Clone(gomock.Any(), originDir, workerDir, "--local", "-c", "gc.auto=0").Return(assert.AnError)

rm := NewRepoManager(context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
rm := newTestRepoManager(t, context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
_, err := rm.Lease(context.Background(), tangopb.BuildDescription{Remote: remote})
require.Error(t, err)
assert.Contains(t, err.Error(), "create worker")
Expand All @@ -247,7 +263,7 @@ func TestLease_DiscoversExistingWorker(t *testing.T) {
require.NoError(t, os.MkdirAll(filepath.Join(root, ".workers", "org/repo", "worker-1", ".git"), 0o755))

// No Clone calls — everything already exists
rm := NewRepoManager(context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
rm := newTestRepoManager(t, context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
ws, err := rm.Lease(context.Background(), tangopb.BuildDescription{Remote: remote})
require.NoError(t, err)
assert.Contains(t, ws.Path(), "worker-1")
Expand All @@ -273,7 +289,7 @@ func TestLease_DifferentRepos_IndependentPools(t *testing.T) {
g.EXPECT().Clone(gomock.Any(), remote2, origin2, "-c", "gc.auto=0").Return(nil)
g.EXPECT().Clone(gomock.Any(), origin2, worker2, "--local", "-c", "gc.auto=0").Return(nil)

rm := NewRepoManager(context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
rm := newTestRepoManager(t, context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
ctx := context.Background()

// Both repos can be leased concurrently even with pool size 1
Expand Down Expand Up @@ -306,7 +322,7 @@ func TestLease_WorkerCloneFails_SlotReturnedToPool(t *testing.T) {
g.EXPECT().Clone(gomock.Any(), originDir, workerDir, "--local", "-c", "gc.auto=0").Return(nil),
)

rm := NewRepoManager(context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
rm := newTestRepoManager(t, context.Background(), Params{Git: g, Logger: zap.NewNop().Sugar(), RepoManagerClonePath: root, WorkerRootPath: filepath.Join(root, ".workers"), PoolSize: 1})
ctx := context.Background()

// First attempt fails
Expand Down
5 changes: 4 additions & 1 deletion example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,16 @@ func run() error {
}
defer os.RemoveAll(workerRootPath)

rm := repomanager.NewRepoManager(appCtx, repomanager.Params{
rm, err := repomanager.NewRepoManager(appCtx, repomanager.Params{
Git: git.New(repoManagerClonePath),
Logger: logger,
RepoManagerClonePath: repoManagerClonePath,
WorkerRootPath: workerRootPath,
PoolSize: cfg.Service.WorkerPoolSize,
})
if err != nil {
return fmt.Errorf("failed to create repo manager: %w", err)
}
orch, err := orchestrator.NewNativeOrchestrator(appCtx, orchestrator.Params{
Storage: store,
RepoManager: rm,
Expand Down
Loading