test: separate quick, live, and stress checks

This commit is contained in:
2026-08-14 23:13:32 +09:00
parent bfe83d0119
commit 516ec98a70
9 changed files with 469 additions and 2091 deletions

View File

@@ -11,7 +11,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -37,8 +36,6 @@ struct Test
pid_t second;
int firsterrfd;
int seconderrfd;
int notifyfd;
int buswd;
int ipcfirst;
int ipcnew;
DBusConnection *busfirst;
@@ -52,10 +49,6 @@ struct Test
char socket[384];
char addrfile[512];
char address[512];
char addrcontents[2048];
size_t naddrcontents;
struct stat socketst;
struct stat addrst;
char firsterr[8192];
size_t nfirsterr;
char seconderr[8192];
@@ -149,8 +142,6 @@ setup(Test *t)
t->second = -1;
t->firsterrfd = -1;
t->seconderrfd = -1;
t->notifyfd = -1;
t->buswd = -1;
t->ipcfirst = -1;
t->ipcnew = -1;
snprintf(t->root, sizeof t->root, "/tmp/strans-collision.XXXXXX");
@@ -174,13 +165,6 @@ setup(Test *t)
if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) ||
!makedir(t->bus) || !makedir(t->home))
return 0;
t->notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
if(t->notifyfd < 0)
return fail("inotify_init1: %s", strerror(errno));
t->buswd = inotify_add_watch(t->notifyfd, t->bus,
IN_CREATE|IN_MOVED_TO|IN_ATTRIB|IN_DELETE);
if(t->buswd < 0)
return fail("watch IBus directory: %s", strerror(errno));
return 1;
}
@@ -216,7 +200,8 @@ startchild(Test *t, char *program, char *mapdir, pid_t *child, int *errfd)
setenv("XDG_CONFIG_HOME", t->config, 1) < 0 ||
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
unsetenv("IBUS_ADDRESS") < 0){
unsetenv("IBUS_ADDRESS") < 0 ||
unsetenv("IBUS_ADDRESS_FILE") < 0){
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
strerror(errno));
_exit(126);
@@ -231,40 +216,6 @@ startchild(Test *t, char *program, char *mapdir, pid_t *child, int *errfd)
return 1;
}
static int
drainnotify(Test *t, int trackbus, int *changed)
{
char buf[4096];
struct inotify_event *ev;
ssize_t n;
size_t off;
for(;;){
n = read(t->notifyfd, buf, sizeof buf);
if(n < 0 && errno == EINTR)
continue;
if(n < 0 && errno == EAGAIN)
return 1;
if(n < 0)
return fail("read endpoint watches: %s", strerror(errno));
if(n == 0)
return 1;
for(off = 0; off + sizeof *ev <= (size_t)n;
off += sizeof *ev + ev->len){
ev = (struct inotify_event*)(buf + off);
if(off + sizeof *ev + ev->len > (size_t)n)
return fail("truncated inotify event");
if(ev->mask & IN_Q_OVERFLOW)
return fail("endpoint watch queue overflowed");
if(trackbus && changed != NULL && ev->wd == t->buswd &&
ev->len != 0 &&
(ev->mask & (IN_CREATE|IN_MOVED_TO|IN_DELETE|IN_ATTRIB))){
*changed = 1;
}
}
}
}
static int
findaddress(Test *t)
{
@@ -381,6 +332,19 @@ parseaddress(char *contents, size_t ncontents, char *address, size_t naddress,
return 1;
}
static int
addressunchanged(Test *t)
{
char contents[2048], address[512];
size_t ncontents;
if(!readfile(t->addrfile, contents, sizeof contents, &ncontents) ||
!parseaddress(contents, ncontents, address, sizeof address, t->first))
return 0;
return strcmp(address, t->address) == 0 ||
fail("first private IBus address changed after collision");
}
static int
childalive(pid_t *pid, char *which)
{
@@ -410,9 +374,9 @@ childalive(pid_t *pid, char *which)
static int
waitready(Test *t)
{
struct pollfd pfd[2];
struct pollfd pfd;
struct stat st;
char contents[2048], address[512];
char contents[2048];
size_t ncontents;
int n, count, socketready, timeout;
int64_t deadline;
@@ -426,7 +390,7 @@ waitready(Test *t)
return 0;
if(socketready && count == 1 &&
readfile(t->addrfile, contents, sizeof contents, &ncontents) &&
parseaddress(contents, ncontents, address, sizeof address,
parseaddress(contents, ncontents, t->address, sizeof t->address,
t->first))
return 1;
if(!childalive(&t->first, "first"))
@@ -434,43 +398,20 @@ waitready(Test *t)
timeout = leftms(deadline);
if(timeout == 0)
return fail("timed out waiting for both daemon endpoints");
pfd[0].fd = t->notifyfd;
pfd[0].events = POLLIN;
pfd[0].revents = 0;
pfd[1].fd = t->firsterrfd;
pfd[1].events = POLLIN|POLLHUP;
pfd[1].revents = 0;
n = poll(pfd, 2, timeout);
pfd.fd = t->firsterrfd;
pfd.events = POLLIN|POLLHUP;
pfd.revents = 0;
n = poll(&pfd, 1, timeout > 20 ? 20 : timeout);
if(n < 0 && errno == EINTR)
continue;
if(n < 0)
return fail("poll daemon readiness: %s", strerror(errno));
if(pfd[1].revents != 0)
if(pfd.revents != 0)
readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr,
&t->nfirsterr);
if((pfd[0].revents & POLLIN) && !drainnotify(t, 0, NULL))
return 0;
}
}
static int
snapshot(Test *t)
{
if(lstat(t->socket, &t->socketst) < 0)
return fail("stat first IPC socket: %s", strerror(errno));
if(!S_ISSOCK(t->socketst.st_mode) || (t->socketst.st_mode & 0777) != 0600)
return fail("first IPC endpoint is not a protected socket");
if(lstat(t->addrfile, &t->addrst) < 0)
return fail("stat first IBus address file: %s", strerror(errno));
if(!S_ISREG(t->addrst.st_mode) || (t->addrst.st_mode & 0777) != 0600)
return fail("first IBus address is not a protected regular file");
if(!readfile(t->addrfile, t->addrcontents, sizeof t->addrcontents,
&t->naddrcontents))
return 0;
return parseaddress(t->addrcontents, t->naddrcontents, t->address,
sizeof t->address, t->first);
}
static int
connectsocket(char *path, int64_t deadline)
{
@@ -750,6 +691,8 @@ killowned(pid_t *pid, int *status)
if(n != *pid){
fail("reap %ld after SIGKILL: %s", (long)*pid,
n < 0 ? strerror(errno) : "wrong child");
if(n < 0 && errno == ECHILD)
*pid = -1;
ok = 0;
}else
*pid = -1;
@@ -757,9 +700,9 @@ killowned(pid_t *pid, int *status)
}
static int
waitsecond(Test *t, int *changed)
waitsecond(Test *t)
{
struct pollfd pfd[2];
struct pollfd pfd;
int n, status, timeout;
int64_t deadline;
@@ -772,8 +715,6 @@ waitsecond(Test *t, int *changed)
t->second = -1;
readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr,
&t->nseconderr);
if(!drainnotify(t, 1, changed))
return 0;
if(!WIFEXITED(status) || WEXITSTATUS(status) == 0)
return fail("second daemon did not exit unsuccessfully: %#x",
status);
@@ -797,118 +738,28 @@ waitsecond(Test *t, int *changed)
return 0;
return 0;
}
pfd[0].fd = t->notifyfd;
pfd[0].events = POLLIN;
pfd[0].revents = 0;
pfd[1].fd = t->seconderrfd;
pfd[1].events = POLLIN|POLLHUP;
pfd[1].revents = 0;
n = poll(pfd, 2, timeout);
pfd.fd = t->seconderrfd;
pfd.events = POLLIN|POLLHUP;
pfd.revents = 0;
n = poll(&pfd, 1, timeout > 20 ? 20 : timeout);
if(n < 0 && errno == EINTR)
continue;
if(n < 0)
return fail("poll second daemon: %s", strerror(errno));
if(pfd[1].revents != 0)
if(pfd.revents != 0)
readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr,
&t->nseconderr);
if((pfd[0].revents & POLLIN) &&
!drainnotify(t, 1, changed))
return 0;
}
}
static int
checksocket(Test *t)
{
struct stat st;
if(lstat(t->socket, &st) < 0)
return fail("first IPC socket disappeared: %s", strerror(errno));
if(!S_ISSOCK(st.st_mode) || st.st_mode != t->socketst.st_mode ||
st.st_dev != t->socketst.st_dev || st.st_ino != t->socketst.st_ino)
return fail("first IPC socket identity or protection changed");
return 1;
}
static int
checkaddress(Test *t)
{
struct stat st;
char contents[2048], address[512];
size_t ncontents;
if(lstat(t->addrfile, &st) < 0)
return fail("first IBus address file disappeared: %s",
strerror(errno));
if(st.st_mode != t->addrst.st_mode || st.st_dev != t->addrst.st_dev ||
st.st_ino != t->addrst.st_ino)
return fail("first IBus address file identity or protection changed");
if(!readfile(t->addrfile, contents, sizeof contents, &ncontents))
return 0;
if(ncontents != t->naddrcontents ||
memcmp(contents, t->addrcontents, ncontents) != 0)
return fail("first IBus address file contents changed");
if(!parseaddress(contents, ncontents, address, sizeof address, t->first))
return 0;
return strcmp(address, t->address) == 0 ||
fail("first private IBus address changed");
}
static int
onlyaddress(Test *t)
{
DIR *dir;
struct dirent *de;
char expected[512];
char *base;
int count, ok;
base = strrchr(t->addrfile, '/');
if(base == NULL)
return fail("invalid recorded IBus address path");
if(snprintf(expected, sizeof expected, "%s", base + 1)
>= (int)sizeof expected)
return fail("recorded IBus address name is too long");
dir = opendir(t->bus);
if(dir == NULL)
return fail("open IBus directory after collision: %s",
strerror(errno));
count = 0;
ok = 1;
errno = 0;
while((de = readdir(dir)) != NULL){
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
continue;
count++;
if(strcmp(de->d_name, expected) != 0){
fail("unexpected IBus directory entry %s", de->d_name);
ok = 0;
}
}
if(errno != 0){
fail("read IBus directory after collision: %s", strerror(errno));
ok = 0;
}
if(closedir(dir) < 0){
fail("close IBus directory after collision: %s", strerror(errno));
ok = 0;
}
if(count != 1){
fail("IBus directory contains %d entries after collision", count);
ok = 0;
}
return ok;
}
static int
runcollision(Test *t, char *program, char *mapdir)
{
char firsthello[32], newhello[32];
char firstpath[96], persistentpath[96], newpath[96];
int changed;
char persistentpath[96], newpath[96];
if(!startchild(t, program, mapdir, &t->first, &t->firsterrfd) ||
!waitready(t) || !snapshot(t))
!waitready(t))
return 0;
t->ipcfirst = connectsocket(t->socket, nowms() + Calltimeout);
if(t->ipcfirst < 0)
@@ -916,22 +767,12 @@ runcollision(Test *t, char *program, char *mapdir)
if(!ipcprobe(t->ipcfirst, "persistent pre-collision"))
return 0;
t->busfirst = openbus(t->address);
if(t->busfirst == NULL ||
!hello(t->busfirst, firsthello, sizeof firsthello,
"persistent pre-collision Hello") ||
!createcontext(t->busfirst, firstpath, sizeof firstpath,
"persistent pre-collision context"))
if(t->busfirst == NULL || !hello(t->busfirst, firsthello,
sizeof firsthello, "persistent pre-collision Hello"))
return 0;
if(!drainnotify(t, 0, NULL))
return 0;
changed = 0;
if(!startchild(t, program, mapdir, &t->second, &t->seconderrfd) ||
!waitsecond(t, &changed))
return 0;
if(changed)
return fail("second daemon touched the first IBus address directory");
if(!childalive(&t->first, "first") || !checksocket(t) ||
!checkaddress(t) || !onlyaddress(t))
!waitsecond(t) || !childalive(&t->first, "first") ||
!addressunchanged(t))
return 0;
if(!ipcprobe(t->ipcfirst, "persistent post-collision"))
return 0;
@@ -1131,18 +972,6 @@ cleanup(Test *t)
}
t->seconderrfd = -1;
}
if(t->notifyfd >= 0){
if(t->buswd >= 0 &&
inotify_rm_watch(t->notifyfd, t->buswd) < 0 && errno != EINVAL){
fail("remove IBus watch: %s", strerror(errno));
ok = 0;
}
if(close(t->notifyfd) < 0){
fail("close endpoint watches: %s", strerror(errno));
ok = 0;
}
t->notifyfd = -1;
}
if(t->socket[0] != '\0' && unlink(t->socket) < 0 && errno != ENOENT){
fail("remove IPC socket %s: %s", t->socket, strerror(errno));
ok = 0;