Files
strans/tests/server_test.c
Hojun-Cho 88ad5f7630 engine: the one capability is a flag
Cclientpreedit was a one-bit mask that every producer set as "want ?
Cclientpreedit : 0" and every consumer masked out again; nothing else
was ever going to join it.  Keyreq carries clientpre, an int that says
whether the client draws the preedit, and the engine and the XIM
context keep it under that name.
2026-08-17 10:57:15 +09:00

549 lines
13 KiB
C

#include <errno.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#include "srv.c"
#include "test.h"
#undef recv
typedef struct Serverfix Serverfix;
typedef struct Testclient Testclient;
/* A held pump plus a private client-slot channel of the wanted depth. */
struct Serverfix
{
Pump pump;
Channel *oldclientc;
};
struct Testclient
{
int fd;
int peer;
dev_t dev;
ino_t ino;
Channel *done;
};
static void
serverbegin(Serverfix *f, int nclients)
{
Drawcmd dc;
uchar token;
f->oldclientc = clientc;
while(channbrecv(drawc, &dc) > 0)
;
clientc = chancreate(sizeof token, nclients);
testengineinit(LangJP);
pumpstart(&f->pump, 0);
pumphold(&f->pump, Pumpall);
}
static void
serverend(Serverfix *f)
{
Drawcmd dc;
pumpstop(&f->pump);
chanfree(clientc);
clientc = f->oldclientc;
while(channbrecv(drawc, &dc) > 0)
;
}
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
nextrequestcap(struct ct *t, Pump *p, int op, int cap)
{
Keyreq req;
memset(&req, 0, sizeof req);
chanrecv(p->trace, &req);
CT_EQ_INT(t, op, req.op);
CT_EQ_INT(t, cap, req.clientpre);
return req;
}
static Keyreq
nextrequest(struct ct *t, Pump *p, int op)
{
return nextrequestcap(t, p, op, 1);
}
static void
allowrequest(Pump *p)
{
uchar token;
token = 0;
chansend(p->go, &token);
}
static int
readreply(struct ct *t, Testclient *client, int want, char *wantcommit,
char *preedit)
{
char commit[Ipcfieldmax+1];
Ipcresp resp;
if(ipcreadresp(client->peer, want, commit, preedit, &resp) < 0)
return CT_ERRORF(t, "response read failed: %s", strerror(errno));
CT_EQ_STR(t, wantcommit, commit);
return resp.eaten;
}
static int
sendframe(struct ct *t, Testclient *client, uchar *frame, int n, int fragment)
{
int i, m;
for(i = 0; i < n; i += m){
m = fragment && n-i > 1 ? 1 : n-i;
if(ipcsend(client->peer, frame+i, m) < 0)
return CT_ERRORF(t, "frame send failed: %s", strerror(errno));
}
return 1;
}
static void
checknoreply(struct ct *t, Testclient *client)
{
uchar byte;
ssize_t n;
errno = 0;
n = recv(client->peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
CT_EQ_INT(t, -1, n);
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
}
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, Pump *p, Testclient *client, void *owner)
{
Keyreq req;
uchar token;
close(client->peer);
client->peer = -1;
req = nextrequest(t, p, Keyrelease);
if(owner != nil)
CT_EQ_PTR(t, owner, req.owner);
CT_CHECK(t, channbrecv(client->done, &token) <= 0);
allowrequest(p);
waitclient(t, client);
}
void
server_connection_ownership(struct ct *t)
{
Serverfix f;
Testclient a, b, c;
Keyreq req;
Str shown;
char preedit[Ipcfieldmax+1];
void *aowner, *bowner, *cowner;
uchar byte, token;
ssize_t n;
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;
serverbegin(&f, 3);
if(!startclient(t, &a) || !startclient(t, &b))
goto cleanup;
if(!sendkey(t, &a, 1, 0, 'k'))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
aowner = req.owner;
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "k", preedit);
if(!sendkey(t, &a, 1, 0, 'a'))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
CT_EQ_PTR(t, aowner, req.owner);
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_PTR(t, aowner, testengineowner());
if(!sendkey(t, &b, 1, 0, 'n'))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
bowner = req.owner;
CT_CHECK(t, bowner != aowner);
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendreset(t, &a, 1))
goto cleanup;
req = nextrequest(t, &f.pump, Keyreset);
CT_EQ_PTR(t, aowner, req.owner);
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendkey(t, &b, 1, 0, 0))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
allowrequest(&f.pump);
CT_CHECK(t, !readreply(t, &b, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
disconnectclient(t, &f.pump, &a, aowner);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendkey(t, &b, 1, 0, 'y'))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
if(!sendkey(t, &b, 1, 0, 'a'))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
CT_EQ_STR(t, "にゃ", preedit);
if(!sendreset(t, &b, 1))
goto cleanup;
req = nextrequest(t, &f.pump, Keyreset);
CT_EQ_PTR(t, bowner, req.owner);
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &b, 1, "にゃ", preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendkey(t, &b, 0, 0, 'k'))
goto cleanup;
req = nextrequestcap(t, &f.pump, Keypress, 0);
CT_EQ_PTR(t, bowner, req.owner);
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &b, 0, "", preedit));
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, &f.pump, Keypress);
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
disconnectclient(t, &f.pump, &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, &f.pump, Keypress);
cowner = req.owner;
close(c.peer);
c.peer = -1;
allowrequest(&f.pump);
req = nextrequest(t, &f.pump, Keyrelease);
CT_EQ_PTR(t, cowner, req.owner);
CT_EQ_PTR(t, cowner, testengineowner());
CT_CHECK(t, channbrecv(c.done, &token) <= 0);
allowrequest(&f.pump);
waitclient(t, &c);
CT_EQ_PTR(t, nil, testengineowner());
CT_CHECK(t, channbrecv(clientc, &token) <= 0);
cleanup:
if(a.peer >= 0)
disconnectclient(t, &f.pump, &a, aowner);
if(b.peer >= 0)
disconnectclient(t, &f.pump, &b, bowner);
if(c.peer >= 0)
disconnectclient(t, &f.pump, &c, cowner);
serverend(&f);
}
void
server_extension_stream(struct ct *t)
{
Serverfix f;
Testclient a, b;
Keyreq req;
uchar frame[Ipccaretsz];
char preedit[Ipcfieldmax+1];
void *aowner, *bowner;
memset(&a, 0, sizeof a);
memset(&b, 0, sizeof b);
a.fd = a.peer = b.fd = b.peer = -1;
aowner = bowner = nil;
serverbegin(&f, 2);
if(!startclient(t, &a))
goto cleanup;
/* Capability and caret frames may be split at any byte boundary. */
ipcpackcap(frame, 1);
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
goto cleanup;
req = nextrequest(t, &f.pump, Keycap);
aowner = req.owner;
CT_EQ_PTR(t, nil, testengineowner());
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
ipcpackcaret(frame, 1, -101, -7, 23);
if(!sendframe(t, &a, frame, Ipccaretsz, 1))
goto cleanup;
req = nextrequest(t, &f.pump, Keycaret);
CT_EQ_PTR(t, aowner, req.owner);
CT_EQ_INT(t, 1, req.caret.valid);
CT_EQ_INT(t, -101, req.caret.x);
CT_EQ_INT(t, -7, req.caret.y);
CT_EQ_INT(t, 23, req.caret.h);
CT_EQ_PTR(t, nil, testengineowner());
allowrequest(&f.pump);
checknoreply(t, &a);
ipcpackreq(frame, 1, 0, 'k');
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
CT_EQ_PTR(t, aowner, req.owner);
CT_EQ_INT(t, 1, req.caret.valid);
CT_EQ_INT(t, -101, req.caret.x);
CT_EQ_INT(t, -7, req.caret.y);
CT_EQ_INT(t, 23, req.caret.h);
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "k", preedit);
CT_EQ_PTR(t, aowner, testengineowner());
ipcpackcap(frame, 0);
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
goto cleanup;
req = nextrequestcap(t, &f.pump, Keycap, 0);
CT_EQ_PTR(t, aowner, req.owner);
CT_EQ_INT(t, 1, req.caret.valid);
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &a, 0, "", preedit));
/* Reset is still the old six-byte frame and does not discard caret. */
ipcpackreset(frame, 0);
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
goto cleanup;
req = nextrequestcap(t, &f.pump, Keyreset, 0);
CT_EQ_PTR(t, aowner, req.owner);
CT_EQ_INT(t, 1, req.caret.valid);
CT_EQ_INT(t, -101, req.caret.x);
CT_EQ_INT(t, -7, req.caret.y);
CT_EQ_INT(t, 23, req.caret.h);
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &a, 0, "k", preedit));
ipcpackcap(frame, 1);
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
goto cleanup;
req = nextrequest(t, &f.pump, Keycap);
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
ipcpackreq(frame, 1, 0, 'n');
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
CT_EQ_INT(t, 1, req.caret.valid);
CT_EQ_INT(t, -101, req.caret.x);
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
disconnectclient(t, &f.pump, &a, aowner);
/* Disconnect drops state; a legacy client gets the historical defaults. */
if(!startclient(t, &b) || !sendkey(t, &b, 1, 0, 'k'))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
bowner = req.owner;
CT_EQ_INT(t, 0, req.caret.valid);
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &b, 1, "", preedit));
CT_EQ_STR(t, "k", preedit);
disconnectclient(t, &f.pump, &b, bowner);
cleanup:
if(a.peer >= 0)
disconnectclient(t, &f.pump, &a, aowner);
if(b.peer >= 0)
disconnectclient(t, &f.pump, &b, bowner);
serverend(&f);
}
void
server_rejects_unknown_extension(struct ct *t)
{
Serverfix f;
Testclient badver, badop, good;
Keyreq req;
uchar frame[Ipccaretsz];
char preedit[Ipcfieldmax+1];
void *owner;
memset(&badver, 0, sizeof badver);
memset(&badop, 0, sizeof badop);
memset(&good, 0, sizeof good);
badver.fd = badver.peer = badop.fd = badop.peer = -1;
good.fd = good.peer = -1;
owner = nil;
serverbegin(&f, 3);
if(!startclient(t, &badver))
goto cleanup;
ipcpackcaret(frame, 1, 3, 4, 5);
frame[1]++;
if(!sendframe(t, &badver, frame, sizeof frame, 0))
goto cleanup;
req = nextrequest(t, &f.pump, Keyrelease);
allowrequest(&f.pump);
waitclient(t, &badver);
close(badver.peer);
badver.peer = -1;
if(!startclient(t, &badop))
goto cleanup;
ipcpackcaret(frame, 1, 3, 4, 5);
frame[2]++;
if(!sendframe(t, &badop, frame, sizeof frame, 1))
goto cleanup;
req = nextrequest(t, &f.pump, Keyrelease);
allowrequest(&f.pump);
waitclient(t, &badop);
close(badop.peer);
badop.peer = -1;
/* A malformed peer must not disturb another client connection. */
if(!startclient(t, &good) || !sendkey(t, &good, 1, 0, 'k'))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
owner = req.owner;
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &good, 1, "", preedit));
CT_EQ_STR(t, "k", preedit);
disconnectclient(t, &f.pump, &good, owner);
cleanup:
if(badver.peer >= 0)
disconnectclient(t, &f.pump, &badver, nil);
if(badop.peer >= 0)
disconnectclient(t, &f.pump, &badop, nil);
if(good.peer >= 0)
disconnectclient(t, &f.pump, &good, owner);
serverend(&f);
}