summaryrefslogtreecommitdiff
path: root/tests/common/test_loop.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/common/test_loop.c')
-rw-r--r--tests/common/test_loop.c248
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;
+}