github.com/grahambrereton-form3/tilt@v0.10.18/internal/store/subscriber_test.go (about) 1 package store 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/assert" 9 ) 10 11 func TestSubscriber(t *testing.T) { 12 st, _ := NewStoreForTesting() 13 ctx := context.Background() 14 s := newFakeSubscriber() 15 st.AddSubscriber(ctx, s) 16 17 st.NotifySubscribers(ctx) 18 call := <-s.onChange 19 close(call.done) 20 } 21 22 func TestSubscriberInterleavedCalls(t *testing.T) { 23 st, _ := NewStoreForTesting() 24 ctx := context.Background() 25 s := newFakeSubscriber() 26 st.AddSubscriber(ctx, s) 27 28 st.NotifySubscribers(ctx) 29 call := <-s.onChange 30 st.NotifySubscribers(ctx) 31 st.NotifySubscribers(ctx) 32 time.Sleep(10 * time.Millisecond) 33 close(call.done) 34 35 call = <-s.onChange 36 close(call.done) 37 38 select { 39 case <-s.onChange: 40 t.Fatal("Expected no more onChange calls") 41 case <-time.After(10 * time.Millisecond): 42 } 43 } 44 45 func TestAddSubscriberToAlreadySetUpListCallsSetUp(t *testing.T) { 46 st, _ := NewStoreForTesting() 47 ctx := context.Background() 48 st.subscribers.SetUp(ctx) 49 50 s := newFakeSubscriber() 51 st.AddSubscriber(ctx, s) 52 53 assert.Equal(t, 1, s.setupCount) 54 } 55 56 func TestAddSubscriberBeforeSetupNoop(t *testing.T) { 57 st, _ := NewStoreForTesting() 58 ctx := context.Background() 59 60 s := newFakeSubscriber() 61 st.AddSubscriber(ctx, s) 62 63 // We haven't called SetUp on subscriber list as a whole, so won't call it on a new individual subscriber 64 assert.Equal(t, 0, s.setupCount) 65 } 66 67 func TestRemoveSubscriber(t *testing.T) { 68 st, _ := NewStoreForTesting() 69 ctx := context.Background() 70 s := newFakeSubscriber() 71 72 st.AddSubscriber(ctx, s) 73 st.NotifySubscribers(ctx) 74 s.assertOnChangeCount(t, 1) 75 76 err := st.RemoveSubscriber(ctx, s) 77 assert.NoError(t, err) 78 st.NotifySubscribers(ctx) 79 s.assertOnChangeCount(t, 0) 80 } 81 82 func TestRemoveSubscriberNotFound(t *testing.T) { 83 st, _ := NewStoreForTesting() 84 s := newFakeSubscriber() 85 ctx := context.Background() 86 err := st.RemoveSubscriber(ctx, s) 87 if assert.Error(t, err) { 88 assert.Contains(t, err.Error(), "Subscriber not found") 89 } 90 } 91 92 func TestSubscriberSetup(t *testing.T) { 93 st, _ := NewStoreForTesting() 94 ctx := context.Background() 95 s := newFakeSubscriber() 96 st.AddSubscriber(ctx, s) 97 98 st.subscribers.SetUp(ctx) 99 100 assert.Equal(t, 1, s.setupCount) 101 } 102 103 func TestSubscriberTeardown(t *testing.T) { 104 st, _ := NewStoreForTesting() 105 ctx := context.Background() 106 s := newFakeSubscriber() 107 st.AddSubscriber(ctx, s) 108 109 go st.Dispatch(NewErrorAction(context.Canceled)) 110 err := st.Loop(ctx) 111 if assert.Error(t, err) { 112 assert.Contains(t, err.Error(), "context canceled") 113 } 114 115 assert.Equal(t, 1, s.teardownCount) 116 } 117 118 func TestSubscriberTeardownOnRemove(t *testing.T) { 119 st, _ := NewStoreForTesting() 120 ctx := context.Background() 121 s := newFakeSubscriber() 122 st.AddSubscriber(ctx, s) 123 124 errChan := make(chan error) 125 go func() { 126 err := st.Loop(ctx) 127 errChan <- err 128 }() 129 130 // Make sure the loop has started. 131 st.NotifySubscribers(ctx) 132 s.assertOnChangeCount(t, 1) 133 134 // Remove the subscriber and make sure it doesn't get a change. 135 _ = st.RemoveSubscriber(ctx, s) 136 st.NotifySubscribers(ctx) 137 s.assertOnChangeCount(t, 0) 138 139 assert.Equal(t, 1, s.teardownCount) 140 141 st.Dispatch(NewErrorAction(context.Canceled)) 142 143 err := <-errChan 144 if assert.Error(t, err) { 145 assert.Contains(t, err.Error(), "context canceled") 146 } 147 assert.Equal(t, 1, s.teardownCount) 148 } 149 150 type fakeSubscriber struct { 151 onChange chan onChangeCall 152 setupCount int 153 teardownCount int 154 } 155 156 func newFakeSubscriber() *fakeSubscriber { 157 return &fakeSubscriber{ 158 onChange: make(chan onChangeCall), 159 } 160 } 161 162 type onChangeCall struct { 163 done chan bool 164 } 165 166 func (f *fakeSubscriber) assertOnChangeCount(t *testing.T, count int) { 167 t.Helper() 168 169 for i := 0; i < count; i++ { 170 f.assertOnChange(t) 171 } 172 173 select { 174 case <-time.After(50 * time.Millisecond): 175 return 176 177 case call := <-f.onChange: 178 close(call.done) 179 t.Fatalf("Expected only %d OnChange calls. Got: %d", count, count+1) 180 } 181 } 182 183 func (f *fakeSubscriber) assertOnChange(t *testing.T) { 184 t.Helper() 185 186 select { 187 case <-time.After(50 * time.Millisecond): 188 t.Fatalf("timed out waiting for subscriber.OnChange") 189 case call := <-f.onChange: 190 close(call.done) 191 } 192 } 193 194 func (f *fakeSubscriber) OnChange(ctx context.Context, st RStore) { 195 call := onChangeCall{done: make(chan bool)} 196 f.onChange <- call 197 <-call.done 198 } 199 200 func (f *fakeSubscriber) SetUp(ctx context.Context) { 201 f.setupCount++ 202 } 203 204 func (f *fakeSubscriber) TearDown(ctx context.Context) { 205 f.teardownCount++ 206 }