blob: 682c7ac79832ae99c2e21dd58ef03be4185fc321 [file] [log] [blame]
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +01001/* Test implementation for osmo_tdef API. */
2/*
3 * (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
4 *
5 * All Rights Reserved
6 *
7 * SPDX-License-Identifier: GPL-2.0+
8 *
9 * Author: Neels Hofmeyr <neels@hofmeyr.de>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 */
25
26#include <stdio.h>
27#include <errno.h>
28#include <limits.h>
29
30#include <osmocom/core/logging.h>
31#include <osmocom/core/application.h>
32#include <osmocom/core/fsm.h>
33
34#include <osmocom/core/tdef.h>
35
36static void *ctx = NULL;
37
38static struct osmo_tdef tdefs[] = {
39 { .T=1, .default_val=100, .desc="100s" },
40 { .T=2, .default_val=100, .unit=OSMO_TDEF_MS, .desc="100ms" },
41 { .T=3, .default_val=100, .unit=OSMO_TDEF_M, .desc="100m" },
42 { .T=4, .default_val=100, .unit=OSMO_TDEF_CUSTOM, .desc="100 potatoes" },
43
44 { .T=7, .default_val=50, .desc="Water Boiling Timeout" }, // default is .unit=OSMO_TDEF_S == 0
45 { .T=8, .default_val=300, .desc="Tea brewing" },
46 { .T=9, .default_val=5, .unit=OSMO_TDEF_M, .desc="Let tea cool down before drinking" },
47 { .T=10, .default_val=20, .unit=OSMO_TDEF_M, .desc="Forgot to drink tea while it's warm" },
48
49 /* test conversions */
50 { .T=1000, .default_val=2*1000, .unit=OSMO_TDEF_MS, .desc="two seconds from ms" },
51 { .T=1001, .default_val=60*1000, .unit=OSMO_TDEF_MS, .desc="one minute from ms" },
52 { .T=1002, .default_val=(ULONG_MAX/60), .unit=OSMO_TDEF_M, .desc="almost too many seconds" },
53 { .T=1003, .default_val=ULONG_MAX, .unit=OSMO_TDEF_M, .desc="too many seconds" },
54 { .T=1004, .default_val=1, .unit=OSMO_TDEF_MS, .desc="one ms" },
55 { .T=1005, .default_val=0, .unit=OSMO_TDEF_MS, .desc="zero ms" },
56 { .T=1006, .default_val=0, .unit=OSMO_TDEF_S, .desc="zero s" },
57 { .T=1007, .default_val=0, .unit=OSMO_TDEF_M, .desc="zero m" },
58 { .T=1008, .default_val=0, .unit=OSMO_TDEF_CUSTOM, .desc="zero" },
59
60 /* test range */
61 { .T=INT_MAX, .default_val=ULONG_MAX, .unit=OSMO_TDEF_S, .desc="very large" },
62 { .T=INT_MAX-1, .default_val=ULONG_MAX-1, .unit=OSMO_TDEF_S, .desc="very large" },
63 { .T=INT_MAX-2, .default_val=LONG_MAX, .unit=OSMO_TDEF_S, .desc="very large" },
64 { .T=INT_MAX-3, .default_val=ULONG_MAX, .unit=OSMO_TDEF_M, .desc="very large in minutes" },
65 { .T=INT_MIN, .default_val=ULONG_MAX, .unit=OSMO_TDEF_S, .desc="negative" },
66
67 { .T=0, .default_val=1, .unit=OSMO_TDEF_CUSTOM, .desc="zero" },
68
69 /* no desc */
70 { .T=123, .default_val=1 },
71
72 {} // <-- important! last entry shall be zero
73};
74
75#define print_tdef_get(T, AS_UNIT) do { \
76 unsigned long val = osmo_tdef_get(tdefs, T, AS_UNIT, 999); \
77 printf("osmo_tdef_get(tdefs, %d, %s, 999)\t= %lu\n", T, osmo_tdef_unit_name(AS_UNIT), val); \
78 } while (0)
79
80#define print_tdef_get_short(T, AS_UNIT) do { \
81 unsigned long val = osmo_tdef_get(tdefs, T, AS_UNIT, 999); \
82 printf("osmo_tdef_get(%d, %s)\t= %lu\n", T, osmo_tdef_unit_name(AS_UNIT), val); \
83 } while (0)
84
85void print_tdef_info(unsigned int T)
86{
87 const struct osmo_tdef *t = osmo_tdef_get_entry(tdefs, T);
88 if (!t) {
89 printf("T%d=NULL", T);
90 return;
91 }
92 printf("T%d=%lu%s", T, t->val, osmo_tdef_unit_name(t->unit));
93 if (t->val != t->default_val)
94 printf("(def=%lu)", t->default_val);
95 printf("\n");
96}
97
98static void test_tdef_get()
99{
100 int i;
101 enum osmo_tdef_unit as_unit;
102
103 printf("\n%s()\n", __func__);
104
105 osmo_tdefs_reset(tdefs); // make all values the default
106
107 for (i = 0; i < ARRAY_SIZE(tdefs)-1; i++) {
108 unsigned int T = tdefs[i].T;
109 print_tdef_info(T);
110 for (as_unit = OSMO_TDEF_S; as_unit <= OSMO_TDEF_CUSTOM; as_unit++) {
111 print_tdef_get_short(T, as_unit);
112 }
113 }
114}
115
116static void test_tdef_get_nonexisting()
117{
118 printf("\n%s()\n", __func__);
119
120 print_tdef_get(5, OSMO_TDEF_S);
121 print_tdef_get(5, OSMO_TDEF_MS);
122 print_tdef_get(5, OSMO_TDEF_M);
123 print_tdef_get(5, OSMO_TDEF_CUSTOM);
124}
125
126static void test_tdef_set_and_get()
127{
128 struct osmo_tdef *t;
129 printf("\n%s()\n", __func__);
130
131 t = osmo_tdef_get_entry(tdefs, 7);
132 printf("setting 7 = 42\n");
133 t->val = 42;
134 print_tdef_info(7);
135 print_tdef_get_short(7, OSMO_TDEF_MS);
136 print_tdef_get_short(7, OSMO_TDEF_S);
137 print_tdef_get_short(7, OSMO_TDEF_M);
138 print_tdef_get_short(7, OSMO_TDEF_CUSTOM);
139
140 printf("setting 7 = 420\n");
141 t->val = 420;
142 print_tdef_info(7);
143 print_tdef_get_short(7, OSMO_TDEF_MS);
144 print_tdef_get_short(7, OSMO_TDEF_S);
145 print_tdef_get_short(7, OSMO_TDEF_M);
146 print_tdef_get_short(7, OSMO_TDEF_CUSTOM);
147
148 printf("resetting\n");
149 osmo_tdefs_reset(tdefs);
150 print_tdef_info(7);
151 print_tdef_get_short(7, OSMO_TDEF_S);
152}
153
154enum test_tdef_fsm_states {
155 S_A = 0,
156 S_B,
157 S_C,
158 S_D,
159 S_G,
160 S_H,
161 S_I,
162 S_J,
163 S_K,
164 S_L,
165 S_M,
166 S_N,
167 S_O,
168 S_X,
169 S_Y,
170 S_Z,
171};
172
173static const struct osmo_tdef_state_timeout test_tdef_state_timeouts[32] = {
174 [S_A] = { .T = 1 },
175 [S_B] = { .T = 2 },
176 [S_C] = { .T = 3 },
177 [S_D] = { .T = 4 },
178
179 [S_G] = { .T = 7 },
180 [S_H] = { .T = 8 },
181 [S_I] = { .T = 9 },
182 [S_J] = { .T = 10 },
183
184 /* keep_timer: adopt whichever T was running before and continue the timeout. */
185 [S_K] = { .keep_timer = true },
186 /* S_F defines an undefined T, but should continue previous state's timeout. */
187 [S_L] = { .T = 123, .keep_timer = true },
188
189 /* range */
190 [S_M] = { .T = INT_MAX },
191 [S_N] = { .T = INT_MIN },
192
193 /* T0 is not addressable from osmo_tdef_state_timeout, since it is indistinguishable from an unset entry. Even
194 * though a timeout value is set for T=0, the transition to state S_O will show "no timer configured". */
195 [S_O] = { .T = 0 },
196
197 /* S_X undefined on purpose */
198 /* S_Y defines a T that does not exist */
199 [S_Y] = { .T = 666 },
200 /* S_Z undefined on purpose */
201};
202
203#define S(x) (1 << (x))
204
205static const struct osmo_fsm_state test_tdef_fsm_states[] = {
206#define DEF_STATE(NAME) \
207 [S_##NAME] = { \
208 .name = #NAME, \
209 .out_state_mask = 0 \
210 | S(S_A) \
211 | S(S_B) \
212 | S(S_C) \
213 | S(S_D) \
214 | S(S_G) \
215 | S(S_H) \
216 | S(S_I) \
217 | S(S_J) \
218 | S(S_K) \
219 | S(S_L) \
220 | S(S_M) \
221 | S(S_N) \
222 | S(S_O) \
223 | S(S_X) \
224 | S(S_Y) \
225 | S(S_Z) \
226 , \
227 }
228
229 DEF_STATE(A),
230 DEF_STATE(B),
231 DEF_STATE(C),
232 DEF_STATE(D),
233
234 DEF_STATE(G),
235 DEF_STATE(H),
236 DEF_STATE(I),
237 DEF_STATE(J),
238
239 DEF_STATE(K),
240 DEF_STATE(L),
241
242 DEF_STATE(M),
243 DEF_STATE(N),
244 DEF_STATE(O),
245
246 DEF_STATE(X),
247 DEF_STATE(Y),
248 /* Z: test not being allowed to transition to other states. */
249 [S_Z] = {
250 .name = "Z",
251 .out_state_mask = 0
252 | S(S_A)
253 ,
254 },
255};
256
257static const struct value_string test_tdef_fsm_event_names[] = { {} };
258
259static struct osmo_fsm test_tdef_fsm = {
260 .name = "tdef_test",
261 .states = test_tdef_fsm_states,
262 .event_names = test_tdef_fsm_event_names,
263 .num_states = ARRAY_SIZE(test_tdef_fsm_states),
264 .log_subsys = DLGLOBAL,
265};
266
267const struct timeval fake_time_start_time = { 123, 456 };
268
269#define fake_time_passes(secs, usecs) do \
270{ \
271 struct timeval diff; \
272 osmo_gettimeofday_override_add(secs, usecs); \
273 osmo_clock_override_add(CLOCK_MONOTONIC, secs, usecs * 1000); \
274 timersub(&osmo_gettimeofday_override_time, &fake_time_start_time, &diff); \
275 printf("Total time passed: %ld.%06ld s\n", diff.tv_sec, diff.tv_usec); \
276 osmo_timers_prepare(); \
277 osmo_timers_update(); \
278} while (0)
279
280void fake_time_start()
281{
282 struct timespec *clock_override;
283
284 osmo_gettimeofday_override_time = fake_time_start_time;
285 osmo_gettimeofday_override = true;
286 clock_override = osmo_clock_override_gettimespec(CLOCK_MONOTONIC);
287 OSMO_ASSERT(clock_override);
288 clock_override->tv_sec = fake_time_start_time.tv_sec;
289 clock_override->tv_nsec = fake_time_start_time.tv_usec * 1000;
290 osmo_clock_override_enable(CLOCK_MONOTONIC, true);
291 fake_time_passes(0, 0);
292}
293
294static void print_fsm_state(struct osmo_fsm_inst *fi)
295{
296 struct timeval remaining;
297 printf("state=%s T=%d", osmo_fsm_inst_state_name(fi), fi->T);
298
299 if (!osmo_timer_pending(&fi->timer)) {
300 printf(", no timeout\n");
301 return;
302 }
303
304 osmo_timer_remaining(&fi->timer, &osmo_gettimeofday_override_time, &remaining);
305 printf(", %lu.%06lu s remaining\n", remaining.tv_sec, remaining.tv_usec);
306}
307
308
309#define test_tdef_fsm_state_chg(NEXT_STATE) do { \
310 const struct osmo_tdef_state_timeout *st = osmo_tdef_get_state_timeout(NEXT_STATE, \
311 test_tdef_state_timeouts); \
312 if (!st) { \
313 printf(" --> %s (no timer configured for this state)\n", \
314 osmo_fsm_state_name(&test_tdef_fsm, NEXT_STATE)); \
315 } else { \
316 struct osmo_tdef *t = osmo_tdef_get_entry(tdefs, st->T); \
317 int rc = osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, test_tdef_state_timeouts, tdefs, 999); \
318 printf(" --> %s (configured as T%d%s %lu %s) rc=%d;\t", osmo_fsm_state_name(&test_tdef_fsm, \
319 NEXT_STATE), \
320 st->T, st->keep_timer ? "(keep_timer)" : "", \
321 t? t->val : -1, t? osmo_tdef_unit_name(t->unit) : "-", \
322 rc); \
323 print_fsm_state(fi); \
324 } \
325 } while(0)
326
327
328
329static void test_tdef_state_timeout(bool test_range)
330{
331 struct osmo_fsm_inst *fi;
332 struct osmo_tdef *m = osmo_tdef_get_entry(tdefs, INT_MAX);
333 unsigned long m_secs;
334 printf("\n%s()\n", __func__);
335
336 osmo_tdefs_reset(tdefs);
337
338 fake_time_start();
339
340 fi = osmo_fsm_inst_alloc(&test_tdef_fsm, ctx, NULL, LOGL_DEBUG, __func__);
341 OSMO_ASSERT(fi);
342 print_fsm_state(fi);
343
344 test_tdef_fsm_state_chg(S_A);
345 test_tdef_fsm_state_chg(S_B);
346 test_tdef_fsm_state_chg(S_C);
347 test_tdef_fsm_state_chg(S_D);
348
349 test_tdef_fsm_state_chg(S_G);
350 test_tdef_fsm_state_chg(S_H);
351 test_tdef_fsm_state_chg(S_I);
352 test_tdef_fsm_state_chg(S_J);
353
354 printf("- test keep_timer:\n");
355 fake_time_passes(123, 45678);
356 print_fsm_state(fi);
357 test_tdef_fsm_state_chg(S_K);
358 test_tdef_fsm_state_chg(S_A);
359 fake_time_passes(23, 45678);
360 print_fsm_state(fi);
361 test_tdef_fsm_state_chg(S_K);
362
363 test_tdef_fsm_state_chg(S_A);
364 fake_time_passes(23, 45678);
365 print_fsm_state(fi);
366 test_tdef_fsm_state_chg(S_L);
367
368 printf("- test large T:\n");
369 test_tdef_fsm_state_chg(S_M);
370
371 printf("- test T<0:\n");
372 test_tdef_fsm_state_chg(S_N);
373
374 printf("- test T=0:\n");
375 test_tdef_fsm_state_chg(S_O);
376
377 printf("- test no timer:\n");
378 test_tdef_fsm_state_chg(S_X);
379
380 printf("- test undefined timer, using default_val arg of osmo_tdef_fsm_inst_state_chg(), here passed as 999:\n");
381 test_tdef_fsm_state_chg(S_Y);
382
383 /* the range of unsigned long is architecture dependent. This test can be invoked manually to see whether
384 * clamping the timeout values works, but the output will be of varying lengths depending on the system's
385 * unsigned long range, and would cause differences in expected output. */
386 if (test_range) {
387 printf("- test range:\n");
388 test_tdef_fsm_state_chg(S_M);
389 /* sweep through all the bits, shifting in 0xfffff.. from the right. */
390 m_secs = 0;
391 do {
392 m_secs = (m_secs << 1) + 1;
393 switch (m_secs) {
394 case 0x7fff:
395 printf("--- int32_t max ---\n");
396 break;
397 case 0xffff:
398 printf("--- uint32_t max ---\n");
399 break;
400 case 0x7fffffff:
401 printf("--- int64_t max ---\n");
402 break;
403 case 0xffffffff:
404 printf("--- uint64_t max ---\n");
405 break;
406 default:
407 break;
408 }
409
410 m->val = m_secs - 1;
411 test_tdef_fsm_state_chg(S_M);
412 m->val = m_secs;
413 test_tdef_fsm_state_chg(S_M);
414 m->val = m_secs + 1;
415 test_tdef_fsm_state_chg(S_M);
416 } while (m_secs < ULONG_MAX);
417 }
418
419 printf("- test disallowed transition:\n");
420 test_tdef_fsm_state_chg(S_Z);
421 test_tdef_fsm_state_chg(S_B);
422 test_tdef_fsm_state_chg(S_C);
423 test_tdef_fsm_state_chg(S_D);
424}
425
426int main(int argc, char **argv)
427{
428 ctx = talloc_named_const(NULL, 0, "tdef_test.c");
429 osmo_init_logging2(ctx, NULL);
430
431 log_set_print_filename(osmo_stderr_target, 0);
432 log_set_print_category(osmo_stderr_target, 1);
433 log_set_use_color(osmo_stderr_target, 0);
434
435 osmo_fsm_register(&test_tdef_fsm);
436
437 test_tdef_get();
438 test_tdef_get_nonexisting();
439 test_tdef_set_and_get();
440 /* Run range test iff any argument is passed on the cmdline. For the rationale, see the comment in
441 * test_tdef_state_timeout(). */
442 test_tdef_state_timeout(argc > 1);
443
444 return EXIT_SUCCESS;
445}