diff options
Diffstat (limited to 'tests/common/test_loop.c')
| -rw-r--r-- | tests/common/test_loop.c | 204 |
1 files changed, 170 insertions, 34 deletions
diff --git a/tests/common/test_loop.c b/tests/common/test_loop.c index 5c49691..bdc33b7 100644 --- a/tests/common/test_loop.c +++ b/tests/common/test_loop.c @@ -4,7 +4,9 @@ #include <assert.h> #include <errno.h> +#include <poll.h> #include <pthread.h> +#include <stdint.h> #include <signal.h> #include <stdio.h> #include <string.h> @@ -29,7 +31,7 @@ static void on_alarm(int signo) } struct post_log { - struct yasl_private_loop *loop; + struct yasl_loop *loop; char order[8]; int count; }; @@ -57,7 +59,7 @@ static void post_b(void *ud) static void test_post_order(void) { - struct yasl_private_loop *l = yasl_private_loop_create(); + struct yasl_loop *l = yasl_loop_create(); assert(l); assert(!yasl_private_loop_post(NULL, post_b, NULL)); assert(!yasl_private_loop_post(l, NULL, NULL)); @@ -65,20 +67,20 @@ static void test_post_order(void) struct post_log log = {.loop = l}; assert(yasl_private_loop_post(l, post_a, &log)); assert(yasl_private_loop_post(l, post_b, &log)); - assert(yasl_private_loop_step(l, 0) >= 1); + assert(yasl_loop_step(l, 0) >= 1); /* A post made while posts run waits for a later step. */ assert(log.count == 2 && memcmp(log.order, "ab", 2) == 0); - assert(yasl_private_loop_step(l, 0) >= 1); + assert(yasl_loop_step(l, 0) >= 1); assert(log.count == 3 && log.order[2] == 'c'); /* Destroy drops posts that never ran. */ assert(yasl_private_loop_post(l, post_b, &log)); - yasl_private_loop_destroy(l); + yasl_loop_destroy(l); assert(log.count == 3); } struct thread_ctx { - struct yasl_private_loop *loop; + struct yasl_loop *loop; bool kick; int signo; _Atomic bool ran; @@ -105,27 +107,27 @@ static void *wake_thread(void *ud) static void test_cross_thread(void) { - struct yasl_private_loop *l = yasl_private_loop_create(); + struct yasl_loop *l = yasl_loop_create(); assert(l); struct thread_ctx post = {.loop = l}; pthread_t t; assert(pthread_create(&t, NULL, wake_thread, &post) == 0); while (!post.ran) - assert(yasl_private_loop_step(l, -1) >= 0); + assert(yasl_loop_step(l, -1) >= 0); pthread_join(t, NULL); /* A kick ends an indefinite wait without running anything. */ struct thread_ctx kick = {.loop = l, .kick = true}; assert(pthread_create(&t, NULL, wake_thread, &kick) == 0); - assert(yasl_private_loop_step(l, -1) >= 1); + assert(yasl_loop_step(l, -1) >= 1); pthread_join(t, NULL); - yasl_private_loop_destroy(l); + yasl_loop_destroy(l); } struct signal_log { - struct yasl_private_loop *loop; + struct yasl_loop *loop; int seen[NSIG]; int total; bool unwatch; @@ -138,7 +140,7 @@ static void on_signal(void *ud, int signo) log->seen[signo]++; log->total++; if (log->unwatch) - yasl_private_loop_unwatch_signals(log->loop); + yasl_loop_unwatch_signals(log->loop); } static volatile sig_atomic_t previous_handler_hits; @@ -149,10 +151,10 @@ static void previous_handler(int signo) previous_handler_hits++; } -static void step_until(struct yasl_private_loop *l, const int *counter, int want) +static void step_until(struct yasl_loop *l, const int *counter, int want) { while (*counter < want) - assert(yasl_private_loop_step(l, -1) >= 0); + assert(yasl_loop_step(l, -1) >= 0); } static void test_signals(void) @@ -161,30 +163,26 @@ static void test_signals(void) sigemptyset(&prev.sa_mask); assert(sigaction(SIGUSR2, &prev, NULL) == 0); - struct yasl_private_loop *l = yasl_private_loop_create(); - struct yasl_private_loop *other = yasl_private_loop_create(); + struct yasl_loop *l = yasl_loop_create(); + struct yasl_loop *other = yasl_loop_create(); assert(l && other); struct signal_log log = {.loop = l}; const int bad[] = {SIGKILL}; - errno = 0; - assert(!yasl_private_loop_watch_signals(l, bad, 1, on_signal, &log) && errno == EINVAL); + assert(yasl_loop_watch_signals(l, bad, 1, on_signal, &log) == -EINVAL); const int sigs[] = {SIGUSR1, SIGUSR2, SIGCHLD}; - errno = 0; - assert(!yasl_private_loop_watch_signals(l, sigs, 0, on_signal, &log) && errno == EINVAL); - errno = 0; - assert(!yasl_private_loop_watch_signals(l, sigs, 3, NULL, &log) && errno == EINVAL); - assert(yasl_private_loop_watch_signals(l, sigs, 3, on_signal, &log)); + assert(yasl_loop_watch_signals(l, sigs, 0, on_signal, &log) == -EINVAL); + assert(yasl_loop_watch_signals(l, sigs, 3, NULL, &log) == -EINVAL); + assert(yasl_loop_watch_signals(l, sigs, 3, on_signal, &log) == 0); /* One signal belongs to one loop at a time. */ - errno = 0; - assert(!yasl_private_loop_watch_signals(other, sigs, 1, on_signal, &log) && errno == EBUSY); + assert(yasl_loop_watch_signals(other, sigs, 1, on_signal, &log) == -EBUSY); /* Deliveries before the callback runs coalesce per signal. */ raise(SIGUSR1); raise(SIGUSR1); raise(SIGUSR2); - assert(yasl_private_loop_step(l, 0) >= 1); + assert(yasl_loop_step(l, 0) >= 1); assert(log.seen[SIGUSR1] == 1 && log.seen[SIGUSR2] == 1); assert(previous_handler_hits == 0); @@ -203,13 +201,16 @@ static void test_signals(void) int status; assert(waitpid(child, &status, 0) == child); - /* Replacing the watch restores the signals it drops. */ + /* A loop has one watch, so a second cannot silently drop the first one's + * signals. Unwatching restores the previous actions. */ const int only_usr1[] = {SIGUSR1}; - assert(yasl_private_loop_watch_signals(l, only_usr1, 1, on_signal, &log)); + assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log) == -EBUSY); + yasl_loop_unwatch_signals(l); + assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log) == 0); raise(SIGUSR2); assert(previous_handler_hits == 1); - assert(yasl_private_loop_watch_signals(other, (const int[]){SIGUSR2}, 1, on_signal, &log)); - yasl_private_loop_unwatch_signals(other); + assert(yasl_loop_watch_signals(other, (const int[]){SIGUSR2}, 1, on_signal, &log) == 0); + yasl_loop_unwatch_signals(other); assert(previous_handler_hits == 1); raise(SIGUSR2); assert(previous_handler_hits == 2); @@ -221,21 +222,153 @@ static void test_signals(void) struct sigaction now; assert(sigaction(SIGUSR1, NULL, &now) == 0); assert(now.sa_handler == SIG_DFL); - yasl_private_loop_unwatch_signals(l); + yasl_loop_unwatch_signals(l); /* Destroy unwatches. */ log.unwatch = false; - assert(yasl_private_loop_watch_signals(l, only_usr1, 1, on_signal, &log)); - yasl_private_loop_destroy(l); + assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log) == 0); + assert(yasl_loop_destroy(l) == 0); assert(sigaction(SIGUSR1, NULL, &now) == 0); assert(now.sa_handler == SIG_DFL); - yasl_private_loop_destroy(other); + yasl_loop_destroy(other); struct sigaction dfl = {.sa_handler = SIG_DFL}; sigemptyset(&dfl.sa_mask); sigaction(SIGUSR2, &dfl, NULL); } +struct fd_log { + int calls; + short revents; +}; + +static void on_readable(void *ud, int fd, short revents) +{ + struct fd_log *log = ud; + char b; + if (revents & POLLIN) + assert(read(fd, &b, 1) == 1); + log->calls++; + log->revents = revents; +} + +static void test_fds(void) +{ + yasl_loop *l = yasl_loop_create(); + assert(l); + int p[2]; + assert(pipe(p) == 0); + struct fd_log log = {0}; + assert(yasl_loop_add_fd(l, -1, POLLIN, on_readable, &log) == -EINVAL); + assert(yasl_loop_add_fd(l, p[0], POLLIN, NULL, &log) == -EINVAL); + int id = yasl_loop_add_fd(l, p[0], POLLIN, on_readable, &log); + assert(id > 0); + assert(yasl_loop_step(l, 0) == 0 && log.calls == 0); + assert(write(p[1], "x", 1) == 1); + assert(yasl_loop_step(l, -1) == 1 && log.calls == 1 && (log.revents & POLLIN)); + /* No requested events: a ready descriptor stays quiet. */ + assert(yasl_loop_set_fd_events(l, id, 0) == 0); + assert(write(p[1], "x", 1) == 1); + assert(yasl_loop_step(l, 0) == 0 && log.calls == 1); + assert(yasl_loop_set_fd_events(l, id, POLLIN) == 0); + assert(yasl_loop_step(l, 0) == 1 && log.calls == 2); + assert(yasl_loop_del_fd(l, id) == 0); + assert(yasl_loop_del_fd(l, id) == -ENOENT); + assert(yasl_loop_set_fd_events(l, id, POLLIN) == -ENOENT); + assert(write(p[1], "x", 1) == 1); + assert(yasl_loop_step(l, 0) == 0 && log.calls == 2); + close(p[0]); + close(p[1]); + assert(yasl_loop_destroy(l) == 0); +} + +struct reentry { + yasl_loop *loop; + int step, run; + bool quit; +}; + +static uint64_t reenter(void *ud, uint64_t id, uint64_t interval_ns) +{ + (void)id; (void)interval_ns; + struct reentry *r = ud; + r->step = yasl_loop_step(r->loop, 0); + r->run = yasl_loop_run(r->loop); + if (r->quit) + yasl_loop_quit(r->loop); + return 0; +} + +static void *step_elsewhere(void *ud) +{ + return (void *)(intptr_t)yasl_loop_step(ud, 0); +} + +static void test_reentry_and_quit(void) +{ + yasl_loop *l = yasl_loop_create(); + assert(l); + /* A callback cannot step its own loop; the nested run keeps the outer quit. */ + struct reentry r = {.loop = l, .quit = true}; + assert(yasl_loop_add_timer(l, 0, reenter, &r)); + assert(yasl_loop_run(l) == 0); + assert(r.step == -EBUSY && r.run == -EBUSY); + + /* A quit before the run ends it at once, and the run clears it. */ + yasl_loop_quit(l); + assert(yasl_loop_run(l) == 0); + r = (struct reentry){.loop = l, .quit = true}; + assert(yasl_loop_add_timer(l, YASL_PRIVATE_MS_TO_NS(5), reenter, &r)); + assert(yasl_loop_run(l) == 0 && r.step == -EBUSY); + + /* Only the creating thread may step. */ + pthread_t t; + void *result; + assert(pthread_create(&t, NULL, step_elsewhere, l) == 0); + pthread_join(t, &result); + assert((intptr_t)result == -EPERM); + assert(yasl_loop_destroy(l) == 0); +} + +static void on_interrupt(int signo) +{ + (void)signo; +} + +struct interrupter { + pthread_t target; + int delay_ms; +}; + +static void *interrupt_thread(void *ud) +{ + struct interrupter *i = ud; + sleep_ms(i->delay_ms); + pthread_kill(i->target, SIGUSR2); + return NULL; +} + +/* An interrupted wait resumes with the time left, not the whole timeout. */ +static void test_interrupted_wait(void) +{ + struct sigaction sa = {.sa_handler = on_interrupt}; + sigemptyset(&sa.sa_mask); + struct sigaction old; + assert(sigaction(SIGUSR2, &sa, &old) == 0); + yasl_loop *l = yasl_loop_create(); + assert(l); + struct interrupter i = {.target = pthread_self(), .delay_ms = 60}; + pthread_t t; + uint64_t start = yasl_time_ns(); + assert(pthread_create(&t, NULL, interrupt_thread, &i) == 0); + assert(yasl_loop_step(l, 100) == 0); + uint64_t elapsed_ms = (yasl_time_ns() - start) / 1000000u; + pthread_join(t, NULL); + assert(elapsed_ms >= 90 && elapsed_ms < 140); + assert(yasl_loop_destroy(l) == 0); + sigaction(SIGUSR2, &old, NULL); +} + int main(void) { signal(SIGALRM, on_alarm); @@ -243,6 +376,9 @@ int main(void) test_post_order(); test_cross_thread(); test_signals(); + test_fds(); + test_reentry_and_quit(); + test_interrupted_wait(); printf("Loop tests passed.\n"); return 0; } |
