diff options
Diffstat (limited to 'src/lib/tests')
| -rw-r--r-- | src/lib/tests/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/lib/tests/auth_test.c | 55 | ||||
| -rw-r--r-- | src/lib/tests/crc32_test.c | 72 | ||||
| -rw-r--r-- | src/lib/tests/hash_test.c | 110 | ||||
| -rw-r--r-- | src/lib/tests/kex_test.c | 14 | ||||
| -rw-r--r-- | src/lib/tests/kex_test_ml_kem.c | 18 | ||||
| -rw-r--r-- | src/lib/tests/tpm_test.c | 2 | ||||
| -rw-r--r-- | src/lib/tests/tw_test.c | 663 |
8 files changed, 846 insertions, 90 deletions
diff --git a/src/lib/tests/CMakeLists.txt b/src/lib/tests/CMakeLists.txt index 5a2f2c52..32836589 100644 --- a/src/lib/tests/CMakeLists.txt +++ b/src/lib/tests/CMakeLists.txt @@ -10,7 +10,6 @@ create_test_sourcelist(${PARENT_DIR}_tests test_suite.c auth_test_slh_dsa.c bitmap_test.c btree_test.c - crc32_test.c crypt_test.c hash_test.c kex_test.c @@ -20,6 +19,7 @@ create_test_sourcelist(${PARENT_DIR}_tests test_suite.c sockets_test.c time_test.c tpm_test.c + tw_test.c ) add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests}) diff --git a/src/lib/tests/auth_test.c b/src/lib/tests/auth_test.c index 1a5a87af..0f3ef715 100644 --- a/src/lib/tests/auth_test.c +++ b/src/lib/tests/auth_test.c @@ -347,6 +347,59 @@ static int test_verify_crt(void) return TEST_RC_FAIL; } +static int test_verify_crt_missing_root_ca(void) +{ + struct auth_ctx * auth; + void * _signed_server_crt; + void * _im_ca_crt; + + TEST_START(); + + auth = auth_create_ctx(); + if (auth == NULL) { + printf("Failed to create auth context.\n"); + goto fail_create_ctx; + } + + if (crypt_load_crt_str(signed_server_crt_ec, &_signed_server_crt) < 0) { + printf("Failed to load signed crt from string.\n"); + goto fail_load_signed; + } + + if (crypt_load_crt_str(im_ca_crt_ec, &_im_ca_crt) < 0) { + printf("Failed to load intermediate crt from string.\n"); + goto fail_load_im_ca; + } + + /* Add only the intermediate CA - root CA is missing */ + if (auth_add_crt_to_store(auth, _im_ca_crt) < 0) { + printf("Failed to add intermediate ca crt to auth store.\n"); + goto fail_add; + } + + if (auth_verify_crt(auth, _signed_server_crt) == 0) { + printf("Verification should fail without root CA.\n"); + goto fail_add; + } + + crypt_free_crt(_im_ca_crt); + crypt_free_crt(_signed_server_crt); + auth_destroy_ctx(auth); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_add: + crypt_free_crt(_im_ca_crt); + fail_load_im_ca: + crypt_free_crt(_signed_server_crt); + fail_load_signed: + auth_destroy_ctx(auth); + fail_create_ctx: + TEST_FAIL(); + return TEST_RC_FAIL; +} + int test_auth_sign(void) { uint8_t buf[TEST_MSG_SIZE]; @@ -526,6 +579,7 @@ int auth_test(int argc, ret |= test_crypt_check_pubkey_crt(); ret |= test_store_add(); ret |= test_verify_crt(); + ret |= test_verify_crt_missing_root_ca(); ret |= test_auth_sign(); ret |= test_auth_bad_signature(); ret |= test_crt_str(); @@ -538,6 +592,7 @@ int auth_test(int argc, (void) test_crypt_check_pubkey_crt; (void) test_store_add; (void) test_verify_crt; + (void) test_verify_crt_missing_root_ca; (void) test_auth_sign; (void) test_auth_bad_signature; (void) test_crt_str; diff --git a/src/lib/tests/crc32_test.c b/src/lib/tests/crc32_test.c deleted file mode 100644 index 5a1ddd87..00000000 --- a/src/lib/tests/crc32_test.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2026 - * - * Test of the CRC32 function - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * 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., http://www.fsf.org/about/contact/. - */ - -#include <ouroboros/crc32.h> - -#include <stdlib.h> -#include <stdint.h> -#include <assert.h> -#include <string.h> -#include <stdio.h> - -/* - * Test vectors calculated at - * https://www.lammertbies.nl/comm/info/crc-calculation.html - */ - -int crc32_test(int argc, - char ** argv) -{ - uint32_t crc = 0; - int i = 0; - - (void) argc; - (void) argv; - - crc32(&crc, "0", 1); - if (crc != 0xF4DBDF21) - return -1; - - crc = 0; - - crc32(&crc, "123456789", 9); - if (crc != 0xCBF43926) - return -1; - - crc = 0; - - crc32(&crc, "987654321", 9); - if (crc != 0x015F0201) - return -1; - - crc32(&crc, "123456789", 9); - if (crc != 0x806B60E3) - return -1; - - crc = 0; - - crc32(&crc, &i , 1); - if (crc != 0xD202EF8D) - return -1; - - return 0; -} diff --git a/src/lib/tests/hash_test.c b/src/lib/tests/hash_test.c index e43847e1..451d3c25 100644 --- a/src/lib/tests/hash_test.c +++ b/src/lib/tests/hash_test.c @@ -39,6 +39,74 @@ struct vec_entry { char * out; }; +static int test_crc8(void) +{ + int ret = 0; + + struct vec_entry vec [] = { + { "", "00" }, + { "123456789", "df" }, + { NULL, NULL } + }; + + struct vec_entry * cur = vec; + + TEST_START(); + + while (cur->in != NULL) { + uint8_t crc; + char res[3]; + + str_hash(HASH_CRC8, &crc, cur->in); + + sprintf(res, "%02x", crc); + if (strcmp(res, cur->out) != 0) { + printf("Hash failed %s != %s.\n", res, cur->out); + ret |= -1; + } + + ++cur; + } + + TEST_END(ret); + + return ret; +} + +static int test_crc16(void) +{ + int ret = 0; + + struct vec_entry vec [] = { + { "", "ffff" }, + { "123456789", "29b1" }, + { NULL, NULL } + }; + + struct vec_entry * cur = vec; + + TEST_START(); + + while (cur->in != NULL) { + uint8_t crc[2]; + char res[5]; + + str_hash(HASH_CRC16, crc, cur->in); + + sprintf(res, "%02x%02x", crc[0], crc[1]); + if (strcmp(res, cur->out) != 0) { + printf("Hash failed %s != %s.\n", res, cur->out); + ret |= -1; + } + + ++cur; + } + + TEST_END(ret); + + return ret; +} + static int test_crc32(void) { int ret = 0; @@ -74,6 +142,42 @@ static int test_crc32(void) return ret; } +static int test_crc64(void) +{ + int ret = 0; + + struct vec_entry vec [] = { + { "", "0000000000000000" }, + { "123456789", "ae8b14860a799888" }, + { "0123456789abcdef", + "091485ca7018730e" }, + { NULL, NULL } + }; + + struct vec_entry * cur = vec; + + TEST_START(); + + while (cur->in != NULL) { + uint8_t crc[8]; + char res[17]; + + str_hash(HASH_CRC64, crc, cur->in); + + sprintf(res, HASH_FMT64, HASH_VAL64(crc)); + if (strcmp(res, cur->out) != 0) { + printf("Hash failed %s != %s.\n", res, cur->out); + ret |= -1; + } + + ++cur; + } + + TEST_END(ret); + + return ret; +} + static int test_md5(void) { int ret = 0; @@ -192,8 +296,14 @@ int hash_test(int argc, (void) argc; (void) argv; + ret |= test_crc8(); + + ret |= test_crc16(); + ret |= test_crc32(); + ret |= test_crc64(); + ret |= test_md5(); ret |= test_sha3(); diff --git a/src/lib/tests/kex_test.c b/src/lib/tests/kex_test.c index ced760fe..6a4f802e 100644 --- a/src/lib/tests/kex_test.c +++ b/src/lib/tests/kex_test.c @@ -106,7 +106,7 @@ static int test_kex_dh_pkp_create_destroy(void) { struct sec_config kex; void * pkp; - uint8_t buf[MSGBUFSZ]; + uint8_t buf[CRYPT_KEY_BUFSZ]; TEST_START(); @@ -134,7 +134,7 @@ static int test_kex_get_algo_from_pk(const char * algo) void * pkp; buffer_t pk; ssize_t len; - uint8_t buf[MSGBUFSZ]; + uint8_t buf[CRYPT_KEY_BUFSZ]; char extracted_algo[256]; TEST_START("(%s)", algo); @@ -204,8 +204,8 @@ static int test_kex_dhe_derive(const char * algo) buffer_t pk1; buffer_t pk2; ssize_t len; - uint8_t buf1[MSGBUFSZ]; - uint8_t buf2[MSGBUFSZ]; + uint8_t buf1[CRYPT_KEY_BUFSZ]; + uint8_t buf2[CRYPT_KEY_BUFSZ]; uint8_t s1[SYMMKEYSZ]; uint8_t s2[SYMMKEYSZ]; @@ -317,7 +317,7 @@ static int test_kex_dhe_corrupted_pubkey(const char * algo) void * pkp; buffer_t pk; ssize_t len; - uint8_t buf[MSGBUFSZ]; + uint8_t buf[CRYPT_KEY_BUFSZ]; uint8_t s[SYMMKEYSZ]; TEST_START("(%s)", algo); @@ -363,8 +363,8 @@ static int test_kex_dhe_wrong_algo(void) void * pkp2; buffer_t pk2; ssize_t len; - uint8_t buf1[MSGBUFSZ]; - uint8_t buf2[MSGBUFSZ]; + uint8_t buf1[CRYPT_KEY_BUFSZ]; + uint8_t buf2[CRYPT_KEY_BUFSZ]; uint8_t s[SYMMKEYSZ]; const char * algo1 = "X25519"; const char * algo2 = "X448"; diff --git a/src/lib/tests/kex_test_ml_kem.c b/src/lib/tests/kex_test_ml_kem.c index 3bb9ae7c..7761c3dc 100644 --- a/src/lib/tests/kex_test_ml_kem.c +++ b/src/lib/tests/kex_test_ml_kem.c @@ -197,8 +197,8 @@ static int test_kex_kem(const char * algo) buffer_t ct; ssize_t len; ssize_t ct_len; - uint8_t buf1[MSGBUFSZ]; - uint8_t buf2[MSGBUFSZ]; + uint8_t buf1[CRYPT_KEY_BUFSZ]; + uint8_t buf2[CRYPT_KEY_BUFSZ]; uint8_t s1[SYMMKEYSZ]; uint8_t s2[SYMMKEYSZ]; int kdf; @@ -262,8 +262,8 @@ static int test_kex_kem_corrupted_ciphertext(const char * algo) buffer_t ct; ssize_t len; ssize_t ct_len; - uint8_t buf1[MSGBUFSZ]; - uint8_t buf2[MSGBUFSZ]; + uint8_t buf1[CRYPT_KEY_BUFSZ]; + uint8_t buf2[CRYPT_KEY_BUFSZ]; uint8_t s1[SYMMKEYSZ]; uint8_t s2[SYMMKEYSZ]; int kdf; @@ -334,9 +334,9 @@ static int test_kex_kem_wrong_keypair(const char * algo) buffer_t ct; ssize_t len; ssize_t ct_len; - uint8_t buf1[MSGBUFSZ]; - uint8_t buf2[MSGBUFSZ]; - uint8_t buf3[MSGBUFSZ]; + uint8_t buf1[CRYPT_KEY_BUFSZ]; + uint8_t buf2[CRYPT_KEY_BUFSZ]; + uint8_t buf3[CRYPT_KEY_BUFSZ]; uint8_t s1[SYMMKEYSZ]; uint8_t s2[SYMMKEYSZ]; @@ -402,8 +402,8 @@ static int test_kex_kem_truncated_ciphertext(const char * algo) buffer_t ct; ssize_t len; ssize_t ct_len; - uint8_t buf1[MSGBUFSZ]; - uint8_t buf2[MSGBUFSZ]; + uint8_t buf1[CRYPT_KEY_BUFSZ]; + uint8_t buf2[CRYPT_KEY_BUFSZ]; uint8_t s1[SYMMKEYSZ]; uint8_t s2[SYMMKEYSZ]; diff --git a/src/lib/tests/tpm_test.c b/src/lib/tests/tpm_test.c index df1d8850..7cc049cd 100644 --- a/src/lib/tests/tpm_test.c +++ b/src/lib/tests/tpm_test.c @@ -21,7 +21,7 @@ */ -#include "tpm.c" +#include <ouroboros/tpm.h> #include <test/test.h> diff --git a/src/lib/tests/tw_test.c b/src/lib/tests/tw_test.c new file mode 100644 index 00000000..32c302c4 --- /dev/null +++ b/src/lib/tests/tw_test.c @@ -0,0 +1,663 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Generic timing-wheel tests + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) +#define _DEFAULT_SOURCE +#else +#define _POSIX_C_SOURCE 200809L +#endif + +#include "config.h" + +#include <test/test.h> + +#include <ouroboros/time.h> +#include <ouroboros/tw.h> + +#include <stdint.h> +#include <stdio.h> +#include <time.h> + +struct payload { + struct tw_entry tw; + int fired; +}; + +struct cancel_payload { + struct tw_entry tw; + int fired; + struct tw_entry * sibling; +}; + +struct repost_payload { + struct tw_entry tw; + int fired; + struct payload * sibling; + uint64_t repost_at; +}; + +static void cb_count(void * arg) +{ + struct payload * p = arg; + p->fired++; +} + +static void cb_cancel_sibling(void * arg) +{ + struct cancel_payload * p = arg; + p->fired++; + tw_cancel(p->sibling); +} + +static void cb_repost_sibling(void * arg) +{ + struct repost_payload * p = arg; + p->fired++; + tw_post(&p->sibling->tw, p->repost_at, cb_count, p->sibling); +} + +static uint64_t now_ns(void) +{ + struct timespec ts; + clock_gettime(PTHREAD_COND_CLOCK, &ts); + return TS_TO_UINT64(ts); +} + +static void sleep_ns(uint64_t ns) +{ + struct timespec ts; + UINT64_TO_TS(ns, &ts); + nanosleep(&ts, NULL); +} + +static int test_tw_init_fini(void) +{ + TEST_START(); + + if (tw_init() < 0) { + printf("tw_init failed.\n"); + goto fail; + } + + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_tw_post_fires_after_deadline(void) +{ + struct payload p; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + tw_init_entry(&p.tw); + p.fired = 0; + + tw_post(&p.tw, now_ns() + 5 * MILLION, cb_count, &p); + + sleep_ns(20 * MILLION); + tw_move(); + + if (p.fired != 1) { + printf("expected 1 fire, got %d\n", p.fired); + goto fail_post; + } + + tw_cancel(&p.tw); + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_post: + tw_cancel(&p.tw); + tw_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_tw_no_fire_before_deadline(void) +{ + struct payload p; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + tw_init_entry(&p.tw); + p.fired = 0; + + tw_post(&p.tw, now_ns() + 100 * MILLION, cb_count, &p); + + sleep_ns(2 * MILLION); + tw_move(); + + if (p.fired != 0) { + printf("expected 0 fires, got %d\n", p.fired); + goto fail_post; + } + + tw_cancel(&p.tw); + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_post: + tw_cancel(&p.tw); + tw_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_tw_cancel_prevents_fire(void) +{ + struct payload p; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + tw_init_entry(&p.tw); + p.fired = 0; + + tw_post(&p.tw, now_ns() + 5 * MILLION, cb_count, &p); + tw_cancel(&p.tw); + + sleep_ns(20 * MILLION); + tw_move(); + + if (p.fired != 0) { + printf("cancelled entry fired %d times\n", p.fired); + goto fail_init; + } + + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_init: + tw_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_tw_cancel_unposted_is_noop(void) +{ + struct tw_entry e; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + tw_init_entry(&e); + tw_cancel(&e); + tw_cancel(&e); + + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_tw_fire_only_once(void) +{ + struct payload p; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + tw_init_entry(&p.tw); + p.fired = 0; + + tw_post(&p.tw, now_ns() + 3 * MILLION, cb_count, &p); + + sleep_ns(20 * MILLION); + tw_move(); + tw_move(); + tw_move(); + + if (p.fired != 1) { + printf("expected 1 fire, got %d after 3 moves\n", p.fired); + goto fail_post; + } + + tw_cancel(&p.tw); + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_post: + tw_cancel(&p.tw); + tw_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Multi-level: post a level-1 (>= 256ms) deadline; should still fire. */ +static int test_tw_post_level1_fires(void) +{ + struct payload p; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + tw_init_entry(&p.tw); + p.fired = 0; + + tw_post(&p.tw, now_ns() + 300 * MILLION, cb_count, &p); + + if (p.tw.lvl != 1) { + printf("expected level 1 placement, got %zu\n", p.tw.lvl); + goto fail_post; + } + + sleep_ns(320 * MILLION); + tw_move(); + + if (p.fired != 1) { + printf("level-1 entry didn't fire (got %d)\n", p.fired); + goto fail_post; + } + + tw_cancel(&p.tw); + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_post: + tw_cancel(&p.tw); + tw_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_tw_many_entries_all_fire(void) +{ + struct payload pl[16]; + size_t i; + size_t total = 0; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + for (i = 0; i < 16; ++i) { + tw_init_entry(&pl[i].tw); + pl[i].fired = 0; + tw_post(&pl[i].tw, now_ns() + (1 + i) * MILLION, + cb_count, &pl[i]); + } + + sleep_ns(40 * MILLION); + tw_move(); + + for (i = 0; i < 16; ++i) + total += pl[i].fired; + + if (total != 16) { + printf("expected 16 fires, got %zu\n", total); + goto fail_post; + } + + for (i = 0; i < 16; ++i) + tw_cancel(&pl[i].tw); + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_post: + for (i = 0; i < 16; ++i) + tw_cancel(&pl[i].tw); + tw_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* tw_next_expiry signals empty wheel via tv_nsec == -1. */ +static int test_tw_next_expiry_empty(void) +{ + struct timespec out; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + tw_next_expiry(&out); + if (out.tv_nsec != -1) { + printf("expected tv_nsec=-1, got %ld\n", (long) out.tv_nsec); + goto fail_init; + } + + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_init: + tw_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* tw_next_expiry returns a deadline within the right ballpark. */ +static int test_tw_next_expiry_returns_deadline(void) +{ + struct payload p; + struct timespec out; + uint64_t target; + uint64_t out_ns; + int64_t skew; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + tw_init_entry(&p.tw); + p.fired = 0; + + target = now_ns() + 50 * MILLION; + tw_post(&p.tw, target, cb_count, &p); + + tw_next_expiry(&out); + out_ns = TS_TO_UINT64(out); + + /* Level-0 quantization gives ±1 slot of skew. */ + skew = (int64_t)(out_ns) - (int64_t)(target); + if (skew < -2 * MILLION || skew > 4 * MILLION) { + printf("deadline not in -2..+4 ms, skew=%ld ns\n", (long) skew); + goto fail_post; + } + + tw_cancel(&p.tw); + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_post: + tw_cancel(&p.tw); + tw_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Repost: fire, then post again. */ +static int test_tw_repost_after_fire(void) +{ + struct payload p; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + tw_init_entry(&p.tw); + p.fired = 0; + + tw_post(&p.tw, now_ns() + 3 * MILLION, cb_count, &p); + sleep_ns(20 * MILLION); + tw_move(); + if (p.fired != 1) { + printf("first fire missed\n"); + goto fail_post; + } + + tw_post(&p.tw, now_ns() + 3 * MILLION, cb_count, &p); + sleep_ns(20 * MILLION); + tw_move(); + if (p.fired != 2) { + printf("second fire missed (fired=%d)\n", p.fired); + goto fail_post; + } + + tw_cancel(&p.tw); + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_post: + tw_cancel(&p.tw); + tw_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Double-post replaces the schedule; only the second fires. */ +static int test_tw_double_post_replaces(void) +{ + struct payload p; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + tw_init_entry(&p.tw); + p.fired = 0; + + tw_post(&p.tw, now_ns() + 30 * MILLION, cb_count, &p); + tw_post(&p.tw, now_ns() + 3 * MILLION, cb_count, &p); + + sleep_ns(20 * MILLION); + tw_move(); + + if (p.fired != 1) { + printf("expected 1 fire after replace, got %d\n", p.fired); + goto fail_post; + } + + sleep_ns(40 * MILLION); + tw_move(); + + if (p.fired != 1) { + printf("first schedule fired after replace (got %d)\n", + p.fired); + goto fail_post; + } + + tw_cancel(&p.tw); + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_post: + tw_cancel(&p.tw); + tw_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Fire callback may safely cancel a sibling in the same slot. */ +static int test_tw_fire_cancels_sibling(void) +{ + struct cancel_payload a; + struct payload b; + uint64_t deadline; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + tw_init_entry(&a.tw); + tw_init_entry(&b.tw); + a.fired = 0; + a.sibling = &b.tw; + b.fired = 0; + + deadline = now_ns() + 3 * MILLION; + tw_post(&a.tw, deadline, cb_cancel_sibling, &a); + tw_post(&b.tw, deadline, cb_count, &b); + + sleep_ns(20 * MILLION); + tw_move(); + + if (a.fired != 1) { + printf("a expected 1 fire, got %d\n", a.fired); + goto fail_post; + } + if (b.fired != 0) { + printf("b should not have fired (got %d)\n", b.fired); + goto fail_post; + } + + tw_cancel(&a.tw); + tw_cancel(&b.tw); + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_post: + tw_cancel(&a.tw); + tw_cancel(&b.tw); + tw_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Fire callback may safely repost a sibling to a future slot. */ +static int test_tw_fire_posts_sibling(void) +{ + struct repost_payload a; + struct payload b; + uint64_t deadline; + + TEST_START(); + + if (tw_init() < 0) + goto fail; + + tw_init_entry(&a.tw); + tw_init_entry(&b.tw); + a.fired = 0; + a.sibling = &b; + a.repost_at = now_ns() + 30 * MILLION; + b.fired = 0; + + deadline = now_ns() + 3 * MILLION; + tw_post(&a.tw, deadline, cb_repost_sibling, &a); + tw_post(&b.tw, deadline, cb_count, &b); + + sleep_ns(20 * MILLION); + tw_move(); + + if (a.fired != 1) { + printf("a expected 1 fire, got %d\n", a.fired); + goto fail_post; + } + if (b.fired != 0) { + printf("b fired before reposted deadline (got %d)\n", + b.fired); + goto fail_post; + } + + sleep_ns(25 * MILLION); + tw_move(); + + if (b.fired != 1) { + printf("b expected 1 fire after repost, got %d\n", + b.fired); + goto fail_post; + } + + tw_cancel(&a.tw); + tw_cancel(&b.tw); + tw_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_post: + tw_cancel(&a.tw); + tw_cancel(&b.tw); + tw_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int tw_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_tw_init_fini(); + ret |= test_tw_post_fires_after_deadline(); + ret |= test_tw_no_fire_before_deadline(); + ret |= test_tw_cancel_prevents_fire(); + ret |= test_tw_cancel_unposted_is_noop(); + ret |= test_tw_fire_only_once(); + ret |= test_tw_post_level1_fires(); + ret |= test_tw_many_entries_all_fire(); + ret |= test_tw_next_expiry_empty(); + ret |= test_tw_next_expiry_returns_deadline(); + ret |= test_tw_repost_after_fire(); + ret |= test_tw_double_post_replaces(); + ret |= test_tw_fire_cancels_sibling(); + ret |= test_tw_fire_posts_sibling(); + + return ret; +} |
