diff options
Diffstat (limited to 'tests/common')
| -rw-r--r-- | tests/common/test_loop.c | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/tests/common/test_loop.c b/tests/common/test_loop.c new file mode 100644 index 0000000..f982e5e --- /dev/null +++ b/tests/common/test_loop.c @@ -0,0 +1,248 @@ +#define _GNU_SOURCE +#define _DARWIN_C_SOURCE +#include "yasl/loop.h" + +#include <assert.h> +#include <errno.h> +#include <pthread.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +static void sleep_ms(int ms) +{ + struct timespec ts = {.tv_sec = ms / 1000, .tv_nsec = (long)(ms % 1000) * 1000000L}; + nanosleep(&ts, NULL); +} + +/* Fail instead of hanging when a wake never arrives. */ +static void on_alarm(int signo) +{ + (void)signo; + static const char msg[] = "loop test timed out\n"; + ssize_t ignored = write(STDERR_FILENO, msg, sizeof(msg) - 1); + (void)ignored; + _exit(1); +} + +struct post_log { + struct yasl_loop *loop; + char order[8]; + int count; +}; + +static void post_a(void *ud); + +static void post_c(void *ud) +{ + struct post_log *log = ud; + log->order[log->count++] = 'c'; +} + +static void post_a(void *ud) +{ + struct post_log *log = ud; + log->order[log->count++] = 'a'; + assert(yasl_loop_post(log->loop, post_c, log)); +} + +static void post_b(void *ud) +{ + struct post_log *log = ud; + log->order[log->count++] = 'b'; +} + +static void test_post_order(void) +{ + struct yasl_loop *l = yasl_loop_create(); + assert(l); + assert(!yasl_loop_post(NULL, post_b, NULL)); + assert(!yasl_loop_post(l, NULL, NULL)); + + struct post_log log = {.loop = l}; + assert(yasl_loop_post(l, post_a, &log)); + assert(yasl_loop_post(l, post_b, &log)); + 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_loop_step(l, 0) >= 1); + assert(log.count == 3 && log.order[2] == 'c'); + + /* Destroy drops posts that never ran. */ + assert(yasl_loop_post(l, post_b, &log)); + yasl_loop_destroy(l); + assert(log.count == 3); +} + +struct thread_ctx { + struct yasl_loop *loop; + bool kick; + int signo; + _Atomic bool ran; +}; + +static void mark_ran(void *ud) +{ + struct thread_ctx *ctx = ud; + ctx->ran = true; +} + +static void *wake_thread(void *ud) +{ + struct thread_ctx *ctx = ud; + sleep_ms(30); + if (ctx->signo) + kill(getpid(), ctx->signo); + else if (ctx->kick) + yasl_loop_kick(ctx->loop); + else + assert(yasl_loop_post(ctx->loop, mark_ran, ctx)); + return NULL; +} + +static void test_cross_thread(void) +{ + 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_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_loop_step(l, -1) >= 1); + pthread_join(t, NULL); + + yasl_loop_destroy(l); +} + +struct signal_log { + struct yasl_loop *loop; + int seen[NSIG]; + int total; + bool unwatch; +}; + +static void on_signal(void *ud, int signo) +{ + struct signal_log *log = ud; + assert(signo > 0 && signo < NSIG); + log->seen[signo]++; + log->total++; + if (log->unwatch) + yasl_loop_unwatch_signals(log->loop); +} + +static volatile sig_atomic_t previous_handler_hits; + +static void previous_handler(int signo) +{ + (void)signo; + previous_handler_hits++; +} + +static void step_until(struct yasl_loop *l, const int *counter, int want) +{ + while (*counter < want) + assert(yasl_loop_step(l, -1) >= 0); +} + +static void test_signals(void) +{ + struct sigaction prev = {.sa_handler = previous_handler}; + sigemptyset(&prev.sa_mask); + assert(sigaction(SIGUSR2, &prev, NULL) == 0); + + 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_loop_watch_signals(l, bad, 1, on_signal, &log) && errno == EINVAL); + const int sigs[] = {SIGUSR1, SIGUSR2, SIGCHLD}; + errno = 0; + assert(!yasl_loop_watch_signals(l, sigs, 0, on_signal, &log) && errno == EINVAL); + errno = 0; + assert(!yasl_loop_watch_signals(l, sigs, 3, NULL, &log) && errno == EINVAL); + assert(yasl_loop_watch_signals(l, sigs, 3, on_signal, &log)); + + /* One signal belongs to one loop at a time. */ + errno = 0; + assert(!yasl_loop_watch_signals(other, sigs, 1, on_signal, &log) && errno == EBUSY); + + /* Deliveries before the callback runs coalesce per signal. */ + raise(SIGUSR1); + raise(SIGUSR1); + raise(SIGUSR2); + assert(yasl_loop_step(l, 0) >= 1); + assert(log.seen[SIGUSR1] == 1 && log.seen[SIGUSR2] == 1); + assert(previous_handler_hits == 0); + + /* A signal sent from another thread wakes an indefinite wait. */ + struct thread_ctx ctx = {.loop = l, .signo = SIGUSR1}; + pthread_t t; + assert(pthread_create(&t, NULL, wake_thread, &ctx) == 0); + step_until(l, &log.seen[SIGUSR1], 2); + pthread_join(t, NULL); + + /* SIGCHLD arrives on the loop thread when a child exits. */ + pid_t child = fork(); + assert(child >= 0); + if (child == 0) _exit(0); + step_until(l, &log.seen[SIGCHLD], 1); + int status; + assert(waitpid(child, &status, 0) == child); + + /* Replacing the watch restores the signals it drops. */ + const int only_usr1[] = {SIGUSR1}; + assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log)); + raise(SIGUSR2); + assert(previous_handler_hits == 1); + assert(yasl_loop_watch_signals(other, (const int[]){SIGUSR2}, 1, on_signal, &log)); + yasl_loop_unwatch_signals(other); + assert(previous_handler_hits == 1); + raise(SIGUSR2); + assert(previous_handler_hits == 2); + + /* A callback may unwatch; the previous default action comes back. */ + log.unwatch = true; + raise(SIGUSR1); + step_until(l, &log.seen[SIGUSR1], 3); + struct sigaction now; + assert(sigaction(SIGUSR1, NULL, &now) == 0); + assert(now.sa_handler == SIG_DFL); + yasl_loop_unwatch_signals(l); + + /* Destroy unwatches. */ + log.unwatch = false; + assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log)); + yasl_loop_destroy(l); + assert(sigaction(SIGUSR1, NULL, &now) == 0); + assert(now.sa_handler == SIG_DFL); + yasl_loop_destroy(other); + + struct sigaction dfl = {.sa_handler = SIG_DFL}; + sigemptyset(&dfl.sa_mask); + sigaction(SIGUSR2, &dfl, NULL); +} + +int main(void) +{ + signal(SIGALRM, on_alarm); + alarm(10); + test_post_order(); + test_cross_thread(); + test_signals(); + printf("Loop tests passed.\n"); + return 0; +} |
