Files
captoys/toybox/test/ocapn.test.js
T
dckcandClaude Opus 5.5 e5ed407e55 capsite/toybox: a mount outlives its handler: the vat restarts on demand
A handler is a live reference, held by the VatAdmin DO; workerd evicts
an idle DO within seconds, and with it the handler, so a vat's page went
503 almost at once.

createVat now keeps a vat's params when they hold only data and
ingresses (an ingress by its key: the VatAdmin can make it again), and
which vat was given which ingress. A request for a mount with no handler
restarts that vat with its params; it rebuilds its root, serves again,
and the request is answered. A vat whose params hold another peer's
objects isn't restartable yet.

The test crashes its own toybox (a crash ends every session to it), and
the creator waits for the root's handoff before it disconnects, as
scripts/hello.js now does too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:44:29 -05:00

106 lines
4.1 KiB
JavaScript

/** @file OCapN vats: a client on the admin socket creates a vat and passes it a remote object. */
import { mkdir, mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { after, before, test } from "node:test";
import assert from "node:assert/strict";
import { E, Far } from "@endo/far";
import { connectClient } from "../client.js";
import { freePort, here, startWorkerd } from "./workerd.js";
const vatSrc = (name) => readFile(join(here, "vats", name), "utf8");
let dir, port, server, client;
const startToybox = () =>
startWorkerd(
"config.capnp",
[
"-s", `http=127.0.0.1:${port}`, "-s", `admin=unix:${join(dir, "admin.sock")}`,
"-d", `state=${join(dir, "state")}`, "-e", `switchboard=unix:${join(dir, "no-switchboard.sock")}`,
],
{ port, host: "127.0.0.1" },
[join(dir, "admin.sock")],
);
before(async () => {
dir = await mkdtemp(join(tmpdir(), "toybox-ocapn-"));
await mkdir(join(dir, "state"));
port = await freePort();
server = await startToybox();
client = await connectClient(join(dir, "admin.sock"), "client-1", { verbose: !!process.env.OCAPN_LOG });
});
after(async () => {
client?.close();
await server?.stop();
await rm(dir, { recursive: true, force: true });
});
test("createVat passes a remote object in; the vat's root comes back", async () => {
const carol = Far("carol", { greet: (name) => `hi ${name}, from carol` });
const { id, root } = await E(client.vatAdmin).createVat(await vatSrc("greeter.js"), { carol });
assert.match(id, /^[0-9a-f]{64}$/);
assert.equal(await E(root).hello(), "hello from the vat");
assert.equal(await E(root).greetVia("vat"), "hi vat, from carol");
});
test("a counter vat counts; two counter vats count apart", async () => {
const src = await vatSrc("counter.js");
const { root: a } = await E(client.vatAdmin).createVat(src);
const { root: b } = await E(client.vatAdmin).createVat(src);
assert.equal(await E(a).increment(), 1);
assert.equal(await E(a).increment(), 2);
assert.equal(await E(b).increment(), 1);
assert.equal(await E(a).read(), 2);
assert.equal(await E(b).read(), 1);
});
test("a vat serves the web through the ingress it is given", async () => {
const ingress = await E(client.vatAdmin).makeIngress({ vat: "hello" });
await E(client.vatAdmin).createVat(await vatSrc("hello.js"), { ingress });
const resp = await fetch(`http://127.0.0.1:${port}/greet?vat=hello`);
assert.equal(resp.status, 200);
assert.equal(resp.headers.get("content-type"), "text/html; charset=utf-8");
assert.match(await resp.text(), /<h1>Hello, world<\/h1><p>from a toybox vat, at \/greet<\/p>/);
});
test("an ingress nobody serves is a 503", async () => {
await E(client.vatAdmin).makeIngress({ vat: "idle" });
assert.equal((await fetch(`http://127.0.0.1:${port}/?vat=idle`)).status, 503);
});
test("after a crash, a request restarts the vat that serves it", async () => {
// its own toybox: a crash breaks every session to it, the shared client's too
const home = await mkdtemp(join(tmpdir(), "toybox-phoenix-"));
await mkdir(join(home, "state"));
const port2 = await freePort();
const start = () =>
startWorkerd(
"config.capnp",
[
"-s", `http=127.0.0.1:${port2}`, "-s", `admin=unix:${join(home, "admin.sock")}`,
"-d", `state=${join(home, "state")}`, "-e", `switchboard=unix:${join(home, "no-switchboard.sock")}`,
],
{ port: port2, host: "127.0.0.1" },
[join(home, "admin.sock")],
);
let toybox = await start();
try {
const creator = await connectClient(join(home, "admin.sock"), "creator");
const ingress = await E(creator.vatAdmin).makeIngress({ vat: "phoenix" });
const { root } = await E(creator.vatAdmin).createVat(await vatSrc("hello.js"), { ingress });
assert.equal((await fetch(`http://127.0.0.1:${port2}/?vat=phoenix`)).status, 200);
await root; // its handoff, settled before the client goes
creator.close();
await toybox.stop();
toybox = await start();
const resp = await fetch(`http://127.0.0.1:${port2}/again?vat=phoenix`);
assert.equal(resp.status, 200);
assert.match(await resp.text(), /Hello, world.*at \/again/);
} finally {
await toybox.stop();
await rm(home, { recursive: true, force: true });
}
});