test(mutations): 100 concurrent comments all retained, no path collisions
This commit is contained in:
parent
ce18f6430c
commit
d485618cf1
1 changed files with 77 additions and 0 deletions
77
mutations_add_comment_test.go
Normal file
77
mutations_add_comment_test.go
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestConcurrentComments_AllRetained — 100 goroutines add comments to the
|
||||||
|
// same post. All should land, no path collisions, no lost writes.
|
||||||
|
func TestConcurrentComments_AllRetained(t *testing.T) {
|
||||||
|
s, _, _ := newTestStore(t)
|
||||||
|
cp := &CreatePost{AuthorID: "scott", Body: "race test"}
|
||||||
|
must(t, Submit(context.Background(), s, cp))
|
||||||
|
|
||||||
|
const N = 100
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errCh := make(chan error, N)
|
||||||
|
for i := 0; i < N; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
m := &AddComment{PostID: cp.ResolvedPostID(), AuthorID: "hermes", Body: "concurrent"}
|
||||||
|
errCh <- Submit(context.Background(), s, m)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
close(errCh)
|
||||||
|
for err := range errCh {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("concurrent submit: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
comments := s.ListComments(cp.ResolvedPostID())
|
||||||
|
if len(comments) != N {
|
||||||
|
t.Fatalf("expected %d comments, got %d", N, len(comments))
|
||||||
|
}
|
||||||
|
// All paths must be unique and parseable.
|
||||||
|
seen := make(map[string]bool, N)
|
||||||
|
for _, c := range comments {
|
||||||
|
if seen[c.Path] {
|
||||||
|
t.Fatalf("duplicate path: %s", c.Path)
|
||||||
|
}
|
||||||
|
seen[c.Path] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBumpOrder_AfterConcurrentComments — concurrent comments land in some
|
||||||
|
// order, but the final post bump_at should be the time of the LAST comment.
|
||||||
|
// With the per-author coarse lock, mutations serialize, so the order is
|
||||||
|
// deterministic given the goroutine schedule. We don't assert exact order
|
||||||
|
// (that would be flaky); we assert that the final bump_at is the latest.
|
||||||
|
func TestBumpOrder_AfterConcurrentComments(t *testing.T) {
|
||||||
|
s, clock, _ := newTestStore(t)
|
||||||
|
cp := &CreatePost{AuthorID: "scott", Body: "bump order"}
|
||||||
|
must(t, Submit(context.Background(), s, cp))
|
||||||
|
initialBump := s.posts[cp.ResolvedPostID()].BumpedAt
|
||||||
|
|
||||||
|
const N = 10
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < N; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
clock.Advance(time.Second)
|
||||||
|
m := &AddComment{PostID: cp.ResolvedPostID(), AuthorID: "hermes", Body: "x"}
|
||||||
|
_ = Submit(context.Background(), s, m)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
got, _ := s.GetPost(cp.ResolvedPostID())
|
||||||
|
if !got.BumpedAt.After(initialBump) {
|
||||||
|
t.Fatalf("post should have been bumped past %v, got %v", initialBump, got.BumpedAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue