package chat
import (
"sync"
"testing"
"craftdesign.group/quarry/internal/events"
)
// A presence change is fanned out while readers come and go: a chat tab
// opens or closes at any moment, including mid-beat. The fan-out must
// neither crash the process (the runtime aborts on a map ranged over
// while another goroutine writes it) nor lose the tick owed to a reader
// who stayed put the whole time.
func TestAPresenceBeatSurvivesTabsOpeningAndClosing(t *testing.T) {
ev, err := events.Start(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer ev.Close()
p, err := NewPresence(ev.Conn())
if err != nil {
t.Fatal(err)
}
defer p.Close()
steady, stopSteady := p.Changed("A")
defer stopSteady()
// The count is high because CI runs without -race: a plain run only
// catches the race when the runtime's own map check trips, and at a
// few hundred rounds that happened in some runs and not others.
const rounds = 5000
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < rounds; i++ {
p.notify("A")
}
}()
go func() {
defer wg.Done()
for i := 0; i < rounds; i++ {
_, stop := p.Changed("A")
stop()
}
}()
wg.Wait()
select {
case <-steady:
t.Log("ok: the reader who stayed was told of the change")
default:
t.Error("FAIL: the reader who stayed through the churn was never ticked")
}
}