test: separate quick, live, and stress checks
This commit is contained in:
26
Makefile
26
Makefile
@@ -51,7 +51,14 @@ docker-build:
|
|||||||
$(DOCKER_RUN) make -B all
|
$(DOCKER_RUN) make -B all
|
||||||
|
|
||||||
docker-check:
|
docker-check:
|
||||||
$(DOCKER_RUN) make -B check TESTARGS="$(TESTARGS)"
|
$(DOCKER_RUN) make -B check check-live check-stress \
|
||||||
|
UNITARGS="$(UNITARGS)"
|
||||||
|
|
||||||
|
docker-check-live:
|
||||||
|
$(DOCKER_RUN) make -B check-live
|
||||||
|
|
||||||
|
docker-check-stress:
|
||||||
|
$(DOCKER_RUN) make -B check-stress
|
||||||
|
|
||||||
docker-bench:
|
docker-bench:
|
||||||
$(DOCKER_RUN) make -B bench
|
$(DOCKER_RUN) make -B bench
|
||||||
@@ -59,11 +66,17 @@ docker-bench:
|
|||||||
docker: docker-image
|
docker: docker-image
|
||||||
$(MAKE) docker-build
|
$(MAKE) docker-build
|
||||||
|
|
||||||
check: verify-map test
|
check: verify-map
|
||||||
|
$(MAKE) -C tests check UNITARGS="$(UNITARGS)"
|
||||||
$(MAKE) -C xim check
|
$(MAKE) -C xim check
|
||||||
|
|
||||||
test: $(PROG) gtk
|
check-live: $(PROG) gtk
|
||||||
$(MAKE) -C tests check TESTARGS="$(TESTARGS)"
|
$(MAKE) -C tests check-live
|
||||||
|
|
||||||
|
check-stress: $(PROG)
|
||||||
|
$(MAKE) -C tests check-stress
|
||||||
|
|
||||||
|
test: check
|
||||||
|
|
||||||
verify-map:
|
verify-map:
|
||||||
python3 map/mktelex.py | cmp - map/telex.map
|
python3 map/mktelex.py | cmp - map/telex.map
|
||||||
@@ -74,5 +87,6 @@ verify-map:
|
|||||||
python3 -B tests/mkhanja_test.py
|
python3 -B tests/mkhanja_test.py
|
||||||
python3 -B tests/skk2ktrans_test.py
|
python3 -B tests/skk2ktrans_test.py
|
||||||
|
|
||||||
.PHONY: all check test verify-map clean gtk bench docker docker-image \
|
.PHONY: all check check-live check-stress test verify-map clean gtk bench \
|
||||||
docker-build docker-check docker-bench
|
docker docker-image docker-build docker-check docker-check-live \
|
||||||
|
docker-check-stress docker-bench
|
||||||
|
|||||||
@@ -19,9 +19,12 @@ XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon-x11)
|
|||||||
LIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS)
|
LIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS)
|
||||||
|
|
||||||
PROG = unit_test
|
PROG = unit_test
|
||||||
LIVE = ibus_live_test ibus_client_smoke gtk_live_test xim_live_test \
|
STRESS = stress_test
|
||||||
ipc_live_test daemon_collision_test daemon_failure_test daemon_restart_test
|
SMOKE = ibus_live_test ibus_client_smoke gtk_live_test xim_live_test \
|
||||||
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
ipc_live_test
|
||||||
|
FAULT = daemon_collision_test daemon_failure_test daemon_restart_test
|
||||||
|
LIVE = $(SMOKE) $(FAULT)
|
||||||
|
TESTSRC = test_util.c str_test.c hash_test.c trie_test.c \
|
||||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
||||||
popup_test.c font_test.c ibus_test.c server_test.c xim_adapter_test.c
|
popup_test.c font_test.c ibus_test.c server_test.c xim_adapter_test.c
|
||||||
TESTOBJ = $(TESTSRC:.c=.o)
|
TESTOBJ = $(TESTSRC:.c=.o)
|
||||||
@@ -29,22 +32,36 @@ PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
|||||||
font.c
|
font.c
|
||||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
||||||
XIMOBJ = unit_xim_keymap.o unit_ximtext.o
|
XIMOBJ = unit_xim_keymap.o unit_ximtext.o
|
||||||
OBJS = $(TESTOBJ) $(PARENTOBJ) $(XIMOBJ)
|
COMMONOBJ = $(TESTOBJ) $(PARENTOBJ) $(XIMOBJ)
|
||||||
|
OBJS = unit_test.o stress_test.o $(COMMONOBJ)
|
||||||
|
|
||||||
all: $(PROG) $(LIVE)
|
all: $(PROG) $(STRESS) $(LIVE)
|
||||||
|
|
||||||
check test: $(PROG) $(LIVE) ../gtk/im-strans.so
|
check test: $(PROG)
|
||||||
./$(PROG) $(TESTARGS)
|
./$(PROG) $(UNITARGS)
|
||||||
|
|
||||||
|
check-live: $(SMOKE) ../strans ../gtk/im-strans.so
|
||||||
./ibus_live_test ../strans ../map ./ibus_client_smoke
|
./ibus_live_test ../strans ../map ./ibus_client_smoke
|
||||||
./gtk_live_test ../gtk/im-strans.so
|
./gtk_live_test ../gtk/im-strans.so
|
||||||
./xim_live_test ../strans ../map
|
./xim_live_test ../strans ../map
|
||||||
./ipc_live_test ../strans ../map
|
./ipc_live_test ../strans ../map
|
||||||
|
|
||||||
|
check-stress: $(STRESS) ibus_live_test ipc_live_test $(FAULT) ../strans
|
||||||
|
./$(STRESS)
|
||||||
|
./ibus_live_test --capacity ../strans ../map
|
||||||
|
./ipc_live_test --capacity ../strans ../map
|
||||||
./daemon_collision_test ../strans ../map
|
./daemon_collision_test ../strans ../map
|
||||||
./daemon_failure_test ../strans
|
./daemon_failure_test ../strans
|
||||||
./daemon_restart_test ../strans ../map
|
./daemon_restart_test ../strans ../map
|
||||||
|
|
||||||
$(PROG): $(OBJS)
|
$(PROG): unit_test.o $(COMMONOBJ)
|
||||||
$(LD) -o $@ $(OBJS) $(LIBS)
|
$(LD) -o $@ unit_test.o $(COMMONOBJ) $(LIBS)
|
||||||
|
|
||||||
|
$(STRESS): stress_test.o $(COMMONOBJ)
|
||||||
|
$(LD) -o $@ stress_test.o $(COMMONOBJ) $(LIBS)
|
||||||
|
|
||||||
|
stress_test.o: unit_test.c
|
||||||
|
$(CC) $(CFLAGS) -DSTRESS -c -o $@ $<
|
||||||
|
|
||||||
ibus_live_test: ibus_live_test.c
|
ibus_live_test: ibus_live_test.c
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g $(DBUS_CFLAGS) -o $@ $< $(DBUS_LIBS)
|
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g $(DBUS_CFLAGS) -o $@ $< $(DBUS_LIBS)
|
||||||
@@ -60,6 +77,9 @@ gtk_live_test: gtk_live_test.c ../ipc.c ../ipc.h
|
|||||||
../gtk/im-strans.so: ../gtk/main.c ../ipc.c ../ipc.h
|
../gtk/im-strans.so: ../gtk/main.c ../ipc.c ../ipc.h
|
||||||
$(MAKE) -C ../gtk
|
$(MAKE) -C ../gtk
|
||||||
|
|
||||||
|
../strans:
|
||||||
|
$(MAKE) -C .. strans
|
||||||
|
|
||||||
xim_live_test: xim_live_test.c ../ipc.c ../ipc.h
|
xim_live_test: xim_live_test.c ../ipc.c ../ipc.h
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(X11_CFLAGS) -o $@ \
|
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(X11_CFLAGS) -o $@ \
|
||||||
xim_live_test.c ../ipc.c $(X11_LIBS)
|
xim_live_test.c ../ipc.c $(X11_LIBS)
|
||||||
@@ -78,7 +98,8 @@ daemon_restart_test: daemon_restart_test.c ../ipc.c ../ipc.h
|
|||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(DBUS_CFLAGS) -o $@ \
|
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(DBUS_CFLAGS) -o $@ \
|
||||||
daemon_restart_test.c ../ipc.c $(DBUS_LIBS)
|
daemon_restart_test.c ../ipc.c $(DBUS_LIBS)
|
||||||
|
|
||||||
$(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h ../cutest/cutest.h
|
unit_test.o stress_test.o $(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h \
|
||||||
|
../cutest/cutest.h
|
||||||
engine_test.o: ../strans.c
|
engine_test.o: ../strans.c
|
||||||
ibus_test.o: CFLAGS += $(IBUS_CFLAGS)
|
ibus_test.o: CFLAGS += $(IBUS_CFLAGS)
|
||||||
ibus_test.o: ../ibus.c
|
ibus_test.o: ../ibus.c
|
||||||
@@ -100,6 +121,6 @@ unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
|||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS) $(PROG) $(LIVE)
|
rm -f $(OBJS) $(PROG) $(STRESS) $(LIVE)
|
||||||
|
|
||||||
.PHONY: all check test clean
|
.PHONY: all check check-live check-stress test clean
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/inotify.h>
|
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
@@ -37,8 +36,6 @@ struct Test
|
|||||||
pid_t second;
|
pid_t second;
|
||||||
int firsterrfd;
|
int firsterrfd;
|
||||||
int seconderrfd;
|
int seconderrfd;
|
||||||
int notifyfd;
|
|
||||||
int buswd;
|
|
||||||
int ipcfirst;
|
int ipcfirst;
|
||||||
int ipcnew;
|
int ipcnew;
|
||||||
DBusConnection *busfirst;
|
DBusConnection *busfirst;
|
||||||
@@ -52,10 +49,6 @@ struct Test
|
|||||||
char socket[384];
|
char socket[384];
|
||||||
char addrfile[512];
|
char addrfile[512];
|
||||||
char address[512];
|
char address[512];
|
||||||
char addrcontents[2048];
|
|
||||||
size_t naddrcontents;
|
|
||||||
struct stat socketst;
|
|
||||||
struct stat addrst;
|
|
||||||
char firsterr[8192];
|
char firsterr[8192];
|
||||||
size_t nfirsterr;
|
size_t nfirsterr;
|
||||||
char seconderr[8192];
|
char seconderr[8192];
|
||||||
@@ -149,8 +142,6 @@ setup(Test *t)
|
|||||||
t->second = -1;
|
t->second = -1;
|
||||||
t->firsterrfd = -1;
|
t->firsterrfd = -1;
|
||||||
t->seconderrfd = -1;
|
t->seconderrfd = -1;
|
||||||
t->notifyfd = -1;
|
|
||||||
t->buswd = -1;
|
|
||||||
t->ipcfirst = -1;
|
t->ipcfirst = -1;
|
||||||
t->ipcnew = -1;
|
t->ipcnew = -1;
|
||||||
snprintf(t->root, sizeof t->root, "/tmp/strans-collision.XXXXXX");
|
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) ||
|
if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) ||
|
||||||
!makedir(t->bus) || !makedir(t->home))
|
!makedir(t->bus) || !makedir(t->home))
|
||||||
return 0;
|
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;
|
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("XDG_CONFIG_HOME", t->config, 1) < 0 ||
|
||||||
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
||||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 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",
|
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
_exit(126);
|
_exit(126);
|
||||||
@@ -231,40 +216,6 @@ startchild(Test *t, char *program, char *mapdir, pid_t *child, int *errfd)
|
|||||||
return 1;
|
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
|
static int
|
||||||
findaddress(Test *t)
|
findaddress(Test *t)
|
||||||
{
|
{
|
||||||
@@ -381,6 +332,19 @@ parseaddress(char *contents, size_t ncontents, char *address, size_t naddress,
|
|||||||
return 1;
|
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
|
static int
|
||||||
childalive(pid_t *pid, char *which)
|
childalive(pid_t *pid, char *which)
|
||||||
{
|
{
|
||||||
@@ -410,9 +374,9 @@ childalive(pid_t *pid, char *which)
|
|||||||
static int
|
static int
|
||||||
waitready(Test *t)
|
waitready(Test *t)
|
||||||
{
|
{
|
||||||
struct pollfd pfd[2];
|
struct pollfd pfd;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
char contents[2048], address[512];
|
char contents[2048];
|
||||||
size_t ncontents;
|
size_t ncontents;
|
||||||
int n, count, socketready, timeout;
|
int n, count, socketready, timeout;
|
||||||
int64_t deadline;
|
int64_t deadline;
|
||||||
@@ -426,7 +390,7 @@ waitready(Test *t)
|
|||||||
return 0;
|
return 0;
|
||||||
if(socketready && count == 1 &&
|
if(socketready && count == 1 &&
|
||||||
readfile(t->addrfile, contents, sizeof contents, &ncontents) &&
|
readfile(t->addrfile, contents, sizeof contents, &ncontents) &&
|
||||||
parseaddress(contents, ncontents, address, sizeof address,
|
parseaddress(contents, ncontents, t->address, sizeof t->address,
|
||||||
t->first))
|
t->first))
|
||||||
return 1;
|
return 1;
|
||||||
if(!childalive(&t->first, "first"))
|
if(!childalive(&t->first, "first"))
|
||||||
@@ -434,43 +398,20 @@ waitready(Test *t)
|
|||||||
timeout = leftms(deadline);
|
timeout = leftms(deadline);
|
||||||
if(timeout == 0)
|
if(timeout == 0)
|
||||||
return fail("timed out waiting for both daemon endpoints");
|
return fail("timed out waiting for both daemon endpoints");
|
||||||
pfd[0].fd = t->notifyfd;
|
pfd.fd = t->firsterrfd;
|
||||||
pfd[0].events = POLLIN;
|
pfd.events = POLLIN|POLLHUP;
|
||||||
pfd[0].revents = 0;
|
pfd.revents = 0;
|
||||||
pfd[1].fd = t->firsterrfd;
|
n = poll(&pfd, 1, timeout > 20 ? 20 : timeout);
|
||||||
pfd[1].events = POLLIN|POLLHUP;
|
|
||||||
pfd[1].revents = 0;
|
|
||||||
n = poll(pfd, 2, timeout);
|
|
||||||
if(n < 0 && errno == EINTR)
|
if(n < 0 && errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
if(n < 0)
|
if(n < 0)
|
||||||
return fail("poll daemon readiness: %s", strerror(errno));
|
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,
|
readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr,
|
||||||
&t->nfirsterr);
|
&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
|
static int
|
||||||
connectsocket(char *path, int64_t deadline)
|
connectsocket(char *path, int64_t deadline)
|
||||||
{
|
{
|
||||||
@@ -750,6 +691,8 @@ killowned(pid_t *pid, int *status)
|
|||||||
if(n != *pid){
|
if(n != *pid){
|
||||||
fail("reap %ld after SIGKILL: %s", (long)*pid,
|
fail("reap %ld after SIGKILL: %s", (long)*pid,
|
||||||
n < 0 ? strerror(errno) : "wrong child");
|
n < 0 ? strerror(errno) : "wrong child");
|
||||||
|
if(n < 0 && errno == ECHILD)
|
||||||
|
*pid = -1;
|
||||||
ok = 0;
|
ok = 0;
|
||||||
}else
|
}else
|
||||||
*pid = -1;
|
*pid = -1;
|
||||||
@@ -757,9 +700,9 @@ killowned(pid_t *pid, int *status)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
waitsecond(Test *t, int *changed)
|
waitsecond(Test *t)
|
||||||
{
|
{
|
||||||
struct pollfd pfd[2];
|
struct pollfd pfd;
|
||||||
int n, status, timeout;
|
int n, status, timeout;
|
||||||
int64_t deadline;
|
int64_t deadline;
|
||||||
|
|
||||||
@@ -772,8 +715,6 @@ waitsecond(Test *t, int *changed)
|
|||||||
t->second = -1;
|
t->second = -1;
|
||||||
readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr,
|
readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr,
|
||||||
&t->nseconderr);
|
&t->nseconderr);
|
||||||
if(!drainnotify(t, 1, changed))
|
|
||||||
return 0;
|
|
||||||
if(!WIFEXITED(status) || WEXITSTATUS(status) == 0)
|
if(!WIFEXITED(status) || WEXITSTATUS(status) == 0)
|
||||||
return fail("second daemon did not exit unsuccessfully: %#x",
|
return fail("second daemon did not exit unsuccessfully: %#x",
|
||||||
status);
|
status);
|
||||||
@@ -797,118 +738,28 @@ waitsecond(Test *t, int *changed)
|
|||||||
return 0;
|
return 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
pfd[0].fd = t->notifyfd;
|
pfd.fd = t->seconderrfd;
|
||||||
pfd[0].events = POLLIN;
|
pfd.events = POLLIN|POLLHUP;
|
||||||
pfd[0].revents = 0;
|
pfd.revents = 0;
|
||||||
pfd[1].fd = t->seconderrfd;
|
n = poll(&pfd, 1, timeout > 20 ? 20 : timeout);
|
||||||
pfd[1].events = POLLIN|POLLHUP;
|
|
||||||
pfd[1].revents = 0;
|
|
||||||
n = poll(pfd, 2, timeout);
|
|
||||||
if(n < 0 && errno == EINTR)
|
if(n < 0 && errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
if(n < 0)
|
if(n < 0)
|
||||||
return fail("poll second daemon: %s", strerror(errno));
|
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,
|
readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr,
|
||||||
&t->nseconderr);
|
&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
|
static int
|
||||||
runcollision(Test *t, char *program, char *mapdir)
|
runcollision(Test *t, char *program, char *mapdir)
|
||||||
{
|
{
|
||||||
char firsthello[32], newhello[32];
|
char firsthello[32], newhello[32];
|
||||||
char firstpath[96], persistentpath[96], newpath[96];
|
char persistentpath[96], newpath[96];
|
||||||
int changed;
|
|
||||||
|
|
||||||
if(!startchild(t, program, mapdir, &t->first, &t->firsterrfd) ||
|
if(!startchild(t, program, mapdir, &t->first, &t->firsterrfd) ||
|
||||||
!waitready(t) || !snapshot(t))
|
!waitready(t))
|
||||||
return 0;
|
return 0;
|
||||||
t->ipcfirst = connectsocket(t->socket, nowms() + Calltimeout);
|
t->ipcfirst = connectsocket(t->socket, nowms() + Calltimeout);
|
||||||
if(t->ipcfirst < 0)
|
if(t->ipcfirst < 0)
|
||||||
@@ -916,22 +767,12 @@ runcollision(Test *t, char *program, char *mapdir)
|
|||||||
if(!ipcprobe(t->ipcfirst, "persistent pre-collision"))
|
if(!ipcprobe(t->ipcfirst, "persistent pre-collision"))
|
||||||
return 0;
|
return 0;
|
||||||
t->busfirst = openbus(t->address);
|
t->busfirst = openbus(t->address);
|
||||||
if(t->busfirst == NULL ||
|
if(t->busfirst == NULL || !hello(t->busfirst, firsthello,
|
||||||
!hello(t->busfirst, firsthello, sizeof firsthello,
|
sizeof firsthello, "persistent pre-collision Hello"))
|
||||||
"persistent pre-collision Hello") ||
|
|
||||||
!createcontext(t->busfirst, firstpath, sizeof firstpath,
|
|
||||||
"persistent pre-collision context"))
|
|
||||||
return 0;
|
return 0;
|
||||||
if(!drainnotify(t, 0, NULL))
|
|
||||||
return 0;
|
|
||||||
changed = 0;
|
|
||||||
if(!startchild(t, program, mapdir, &t->second, &t->seconderrfd) ||
|
if(!startchild(t, program, mapdir, &t->second, &t->seconderrfd) ||
|
||||||
!waitsecond(t, &changed))
|
!waitsecond(t) || !childalive(&t->first, "first") ||
|
||||||
return 0;
|
!addressunchanged(t))
|
||||||
if(changed)
|
|
||||||
return fail("second daemon touched the first IBus address directory");
|
|
||||||
if(!childalive(&t->first, "first") || !checksocket(t) ||
|
|
||||||
!checkaddress(t) || !onlyaddress(t))
|
|
||||||
return 0;
|
return 0;
|
||||||
if(!ipcprobe(t->ipcfirst, "persistent post-collision"))
|
if(!ipcprobe(t->ipcfirst, "persistent post-collision"))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1131,18 +972,6 @@ cleanup(Test *t)
|
|||||||
}
|
}
|
||||||
t->seconderrfd = -1;
|
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){
|
if(t->socket[0] != '\0' && unlink(t->socket) < 0 && errno != ENOENT){
|
||||||
fail("remove IPC socket %s: %s", t->socket, strerror(errno));
|
fail("remove IPC socket %s: %s", t->socket, strerror(errno));
|
||||||
ok = 0;
|
ok = 0;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/inotify.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
@@ -19,19 +18,14 @@
|
|||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Failtimeout = 8000,
|
Failtimeout = 4000,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Test Test;
|
typedef struct Test Test;
|
||||||
|
|
||||||
struct Test
|
struct Test
|
||||||
{
|
{
|
||||||
pid_t child;
|
pid_t child;
|
||||||
int errfd;
|
int errfd;
|
||||||
int notifyfd;
|
|
||||||
int runtimewd;
|
|
||||||
int buswd;
|
|
||||||
int endpointclean;
|
|
||||||
char root[256];
|
char root[256];
|
||||||
char runtime[320];
|
char runtime[320];
|
||||||
char config[320];
|
char config[320];
|
||||||
@@ -40,14 +34,10 @@ struct Test
|
|||||||
char home[320];
|
char home[320];
|
||||||
char socket[384];
|
char socket[384];
|
||||||
char badmap[320];
|
char badmap[320];
|
||||||
char expected[768];
|
|
||||||
char err[4096];
|
char err[4096];
|
||||||
size_t nerr;
|
size_t nerr;
|
||||||
};
|
};
|
||||||
|
|
||||||
static uint32_t endpointmask = IN_CREATE|IN_MOVED_TO|IN_MOVED_FROM|IN_ATTRIB|
|
|
||||||
IN_DELETE|IN_MODIFY|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF;
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
fail(char *fmt, ...)
|
fail(char *fmt, ...)
|
||||||
{
|
{
|
||||||
@@ -82,9 +72,7 @@ leftms(int64_t deadline)
|
|||||||
n = deadline - n;
|
n = deadline - n;
|
||||||
if(n <= 0)
|
if(n <= 0)
|
||||||
return 0;
|
return 0;
|
||||||
if(n > 0x7fffffff)
|
return n > INT_MAX ? INT_MAX : n;
|
||||||
return 0x7fffffff;
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -100,9 +88,7 @@ readerrors(Test *t)
|
|||||||
{
|
{
|
||||||
ssize_t n;
|
ssize_t n;
|
||||||
|
|
||||||
if(t->errfd < 0)
|
while(t->errfd >= 0 && t->nerr + 1 < sizeof t->err){
|
||||||
return;
|
|
||||||
while(t->nerr + 1 < sizeof t->err){
|
|
||||||
n = read(t->errfd, t->err + t->nerr,
|
n = read(t->errfd, t->err + t->nerr,
|
||||||
sizeof t->err - t->nerr - 1);
|
sizeof t->err - t->nerr - 1);
|
||||||
if(n > 0){
|
if(n > 0){
|
||||||
@@ -116,26 +102,12 @@ readerrors(Test *t)
|
|||||||
t->err[t->nerr] = '\0';
|
t->err[t->nerr] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
showerrors(Test *t)
|
|
||||||
{
|
|
||||||
readerrors(t);
|
|
||||||
if(t->nerr != 0)
|
|
||||||
fprintf(stderr, "daemon_failure_test: daemon stderr:\n%s", t->err);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
setup(Test *t)
|
setup(Test *t)
|
||||||
{
|
{
|
||||||
memset(t, 0, sizeof *t);
|
memset(t, 0, sizeof *t);
|
||||||
t->child = -1;
|
t->child = -1;
|
||||||
t->errfd = -1;
|
t->errfd = -1;
|
||||||
t->notifyfd = -1;
|
|
||||||
t->runtimewd = -1;
|
|
||||||
t->buswd = -1;
|
|
||||||
t->endpointclean = 1;
|
|
||||||
if(nowms() < 0)
|
|
||||||
return fail("read monotonic clock: %s", strerror(errno));
|
|
||||||
snprintf(t->root, sizeof t->root, "/tmp/strans-failure.XXXXXX");
|
snprintf(t->root, sizeof t->root, "/tmp/strans-failure.XXXXXX");
|
||||||
if(mkdtemp(t->root) == NULL){
|
if(mkdtemp(t->root) == NULL){
|
||||||
t->root[0] = '\0';
|
t->root[0] = '\0';
|
||||||
@@ -154,24 +126,10 @@ setup(Test *t)
|
|||||||
snprintf(t->socket, sizeof t->socket, "%s/strans.sock", t->runtime)
|
snprintf(t->socket, sizeof t->socket, "%s/strans.sock", t->runtime)
|
||||||
>= (int)sizeof t->socket ||
|
>= (int)sizeof t->socket ||
|
||||||
snprintf(t->badmap, sizeof t->badmap, "%s/missing-map", t->root)
|
snprintf(t->badmap, sizeof t->badmap, "%s/missing-map", t->root)
|
||||||
>= (int)sizeof t->badmap ||
|
>= (int)sizeof t->badmap)
|
||||||
snprintf(t->expected, sizeof t->expected,
|
|
||||||
"strans: can't open: %s/hira.map\n", t->badmap)
|
|
||||||
>= (int)sizeof t->expected)
|
|
||||||
return fail("temporary path is too long");
|
return fail("temporary path is too long");
|
||||||
if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) ||
|
return makedir(t->runtime) && makedir(t->config) && makedir(t->ibus) &&
|
||||||
!makedir(t->bus) || !makedir(t->home))
|
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->runtimewd = inotify_add_watch(t->notifyfd, t->runtime, endpointmask);
|
|
||||||
if(t->runtimewd < 0)
|
|
||||||
return fail("watch runtime directory: %s", strerror(errno));
|
|
||||||
t->buswd = inotify_add_watch(t->notifyfd, t->bus, endpointmask);
|
|
||||||
if(t->buswd < 0)
|
|
||||||
return fail("watch IBus directory: %s", strerror(errno));
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -205,11 +163,9 @@ startchild(Test *t, char *program)
|
|||||||
setenv("XDG_CONFIG_HOME", t->config, 1) < 0 ||
|
setenv("XDG_CONFIG_HOME", t->config, 1) < 0 ||
|
||||||
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
||||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
||||||
unsetenv("IBUS_ADDRESS") < 0){
|
unsetenv("IBUS_ADDRESS") < 0 ||
|
||||||
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
unsetenv("IBUS_ADDRESS_FILE") < 0)
|
||||||
strerror(errno));
|
|
||||||
_exit(126);
|
_exit(126);
|
||||||
}
|
|
||||||
execl(program, program, t->badmap, (char*)0);
|
execl(program, program, t->badmap, (char*)0);
|
||||||
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
||||||
_exit(127);
|
_exit(127);
|
||||||
@@ -220,330 +176,92 @@ startchild(Test *t, char *program)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
drainnotify(Test *t)
|
|
||||||
{
|
|
||||||
char buf[4096];
|
|
||||||
char *where;
|
|
||||||
struct inotify_event *ev;
|
|
||||||
ssize_t n;
|
|
||||||
size_t off;
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
ok = 1;
|
|
||||||
for(;;){
|
|
||||||
n = read(t->notifyfd, buf, sizeof buf);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n < 0 && errno == EAGAIN)
|
|
||||||
return ok;
|
|
||||||
if(n < 0)
|
|
||||||
return fail("read endpoint watches: %s", strerror(errno));
|
|
||||||
if(n == 0)
|
|
||||||
return ok;
|
|
||||||
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){
|
|
||||||
t->endpointclean = 0;
|
|
||||||
fail("endpoint watch queue overflowed");
|
|
||||||
ok = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(ev->mask & (IN_IGNORED|IN_UNMOUNT)){
|
|
||||||
t->endpointclean = 0;
|
|
||||||
fail("endpoint watch became invalid: %#x", ev->mask);
|
|
||||||
ok = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(ev->wd != t->runtimewd && ev->wd != t->buswd)
|
|
||||||
continue;
|
|
||||||
if(!(ev->mask & endpointmask))
|
|
||||||
continue;
|
|
||||||
where = ev->wd == t->runtimewd ? "runtime" : "IBus";
|
|
||||||
t->endpointclean = 0;
|
|
||||||
fail("unexpected %s endpoint event %#x for %s", where,
|
|
||||||
ev->mask, ev->len != 0 ? ev->name : "directory");
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(off != (size_t)n)
|
|
||||||
return fail("truncated inotify event buffer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
killowned(Test *t, int *status)
|
|
||||||
{
|
|
||||||
pid_t pid;
|
|
||||||
int n, ok;
|
|
||||||
|
|
||||||
if(t->child <= 0)
|
|
||||||
return 1;
|
|
||||||
pid = t->child;
|
|
||||||
ok = 1;
|
|
||||||
if(kill(pid, SIGKILL) < 0 && errno != ESRCH){
|
|
||||||
fail("kill -9 %ld: %s", (long)pid, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
do
|
|
||||||
n = waitpid(pid, status, 0);
|
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
if(n != pid){
|
|
||||||
fail("reap %ld after SIGKILL: %s", (long)pid,
|
|
||||||
n < 0 ? strerror(errno) : "wrong child");
|
|
||||||
ok = 0;
|
|
||||||
}else
|
|
||||||
t->child = -1;
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
waitfailed(Test *t)
|
waitfailed(Test *t)
|
||||||
{
|
{
|
||||||
struct pollfd pfd[2];
|
struct pollfd pfd;
|
||||||
pid_t pid;
|
int n, status;
|
||||||
int n, ok, status, timeout;
|
|
||||||
int64_t deadline;
|
int64_t deadline;
|
||||||
|
|
||||||
pid = t->child;
|
|
||||||
ok = 1;
|
|
||||||
deadline = nowms();
|
deadline = nowms();
|
||||||
if(deadline < 0){
|
if(deadline < 0)
|
||||||
fail("read monotonic clock before waiting for daemon: %s",
|
return fail("read monotonic clock: %s", strerror(errno));
|
||||||
strerror(errno));
|
|
||||||
killowned(t, &status);
|
|
||||||
readerrors(t);
|
|
||||||
drainnotify(t);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
deadline += Failtimeout;
|
deadline += Failtimeout;
|
||||||
for(;;){
|
for(;;){
|
||||||
do
|
n = waitpid(t->child, &status, WNOHANG);
|
||||||
n = waitpid(pid, &status, WNOHANG);
|
if(n == t->child){
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
if(n == pid){
|
|
||||||
t->child = -1;
|
t->child = -1;
|
||||||
readerrors(t);
|
readerrors(t);
|
||||||
if(!drainnotify(t))
|
|
||||||
ok = 0;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(n < 0){
|
|
||||||
fail("waitpid failed daemon %ld: %s", (long)pid,
|
|
||||||
strerror(errno));
|
|
||||||
if(errno == ECHILD){
|
|
||||||
t->child = -1;
|
|
||||||
readerrors(t);
|
|
||||||
drainnotify(t);
|
|
||||||
}else{
|
|
||||||
killowned(t, &status);
|
|
||||||
readerrors(t);
|
|
||||||
drainnotify(t);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
timeout = leftms(deadline);
|
|
||||||
if(timeout < 0){
|
|
||||||
fail("read monotonic clock while waiting for daemon: %s",
|
|
||||||
strerror(errno));
|
|
||||||
killowned(t, &status);
|
|
||||||
readerrors(t);
|
|
||||||
drainnotify(t);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if(timeout == 0){
|
|
||||||
fail("daemon %ld did not exit after map initialization failure",
|
|
||||||
(long)pid);
|
|
||||||
killowned(t, &status);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
pfd[0].fd = t->notifyfd;
|
|
||||||
pfd[0].events = POLLIN;
|
|
||||||
pfd[0].revents = 0;
|
|
||||||
pfd[1].fd = t->errfd;
|
|
||||||
pfd[1].events = POLLIN|POLLHUP;
|
|
||||||
pfd[1].revents = 0;
|
|
||||||
n = poll(pfd, 2, timeout);
|
|
||||||
if(n < 0 && errno == EINTR)
|
if(n < 0 && errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
if(n < 0){
|
if(n < 0){
|
||||||
fail("poll failed daemon: %s", strerror(errno));
|
if(errno == ECHILD)
|
||||||
killowned(t, &status);
|
t->child = -1;
|
||||||
readerrors(t);
|
return fail("waitpid: %s", strerror(errno));
|
||||||
drainnotify(t);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if(pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)){
|
n = leftms(deadline);
|
||||||
fail("endpoint watch became unusable: %#x", pfd[0].revents);
|
if(n < 0)
|
||||||
killowned(t, &status);
|
return fail("read monotonic clock: %s", strerror(errno));
|
||||||
readerrors(t);
|
if(n == 0)
|
||||||
drainnotify(t);
|
return fail("daemon did not exit after map initialization failure");
|
||||||
return 0;
|
pfd.fd = t->errfd;
|
||||||
}
|
pfd.events = POLLIN|POLLHUP;
|
||||||
if(pfd[1].revents & (POLLERR|POLLNVAL)){
|
pfd.revents = 0;
|
||||||
fail("daemon stderr pipe became unusable: %#x",
|
n = poll(&pfd, 1, n);
|
||||||
pfd[1].revents);
|
if(n < 0 && errno == EINTR)
|
||||||
killowned(t, &status);
|
continue;
|
||||||
readerrors(t);
|
if(n < 0)
|
||||||
drainnotify(t);
|
return fail("poll daemon: %s", strerror(errno));
|
||||||
return 0;
|
if(n > 0)
|
||||||
}
|
|
||||||
if((pfd[0].revents & POLLIN) && !drainnotify(t))
|
|
||||||
ok = 0;
|
|
||||||
if(pfd[1].revents & (POLLIN|POLLHUP))
|
|
||||||
readerrors(t);
|
readerrors(t);
|
||||||
}
|
}
|
||||||
if(!WIFEXITED(status) || WEXITSTATUS(status) != 1)
|
if(!WIFEXITED(status) || WEXITSTATUS(status) == 0)
|
||||||
return fail("daemon map failure had wait status %#x", status);
|
return fail("daemon map failure had wait status %#x", status);
|
||||||
if(strcmp(t->err, t->expected) != 0)
|
if(strstr(t->err, "can't open") == NULL || strstr(t->err, t->badmap) == NULL)
|
||||||
return fail("daemon did not report the expected map initialization failure");
|
return fail("daemon did not report the missing map directory");
|
||||||
return ok && t->endpointclean;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
dirempty(char *path, char *which)
|
emptydir(char *path)
|
||||||
{
|
{
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
struct dirent *de;
|
struct dirent *de;
|
||||||
int count, ok;
|
int empty;
|
||||||
|
|
||||||
dir = opendir(path);
|
dir = opendir(path);
|
||||||
if(dir == NULL)
|
if(dir == NULL)
|
||||||
return fail("open %s directory %s: %s", which, path, strerror(errno));
|
return fail("open %s: %s", path, strerror(errno));
|
||||||
count = 0;
|
empty = 1;
|
||||||
ok = 1;
|
|
||||||
errno = 0;
|
errno = 0;
|
||||||
while((de = readdir(dir)) != NULL){
|
while((de = readdir(dir)) != NULL)
|
||||||
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
if(strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0){
|
||||||
continue;
|
fail("unexpected endpoint %s/%s", path, de->d_name);
|
||||||
count++;
|
empty = 0;
|
||||||
fail("unexpected %s directory entry %s", which, de->d_name);
|
}
|
||||||
ok = 0;
|
|
||||||
errno = 0;
|
|
||||||
}
|
|
||||||
if(errno != 0){
|
if(errno != 0){
|
||||||
fail("read %s directory %s: %s", which, path, strerror(errno));
|
fail("read %s: %s", path, strerror(errno));
|
||||||
ok = 0;
|
empty = 0;
|
||||||
}
|
}
|
||||||
if(closedir(dir) < 0){
|
if(closedir(dir) < 0){
|
||||||
fail("close %s directory %s: %s", which, path, strerror(errno));
|
fail("close %s: %s", path, strerror(errno));
|
||||||
ok = 0;
|
empty = 0;
|
||||||
}
|
}
|
||||||
if(count != 0)
|
return empty;
|
||||||
ok = 0;
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
checkendpoints(Test *t)
|
checkendpoints(Test *t)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
int ok;
|
|
||||||
|
|
||||||
ok = 1;
|
if(lstat(t->socket, &st) == 0)
|
||||||
errno = 0;
|
return fail("IPC endpoint exists after failed startup: %s", t->socket);
|
||||||
if(lstat(t->socket, &st) == 0){
|
if(errno != ENOENT)
|
||||||
fail("IPC endpoint exists after failed startup: %s", t->socket);
|
return fail("lstat IPC endpoint: %s", strerror(errno));
|
||||||
ok = 0;
|
return emptydir(t->runtime) && emptydir(t->bus);
|
||||||
}else if(errno != ENOENT){
|
|
||||||
fail("lstat failed IPC endpoint: %s", strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(!dirempty(t->runtime, "runtime"))
|
|
||||||
ok = 0;
|
|
||||||
if(!dirempty(t->bus, "IBus"))
|
|
||||||
ok = 0;
|
|
||||||
return ok && t->endpointclean;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int clearfd(int);
|
|
||||||
|
|
||||||
static int
|
|
||||||
removeentry(int dirfd, char *name)
|
|
||||||
{
|
|
||||||
struct stat st;
|
|
||||||
int fd, ok;
|
|
||||||
|
|
||||||
if(fstatat(dirfd, name, &st, AT_SYMLINK_NOFOLLOW) < 0)
|
|
||||||
return fail("stat cleanup entry %s: %s", name, strerror(errno));
|
|
||||||
if(!S_ISDIR(st.st_mode)){
|
|
||||||
if(unlinkat(dirfd, name, 0) == 0 || errno == ENOENT)
|
|
||||||
return 1;
|
|
||||||
return fail("remove cleanup entry %s: %s", name, strerror(errno));
|
|
||||||
}
|
|
||||||
fd = openat(dirfd, name, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
|
||||||
if(fd < 0)
|
|
||||||
return fail("open cleanup directory %s: %s", name, strerror(errno));
|
|
||||||
ok = clearfd(fd);
|
|
||||||
if(close(fd) < 0){
|
|
||||||
fail("close cleanup directory %s: %s", name, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(unlinkat(dirfd, name, AT_REMOVEDIR) < 0 && errno != ENOENT){
|
|
||||||
fail("remove cleanup directory %s: %s", name, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
clearfd(int fd)
|
|
||||||
{
|
|
||||||
DIR *dir;
|
|
||||||
struct dirent *de;
|
|
||||||
int copy, ok;
|
|
||||||
|
|
||||||
copy = dup(fd);
|
|
||||||
if(copy < 0)
|
|
||||||
return fail("duplicate cleanup directory: %s", strerror(errno));
|
|
||||||
dir = fdopendir(copy);
|
|
||||||
if(dir == NULL){
|
|
||||||
close(copy);
|
|
||||||
return fail("open cleanup directory stream: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
ok = 1;
|
|
||||||
errno = 0;
|
|
||||||
while((de = readdir(dir)) != NULL){
|
|
||||||
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
|
||||||
continue;
|
|
||||||
if(!removeentry(fd, de->d_name))
|
|
||||||
ok = 0;
|
|
||||||
errno = 0;
|
|
||||||
}
|
|
||||||
if(errno != 0){
|
|
||||||
fail("read cleanup directory: %s", strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(closedir(dir) < 0){
|
|
||||||
fail("close cleanup directory stream: %s", strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
removeroot(Test *t)
|
|
||||||
{
|
|
||||||
int fd, ok;
|
|
||||||
|
|
||||||
if(t->root[0] == '\0')
|
|
||||||
return 1;
|
|
||||||
fd = open(t->root, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
|
||||||
if(fd < 0)
|
|
||||||
return errno == ENOENT || fail("open cleanup root %s: %s", t->root,
|
|
||||||
strerror(errno));
|
|
||||||
ok = clearfd(fd);
|
|
||||||
if(close(fd) < 0){
|
|
||||||
fail("close cleanup root %s: %s", t->root, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(rmdir(t->root) < 0 && errno != ENOENT){
|
|
||||||
fail("rmdir %s: %s", t->root, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -553,53 +271,31 @@ cleanup(Test *t)
|
|||||||
|
|
||||||
ok = 1;
|
ok = 1;
|
||||||
if(t->child > 0){
|
if(t->child > 0){
|
||||||
|
kill(t->child, SIGKILL);
|
||||||
do
|
do
|
||||||
n = waitpid(t->child, &status, WNOHANG);
|
n = waitpid(t->child, &status, 0);
|
||||||
while(n < 0 && errno == EINTR);
|
while(n < 0 && errno == EINTR);
|
||||||
if(n == t->child)
|
if(n != t->child)
|
||||||
t->child = -1;
|
ok = fail("reap child: %s", strerror(errno));
|
||||||
else if(n == 0){
|
t->child = -1;
|
||||||
fail("failed-start daemon still running during cleanup");
|
|
||||||
ok = 0;
|
|
||||||
if(!killowned(t, &status))
|
|
||||||
ok = 0;
|
|
||||||
}else{
|
|
||||||
fail("check failed-start daemon during cleanup: %s",
|
|
||||||
strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
if(errno == ECHILD)
|
|
||||||
t->child = -1;
|
|
||||||
else if(!killowned(t, &status))
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
readerrors(t);
|
readerrors(t);
|
||||||
if(t->errfd >= 0){
|
if(t->errfd >= 0 && close(t->errfd) < 0)
|
||||||
if(close(t->errfd) < 0){
|
ok = fail("close stderr: %s", strerror(errno));
|
||||||
fail("close daemon stderr: %s", strerror(errno));
|
if(t->socket[0] != '\0' && unlink(t->socket) < 0 && errno != ENOENT)
|
||||||
ok = 0;
|
ok = fail("remove socket: %s", strerror(errno));
|
||||||
}
|
if(t->bus[0] != '\0' && rmdir(t->bus) < 0 && errno != ENOENT)
|
||||||
t->errfd = -1;
|
ok = fail("remove bus directory: %s", strerror(errno));
|
||||||
}
|
if(t->ibus[0] != '\0' && rmdir(t->ibus) < 0 && errno != ENOENT)
|
||||||
if(t->notifyfd >= 0){
|
ok = fail("remove IBus directory: %s", strerror(errno));
|
||||||
if(t->runtimewd >= 0 &&
|
if(t->config[0] != '\0' && rmdir(t->config) < 0 && errno != ENOENT)
|
||||||
inotify_rm_watch(t->notifyfd, t->runtimewd) < 0 && errno != EINVAL){
|
ok = fail("remove config directory: %s", strerror(errno));
|
||||||
fail("remove runtime watch: %s", strerror(errno));
|
if(t->runtime[0] != '\0' && rmdir(t->runtime) < 0 && errno != ENOENT)
|
||||||
ok = 0;
|
ok = fail("remove runtime directory: %s", strerror(errno));
|
||||||
}
|
if(t->home[0] != '\0' && rmdir(t->home) < 0 && errno != ENOENT)
|
||||||
if(t->buswd >= 0 &&
|
ok = fail("remove home directory: %s", strerror(errno));
|
||||||
inotify_rm_watch(t->notifyfd, t->buswd) < 0 && errno != EINVAL){
|
if(t->root[0] != '\0' && rmdir(t->root) < 0 && errno != ENOENT)
|
||||||
fail("remove IBus watch: %s", strerror(errno));
|
ok = fail("remove root directory: %s", strerror(errno));
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(close(t->notifyfd) < 0){
|
|
||||||
fail("close endpoint watches: %s", strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
t->notifyfd = -1;
|
|
||||||
}
|
|
||||||
if(!removeroot(t))
|
|
||||||
ok = 0;
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,17 +312,15 @@ main(int argc, char **argv)
|
|||||||
ok = setup(&test);
|
ok = setup(&test);
|
||||||
if(ok)
|
if(ok)
|
||||||
ok = startchild(&test, argv[1]);
|
ok = startchild(&test, argv[1]);
|
||||||
if(ok){
|
if(ok)
|
||||||
ok = waitfailed(&test);
|
ok = waitfailed(&test) && checkendpoints(&test);
|
||||||
if(test.child <= 0 && !checkendpoints(&test))
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(!cleanup(&test))
|
if(!cleanup(&test))
|
||||||
ok = 0;
|
ok = 0;
|
||||||
if(!ok){
|
if(!ok){
|
||||||
showerrors(&test);
|
if(test.nerr != 0)
|
||||||
|
fprintf(stderr, "daemon_failure_test: daemon stderr:\n%s", test.err);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
printf("failed daemon startup published no endpoints: ok\n");
|
printf("failed daemon startup leaves no endpoints: ok\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/inotify.h>
|
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
@@ -28,10 +27,6 @@ enum
|
|||||||
Starttimeout = 8000,
|
Starttimeout = 8000,
|
||||||
Stoptimeout = 3000,
|
Stoptimeout = 3000,
|
||||||
Ctrlmask = 1<<2,
|
Ctrlmask = 1<<2,
|
||||||
|
|
||||||
Phaseignore,
|
|
||||||
Phasequiet,
|
|
||||||
Phaserecover,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Test Test;
|
typedef struct Test Test;
|
||||||
@@ -45,19 +40,9 @@ struct Test
|
|||||||
int firsterrfd;
|
int firsterrfd;
|
||||||
int seconderrfd;
|
int seconderrfd;
|
||||||
int helpererrfd;
|
int helpererrfd;
|
||||||
int notifyfd;
|
|
||||||
int runtimewd;
|
|
||||||
int buswd;
|
|
||||||
int ipcfirst;
|
int ipcfirst;
|
||||||
int ipcnew;
|
int ipcnew;
|
||||||
int busfirstfd;
|
int busfirstfd;
|
||||||
int phase;
|
|
||||||
int socketdeleted;
|
|
||||||
int socketcreated;
|
|
||||||
int bustempcreated;
|
|
||||||
int bustempmoved;
|
|
||||||
int buspublished;
|
|
||||||
uint32_t buscookie;
|
|
||||||
DBusConnection *busfirst;
|
DBusConnection *busfirst;
|
||||||
DBusConnection *busnew;
|
DBusConnection *busnew;
|
||||||
char root[256];
|
char root[256];
|
||||||
@@ -68,18 +53,8 @@ struct Test
|
|||||||
char home[320];
|
char home[320];
|
||||||
char socket[384];
|
char socket[384];
|
||||||
char addrfile[512];
|
char addrfile[512];
|
||||||
char addrbase[256];
|
|
||||||
char firstaddress[512];
|
char firstaddress[512];
|
||||||
char secondaddress[512];
|
char secondaddress[512];
|
||||||
char firstcontents[2048];
|
|
||||||
size_t nfirstcontents;
|
|
||||||
char secondcontents[2048];
|
|
||||||
size_t nsecondcontents;
|
|
||||||
char bustemp[256];
|
|
||||||
struct stat firstsocketst;
|
|
||||||
struct stat firstaddrst;
|
|
||||||
struct stat secondsocketst;
|
|
||||||
struct stat secondaddrst;
|
|
||||||
char firsterr[8192];
|
char firsterr[8192];
|
||||||
size_t nfirsterr;
|
size_t nfirsterr;
|
||||||
char seconderr[8192];
|
char seconderr[8192];
|
||||||
@@ -88,9 +63,6 @@ struct Test
|
|||||||
size_t nhelpererr;
|
size_t nhelpererr;
|
||||||
};
|
};
|
||||||
|
|
||||||
static uint32_t watchmask = IN_CREATE|IN_DELETE|IN_ATTRIB|IN_MOVED_FROM|
|
|
||||||
IN_MOVED_TO|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF;
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
fail(char *fmt, ...)
|
fail(char *fmt, ...)
|
||||||
{
|
{
|
||||||
@@ -191,13 +163,9 @@ setup(Test *t)
|
|||||||
t->firsterrfd = -1;
|
t->firsterrfd = -1;
|
||||||
t->seconderrfd = -1;
|
t->seconderrfd = -1;
|
||||||
t->helpererrfd = -1;
|
t->helpererrfd = -1;
|
||||||
t->notifyfd = -1;
|
|
||||||
t->runtimewd = -1;
|
|
||||||
t->buswd = -1;
|
|
||||||
t->ipcfirst = -1;
|
t->ipcfirst = -1;
|
||||||
t->ipcnew = -1;
|
t->ipcnew = -1;
|
||||||
t->busfirstfd = -1;
|
t->busfirstfd = -1;
|
||||||
t->phase = Phaseignore;
|
|
||||||
memset(&sa, 0, sizeof sa);
|
memset(&sa, 0, sizeof sa);
|
||||||
sa.sa_handler = SIG_DFL;
|
sa.sa_handler = SIG_DFL;
|
||||||
if(sigemptyset(&sa.sa_mask) < 0 || sigaction(SIGCHLD, &sa, NULL) < 0)
|
if(sigemptyset(&sa.sa_mask) < 0 || sigaction(SIGCHLD, &sa, NULL) < 0)
|
||||||
@@ -225,15 +193,6 @@ setup(Test *t)
|
|||||||
if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) ||
|
if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) ||
|
||||||
!makedir(t->bus) || !makedir(t->home))
|
!makedir(t->bus) || !makedir(t->home))
|
||||||
return 0;
|
return 0;
|
||||||
t->notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
|
|
||||||
if(t->notifyfd < 0)
|
|
||||||
return fail("inotify_init1: %s", strerror(errno));
|
|
||||||
t->runtimewd = inotify_add_watch(t->notifyfd, t->runtime, watchmask);
|
|
||||||
if(t->runtimewd < 0)
|
|
||||||
return fail("watch runtime directory: %s", strerror(errno));
|
|
||||||
t->buswd = inotify_add_watch(t->notifyfd, t->bus, watchmask);
|
|
||||||
if(t->buswd < 0)
|
|
||||||
return fail("watch IBus directory: %s", strerror(errno));
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +243,8 @@ startchild(Test *t, char *program, char *mapdir, pid_t *child, int *errfd)
|
|||||||
setenv("XDG_CONFIG_HOME", t->config, 1) < 0 ||
|
setenv("XDG_CONFIG_HOME", t->config, 1) < 0 ||
|
||||||
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
||||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 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",
|
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
_exit(126);
|
_exit(126);
|
||||||
@@ -299,150 +259,6 @@ startchild(Test *t, char *program, char *mapdir, pid_t *child, int *errfd)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
runtimeevent(Test *t, struct inotify_event *ev)
|
|
||||||
{
|
|
||||||
uint32_t mask;
|
|
||||||
|
|
||||||
if(ev->len == 0 || strcmp(ev->name, "strans.sock") != 0)
|
|
||||||
return fail("unexpected runtime entry event %#x for %s", ev->mask,
|
|
||||||
ev->len != 0 ? ev->name : "directory");
|
|
||||||
mask = ev->mask & watchmask;
|
|
||||||
if(mask & IN_DELETE){
|
|
||||||
if(t->socketdeleted || t->socketcreated)
|
|
||||||
return fail("IPC socket deletion occurred out of order");
|
|
||||||
t->socketdeleted = 1;
|
|
||||||
mask &= ~IN_DELETE;
|
|
||||||
}
|
|
||||||
if(mask & IN_CREATE){
|
|
||||||
if(!t->socketdeleted || t->socketcreated)
|
|
||||||
return fail("IPC socket creation occurred before stale deletion");
|
|
||||||
t->socketcreated = 1;
|
|
||||||
mask &= ~IN_CREATE;
|
|
||||||
}
|
|
||||||
if(mask & IN_ATTRIB){
|
|
||||||
if(!t->socketcreated)
|
|
||||||
return fail("IPC socket protection occurred before recreation");
|
|
||||||
mask &= ~IN_ATTRIB;
|
|
||||||
}
|
|
||||||
if(mask != 0)
|
|
||||||
return fail("unexpected IPC recovery event %#x", ev->mask);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
tempname(Test *t, char *name)
|
|
||||||
{
|
|
||||||
size_t n, nn;
|
|
||||||
|
|
||||||
n = strlen(t->addrbase);
|
|
||||||
nn = strlen(name);
|
|
||||||
if(nn <= n + 5)
|
|
||||||
return 0;
|
|
||||||
return strncmp(name, t->addrbase, n) == 0 &&
|
|
||||||
strncmp(name + n, ".tmp.", 5) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
busevent(Test *t, struct inotify_event *ev)
|
|
||||||
{
|
|
||||||
uint32_t mask;
|
|
||||||
|
|
||||||
if(ev->len == 0)
|
|
||||||
return fail("unexpected unnamed IBus directory event %#x", ev->mask);
|
|
||||||
mask = ev->mask & watchmask;
|
|
||||||
if(tempname(t, ev->name)){
|
|
||||||
if(mask & IN_CREATE){
|
|
||||||
if(t->bustempcreated || snprintf(t->bustemp, sizeof t->bustemp,
|
|
||||||
"%s", ev->name) >= (int)sizeof t->bustemp)
|
|
||||||
return fail("invalid replacement IBus temporary file");
|
|
||||||
t->bustempcreated = 1;
|
|
||||||
mask &= ~IN_CREATE;
|
|
||||||
}
|
|
||||||
if(!t->bustempcreated || strcmp(ev->name, t->bustemp) != 0)
|
|
||||||
return fail("unowned IBus temporary file event for %s", ev->name);
|
|
||||||
if(mask & IN_ATTRIB)
|
|
||||||
mask &= ~IN_ATTRIB;
|
|
||||||
if(mask & IN_CLOSE_WRITE)
|
|
||||||
mask &= ~IN_CLOSE_WRITE;
|
|
||||||
if(mask & IN_MOVED_FROM){
|
|
||||||
if(t->bustempmoved || ev->cookie == 0)
|
|
||||||
return fail("invalid IBus temporary-file rename");
|
|
||||||
t->bustempmoved = 1;
|
|
||||||
t->buscookie = ev->cookie;
|
|
||||||
mask &= ~IN_MOVED_FROM;
|
|
||||||
}
|
|
||||||
if(mask != 0)
|
|
||||||
return fail("unexpected IBus temporary-file event %#x", ev->mask);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(strcmp(ev->name, t->addrbase) != 0)
|
|
||||||
return fail("unexpected IBus recovery entry %s", ev->name);
|
|
||||||
if(mask == IN_MOVED_TO && t->bustempmoved && ev->cookie == t->buscookie){
|
|
||||||
t->buspublished = 1;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return fail("IBus address replacement was not one atomic rename: %#x",
|
|
||||||
ev->mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
drainnotify(Test *t)
|
|
||||||
{
|
|
||||||
char buf[4096];
|
|
||||||
struct inotify_event *ev;
|
|
||||||
ssize_t n;
|
|
||||||
size_t off;
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
ok = 1;
|
|
||||||
for(;;){
|
|
||||||
n = read(t->notifyfd, buf, sizeof buf);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n < 0 && errno == EAGAIN)
|
|
||||||
return ok;
|
|
||||||
if(n < 0)
|
|
||||||
return fail("read endpoint watches: %s", strerror(errno));
|
|
||||||
if(n == 0)
|
|
||||||
return ok;
|
|
||||||
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){
|
|
||||||
fail("endpoint watch queue overflowed");
|
|
||||||
ok = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(ev->mask & (IN_IGNORED|IN_UNMOUNT)){
|
|
||||||
fail("endpoint watch became invalid: %#x", ev->mask);
|
|
||||||
ok = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(ev->wd != t->runtimewd && ev->wd != t->buswd)
|
|
||||||
continue;
|
|
||||||
if(t->phase == Phaseignore)
|
|
||||||
continue;
|
|
||||||
if(t->phase == Phasequiet){
|
|
||||||
fail("hard crash changed %s endpoint %s with event %#x",
|
|
||||||
ev->wd == t->runtimewd ? "IPC" : "IBus",
|
|
||||||
ev->len != 0 ? ev->name : "directory", ev->mask);
|
|
||||||
ok = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(ev->wd == t->runtimewd){
|
|
||||||
if(!runtimeevent(t, ev))
|
|
||||||
ok = 0;
|
|
||||||
}else if(!busevent(t, ev))
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(off != (size_t)n)
|
|
||||||
return fail("truncated inotify event buffer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
findaddress(Test *t)
|
findaddress(Test *t)
|
||||||
{
|
{
|
||||||
@@ -600,9 +416,9 @@ childalive(pid_t *pid, char *which)
|
|||||||
static int
|
static int
|
||||||
waitready(Test *t, pid_t *child, int errfd, char *which)
|
waitready(Test *t, pid_t *child, int errfd, char *which)
|
||||||
{
|
{
|
||||||
struct pollfd pfd[2];
|
struct pollfd pfd;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
char contents[2048], address[512];
|
char contents[2048], *address;
|
||||||
size_t ncontents;
|
size_t ncontents;
|
||||||
pid_t declared;
|
pid_t declared;
|
||||||
int count, n, socketready, timeout;
|
int count, n, socketready, timeout;
|
||||||
@@ -613,6 +429,7 @@ waitready(Test *t, pid_t *child, int errfd, char *which)
|
|||||||
return fail("read monotonic clock before %s startup: %s", which,
|
return fail("read monotonic clock before %s startup: %s", which,
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
deadline += Starttimeout;
|
deadline += Starttimeout;
|
||||||
|
address = child == &t->first ? t->firstaddress : t->secondaddress;
|
||||||
for(;;){
|
for(;;){
|
||||||
socketready = lstat(t->socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
socketready = lstat(t->socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
||||||
(st.st_mode & 0777) == 0600;
|
(st.st_mode & 0777) == 0600;
|
||||||
@@ -621,7 +438,7 @@ waitready(Test *t, pid_t *child, int errfd, char *which)
|
|||||||
return 0;
|
return 0;
|
||||||
if(socketready && count == 1 &&
|
if(socketready && count == 1 &&
|
||||||
readfile(t->addrfile, contents, sizeof contents, &ncontents) &&
|
readfile(t->addrfile, contents, sizeof contents, &ncontents) &&
|
||||||
parseaddress(contents, ncontents, address, sizeof address,
|
parseaddress(contents, ncontents, address, 512,
|
||||||
&declared) && declared == *child && privateaddress(address, declared))
|
&declared) && declared == *child && privateaddress(address, declared))
|
||||||
return 1;
|
return 1;
|
||||||
if(!childalive(child, which))
|
if(!childalive(child, which))
|
||||||
@@ -632,27 +449,19 @@ waitready(Test *t, pid_t *child, int errfd, char *which)
|
|||||||
strerror(errno));
|
strerror(errno));
|
||||||
if(timeout == 0)
|
if(timeout == 0)
|
||||||
return fail("timed out waiting for both %s daemon endpoints", which);
|
return fail("timed out waiting for both %s daemon endpoints", which);
|
||||||
pfd[0].fd = t->notifyfd;
|
pfd.fd = errfd;
|
||||||
pfd[0].events = POLLIN;
|
pfd.events = POLLIN|POLLHUP;
|
||||||
pfd[0].revents = 0;
|
pfd.revents = 0;
|
||||||
pfd[1].fd = errfd;
|
n = poll(&pfd, 1, timeout > 20 ? 20 : timeout);
|
||||||
pfd[1].events = POLLIN|POLLHUP;
|
|
||||||
pfd[1].revents = 0;
|
|
||||||
n = poll(pfd, 2, timeout);
|
|
||||||
if(n < 0 && errno == EINTR)
|
if(n < 0 && errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
if(n < 0)
|
if(n < 0)
|
||||||
return fail("poll %s daemon readiness: %s", which,
|
return fail("poll %s daemon readiness: %s", which,
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
if(pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL))
|
if(pfd.revents & (POLLERR|POLLNVAL))
|
||||||
return fail("endpoint watch became unusable: %#x", pfd[0].revents);
|
|
||||||
if(pfd[1].revents & (POLLERR|POLLNVAL))
|
|
||||||
return fail("%s stderr pipe became unusable: %#x", which,
|
return fail("%s stderr pipe became unusable: %#x", which,
|
||||||
pfd[1].revents);
|
pfd.revents);
|
||||||
if(pfd[0].revents & POLLIN)
|
if(pfd.revents & (POLLIN|POLLHUP)){
|
||||||
if(!drainnotify(t))
|
|
||||||
return 0;
|
|
||||||
if(pfd[1].revents & (POLLIN|POLLHUP)){
|
|
||||||
if(child == &t->first)
|
if(child == &t->first)
|
||||||
readpipe(errfd, t->firsterr, sizeof t->firsterr,
|
readpipe(errfd, t->firsterr, sizeof t->firsterr,
|
||||||
&t->nfirsterr);
|
&t->nfirsterr);
|
||||||
@@ -663,39 +472,6 @@ waitready(Test *t, pid_t *child, int errfd, char *which)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
snapshotfirst(Test *t)
|
|
||||||
{
|
|
||||||
char *base;
|
|
||||||
pid_t declared;
|
|
||||||
|
|
||||||
if(lstat(t->socket, &t->firstsocketst) < 0)
|
|
||||||
return fail("stat first IPC socket: %s", strerror(errno));
|
|
||||||
if(!S_ISSOCK(t->firstsocketst.st_mode) ||
|
|
||||||
(t->firstsocketst.st_mode & 0777) != 0600)
|
|
||||||
return fail("first IPC endpoint is not a protected socket");
|
|
||||||
if(lstat(t->addrfile, &t->firstaddrst) < 0)
|
|
||||||
return fail("stat first IBus address file: %s", strerror(errno));
|
|
||||||
if(!S_ISREG(t->firstaddrst.st_mode) ||
|
|
||||||
(t->firstaddrst.st_mode & 0777) != 0600)
|
|
||||||
return fail("first IBus address is not a protected regular file");
|
|
||||||
if(!readfile(t->addrfile, t->firstcontents, sizeof t->firstcontents,
|
|
||||||
&t->nfirstcontents) ||
|
|
||||||
!parseaddress(t->firstcontents, t->nfirstcontents, t->firstaddress,
|
|
||||||
sizeof t->firstaddress, &declared))
|
|
||||||
return 0;
|
|
||||||
if(declared != t->firstpid)
|
|
||||||
return fail("first IBus address PID %ld, expected %ld", (long)declared,
|
|
||||||
(long)t->firstpid);
|
|
||||||
if(!privateaddress(t->firstaddress, declared))
|
|
||||||
return 0;
|
|
||||||
base = strrchr(t->addrfile, '/');
|
|
||||||
if(base == NULL || snprintf(t->addrbase, sizeof t->addrbase, "%s", base+1)
|
|
||||||
>= (int)sizeof t->addrbase)
|
|
||||||
return fail("first IBus address filename is invalid");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
connectsocket(char *path, int64_t deadline)
|
connectsocket(char *path, int64_t deadline)
|
||||||
{
|
{
|
||||||
@@ -1200,47 +976,6 @@ waitbusclosed(Test *t)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
onlyaddress(Test *t, char *where)
|
|
||||||
{
|
|
||||||
DIR *dir;
|
|
||||||
struct dirent *de;
|
|
||||||
int count, ok;
|
|
||||||
|
|
||||||
dir = opendir(t->bus);
|
|
||||||
if(dir == NULL)
|
|
||||||
return fail("open IBus directory %s %s: %s", t->bus, where,
|
|
||||||
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, t->addrbase) != 0){
|
|
||||||
fail("unexpected IBus entry %s %s", de->d_name, where);
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
errno = 0;
|
|
||||||
}
|
|
||||||
if(errno != 0){
|
|
||||||
fail("read IBus directory %s %s: %s", t->bus, where,
|
|
||||||
strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(closedir(dir) < 0){
|
|
||||||
fail("close IBus directory %s %s: %s", t->bus, where,
|
|
||||||
strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(count != 1){
|
|
||||||
fail("IBus directory has %d entries %s", count, where);
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
checkstale(Test *t)
|
checkstale(Test *t)
|
||||||
{
|
{
|
||||||
@@ -1251,24 +986,16 @@ checkstale(Test *t)
|
|||||||
|
|
||||||
if(lstat(t->socket, &st) < 0)
|
if(lstat(t->socket, &st) < 0)
|
||||||
return fail("stale IPC socket disappeared: %s", strerror(errno));
|
return fail("stale IPC socket disappeared: %s", strerror(errno));
|
||||||
if(!S_ISSOCK(st.st_mode) || st.st_mode != t->firstsocketst.st_mode ||
|
if(!S_ISSOCK(st.st_mode))
|
||||||
st.st_dev != t->firstsocketst.st_dev ||
|
return fail("stale IPC endpoint is not a socket");
|
||||||
st.st_ino != t->firstsocketst.st_ino)
|
|
||||||
return fail("stale IPC socket identity or protection changed");
|
|
||||||
if(lstat(t->addrfile, &st) < 0)
|
if(lstat(t->addrfile, &st) < 0)
|
||||||
return fail("stale IBus address file disappeared: %s", strerror(errno));
|
return fail("stale IBus address file disappeared: %s", strerror(errno));
|
||||||
if(st.st_mode != t->firstaddrst.st_mode ||
|
|
||||||
st.st_dev != t->firstaddrst.st_dev || st.st_ino != t->firstaddrst.st_ino)
|
|
||||||
return fail("stale IBus address identity or protection changed");
|
|
||||||
if(!readfile(t->addrfile, contents, sizeof contents, &ncontents) ||
|
if(!readfile(t->addrfile, contents, sizeof contents, &ncontents) ||
|
||||||
!parseaddress(contents, ncontents, address, sizeof address, &declared))
|
!parseaddress(contents, ncontents, address, sizeof address, &declared))
|
||||||
return 0;
|
return 0;
|
||||||
if(ncontents != t->nfirstcontents ||
|
|
||||||
memcmp(contents, t->firstcontents, ncontents) != 0)
|
|
||||||
return fail("stale IBus address contents changed after hard crash");
|
|
||||||
if(declared != t->firstpid || strcmp(address, t->firstaddress) != 0)
|
if(declared != t->firstpid || strcmp(address, t->firstaddress) != 0)
|
||||||
return fail("stale IBus address no longer names the dead first daemon");
|
return fail("stale IBus address no longer names the dead first daemon");
|
||||||
return onlyaddress(t, "after hard crash");
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -1479,52 +1206,6 @@ rejectold(Test *t, char *self, char *where)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
recoveryevents(Test *t)
|
|
||||||
{
|
|
||||||
if(!t->socketdeleted || !t->socketcreated)
|
|
||||||
return fail("replacement did not delete and recreate the stale IPC path");
|
|
||||||
if(!t->bustempcreated || !t->bustempmoved || !t->buspublished)
|
|
||||||
return fail("replacement IBus address was not atomically published");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
snapshotsecond(Test *t)
|
|
||||||
{
|
|
||||||
pid_t declared;
|
|
||||||
|
|
||||||
if(lstat(t->socket, &t->secondsocketst) < 0)
|
|
||||||
return fail("stat replacement IPC socket: %s", strerror(errno));
|
|
||||||
if(!S_ISSOCK(t->secondsocketst.st_mode) ||
|
|
||||||
(t->secondsocketst.st_mode & 0777) != 0600)
|
|
||||||
return fail("replacement IPC endpoint is not a protected socket");
|
|
||||||
if(lstat(t->addrfile, &t->secondaddrst) < 0)
|
|
||||||
return fail("stat replacement IBus address file: %s", strerror(errno));
|
|
||||||
if(!S_ISREG(t->secondaddrst.st_mode) ||
|
|
||||||
(t->secondaddrst.st_mode & 0777) != 0600)
|
|
||||||
return fail("replacement IBus address is not a protected regular file");
|
|
||||||
if(t->secondaddrst.st_dev == t->firstaddrst.st_dev &&
|
|
||||||
t->secondaddrst.st_ino == t->firstaddrst.st_ino)
|
|
||||||
return fail("replacement IBus address retained the stale inode");
|
|
||||||
if(!readfile(t->addrfile, t->secondcontents, sizeof t->secondcontents,
|
|
||||||
&t->nsecondcontents) ||
|
|
||||||
!parseaddress(t->secondcontents, t->nsecondcontents, t->secondaddress,
|
|
||||||
sizeof t->secondaddress, &declared))
|
|
||||||
return 0;
|
|
||||||
if(declared != t->second)
|
|
||||||
return fail("replacement IBus address PID %ld, expected %ld",
|
|
||||||
(long)declared, (long)t->second);
|
|
||||||
if(!privateaddress(t->secondaddress, declared))
|
|
||||||
return 0;
|
|
||||||
if(t->nsecondcontents == t->nfirstcontents &&
|
|
||||||
memcmp(t->secondcontents, t->firstcontents, t->nfirstcontents) == 0)
|
|
||||||
return fail("replacement IBus address contents did not change");
|
|
||||||
if(strcmp(t->secondaddress, t->firstaddress) == 0)
|
|
||||||
return fail("replacement reused the dead private IBus address");
|
|
||||||
return onlyaddress(t, "after replacement");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
openreplacement(Test *t)
|
openreplacement(Test *t)
|
||||||
{
|
{
|
||||||
@@ -1747,7 +1428,6 @@ cleanup(Test *t)
|
|||||||
ok = 0;
|
ok = 0;
|
||||||
if(t->first > 0 && !killreap(&t->first, t->firsterrfd, "first daemon"))
|
if(t->first > 0 && !killreap(&t->first, t->firsterrfd, "first daemon"))
|
||||||
ok = 0;
|
ok = 0;
|
||||||
t->phase = Phaseignore;
|
|
||||||
if(!stopsecond(t))
|
if(!stopsecond(t))
|
||||||
ok = 0;
|
ok = 0;
|
||||||
readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr, &t->nfirsterr);
|
readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr, &t->nfirsterr);
|
||||||
@@ -1776,23 +1456,6 @@ cleanup(Test *t)
|
|||||||
}
|
}
|
||||||
t->helpererrfd = -1;
|
t->helpererrfd = -1;
|
||||||
}
|
}
|
||||||
if(t->notifyfd >= 0){
|
|
||||||
if(t->runtimewd >= 0 &&
|
|
||||||
inotify_rm_watch(t->notifyfd, t->runtimewd) < 0 && errno != EINVAL){
|
|
||||||
fail("remove runtime watch: %s", strerror(errno));
|
|
||||||
ok = 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(!removeroot(t))
|
if(!removeroot(t))
|
||||||
ok = 0;
|
ok = 0;
|
||||||
return ok;
|
return ok;
|
||||||
@@ -1805,23 +1468,19 @@ runrestart(Test *t, char *self, char *program, char *mapdir)
|
|||||||
return 0;
|
return 0;
|
||||||
t->firstpid = t->first;
|
t->firstpid = t->first;
|
||||||
if(!waitready(t, &t->first, t->firsterrfd, "first") ||
|
if(!waitready(t, &t->first, t->firsterrfd, "first") ||
|
||||||
!snapshotfirst(t) || !openpersistent(t) || !drainnotify(t))
|
!openpersistent(t))
|
||||||
return 0;
|
return 0;
|
||||||
t->phase = Phasequiet;
|
|
||||||
if(!hardcrash(t) || !waitipcclosed(t) || !waitbusclosed(t) ||
|
if(!hardcrash(t) || !waitipcclosed(t) || !waitbusclosed(t) ||
|
||||||
!drainnotify(t) || !checkstale(t) ||
|
!checkstale(t) || !rejectold(t, self, "after hard crash"))
|
||||||
!rejectold(t, self, "after hard crash") || !drainnotify(t))
|
|
||||||
return 0;
|
return 0;
|
||||||
closebus(&t->busfirst);
|
closebus(&t->busfirst);
|
||||||
if(close(t->ipcfirst) < 0)
|
if(close(t->ipcfirst) < 0)
|
||||||
return fail("close disconnected persistent IPC client: %s",
|
return fail("close disconnected persistent IPC client: %s",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
t->ipcfirst = -1;
|
t->ipcfirst = -1;
|
||||||
t->phase = Phaserecover;
|
|
||||||
if(!startchild(t, program, mapdir, &t->second, &t->seconderrfd) ||
|
if(!startchild(t, program, mapdir, &t->second, &t->seconderrfd) ||
|
||||||
!waitready(t, &t->second, t->seconderrfd, "second") ||
|
!waitready(t, &t->second, t->seconderrfd, "second") ||
|
||||||
!drainnotify(t) || !recoveryevents(t) || !snapshotsecond(t) ||
|
!openreplacement(t))
|
||||||
!rejectold(t, self, "after replacement") || !openreplacement(t))
|
|
||||||
return 0;
|
return 0;
|
||||||
return childalive(&t->second, "second");
|
return childalive(&t->second, "second");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,31 +8,17 @@ typedef struct Log Log;
|
|||||||
struct Log
|
struct Log
|
||||||
{
|
{
|
||||||
GMainLoop *loop;
|
GMainLoop *loop;
|
||||||
int want;
|
|
||||||
int legacy;
|
int legacy;
|
||||||
int modern;
|
int modern;
|
||||||
|
int commit;
|
||||||
int invalid;
|
int invalid;
|
||||||
int sawtext;
|
int sawpreedit;
|
||||||
int sawempty;
|
int sawcommit;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
legacy(IBusInputContext *ctx, IBusText *text, guint cursor,
|
legacy(IBusInputContext *ctx, IBusText *text, guint cursor,
|
||||||
gboolean visible, void *arg)
|
gboolean visible, void *arg)
|
||||||
{
|
|
||||||
Log *log;
|
|
||||||
|
|
||||||
(void)ctx;
|
|
||||||
(void)text;
|
|
||||||
(void)cursor;
|
|
||||||
(void)visible;
|
|
||||||
log = arg;
|
|
||||||
log->legacy++;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
modern(IBusInputContext *ctx, IBusText *text, guint cursor,
|
|
||||||
gboolean visible, guint mode, void *arg)
|
|
||||||
{
|
{
|
||||||
IBusAttrList *attrs;
|
IBusAttrList *attrs;
|
||||||
IBusAttribute *a;
|
IBusAttribute *a;
|
||||||
@@ -41,28 +27,60 @@ modern(IBusInputContext *ctx, IBusText *text, guint cursor,
|
|||||||
|
|
||||||
(void)ctx;
|
(void)ctx;
|
||||||
log = arg;
|
log = arg;
|
||||||
log->modern++;
|
log->legacy++;
|
||||||
s = ibus_text_get_text(text);
|
s = ibus_text_get_text(text);
|
||||||
attrs = ibus_text_get_attributes(text);
|
if(strcmp(s, "k") == 0){
|
||||||
a = ibus_attr_list_get(attrs, 0);
|
attrs = ibus_text_get_attributes(text);
|
||||||
if(s[0] != '\0'){
|
a = attrs == NULL ? NULL : ibus_attr_list_get(attrs, 0);
|
||||||
if(strcmp(s, "k") != 0 || cursor != 1 || !visible ||
|
if(cursor != 1 || !visible || attrs == NULL || a == NULL ||
|
||||||
mode != IBUS_ENGINE_PREEDIT_CLEAR || a == NULL ||
|
|
||||||
a->type != IBUS_ATTR_TYPE_UNDERLINE ||
|
a->type != IBUS_ATTR_TYPE_UNDERLINE ||
|
||||||
a->value != IBUS_ATTR_UNDERLINE_SINGLE ||
|
a->value != IBUS_ATTR_UNDERLINE_SINGLE ||
|
||||||
a->start_index != 0 || a->end_index != 1 ||
|
a->start_index != 0 || a->end_index != 1 ||
|
||||||
ibus_attr_list_get(attrs, 1) != NULL)
|
ibus_attr_list_get(attrs, 1) != NULL)
|
||||||
log->invalid = 1;
|
log->invalid = 1;
|
||||||
else
|
else
|
||||||
log->sawtext = 1;
|
log->sawpreedit = 1;
|
||||||
}else{
|
|
||||||
if(cursor != 0 || visible || mode != IBUS_ENGINE_PREEDIT_CLEAR ||
|
|
||||||
a != NULL)
|
|
||||||
log->invalid = 1;
|
|
||||||
else
|
|
||||||
log->sawempty = 1;
|
|
||||||
}
|
}
|
||||||
if(log->loop != NULL && log->modern >= log->want)
|
if(log->loop != NULL && log->sawpreedit && log->sawcommit)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
modern(IBusInputContext *ctx, IBusText *text, guint cursor,
|
||||||
|
gboolean visible, guint mode, void *arg)
|
||||||
|
{
|
||||||
|
Log *log;
|
||||||
|
|
||||||
|
(void)ctx;
|
||||||
|
(void)text;
|
||||||
|
(void)cursor;
|
||||||
|
(void)visible;
|
||||||
|
(void)mode;
|
||||||
|
log = arg;
|
||||||
|
log->modern++;
|
||||||
|
log->invalid = 1;
|
||||||
|
if(log->loop != NULL)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
commit(IBusInputContext *ctx, IBusText *text, void *arg)
|
||||||
|
{
|
||||||
|
IBusAttrList *attrs;
|
||||||
|
Log *log;
|
||||||
|
const char *s;
|
||||||
|
|
||||||
|
(void)ctx;
|
||||||
|
log = arg;
|
||||||
|
log->commit++;
|
||||||
|
s = ibus_text_get_text(text);
|
||||||
|
attrs = ibus_text_get_attributes(text);
|
||||||
|
if(strcmp(s, "か") != 0 || attrs == NULL ||
|
||||||
|
ibus_attr_list_get(attrs, 0) != NULL)
|
||||||
|
log->invalid = 1;
|
||||||
|
else
|
||||||
|
log->sawcommit = 1;
|
||||||
|
if(log->loop != NULL && log->sawpreedit && log->sawcommit)
|
||||||
g_main_loop_quit(log->loop);
|
g_main_loop_quit(log->loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,12 +92,11 @@ timeout(void *arg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
waitmodern(Log *log, int want)
|
waitdone(Log *log)
|
||||||
{
|
{
|
||||||
guint timer;
|
guint timer;
|
||||||
|
|
||||||
log->want = want;
|
if(log->sawpreedit && log->sawcommit)
|
||||||
if(log->modern >= want)
|
|
||||||
return 1;
|
return 1;
|
||||||
log->loop = g_main_loop_new(NULL, FALSE);
|
log->loop = g_main_loop_new(NULL, FALSE);
|
||||||
timer = g_timeout_add_seconds(4, timeout, log->loop);
|
timer = g_timeout_add_seconds(4, timeout, log->loop);
|
||||||
@@ -88,7 +105,7 @@ waitmodern(Log *log, int want)
|
|||||||
g_source_remove(timer);
|
g_source_remove(timer);
|
||||||
g_main_loop_unref(log->loop);
|
g_main_loop_unref(log->loop);
|
||||||
log->loop = NULL;
|
log->loop = NULL;
|
||||||
return log->modern >= want;
|
return log->sawpreedit && log->sawcommit;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
@@ -104,6 +121,7 @@ main(int argc, char **argv)
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
if(setenv("IBUS_ADDRESS", argv[1], 1) < 0 ||
|
if(setenv("IBUS_ADDRESS", argv[1], 1) < 0 ||
|
||||||
|
unsetenv("IBUS_ADDRESS_FILE") < 0 ||
|
||||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 || unsetenv("DISPLAY") < 0){
|
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 || unsetenv("DISPLAY") < 0){
|
||||||
perror("ibus_client_smoke: environment");
|
perror("ibus_client_smoke: environment");
|
||||||
return 1;
|
return 1;
|
||||||
@@ -125,30 +143,27 @@ main(int argc, char **argv)
|
|||||||
g_signal_connect(ctx, "update-preedit-text", G_CALLBACK(legacy), &log);
|
g_signal_connect(ctx, "update-preedit-text", G_CALLBACK(legacy), &log);
|
||||||
g_signal_connect(ctx, "update-preedit-text-with-mode", G_CALLBACK(modern),
|
g_signal_connect(ctx, "update-preedit-text-with-mode", G_CALLBACK(modern),
|
||||||
&log);
|
&log);
|
||||||
|
g_signal_connect(ctx, "commit-text", G_CALLBACK(commit), &log);
|
||||||
ibus_input_context_set_capabilities(ctx, IBUS_CAP_PREEDIT_TEXT);
|
ibus_input_context_set_capabilities(ctx, IBUS_CAP_PREEDIT_TEXT);
|
||||||
ibus_input_context_set_client_commit_preedit(ctx, TRUE);
|
|
||||||
ibus_input_context_focus_in(ctx);
|
ibus_input_context_focus_in(ctx);
|
||||||
ok = ibus_input_context_process_key_event(ctx, 'n', 0,
|
ok = ibus_input_context_process_key_event(ctx, 'n', 0, IBUS_CONTROL_MASK) &&
|
||||||
IBUS_CONTROL_MASK) && waitmodern(&log, 1) &&
|
|
||||||
ibus_input_context_process_key_event(ctx, 'k', 0, 0) &&
|
ibus_input_context_process_key_event(ctx, 'k', 0, 0) &&
|
||||||
waitmodern(&log, 2);
|
ibus_input_context_process_key_event(ctx, 'a', 0, 0) &&
|
||||||
ibus_input_context_reset(ctx);
|
ibus_input_context_process_key_event(ctx, '0', 0, 0) &&
|
||||||
if(ok)
|
waitdone(&log);
|
||||||
ok = waitmodern(&log, 3);
|
|
||||||
ibus_input_context_focus_out(ctx);
|
ibus_input_context_focus_out(ctx);
|
||||||
if(ok)
|
if(log.legacy == 0 || log.modern != 0 || log.commit != 1 ||
|
||||||
ok = waitmodern(&log, 4);
|
log.invalid || !log.sawpreedit || !log.sawcommit)
|
||||||
if(log.legacy != 0 || log.modern != 4 || log.invalid ||
|
|
||||||
!log.sawtext || !log.sawempty)
|
|
||||||
ok = 0;
|
ok = 0;
|
||||||
g_object_unref(ctx);
|
g_object_unref(ctx);
|
||||||
g_object_unref(bus);
|
g_object_unref(bus);
|
||||||
if(!ok){
|
if(!ok){
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"ibus_client_smoke: legacy=%d modern=%d invalid=%d text=%d empty=%d\n",
|
"ibus_client_smoke: legacy=%d modern=%d commit=%d invalid=%d preedit=%d committed=%d\n",
|
||||||
log.legacy, log.modern, log.invalid, log.sawtext, log.sawempty);
|
log.legacy, log.modern, log.commit, log.invalid,
|
||||||
|
log.sawpreedit, log.sawcommit);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
printf("official libibus client preedit: ok\n");
|
printf("official libibus client preedit and commit: ok\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@ enum
|
|||||||
Calltimeout = 4000,
|
Calltimeout = 4000,
|
||||||
Starttimeout = 8000,
|
Starttimeout = 8000,
|
||||||
Stoptimeout = 3000,
|
Stoptimeout = 3000,
|
||||||
|
Cappreedit = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Daemon Daemon;
|
typedef struct Daemon Daemon;
|
||||||
@@ -141,11 +142,19 @@ waitsocket(Daemon *d, int notifyfd)
|
|||||||
if(lstat(d->socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
if(lstat(d->socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
||||||
(st.st_mode & 0777) == 0600)
|
(st.st_mode & 0777) == 0600)
|
||||||
return 1;
|
return 1;
|
||||||
if(waitpid(d->pid, &status, WNOHANG) == d->pid){
|
do
|
||||||
|
n = waitpid(d->pid, &status, WNOHANG);
|
||||||
|
while(n < 0 && errno == EINTR);
|
||||||
|
if(n == d->pid){
|
||||||
d->pid = -1;
|
d->pid = -1;
|
||||||
readerrors(d);
|
readerrors(d);
|
||||||
return fail("daemon exited before creating IPC socket");
|
return fail("daemon exited before creating IPC socket");
|
||||||
}
|
}
|
||||||
|
if(n < 0){
|
||||||
|
if(errno == ECHILD)
|
||||||
|
d->pid = -1;
|
||||||
|
return fail("wait for daemon startup: %s", strerror(errno));
|
||||||
|
}
|
||||||
timeout = leftms(deadline);
|
timeout = leftms(deadline);
|
||||||
if(timeout == 0)
|
if(timeout == 0)
|
||||||
return fail("timed out waiting for protected IPC socket %s",
|
return fail("timed out waiting for protected IPC socket %s",
|
||||||
@@ -182,8 +191,10 @@ startdaemon(Daemon *d, char *program, char *mapdir)
|
|||||||
d->pid = -1;
|
d->pid = -1;
|
||||||
d->errfd = -1;
|
d->errfd = -1;
|
||||||
snprintf(d->root, sizeof d->root, "/tmp/strans-ipc.XXXXXX");
|
snprintf(d->root, sizeof d->root, "/tmp/strans-ipc.XXXXXX");
|
||||||
if(mkdtemp(d->root) == NULL)
|
if(mkdtemp(d->root) == NULL){
|
||||||
|
d->root[0] = '\0';
|
||||||
return fail("mkdtemp: %s", strerror(errno));
|
return fail("mkdtemp: %s", strerror(errno));
|
||||||
|
}
|
||||||
if(snprintf(d->runtime, sizeof d->runtime, "%s/runtime", d->root)
|
if(snprintf(d->runtime, sizeof d->runtime, "%s/runtime", d->root)
|
||||||
>= (int)sizeof d->runtime ||
|
>= (int)sizeof d->runtime ||
|
||||||
snprintf(d->config, sizeof d->config, "%s/config", d->root)
|
snprintf(d->config, sizeof d->config, "%s/config", d->root)
|
||||||
@@ -236,10 +247,10 @@ startdaemon(Daemon *d, char *program, char *mapdir)
|
|||||||
}
|
}
|
||||||
if(setenv("XDG_RUNTIME_DIR", d->runtime, 1) < 0 ||
|
if(setenv("XDG_RUNTIME_DIR", d->runtime, 1) < 0 ||
|
||||||
setenv("XDG_CONFIG_HOME", d->config, 1) < 0 ||
|
setenv("XDG_CONFIG_HOME", d->config, 1) < 0 ||
|
||||||
setenv("HOME", d->home, 1) < 0 ||
|
setenv("HOME", d->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
||||||
setenv("DISPLAY", "invalid", 1) < 0 ||
|
|
||||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 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",
|
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
_exit(126);
|
_exit(126);
|
||||||
@@ -323,6 +334,8 @@ killdaemon(Daemon *d, int *status)
|
|||||||
if(n != d->pid){
|
if(n != d->pid){
|
||||||
fail("reap %ld after SIGKILL: %s", (long)d->pid,
|
fail("reap %ld after SIGKILL: %s", (long)d->pid,
|
||||||
n < 0 ? strerror(errno) : "wrong child");
|
n < 0 ? strerror(errno) : "wrong child");
|
||||||
|
if(n < 0 && errno == ECHILD)
|
||||||
|
d->pid = -1;
|
||||||
ok = 0;
|
ok = 0;
|
||||||
}else
|
}else
|
||||||
d->pid = -1;
|
d->pid = -1;
|
||||||
@@ -614,6 +627,15 @@ sendreset(int fd, int want)
|
|||||||
return ipcsend(fd, req, sizeof req) == 0;
|
return ipcsend(fd, req, sizeof req) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sendcap(int fd, int cap)
|
||||||
|
{
|
||||||
|
unsigned char req[Ipcreqsz];
|
||||||
|
|
||||||
|
ipcpackcap(req, cap);
|
||||||
|
return ipcsend(fd, req, sizeof req) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
||||||
char *where)
|
char *where)
|
||||||
@@ -740,7 +762,25 @@ overflowrejected(char *path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
runlistener(Daemon *d)
|
runsmoke(Daemon *d)
|
||||||
|
{
|
||||||
|
int client, ok;
|
||||||
|
|
||||||
|
client = connectuntil(d->socket, nowms() + Calltimeout);
|
||||||
|
if(client < 0)
|
||||||
|
return fail("connect client: %s", strerror(errno));
|
||||||
|
ok = sendcap(client, Cappreedit) &&
|
||||||
|
expectresponse(client, 1, 1, "", "", "negotiate preedit") &&
|
||||||
|
requestkey(client, 1, Mctrl, 'n', 1, "", "",
|
||||||
|
"select Japanese") &&
|
||||||
|
requestkey(client, 1, 0, 'k', 1, "", "k", "preedit") &&
|
||||||
|
requestreset(client, 1, 1, "", "reset");
|
||||||
|
close(client);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
runcapacity(Daemon *d)
|
||||||
{
|
{
|
||||||
int client[Maxclients];
|
int client[Maxclients];
|
||||||
int i, ok;
|
int i, ok;
|
||||||
@@ -753,17 +793,8 @@ runlistener(Daemon *d)
|
|||||||
fail("connect first client: %s", strerror(errno));
|
fail("connect first client: %s", strerror(errno));
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "", "select Japanese") ||
|
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "",
|
||||||
!requestkey(client[0], 1, 0, 'k', 1, "", "k", "GTK preedit") ||
|
"select Japanese"))
|
||||||
!requestreset(client[0], 1, 1, "", "active reset"))
|
|
||||||
goto out;
|
|
||||||
if(!sendkey(client[0], 0, 0, 'k') ||
|
|
||||||
!sendkey(client[0], 1, 0, Kmodfirst)){
|
|
||||||
fail("send pipelined XIM/GTK requests: %s", strerror(errno));
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if(!expectresponse(client[0], 0, 1, "", "", "XIM framing") ||
|
|
||||||
!expectresponse(client[0], 1, 0, "", "k", "GTK after XIM framing"))
|
|
||||||
goto out;
|
goto out;
|
||||||
for(i = 1; i < Maxclients; i++){
|
for(i = 1; i < Maxclients; i++){
|
||||||
client[i] = connectuntil(d->socket, nowms() + Calltimeout);
|
client[i] = connectuntil(d->socket, nowms() + Calltimeout);
|
||||||
@@ -799,21 +830,26 @@ int
|
|||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
Daemon daemon;
|
Daemon daemon;
|
||||||
int ok;
|
char *program, *mapdir;
|
||||||
|
int capacity, ok;
|
||||||
|
|
||||||
if(argc != 3){
|
capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0;
|
||||||
fprintf(stderr, "usage: ipc_live_test strans mapdir\n");
|
if((!capacity && argc != 3) || (capacity && argc != 4)){
|
||||||
|
fprintf(stderr,
|
||||||
|
"usage: ipc_live_test [--capacity] strans mapdir\n");
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
ok = startdaemon(&daemon, argv[1], argv[2]);
|
program = argv[1+capacity];
|
||||||
|
mapdir = argv[2+capacity];
|
||||||
|
ok = startdaemon(&daemon, program, mapdir);
|
||||||
if(ok)
|
if(ok)
|
||||||
ok = runlistener(&daemon);
|
ok = capacity ? runcapacity(&daemon) : runsmoke(&daemon);
|
||||||
if(!ok)
|
if(!ok)
|
||||||
showerrors(&daemon);
|
showerrors(&daemon);
|
||||||
if(!stopdaemon(&daemon))
|
if(!stopdaemon(&daemon))
|
||||||
ok = 0;
|
ok = 0;
|
||||||
if(!ok)
|
if(!ok)
|
||||||
return 1;
|
return 1;
|
||||||
printf("ipc live listener: ok\n");
|
printf("ipc %s: ok\n", capacity ? "connection capacity" : "live smoke");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ testmapinit(void)
|
|||||||
testvi.map = trieopen("../map/telex.map");
|
testvi.map = trieopen("../map/telex.map");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef STRESS
|
||||||
static const struct ct_test tests[] = {
|
static const struct ct_test tests[] = {
|
||||||
{ "str/init-utf8", str_init_utf8 },
|
{ "str/init-utf8", str_init_utf8 },
|
||||||
{ "str/edit-and-alias", str_edit_and_alias },
|
{ "str/edit-and-alias", str_edit_and_alias },
|
||||||
@@ -119,7 +120,6 @@ static const struct ct_test tests[] = {
|
|||||||
{ "engine/hanja-korean-keys", engine_hanja_korean_keys },
|
{ "engine/hanja-korean-keys", engine_hanja_korean_keys },
|
||||||
{ "engine/hanja-backspace", engine_hanja_backspace },
|
{ "engine/hanja-backspace", engine_hanja_backspace },
|
||||||
{ "engine/hanja-input-languages", engine_hanja_input_languages },
|
{ "engine/hanja-input-languages", engine_hanja_input_languages },
|
||||||
{ "engine/randomized-stress", engine_randomized_stress },
|
|
||||||
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
|
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
|
||||||
{ "dict/candidates", dictionary_candidates },
|
{ "dict/candidates", dictionary_candidates },
|
||||||
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
||||||
@@ -155,6 +155,11 @@ static const struct ct_test tests[] = {
|
|||||||
{ "xim/callback-owner-loss", xim_adapter_callback_owner_loss },
|
{ "xim/callback-owner-loss", xim_adapter_callback_owner_loss },
|
||||||
{ "xim/commit-encoding", xim_adapter_commit_encoding },
|
{ "xim/commit-encoding", xim_adapter_commit_encoding },
|
||||||
};
|
};
|
||||||
|
#else
|
||||||
|
static const struct ct_test tests[] = {
|
||||||
|
{ "engine/randomized-stress", engine_randomized_stress },
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
void
|
void
|
||||||
threadmain(int argc, char **argv)
|
threadmain(int argc, char **argv)
|
||||||
|
|||||||
Reference in New Issue
Block a user