Files
improvise/harness/tests/opfs.spec.js
T
Edward Langley 12714c7527 feat(browser): a boot failure is a state with a reason, not silence
improvise-1wft and the five beads it blocked were one failure: nothing
carried a boot failure's REASON from the worker to the page. The channel
is one rule -- a protocol frame crosses the port as a STRING, a shell
notice as an OBJECT -- so it needs no protocol change and the
discriminator is total.

A boot failure is a STATE, not a rejected promise. A document another
context holds the exclusive handle to now OPENS read-only: measured first
(harness probe, Chromium + Firefox), getFile() reads the bytes while the
handle is held, with the second-handle refusal asserted so the probe
cannot pass vacuously.

- improvise-1wft  the channel: Notice/NoticeKind, boot_notice, take_notice
- improvise-i0v9  read-only boot over vfs::ReadOnly + OpfsFs::read_unattached
- improvise-86nw  start() rejecting for any reason now posts BootFailed
- improvise-9cma  boot::document_name -- and a/b/c.improv was ACCEPTED as
                  c.improv, not rejected, which is worse than the report
- improvise-iobc  vfs::Origin: a seed error is a notice only when the
                  document really had to be created without it
- improvise-vep8  the watchdog arms BEFORE transport.ready; its message
                  stopped claiming the connection was accepted
- improvise-7gip  DedicatedWorkerGlobalScope (needs a web-sys feature)
- improvise-zre1  a persist that fails raises a notice; the trigger is the
                  dirty flag surviving, observed rather than predicted
- improvise-7gf8  the render breaker trips once instead of retrying forever
- improvise-ociy  non-OPEN sends are loud; a viewport announcement is not
- improvise-y01k  the three ws-server drops are logged
- improvise-r4nf  serde failures throw instead of reading as "nothing to send"

Every notice routes through shell.fail, so gotcha 48's disarm half is not
re-broken. Two comments in shell.spec.js stated the opposite of the new
arming order and were corrected rather than left.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-21 14:51:34 -07:00

100 lines
3.8 KiB
JavaScript

// `storage_contract` against `OpfsFs`, in a browser (improvise-vvl3/xf1). The
// assertions here are about *the report*: context/test-evidence/clients.md.
import { test, expect } from "@playwright/test";
/** Run one entry point in the harness worker. */
async function run(page, cmd) {
return JSON.parse(await page.evaluate((c) => window.opfs(c), cmd));
}
/** The checks the report must contain, in order. Named here as well as in Rust
* on purpose — see context/test-evidence/clients.md. */
const EXPECTED = [
"open_and_refresh_see_an_origin_root_holding_no_documents",
"an_attached_but_unwritten_entry_is_not_a_document",
"a_directory_entry_is_not_a_document",
"refresh_walks_every_key_and_sorts_them",
"read_and_write_are_absolute_at_offset_zero",
'storage_contract_at_""',
'storage_contract_at_"docs"',
"a_delete_is_visible_at_once_and_lands_on_commit",
];
test.describe("OpfsFs in a real browser", () => {
test.beforeEach(async ({ page, browserName }, testInfo) => {
await page.goto("/opfs/index.html");
// The skip is conditional on the PROBE, not on the browser name, so it
// cannot quietly swallow a real regression. See clients.md.
const opfs = await page.evaluate(async () => {
try {
await navigator.storage.getDirectory();
return "yes";
} catch (err) {
return String(err?.name ?? err);
}
});
if (opfs !== "yes") {
expect(
browserName,
`${browserName} lost OPFS (${opfs}) — that is a regression, not a known gap`,
).toBe("webkit");
testInfo.skip(true, `this WebKit build provides no OPFS (${opfs})`);
}
});
// Check the checker, and do it first: a green run against a worker that
// never started is the failure this repo has a doctrine about.
test("the harness can see a failure", async ({ page }) => {
const report = await run(page, "run_canary");
expect(report).toHaveLength(1);
expect(report[0].name).toBe("the_canary_must_fail");
expect(report[0].ok).toBe(false);
expect(report[0].detail).toContain("the harness can see a failure");
});
test("every check in the battery runs and passes", async ({ page }) => {
const report = await run(page, "run_checks");
// The battery is complete before any of it is believed.
expect(report.map((c) => c.name)).toEqual(EXPECTED);
const failed = report.filter((c) => !c.ok);
expect(
failed,
`failing checks:\n${failed.map((c) => ` ${c.name}: ${c.detail}`).join("\n")}`,
).toEqual([]);
});
// Measurements, not contracts: the read-only boot path (improvise-1wft,
// i0v9) is decided on these, so they run per engine and say which.
test("the probes the read-only boot design rests on", async ({ page }, testInfo) => {
const report = await run(page, "run_probes");
expect(report.map((c) => c.name)).toEqual([
"getfile_reads_a_document_under_a_foreign_sync_handle",
"a_dedicated_worker_satisfies_the_narrow_scope_guard",
]);
const failed = report.filter((c) => !c.ok);
expect(
failed,
`failing probes:\n${failed.map((c) => ` ${c.name}: ${c.detail}`).join("\n")}`,
).toEqual([]);
// The measurement is the point, so it is attached rather than only asserted.
await testInfo.attach("opfs-probes.json", {
body: JSON.stringify(report, null, 2),
contentType: "application/json",
});
});
// The one thing no in-page check can establish: "commit_deletes actually
// removed it" is only answered by a fresh page.
test("what OPFS held survives a reload, and a committed delete does not", async ({
page,
}) => {
expect(await page.evaluate(() => window.opfs("before_reload"))).toBe("staged");
await page.reload();
expect(await page.evaluate(() => window.opfs("after_reload"))).toBe("survived");
});
});