package web import ( "sync" "testing" ) func TestHub_DeliversToSubscribers(t *testing.T) { h := newGamesHub() ch, done := h.subscribe("t1") defer done() h.publish("t1", hubFrame{Version: 3, Data: []byte("hi")}) f := <-ch if f.Version != 3 || string(f.Data) != "hi" { t.Fatalf("got %+v", f) } } func TestHub_OnlyToTheRightTable(t *testing.T) { h := newGamesHub() ch1, d1 := h.subscribe("t1") defer d1() ch2, d2 := h.subscribe("t2") defer d2() h.publish("t1", hubFrame{Version: 1}) select { case <-ch2: t.Fatal("t2 should not have received t1's frame") default: } if f := <-ch1; f.Version != 1 { t.Fatalf("t1 got %+v", f) } } // TestHub_PublishNeverBlocks is the load-bearing property: a subscriber that // never reads must not be able to hold up a publish, because publish happens // under the table lock and a blocked publish stalls the turn clock for everyone. func TestHub_PublishNeverBlocks(t *testing.T) { h := newGamesHub() _, done := h.subscribe("t1") // never read from defer done() // Far more than the buffer. If any of these blocked, the test would hang. blocked := make(chan struct{}) go func() { for i := 0; i < subChanBuffer*10; i++ { h.publish("t1", hubFrame{Version: int64(i)}) } close(blocked) }() <-blocked } func TestHub_UnsubscribeStopsDelivery(t *testing.T) { h := newGamesHub() ch, done := h.subscribe("t1") done() if h.watchers("t1") != 0 { t.Fatalf("watchers should be 0 after unsubscribe, got %d", h.watchers("t1")) } h.publish("t1", hubFrame{Version: 1}) select { case _, ok := <-ch: if ok { t.Fatal("a frame arrived after unsubscribe") } default: } } func TestHub_ConcurrentSubscribers(t *testing.T) { h := newGamesHub() var wg sync.WaitGroup for i := 0; i < 50; i++ { wg.Add(1) go func() { defer wg.Done() ch, done := h.subscribe("t1") defer done() <-ch }() } // Let them all register, then flood so every one of them reads at least one. for h.watchers("t1") < 50 { } for i := 0; i < subChanBuffer; i++ { h.publish("t1", hubFrame{Version: int64(i)}) } wg.Wait() }