diff options
Diffstat (limited to 'tests/tdef/tdef_test.c')
-rw-r--r-- | tests/tdef/tdef_test.c | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/tests/tdef/tdef_test.c b/tests/tdef/tdef_test.c new file mode 100644 index 00000000..682c7ac7 --- /dev/null +++ b/tests/tdef/tdef_test.c @@ -0,0 +1,445 @@ +/* Test implementation for osmo_tdef API. */ +/* + * (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * Author: Neels Hofmeyr <neels@hofmeyr.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <errno.h> +#include <limits.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/application.h> +#include <osmocom/core/fsm.h> + +#include <osmocom/core/tdef.h> + +static void *ctx = NULL; + +static struct osmo_tdef tdefs[] = { + { .T=1, .default_val=100, .desc="100s" }, + { .T=2, .default_val=100, .unit=OSMO_TDEF_MS, .desc="100ms" }, + { .T=3, .default_val=100, .unit=OSMO_TDEF_M, .desc="100m" }, + { .T=4, .default_val=100, .unit=OSMO_TDEF_CUSTOM, .desc="100 potatoes" }, + + { .T=7, .default_val=50, .desc="Water Boiling Timeout" }, // default is .unit=OSMO_TDEF_S == 0 + { .T=8, .default_val=300, .desc="Tea brewing" }, + { .T=9, .default_val=5, .unit=OSMO_TDEF_M, .desc="Let tea cool down before drinking" }, + { .T=10, .default_val=20, .unit=OSMO_TDEF_M, .desc="Forgot to drink tea while it's warm" }, + + /* test conversions */ + { .T=1000, .default_val=2*1000, .unit=OSMO_TDEF_MS, .desc="two seconds from ms" }, + { .T=1001, .default_val=60*1000, .unit=OSMO_TDEF_MS, .desc="one minute from ms" }, + { .T=1002, .default_val=(ULONG_MAX/60), .unit=OSMO_TDEF_M, .desc="almost too many seconds" }, + { .T=1003, .default_val=ULONG_MAX, .unit=OSMO_TDEF_M, .desc="too many seconds" }, + { .T=1004, .default_val=1, .unit=OSMO_TDEF_MS, .desc="one ms" }, + { .T=1005, .default_val=0, .unit=OSMO_TDEF_MS, .desc="zero ms" }, + { .T=1006, .default_val=0, .unit=OSMO_TDEF_S, .desc="zero s" }, + { .T=1007, .default_val=0, .unit=OSMO_TDEF_M, .desc="zero m" }, + { .T=1008, .default_val=0, .unit=OSMO_TDEF_CUSTOM, .desc="zero" }, + + /* test range */ + { .T=INT_MAX, .default_val=ULONG_MAX, .unit=OSMO_TDEF_S, .desc="very large" }, + { .T=INT_MAX-1, .default_val=ULONG_MAX-1, .unit=OSMO_TDEF_S, .desc="very large" }, + { .T=INT_MAX-2, .default_val=LONG_MAX, .unit=OSMO_TDEF_S, .desc="very large" }, + { .T=INT_MAX-3, .default_val=ULONG_MAX, .unit=OSMO_TDEF_M, .desc="very large in minutes" }, + { .T=INT_MIN, .default_val=ULONG_MAX, .unit=OSMO_TDEF_S, .desc="negative" }, + + { .T=0, .default_val=1, .unit=OSMO_TDEF_CUSTOM, .desc="zero" }, + + /* no desc */ + { .T=123, .default_val=1 }, + + {} // <-- important! last entry shall be zero +}; + +#define print_tdef_get(T, AS_UNIT) do { \ + unsigned long val = osmo_tdef_get(tdefs, T, AS_UNIT, 999); \ + printf("osmo_tdef_get(tdefs, %d, %s, 999)\t= %lu\n", T, osmo_tdef_unit_name(AS_UNIT), val); \ + } while (0) + +#define print_tdef_get_short(T, AS_UNIT) do { \ + unsigned long val = osmo_tdef_get(tdefs, T, AS_UNIT, 999); \ + printf("osmo_tdef_get(%d, %s)\t= %lu\n", T, osmo_tdef_unit_name(AS_UNIT), val); \ + } while (0) + +void print_tdef_info(unsigned int T) +{ + const struct osmo_tdef *t = osmo_tdef_get_entry(tdefs, T); + if (!t) { + printf("T%d=NULL", T); + return; + } + printf("T%d=%lu%s", T, t->val, osmo_tdef_unit_name(t->unit)); + if (t->val != t->default_val) + printf("(def=%lu)", t->default_val); + printf("\n"); +} + +static void test_tdef_get() +{ + int i; + enum osmo_tdef_unit as_unit; + + printf("\n%s()\n", __func__); + + osmo_tdefs_reset(tdefs); // make all values the default + + for (i = 0; i < ARRAY_SIZE(tdefs)-1; i++) { + unsigned int T = tdefs[i].T; + print_tdef_info(T); + for (as_unit = OSMO_TDEF_S; as_unit <= OSMO_TDEF_CUSTOM; as_unit++) { + print_tdef_get_short(T, as_unit); + } + } +} + +static void test_tdef_get_nonexisting() +{ + printf("\n%s()\n", __func__); + + print_tdef_get(5, OSMO_TDEF_S); + print_tdef_get(5, OSMO_TDEF_MS); + print_tdef_get(5, OSMO_TDEF_M); + print_tdef_get(5, OSMO_TDEF_CUSTOM); +} + +static void test_tdef_set_and_get() +{ + struct osmo_tdef *t; + printf("\n%s()\n", __func__); + + t = osmo_tdef_get_entry(tdefs, 7); + printf("setting 7 = 42\n"); + t->val = 42; + print_tdef_info(7); + print_tdef_get_short(7, OSMO_TDEF_MS); + print_tdef_get_short(7, OSMO_TDEF_S); + print_tdef_get_short(7, OSMO_TDEF_M); + print_tdef_get_short(7, OSMO_TDEF_CUSTOM); + + printf("setting 7 = 420\n"); + t->val = 420; + print_tdef_info(7); + print_tdef_get_short(7, OSMO_TDEF_MS); + print_tdef_get_short(7, OSMO_TDEF_S); + print_tdef_get_short(7, OSMO_TDEF_M); + print_tdef_get_short(7, OSMO_TDEF_CUSTOM); + + printf("resetting\n"); + osmo_tdefs_reset(tdefs); + print_tdef_info(7); + print_tdef_get_short(7, OSMO_TDEF_S); +} + +enum test_tdef_fsm_states { + S_A = 0, + S_B, + S_C, + S_D, + S_G, + S_H, + S_I, + S_J, + S_K, + S_L, + S_M, + S_N, + S_O, + S_X, + S_Y, + S_Z, +}; + +static const struct osmo_tdef_state_timeout test_tdef_state_timeouts[32] = { + [S_A] = { .T = 1 }, + [S_B] = { .T = 2 }, + [S_C] = { .T = 3 }, + [S_D] = { .T = 4 }, + + [S_G] = { .T = 7 }, + [S_H] = { .T = 8 }, + [S_I] = { .T = 9 }, + [S_J] = { .T = 10 }, + + /* keep_timer: adopt whichever T was running before and continue the timeout. */ + [S_K] = { .keep_timer = true }, + /* S_F defines an undefined T, but should continue previous state's timeout. */ + [S_L] = { .T = 123, .keep_timer = true }, + + /* range */ + [S_M] = { .T = INT_MAX }, + [S_N] = { .T = INT_MIN }, + + /* T0 is not addressable from osmo_tdef_state_timeout, since it is indistinguishable from an unset entry. Even + * though a timeout value is set for T=0, the transition to state S_O will show "no timer configured". */ + [S_O] = { .T = 0 }, + + /* S_X undefined on purpose */ + /* S_Y defines a T that does not exist */ + [S_Y] = { .T = 666 }, + /* S_Z undefined on purpose */ +}; + +#define S(x) (1 << (x)) + +static const struct osmo_fsm_state test_tdef_fsm_states[] = { +#define DEF_STATE(NAME) \ + [S_##NAME] = { \ + .name = #NAME, \ + .out_state_mask = 0 \ + | S(S_A) \ + | S(S_B) \ + | S(S_C) \ + | S(S_D) \ + | S(S_G) \ + | S(S_H) \ + | S(S_I) \ + | S(S_J) \ + | S(S_K) \ + | S(S_L) \ + | S(S_M) \ + | S(S_N) \ + | S(S_O) \ + | S(S_X) \ + | S(S_Y) \ + | S(S_Z) \ + , \ + } + + DEF_STATE(A), + DEF_STATE(B), + DEF_STATE(C), + DEF_STATE(D), + + DEF_STATE(G), + DEF_STATE(H), + DEF_STATE(I), + DEF_STATE(J), + + DEF_STATE(K), + DEF_STATE(L), + + DEF_STATE(M), + DEF_STATE(N), + DEF_STATE(O), + + DEF_STATE(X), + DEF_STATE(Y), + /* Z: test not being allowed to transition to other states. */ + [S_Z] = { + .name = "Z", + .out_state_mask = 0 + | S(S_A) + , + }, +}; + +static const struct value_string test_tdef_fsm_event_names[] = { {} }; + +static struct osmo_fsm test_tdef_fsm = { + .name = "tdef_test", + .states = test_tdef_fsm_states, + .event_names = test_tdef_fsm_event_names, + .num_states = ARRAY_SIZE(test_tdef_fsm_states), + .log_subsys = DLGLOBAL, +}; + +const struct timeval fake_time_start_time = { 123, 456 }; + +#define fake_time_passes(secs, usecs) do \ +{ \ + struct timeval diff; \ + osmo_gettimeofday_override_add(secs, usecs); \ + osmo_clock_override_add(CLOCK_MONOTONIC, secs, usecs * 1000); \ + timersub(&osmo_gettimeofday_override_time, &fake_time_start_time, &diff); \ + printf("Total time passed: %ld.%06ld s\n", diff.tv_sec, diff.tv_usec); \ + osmo_timers_prepare(); \ + osmo_timers_update(); \ +} while (0) + +void fake_time_start() +{ + struct timespec *clock_override; + + osmo_gettimeofday_override_time = fake_time_start_time; + osmo_gettimeofday_override = true; + clock_override = osmo_clock_override_gettimespec(CLOCK_MONOTONIC); + OSMO_ASSERT(clock_override); + clock_override->tv_sec = fake_time_start_time.tv_sec; + clock_override->tv_nsec = fake_time_start_time.tv_usec * 1000; + osmo_clock_override_enable(CLOCK_MONOTONIC, true); + fake_time_passes(0, 0); +} + +static void print_fsm_state(struct osmo_fsm_inst *fi) +{ + struct timeval remaining; + printf("state=%s T=%d", osmo_fsm_inst_state_name(fi), fi->T); + + if (!osmo_timer_pending(&fi->timer)) { + printf(", no timeout\n"); + return; + } + + osmo_timer_remaining(&fi->timer, &osmo_gettimeofday_override_time, &remaining); + printf(", %lu.%06lu s remaining\n", remaining.tv_sec, remaining.tv_usec); +} + + +#define test_tdef_fsm_state_chg(NEXT_STATE) do { \ + const struct osmo_tdef_state_timeout *st = osmo_tdef_get_state_timeout(NEXT_STATE, \ + test_tdef_state_timeouts); \ + if (!st) { \ + printf(" --> %s (no timer configured for this state)\n", \ + osmo_fsm_state_name(&test_tdef_fsm, NEXT_STATE)); \ + } else { \ + struct osmo_tdef *t = osmo_tdef_get_entry(tdefs, st->T); \ + int rc = osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, test_tdef_state_timeouts, tdefs, 999); \ + printf(" --> %s (configured as T%d%s %lu %s) rc=%d;\t", osmo_fsm_state_name(&test_tdef_fsm, \ + NEXT_STATE), \ + st->T, st->keep_timer ? "(keep_timer)" : "", \ + t? t->val : -1, t? osmo_tdef_unit_name(t->unit) : "-", \ + rc); \ + print_fsm_state(fi); \ + } \ + } while(0) + + + +static void test_tdef_state_timeout(bool test_range) +{ + struct osmo_fsm_inst *fi; + struct osmo_tdef *m = osmo_tdef_get_entry(tdefs, INT_MAX); + unsigned long m_secs; + printf("\n%s()\n", __func__); + + osmo_tdefs_reset(tdefs); + + fake_time_start(); + + fi = osmo_fsm_inst_alloc(&test_tdef_fsm, ctx, NULL, LOGL_DEBUG, __func__); + OSMO_ASSERT(fi); + print_fsm_state(fi); + + test_tdef_fsm_state_chg(S_A); + test_tdef_fsm_state_chg(S_B); + test_tdef_fsm_state_chg(S_C); + test_tdef_fsm_state_chg(S_D); + + test_tdef_fsm_state_chg(S_G); + test_tdef_fsm_state_chg(S_H); + test_tdef_fsm_state_chg(S_I); + test_tdef_fsm_state_chg(S_J); + + printf("- test keep_timer:\n"); + fake_time_passes(123, 45678); + print_fsm_state(fi); + test_tdef_fsm_state_chg(S_K); + test_tdef_fsm_state_chg(S_A); + fake_time_passes(23, 45678); + print_fsm_state(fi); + test_tdef_fsm_state_chg(S_K); + + test_tdef_fsm_state_chg(S_A); + fake_time_passes(23, 45678); + print_fsm_state(fi); + test_tdef_fsm_state_chg(S_L); + + printf("- test large T:\n"); + test_tdef_fsm_state_chg(S_M); + + printf("- test T<0:\n"); + test_tdef_fsm_state_chg(S_N); + + printf("- test T=0:\n"); + test_tdef_fsm_state_chg(S_O); + + printf("- test no timer:\n"); + test_tdef_fsm_state_chg(S_X); + + printf("- test undefined timer, using default_val arg of osmo_tdef_fsm_inst_state_chg(), here passed as 999:\n"); + test_tdef_fsm_state_chg(S_Y); + + /* the range of unsigned long is architecture dependent. This test can be invoked manually to see whether + * clamping the timeout values works, but the output will be of varying lengths depending on the system's + * unsigned long range, and would cause differences in expected output. */ + if (test_range) { + printf("- test range:\n"); + test_tdef_fsm_state_chg(S_M); + /* sweep through all the bits, shifting in 0xfffff.. from the right. */ + m_secs = 0; + do { + m_secs = (m_secs << 1) + 1; + switch (m_secs) { + case 0x7fff: + printf("--- int32_t max ---\n"); + break; + case 0xffff: + printf("--- uint32_t max ---\n"); + break; + case 0x7fffffff: + printf("--- int64_t max ---\n"); + break; + case 0xffffffff: + printf("--- uint64_t max ---\n"); + break; + default: + break; + } + + m->val = m_secs - 1; + test_tdef_fsm_state_chg(S_M); + m->val = m_secs; + test_tdef_fsm_state_chg(S_M); + m->val = m_secs + 1; + test_tdef_fsm_state_chg(S_M); + } while (m_secs < ULONG_MAX); + } + + printf("- test disallowed transition:\n"); + test_tdef_fsm_state_chg(S_Z); + test_tdef_fsm_state_chg(S_B); + test_tdef_fsm_state_chg(S_C); + test_tdef_fsm_state_chg(S_D); +} + +int main(int argc, char **argv) +{ + ctx = talloc_named_const(NULL, 0, "tdef_test.c"); + osmo_init_logging2(ctx, NULL); + + log_set_print_filename(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + log_set_use_color(osmo_stderr_target, 0); + + osmo_fsm_register(&test_tdef_fsm); + + test_tdef_get(); + test_tdef_get_nonexisting(); + test_tdef_set_and_get(); + /* Run range test iff any argument is passed on the cmdline. For the rationale, see the comment in + * test_tdef_state_timeout(). */ + test_tdef_state_timeout(argc > 1); + + return EXIT_SUCCESS; +} |