From b44663096f88f809e0d75eae0f5daa701e3ff039 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 14 Aug 2026 00:11:03 +0900 Subject: [PATCH] tests: verify live daemon cleanup --- tests/ibus_live_test.c | 118 +++++++++++++++++++++++++++++++---------- tests/ipc_live_test.c | 88 ++++++++++++++++++++++++------ 2 files changed, 160 insertions(+), 46 deletions(-) diff --git a/tests/ibus_live_test.c b/tests/ibus_live_test.c index 18e7424..6008b88 100644 --- a/tests/ibus_live_test.c +++ b/tests/ibus_live_test.c @@ -51,7 +51,8 @@ struct Siglog int invalid; int npreedit; int ncommit; - char path[96]; + char preeditpath[96]; + char commitpath[96]; char preedit[256]; char commit[256]; dbus_uint32_t cursor; @@ -279,12 +280,15 @@ startdaemon(Daemon *d, char *program, char *mapdir) dup2(errpipe[1], STDERR_FILENO) < 0) _exit(126); close(errpipe[1]); - setenv("XDG_RUNTIME_DIR", d->runtime, 1); - setenv("XDG_CONFIG_HOME", d->config, 1); - setenv("HOME", d->home, 1); - unsetenv("DISPLAY"); - unsetenv("DBUS_SESSION_BUS_ADDRESS"); - unsetenv("IBUS_ADDRESS"); + if(setenv("XDG_RUNTIME_DIR", d->runtime, 1) < 0 || + setenv("XDG_CONFIG_HOME", d->config, 1) < 0 || + setenv("HOME", d->home, 1) < 0 || unsetenv("DISPLAY") < 0 || + unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 || + unsetenv("IBUS_ADDRESS") < 0){ + dprintf(STDERR_FILENO, "set daemon environment: %s\n", + strerror(errno)); + _exit(126); + } execl(program, program, mapdir, missing, (char*)0); dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno)); _exit(127); @@ -350,36 +354,82 @@ rmdirknown(char *path) return fail("rmdir %s: %s", path, strerror(errno)); } +static int +killdaemon(Daemon *d, int *status) +{ + int n, ok; + + ok = 1; + if(kill(d->pid, SIGKILL) < 0 && errno != ESRCH){ + fail("kill -9 %ld: %s", (long)d->pid, strerror(errno)); + ok = 0; + } + do + n = waitpid(d->pid, status, 0); + while(n < 0 && errno == EINTR); + if(n != d->pid){ + fail("reap %ld after SIGKILL: %s", (long)d->pid, + n < 0 ? strerror(errno) : "wrong child"); + ok = 0; + }else + d->pid = -1; + return ok; +} + static int stopdaemon(Daemon *d) { struct pollfd pfd; - int ok, status, n; + int ok, status, n, reaped; int64_t deadline; ok = 1; if(d->pid > 0){ - if(kill(d->pid, SIGTERM) < 0 && errno != ESRCH){ + do + n = waitpid(d->pid, &status, WNOHANG); + while(n < 0 && errno == EINTR); + if(n == d->pid){ + fail("daemon exited before test termination with wait status %#x", + status); + d->pid = -1; + ok = 0; + }else if(n < 0){ + fail("check daemon %ld before termination: %s", + (long)d->pid, strerror(errno)); + if(errno == ECHILD) + d->pid = -1; + ok = 0; + } + } + if(d->pid > 0){ + if(kill(d->pid, SIGTERM) < 0){ fail("kill %ld: %s", (long)d->pid, strerror(errno)); ok = 0; } + reaped = 0; deadline = nowms() + Stoptimeout; for(;;){ n = waitpid(d->pid, &status, WNOHANG); - if(n == d->pid) + if(n == d->pid){ + reaped = 1; + d->pid = -1; break; - if(n < 0 && errno != EINTR){ + } + if(n < 0 && errno == EINTR) + continue; + if(n < 0){ fail("waitpid %ld: %s", (long)d->pid, strerror(errno)); ok = 0; + if(errno == ECHILD) + d->pid = -1; break; } n = leftms(deadline); if(n == 0){ fail("daemon %ld did not stop after SIGTERM", (long)d->pid); ok = 0; - kill(d->pid, SIGKILL); - while(waitpid(d->pid, &status, 0) < 0 && errno == EINTR) - ; + if(!killdaemon(d, &status)) + ok = 0; break; } pfd.fd = d->errfd; @@ -388,14 +438,19 @@ stopdaemon(Daemon *d) if(poll(&pfd, 1, n) < 0 && errno != EINTR){ fail("poll daemon %ld: %s", (long)d->pid, strerror(errno)); ok = 0; - kill(d->pid, SIGKILL); - while(waitpid(d->pid, &status, 0) < 0 && errno == EINTR) - ; + if(!killdaemon(d, &status)) + ok = 0; break; } readerrors(d); } - d->pid = -1; + /* plan9port turns a caught termination note into exit status 1. */ + if(reaped && + !((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) || + (WIFEXITED(status) && WEXITSTATUS(status) == 1))){ + fail("daemon exited with unexpected wait status %#x", status); + ok = 0; + } } readerrors(d); if(d->errfd >= 0){ @@ -490,15 +545,13 @@ onsignal(DBusConnection *conn, DBusMessage *m, void *arg) if(dbus_message_get_type(m) != DBUS_MESSAGE_TYPE_SIGNAL || !dbus_message_has_interface(m, "org.freedesktop.IBus.InputContext")) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - path = dbus_message_get_path(m); - if(path == NULL || snprintf(log->path, sizeof log->path, "%s", path) - >= (int)sizeof log->path){ - log->invalid = 1; - return DBUS_HANDLER_RESULT_HANDLED; - } if(dbus_message_has_member(m, "UpdatePreeditText")){ log->npreedit++; - if(!dbus_message_has_signature(m, "vub") || + path = dbus_message_get_path(m); + if(path == NULL || snprintf(log->preeditpath, + sizeof log->preeditpath, "%s", path) >= + (int)sizeof log->preeditpath || + !dbus_message_has_signature(m, "vub") || !dbus_message_iter_init(m, &it) || !ibustext(&it, log->preedit, sizeof log->preedit) || !dbus_message_iter_next(&it) || @@ -519,7 +572,11 @@ onsignal(DBusConnection *conn, DBusMessage *m, void *arg) } if(dbus_message_has_member(m, "CommitText")){ log->ncommit++; - if(!dbus_message_has_signature(m, "v") || + path = dbus_message_get_path(m); + if(path == NULL || snprintf(log->commitpath, + sizeof log->commitpath, "%s", path) >= + (int)sizeof log->commitpath || + !dbus_message_has_signature(m, "v") || !dbus_message_iter_init(m, &it) || !ibustext(&it, log->commit, sizeof log->commit) || dbus_message_iter_next(&it)) @@ -834,10 +891,11 @@ preedit(Siglog *log, char *path, char *text, unsigned int cursor, int visible, char *where) { if(log->invalid || log->npreedit != 1 || log->ncommit != 0 || - strcmp(log->path, path) != 0 || strcmp(log->preedit, text) != 0 || + strcmp(log->preeditpath, path) != 0 || + strcmp(log->preedit, text) != 0 || log->cursor != cursor || (log->visible != FALSE) != (visible != 0)) return fail("%s preedit: count=%d commit=%d path=%s text=%s cursor=%u visible=%d", - where, log->npreedit, log->ncommit, log->path, log->preedit, + where, log->npreedit, log->ncommit, log->preeditpath, log->preedit, (unsigned)log->cursor, log->visible != FALSE); return 1; } @@ -846,7 +904,9 @@ static int committed(Siglog *log, char *path, char *text, char *where) { if(log->invalid || log->npreedit != 1 || log->ncommit != 1 || - strcmp(log->path, path) != 0 || strcmp(log->commit, text) != 0 || + strcmp(log->preeditpath, path) != 0 || + strcmp(log->commitpath, path) != 0 || + strcmp(log->commit, text) != 0 || strcmp(log->preedit, "") != 0 || log->cursor != 0 || log->visible != FALSE) return fail("%s signals: preedit=%d commit=%d text=%s/%s", diff --git a/tests/ipc_live_test.c b/tests/ipc_live_test.c index 7a06427..fbff67f 100644 --- a/tests/ipc_live_test.c +++ b/tests/ipc_live_test.c @@ -228,12 +228,15 @@ startdaemon(Daemon *d, char *program, char *mapdir) dup2(errpipe[1], STDERR_FILENO) < 0) _exit(126); close(errpipe[1]); - setenv("XDG_RUNTIME_DIR", d->runtime, 1); - setenv("XDG_CONFIG_HOME", d->config, 1); - setenv("HOME", d->home, 1); - unsetenv("DISPLAY"); - unsetenv("DBUS_SESSION_BUS_ADDRESS"); - unsetenv("IBUS_ADDRESS"); + if(setenv("XDG_RUNTIME_DIR", d->runtime, 1) < 0 || + setenv("XDG_CONFIG_HOME", d->config, 1) < 0 || + setenv("HOME", d->home, 1) < 0 || unsetenv("DISPLAY") < 0 || + unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 || + unsetenv("IBUS_ADDRESS") < 0){ + dprintf(STDERR_FILENO, "set daemon environment: %s\n", + strerror(errno)); + _exit(126); + } execl(program, program, mapdir, missing, (char*)0); dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno)); _exit(127); @@ -297,36 +300,82 @@ rmdirknown(char *path) return fail("rmdir %s: %s", path, strerror(errno)); } +static int +killdaemon(Daemon *d, int *status) +{ + int n, ok; + + ok = 1; + if(kill(d->pid, SIGKILL) < 0 && errno != ESRCH){ + fail("kill -9 %ld: %s", (long)d->pid, strerror(errno)); + ok = 0; + } + do + n = waitpid(d->pid, status, 0); + while(n < 0 && errno == EINTR); + if(n != d->pid){ + fail("reap %ld after SIGKILL: %s", (long)d->pid, + n < 0 ? strerror(errno) : "wrong child"); + ok = 0; + }else + d->pid = -1; + return ok; +} + static int stopdaemon(Daemon *d) { struct pollfd pfd; - int ok, status, n; + int ok, status, n, reaped; int64_t deadline; ok = 1; if(d->pid > 0){ - if(kill(d->pid, SIGTERM) < 0 && errno != ESRCH){ + do + n = waitpid(d->pid, &status, WNOHANG); + while(n < 0 && errno == EINTR); + if(n == d->pid){ + fail("daemon exited before test termination with wait status %#x", + status); + d->pid = -1; + ok = 0; + }else if(n < 0){ + fail("check daemon %ld before termination: %s", + (long)d->pid, strerror(errno)); + if(errno == ECHILD) + d->pid = -1; + ok = 0; + } + } + if(d->pid > 0){ + if(kill(d->pid, SIGTERM) < 0){ fail("kill %ld: %s", (long)d->pid, strerror(errno)); ok = 0; } + reaped = 0; deadline = nowms() + Stoptimeout; for(;;){ n = waitpid(d->pid, &status, WNOHANG); - if(n == d->pid) + if(n == d->pid){ + reaped = 1; + d->pid = -1; break; - if(n < 0 && errno != EINTR){ + } + if(n < 0 && errno == EINTR) + continue; + if(n < 0){ fail("waitpid %ld: %s", (long)d->pid, strerror(errno)); ok = 0; + if(errno == ECHILD) + d->pid = -1; break; } n = leftms(deadline); if(n == 0){ fail("daemon %ld did not stop after SIGTERM", (long)d->pid); ok = 0; - kill(d->pid, SIGKILL); - while(waitpid(d->pid, &status, 0) < 0 && errno == EINTR) - ; + if(!killdaemon(d, &status)) + ok = 0; break; } pfd.fd = d->errfd; @@ -335,14 +384,19 @@ stopdaemon(Daemon *d) if(poll(&pfd, 1, n) < 0 && errno != EINTR){ fail("poll daemon %ld: %s", (long)d->pid, strerror(errno)); ok = 0; - kill(d->pid, SIGKILL); - while(waitpid(d->pid, &status, 0) < 0 && errno == EINTR) - ; + if(!killdaemon(d, &status)) + ok = 0; break; } readerrors(d); } - d->pid = -1; + /* plan9port turns a caught termination note into exit status 1. */ + if(reaped && + !((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) || + (WIFEXITED(status) && WEXITSTATUS(status) == 1))){ + fail("daemon exited with unexpected wait status %#x", status); + ok = 0; + } } readerrors(d); if(d->errfd >= 0){