v4 / vlib / x / async / periodic_test.v
422 lines · 391 sloc · 9.68 KB · a66aa5b0fe618b32988f4fd9b235017c336940ba
Raw
1import context
2import time
3import x.async as xasync
4
5fn test_every_runs_at_least_one_iteration() {
6 parent_ctx, cancel := xasync.with_cancel()
7 ran := chan bool{cap: 1}
8 result := chan string{cap: 1}
9 worker := spawn fn [parent_ctx, ran, result] () {
10 xasync.every(parent_ctx, 5 * time.millisecond, fn [ran] (mut ctx context.Context) ! {
11 _ = ctx
12 ran <- true
13 }) or {
14 result <- err.msg()
15 return
16 }
17 result <- 'ok'
18 }()
19
20 select {
21 did_run := <-ran {
22 assert did_run
23 }
24 1 * time.second {
25 assert false, 'periodic job did not run'
26 }
27 }
28 cancel()
29 select {
30 msg := <-result {
31 assert msg == 'context canceled'
32 }
33 1 * time.second {
34 assert false, 'every did not stop after cancellation'
35 }
36 }
37 worker.wait()
38}
39
40fn test_every_stops_on_cancellation() {
41 parent_ctx, cancel := xasync.with_cancel()
42 entered := chan bool{cap: 1}
43 result := chan string{cap: 1}
44 worker := spawn fn [parent_ctx, entered, result] () {
45 entered <- true
46 xasync.every(parent_ctx, 1 * time.second, fn (mut ctx context.Context) ! {
47 _ = ctx
48 }) or {
49 result <- err.msg()
50 return
51 }
52 result <- 'ok'
53 }()
54
55 select {
56 did_enter := <-entered {
57 assert did_enter
58 }
59 1 * time.second {
60 assert false, 'every worker did not start'
61 }
62 }
63 cancel()
64 select {
65 msg := <-result {
66 assert msg == 'context canceled'
67 }
68 1 * time.second {
69 assert false, 'every did not stop on context cancellation'
70 }
71 }
72 worker.wait()
73}
74
75fn test_every_returns_immediately_when_parent_is_already_canceled() {
76 parent_ctx, cancel := xasync.with_cancel()
77 cancel()
78 xasync.every(parent_ctx, 1 * time.second, fn (mut ctx context.Context) ! {
79 _ = ctx
80 }) or {
81 assert err.msg() == 'context canceled'
82 return
83 }
84 assert false
85}
86
87fn test_every_returns_iteration_error() {
88 parent_ctx, cancel := xasync.with_cancel()
89 result := chan string{cap: 1}
90 worker := spawn fn [parent_ctx, result] () {
91 xasync.every(parent_ctx, 5 * time.millisecond, fn (mut ctx context.Context) ! {
92 _ = ctx
93 return error('periodic failed')
94 }) or {
95 result <- err.msg()
96 return
97 }
98 result <- 'ok'
99 }()
100
101 select {
102 msg := <-result {
103 assert msg == 'periodic failed'
104 }
105 1 * time.second {
106 assert false, 'every did not return the periodic job error'
107 }
108 }
109 cancel()
110 worker.wait()
111}
112
113fn test_every_rejects_zero_interval() {
114 xasync.every(context.background(), 0 * time.millisecond, fn (mut ctx context.Context) ! {
115 _ = ctx
116 }) or {
117 assert err.msg() == 'async: interval must be positive'
118 return
119 }
120 assert false
121}
122
123fn test_every_rejects_negative_interval() {
124 xasync.every(context.background(), -1 * time.millisecond, fn (mut ctx context.Context) ! {
125 _ = ctx
126 }) or {
127 assert err.msg() == 'async: interval must be positive'
128 return
129 }
130 assert false
131}
132
133fn test_every_rejects_nil_job() {
134 nil_job := unsafe { xasync.JobFn(nil) }
135 xasync.every(context.background(), 1 * time.second, nil_job) or {
136 assert err.msg() == 'async: job function is nil'
137 return
138 }
139 assert false
140}
141
142fn test_every_does_not_overlap_iterations() {
143 parent_ctx, cancel := xasync.with_cancel()
144 active := chan bool{cap: 1}
145 active <- true
146 entered := chan bool{cap: 2}
147 release := chan bool{cap: 2}
148 overlap := chan bool{cap: 1}
149 result := chan string{cap: 1}
150 worker := spawn fn [parent_ctx, active, entered, release, overlap, result] () {
151 xasync.every(parent_ctx, 5 * time.millisecond, fn [active, entered, release, overlap] (mut ctx context.Context) ! {
152 select {
153 _ := <-active {}
154 else {
155 overlap <- true
156 return error('periodic overlap')
157 }
158 }
159 entered <- true
160 done := ctx.done()
161 select {
162 _ := <-release {}
163 _ := <-done {
164 active <- true
165 return ctx.err()
166 }
167 }
168 active <- true
169 }) or {
170 result <- err.msg()
171 return
172 }
173 result <- 'ok'
174 }()
175
176 wait_for_periodic_entry(entered)
177 select {
178 _ := <-entered {
179 assert false, 'periodic iterations overlapped while first job was still running'
180 }
181 50 * time.millisecond {}
182 }
183 select {
184 did_overlap := <-overlap {
185 assert !did_overlap
186 }
187 else {}
188 }
189
190 release <- true
191 wait_for_periodic_entry(entered)
192 cancel()
193 release <- true
194 select {
195 msg := <-result {
196 assert msg == 'context canceled'
197 }
198 1 * time.second {
199 assert false, 'every did not stop after non-overlap test cancellation'
200 }
201 }
202 worker.wait()
203}
204
205fn test_start_every_rejects_invalid_interval() {
206 xasync.start_every(context.background(), 0 * time.millisecond, fn (mut ctx context.Context) ! {
207 _ = ctx
208 }) or {
209 assert err.msg() == 'async: interval must be positive'
210 return
211 }
212 assert false
213}
214
215fn test_start_every_rejects_negative_interval() {
216 xasync.start_every(context.background(), -1 * time.millisecond, fn (mut ctx context.Context) ! {
217 _ = ctx
218 }) or {
219 assert err.msg() == 'async: interval must be positive'
220 return
221 }
222 assert false
223}
224
225fn test_start_every_rejects_nil_job() {
226 nil_job := unsafe { xasync.JobFn(nil) }
227 xasync.start_every(context.background(), 1 * time.second, nil_job) or {
228 assert err.msg() == 'async: job function is nil'
229 return
230 }
231 assert false
232}
233
234fn test_start_every_returns_immediately_when_parent_is_already_canceled() {
235 parent_ctx, cancel := xasync.with_cancel()
236 cancel()
237 xasync.start_every(parent_ctx, 1 * time.second, fn (mut ctx context.Context) ! {
238 _ = ctx
239 }) or {
240 assert err.msg() == 'context canceled'
241 return
242 }
243 assert false
244}
245
246fn test_periodic_handle_stop_before_first_iteration() {
247 ran := chan bool{cap: 1}
248 mut handle := xasync.start_every(context.background(), 1 * time.second, fn [ran] (mut ctx context.Context) ! {
249 _ = ctx
250 ran <- true
251 })!
252 handle.stop()
253 handle.wait()!
254 assert_no_periodic_signal(ran, 'periodic handle ran before first interval after stop')
255}
256
257fn test_periodic_handle_stop_between_ticks() {
258 ticks := chan bool{cap: 2}
259 mut handle := xasync.start_every(context.background(), 5 * time.millisecond, fn [ticks] (mut ctx context.Context) ! {
260 _ = ctx
261 ticks <- true
262 })!
263 wait_for_periodic_entry(ticks)
264 handle.stop()
265 handle.wait()!
266 assert_no_periodic_signal(ticks, 'periodic handle ticked again after stop and wait')
267}
268
269fn test_periodic_handle_stop_during_long_job_waits_for_job_to_return() {
270 entered := chan bool{cap: 1}
271 release := chan bool{cap: 1}
272 waited := chan bool{cap: 1}
273 mut handle := xasync.start_every(context.background(), 5 * time.millisecond, fn [entered, release] (mut ctx context.Context) ! {
274 _ = ctx
275 entered <- true
276 _ := <-release
277 })!
278 wait_for_periodic_entry(entered)
279 handle.stop()
280
281 wait_thread := spawn fn [mut handle, waited] () {
282 handle.wait() or {
283 waited <- false
284 return
285 }
286 waited <- true
287 }()
288 assert_no_periodic_signal(waited, 'periodic handle wait returned before long job completed')
289 release <- true
290 wait_for_periodic_entry(waited)
291 wait_thread.wait()
292}
293
294fn test_periodic_handle_wait_returns_job_error() {
295 mut handle := xasync.start_every(context.background(), 5 * time.millisecond, fn (mut ctx context.Context) ! {
296 _ = ctx
297 return error('periodic handle failed')
298 })!
299 handle.wait() or {
300 assert err.msg() == 'periodic handle failed'
301 return
302 }
303 assert false
304}
305
306fn test_periodic_handle_wait_returns_job_context_canceled_error_after_stop() {
307 entered := chan bool{cap: 1}
308 release := chan bool{cap: 1}
309 mut handle := xasync.start_every(context.background(), 5 * time.millisecond, fn [entered, release] (mut ctx context.Context) ! {
310 _ = ctx
311 entered <- true
312 _ := <-release
313 return error('context canceled')
314 })!
315 wait_for_periodic_entry(entered)
316 handle.stop()
317 release <- true
318 handle.wait() or {
319 assert err.msg() == 'context canceled'
320 return
321 }
322 assert false, 'job-returned context canceled error was treated as normal stop'
323}
324
325fn test_periodic_handle_wait_returns_parent_cancellation() {
326 parent_ctx, cancel := xasync.with_cancel()
327 mut handle := xasync.start_every(parent_ctx, 1 * time.second, fn (mut ctx context.Context) ! {
328 _ = ctx
329 })!
330 cancel()
331 handle.wait() or {
332 assert err.msg() == 'context canceled'
333 return
334 }
335 assert false
336}
337
338fn test_periodic_handle_stop_after_parent_cancel_keeps_parent_error() {
339 parent_ctx, cancel := xasync.with_cancel()
340 mut handle := xasync.start_every(parent_ctx, 1 * time.second, fn (mut ctx context.Context) ! {
341 _ = ctx
342 })!
343 cancel()
344 handle.stop()
345 handle.wait() or {
346 assert err.msg() == 'context canceled'
347 return
348 }
349 assert false
350}
351
352fn test_periodic_handle_does_not_overlap_iterations() {
353 active := chan bool{cap: 1}
354 active <- true
355 entered := chan bool{cap: 2}
356 release := chan bool{cap: 2}
357 overlap := chan bool{cap: 1}
358 mut handle := xasync.start_every(context.background(), 5 * time.millisecond, fn [active, entered, release, overlap] (mut ctx context.Context) ! {
359 select {
360 _ := <-active {}
361 else {
362 overlap <- true
363 return error('periodic handle overlap')
364 }
365 }
366 entered <- true
367 _ = ctx
368 _ := <-release
369 active <- true
370 })!
371
372 wait_for_periodic_entry(entered)
373 assert_no_periodic_signal(entered,
374 'periodic handle iterations overlapped while first job was still running')
375 select {
376 did_overlap := <-overlap {
377 assert !did_overlap
378 }
379 else {}
380 }
381
382 release <- true
383 wait_for_periodic_entry(entered)
384 handle.stop()
385 release <- true
386 handle.wait()!
387 assert_no_periodic_signal(entered, 'periodic handle started another iteration after stop')
388}
389
390fn test_periodic_handle_double_stop_and_wait() {
391 mut handle := xasync.start_every(context.background(), 1 * time.second, fn (mut ctx context.Context) ! {
392 _ = ctx
393 })!
394 handle.stop()
395 handle.stop()
396 handle.wait()!
397 handle.wait() or {
398 assert err.msg() == 'async: periodic wait was already called'
399 return
400 }
401 assert false
402}
403
404fn wait_for_periodic_entry(entered chan bool) {
405 select {
406 did_enter := <-entered {
407 assert did_enter
408 }
409 1 * time.second {
410 assert false, 'periodic job did not enter'
411 }
412 }
413}
414
415fn assert_no_periodic_signal(signal chan bool, message string) {
416 select {
417 _ := <-signal {
418 assert false, message
419 }
420 50 * time.millisecond {}
421 }
422}
423