tests: cover ownership across ipc connections

This commit is contained in:
2026-08-13 23:07:25 +09:00
parent 21a94d447c
commit bb2a970db1
5 changed files with 399 additions and 1 deletions

370
tests/server_test.c Normal file
View File

@@ -0,0 +1,370 @@
#include <errno.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#include "../srv.c"
#include "../cutest/cutest.h"
void testengineinit(int);
void testenginehandle(Keyreq*);
void* testengineowner(void);
void testenginepreedit(Str*);
#undef recv
typedef struct Enginegate Enginegate;
typedef struct Testclient Testclient;
struct Enginegate
{
Channel *seen;
Channel *go;
Channel *stop;
Channel *done;
};
struct Testclient
{
int fd;
int peer;
dev_t dev;
ino_t ino;
Channel *done;
};
static void
enginegate(void *arg)
{
Enginegate *g;
Keyreq req;
uchar token;
Alt alts[] = {
{keyc, &req, CHANRCV, nil},
{nil, &token, CHANRCV, nil},
{nil, nil, CHANEND, nil},
};
g = arg;
alts[1].c = g->stop;
for(;;)
switch(alt(alts)){
case 0:
chansend(g->seen, &req);
chanrecv(g->go, &token);
testenginehandle(&req);
break;
case 1:
chansend(g->done, &token);
return;
}
}
static void
clientproc(void *arg)
{
Testclient *client;
uchar token;
client = arg;
clientthread((void*)(uintptr)client->fd);
token = 0;
chansend(client->done, &token);
}
static int
startclient(struct ct *t, Testclient *client)
{
struct stat st;
int fd[2];
uchar token;
memset(client, 0, sizeof *client);
client->fd = -1;
client->peer = -1;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
return CT_ERRORF(t, "socketpair failed: %s", strerror(errno));
client->fd = fd[0];
client->peer = fd[1];
if(fstat(client->fd, &st) < 0){
close(client->fd);
close(client->peer);
client->fd = -1;
client->peer = -1;
return CT_ERRORF(t, "socket stat failed: %s", strerror(errno));
}
client->dev = st.st_dev;
client->ino = st.st_ino;
client->done = chancreate(sizeof token, 0);
token = 0;
if(channbsend(clientc, &token) <= 0){
close(client->fd);
close(client->peer);
chanfree(client->done);
client->fd = -1;
client->peer = -1;
client->done = nil;
return CT_ERRORF(t, "client slot unavailable");
}
if(proccreate(clientproc, client, 8192) < 0){
chanrecv(clientc, &token);
close(client->fd);
close(client->peer);
chanfree(client->done);
client->fd = -1;
client->peer = -1;
client->done = nil;
return CT_ERRORF(t, "client worker creation failed");
}
return 1;
}
static int
sendkey(struct ct *t, Testclient *client, int want, u32int mod, u32int key)
{
uchar req[Ipcreqsz];
ipcpackreq(req, want, mod, key);
if(ipcsend(client->peer, req, sizeof req) == 0)
return 1;
return CT_ERRORF(t, "request send failed: %s", strerror(errno));
}
static int
sendreset(struct ct *t, Testclient *client, int want)
{
uchar req[Ipcreqsz];
ipcpackreset(req, want);
if(ipcsend(client->peer, req, sizeof req) == 0)
return 1;
return CT_ERRORF(t, "reset send failed: %s", strerror(errno));
}
static Keyreq
nextrequest(struct ct *t, Enginegate *g, int op)
{
Keyreq req;
memset(&req, 0, sizeof req);
chanrecv(g->seen, &req);
CT_EQ_INT(t, op, req.op);
return req;
}
static void
allowrequest(Enginegate *g)
{
uchar token;
token = 0;
chansend(g->go, &token);
}
static int
readreply(struct ct *t, Testclient *client, int want, char *preedit, int npreedit)
{
char commit[Maxutf];
Ipcresp resp;
if(ipcreadresp(client->peer, want, commit, sizeof commit,
preedit, npreedit, &resp) < 0)
return CT_ERRORF(t, "response read failed: %s", strerror(errno));
CT_EQ_STR(t, "", commit);
return resp.eaten;
}
static void
waitclient(struct ct *t, Testclient *client)
{
struct stat st;
uchar token;
chanrecv(client->done, &token);
errno = 0;
if(fstat(client->fd, &st) == 0)
CT_CHECK(t, st.st_dev != client->dev || st.st_ino != client->ino);
else
CT_EQ_INT(t, EBADF, errno);
chanfree(client->done);
client->done = nil;
}
static void
disconnectclient(struct ct *t, Enginegate *g, Testclient *client, void *owner)
{
Keyreq req;
uchar token;
close(client->peer);
client->peer = -1;
req = nextrequest(t, g, Keyrelease);
if(owner != nil)
CT_EQ_PTR(t, owner, req.owner);
CT_CHECK(t, channbrecv(client->done, &token) <= 0);
allowrequest(g);
waitclient(t, client);
}
void
server_connection_ownership(struct ct *t)
{
Channel *oldclientc;
Enginegate gate;
Testclient a, b, c;
Keyreq req;
Drawcmd dc;
Str shown;
char preedit[Maxutf];
void *aowner, *bowner, *cowner;
uchar byte, token;
ssize_t n;
int gateactive;
memset(&gate, 0, sizeof gate);
memset(&a, 0, sizeof a);
memset(&b, 0, sizeof b);
memset(&c, 0, sizeof c);
a.fd = a.peer = b.fd = b.peer = c.fd = c.peer = -1;
aowner = bowner = cowner = nil;
oldclientc = clientc;
while(channbrecv(drawc, &dc) > 0)
;
clientc = chancreate(sizeof token, 3);
gate.seen = chancreate(sizeof(Keyreq), 0);
gate.go = chancreate(sizeof token, 0);
gate.stop = chancreate(sizeof token, 0);
gate.done = chancreate(sizeof token, 0);
testengineinit(LangJP);
gateactive = proccreate(enginegate, &gate, 8192) >= 0;
if(!CT_CHECK(t, gateactive))
goto cleanup;
if(!startclient(t, &a) || !startclient(t, &b))
goto cleanup;
if(!sendkey(t, &a, 1, 0, 'k'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
aowner = req.owner;
allowrequest(&gate);
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "k", preedit);
if(!sendkey(t, &a, 1, 0, 'a'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
CT_EQ_PTR(t, aowner, req.owner);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_PTR(t, aowner, testengineowner());
if(!sendkey(t, &b, 1, 0, 'n'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
bowner = req.owner;
CT_CHECK(t, bowner != aowner);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendreset(t, &a, 1))
goto cleanup;
req = nextrequest(t, &gate, Keyreset);
CT_EQ_PTR(t, aowner, req.owner);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendkey(t, &b, 1, 0, Kmodfirst))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
allowrequest(&gate);
CT_CHECK(t, !readreply(t, &b, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "", preedit);
disconnectclient(t, &gate, &a, aowner);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendkey(t, &b, 1, 0, 'y'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
if(!sendkey(t, &b, 1, 0, 'a'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "にゃ", preedit);
if(!sendreset(t, &b, 1))
goto cleanup;
req = nextrequest(t, &gate, Keyreset);
CT_EQ_PTR(t, bowner, req.owner);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendkey(t, &b, 0, 0, 'k'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
CT_EQ_PTR(t, bowner, req.owner);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &b, 0, nil, 0));
errno = 0;
n = recv(b.peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
CT_EQ_INT(t, -1, n);
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendkey(t, &b, 1, 0, 'a'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "", preedit);
disconnectclient(t, &gate, &b, bowner);
CT_EQ_PTR(t, nil, testengineowner());
testenginepreedit(&shown);
CT_EQ_INT(t, 0, shown.n);
if(!startclient(t, &c))
goto cleanup;
if(!sendkey(t, &c, 1, 0, 'k'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
cowner = req.owner;
close(c.peer);
c.peer = -1;
allowrequest(&gate);
req = nextrequest(t, &gate, Keyrelease);
CT_EQ_PTR(t, cowner, req.owner);
CT_EQ_PTR(t, cowner, testengineowner());
CT_CHECK(t, channbrecv(c.done, &token) <= 0);
allowrequest(&gate);
waitclient(t, &c);
CT_EQ_PTR(t, nil, testengineowner());
CT_CHECK(t, channbrecv(clientc, &token) <= 0);
cleanup:
if(a.peer >= 0)
disconnectclient(t, &gate, &a, aowner);
if(b.peer >= 0)
disconnectclient(t, &gate, &b, bowner);
if(c.peer >= 0)
disconnectclient(t, &gate, &c, cowner);
if(gateactive){
token = 0;
chansend(gate.stop, &token);
chanrecv(gate.done, &token);
}
chanfree(gate.seen);
chanfree(gate.go);
chanfree(gate.stop);
chanfree(gate.done);
chanfree(clientc);
clientc = oldclientc;
while(channbrecv(drawc, &dc) > 0)
;
}