Skip to content

Commit 1a5f4a6

Browse files
wesmclaude
andcommitted
fix: eliminate data race and dropped decode error in mockEnqueue
mockEnqueue wrote to a shared capturedEnqueue struct in the HTTP handler goroutine and returned a pointer for tests to read without synchronization, tripping -race. The json.Decode error was also silently discarded. Replace the shared pointer with a buffered channel: the handler decodes (checking the error), sends the value, then responds. Tests receive from the channel after executeReviewCmd returns, giving a clean happens-before edge. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 615dff0 commit 1a5f4a6

File tree

1 file changed

+25
-12
lines changed

1 file changed

+25
-12
lines changed

cmd/roborev/review_test.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,24 @@ type capturedEnqueue struct {
5151

5252
func mockEnqueue(
5353
t *testing.T, mux *http.ServeMux,
54-
) *capturedEnqueue {
54+
) <-chan capturedEnqueue {
5555
t.Helper()
56-
var captured capturedEnqueue
56+
ch := make(chan capturedEnqueue, 1)
5757
mux.HandleFunc("/api/enqueue", func(
5858
w http.ResponseWriter, r *http.Request,
5959
) {
60-
json.NewDecoder(r.Body).Decode(&captured)
60+
var req capturedEnqueue
61+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
62+
t.Errorf("mockEnqueue: decode body: %v", err)
63+
http.Error(w, "bad request", http.StatusBadRequest)
64+
return
65+
}
66+
ch <- req
6167
respondJSON(w, http.StatusCreated, storage.ReviewJob{
62-
ID: 1, GitRef: captured.GitRef, Agent: "test",
68+
ID: 1, GitRef: req.GitRef, Agent: "test",
6369
})
6470
})
65-
return &captured
71+
return ch
6672
}
6773

6874
func mockWaitableReview(
@@ -100,7 +106,7 @@ func mockWaitableReview(
100106
func TestEnqueueCmdPositionalArg(t *testing.T) {
101107
t.Run("positional arg overrides default HEAD", func(t *testing.T) {
102108
repo, mux := setupTestEnvironment(t)
103-
req := mockEnqueue(t, mux)
109+
reqCh := mockEnqueue(t, mux)
104110

105111
firstSHA := repo.CommitFile("file1.txt", "first", "first commit")
106112
repo.CommitFile("file2.txt", "second", "second commit")
@@ -111,6 +117,7 @@ func TestEnqueueCmdPositionalArg(t *testing.T) {
111117
t.Fatalf("enqueue failed: %v", err)
112118
}
113119

120+
req := <-reqCh
114121
if req.GitRef != shortFirstSHA {
115122
t.Errorf("Expected SHA %s, got %s", shortFirstSHA, req.GitRef)
116123
}
@@ -121,7 +128,7 @@ func TestEnqueueCmdPositionalArg(t *testing.T) {
121128

122129
t.Run("sha flag works", func(t *testing.T) {
123130
repo, mux := setupTestEnvironment(t)
124-
req := mockEnqueue(t, mux)
131+
reqCh := mockEnqueue(t, mux)
125132

126133
firstSHA := repo.CommitFile("file1.txt", "first", "first commit")
127134
repo.CommitFile("file2.txt", "second", "second commit")
@@ -132,14 +139,15 @@ func TestEnqueueCmdPositionalArg(t *testing.T) {
132139
t.Fatalf("enqueue failed: %v", err)
133140
}
134141

142+
req := <-reqCh
135143
if req.GitRef != shortFirstSHA {
136144
t.Errorf("Expected SHA %s, got %s", shortFirstSHA, req.GitRef)
137145
}
138146
})
139147

140148
t.Run("defaults to HEAD", func(t *testing.T) {
141149
repo, mux := setupTestEnvironment(t)
142-
req := mockEnqueue(t, mux)
150+
reqCh := mockEnqueue(t, mux)
143151

144152
repo.CommitFile("file1.txt", "first", "first commit")
145153

@@ -148,6 +156,7 @@ func TestEnqueueCmdPositionalArg(t *testing.T) {
148156
t.Fatalf("enqueue failed: %v", err)
149157
}
150158

159+
req := <-reqCh
151160
if req.GitRef != "HEAD" {
152161
t.Errorf("Expected HEAD, got %s", req.GitRef)
153162
}
@@ -389,7 +398,7 @@ func TestReviewFlagValidation(t *testing.T) {
389398
func TestReviewSinceFlag(t *testing.T) {
390399
t.Run("since with valid ref succeeds", func(t *testing.T) {
391400
repo, mux := setupTestEnvironment(t)
392-
req := mockEnqueue(t, mux)
401+
reqCh := mockEnqueue(t, mux)
393402

394403
firstSHA := repo.CommitFile("file1.txt", "first", "first commit")
395404
repo.CommitFile("file2.txt", "second", "second commit")
@@ -399,6 +408,7 @@ func TestReviewSinceFlag(t *testing.T) {
399408
t.Fatalf("unexpected error: %v", err)
400409
}
401410

411+
req := <-reqCh
402412
if !strings.Contains(req.GitRef, firstSHA) {
403413
t.Errorf("expected git_ref to contain first SHA %s, got %s", firstSHA, req.GitRef)
404414
}
@@ -448,7 +458,7 @@ func TestReviewBranchFlag(t *testing.T) {
448458

449459
t.Run("branch review succeeds with commits", func(t *testing.T) {
450460
repo, mux := setupTestEnvironment(t)
451-
req := mockEnqueue(t, mux)
461+
reqCh := mockEnqueue(t, mux)
452462

453463
repo.Run("symbolic-ref", "HEAD", "refs/heads/main")
454464
repo.CommitFile("file.txt", "content", "initial")
@@ -461,6 +471,7 @@ func TestReviewBranchFlag(t *testing.T) {
461471
t.Fatalf("unexpected error: %v", err)
462472
}
463473

474+
req := <-reqCh
464475
if !strings.Contains(req.GitRef, mainSHA) {
465476
t.Errorf("expected git_ref to contain main SHA %s, got %s", mainSHA, req.GitRef)
466477
}
@@ -473,7 +484,7 @@ func TestReviewBranchFlag(t *testing.T) {
473484
func TestReviewFastFlag(t *testing.T) {
474485
t.Run("fast flag sets reasoning to fast", func(t *testing.T) {
475486
repo, mux := setupTestEnvironment(t)
476-
req := mockEnqueue(t, mux)
487+
reqCh := mockEnqueue(t, mux)
477488

478489
repo.CommitFile("file.txt", "content", "initial")
479490

@@ -482,14 +493,15 @@ func TestReviewFastFlag(t *testing.T) {
482493
t.Fatalf("unexpected error: %v", err)
483494
}
484495

496+
req := <-reqCh
485497
if req.Reasoning != "fast" {
486498
t.Errorf("expected reasoning 'fast', got %q", req.Reasoning)
487499
}
488500
})
489501

490502
t.Run("explicit reasoning takes precedence over fast", func(t *testing.T) {
491503
repo, mux := setupTestEnvironment(t)
492-
req := mockEnqueue(t, mux)
504+
reqCh := mockEnqueue(t, mux)
493505

494506
repo.CommitFile("file.txt", "content", "initial")
495507

@@ -498,6 +510,7 @@ func TestReviewFastFlag(t *testing.T) {
498510
t.Fatalf("unexpected error: %v", err)
499511
}
500512

513+
req := <-reqCh
501514
if req.Reasoning != "thorough" {
502515
t.Errorf("expected reasoning 'thorough' (explicit flag should win), got %q", req.Reasoning)
503516
}

0 commit comments

Comments
 (0)