Files
strans/tests/ibus_test.c
Hojun-Cho 88ad5f7630 engine: the one capability is a flag
Cclientpreedit was a one-bit mask that every producer set as "want ?
Cclientpreedit : 0" and every consumer masked out again; nothing else
was ever going to join it.  Keyreq carries clientpre, an int that says
whether the client draws the preedit, and the engine and the XIM
context keep it under that name.
2026-08-17 10:57:15 +09:00

447 lines
11 KiB
C

#include "ibus.c"
#include "test.h"
#include <errno.h>
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);
}
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;
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 = 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;
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, &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;
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());
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, &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());
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);
}