strengthen core unit tests
Add table-driven dictionary miss and language-switch state coverage, regress stale candidates after backspace, and clear them before dictionary refresh. Keep IPC and runtime-created file checks outside the unit runner.
This commit is contained in:
@@ -29,8 +29,8 @@ mirrors must provide the same sibling; initialize it after cloning with
|
|||||||
|
|
||||||
The suite covers the fixed-size UTF-8 string, hash table, trie fixtures,
|
The suite covers the fixed-size UTF-8 string, hash table, trie fixtures,
|
||||||
Japanese/Hangul/Telex state transitions, dictionary candidates, engine
|
Japanese/Hangul/Telex state transitions, dictionary candidates, engine
|
||||||
selection state, and the byte-stream IPC path. Tests use explicit case tables,
|
selection state, and production map loading. Tests use explicit case tables,
|
||||||
boundary values, and small regressions for repaired core contracts. GUI
|
boundary values, and small regressions for repaired core contracts. IPC and GUI
|
||||||
protocol stacks are outside this suite and require separate integration checks.
|
protocol stacks are outside this suite and require separate integration checks.
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|||||||
3
strans.c
3
strans.c
@@ -86,11 +86,10 @@ dictqmap(Im *im)
|
|||||||
{
|
{
|
||||||
Str dict;
|
Str dict;
|
||||||
|
|
||||||
if(!mapget(im->l->map, &im->pre, &dict)){
|
|
||||||
clearkouho();
|
clearkouho();
|
||||||
show();
|
show();
|
||||||
|
if(!mapget(im->l->map, &im->pre, &dict))
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
dictsend(im, &dict);
|
dictsend(im, &dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ LIBS = -lthread -lbio
|
|||||||
|
|
||||||
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
|
||||||
TESTOBJ = $(TESTSRC:.c=.o)
|
TESTOBJ = $(TESTSRC:.c=.o)
|
||||||
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c srv.c
|
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c
|
||||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
||||||
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
ka か
|
ka か
|
||||||
sa さ
|
sa さ
|
||||||
|
n ん
|
||||||
|
na な
|
||||||
|
|||||||
@@ -51,3 +51,38 @@ cleanup:
|
|||||||
hmapfree(lang->dict);
|
hmapfree(lang->dict);
|
||||||
lang->dict = saved;
|
lang->dict = saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
dictionary_misses_clear_result(struct ct *t)
|
||||||
|
{
|
||||||
|
static const struct {
|
||||||
|
char *name;
|
||||||
|
char *key;
|
||||||
|
char *pre;
|
||||||
|
} cases[] = {
|
||||||
|
{ "empty key", "", "empty-preedit" },
|
||||||
|
{ "missing key", "missing", "missing-preedit" },
|
||||||
|
};
|
||||||
|
Dictreq req;
|
||||||
|
Dictres res;
|
||||||
|
Hmap *saved;
|
||||||
|
Lang *lang;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
lang = getlang(LangJP);
|
||||||
|
saved = lang->dict;
|
||||||
|
lang->dict = hmapalloc(1);
|
||||||
|
for(i = 0; i < nelem(cases); i++){
|
||||||
|
memset(&res, 0xa5, sizeof res);
|
||||||
|
req.key = mkstr(cases[i].key);
|
||||||
|
req.pre = mkstr(cases[i].pre);
|
||||||
|
req.lang = LangJP;
|
||||||
|
dictlookup(&req, &res);
|
||||||
|
if(res.nkouho != 0)
|
||||||
|
CT_ERRORF(t, "%s: want 0 candidates, got %d",
|
||||||
|
cases[i].name, res.nkouho);
|
||||||
|
checkstr(t, cases[i].name, cases[i].pre, &res.key);
|
||||||
|
}
|
||||||
|
hmapfree(lang->dict);
|
||||||
|
lang->dict = saved;
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,6 +54,32 @@ engine_clears_candidates(struct ct *t)
|
|||||||
CT_EQ_INT(t, -1, im.sel);
|
CT_EQ_INT(t, -1, im.sel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_backspace_clears_candidates(struct ct *t)
|
||||||
|
{
|
||||||
|
Lang *lang;
|
||||||
|
Str com, shown;
|
||||||
|
|
||||||
|
lang = getlang(LangJP);
|
||||||
|
init();
|
||||||
|
im.l = lang;
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('n', 0, &com));
|
||||||
|
CT_CHECK(t, keystroke('a', 0, &com));
|
||||||
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, "before backspace", "な", &shown);
|
||||||
|
im.kouho[0] = mkstr("stale-candidate");
|
||||||
|
im.nkouho = 1;
|
||||||
|
im.sel = 0;
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
checkstr(t, "backspace commit", "", &com);
|
||||||
|
checkstr(t, "raw preedit after backspace", "n", &im.pre);
|
||||||
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, "shown preedit after backspace", "ん", &shown);
|
||||||
|
CT_EQ_INT(t, 0, im.nkouho);
|
||||||
|
CT_EQ_INT(t, -1, im.sel);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
engine_selects_visible_candidate(struct ct *t)
|
engine_selects_visible_candidate(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -156,3 +182,76 @@ engine_telex_history_bound(struct ct *t)
|
|||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
CT_EQ_INT(t, 0, im.raw.n);
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_language_switch_state(struct ct *t)
|
||||||
|
{
|
||||||
|
static const struct {
|
||||||
|
char *name;
|
||||||
|
Rune key;
|
||||||
|
int mod;
|
||||||
|
int lang;
|
||||||
|
char *pre;
|
||||||
|
char *raw;
|
||||||
|
char *commit;
|
||||||
|
int stale;
|
||||||
|
} steps[] = {
|
||||||
|
{ "select Vietnamese", 'v', Mctrl, LangVI, "", "", "", 0 },
|
||||||
|
{ "Vietnamese a", 'a', 0, LangVI, "a", "a", "", 0 },
|
||||||
|
{ "Vietnamese tone", 's', 0, LangVI, "as", "as", "", 0 },
|
||||||
|
{ "switch to Korean", 's', Mctrl, LangKO, "", "", "", 1 },
|
||||||
|
{ "Korean initial", 'r', 0, LangKO, "ㄱ", "", "", 0 },
|
||||||
|
{ "Korean syllable", 'k', 0, LangKO, "가", "", "", 0 },
|
||||||
|
{ "switch to Vietnamese", 'v', Mctrl, LangVI, "", "", "", 0 },
|
||||||
|
{ "Vietnamese a again", 'a', 0, LangVI, "a", "a", "", 0 },
|
||||||
|
{ "Vietnamese tone again", 's', 0, LangVI, "as", "as", "", 0 },
|
||||||
|
{ "commit Vietnamese", Kret, 0, LangVI, "", "", "á", 0 },
|
||||||
|
};
|
||||||
|
char where[80];
|
||||||
|
Lang *vi;
|
||||||
|
Trie *saved;
|
||||||
|
Str com;
|
||||||
|
int eaten, i;
|
||||||
|
|
||||||
|
vi = getlang(LangVI);
|
||||||
|
if(!CT_CHECK(t, vi != nil))
|
||||||
|
return;
|
||||||
|
saved = vi->map;
|
||||||
|
vi->map = testvi.map;
|
||||||
|
init();
|
||||||
|
for(i = 0; i < nelem(steps); i++){
|
||||||
|
if(steps[i].stale){
|
||||||
|
im.kouho[0] = mkstr("stale");
|
||||||
|
im.nkouho = 1;
|
||||||
|
im.sel = 0;
|
||||||
|
}
|
||||||
|
sclear(&com);
|
||||||
|
eaten = keystroke(steps[i].key, steps[i].mod, &com);
|
||||||
|
if(eaten != 1){
|
||||||
|
CT_ERRORF(t, "%s: want eaten 1, got %d",
|
||||||
|
steps[i].name, eaten);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(im.l == nil || im.l->lang != steps[i].lang){
|
||||||
|
CT_ERRORF(t, "%s: want language %d, got %d",
|
||||||
|
steps[i].name, steps[i].lang,
|
||||||
|
im.l == nil ? -1 : im.l->lang);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
snprint(where, sizeof where, "%s preedit", steps[i].name);
|
||||||
|
if(!checkstr(t, where, steps[i].pre, &im.pre))
|
||||||
|
break;
|
||||||
|
snprint(where, sizeof where, "%s raw", steps[i].name);
|
||||||
|
if(!checkstr(t, where, steps[i].raw, &im.raw))
|
||||||
|
break;
|
||||||
|
snprint(where, sizeof where, "%s commit", steps[i].name);
|
||||||
|
if(!checkstr(t, where, steps[i].commit, &com))
|
||||||
|
break;
|
||||||
|
if(im.nkouho != 0 || im.sel != -1){
|
||||||
|
CT_ERRORF(t, "%s: want candidates 0 and selection -1, got %d and %d",
|
||||||
|
steps[i].name, im.nkouho, im.sel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vi->map = saved;
|
||||||
|
}
|
||||||
|
|||||||
411
tests/ipc_test.c
411
tests/ipc_test.c
@@ -1,411 +0,0 @@
|
|||||||
#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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,15 +28,12 @@ void vietnamese_transitions(struct ct*);
|
|||||||
void vietnamese_backspace(struct ct*);
|
void vietnamese_backspace(struct ct*);
|
||||||
void vietnamese_state_lifetime(struct ct*);
|
void vietnamese_state_lifetime(struct ct*);
|
||||||
void engine_clears_candidates(struct ct*);
|
void engine_clears_candidates(struct ct*);
|
||||||
|
void engine_backspace_clears_candidates(struct ct*);
|
||||||
void engine_selects_visible_candidate(struct ct*);
|
void engine_selects_visible_candidate(struct ct*);
|
||||||
void engine_commit_contract(struct ct*);
|
void engine_commit_contract(struct ct*);
|
||||||
|
void engine_language_switch_state(struct ct*);
|
||||||
void engine_telex_history_bound(struct ct*);
|
void engine_telex_history_bound(struct ct*);
|
||||||
void dictionary_candidates(struct ct*);
|
void dictionary_candidates(struct ct*);
|
||||||
void ipc_request_codec(struct ct*);
|
void dictionary_misses_clear_result(struct ct*);
|
||||||
void ipc_response_pack_boundaries(struct ct*);
|
|
||||||
void ipc_response_codec_and_drain(struct ct*);
|
|
||||||
void ipc_truncated_frame(struct ct*);
|
|
||||||
void ipc_fragmented_request(struct ct*);
|
|
||||||
void ipc_closed_peer(struct ct*);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,37 +1,5 @@
|
|||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
static void
|
|
||||||
checkcrlf(struct ct *t)
|
|
||||||
{
|
|
||||||
static char data[] = "crlf\tvalue\r\n";
|
|
||||||
char path[80], *v;
|
|
||||||
Trie *trie;
|
|
||||||
int fd, n;
|
|
||||||
|
|
||||||
snprint(path, sizeof path, "/tmp/strans-crlf-%d.map", getpid());
|
|
||||||
remove(path);
|
|
||||||
fd = create(path, OWRITE, 0600);
|
|
||||||
if(fd < 0){
|
|
||||||
CT_ERRORF(t, "cannot create CRLF fixture");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(write(fd, data, sizeof data - 1) != (long)(sizeof data - 1)){
|
|
||||||
CT_ERRORF(t, "cannot write CRLF fixture");
|
|
||||||
close(fd);
|
|
||||||
remove(path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
close(fd);
|
|
||||||
trie = trieopen(path);
|
|
||||||
remove(path);
|
|
||||||
v = trieget(trie, "crlf", 4, &n);
|
|
||||||
if(CT_CHECK(t, v != nil)){
|
|
||||||
CT_EQ_INT(t, 5, n);
|
|
||||||
CT_EQ_MEM(t, "value", v, n);
|
|
||||||
}
|
|
||||||
trieclose(trie);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
trie_exact_prefix_and_duplicate(struct ct *t)
|
trie_exact_prefix_and_duplicate(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -64,7 +32,6 @@ trie_exact_prefix_and_duplicate(struct ct *t)
|
|||||||
CT_EQ_PTR(t, nil, trieget(trie, "missing", 7, &n));
|
CT_EQ_PTR(t, nil, trieget(trie, "missing", 7, &n));
|
||||||
cleanup:
|
cleanup:
|
||||||
trieclose(trie);
|
trieclose(trie);
|
||||||
checkcrlf(t);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -75,16 +75,13 @@ static const struct ct_test tests[] = {
|
|||||||
{ "telex/backspace", vietnamese_backspace },
|
{ "telex/backspace", vietnamese_backspace },
|
||||||
{ "telex/state-lifetime", vietnamese_state_lifetime },
|
{ "telex/state-lifetime", vietnamese_state_lifetime },
|
||||||
{ "engine/clears-candidates", engine_clears_candidates },
|
{ "engine/clears-candidates", engine_clears_candidates },
|
||||||
|
{ "engine/backspace-clears-candidates", engine_backspace_clears_candidates },
|
||||||
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
|
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
|
||||||
{ "engine/commit-contract", engine_commit_contract },
|
{ "engine/commit-contract", engine_commit_contract },
|
||||||
|
{ "engine/language-switch-state", engine_language_switch_state },
|
||||||
{ "engine/telex-history-bound", engine_telex_history_bound },
|
{ "engine/telex-history-bound", engine_telex_history_bound },
|
||||||
{ "dict/candidates", dictionary_candidates },
|
{ "dict/candidates", dictionary_candidates },
|
||||||
{ "ipc/request-codec", ipc_request_codec },
|
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
||||||
{ "ipc/response-pack-boundaries", ipc_response_pack_boundaries },
|
|
||||||
{ "ipc/response-codec-drain", ipc_response_codec_and_drain },
|
|
||||||
{ "ipc/truncated-frame", ipc_truncated_frame },
|
|
||||||
{ "ipc/fragmented-request", ipc_fragmented_request },
|
|
||||||
{ "ipc/closed-peer", ipc_closed_peer },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
Reference in New Issue
Block a user