Repository navigation
Surviving mutations in the crank-rollback / GC / vat-lifecycle stack's tests #1075
Copy link
Copy link
Closed
Description
Collected from a mutation-style review of #1021, #1022 and #1023. Each item names the exact mutation that survives the suite today. Ordered by the severity of the bug it lets through.
#1023
performVatRestartnever throws out of the crank is unpinned. Mutation: appendthrow error;aftersettle?.reject(error). Everything passes, because the test harness mocksenqueueRestartVat: vi.fn((vatId) => { vatManager.performVatRestart(vatId).catch(() => undefined); })— the.catchswallows exactly the failure the tests exist to detect. Its comment claims "a regression on that shows up as a failing assertion"; it does not. Fix: record the outcome (.then(() => 'completed', (e) => e)) and assert it. See A failingperformVatRestartcatch can escape the crank and replay the same failing restart forever #1065 for the underlying bug.terminateVathas no fallback mark when a death write throws, and no test. MakedeleteVatthrow duringterminateVat: the handle is gone, the store is unmarked,isVatActivestill true → the next delivery kills the run loop. SeestopVatrecords a vat's death before killing its worker, so a failed record strands a live worker #1067.- The
terminateVat→stopVatswap inKernel.tshas no test. Mutation: revert toterminateVat.Kernel.test.tsmocksKernelQueuewholesale, so the callback is never invoked from a real crank; a revert hangs the kernel silently (withStoreOutOfCrankwould wait for the crank that is calling it). - The whole
#vatsInFlux/provideVatmechanism can be deleted and the suite passes. The intended pin cannot see the difference:withStoreOutOfCrankis mocked asasync (work) => work(), so#retireVathas already run synchronously by the time the test callsprovideVat. Pin the ordering — flag from the promise'scatch,drainMicrotasks(), assert not settled, then release. Also unpinned:Kernel.ts'sgetVat→provideVatswap in#getEndpoint. enqueueRestartVatwaking a sleeping run loop is unpinned. Mutation: callkernelStore.enqueueRundirectly instead of#enqueueRun. Consequence: a restart requested while the queue is empty never runs until unrelated traffic arrives.performVatRestart's!#vats.hasearly return can be deleted; both existing assertions still hold. Pin withexpect(deleteVat).toHaveBeenCalledTimes(1).getImporters' deduplication can be dropped: the new test callsdeleteVat('v2')beforemarkVatAsTerminated, so the duplicate never arises. Swap the two calls.gc-finalize.ts's twoawait delay(0)drains have no test; the integration test that would have noticed was reworked into a retry loop that masks the latency.
#1022
- A remote that refuses a GC delivery — the test asserts only the returned
{ didDelivery: 'r1' }. Mutation: move thelive.forEachteardown after the successfulawait endpoint[method](erefs). Everything passes, while in production the action is already spent and the kref is never freed. - No real-store test that an aborted GC delivery restores the release. The existing test proves the router returns
{ abort: true, terminate }against a mock store.gc-delivery.test.tsalready has the fixture to prove the store comes back;retireExportsis the case that matters, since it is the only one that also orphans. - Every new error log in the PR is unasserted — four
logger?.errorcalls, all deletable with a green suite, in a PR whose framing is "stop hiding GC delivery failures". Both seams (makeKernelStore(kdb, logger?),new KernelRouter(…, logger?)) already exist. - Remote GC delivery has no real-store coverage at all;
gc-delivery.test.tsregisters only vats, and the remote leg is where the two divergent branches live. One added test closes this and item 9. - The sort-before-harden fix pins the non-throwing half but not the ordering half —
setGCActionssorts upstream, so deletingkrefs.sort()outright passes.
#1021
RemoteHandle.handleRemoteMessageis never tested against an open crank. Mutation: replaceawait withStoreOutOfCrank(() => {…})with an immediately-invoked version. Every test passes — the two new turn tests assertoutOfCrankWorkPending()isundefinedafter the call, which is trivially true when no turn was taken.RemoteManager.test.tsdoes open a crank first;RemoteHandle, which carries every ordinarydeliver, does not.- The wasm driver no longer exercises
assertSafeIdentifier— thesavepoint functionalitydescribe was deleted and not replaced. All three calls can be removed with a green suite. Savepoint names are string-interpolated into SQL and include peer-supplied input (peerIncarnation_${peerId},receive_${remoteId}_${seq}). The nodejs driver kept its equivalent, so this is a one-sided regression on the browser driver. - The wasm
No such savepointguard is uncovered in bothrollbackSavepointandreleaseSavepoint; the test that looks like it covers this asserts SQLite's own lowercase message instead. - Dropping
verbosefrom the nodejs driver is untested — a one-line revert away from logging every statement with values inlined. See The browser kernel worker logs every kv set and delete with its value #1073. crank.cross-crank-gc.test.tsruns againstmakeMapKernelDatabase, whose savepoint methods are empty functions, sorunCrank({ rollback: true })performs no database rollback at all. The RAM assertions are valid, but the file's cross-crank durability claim is not tested.
Small, mechanical
crank.test.tsassignscontext.savepoints = ['test']— astring[]where the type is nowSavepoint[].beginOutOfCrank/endOutOfCrankare mocked in three test stores but are private tocrank.tsand on no store surface. Inert, and misleading to the next reader.vat-death-and-cranks.test.tsspies onVatHandle.makeinside its fixture with no restore.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
sirtimid commentedon Sep 15, 2026
Closing: these mutations are against the stack's tests, and the stack is being split into small PRs against main with tests rewritten per PR. The items about
assertSafeIdentifiercoverage and droppingverboseare on the relevant PRs' checklists.