#include "xim/xim.c" #include "cutest/cutest.h" void testengineinit(int); void testenginehandle(Keyreq*); void* testengineowner(void); void testenginepreedit(Str*); typedef struct Ximfix Ximfix; struct Ximfix { Channel *oldreply; Channel *trace; Channel *stop; Channel *done; Channel *waiting; Channel *ack; int holdrelease; int pumpactive; }; typedef struct Freejob Freejob; struct Freejob { Ic *state; Channel *done; }; static void enginepump(void *arg) { Ximfix *f; Keyreq req; uchar token; Alt alts[] = { {keyc, &req, CHANRCV, nil}, {nil, &token, CHANRCV, nil}, {nil, nil, CHANEND, nil}, }; f = arg; alts[1].c = f->stop; for(;;) switch(alt(alts)){ case 0: chansend(f->trace, &req); if(f->holdrelease && req.op == Keyrelease){ token = 0; chansend(f->waiting, &token); chanrecv(f->ack, &token); } testenginehandle(&req); break; case 1: chansend(f->done, &token); return; } } static int ximbegin(struct ct *t, Ximfix *f, int lang) { Drawcmd dc; memset(f, 0, sizeof *f); while(channbrecv(drawc, &dc) > 0) ; testengineinit(lang); f->oldreply = replyc; replyc = chancreate(sizeof(Keyres), 0); f->trace = chancreate(sizeof(Keyreq), 16); f->stop = chancreate(sizeof(uchar), 0); f->done = chancreate(sizeof(uchar), 0); f->waiting = chancreate(sizeof(uchar), 0); f->ack = chancreate(sizeof(uchar), 0); f->pumpactive = threadcreate(enginepump, f, 8192) >= 0; return CT_CHECK(t, replyc != nil && f->trace != nil && f->stop != nil && f->done != nil && f->waiting != nil && f->ack != nil && f->pumpactive); } static void ximend(Ximfix *f) { Drawcmd dc; uchar token; if(f->pumpactive){ token = 0; chansend(f->stop, &token); chanrecv(f->done, &token); } testengineinit(LangEN); while(channbrecv(drawc, &dc) > 0) ; chanfree(replyc); replyc = f->oldreply; chanfree(f->trace); chanfree(f->stop); chanfree(f->done); chanfree(f->waiting); chanfree(f->ack); } static Keyreq nexttrace(struct ct *t, Ximfix *f, int op, Ic *state) { Keyreq req; memset(&req, 0, sizeof req); if(!CT_CHECK(t, channbrecv(f->trace, &req) > 0)) return req; CT_EQ_INT(t, op, req.op); CT_EQ_PTR(t, state, req.owner); CT_EQ_PTR(t, replyc, req.reply); return req; } static void notrace(struct ct *t, Ximfix *f) { Keyreq req; CT_CHECK(t, channbrecv(f->trace, &req) <= 0); } static void checkstr(struct ct *t, char *want, Str *got) { char buf[Maxutf]; stoutf(got, buf, sizeof buf); CT_EQ_STR(t, want, buf); } static void checkenginepreedit(struct ct *t, char *want) { Str preedit; testenginepreedit(&preedit); checkstr(t, want, &preedit); } void xim_adapter_key_contract(struct ct *t) { Ximfix f; Ic state; Keyreq req; Keyres res; u32int allmod; memset(&state, 0, sizeof state); if(!ximbegin(t, &f, LangJP)) goto cleanup; allmod = ~0; keypress(&state, 'x', allmod, &res); req = nexttrace(t, &f, Keypress, &state); CT_EQ_UINT(t, Mmask, req.mod); CT_CHECK(t, !res.eaten); keypress(&state, 'k', 0, &res); req = nexttrace(t, &f, Keypress, &state); CT_EQ_UINT(t, 0, req.mod); CT_CHECK(t, res.eaten); keypress(&state, 'a', 0, &res); nexttrace(t, &f, Keypress, &state); CT_CHECK(t, res.eaten); checkstr(t, "か", &res.preedit); keypress(&state, Kspec|0x55, 0, &res); nexttrace(t, &f, Keypress, &state); CT_CHECK(t, !res.eaten); checkstr(t, "か", &res.commit); checkstr(t, "", &res.preedit); cleanup: ximend(&f); } void xim_adapter_release_lifecycle(struct ct *t) { Ximfix f; Ic a, b, *dead; Keyres res; memset(&a, 0, sizeof a); memset(&b, 0, sizeof b); dead = nil; if(!ximbegin(t, &f, LangJP)) goto cleanup; keypress(&a, 'k', 0, &res); nexttrace(t, &f, Keypress, &a); keypress(&a, 'a', 0, &res); nexttrace(t, &f, Keypress, &a); keypress(&b, 'n', 0, &res); nexttrace(t, &f, Keypress, &b); checkstr(t, "ん", &res.preedit); CT_EQ_PTR(t, &b, testengineowner()); /* ResetIC and focus loss use the same idempotent release path. */ release(&a); nexttrace(t, &f, Keyrelease, &a); checkenginepreedit(t, "ん"); CT_EQ_PTR(t, &b, testengineowner()); release(&a); notrace(t, &f); release(&b); nexttrace(t, &f, Keyrelease, &b); CT_EQ_PTR(t, nil, testengineowner()); keypress(&b, 'k', 0, &res); nexttrace(t, &f, Keypress, &b); release(&b); nexttrace(t, &f, Keyrelease, &b); release(&b); notrace(t, &f); dead = calloc(1, sizeof *dead); if(!CT_CHECK(t, dead != nil)) goto cleanup; dead->next = ics; ics = dead; keypress(dead, 'n', 0, &res); nexttrace(t, &f, Keypress, dead); CT_EQ_PTR(t, dead, testengineowner()); icfree(dead); nexttrace(t, &f, Keyrelease, dead); dead = nil; CT_EQ_PTR(t, nil, ics); CT_EQ_PTR(t, nil, testengineowner()); cleanup: if(dead != nil){ icunlink(dead); free(dead); } ximend(&f); } static void freeworker(void *arg) { Freejob *job; uchar token; job = arg; icfree(job->state); token = 0; chansend(job->done, &token); } void xim_adapter_free_waits_for_release(struct ct *t) { Ximfix f; Freejob job; Ic *state; Channel *freed; Keyres res; uchar token; int workeractive; state = nil; freed = nil; workeractive = 0; if(!ximbegin(t, &f, LangJP)) goto cleanup; state = calloc(1, sizeof *state); freed = chancreate(sizeof(uchar), 0); if(!CT_CHECK(t, state != nil && freed != nil)) goto cleanup; state->next = ics; ics = state; keypress(state, 'n', 0, &res); nexttrace(t, &f, Keypress, state); f.holdrelease = 1; job.state = state; job.done = freed; workeractive = threadcreate(freeworker, &job, 8192) >= 0; if(!CT_CHECK(t, workeractive)) goto cleanup; chanrecv(f.waiting, &token); nexttrace(t, &f, Keyrelease, state); CT_CHECK(t, channbrecv(freed, &token) <= 0); CT_EQ_PTR(t, state, ics); CT_EQ_PTR(t, state, testengineowner()); chansend(f.ack, &token); chanrecv(freed, &token); workeractive = 0; state = nil; CT_EQ_PTR(t, nil, ics); CT_EQ_PTR(t, nil, testengineowner()); cleanup: if(workeractive){ chansend(f.ack, &token); chanrecv(freed, &token); state = nil; } if(state != nil){ icunlink(state); free(state); } if(freed != nil) chanfree(freed); ximend(&f); }