The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
timer_tests.c
Go to the documentation of this file.
1/*
2 * This library is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2.1 of the License, or (at your option) any later version.
6 *
7 * This library is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with this library; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/** Tests for timer lists
18 *
19 * @file src/lib/util/timer_tests.c
20 *
21 * @copyright 2025 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
22 */
23#include <freeradius-devel/util/acutest.h>
24#include <freeradius-devel/util/acutest_helpers.h>
25#include <freeradius-devel/util/time.h>
26#include <freeradius-devel/util/timer.h>
27
28/** Defines an artificial time source for a test
29 *
30 * Defines _name + _time() and _name + _set() functions.
31 */
32#define TIME_SOURCE(_name) \
33 static fr_time_t _name##_timer = fr_time_wrap(0); \
34 static fr_time_t _name##_time(void) \
35 { \
36 return _name##_timer; \
37 } \
38 static void _name##_set(fr_time_t t) \
39 { \
40 _name##_timer = t; \
41 }
42
43TIME_SOURCE(basic)
44
45/** Verifies time passed in is not 0, that tl is not NULL, and writes true to uctx (must be a bool)
46 *
47 */
48static void timer_cb(fr_timer_list_t *tl, fr_time_t now, void *uctx)
49{
50 bool *fired = (bool *)uctx;
51
52 TEST_CHECK(tl != NULL);
54
55 *fired = true;
56}
57
59{
60 fr_time_t now;
61 fr_timer_t *event1 = NULL, *event1a = NULL, *event2 = NULL, *event3 = NULL, *event4 = NULL, *event5 = NULL, *event6 = NULL;
62 bool event1_fired = false, event1a_fired = false, event2_fired = false, event3_fired = false, event4_fired = false, event5_fired = false, event6_fired = false;
63 int ret;
64
65 /*
66 * Should fire together
67 */
68 ret = fr_timer_in(NULL, tl, &event1, fr_time_delta_from_sec(1), true, timer_cb, &event1_fired);
69 TEST_CHECK(ret == 0);
70
71 ret = fr_timer_in(NULL, tl, &event1a, fr_time_delta_from_sec(1), true, timer_cb, &event1a_fired);
72 TEST_CHECK(ret == 0);
73
74 ret = fr_timer_in(NULL, tl, &event2, fr_time_delta_from_sec(2), true, timer_cb, &event2_fired);
75 TEST_CHECK(ret == 0);
76
77 ret = fr_timer_in(NULL, tl, &event3, fr_time_delta_from_sec(3), true, timer_cb, &event3_fired);
78 TEST_CHECK(ret == 0);
79
80 /*
81 * Will be disarmed before it fires
82 */
83 ret = fr_timer_in(NULL, tl, &event4, fr_time_delta_from_sec(3), true, timer_cb, &event4_fired);
84 TEST_CHECK(ret == 0);
85
86 /*
87 * Will be delete before it fires
88 */
89 ret = fr_timer_in(NULL, tl, &event5, fr_time_delta_from_sec(4), true, timer_cb, &event5_fired);
90 TEST_CHECK(ret == 0);
91
92 ret = fr_timer_in(NULL, tl, &event6, fr_time_delta_from_sec(4), true, timer_cb, &event6_fired);
93 TEST_CHECK(ret == 0);
94
95 /*
96 * No events should have fired yet
97 */
99
100 now = fr_time_from_sec(1);
101
102 /*
103 * First batch of events
104 */
105 TEST_CHECK(fr_timer_list_run(tl, &now) == 2);
106 TEST_CHECK(event1_fired == true);
107 TEST_CHECK(event1a_fired == true);
108 TEST_CHECK(event2_fired == false);
109 TEST_CHECK(event1 == NULL);
110 TEST_CHECK(event1a == NULL);
111
112 /*
113 * Second batch of events (single event)
114 */
115 TEST_CHECK(fr_timer_list_run(tl, &now) == 1);
116 TEST_CHECK(event2 == NULL);
117 TEST_CHECK(event2_fired == true);
118 TEST_CHECK(event3_fired == false);
119 TEST_CHECK(event4_fired == false);
120
121 /*
122 * Now disarm event 4, so it doesn't fire
123 */
124 TEST_CHECK(fr_timer_disarm(event4) == 0);
125
126 now = fr_time_from_sec(3);
127 TEST_CHECK(fr_timer_list_run(tl, &now) == 1);
128
129 TEST_CHECK(event3 == NULL);
130 TEST_CHECK(event4 != NULL);
131 TEST_CHECK(event3_fired == true);
132 TEST_CHECK(event4_fired == false);
133
134 /*
135 * Now free event 5, so it doesn't fire
136 */
137 TEST_CHECK(fr_timer_delete(&event5) == 0);
138
139 now = fr_time_from_sec(4);
140 TEST_CHECK(fr_timer_list_run(tl, &now) == 1);
141 TEST_CHECK(event5_fired == false);
142 TEST_CHECK(event6_fired == true);
143 TEST_CHECK(event5 == NULL);
144 TEST_CHECK(event6 == NULL);
145
146 /*
147 * Re-arm event 4
148 */
149 now = fr_time_from_sec(4);
150 ret = fr_timer_at(NULL, tl, &event4, fr_time_from_sec(3), false, timer_cb, &event4_fired);
151 TEST_CHECK(ret == 0);
152 TEST_CHECK(fr_timer_list_run(tl, &now) == 1);
153 TEST_CHECK(event4_fired == true);
154 TEST_CHECK(event4 != NULL);
155
156 talloc_free(event4); /* This needs to be freed before its parent stack memory goes out of scope */
157}
158
159static void lst_basic_test(void)
160{
161 fr_timer_list_t *tl;
162
163 tl = fr_timer_list_lst_alloc(NULL, NULL);
164 TEST_CHECK(tl != NULL);
165 if (tl == NULL) return;
166
167 fr_timer_list_set_time_func(tl, basic_time);
168
170
171 talloc_free(tl);
172}
173
174typedef struct {
175 bool *fired;
178
179static void timer_cb_deferred(fr_timer_list_t *tl, fr_time_t now, void *uctx)
180{
181 deferred_uctx_t *ctx = (deferred_uctx_t *)uctx;
182
183 TEST_CHECK(fr_timer_at(NULL, tl, &ctx->event, now, true, timer_cb, ctx->fired) == 0);
185
186 TEST_CHECK(fr_timer_list_run(tl, &now) == 0); /* Event won't run immediately because we're in a callback */
187}
188
190{
191 fr_time_t now;
192 fr_timer_t *event1 = NULL;
193 bool deferred_event_fired = false;
194
195 deferred_uctx_t ctx = { .fired = &deferred_event_fired, .event = NULL };
196
197 fr_timer_list_set_time_func(tl, basic_time);
198
199 now = fr_time_from_sec(1);
200 TEST_CHECK(fr_timer_at(NULL, tl, &event1, fr_time_from_sec(1), true, timer_cb_deferred, &ctx) == 0);
201
202 /*
203 * The inner fr_timer_list_run call moves the event from the deferred
204 * list into the lst, where it's immediately executed, which is why
205 * we get 2 events firing here.
206 */
207 TEST_CHECK(fr_timer_list_run(tl, &now) == 2);
208 TEST_CHECK(deferred_event_fired == true);
209
210 now = fr_time_from_sec(1);
211}
212
213static void ordered_basic_test(void)
214{
215 fr_timer_list_t *tl;
216
217 tl = fr_timer_list_ordered_alloc(NULL, NULL);
218 TEST_CHECK(tl != NULL);
219 if (tl == NULL) return;
220
221 fr_timer_list_set_time_func(tl, basic_time);
222
224
225 talloc_free(tl);
226}
227
228static void lst_deferred_test(void)
229{
230 fr_timer_list_t *tl;
231
232 tl = fr_timer_list_lst_alloc(NULL, NULL);
233 TEST_CHECK(tl != NULL);
234 if (tl == NULL) return;
235
237
238 talloc_free(tl);
239}
240
241static void ordered_deferred_test(void)
242{
243 fr_timer_list_t *tl;
244
245 tl = fr_timer_list_ordered_alloc(NULL, NULL);
246 TEST_CHECK(tl != NULL);
247 if (tl == NULL) return;
248
250
251 talloc_free(tl);
252}
253
255{
256 fr_timer_list_t *tl;
257 fr_timer_t *event1 = NULL, *event2 = NULL;
258 bool event1_fired = false, event2_fired = false;
259 int ret;
260
261 tl = fr_timer_list_ordered_alloc(NULL, NULL);
262 TEST_CHECK(tl != NULL);
263 if (tl == NULL) return;
264
265 fr_timer_list_set_time_func(tl, basic_time);
266
267 ret = fr_timer_in(NULL, tl, &event1, fr_time_delta_from_sec(5), true, timer_cb, &event1_fired);
268 TEST_CHECK(ret == 0);
269
270 /*
271 * Should fail (wrong order)
272 */
273 ret = fr_timer_in(NULL, tl, &event2, fr_time_delta_from_sec(1), true, timer_cb, &event2_fired);
274 TEST_CHECK(ret == -1);
275
276 talloc_free(tl);
277}
278
279static void nested_test(fr_timer_list_t *tl_outer, fr_timer_list_t *tl_inner)
280{
281 fr_timer_t *event1_inner = NULL, *event2_inner = NULL;
282 bool event1_inner_fired = false, event2_inner_fired = false;
283 fr_time_t now;
284
285 int ret;
286
287 TEST_CHECK(fr_timer_list_num_events(tl_outer) == 0);
288
289 /*
290 * Should insert a single event into the outer list
291 */
292 ret = fr_timer_in(NULL, tl_inner, &event1_inner, fr_time_delta_from_sec(1), true, timer_cb, &event1_inner_fired);
293 TEST_CHECK(ret == 0);
294
295 TEST_CHECK(fr_timer_list_num_events(tl_outer) == 1);
296 TEST_CHECK(fr_timer_list_num_events(tl_inner) == 1);
297
298 /*
299 * Disable the event, the outer event count should drop to 0
300 */
301 TEST_CHECK(fr_timer_disarm(event1_inner) == 0);
302 TEST_CHECK(fr_timer_list_num_events(tl_outer) == 0);
303 TEST_CHECK(fr_timer_list_num_events(tl_inner) == 0);
304
305 /*
306 * Re-Enable the event
307 */
308 ret = fr_timer_in(NULL, tl_inner, &event1_inner, fr_time_delta_from_sec(1), true, timer_cb, &event1_inner_fired);
309 TEST_CHECK(ret == 0);
310
311 TEST_CHECK(fr_timer_list_num_events(tl_outer) == 1);
312 TEST_CHECK(fr_timer_list_num_events(tl_inner) == 1);
313
314 ret = fr_timer_in(NULL, tl_inner, &event2_inner, fr_time_delta_from_sec(1), true, timer_cb, &event2_inner_fired);
315 TEST_CHECK(ret == 0);
316
317 TEST_CHECK(fr_timer_list_num_events(tl_outer) == 1);
318 TEST_CHECK(fr_timer_list_num_events(tl_inner) == 2);
319
320 now = fr_time_from_sec(1);
321
322 /*
323 * One event should fire, which should run all the events in the inner list
324 */
325 TEST_CHECK(fr_timer_list_run(tl_outer, &now) == 1);
326 TEST_CHECK(event1_inner_fired == true);
327 TEST_CHECK(event2_inner_fired == true);
328
329 TEST_CHECK(fr_timer_list_num_events(tl_outer) == 0);
330 TEST_CHECK(fr_timer_list_num_events(tl_inner) == 0);
331}
332
333static void lst_nested(void)
334{
335 fr_timer_list_t *tl_outer, *tl_inner;
336 fr_timer_t *event1_inner = NULL;
337 int ret;
338
339 tl_outer = fr_timer_list_lst_alloc(NULL, NULL);
340 TEST_CHECK(tl_outer != NULL);
341 if (tl_outer == NULL) return;
342
343 tl_inner = fr_timer_list_lst_alloc(tl_outer, tl_outer);
344 TEST_CHECK(tl_inner != NULL);
345 if (tl_inner == NULL) return;
346
347 fr_timer_list_set_time_func(tl_outer, basic_time);
348 fr_timer_list_set_time_func(tl_inner, basic_time);
349
350 nested_test(tl_outer, tl_inner);
351
352 ret = fr_timer_in(NULL, tl_inner, &event1_inner, fr_time_delta_from_sec(1), true, timer_cb, NULL);
353 TEST_CHECK(ret == 0);
354
355 TEST_CHECK(fr_timer_list_num_events(tl_outer) == 1);
356 TEST_CHECK(fr_timer_list_num_events(tl_inner) == 1);
357
358 talloc_free(tl_inner);
359
360 TEST_CHECK(fr_timer_list_num_events(tl_outer) == 0);
361 TEST_CHECK(event1_inner == NULL);
362
363 talloc_free(tl_outer);
364}
365
366static void ordered_nested(void)
367{
368 fr_timer_list_t *tl_outer, *tl_inner;
369 fr_timer_t *event1_inner = NULL;
370 int ret;
371
372 tl_outer = fr_timer_list_ordered_alloc(NULL, NULL);
373 TEST_CHECK(tl_outer != NULL);
374 if (tl_outer == NULL) return;
375
376 tl_inner = fr_timer_list_ordered_alloc(tl_outer, tl_outer);
377 TEST_CHECK(tl_inner != NULL);
378 if (tl_inner == NULL) return;
379
380 fr_timer_list_set_time_func(tl_outer, basic_time);
381 fr_timer_list_set_time_func(tl_inner, basic_time);
382
383 nested_test(tl_outer, tl_inner);
384
385 ret = fr_timer_in(NULL, tl_inner, &event1_inner, fr_time_delta_from_sec(1), true, timer_cb, NULL);
386 TEST_CHECK(ret == 0);
387
388 TEST_CHECK(fr_timer_list_num_events(tl_outer) == 1);
389 TEST_CHECK(fr_timer_list_num_events(tl_inner) == 1);
390
391 talloc_free(tl_inner);
392
393 TEST_CHECK(fr_timer_list_num_events(tl_outer) == 0);
394 TEST_CHECK(event1_inner == NULL);
395
396 talloc_free(tl_outer);
397}
398
400 { "lst_basic", lst_basic_test },
401 { "ordered_basic", ordered_basic_test },
402 { "lst_deferred", lst_deferred_test },
403 { "ordered_deferred", ordered_deferred_test },
404 { "ordered_bad_inserts", ordered_bad_inserts_test },
405 { "lst_nested", lst_nested },
406 { "ordered_nested", ordered_nested },
407 { NULL }
408};
#define TEST_CHECK(cond)
Definition acutest.h:85
talloc_free(reap)
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition time.h:590
#define fr_time_wrap(_time)
Definition time.h:145
static fr_time_t fr_time_from_sec(time_t when)
Convert a time_t (wallclock time) to a fr_time_t (internal time)
Definition time.h:858
#define fr_time_gt(_a, _b)
Definition time.h:237
"server local" time.
Definition time.h:69
int fr_timer_list_run(fr_timer_list_t *tl, fr_time_t *when)
Execute any pending events in the event loop.
Definition timer.c:815
uint64_t fr_timer_list_num_events(fr_timer_list_t *tl)
Return number of pending events.
Definition timer.c:965
fr_timer_list_t * fr_timer_list_ordered_alloc(TALLOC_CTX *ctx, fr_timer_list_t *parent)
Allocate a new sorted event timer list.
Definition timer.c:1076
fr_timer_list_t * fr_timer_list_lst_alloc(TALLOC_CTX *ctx, fr_timer_list_t *parent)
Allocate a new lst based timer list.
Definition timer.c:1050
int fr_timer_disarm(fr_timer_t *ev)
Remove an event from the event list, but don't free the memory.
Definition timer.c:606
void fr_timer_list_set_time_func(fr_timer_list_t *tl, fr_event_time_source_t func)
Override event list time source.
Definition timer.c:993
int fr_timer_delete(fr_timer_t **ev_p)
Delete a timer event and free its memory.
Definition timer.c:643
An event timer list.
Definition timer.c:53
A timer event.
Definition timer.c:79
#define fr_timer_in(...)
Definition timer.h:86
#define fr_timer_at(...)
Definition timer.h:80
TEST_LIST
static void ordered_nested(void)
fr_timer_t * event
static void lst_nested(void)
static void ordered_basic_test(void)
static void nested_test(fr_timer_list_t *tl_outer, fr_timer_list_t *tl_inner)
#define TIME_SOURCE(_name)
Defines an artificial time source for a test.
Definition timer_tests.c:32
static void deferred_timer_list_tests(fr_timer_list_t *tl)
static void lst_basic_test(void)
static void ordered_deferred_test(void)
static void lst_deferred_test(void)
static void timer_cb_deferred(fr_timer_list_t *tl, fr_time_t now, void *uctx)
static void basic_timer_list_tests(fr_timer_list_t *tl)
Definition timer_tests.c:58
static void timer_cb(fr_timer_list_t *tl, fr_time_t now, void *uctx)
Verifies time passed in is not 0, that tl is not NULL, and writes true to uctx (must be a bool)
Definition timer_tests.c:48
static void ordered_bad_inserts_test(void)
int nonnull(2, 5))