#include "../ibus.c" #include "../cutest/cutest.h" void testengineinit(int); void testenginehandle(Keyreq*); void* testengineowner(void); void testenginepreedit(Str*); void testenginecaret(Caret*); enum { Testctrlmask = 1<<2, }; typedef struct Ibusfix Ibusfix; struct Ibusfix { Channel *oldreply; Channel *trace; Channel *stop; Channel *done; DBusConnection *c1; DBusConnection *c2; int pumpactive; }; static void enginepump(void *arg) { Ibusfix *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); testenginehandle(&req); break; case 1: chansend(f->done, &token); return; } } static int ibusbegin(struct ct *t, Ibusfix *f) { Drawcmd dc; memset(f, 0, sizeof *f); memset(contexts, 0, sizeof contexts); memset(conns, 0, sizeof conns); nconns = 0; while(channbrecv(drawc, &dc) > 0) ; testengineinit(LangEN); f->oldreply = replyc; replyc = chancreate(sizeof(Keyres), 0); f->trace = chancreate(sizeof(Keyreq), Maxcontexts); f->stop = chancreate(sizeof(uchar), 0); f->done = chancreate(sizeof(uchar), 0); f->pumpactive = threadcreate(enginepump, f, 8192) >= 0; f->c1 = malloc(1); f->c2 = malloc(1); return CT_CHECK(t, f->pumpactive && f->c1 != nil && f->c2 != nil); } static void ibusend(Ibusfix *f) { Drawcmd dc; Keyreq req; uchar token; int i; if(f->pumpactive){ for(i = 0; i < nelem(contexts); i++) if(contexts[i].conn != nil) dropcontext(&contexts[i]); while(channbrecv(f->trace, &req) > 0) ; token = 0; chansend(f->stop, &token); chanrecv(f->done, &token); } memset(contexts, 0, sizeof contexts); memset(conns, 0, sizeof conns); nconns = 0; testengineinit(LangEN); while(channbrecv(drawc, &dc) > 0) ; free(f->c1); free(f->c2); chanfree(replyc); replyc = f->oldreply; chanfree(f->trace); chanfree(f->stop); chanfree(f->done); } static Keyreq nexttrace(struct ct *t, Ibusfix *f, int op, Ictx *ctx) { 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, ctx, req.owner); CT_EQ_PTR(t, replyc, req.reply); return req; } static void notrace(struct ct *t, Ibusfix *f) { Keyreq req; CT_CHECK(t, channbrecv(f->trace, &req) <= 0); } static void checkpreedit(struct ct *t, char *want, Keyres *res) { char got[Maxutf]; stoutf(&res->preedit, got, sizeof got); CT_EQ_STR(t, want, got); } static void checkenginepreedit(struct ct *t, char *want) { char got[Maxutf]; Str preedit; testenginepreedit(&preedit); stoutf(&preedit, got, sizeof got); CT_EQ_STR(t, want, got); } static Keyres contextkey(struct ct *t, Ibusfix *f, Ictx *ctx, u32int sym, u32int state) { Keyres res; memset(&res, 0, sizeof res); CT_CHECK(t, processkey(ctx, sym, state, &res)); nexttrace(t, f, Keypress, ctx); return res; } void ibus_context_lifecycle(struct ct *t) { Ibusfix f; Ictx *a, *b, *reused; Keyreq req; Keyres res; Caret at; if(!ibusbegin(t, &f)) goto cleanup; a = newcontext(f.c1, "/context/one"); b = newcontext(f.c2, "/context/one"); if(!CT_CHECK(t, a != nil && b != nil && a != b)) goto cleanup; CT_EQ_PTR(t, a, findcontext(f.c1, "/context/one")); CT_EQ_PTR(t, b, findcontext(f.c2, "/context/one")); CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/missing")); memset(&res, 0, sizeof res); CT_CHECK(t, !processkey(a, 'x', 0, &res)); CT_CHECK(t, !res.eaten); notrace(t, &f); CT_EQ_PTR(t, nil, testengineowner()); setcursor(a, 10, 20, 14); notrace(t, &f); CT_CHECK(t, a->caret.valid); a->focused = 1; memset(&res, 0, sizeof res); CT_CHECK(t, !processkey(a, 'x', Relmask, &res)); CT_CHECK(t, !res.eaten); notrace(t, &f); CT_EQ_PTR(t, nil, testengineowner()); memset(&res, 0, sizeof res); CT_CHECK(t, processkey(a, 'x', 0, &res)); req = nexttrace(t, &f, Keypress, a); CT_CHECK(t, !res.eaten); CT_CHECK(t, req.caret.valid); CT_EQ_INT(t, 10, req.caret.x); CT_EQ_INT(t, 20, req.caret.y); CT_EQ_INT(t, 14, req.caret.h); CT_EQ_PTR(t, a, testengineowner()); testenginecaret(&at); CT_CHECK(t, at.valid); CT_EQ_INT(t, 10, at.x); CT_EQ_INT(t, 20, at.y); res = contextkey(t, &f, a, 'n', Testctrlmask); CT_CHECK(t, res.eaten); contextkey(t, &f, a, 'k', 0); res = contextkey(t, &f, a, 'a', 0); checkpreedit(t, "か", &res); b->focused = 1; res = contextkey(t, &f, b, 'n', 0); checkpreedit(t, "ん", &res); CT_EQ_PTR(t, b, testengineowner()); setcursor(b, 50, 60, 12); req = nexttrace(t, &f, Keycaret, b); CT_CHECK(t, req.caret.valid); CT_EQ_INT(t, 50, req.caret.x); testenginecaret(&at); CT_CHECK(t, at.valid); CT_EQ_INT(t, 50, at.x); memset(&res, 0, sizeof res); sendrequest(a, Keyreset, 0, 0, &res); nexttrace(t, &f, Keyreset, a); checkpreedit(t, "", &res); checkenginepreedit(t, "ん"); CT_EQ_PTR(t, b, testengineowner()); setcursor(a, 90, 100, 20); nexttrace(t, &f, Keycaret, a); testenginecaret(&at); CT_EQ_INT(t, 50, at.x); CT_EQ_INT(t, 60, at.y); releasecontext(a); nexttrace(t, &f, Keyrelease, a); CT_CHECK(t, !a->focused); CT_CHECK(t, !a->caret.valid); checkenginepreedit(t, "ん"); CT_EQ_PTR(t, b, testengineowner()); releasecontext(a); notrace(t, &f); dropcontext(a); notrace(t, &f); CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/one")); reused = newcontext(f.c1, "/context/stale-destroy"); CT_EQ_PTR(t, a, reused); reused->focused = 1; dropcontext(reused); nexttrace(t, &f, Keyrelease, reused); notrace(t, &f); CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/stale-destroy")); checkenginepreedit(t, "ん"); CT_EQ_PTR(t, b, testengineowner()); reused = newcontext(f.c1, "/context/reused"); CT_EQ_PTR(t, a, reused); reused->focused = 1; dropconncontexts(f.c1); nexttrace(t, &f, Keyrelease, reused); notrace(t, &f); CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/reused")); checkenginepreedit(t, "ん"); CT_EQ_PTR(t, b, testengineowner()); dropconncontexts(f.c1); notrace(t, &f); memset(&res, 0, sizeof res); CT_CHECK(t, !processkey(b, 'x', Relmask, &res)); CT_CHECK(t, !res.eaten); notrace(t, &f); CT_EQ_PTR(t, b, testengineowner()); cleanup: ibusend(&f); } void ibus_active_release_lifecycle(struct ct *t) { Ibusfix f; Ictx *ctx, *reused; Keyres res; if(!ibusbegin(t, &f)) goto cleanup; ctx = newcontext(f.c1, "/context/reset"); if(!CT_CHECK(t, ctx != nil)) goto cleanup; ctx->focused = 1; contextkey(t, &f, ctx, 'n', Testctrlmask); res = contextkey(t, &f, ctx, 'n', 0); checkpreedit(t, "ん", &res); memset(&res, 0, sizeof res); sendrequest(ctx, Keyreset, 0, 0, &res); nexttrace(t, &f, Keyreset, ctx); checkpreedit(t, "", &res); checkenginepreedit(t, ""); CT_CHECK(t, ctx->focused); CT_EQ_PTR(t, ctx, testengineowner()); res = contextkey(t, &f, ctx, 'k', 0); checkpreedit(t, "k", &res); CT_EQ_PTR(t, ctx, testengineowner()); releasecontext(ctx); nexttrace(t, &f, Keyrelease, ctx); CT_CHECK(t, !ctx->focused); checkenginepreedit(t, ""); CT_EQ_PTR(t, nil, testengineowner()); releasecontext(ctx); notrace(t, &f); dropcontext(ctx); notrace(t, &f); CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/reset")); reused = newcontext(f.c1, "/context/destroy"); CT_EQ_PTR(t, ctx, reused); reused->focused = 1; contextkey(t, &f, reused, 'n', 0); CT_EQ_PTR(t, reused, testengineowner()); dropcontext(reused); nexttrace(t, &f, Keyrelease, reused); CT_EQ_PTR(t, nil, testengineowner()); notrace(t, &f); CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/destroy")); reused = newcontext(f.c1, "/context/prune"); CT_EQ_PTR(t, ctx, reused); CT_EQ_PTR(t, nil, testengineowner()); memset(&res, 0, sizeof res); CT_CHECK(t, !processkey(reused, 'x', 0, &res)); CT_CHECK(t, !res.eaten); notrace(t, &f); reused->focused = 1; contextkey(t, &f, reused, 'n', 0); CT_EQ_PTR(t, reused, testengineowner()); dropconncontexts(f.c1); nexttrace(t, &f, Keyrelease, reused); CT_EQ_PTR(t, nil, testengineowner()); notrace(t, &f); CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/prune")); dropconncontexts(f.c1); notrace(t, &f); cleanup: ibusend(&f); }