diff --git a/tests/Makefile b/tests/Makefile index 568651e..50e4525 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -10,7 +10,7 @@ LIBS = -lthread -lbio $(FT_LIBS) $(IBUS_LIBS) PROG = unit_test 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 \ - 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) PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \ font.c @@ -29,6 +29,7 @@ $(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h ../cutest/cutest.h engine_test.o: ../strans.c ibus_test.o: CFLAGS += $(IBUS_CFLAGS) ibus_test.o: ../ibus.c +server_test.o: ../srv.c unit_font.o: CFLAGS += $(FT_CFLAGS) unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h diff --git a/tests/engine_test.c b/tests/engine_test.c index 508071a..8148eb0 100644 --- a/tests/engine_test.c +++ b/tests/engine_test.c @@ -9,6 +9,31 @@ Str shownpre(Im*); 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 vietnamese_state_lifetime(struct ct *t) { diff --git a/tests/server_test.c b/tests/server_test.c new file mode 100644 index 0000000..bc55b9f --- /dev/null +++ b/tests/server_test.c @@ -0,0 +1,370 @@ +#include +#include +#include +#include + +#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) + ; +} diff --git a/tests/test.h b/tests/test.h index 9aeee02..8267bfc 100644 --- a/tests/test.h +++ b/tests/test.h @@ -70,4 +70,5 @@ void ipc_response_empty_and_preedit(struct ct*); void ipc_response_max_and_drain(struct ct*); void ipc_response_fragmented_and_truncated(struct ct*); void ipc_broken_peer_send(struct ct*); +void server_connection_ownership(struct ct*); void ibus_context_lifecycle(struct ct*); diff --git a/tests/unit_test.c b/tests/unit_test.c index ecae4df..636f2ed 100644 --- a/tests/unit_test.c +++ b/tests/unit_test.c @@ -124,6 +124,7 @@ static const struct ct_test tests[] = { { "ipc/response-max-drain", ipc_response_max_and_drain }, { "ipc/response-fragmented-truncated", ipc_response_fragmented_and_truncated }, { "ipc/broken-peer-send", ipc_broken_peer_send }, + { "server/connection-ownership", server_connection_ownership }, { "ibus/context-lifecycle", ibus_context_lifecycle }, };