tests: cover ownership across ipc connections
This commit is contained in:
@@ -10,7 +10,7 @@ LIBS = -lthread -lbio $(FT_LIBS) $(IBUS_LIBS)
|
|||||||
PROG = unit_test
|
PROG = unit_test
|
||||||
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
TESTSRC = unit_test.c 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
|
popup_test.c font_test.c ibus_test.c server_test.c
|
||||||
TESTOBJ = $(TESTSRC:.c=.o)
|
TESTOBJ = $(TESTSRC:.c=.o)
|
||||||
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
||||||
font.c
|
font.c
|
||||||
@@ -29,6 +29,7 @@ $(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
|
||||||
|
server_test.o: ../srv.c
|
||||||
unit_font.o: CFLAGS += $(FT_CFLAGS)
|
unit_font.o: CFLAGS += $(FT_CFLAGS)
|
||||||
|
|
||||||
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
||||||
|
|||||||
@@ -9,6 +9,31 @@ Str shownpre(Im*);
|
|||||||
|
|
||||||
static int typekeys(struct ct*, char*, Str*);
|
static int typekeys(struct ct*, char*, Str*);
|
||||||
|
|
||||||
|
void
|
||||||
|
testengineinit(int lang)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
im.l = getlang(lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testenginehandle(Keyreq *req)
|
||||||
|
{
|
||||||
|
imhandlekey(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
void*
|
||||||
|
testengineowner(void)
|
||||||
|
{
|
||||||
|
return activeowner;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testenginepreedit(Str *preedit)
|
||||||
|
{
|
||||||
|
impre(preedit);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
vietnamese_state_lifetime(struct ct *t)
|
vietnamese_state_lifetime(struct ct *t)
|
||||||
{
|
{
|
||||||
|
|||||||
370
tests/server_test.c
Normal file
370
tests/server_test.c
Normal 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)
|
||||||
|
;
|
||||||
|
}
|
||||||
@@ -70,4 +70,5 @@ void ipc_response_empty_and_preedit(struct ct*);
|
|||||||
void ipc_response_max_and_drain(struct ct*);
|
void ipc_response_max_and_drain(struct ct*);
|
||||||
void ipc_response_fragmented_and_truncated(struct ct*);
|
void ipc_response_fragmented_and_truncated(struct ct*);
|
||||||
void ipc_broken_peer_send(struct ct*);
|
void ipc_broken_peer_send(struct ct*);
|
||||||
|
void server_connection_ownership(struct ct*);
|
||||||
void ibus_context_lifecycle(struct ct*);
|
void ibus_context_lifecycle(struct ct*);
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ static const struct ct_test tests[] = {
|
|||||||
{ "ipc/response-max-drain", ipc_response_max_and_drain },
|
{ "ipc/response-max-drain", ipc_response_max_and_drain },
|
||||||
{ "ipc/response-fragmented-truncated", ipc_response_fragmented_and_truncated },
|
{ "ipc/response-fragmented-truncated", ipc_response_fragmented_and_truncated },
|
||||||
{ "ipc/broken-peer-send", ipc_broken_peer_send },
|
{ "ipc/broken-peer-send", ipc_broken_peer_send },
|
||||||
|
{ "server/connection-ownership", server_connection_ownership },
|
||||||
{ "ibus/context-lifecycle", ibus_context_lifecycle },
|
{ "ibus/context-lifecycle", ibus_context_lifecycle },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user