Six live tests carried private copies of the same daemon harness: the
private XDG_RUNTIME_DIR, the fork/exec with captured stderr, the
readiness waits, the SIGTERM and SIGKILL paths, the IBus address file
reader, the socket connect, and the IPC probe. Three of them also
carried the same libdbus helpers.
Extract one implementation into tests/live.c and, so that the binaries
that do not link dbus-1 keep not linking it, the D-Bus half into
tests/livebus.c. Both are compiled into each binary the way ../ipc.c
already is. gtk_live_test keeps its own fake-server harness and only
takes fail, nowms and leftms; xim_live_test keeps its own Xvfb and
process-group daemon spawn, which must inherit DISPLAY, and takes the
temporary directory, timing and cleanup halves.
The copies had drifted; the harness keeps the stricter behaviour.
- nowms reports a broken clock (-1) instead of pretending it read
zero, and leftms turns that into an expired deadline, so a loop
ends in a timeout failure rather than spinning. livesetup checks
the clock once up front, as daemon_restart_test did.
- Timeouts compare with <= 0, not == 0.
- readuntil keeps the three-way result (complete, peer closed, error)
from ipc_live_test and daemon_restart_test rather than folding
peer closure into ECONNRESET.
- The daemon's stdout and stderr are both captured, and a failing
setenv is reported, for every daemon; daemon_failure_test captured
stderr only and said nothing about setenv.
- The child keeps daemon_restart_test's careful redirect that also
works when the pipe lands on fd 1 or 2.
- parseaddress requires a positive declared PID.
- killdaemon reports ECHILD after SIGKILL as a failure; one copy
accepted it. It now reaps with a blocking waitpid, which SIGKILL
guarantees will return, instead of daemon_restart_test's polled
wait with its own timeout diagnostic.
- stopdaemon tolerates ESRCH on SIGTERM, a benign race two copies
reported as an error.
- liveclean removes the socket and address files and then rmdirs
each directory, reporting leftovers, rather than deleting the
temporary root recursively.
- ibus_live_test now waits for the IPC socket and the address file
by polling, dropping its inotify variant; it asserted only the
address file before.
235 lines
5.4 KiB
C
235 lines
5.4 KiB
C
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include "live.h"
|
|
#include "livebus.h"
|
|
|
|
enum
|
|
{
|
|
Maxconnections = 64,
|
|
};
|
|
|
|
static int
|
|
invalidcall(DBusConnection *conn, char *path, char *member)
|
|
{
|
|
DBusMessage *reply;
|
|
const char *name;
|
|
int ok;
|
|
|
|
reply = sendcall(conn, contextcall(path, member), member);
|
|
if(reply == NULL)
|
|
return 0;
|
|
name = dbus_message_get_error_name(reply);
|
|
ok = dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR &&
|
|
name != NULL && strcmp(name, DBUS_ERROR_INVALID_ARGS) == 0;
|
|
if(!ok)
|
|
fail("malformed %s returned %s, expected %s", member,
|
|
name != NULL ? name : "a non-error reply",
|
|
DBUS_ERROR_INVALID_ARGS);
|
|
dbus_message_unref(reply);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
overflowrejected(char *address)
|
|
{
|
|
DBusConnection *conn;
|
|
DBusError err;
|
|
struct pollfd pfd;
|
|
int fd, n, ok, timeout;
|
|
int64_t deadline;
|
|
|
|
dbus_error_init(&err);
|
|
conn = dbus_connection_open_private(address, &err);
|
|
if(conn == NULL){
|
|
ok = dbus_error_is_set(&err);
|
|
dbus_error_free(&err);
|
|
return ok || fail("overflow connection failed without a D-Bus error");
|
|
}
|
|
dbus_error_free(&err);
|
|
dbus_connection_set_exit_on_disconnect(conn, FALSE);
|
|
if(!dbus_connection_get_unix_fd(conn, &fd)){
|
|
closebus(&conn);
|
|
return fail("overflow connection has no Unix descriptor");
|
|
}
|
|
ok = 0;
|
|
deadline = nowms() + Calltimeout;
|
|
for(;;){
|
|
if(!dbus_connection_get_is_connected(conn)){
|
|
ok = 1;
|
|
break;
|
|
}
|
|
timeout = leftms(deadline);
|
|
if(timeout == 0){
|
|
fail("65th IBus connection was not rejected");
|
|
break;
|
|
}
|
|
pfd.fd = fd;
|
|
pfd.events = POLLIN;
|
|
pfd.revents = 0;
|
|
n = poll(&pfd, 1, timeout);
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
if(n < 0){
|
|
fail("poll overflow IBus connection: %s", strerror(errno));
|
|
break;
|
|
}
|
|
if(n == 0)
|
|
continue;
|
|
if(pfd.revents & POLLNVAL){
|
|
fail("overflow IBus descriptor became invalid");
|
|
break;
|
|
}
|
|
dbus_connection_read_write_dispatch(conn, 0);
|
|
}
|
|
closebus(&conn);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
runcontract(char *address)
|
|
{
|
|
static char *bad[] = {
|
|
"ProcessKeyEvent",
|
|
"SetEngine",
|
|
"SetCapabilities",
|
|
"SetCursorLocation",
|
|
};
|
|
DBusConnection *conn;
|
|
char path[96];
|
|
int i, ok;
|
|
|
|
conn = openbus(address, "contract");
|
|
if(conn == NULL)
|
|
return 0;
|
|
ok = hello(conn, NULL, 0, "contract Hello") &&
|
|
createcontext(conn, path, sizeof path, "contract context");
|
|
for(i = 0; ok && i < (int)(sizeof bad / sizeof bad[0]); i++)
|
|
ok = invalidcall(conn, path, bad[i]);
|
|
closebus(&conn);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
runcapacity(char *address)
|
|
{
|
|
DBusConnection *conn[Maxconnections];
|
|
int i, ok;
|
|
|
|
for(i = 0; i < Maxconnections; i++)
|
|
conn[i] = NULL;
|
|
ok = 0;
|
|
for(i = 0; i < Maxconnections; i++){
|
|
conn[i] = openbus(address, "capacity");
|
|
if(conn[i] == NULL || !hello(conn[i], NULL, 0, "capacity Hello"))
|
|
goto out;
|
|
}
|
|
if(!overflowrejected(address))
|
|
goto out;
|
|
closebus(&conn[Maxconnections-1]);
|
|
if(!createcontext(conn[0], NULL, 0, "capacity context"))
|
|
goto out;
|
|
conn[Maxconnections-1] = openbus(address, "replacement");
|
|
if(conn[Maxconnections-1] == NULL ||
|
|
!hello(conn[Maxconnections-1], NULL, 0, "replacement Hello") ||
|
|
!createcontext(conn[Maxconnections-1], NULL, 0, "replacement context"))
|
|
goto out;
|
|
ok = 1;
|
|
out:
|
|
for(i = 0; i < Maxconnections; i++)
|
|
closebus(&conn[i]);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
runclient(Live *l, char *address, char *program)
|
|
{
|
|
pid_t pid, n;
|
|
int status;
|
|
int64_t deadline;
|
|
|
|
pid = fork();
|
|
if(pid < 0)
|
|
return fail("fork official libibus client: %s", strerror(errno));
|
|
if(pid == 0){
|
|
if(setenv("XDG_RUNTIME_DIR", l->runtime, 1) < 0 ||
|
|
setenv("XDG_CONFIG_HOME", l->config, 1) < 0 ||
|
|
setenv("HOME", l->home, 1) < 0 ||
|
|
unsetenv("IBUS_ADDRESS_FILE") < 0)
|
|
_exit(126);
|
|
execl(program, program, address, (char*)0);
|
|
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
|
_exit(127);
|
|
}
|
|
deadline = nowms() + Starttimeout;
|
|
for(;;){
|
|
n = waitpid(pid, &status, WNOHANG);
|
|
if(n == pid)
|
|
break;
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
if(n < 0)
|
|
return fail("wait official libibus client: %s", strerror(errno));
|
|
if(leftms(deadline) == 0){
|
|
fail("official libibus client timed out");
|
|
kill(pid, SIGKILL);
|
|
do
|
|
n = waitpid(pid, &status, 0);
|
|
while(n < 0 && errno == EINTR);
|
|
return 0;
|
|
}
|
|
pausems(10);
|
|
}
|
|
if(!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
|
return fail("official libibus client exited with wait status %#x",
|
|
status);
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
Daemon daemon;
|
|
Live live;
|
|
char address[512];
|
|
int capacity, ok;
|
|
|
|
testname = "ibus_live_test";
|
|
capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0;
|
|
if(argc != 4){
|
|
fprintf(stderr, "usage: ibus_live_test strans mapdir libibus-client\n");
|
|
fprintf(stderr, " ibus_live_test --capacity strans mapdir\n");
|
|
return 2;
|
|
}
|
|
daemoninit(&daemon, "daemon");
|
|
ok = livesetup(&live, "ibus") &&
|
|
startdaemon(&live, &daemon, argv[capacity ? 2 : 1],
|
|
argv[capacity ? 3 : 2]) &&
|
|
waitready(&live, &daemon, address, sizeof address);
|
|
if(ok)
|
|
ok = capacity ? runcapacity(address) :
|
|
runclient(&live, address, argv[3]) && runcontract(address);
|
|
if(!ok)
|
|
showerrors(&daemon);
|
|
if(!stopdaemon(&daemon))
|
|
ok = 0;
|
|
if(!closeerrors(&daemon))
|
|
ok = 0;
|
|
if(!liveclean(&live))
|
|
ok = 0;
|
|
if(!ok)
|
|
return 1;
|
|
printf(capacity ? "ibus connection capacity: ok\n" :
|
|
"ibus official client and malformed call: ok\n");
|
|
return 0;
|
|
}
|