add focused unit and map checks
This commit is contained in:
411
tests/ipc_test.c
Normal file
411
tests/ipc_test.c
Normal file
@@ -0,0 +1,411 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#undef waitpid
|
||||
|
||||
enum
|
||||
{
|
||||
Childtimeout = 2000,
|
||||
};
|
||||
|
||||
typedef struct Childres Childres;
|
||||
struct Childres
|
||||
{
|
||||
int rc;
|
||||
unsigned int want;
|
||||
unsigned int mod;
|
||||
unsigned int key;
|
||||
};
|
||||
|
||||
static long long
|
||||
nowms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
||||
return -1;
|
||||
return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
static int
|
||||
waitreadable(int fd, int timeout)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
long long end, now;
|
||||
int n, left;
|
||||
|
||||
end = nowms() + timeout;
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLIN;
|
||||
for(;;){
|
||||
now = nowms();
|
||||
if(now < 0 || now >= end)
|
||||
return -1;
|
||||
left = end - now;
|
||||
n = poll(&pfd, 1, left);
|
||||
if(n > 0)
|
||||
return (pfd.revents & (POLLIN|POLLHUP)) != 0 ? 0 : -1;
|
||||
if(n == 0)
|
||||
return -1;
|
||||
if(errno != EINTR)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
readdeadline(int fd, void *buf, size_t n, int timeout)
|
||||
{
|
||||
unsigned char *p;
|
||||
long long end, now;
|
||||
ssize_t got;
|
||||
|
||||
p = buf;
|
||||
end = nowms() + timeout;
|
||||
while(n > 0){
|
||||
now = nowms();
|
||||
if(now < 0 || now >= end || waitreadable(fd, end - now) < 0)
|
||||
return -1;
|
||||
got = read(fd, p, n);
|
||||
if(got < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(got <= 0)
|
||||
return -1;
|
||||
p += got;
|
||||
n -= got;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
waitqueueempty(int fd, int timeout)
|
||||
{
|
||||
long long end;
|
||||
int n;
|
||||
|
||||
end = nowms() + timeout;
|
||||
for(;;){
|
||||
if(ioctl(fd, FIONREAD, &n) < 0)
|
||||
return -1;
|
||||
if(n == 0)
|
||||
return 0;
|
||||
if(nowms() >= end)
|
||||
return -1;
|
||||
sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
reapchild(pid_t pid, int *status, int timeout)
|
||||
{
|
||||
long long end;
|
||||
pid_t got;
|
||||
|
||||
end = nowms() + timeout;
|
||||
for(;;){
|
||||
got = waitpid(pid, status, WNOHANG);
|
||||
if(got == pid)
|
||||
return 0;
|
||||
if(got < 0 && errno != EINTR)
|
||||
return -1;
|
||||
if(nowms() >= end)
|
||||
break;
|
||||
poll(nil, 0, 1);
|
||||
}
|
||||
if(kill(pid, SIGKILL) < 0 && errno != ESRCH)
|
||||
return -1;
|
||||
end = nowms() + timeout;
|
||||
for(;;){
|
||||
got = waitpid(pid, status, WNOHANG);
|
||||
if(got == pid)
|
||||
return 0;
|
||||
if(got < 0 && errno != EINTR)
|
||||
return -1;
|
||||
if(nowms() >= end)
|
||||
return -1;
|
||||
poll(nil, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ipc_request_codec(struct ct *t)
|
||||
{
|
||||
static const struct {
|
||||
int want;
|
||||
unsigned int mod, key;
|
||||
unsigned char bytes[Ipcreqsz];
|
||||
} cases[] = {
|
||||
{ 0, 0, 0, { 0, 0, 0, 0, 0, 0 } },
|
||||
{ 1, Mctrl|Mshift, 0x1234,
|
||||
{ 1, Mctrl|Mshift, 0x34, 0x12, 0, 0 } },
|
||||
{ 1, Malt, 0x1f600,
|
||||
{ 1, Malt, 0x00, 0xf6, 0x01, 0x00 } },
|
||||
{ 1, 0, 0xf008,
|
||||
{ 1, 0, 0x08, 0xf0, 0x00, 0x00 } },
|
||||
{ 1, 0, Kback,
|
||||
{ 1, 0, 0x08, 0x00, 0x11, 0x00 } },
|
||||
{ 1, Msuper, 0x89abcdefU,
|
||||
{ 1, Msuper, 0xef, 0xcd, 0xab, 0x89 } },
|
||||
};
|
||||
unsigned char got[Ipcreqsz];
|
||||
unsigned int mod, key;
|
||||
int i, want;
|
||||
|
||||
for(i = 0; i < nelem(cases); i++){
|
||||
ipcpackreq(got, cases[i].want, cases[i].mod, cases[i].key);
|
||||
if(!CT_EQ_MEM(t, cases[i].bytes, got, sizeof got))
|
||||
continue;
|
||||
ipcunpackreq(got, &want, &mod, &key);
|
||||
CT_EQ_INT(t, cases[i].want != 0, want);
|
||||
CT_EQ_UINT(t, cases[i].mod, mod);
|
||||
CT_EQ_UINT(t, cases[i].key, key);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ipc_response_pack_boundaries(struct ct *t)
|
||||
{
|
||||
static const unsigned char want[] = { 1, 1, 0, 'A', 2, 0, 'x', 'y' };
|
||||
static const unsigned char want0[] = { 1, 1, 0, 'A' };
|
||||
unsigned char out[Ipcmaxresp+1], field[Ipcfieldmax];
|
||||
int n;
|
||||
|
||||
n = ipcpackresp(out, sizeof out, 7, "A", 1, "xy", 2, 1);
|
||||
CT_EQ_INT(t, sizeof want, n);
|
||||
CT_EQ_MEM(t, want, out, sizeof want);
|
||||
n = ipcpackresp(out, sizeof out, 1, "A", 1, "xy", 2, 0);
|
||||
CT_EQ_INT(t, sizeof want0, n);
|
||||
CT_EQ_MEM(t, want0, out, sizeof want0);
|
||||
|
||||
memset(field, 'x', sizeof field);
|
||||
memset(out, 0xa5, sizeof out);
|
||||
n = ipcpackresp(out, Ipcmaxresp, 1,
|
||||
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
||||
CT_EQ_INT(t, Ipcmaxresp, n);
|
||||
CT_EQ_INT(t, 0, out[1]);
|
||||
CT_EQ_INT(t, 1, out[2]);
|
||||
CT_EQ_INT(t, 0, out[3+Ipcfieldmax]);
|
||||
CT_EQ_INT(t, 1, out[4+Ipcfieldmax]);
|
||||
CT_EQ_INT(t, 0xa5, out[Ipcmaxresp]);
|
||||
CT_EQ_INT(t, 'x', out[Ipcmaxresp-1]);
|
||||
|
||||
memset(out, 0xa5, sizeof out);
|
||||
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1,
|
||||
(char*)field, sizeof field, (char*)field, sizeof field, 1));
|
||||
CT_EQ_INT(t, 0xa5, out[0]);
|
||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
||||
nil, 1, nil, 0, 0));
|
||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
||||
nil, 0, nil, 1, 1));
|
||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
||||
(char*)field, Ipcfieldmax+1, nil, 0, 0));
|
||||
}
|
||||
|
||||
void
|
||||
ipc_response_codec_and_drain(struct ct *t)
|
||||
{
|
||||
unsigned char first[Ipcmaxresp];
|
||||
static const unsigned char second[] = { 0, 2, 0, 'o', 'k' };
|
||||
unsigned char field[Ipcfieldmax];
|
||||
char commit[8], preedit[8];
|
||||
Ipcresp resp;
|
||||
int fd[2], n;
|
||||
|
||||
memset(field, 'x', sizeof field);
|
||||
n = ipcpackresp(first, sizeof first, 1,
|
||||
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
||||
if(!CT_EQ_INT(t, Ipcmaxresp, n))
|
||||
return;
|
||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||
CT_ERRORF(t, "socketpair failed");
|
||||
return;
|
||||
}
|
||||
if(!CT_EQ_INT(t, 0, ipcsend(fd[0], first, n)))
|
||||
goto cleanup;
|
||||
if(!CT_EQ_INT(t, 0, ipcsend(fd[0], second, sizeof second)))
|
||||
goto cleanup;
|
||||
if(!CT_EQ_INT(t, 0, shutdown(fd[0], SHUT_WR)))
|
||||
goto cleanup;
|
||||
if(!CT_EQ_INT(t, 0,
|
||||
ipcreadresp(fd[1], 1, commit, sizeof commit,
|
||||
preedit, sizeof preedit, &resp)))
|
||||
goto cleanup;
|
||||
CT_EQ_INT(t, 1, resp.eaten);
|
||||
CT_EQ_SIZE(t, Ipcfieldmax, resp.ncommit);
|
||||
CT_EQ_SIZE(t, Ipcfieldmax, resp.npreedit);
|
||||
CT_EQ_STR(t, "xxxxxxx", commit);
|
||||
CT_EQ_STR(t, "xxxxxxx", preedit);
|
||||
if(!CT_EQ_INT(t, 0,
|
||||
ipcreadresp(fd[1], 0, commit, sizeof commit,
|
||||
preedit, sizeof preedit, &resp)))
|
||||
goto cleanup;
|
||||
CT_EQ_INT(t, 0, resp.eaten);
|
||||
CT_EQ_STR(t, "ok", commit);
|
||||
CT_EQ_STR(t, "", preedit);
|
||||
cleanup:
|
||||
close(fd[0]);
|
||||
close(fd[1]);
|
||||
}
|
||||
|
||||
void
|
||||
ipc_truncated_frame(struct ct *t)
|
||||
{
|
||||
unsigned char b = 1, req[Ipcreqsz];
|
||||
int fd[2];
|
||||
|
||||
if(pipe(fd) < 0){
|
||||
CT_ERRORF(t, "pipe failed");
|
||||
return;
|
||||
}
|
||||
if(!CT_EQ_INT(t, 1, write(fd[1], &b, 1))){
|
||||
close(fd[0]);
|
||||
close(fd[1]);
|
||||
return;
|
||||
}
|
||||
close(fd[1]);
|
||||
CT_EQ_INT(t, -1, ipcreadn(fd[0], req, sizeof req));
|
||||
close(fd[0]);
|
||||
}
|
||||
|
||||
void
|
||||
ipc_fragmented_request(struct ct *t)
|
||||
{
|
||||
unsigned char req[Ipcreqsz];
|
||||
Childres res;
|
||||
Keyreq got;
|
||||
pid_t pid;
|
||||
int data[2], result[2], status;
|
||||
|
||||
pid = -1;
|
||||
data[0] = data[1] = result[0] = result[1] = -1;
|
||||
ipcpackreq(req, 1, Mctrl|Mshift, 0x1f600);
|
||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, data) < 0){
|
||||
CT_ERRORF(t, "data socketpair failed");
|
||||
return;
|
||||
}
|
||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, result) < 0){
|
||||
CT_ERRORF(t, "result socketpair failed");
|
||||
goto cleanup;
|
||||
}
|
||||
pid = fork();
|
||||
if(pid < 0){
|
||||
CT_ERRORF(t, "fork failed");
|
||||
goto cleanup;
|
||||
}
|
||||
if(pid == 0){
|
||||
close(data[0]);
|
||||
close(result[0]);
|
||||
memset(&res, 0, sizeof res);
|
||||
res.rc = srvreadkey(data[1], &got);
|
||||
if(res.rc == 0){
|
||||
res.want = got.want;
|
||||
res.mod = got.mod;
|
||||
res.key = got.ks;
|
||||
}
|
||||
_exit(ipcsend(result[1], &res, sizeof res) < 0 ? 1 : 0);
|
||||
}
|
||||
close(result[1]);
|
||||
result[1] = -1;
|
||||
if(ipcsend(data[0], req, Ipcreqsz/2) < 0){
|
||||
CT_ERRORF(t, "first request fragment failed");
|
||||
goto cleanup;
|
||||
}
|
||||
if(waitqueueempty(data[1], Childtimeout) < 0){
|
||||
CT_ERRORF(t, "child did not consume first request fragment");
|
||||
goto cleanup;
|
||||
}
|
||||
if(ipcsend(data[0], req+Ipcreqsz/2, Ipcreqsz-Ipcreqsz/2) < 0){
|
||||
CT_ERRORF(t, "second request fragment failed");
|
||||
goto cleanup;
|
||||
}
|
||||
if(readdeadline(result[0], &res, sizeof res, Childtimeout) < 0){
|
||||
CT_ERRORF(t, "timed out waiting for decoded request");
|
||||
goto cleanup;
|
||||
}
|
||||
CT_EQ_INT(t, 0, res.rc);
|
||||
CT_EQ_INT(t, 1, res.want);
|
||||
CT_EQ_UINT(t, Mctrl|Mshift, res.mod);
|
||||
CT_EQ_UINT(t, 0x1f600, res.key);
|
||||
cleanup:
|
||||
if(data[0] >= 0)
|
||||
close(data[0]);
|
||||
if(data[1] >= 0)
|
||||
close(data[1]);
|
||||
if(result[0] >= 0)
|
||||
close(result[0]);
|
||||
if(result[1] >= 0)
|
||||
close(result[1]);
|
||||
if(pid > 0){
|
||||
if(reapchild(pid, &status, Childtimeout) < 0)
|
||||
CT_ERRORF(t, "could not reap request reader");
|
||||
else if(!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
||||
CT_ERRORF(t, "request reader failed");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ipc_closed_peer(struct ct *t)
|
||||
{
|
||||
unsigned char b;
|
||||
pid_t pid;
|
||||
int data[2], syncfd[2], status;
|
||||
|
||||
pid = -1;
|
||||
data[0] = data[1] = syncfd[0] = syncfd[1] = -1;
|
||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, data) < 0){
|
||||
CT_ERRORF(t, "data socketpair failed");
|
||||
return;
|
||||
}
|
||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, syncfd) < 0){
|
||||
CT_ERRORF(t, "synchronization socketpair failed");
|
||||
goto cleanup;
|
||||
}
|
||||
pid = fork();
|
||||
if(pid < 0){
|
||||
CT_ERRORF(t, "fork failed");
|
||||
goto cleanup;
|
||||
}
|
||||
if(pid == 0){
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
close(data[1]);
|
||||
close(syncfd[0]);
|
||||
if(ipcsend(syncfd[1], "r", 1) < 0 ||
|
||||
readdeadline(syncfd[1], &b, 1, Childtimeout) < 0)
|
||||
_exit(2);
|
||||
_exit(ipcsend(data[0], "x", 1) < 0 ? 0 : 1);
|
||||
}
|
||||
close(data[0]);
|
||||
data[0] = -1;
|
||||
close(data[1]);
|
||||
data[1] = -1;
|
||||
close(syncfd[1]);
|
||||
syncfd[1] = -1;
|
||||
if(readdeadline(syncfd[0], &b, 1, Childtimeout) < 0 ||
|
||||
ipcsend(syncfd[0], "g", 1) < 0)
|
||||
CT_ERRORF(t, "child synchronization failed");
|
||||
cleanup:
|
||||
if(data[0] >= 0)
|
||||
close(data[0]);
|
||||
if(data[1] >= 0)
|
||||
close(data[1]);
|
||||
if(syncfd[0] >= 0)
|
||||
close(syncfd[0]);
|
||||
if(syncfd[1] >= 0)
|
||||
close(syncfd[1]);
|
||||
if(pid > 0){
|
||||
if(reapchild(pid, &status, Childtimeout) < 0)
|
||||
CT_ERRORF(t, "could not reap closed-peer writer");
|
||||
else if(!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
||||
CT_ERRORF(t, "write to closed peer did not return -1");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user