#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); 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); } void ibus_startup_requires_ownership(struct ct *t) { char root[] = "/tmp/strans-ibus-startup.XXXXXX"; char busdir[128], ibusdir[128], path[512], *old, *saved; struct stat st; int i, made, rv; made = 0; busdir[0] = '\0'; ibusdir[0] = '\0'; path[0] = '\0'; old = getenv("XDG_CONFIG_HOME"); saved = old == nil ? nil : strdup(old); if(old != nil && !CT_CHECK(t, saved != nil)) return; if(!CT_CHECK(t, mkdtemp(root) != nil)) goto cleanup; made = 1; if(!CT_CHECK(t, snprintf(busdir, sizeof busdir, "%s/ibus/bus", root) < (int)sizeof busdir) || !CT_CHECK(t, snprintf(ibusdir, sizeof ibusdir, "%s/ibus", root) < (int)sizeof ibusdir) || !CT_CHECK(t, setenv("XDG_CONFIG_HOME", root, 1) == 0) || !CT_CHECK(t, buildaddrpath(path, sizeof path) == 0)) goto cleanup; memset(watches, 0, sizeof watches); for(i = 0; i < nelem(watches); i++) watches[i] = (DBusWatch*)1; nwatches = nelem(watches); rv = ibusinit(); CT_EQ_INT(t, -1, rv); CT_EQ_PTR(t, nil, srv); CT_CHECK(t, lstat(path, &st) < 0 && errno == ENOENT); if(rv == 0){ dbus_server_disconnect(srv); dbus_server_unref(srv); srv = nil; unlinkaddr(); atexitdont(unlinkaddr); } memset(watches, 0, sizeof watches); nwatches = 0; while(atexit(unlinkaddr)) continue; CT_EQ_INT(t, -1, ibusinit()); CT_EQ_PTR(t, nil, srv); CT_CHECK(t, lstat(path, &st) < 0 && errno == ENOENT); cleanup: if(srv != nil){ dbus_server_disconnect(srv); dbus_server_unref(srv); srv = nil; } unlinkaddr(); atexitdont(unlinkaddr); memset(watches, 0, sizeof watches); nwatches = 0; addrfile[0] = '\0'; if(saved != nil){ setenv("XDG_CONFIG_HOME", saved, 1); free(saved); }else unsetenv("XDG_CONFIG_HOME"); if(made){ if(path[0] != '\0') unlink(path); if(busdir[0] != '\0') CT_CHECK(t, rmdir(busdir) == 0 || errno == ENOENT); if(ibusdir[0] != '\0') CT_CHECK(t, rmdir(ibusdir) == 0 || errno == ENOENT); 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, ctx->cap & 1 ? Cclientpreedit : 0, req.cap); 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; memset(&res, 0, sizeof res); CT_CHECK(t, processkey(ctx, sym, state, &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 = Cclientpreedit; 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 = Cclientpreedit; 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; 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 = Cclientpreedit; 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, &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); } 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); 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()); 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); 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()); 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); }