blob: f4feb338dde9c67568e041c0f6962cddbaff7c9c [file] [log] [blame]
Serge Bazanskic00318e2021-03-03 12:39:24 +01001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
Serge Bazanski68ca5ee2021-04-27 16:09:16 +020017package memory
Serge Bazanskic00318e2021-03-03 12:39:24 +010018
19import (
20 "context"
21 "fmt"
22 "sync"
23 "sync/atomic"
24 "testing"
25 "time"
26)
27
Serge Bazanski68ca5ee2021-04-27 16:09:16 +020028// TestAsync exercises the high-level behaviour of a Value, in which a
Serge Bazanskic00318e2021-03-03 12:39:24 +010029// watcher is able to catch up to the newest Set value.
30func TestAsync(t *testing.T) {
Serge Bazanski68ca5ee2021-04-27 16:09:16 +020031 p := Value{}
Serge Bazanskic00318e2021-03-03 12:39:24 +010032 p.Set(0)
33
34 ctx := context.Background()
35
36 // The 0 from Set() should be available via .Get().
37 watcher := p.Watch()
38 val, err := watcher.Get(ctx)
39 if err != nil {
40 t.Fatalf("Get: %v", err)
41 }
42 if want, got := 0, val.(int); want != got {
43 t.Fatalf("Value: got %d, wanted %d", got, want)
44 }
45
46 // Send a large amount of updates that the watcher does not actively .Get().
47 for i := 1; i <= 100; i++ {
48 p.Set(i)
49 }
50
51 // The watcher should still end up with the newest .Set() value on the next
52 // .Get() call.
53 val, err = watcher.Get(ctx)
54 if err != nil {
55 t.Fatalf("Get: %v", err)
56 }
57 if want, got := 100, val.(int); want != got {
58 t.Fatalf("Value: got %d, wanted %d", got, want)
59 }
60}
61
Serge Bazanski68ca5ee2021-04-27 16:09:16 +020062// TestSyncBlocks exercises the Value's 'Sync' field, which makes all
Serge Bazanskic00318e2021-03-03 12:39:24 +010063// Set() calls block until all respective watchers .Get() the updated data.
64// This particular test ensures that .Set() calls to a Watcher result in a
65// prefect log of updates being transmitted to a watcher.
66func TestSync(t *testing.T) {
Serge Bazanski68ca5ee2021-04-27 16:09:16 +020067 p := Value{
Serge Bazanskic00318e2021-03-03 12:39:24 +010068 Sync: true,
69 }
70 values := make(chan int, 100)
71 wg := sync.WaitGroup{}
72 wg.Add(1)
73 go func() {
74 ctx := context.Background()
75 watcher := p.Watch()
76 wg.Done()
77 for {
78 value, err := watcher.Get(ctx)
79 if err != nil {
80 panic(err)
81 }
82 values <- value.(int)
83 }
84 }()
85
86 p.Set(0)
87 wg.Wait()
88
89 want := []int{1, 2, 3, 4}
90 for _, w := range want {
91 p.Set(w)
92 }
93
94 timeout := time.After(time.Second)
95 for i, w := range append([]int{0}, want...) {
96 select {
97 case <-timeout:
98 t.Fatalf("timed out on value %d (%d)", i, w)
99 case val := <-values:
100 if w != val {
101 t.Errorf("value %d was %d, wanted %d", i, val, w)
102 }
103 }
104 }
105}
106
Serge Bazanski68ca5ee2021-04-27 16:09:16 +0200107// TestSyncBlocks exercises the Value's 'Sync' field, which makes all
Serge Bazanskic00318e2021-03-03 12:39:24 +0100108// Set() calls block until all respective watchers .Get() the updated data.
109// This particular test ensures that .Set() calls actually block when a watcher
110// is unattended.
111func TestSyncBlocks(t *testing.T) {
Serge Bazanski68ca5ee2021-04-27 16:09:16 +0200112 p := Value{
Serge Bazanskic00318e2021-03-03 12:39:24 +0100113 Sync: true,
114 }
115 ctx := context.Background()
116
117 // Shouldn't block, as there's no declared watchers.
118 p.Set(0)
119
120 watcher := p.Watch()
121
122 // Should retrieve the zero, more requests will pend.
123 value, err := watcher.Get(ctx)
124 if err != nil {
125 t.Fatalf("Get: %v", err)
126 }
127 if want, got := 0, value.(int); want != got {
128 t.Fatalf("Got initial value %d, wanted %d", got, want)
129 }
130
131 // .Set() Should block, as watcher is unattended.
132 //
133 // Whether something blocks in Go is untestable in a robust way (see: halting
134 // problem). We work around this this by introducing a 'stage' int64, which is
135 // put on the 'c' channel after the needs-to-block function returns. We then
136 // perform an action that should unblock this function right after updating
137 // 'stage' to a different value.
138 // Then, we observe what was put on the channel: If it's the initial value, it
139 // means the function didn't block when expected. Otherwise, it means the
140 // function unblocked when expected.
141 stage := int64(0)
142 c := make(chan int64, 1)
143 go func() {
144 p.Set(1)
145 c <- atomic.LoadInt64(&stage)
146 }()
147
148 // Getting should unblock the provider. Mark via 'stage' variable that
149 // unblocking now is expected.
150 atomic.StoreInt64(&stage, int64(1))
151 // Potential race: .Set() unblocks here due to some bug, before .Get() is
152 // called, and we record a false positive.
153 value, err = watcher.Get(ctx)
154 if err != nil {
155 t.Fatalf("Get: %v", err)
156 }
157
158 res := <-c
159 if res != int64(1) {
160 t.Fatalf("Set() returned before Get()")
161 }
162
163 if want, got := 1, value.(int); want != got {
164 t.Fatalf("Wanted value %d, got %d", want, got)
165 }
166
167 // Closing the watcher and setting should not block anymore.
168 if err := watcher.Close(); err != nil {
169 t.Fatalf("Close: %v", err)
170 }
171 // Last step, if this blocks we will get a deadlock error and the test will panic.
172 p.Set(2)
173}
174
175// TestMultipleGets verifies that calling .Get() on a single watcher from two
176// goroutines is prevented by returning an error in exactly one of them.
177func TestMultipleGets(t *testing.T) {
Serge Bazanski68ca5ee2021-04-27 16:09:16 +0200178 p := Value{}
Serge Bazanskic00318e2021-03-03 12:39:24 +0100179 ctx := context.Background()
180
181 w := p.Watch()
182
183 tryError := func(errs chan error) {
184 _, err := w.Get(ctx)
185 errs <- err
186 }
187 errs := make(chan error, 2)
188 go tryError(errs)
189 go tryError(errs)
190
191 for err := range errs {
192 if err == nil {
193 t.Fatalf("A Get call succeeded, while it should have blocked or returned an error")
194 } else {
195 // Found the error, test succeeded.
196 break
197 }
198 }
199}
200
Serge Bazanski68ca5ee2021-04-27 16:09:16 +0200201// TestConcurrency attempts to stress the Value/Watcher
Serge Bazanskic00318e2021-03-03 12:39:24 +0100202// implementation to design limits (a hundred simultaneous watchers), ensuring
203// that the watchers all settle to the final set value.
204func TestConcurrency(t *testing.T) {
205 ctx := context.Background()
206
Serge Bazanski68ca5ee2021-04-27 16:09:16 +0200207 p := Value{}
Serge Bazanskic00318e2021-03-03 12:39:24 +0100208 p.Set(0)
209
210 // Number of watchers to create.
211 watcherN := 100
212 // Expected final value to be Set().
213 final := 100
214 // Result channel per watcher.
215 resC := make([]chan error, watcherN)
216
217 // Spawn watcherN watchers.
218 for i := 0; i < watcherN; i++ {
219 resC[i] = make(chan error, 1)
220 go func(id int) {
221 // done is a helper function that will put an error on the
222 // respective watcher's resC.
223 done := func(err error) {
224 resC[id] <- err
225 close(resC[id])
226 }
227
228 watcher := p.Watch()
229 // prev is used to ensure the values received are monotonic.
230 prev := -1
231 for {
232 val, err := watcher.Get(ctx)
233 if err != nil {
234 done(err)
235 return
236 }
237
238 // Ensure monotonicity of received data.
239 if val.(int) <= prev {
240 done(fmt.Errorf("received out of order data: %d after %d", val, prev))
241 }
242 prev = val.(int)
243
244 // Quit when the final value is received.
245 if val == final {
246 done(nil)
247 return
248 }
249
250 // Sleep a bit, depending on the watcher. This makes each
251 // watcher behave slightly differently, and attempts to
252 // exercise races dependent on sleep time between subsequent
253 // Get calls.
254 time.Sleep(time.Millisecond * time.Duration(id))
255 }
256 }(i)
257 }
258
259 // Set 1..final on the value.
260 for i := 1; i <= final; i++ {
261 p.Set(i)
262 }
263
264 // Ensure all watchers exit with no error.
265 for i, c := range resC {
266 err := <-c
267 if err != nil {
268 t.Errorf("Watcher %d returned %v", i, err)
269 }
270 }
271}
272
273// TestCanceling exercises whether a context canceling in a .Get() gracefully
274// aborts that particular Get call, but also allows subsequent use of the same
275// watcher.
276func TestCanceling(t *testing.T) {
Serge Bazanski68ca5ee2021-04-27 16:09:16 +0200277 p := Value{
Serge Bazanskic00318e2021-03-03 12:39:24 +0100278 Sync: true,
279 }
280
281 ctx, ctxC := context.WithCancel(context.Background())
282
283 watcher := p.Watch()
284
285 // errs will contain the error returned by Get.
286 errs := make(chan error, 1)
287 go func() {
288 // This Get will block, as no initial data has been Set on the value.
289 _, err := watcher.Get(ctx)
290 errs <- err
291 }()
292
293 // Cancel the context, and expect that context error to propagate to the .Get().
294 ctxC()
295 if want, got := ctx.Err(), <-errs; want != got {
296 t.Fatalf("Get should've returned %v, got %v", want, got)
297 }
298
299 // Do another .Get() on the same watcher with a new context. Even though the
300 // call was aborted via a context cancel, the watcher should continue working.
301 ctx = context.Background()
302 go func() {
303 _, err := watcher.Get(ctx)
304 errs <- err
305 }()
306
307 // Unblock the .Get now.
308 p.Set(1)
309 if want, got := error(nil), <-errs; want != got {
310 t.Fatalf("Get should've returned %v, got %v", want, got)
311 }
312}
313
314// TestSetAfterWatch ensures that if a value is updated between a Watch and the
315// initial Get, only the newest Set value is returns.
316func TestSetAfterWatch(t *testing.T) {
317 ctx := context.Background()
318
Serge Bazanski68ca5ee2021-04-27 16:09:16 +0200319 p := Value{}
Serge Bazanskic00318e2021-03-03 12:39:24 +0100320 p.Set(0)
321
322 watcher := p.Watch()
323 p.Set(1)
324
325 data, err := watcher.Get(ctx)
326 if err != nil {
327 t.Fatalf("Get: %v", err)
328 }
329 if want, got := 1, data.(int); want != got {
330 t.Errorf("Get should've returned %v, got %v", want, got)
331 }
332}