Skip to content

Commit 6e3269f

Browse files
fix(strategy): stabilize fetch/rebase git CLI tests under race (#40)
Serialize t.Chdir-based git integration tests and clone into dedicated temp directories to avoid CI flakes from nested temp paths and cwd races. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a3dd0cb commit 6e3269f

2 files changed

Lines changed: 35 additions & 20 deletions

File tree

cli/strategy/push_common_2_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import (
3131
//
3232
// Not parallel: uses t.Chdir() (required for OpenRepository).
3333
func TestFetchAndRebase_URLTarget_ReconcilesFetchedTempRef(t *testing.T) {
34+
acquireGitCLITest(t)
35+
3436
ctx := context.Background()
3537
branchName := paths.MetadataBranchName
3638

@@ -66,8 +68,7 @@ func TestFetchAndRebase_URLTarget_ReconcilesFetchedTempRef(t *testing.T) {
6668
gitRun(setupDir, "push", "origin", branchName)
6769
gitRun(setupDir, "checkout", "main")
6870

69-
cloneDir := filepath.Join(t.TempDir(), "clone")
70-
require.NoError(t, os.MkdirAll(cloneDir, 0o755))
71+
cloneDir := t.TempDir()
7172
gitRun(cloneDir, "clone", bareDir, ".")
7273
gitRun(cloneDir, "config", "user.email", "test@test.com")
7374
gitRun(cloneDir, "config", "user.name", "Test User")
@@ -128,6 +129,8 @@ func TestFetchAndRebase_URLTarget_ReconcilesFetchedTempRef(t *testing.T) {
128129
//
129130
// Not parallel: uses t.Chdir() (required for OpenRepository).
130131
func TestFetchAndRebase_FlaggedOriginTarget_UsesTempRef(t *testing.T) {
132+
acquireGitCLITest(t)
133+
131134
ctx := context.Background()
132135
branchName := paths.MetadataBranchName
133136

@@ -163,8 +166,7 @@ func TestFetchAndRebase_FlaggedOriginTarget_UsesTempRef(t *testing.T) {
163166
gitRun(setupDir, "push", "origin", branchName)
164167
gitRun(setupDir, "checkout", "main")
165168

166-
cloneDir := filepath.Join(t.TempDir(), "clone")
167-
require.NoError(t, os.MkdirAll(cloneDir, 0o755))
169+
cloneDir := t.TempDir()
168170
gitRun(cloneDir, "clone", bareDir, ".")
169171
gitRun(cloneDir, "config", "user.email", "test@test.com")
170172
gitRun(cloneDir, "config", "user.name", "Test User")

cli/strategy/push_common_test.go

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"os/exec"
77
"path/filepath"
8+
"sync"
89
"testing"
910

1011
"github.com/GrayCodeAI/trace/cli/checkpoint"
@@ -19,6 +20,16 @@ import (
1920
"github.com/stretchr/testify/require"
2021
)
2122

23+
// gitCLITestMu serializes git CLI tests that call t.Chdir(). Parallel tests in
24+
// this package can otherwise race on process cwd and temp-repo cleanup under -race.
25+
var gitCLITestMu sync.Mutex
26+
27+
func acquireGitCLITest(t *testing.T) {
28+
t.Helper()
29+
gitCLITestMu.Lock()
30+
t.Cleanup(gitCLITestMu.Unlock)
31+
}
32+
2233
func TestHasUnpushedSessionsCommon(t *testing.T) {
2334
t.Parallel()
2435

@@ -183,6 +194,8 @@ func TestPushBranchIfNeeded_LocalBareRepo_PushesSuccessfully(t *testing.T) {
183194
//
184195
// Not parallel: uses t.Chdir() (required for OpenRepository).
185196
func TestFetchAndRebase_DivergedBranches(t *testing.T) {
197+
acquireGitCLITest(t)
198+
186199
ctx := context.Background()
187200
branchName := paths.MetadataBranchName
188201

@@ -221,10 +234,8 @@ func TestFetchAndRebase_DivergedBranches(t *testing.T) {
221234
gitRun(workDir, "checkout", "main")
222235

223236
// 2. Clone into two separate working directories
224-
cloneA := filepath.Join(t.TempDir(), "cloneA")
225-
cloneB := filepath.Join(t.TempDir(), "cloneB")
226-
require.NoError(t, os.MkdirAll(cloneA, 0o755))
227-
require.NoError(t, os.MkdirAll(cloneB, 0o755))
237+
cloneA := t.TempDir()
238+
cloneB := t.TempDir()
228239

229240
gitRun(cloneA, "clone", bareDir, ".")
230241
gitRun(cloneA, "config", "user.email", "a@test.com")
@@ -307,6 +318,8 @@ func TestFetchAndRebase_DivergedBranches(t *testing.T) {
307318
//
308319
// Not parallel: uses t.Chdir() (required for OpenRepository).
309320
func TestFetchAndRebase_LocalBehind(t *testing.T) {
321+
acquireGitCLITest(t)
322+
310323
ctx := context.Background()
311324
branchName := paths.MetadataBranchName
312325

@@ -343,8 +356,7 @@ func TestFetchAndRebase_LocalBehind(t *testing.T) {
343356
gitRun(workDir, "checkout", "main")
344357

345358
// Clone
346-
cloneDir := filepath.Join(t.TempDir(), "clone")
347-
require.NoError(t, os.MkdirAll(cloneDir, 0o755))
359+
cloneDir := t.TempDir()
348360
gitRun(cloneDir, "clone", bareDir, ".")
349361
gitRun(cloneDir, "config", "user.email", "test@test.com")
350362
gitRun(cloneDir, "config", "user.name", "Test User")
@@ -387,6 +399,8 @@ func TestFetchAndRebase_LocalBehind(t *testing.T) {
387399
//
388400
// Not parallel: uses t.Chdir() (required for OpenRepository).
389401
func TestFetchAndRebase_MergeBaseOnSecondParent_DoesNotReplayAncestors(t *testing.T) {
402+
acquireGitCLITest(t)
403+
390404
ctx := context.Background()
391405
branchName := paths.MetadataBranchName
392406

@@ -425,10 +439,8 @@ func TestFetchAndRebase_MergeBaseOnSecondParent_DoesNotReplayAncestors(t *testin
425439
gitRun(setupDir, "checkout", "main")
426440

427441
// Clone twice: local gets the old merge-commit history, remote advances later.
428-
cloneLocal := filepath.Join(t.TempDir(), "clone-local")
429-
cloneRemote := filepath.Join(t.TempDir(), "clone-remote")
430-
require.NoError(t, os.MkdirAll(cloneLocal, 0o755))
431-
require.NoError(t, os.MkdirAll(cloneRemote, 0o755))
442+
cloneLocal := t.TempDir()
443+
cloneRemote := t.TempDir()
432444

433445
for _, dir := range []string{cloneLocal, cloneRemote} {
434446
gitRun(dir, "clone", bareDir, ".")
@@ -518,6 +530,8 @@ func TestFetchAndRebase_MergeBaseOnSecondParent_DoesNotReplayAncestors(t *testin
518530
//
519531
// Not parallel: uses t.Chdir() (required for OpenRepository).
520532
func TestFetchAndRebase_DoesNotResurrectRemoteOnlyCheckpointFromMerge(t *testing.T) {
533+
acquireGitCLITest(t)
534+
521535
ctx := context.Background()
522536
branchName := paths.MetadataBranchName
523537

@@ -553,10 +567,8 @@ func TestFetchAndRebase_DoesNotResurrectRemoteOnlyCheckpointFromMerge(t *testing
553567
gitRun(setupDir, "push", "origin", branchName)
554568
gitRun(setupDir, "checkout", "main")
555569

556-
cloneLocal := filepath.Join(t.TempDir(), "clone-local")
557-
cloneRemote := filepath.Join(t.TempDir(), "clone-remote")
558-
require.NoError(t, os.MkdirAll(cloneLocal, 0o755))
559-
require.NoError(t, os.MkdirAll(cloneRemote, 0o755))
570+
cloneLocal := t.TempDir()
571+
cloneRemote := t.TempDir()
560572

561573
for _, dir := range []string{cloneLocal, cloneRemote} {
562574
gitRun(dir, "clone", bareDir, ".")
@@ -633,6 +645,8 @@ func TestFetchAndRebase_DoesNotResurrectRemoteOnlyCheckpointFromMerge(t *testing
633645
//
634646
// Not parallel: uses t.Chdir() (required for OpenRepository).
635647
func TestFetchAndRebase_NonOriginRemote_ReconcilesFetchedRef(t *testing.T) {
648+
acquireGitCLITest(t)
649+
636650
ctx := context.Background()
637651
branchName := paths.MetadataBranchName
638652

@@ -668,8 +682,7 @@ func TestFetchAndRebase_NonOriginRemote_ReconcilesFetchedRef(t *testing.T) {
668682
gitRun(setupDir, "push", "origin", branchName)
669683
gitRun(setupDir, "checkout", "main")
670684

671-
cloneDir := filepath.Join(t.TempDir(), "clone")
672-
require.NoError(t, os.MkdirAll(cloneDir, 0o755))
685+
cloneDir := t.TempDir()
673686
gitRun(cloneDir, "clone", bareDir, ".")
674687
gitRun(cloneDir, "config", "user.email", "test@test.com")
675688
gitRun(cloneDir, "config", "user.name", "Test User")

0 commit comments

Comments
 (0)