#include "ibus.c" #include "test.h" #include enum { Testctrlmask = 1<<2, }; typedef struct Ibusfix Ibusfix; struct Ibusfix { Channel *oldreply; Pump pump; DBusConnection *c1; DBusConnection *c2; }; void ibus_machine_id_fallback(struct ct *t) { char root[] = "/tmp/strans-machine-id.XXXXXX"; char primary[128], fallback[128], override[128], got[512]; char *path[2], *old, *saved; FILE *fp; int madeprimary, madefallback; madeprimary = 0; madefallback = 0; if(!CT_CHECK(t, mkdtemp(root) != nil)) return; if(!CT_CHECK(t, snprintf(primary, sizeof primary, "%s/primary", root) < (int)sizeof primary) || !CT_CHECK(t, snprintf(fallback, sizeof fallback, "%s/fallback", root) < (int)sizeof fallback)) goto cleanup; if(!CT_CHECK(t, mkdir(primary, 0700) == 0)) goto cleanup; madeprimary = 1; fp = fopen(fallback, "w"); if(!CT_CHECK(t, fp != nil)) goto cleanup; madefallback = 1; if(!CT_CHECK(t, fputs("fallback-machine-id\n", fp) >= 0)){ fclose(fp); goto cleanup; } if(!CT_CHECK(t, fclose(fp) == 0)) goto cleanup; path[0] = primary; path[1] = fallback; machineidfiles(got, sizeof got, path, nelem(path)); CT_EQ_STR(t, "fallback-machine-id", got); path[1] = primary; machineidfiles(got, sizeof got, path, nelem(path)); CT_EQ_STR(t, "", got); old = getenv("IBUS_ADDRESS_FILE"); saved = old == nil ? nil : strdup(old); if(old == nil || CT_CHECK(t, saved != nil)){ if(CT_CHECK(t, snprintf(override, sizeof override, "%s/address", root) < (int)sizeof override) && CT_CHECK(t, setenv("IBUS_ADDRESS_FILE", override, 1) == 0)){ CT_EQ_INT(t, 0, buildaddrpath(got, sizeof got)); CT_EQ_STR(t, override, got); CT_CHECK(t, setenv("IBUS_ADDRESS_FILE", "", 1) == 0); CT_EQ_INT(t, -1, buildaddrpath(got, sizeof got)); } if(saved != nil){ setenv("IBUS_ADDRESS_FILE", saved, 1); free(saved); }else unsetenv("IBUS_ADDRESS_FILE"); } cleanup: if(madefallback) CT_CHECK(t, unlink(fallback) == 0); if(madeprimary) CT_CHECK(t, rmdir(primary) == 0); CT_CHECK(t, rmdir(root) == 0); } 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; preowner = nil; while(channbrecv(drawc, &dc) > 0) ; testengineinit(LangEN); f->oldreply = replyc; replyc = chancreate(sizeof(Keyres), 0); pumpstart(&f->pump, Maxcontexts); f->c1 = malloc(1); f->c2 = malloc(1); return CT_CHECK(t, f->c1 != nil && f->c2 != nil); } static void ibusend(Ibusfix *f) { Drawcmd dc; int i; for(i = 0; i < nelem(contexts); i++) if(contexts[i].conn != nil) dropcontext(&contexts[i]); pumpstop(&f->pump); memset(contexts, 0, sizeof contexts); memset(conns, 0, sizeof conns); nconns = 0; preowner = nil; testengineinit(LangEN); while(channbrecv(drawc, &dc) > 0) ; free(f->c1); free(f->c2); chanfree(replyc); replyc = f->oldreply; } 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->pump.trace, &req) > 0)) return req; CT_EQ_INT(t, op, req.op); CT_EQ_PTR(t, ctx, req.owner); if(ctx != nil) CT_EQ_INT(t, clientpreedit(ctx), req.clientpre); CT_EQ_PTR(t, replyc, req.reply); return req; } static void notrace(struct ct *t, Ibusfix *f) { Keyreq req; CT_CHECK(t, channbrecv(f->pump.trace, &req) <= 0); } static Keyres contextkey(struct ct *t, Ibusfix *f, Ictx *ctx, u32int sym, u32int state) { Keyres res; char text[Maxutf]; memset(&res, 0, sizeof res); CT_CHECK(t, processkey(ctx, sym, state, text, sizeof text, &res)); nexttrace(t, f, Keypress, ctx); return res; } void ibus_capability_policy(struct ct *t) { Ibusfix f; Ictx *a, *b; Keyres res; if(!ibusbegin(t, &f)) goto cleanup; a = newcontext(f.c1, "/context/cap-a"); b = newcontext(f.c2, "/context/cap-b"); if(!CT_CHECK(t, a != nil && b != nil)) goto cleanup; CT_EQ_INT(t, 0, a->cap); a->focused = 1; res = contextkey(t, &f, a, 'n', Testctrlmask); CT_CHECK(t, res.eaten); res = contextkey(t, &f, a, 'k', 0); checkstr(t, "preedit", "k", &res.preedit); CT_EQ_PTR(t, a, testengineowner()); a->cap = Ibuscappreedit; memset(&res, 0, sizeof res); sendrequest(a, Keycap, 0, 0, &res); nexttrace(t, &f, Keycap, a); CT_CHECK(t, res.eaten); checkstr(t, "preedit", "k", &res.preedit); b->focused = 1; b->cap = Ibuscappreedit; memset(&res, 0, sizeof res); sendrequest(b, Keycap, 0, 0, &res); nexttrace(t, &f, Keycap, b); CT_CHECK(t, !res.eaten); CT_EQ_PTR(t, a, testengineowner()); checkenginepreedit(t, "k"); a->cap = 0; memset(&res, 0, sizeof res); sendrequest(a, Keycap, 0, 0, &res); nexttrace(t, &f, Keycap, a); CT_CHECK(t, res.eaten); checkstr(t, "preedit", "k", &res.preedit); cleanup: ibusend(&f); } void ibus_private_input_policy(struct ct *t) { static const struct { u32int purpose; int want; } cases[] = { { 0, 0 }, { Ibuspurposepassword, 1 }, { Ibuspurposepin, 1 }, { 37, 0 }, }; Ibusfix f; Ictx *ctx; Keyres res; char text[Maxutf]; int i; if(!ibusbegin(t, &f)) goto cleanup; ctx = newcontext(f.c1, "/context/private"); if(!CT_CHECK(t, ctx != nil)) goto cleanup; for(i = 0; i < nelem(cases); i++){ ctx->purpose = cases[i].purpose; CT_EQ_INT(t, cases[i].want, hidden(ctx)); } ctx->focused = 1; ctx->cap = 1; ctx->purpose = 0; contextkey(t, &f, ctx, 'n', Testctrlmask); res = contextkey(t, &f, ctx, 'k', 0); checkstr(t, "preedit", "k", &res.preedit); ctx->purpose = Ibuspurposepassword; memset(&res, 0, sizeof res); CT_CHECK(t, !processkey(ctx, 'x', 0, text, sizeof text, &res)); CT_CHECK(t, !res.eaten); notrace(t, &f); checkenginepreedit(t, "k"); ctx->purpose = 0; res = contextkey(t, &f, ctx, 'a', 0); checkstr(t, "preedit", "か", &res.preedit); cleanup: ibusend(&f); } /* A second release is silent, and dropping frees the slot. */ static void checkdrop(struct ct *t, Ibusfix *f, Ictx *ctx, char *path) { releasecontext(ctx); notrace(t, f); dropcontext(ctx); notrace(t, f); CT_EQ_PTR(t, nil, findcontext(f->c1, path)); } void ibus_context_lifecycle(struct ct *t) { Ibusfix f; Ictx *a, *b, *reused; Keyreq req; Keyres res; Caret at; char text[Maxutf]; 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")); setcursor(a, -10, -20, 14); notrace(t, &f); CT_CHECK(t, a->caret.valid); memset(&res, 0, sizeof res); CT_CHECK(t, !processkey(a, 'x', Relmask, text, sizeof text, &res)); CT_CHECK(t, !res.eaten); notrace(t, &f); CT_CHECK(t, !a->focused); CT_EQ_PTR(t, nil, testengineowner()); /* A key stands for the FocusIn a client may never send. */ memset(&res, 0, sizeof res); CT_CHECK(t, processkey(a, 'x', 0, text, sizeof text, &res)); req = nexttrace(t, &f, Keypress, a); CT_CHECK(t, a->focused); 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); checkstr(t, "preedit", "か", &res.preedit); b->focused = 1; res = contextkey(t, &f, b, 'n', 0); checkstr(t, "preedit", "ん", &res.preedit); 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); checkstr(t, "preedit", "", &res.preedit); 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()); checkdrop(t, &f, a, "/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, text, sizeof text, &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); checkstr(t, "preedit", "ん", &res.preedit); memset(&res, 0, sizeof res); sendrequest(ctx, Keyreset, 0, 0, &res); nexttrace(t, &f, Keyreset, ctx); checkstr(t, "preedit", "", &res.preedit); checkenginepreedit(t, ""); CT_CHECK(t, ctx->focused); CT_EQ_PTR(t, ctx, testengineowner()); res = contextkey(t, &f, ctx, 'k', 0); checkstr(t, "preedit", "k", &res.preedit); 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()); checkdrop(t, &f, ctx, "/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()); contextkey(t, &f, reused, 'n', 0); CT_CHECK(t, reused->focused); 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); }