ximbegin took a struct ct* only to USED() it and always returned 1, so its seven `if(!ximbegin(...)) goto cleanup;` call sites tested nothing and three of the cleanup labels they jumped to were unreachable. ibusbegin malloc'd one byte twice so that its two fake DBusConnections would differ by address, then CT_CHECKed the mallocs; two bytes in the fixture are two addresses, and nothing has to be freed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
440 lines
11 KiB
C
440 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;
|
|
/* A fake connection is nothing but an address to tell two apart. */
|
|
char fake[2];
|
|
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 void
|
|
ibusbegin(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 = (DBusConnection*)&f->fake[0];
|
|
f->c2 = (DBusConnection*)&f->fake[1];
|
|
}
|
|
|
|
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)
|
|
;
|
|
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;
|
|
|
|
ibusbegin(&f);
|
|
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 },
|
|
{ Purposepassword, 1 },
|
|
{ Purposepin, 1 },
|
|
{ 37, 0 },
|
|
};
|
|
Ibusfix f;
|
|
Ictx *ctx;
|
|
Keyres res;
|
|
char text[Maxutf];
|
|
int i;
|
|
|
|
ibusbegin(&f);
|
|
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 = Purposepassword;
|
|
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];
|
|
|
|
ibusbegin(&f);
|
|
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;
|
|
|
|
ibusbegin(&f);
|
|
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);
|
|
}
|