package main import ( "context" "sync" "sync/atomic" "testing" "time" ) // TestLockController_PromotesUnderContention — N goroutines hit the same // author simultaneously. The controller should observe the queue depth and // move the scope from Coarse to Medium. func TestLockController_PromotesUnderContention(t *testing.T) { s, _, _ := newTestStore(t) cp := &CreatePost{AuthorID: "scott", Body: "lock test"} must(t, Submit(context.Background(), s, cp)) const N = 80 var wg sync.WaitGroup var ran atomic.Int64 for i := 0; i < N; i++ { wg.Add(1) go func() { defer wg.Done() m := &AddComment{PostID: cp.ResolvedPostID(), AuthorID: "hermes", Body: "y"} if err := Submit(context.Background(), s, m); err == nil { ran.Add(1) } }() } wg.Wait() st := s.lockCtrl.getOrCreate("hermes") // After 80 concurrent submissions, scope may have promoted. We don't // assert the exact scope (depends on timing); we just assert no deadlock // and all ran. if ran.Load() != N { t.Fatalf("expected %d to run, got %d", N, ran.Load()) } t.Logf("final scope for hermes: %s (queueDepth=%d)", st.scope, st.queueDepth) } // TestLockController_DemotesOnIdle — after a quiet period, scope should // settle back to Coarse. Trigger Tick() manually to simulate the periodic // controller. func TestLockController_DemotesOnIdle(t *testing.T) { s, _, _ := newTestStore(t) cp := &CreatePost{AuthorID: "scott", Body: "demote test"} must(t, Submit(context.Background(), s, cp)) // Manually set state to Fine, then tick to decay queue depth and demote. st := s.lockCtrl.getOrCreate("hermes") st.scope = ScopeFine st.queueDepth = 5 // below fineToMediumBelow=100 st.belowSince = time.Now().Add(-time.Hour) // way past the demotion window s.lockCtrl.Tick() chosen := s.lockCtrl.chooseScope(st) if chosen == ScopeFine { t.Fatalf("expected demotion from Fine, still Fine") } }