oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package live
2
3import (
4 "sync"
5 "testing"
6 "time"
7)
8
9// recv waits for one message, or fails. Every test wants this and none
10// of them vary the timeout.
11func recv(t *testing.T, s *Sub, what string) Message {
12 t.Helper()
13 select {
14 case m := <-s.C():
15 return m
16 case <-time.After(2 * time.Second):
17 t.Fatalf("%s: received nothing", what)
18 return Message{}
19 }
20}
21
22func TestPublishReachesEverySubscriberOnTheTab(t *testing.T) {
23 h := NewHub()
24 a := h.Subscribe(7)
25 b := h.Subscribe(7)
26 defer a.Close()
27 defer b.Close()
28
29 h.Publish(7, Message{Version: 3, Data: []byte(`{"v":3}`)})
30
31 for i, s := range []*Sub{a, b} {
32 if m := recv(t, s, "subscriber"); m.Version != 3 {
33 t.Errorf("subscriber %d got version %d, want 3", i, m.Version)
34 }
35 }
36}
37
38// The tab id is the whole routing key. If it leaks, one team's edits
39// paint into another sheet's grid.
40func TestPublishDoesNotReachAnotherTab(t *testing.T) {
41 h := NewHub()
42 here := h.Subscribe(7)
43 elsewhere := h.Subscribe(8)
44 defer here.Close()
45 defer elsewhere.Close()
46
47 h.Publish(7, Message{Version: 3})
48
49 recv(t, here, "the subscribed tab")
50 select {
51 case m := <-elsewhere.C():
52 t.Fatalf("a subscriber on tab 8 received tab 7's message: %+v", m)
53 case <-time.After(50 * time.Millisecond):
54 }
55}
56
57func TestPublishWithNoSubscribersIsFine(t *testing.T) {
58 // One person editing alone is the common case, not an edge case.
59 NewHub().Publish(7, Message{Version: 1})
60}
61
62func TestCloseUnsubscribes(t *testing.T) {
63 h := NewHub()
64 s := h.Subscribe(7)
65 if got := h.Subscribers(7); got != 1 {
66 t.Fatalf("Subscribers(7) = %d after one Subscribe, want 1", got)
67 }
68 s.Close()
69 if got := h.Subscribers(7); got != 0 {
70 t.Fatalf("after Close, Subscribers(7) = %d, want 0", got)
71 }
72 // An SSE handler defers Close and may also reach it on a write
73 // error. A double close must not panic in a goroutine no request
74 // owns.
75 s.Close()
76}
77
78func TestClosingOneSubscriberLeavesTheOthers(t *testing.T) {
79 h := NewHub()
80 a := h.Subscribe(7)
81 b := h.Subscribe(7)
82 defer b.Close()
83 a.Close()
84
85 h.Publish(7, Message{Version: 9})
86 if m := recv(t, b, "the surviving subscriber"); m.Version != 9 {
87 t.Fatalf("surviving subscriber got version %d, want 9", m.Version)
88 }
89}
90
91// A tab that has stopped reading must never block the writer: the
92// publisher is a request handler that has already committed a database
93// write.
94func TestASlowSubscriberIsDroppedNotBlocking(t *testing.T) {
95 h := NewHub()
96 s := h.Subscribe(7)
97 defer s.Close()
98
99 for i := 0; i < subBuffer+10; i++ {
100 done := make(chan struct{})
101 go func(i int) {
102 h.Publish(7, Message{Version: uint64(i)})
103 close(done)
104 }(i)
105 select {
106 case <-done:
107 case <-time.After(2 * time.Second):
108 t.Fatalf("Publish blocked on message %d — one wedged browser would hold a writer open", i)
109 }
110 }
111 if !s.Lost() {
112 t.Fatal("an overrun subscriber is not marked lost; it would paint a grid with holes in it")
113 }
114}
115
116func TestAKeepingUpSubscriberIsNotLost(t *testing.T) {
117 h := NewHub()
118 s := h.Subscribe(7)
119 defer s.Close()
120 for i := 0; i < subBuffer*3; i++ {
121 h.Publish(7, Message{Version: uint64(i)})
122 recv(t, s, "keeping up")
123 }
124 if s.Lost() {
125 t.Fatal("a subscriber that read every message was declared lost")
126 }
127}
128
129// Subscribe, Publish and Close all race in production: a tab opening
130// while another closes while an edit lands.
131func TestConcurrentSubscribePublishAndClose(t *testing.T) {
132 h := NewHub()
133 var wg sync.WaitGroup
134 for i := 0; i < 20; i++ {
135 wg.Add(2)
136 go func() {
137 defer wg.Done()
138 s := h.Subscribe(7)
139 s.Close()
140 }()
141 go func() {
142 defer wg.Done()
143 h.Publish(7, Message{Version: 1, Data: []byte("x")})
144 }()
145 }
146 wg.Wait()
147 if got := h.Subscribers(7); got != 0 {
148 t.Fatalf("Subscribers(7) = %d after every subscriber closed, want 0", got)
149 }
150}
151
152// An instance that has served many tabs over a week must not keep one
153// empty map per tab it has ever seen.
154func TestAnEmptiedTabIsForgotten(t *testing.T) {
155 h := NewHub()
156 for id := uint64(1); id <= 50; id++ {
157 h.Subscribe(id).Close()
158 }
159 h.mu.Lock()
160 n := len(h.tabs)
161 h.mu.Unlock()
162 if n != 0 {
163 t.Fatalf("the hub still holds %d tab entries after every subscriber left", n)
164 }
165}
166