blob: b7d168bb6f025b31e987a065bbe48ef7eef6604d [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"
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +020021 "errors"
Serge Bazanskic00318e2021-03-03 12:39:24 +010022 "fmt"
Serge Bazanskic00318e2021-03-03 12:39:24 +010023 "testing"
24 "time"
Jan Schär36bde9c2024-03-19 15:05:33 +010025
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020026 "source.monogon.dev/osbase/event"
Serge Bazanskic00318e2021-03-03 12:39:24 +010027)
28
Serge Bazanski68ca5ee2021-04-27 16:09:16 +020029// TestAsync exercises the high-level behaviour of a Value, in which a
Serge Bazanskic00318e2021-03-03 12:39:24 +010030// watcher is able to catch up to the newest Set value.
31func TestAsync(t *testing.T) {
Serge Bazanski37110c32023-03-01 13:57:27 +000032 p := Value[int]{}
Serge Bazanskic00318e2021-03-03 12:39:24 +010033 p.Set(0)
34
35 ctx := context.Background()
36
37 // The 0 from Set() should be available via .Get().
38 watcher := p.Watch()
39 val, err := watcher.Get(ctx)
40 if err != nil {
41 t.Fatalf("Get: %v", err)
42 }
Serge Bazanski37110c32023-03-01 13:57:27 +000043 if want, got := 0, val; want != got {
Serge Bazanskic00318e2021-03-03 12:39:24 +010044 t.Fatalf("Value: got %d, wanted %d", got, want)
45 }
46
47 // Send a large amount of updates that the watcher does not actively .Get().
48 for i := 1; i <= 100; i++ {
49 p.Set(i)
50 }
51
52 // The watcher should still end up with the newest .Set() value on the next
53 // .Get() call.
54 val, err = watcher.Get(ctx)
55 if err != nil {
56 t.Fatalf("Get: %v", err)
57 }
Serge Bazanski37110c32023-03-01 13:57:27 +000058 if want, got := 100, val; want != got {
Serge Bazanskic00318e2021-03-03 12:39:24 +010059 t.Fatalf("Value: got %d, wanted %d", got, want)
60 }
61}
62
Serge Bazanskic00318e2021-03-03 12:39:24 +010063// TestMultipleGets verifies that calling .Get() on a single watcher from two
64// goroutines is prevented by returning an error in exactly one of them.
65func TestMultipleGets(t *testing.T) {
Serge Bazanski37110c32023-03-01 13:57:27 +000066 p := Value[int]{}
Serge Bazanskic00318e2021-03-03 12:39:24 +010067 ctx := context.Background()
68
69 w := p.Watch()
70
71 tryError := func(errs chan error) {
72 _, err := w.Get(ctx)
73 errs <- err
74 }
75 errs := make(chan error, 2)
76 go tryError(errs)
77 go tryError(errs)
78
79 for err := range errs {
80 if err == nil {
81 t.Fatalf("A Get call succeeded, while it should have blocked or returned an error")
82 } else {
83 // Found the error, test succeeded.
84 break
85 }
86 }
87}
88
Serge Bazanski68ca5ee2021-04-27 16:09:16 +020089// TestConcurrency attempts to stress the Value/Watcher
Serge Bazanskic00318e2021-03-03 12:39:24 +010090// implementation to design limits (a hundred simultaneous watchers), ensuring
91// that the watchers all settle to the final set value.
92func TestConcurrency(t *testing.T) {
93 ctx := context.Background()
94
Serge Bazanski37110c32023-03-01 13:57:27 +000095 p := Value[int]{}
Serge Bazanskic00318e2021-03-03 12:39:24 +010096 p.Set(0)
97
98 // Number of watchers to create.
99 watcherN := 100
100 // Expected final value to be Set().
101 final := 100
102 // Result channel per watcher.
103 resC := make([]chan error, watcherN)
104
105 // Spawn watcherN watchers.
106 for i := 0; i < watcherN; i++ {
107 resC[i] = make(chan error, 1)
108 go func(id int) {
109 // done is a helper function that will put an error on the
110 // respective watcher's resC.
111 done := func(err error) {
112 resC[id] <- err
113 close(resC[id])
114 }
115
116 watcher := p.Watch()
117 // prev is used to ensure the values received are monotonic.
118 prev := -1
119 for {
120 val, err := watcher.Get(ctx)
121 if err != nil {
122 done(err)
123 return
124 }
125
126 // Ensure monotonicity of received data.
Serge Bazanski37110c32023-03-01 13:57:27 +0000127 if val <= prev {
Serge Bazanskic00318e2021-03-03 12:39:24 +0100128 done(fmt.Errorf("received out of order data: %d after %d", val, prev))
129 }
Serge Bazanski37110c32023-03-01 13:57:27 +0000130 prev = val
Serge Bazanskic00318e2021-03-03 12:39:24 +0100131
132 // Quit when the final value is received.
133 if val == final {
134 done(nil)
135 return
136 }
137
138 // Sleep a bit, depending on the watcher. This makes each
139 // watcher behave slightly differently, and attempts to
140 // exercise races dependent on sleep time between subsequent
141 // Get calls.
142 time.Sleep(time.Millisecond * time.Duration(id))
143 }
144 }(i)
145 }
146
147 // Set 1..final on the value.
148 for i := 1; i <= final; i++ {
149 p.Set(i)
150 }
151
152 // Ensure all watchers exit with no error.
153 for i, c := range resC {
154 err := <-c
155 if err != nil {
156 t.Errorf("Watcher %d returned %v", i, err)
157 }
158 }
159}
160
161// TestCanceling exercises whether a context canceling in a .Get() gracefully
162// aborts that particular Get call, but also allows subsequent use of the same
163// watcher.
164func TestCanceling(t *testing.T) {
Jan Schär5b997a12024-12-19 15:10:07 +0100165 p := Value[int]{}
Serge Bazanskic00318e2021-03-03 12:39:24 +0100166
167 ctx, ctxC := context.WithCancel(context.Background())
168
169 watcher := p.Watch()
170
171 // errs will contain the error returned by Get.
172 errs := make(chan error, 1)
173 go func() {
174 // This Get will block, as no initial data has been Set on the value.
175 _, err := watcher.Get(ctx)
176 errs <- err
177 }()
178
179 // Cancel the context, and expect that context error to propagate to the .Get().
180 ctxC()
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200181 if want, got := ctx.Err(), <-errs; !errors.Is(got, want) {
Serge Bazanskic00318e2021-03-03 12:39:24 +0100182 t.Fatalf("Get should've returned %v, got %v", want, got)
183 }
184
185 // Do another .Get() on the same watcher with a new context. Even though the
186 // call was aborted via a context cancel, the watcher should continue working.
187 ctx = context.Background()
188 go func() {
189 _, err := watcher.Get(ctx)
190 errs <- err
191 }()
192
193 // Unblock the .Get now.
194 p.Set(1)
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200195 if want, got := error(nil), <-errs; !errors.Is(got, want) {
Serge Bazanskic00318e2021-03-03 12:39:24 +0100196 t.Fatalf("Get should've returned %v, got %v", want, got)
197 }
198}
199
200// TestSetAfterWatch ensures that if a value is updated between a Watch and the
201// initial Get, only the newest Set value is returns.
202func TestSetAfterWatch(t *testing.T) {
203 ctx := context.Background()
204
Serge Bazanski37110c32023-03-01 13:57:27 +0000205 p := Value[int]{}
Serge Bazanskic00318e2021-03-03 12:39:24 +0100206 p.Set(0)
207
208 watcher := p.Watch()
209 p.Set(1)
210
211 data, err := watcher.Get(ctx)
212 if err != nil {
213 t.Fatalf("Get: %v", err)
214 }
Serge Bazanski37110c32023-03-01 13:57:27 +0000215 if want, got := 1, data; want != got {
Serge Bazanskic00318e2021-03-03 12:39:24 +0100216 t.Errorf("Get should've returned %v, got %v", want, got)
217 }
218}
Jan Schär36bde9c2024-03-19 15:05:33 +0100219
220// TestWatchersList ensures that the list of watchers is managed correctly,
221// i.e. there is no memory leak and closed watchers are removed while
222// keeping all non-closed watchers.
223func TestWatchersList(t *testing.T) {
224 ctx := context.Background()
225 p := Value[int]{}
226
227 var watchers []event.Watcher[int]
228 for i := 0; i < 100; i++ {
229 watchers = append(watchers, p.Watch())
230 }
231 for i := 0; i < 10000; i++ {
232 watchers[10].Close()
233 watchers[10] = p.Watch()
234 }
235
236 if want, got := 1000, cap(p.watchers); want <= got {
237 t.Fatalf("Got capacity %d, wanted less than %d", got, want)
238 }
239
240 p.Set(1)
241 if want, got := 100, len(p.watchers); want != got {
242 t.Fatalf("Got %d watchers, wanted %d", got, want)
243 }
244
245 for _, watcher := range watchers {
246 data, err := watcher.Get(ctx)
247 if err != nil {
248 t.Fatalf("Get: %v", err)
249 }
250 if want, got := 1, data; want != got {
251 t.Errorf("Get should've returned %v, got %v", want, got)
252 }
253 }
254}