test(trace): guard writer mutex via WriteStep/Close race on w.file

This commit is contained in:
pj committed 2026-06-06 13:04:15 +05:30
1 parent db3857a913
commit e21cd44089
1 file changed
+12 -18
+12 -18
View File
@@ -289,16 +289,16 @@ func TestWriteStep_MultipleStepsAppend(t *testing.T) {
} }
} }
// Bug class: dropping the writer mutex would let concurrent encoder.Encode // Bug class: dropping the writer mutex unsynchronizes the w.file field that
// calls interleave, producing torn JSONL lines. Run under -race: every line // WriteStep reads and Close nils out. Run under -race with WriteStep racing
// must be a complete, parseable Step and all N must arrive. // Close: a missing mutex is a reported data race on w.file, and any step that
func TestWriteStep_ConcurrentWritesAreWellFormed(t *testing.T) { // survives Close must still be a complete, parseable JSONL line.
func TestWriteStep_RacesCloseSafely(t *testing.T) {
directory := t.TempDir() directory := t.TempDir()
writer, err := NewWriter(directory) writer, err := NewWriter(directory)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer writer.Close()
const n = 50 const n = 50
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -306,27 +306,21 @@ func TestWriteStep_ConcurrentWritesAreWellFormed(t *testing.T) {
wg.Add(1) wg.Add(1)
go func(index int) { go func(index int) {
defer wg.Done() defer wg.Done()
if err := writer.WriteStep(Step{Index: index, Screen: "s"}); err != nil { // Tolerate "writer is closed": the contract under test is that the
t.Errorf("WriteStep: %v", err) // w.file access is synchronized, not that every write lands.
} _ = writer.WriteStep(Step{Index: index, Screen: "s"})
}(index) }(index)
} }
if err := writer.Close(); err != nil {
t.Fatalf("Close: %v", err)
}
wg.Wait() wg.Wait()
lines := readLines(t, filepath.Join(directory, "trace.jsonl")) for _, line := range readLines(t, filepath.Join(directory, "trace.jsonl")) {
if len(lines) != n {
t.Fatalf("expected %d lines, got %d", n, len(lines))
}
seen := make(map[int]bool, n)
for _, line := range lines {
var got Step var got Step
if err := json.Unmarshal([]byte(line), &got); err != nil { if err := json.Unmarshal([]byte(line), &got); err != nil {
t.Fatalf("torn JSONL line: %v\n%s", err, line) t.Fatalf("torn JSONL line: %v\n%s", err, line)
} }
seen[got.Index] = true
}
if len(seen) != n {
t.Errorf("expected %d distinct steps, got %d", n, len(seen))
} }
} }