Six handlers built method returns by hand and only two of them checked for a nil message; reply() builds them all and dies on OOM like the rest of the daemon. DBusError objects were initialised and freed but never read: libdbus accepts nil. Properties.Get always errored, so it is one line in onmsg; the introspection XML served nobody (libibus never asks) and had to be kept in sync by hand. Ibushinthidden was not an IBus hint, so that term of hidden() never fired. writeaddr uses the syscalls directly and the address file's dev/ino is the ownership proof; buildaddrpath makes the directory in place. An empty UpdatePreeditText goes out only to the context that shows a preedit.
594 lines
14 KiB
C
594 lines
14 KiB
C
#include "ibus.c"
|
|
#include "cutest/cutest.h"
|
|
|
|
#include <errno.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;
|
|
};
|
|
|
|
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 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;
|
|
preowner = nil;
|
|
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;
|
|
preowner = nil;
|
|
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);
|
|
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->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_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);
|
|
checkpreedit(t, "k", &res);
|
|
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);
|
|
checkpreedit(t, "k", &res);
|
|
|
|
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);
|
|
checkpreedit(t, "k", &res);
|
|
cleanup:
|
|
ibusend(&f);
|
|
}
|
|
|
|
void
|
|
ibus_private_input_policy(struct ct *t)
|
|
{
|
|
static const struct { u32int purpose, hints; int want; } cases[] = {
|
|
{ 0, 0, 0 },
|
|
{ Ibuspurposepassword, 0, 1 },
|
|
{ Ibuspurposepin, 0, 1 },
|
|
{ 0, 1<<11, 0 },
|
|
{ 37, 1<<20, 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;
|
|
ctx->hints = cases[i].hints;
|
|
CT_EQ_INT(t, cases[i].want, hidden(ctx));
|
|
}
|
|
|
|
ctx->focused = 1;
|
|
ctx->cap = Cclientpreedit;
|
|
ctx->purpose = 0;
|
|
ctx->hints = 0;
|
|
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
|
res = contextkey(t, &f, ctx, 'k', 0);
|
|
checkpreedit(t, "k", &res);
|
|
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;
|
|
ctx->hints = 1<<11;
|
|
res = contextkey(t, &f, ctx, 'a', 0);
|
|
checkpreedit(t, "か", &res);
|
|
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);
|
|
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);
|
|
}
|