blob: c98eb424595a620b34f6bfd734b7f50588b311bb [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Serge Bazanskic00318e2021-03-03 12:39:24 +01002// SPDX-License-Identifier: Apache-2.0
Serge Bazanskic00318e2021-03-03 12:39:24 +01003
Serge Bazanski68ca5ee2021-04-27 16:09:16 +02004package memory
Serge Bazanskic00318e2021-03-03 12:39:24 +01005
6import (
7 "context"
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +02008 "errors"
Serge Bazanskic00318e2021-03-03 12:39:24 +01009 "fmt"
Serge Bazanskic00318e2021-03-03 12:39:24 +010010 "testing"
11 "time"
Jan Schär36bde9c2024-03-19 15:05:33 +010012
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020013 "source.monogon.dev/osbase/event"
Serge Bazanskic00318e2021-03-03 12:39:24 +010014)
15
Serge Bazanski68ca5ee2021-04-27 16:09:16 +020016// TestAsync exercises the high-level behaviour of a Value, in which a
Serge Bazanskic00318e2021-03-03 12:39:24 +010017// watcher is able to catch up to the newest Set value.
18func TestAsync(t *testing.T) {
Serge Bazanski37110c32023-03-01 13:57:27 +000019 p := Value[int]{}
Serge Bazanskic00318e2021-03-03 12:39:24 +010020 p.Set(0)
21
22 ctx := context.Background()
23
24 // The 0 from Set() should be available via .Get().
25 watcher := p.Watch()
26 val, err := watcher.Get(ctx)
27 if err != nil {
28 t.Fatalf("Get: %v", err)
29 }
Serge Bazanski37110c32023-03-01 13:57:27 +000030 if want, got := 0, val; want != got {
Serge Bazanskic00318e2021-03-03 12:39:24 +010031 t.Fatalf("Value: got %d, wanted %d", got, want)
32 }
33
34 // Send a large amount of updates that the watcher does not actively .Get().
35 for i := 1; i <= 100; i++ {
36 p.Set(i)
37 }
38
39 // The watcher should still end up with the newest .Set() value on the next
40 // .Get() call.
41 val, err = watcher.Get(ctx)
42 if err != nil {
43 t.Fatalf("Get: %v", err)
44 }
Serge Bazanski37110c32023-03-01 13:57:27 +000045 if want, got := 100, val; want != got {
Serge Bazanskic00318e2021-03-03 12:39:24 +010046 t.Fatalf("Value: got %d, wanted %d", got, want)
47 }
48}
49
Serge Bazanskic00318e2021-03-03 12:39:24 +010050// TestMultipleGets verifies that calling .Get() on a single watcher from two
51// goroutines is prevented by returning an error in exactly one of them.
52func TestMultipleGets(t *testing.T) {
Serge Bazanski37110c32023-03-01 13:57:27 +000053 p := Value[int]{}
Serge Bazanskic00318e2021-03-03 12:39:24 +010054 ctx := context.Background()
55
56 w := p.Watch()
57
58 tryError := func(errs chan error) {
59 _, err := w.Get(ctx)
60 errs <- err
61 }
62 errs := make(chan error, 2)
63 go tryError(errs)
64 go tryError(errs)
65
66 for err := range errs {
67 if err == nil {
68 t.Fatalf("A Get call succeeded, while it should have blocked or returned an error")
69 } else {
70 // Found the error, test succeeded.
71 break
72 }
73 }
74}
75
Serge Bazanski68ca5ee2021-04-27 16:09:16 +020076// TestConcurrency attempts to stress the Value/Watcher
Serge Bazanskic00318e2021-03-03 12:39:24 +010077// implementation to design limits (a hundred simultaneous watchers), ensuring
78// that the watchers all settle to the final set value.
79func TestConcurrency(t *testing.T) {
80 ctx := context.Background()
81
Serge Bazanski37110c32023-03-01 13:57:27 +000082 p := Value[int]{}
Serge Bazanskic00318e2021-03-03 12:39:24 +010083 p.Set(0)
84
85 // Number of watchers to create.
86 watcherN := 100
87 // Expected final value to be Set().
88 final := 100
89 // Result channel per watcher.
90 resC := make([]chan error, watcherN)
91
92 // Spawn watcherN watchers.
93 for i := 0; i < watcherN; i++ {
94 resC[i] = make(chan error, 1)
95 go func(id int) {
96 // done is a helper function that will put an error on the
97 // respective watcher's resC.
98 done := func(err error) {
99 resC[id] <- err
100 close(resC[id])
101 }
102
103 watcher := p.Watch()
104 // prev is used to ensure the values received are monotonic.
105 prev := -1
106 for {
107 val, err := watcher.Get(ctx)
108 if err != nil {
109 done(err)
110 return
111 }
112
113 // Ensure monotonicity of received data.
Serge Bazanski37110c32023-03-01 13:57:27 +0000114 if val <= prev {
Serge Bazanskic00318e2021-03-03 12:39:24 +0100115 done(fmt.Errorf("received out of order data: %d after %d", val, prev))
116 }
Serge Bazanski37110c32023-03-01 13:57:27 +0000117 prev = val
Serge Bazanskic00318e2021-03-03 12:39:24 +0100118
119 // Quit when the final value is received.
120 if val == final {
121 done(nil)
122 return
123 }
124
125 // Sleep a bit, depending on the watcher. This makes each
126 // watcher behave slightly differently, and attempts to
127 // exercise races dependent on sleep time between subsequent
128 // Get calls.
129 time.Sleep(time.Millisecond * time.Duration(id))
130 }
131 }(i)
132 }
133
134 // Set 1..final on the value.
135 for i := 1; i <= final; i++ {
136 p.Set(i)
137 }
138
139 // Ensure all watchers exit with no error.
140 for i, c := range resC {
141 err := <-c
142 if err != nil {
143 t.Errorf("Watcher %d returned %v", i, err)
144 }
145 }
146}
147
148// TestCanceling exercises whether a context canceling in a .Get() gracefully
149// aborts that particular Get call, but also allows subsequent use of the same
150// watcher.
151func TestCanceling(t *testing.T) {
Jan Schär5b997a12024-12-19 15:10:07 +0100152 p := Value[int]{}
Serge Bazanskic00318e2021-03-03 12:39:24 +0100153
154 ctx, ctxC := context.WithCancel(context.Background())
155
156 watcher := p.Watch()
157
158 // errs will contain the error returned by Get.
159 errs := make(chan error, 1)
160 go func() {
161 // This Get will block, as no initial data has been Set on the value.
162 _, err := watcher.Get(ctx)
163 errs <- err
164 }()
165
166 // Cancel the context, and expect that context error to propagate to the .Get().
167 ctxC()
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200168 if want, got := ctx.Err(), <-errs; !errors.Is(got, want) {
Serge Bazanskic00318e2021-03-03 12:39:24 +0100169 t.Fatalf("Get should've returned %v, got %v", want, got)
170 }
171
172 // Do another .Get() on the same watcher with a new context. Even though the
173 // call was aborted via a context cancel, the watcher should continue working.
174 ctx = context.Background()
175 go func() {
176 _, err := watcher.Get(ctx)
177 errs <- err
178 }()
179
180 // Unblock the .Get now.
181 p.Set(1)
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200182 if want, got := error(nil), <-errs; !errors.Is(got, want) {
Serge Bazanskic00318e2021-03-03 12:39:24 +0100183 t.Fatalf("Get should've returned %v, got %v", want, got)
184 }
185}
186
187// TestSetAfterWatch ensures that if a value is updated between a Watch and the
188// initial Get, only the newest Set value is returns.
189func TestSetAfterWatch(t *testing.T) {
190 ctx := context.Background()
191
Serge Bazanski37110c32023-03-01 13:57:27 +0000192 p := Value[int]{}
Serge Bazanskic00318e2021-03-03 12:39:24 +0100193 p.Set(0)
194
195 watcher := p.Watch()
196 p.Set(1)
197
198 data, err := watcher.Get(ctx)
199 if err != nil {
200 t.Fatalf("Get: %v", err)
201 }
Serge Bazanski37110c32023-03-01 13:57:27 +0000202 if want, got := 1, data; want != got {
Serge Bazanskic00318e2021-03-03 12:39:24 +0100203 t.Errorf("Get should've returned %v, got %v", want, got)
204 }
205}
Jan Schär36bde9c2024-03-19 15:05:33 +0100206
207// TestWatchersList ensures that the list of watchers is managed correctly,
208// i.e. there is no memory leak and closed watchers are removed while
209// keeping all non-closed watchers.
210func TestWatchersList(t *testing.T) {
211 ctx := context.Background()
212 p := Value[int]{}
213
214 var watchers []event.Watcher[int]
215 for i := 0; i < 100; i++ {
216 watchers = append(watchers, p.Watch())
217 }
218 for i := 0; i < 10000; i++ {
219 watchers[10].Close()
220 watchers[10] = p.Watch()
221 }
222
223 if want, got := 1000, cap(p.watchers); want <= got {
224 t.Fatalf("Got capacity %d, wanted less than %d", got, want)
225 }
226
227 p.Set(1)
228 if want, got := 100, len(p.watchers); want != got {
229 t.Fatalf("Got %d watchers, wanted %d", got, want)
230 }
231
232 for _, watcher := range watchers {
233 data, err := watcher.Get(ctx)
234 if err != nil {
235 t.Fatalf("Get: %v", err)
236 }
237 if want, got := 1, data; want != got {
238 t.Errorf("Get should've returned %v, got %v", want, got)
239 }
240 }
241}