From 886528c3044488da57a80bc47290b031fa0713ce Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 18 Jan 2023 16:52:54 -0800 Subject: [PATCH 1/2] fix(swing-store): replace getAllState/etc with a debug facet Previously, `getAllState` and `setAllState` were swing-store helper methods which copy (or set) all the state of a store at once, used exclusively for testing. These tests would either want to inspect the swing-store contents directly, or clone a swing-store for e.g. replay testing. This commit replaces both with a new `debug` facet (a sibling of `kernelStorage` and `hostStorage`), which offers two methods. `debug.dump()` returns a JS Object with the store data in an easy-to-examine format (`dump.kvEntries['key']=value`, `dump.streams[vatID]=[..]`, and `dump.snapshots[vatID] = {endPos, hash, compressedSnapshot }`. For cloning, `debug.serialize()` returns a Buffer that has a raw copy of the SQLite backing store. This can be used to make a new DB by passing it as an option to `initSwingStore`: ```js const serialized = swingstore1.debug.serialize(); const swingstore2 = initSwingStore(null, { serialized }); ``` Note that both `.serialize()` and `{ serialized }` require an in-RAM database, rather than an on-disk one. It also cleans up the streamstore types exports a bit. --- .../test-change-parameters.js | 13 +- .../SwingSet/test/devices/test-devices.js | 6 +- .../promise-watcher/test-promise-watcher.js | 13 +- .../test/test-activityhash-vs-start.js | 18 +-- packages/SwingSet/test/test-state.js | 67 ++++----- .../SwingSet/test/test-transcript-light.js | 71 ++++----- packages/SwingSet/test/test-transcript.js | 49 +++---- .../test/upgrade/test-upgrade-replay.js | 15 +- .../SwingSet/test/upgrade/test-upgrade.js | 11 +- .../terminate/test-terminate-replay.js | 14 +- .../vat-admin/terminate/test-terminate.js | 14 +- .../SwingSet/test/vat-admin/test-replay.js | 14 +- packages/swing-store/src/snapStore.js | 28 +++- packages/swing-store/src/streamStore.js | 32 ++-- packages/swing-store/src/swingStore.js | 138 +++++++++--------- packages/swing-store/test/test-state.js | 49 +++---- 16 files changed, 265 insertions(+), 287 deletions(-) diff --git a/packages/SwingSet/test/change-parameters/test-change-parameters.js b/packages/SwingSet/test/change-parameters/test-change-parameters.js index cfd32a59c21..b5f0bae078d 100644 --- a/packages/SwingSet/test/change-parameters/test-change-parameters.js +++ b/packages/SwingSet/test/change-parameters/test-change-parameters.js @@ -3,7 +3,7 @@ import { test } from '../../tools/prepare-test-env-ava.js'; // eslint-disable-next-line import/order import { assert } from '@agoric/assert'; -import { initSwingStore, getAllState } from '@agoric/swing-store'; +import { initSwingStore } from '@agoric/swing-store'; import { initializeSwingset, makeSwingsetController } from '../../src/index.js'; import { kunser } from '../../src/lib/kmarshal.js'; @@ -11,17 +11,6 @@ function bfile(name) { return new URL(name, import.meta.url).pathname; } -// eslint-disable-next-line no-unused-vars -function dumpState(kernelStorage, vatID) { - const s = getAllState(kernelStorage).kvStuff; - const keys = Array.from(Object.keys(s)).sort(); - for (const k of keys) { - if (k.startsWith(`${vatID}.vs.`)) { - console.log(k, s[k]); - } - } -} - async function testChangeParameters(t) { const config = { bootstrap: 'bootstrap', diff --git a/packages/SwingSet/test/devices/test-devices.js b/packages/SwingSet/test/devices/test-devices.js index ca3fa38232d..528fb0333ab 100644 --- a/packages/SwingSet/test/devices/test-devices.js +++ b/packages/SwingSet/test/devices/test-devices.js @@ -2,7 +2,7 @@ import { test } from '../../tools/prepare-test-env-ava.js'; import bundleSource from '@endo/bundle-source'; -import { initSwingStore, getAllState } from '@agoric/swing-store'; +import { initSwingStore } from '@agoric/swing-store'; import { parse } from '@endo/marshal'; import { @@ -211,7 +211,7 @@ test.serial('d2.5', async t => { }); test.serial('device state', async t => { - const kernelStorage = initSwingStore().kernelStorage; + const { kernelStorage, debug } = initSwingStore(); const config = { bootstrap: 'bootstrap', vats: { @@ -236,7 +236,7 @@ test.serial('device state', async t => { const d3 = c1.deviceNameToID('d3'); await c1.run(); t.deepEqual(c1.dump().log, ['undefined', 'w+r', 'called', 'got {"s":"new"}']); - const s = getAllState(kernelStorage).kvStuff; + const s = debug.dump().kvEntries; t.deepEqual(JSON.parse(s[`${d3}.deviceState`]), kser({ s: 'new' })); t.deepEqual(JSON.parse(s[`${d3}.o.nextID`]), 10); }); diff --git a/packages/SwingSet/test/promise-watcher/test-promise-watcher.js b/packages/SwingSet/test/promise-watcher/test-promise-watcher.js index ed9b2bc2bb7..d4bbd070ac6 100644 --- a/packages/SwingSet/test/promise-watcher/test-promise-watcher.js +++ b/packages/SwingSet/test/promise-watcher/test-promise-watcher.js @@ -8,24 +8,13 @@ import { test } from '../../tools/prepare-test-env-ava.js'; // eslint-disable-next-line import/order import { assert } from '@agoric/assert'; // eslint-disable-next-line import/order -import { initSwingStore, getAllState } from '@agoric/swing-store'; +import { initSwingStore } from '@agoric/swing-store'; import { initializeSwingset, makeSwingsetController } from '../../src/index.js'; function bfile(name) { return new URL(name, import.meta.url).pathname; } -// eslint-disable-next-line no-unused-vars -function dumpState(kernelStorage, vatID) { - const s = getAllState(kernelStorage).kvStuff; - const keys = Array.from(Object.keys(s)).sort(); - for (const k of keys) { - if (k.startsWith(`${vatID}.vs.`)) { - console.log(k, s[k]); - } - } -} - async function testPromiseWatcher(t) { const config = { includeDevDependencies: true, // for vat-data diff --git a/packages/SwingSet/test/test-activityhash-vs-start.js b/packages/SwingSet/test/test-activityhash-vs-start.js index 31900b62e2a..018f5dc9d13 100644 --- a/packages/SwingSet/test/test-activityhash-vs-start.js +++ b/packages/SwingSet/test/test-activityhash-vs-start.js @@ -2,7 +2,7 @@ import { test } from '../tools/prepare-test-env-ava.js'; // eslint-disable-next-line import/order -import { initSwingStore, getAllState, setAllState } from '@agoric/swing-store'; +import { initSwingStore } from '@agoric/swing-store'; import { initializeSwingset, makeSwingsetController } from '../src/index.js'; import { buildTimer } from '../src/devices/timer/timer.js'; @@ -38,7 +38,7 @@ test.serial('restarting kernel does not change activityhash', async t => { const deviceEndowments1 = { timer: { ...timer1.endowments }, }; - const ks1 = initSwingStore().kernelStorage; + const { kernelStorage: ks1, debug: debug1 } = initSwingStore(); // console.log(`--c1 build`); await initializeSwingset(config, [], ks1); const c1 = await makeSwingsetController(ks1, deviceEndowments1); @@ -48,8 +48,8 @@ test.serial('restarting kernel does not change activityhash', async t => { // console.log(`--c1 run1`); await c1.run(); - // console.log(`--c1 getAllState`); - const state = getAllState(ks1); + // console.log(`--c1 serialize`); + const serialized = debug1.serialize(); // console.log(`ah: ${c1.getActivityhash()}`); // console.log(`--c1 poll1`); @@ -70,8 +70,7 @@ test.serial('restarting kernel does not change activityhash', async t => { const deviceEndowments2 = { timer: { ...timer2.endowments }, }; - const ks2 = initSwingStore().kernelStorage; - setAllState(ks2, state); + const { kernelStorage: ks2 } = initSwingStore(null, { serialized }); // console.log(`--c2 build`); const c2 = await makeSwingsetController(ks2, deviceEndowments2); // console.log(`ah: ${c2.getActivityhash()}`); @@ -102,14 +101,14 @@ test.serial('comms initialize is deterministic', async t => { const config = {}; config.bootstrap = 'bootstrap'; config.vats = { bootstrap: { sourceSpec } }; - const ks1 = initSwingStore().kernelStorage; + const { kernelStorage: ks1, debug: debug1 } = initSwingStore(); await initializeSwingset(config, [], ks1); const c1 = await makeSwingsetController(ks1, {}); c1.pinVatRoot('bootstrap'); // the bootstrap message will cause comms to initialize itself await c1.run(); - const state = getAllState(ks1); + const serialized = debug1.serialize(); // but the second message should not c1.queueToVatRoot('bootstrap', 'addRemote', ['remote2']); @@ -118,8 +117,7 @@ test.serial('comms initialize is deterministic', async t => { await c1.shutdown(); // a kernel restart is loading a new kernel from the same state - const ks2 = initSwingStore().kernelStorage; - setAllState(ks2, state); + const { kernelStorage: ks2 } = initSwingStore(null, { serialized }); const c2 = await makeSwingsetController(ks2, {}); // the "am I already initialized?" check must be identical to the diff --git a/packages/SwingSet/test/test-state.js b/packages/SwingSet/test/test-state.js index d09104a4c86..abb314ff02e 100644 --- a/packages/SwingSet/test/test-state.js +++ b/packages/SwingSet/test/test-state.js @@ -3,7 +3,7 @@ import { test } from '../tools/prepare-test-env-ava.js'; // eslint-disable-next-line import/order import { createHash } from 'crypto'; -import { initSwingStore, getAllState, setAllState } from '@agoric/swing-store'; +import { initSwingStore } from '@agoric/swing-store'; import makeKernelKeeper from '../src/kernel/state/kernelKeeper.js'; import { makeKernelStats } from '../src/kernel/state/stats.js'; import { KERNEL_STATS_METRICS } from '../src/kernel/metrics.js'; @@ -15,8 +15,8 @@ import { const ignoredStateKeys = ['activityhash', 'kernelStats', 'local.kernelStats']; -function checkState(t, getState, expected) { - const state = getState(); +function checkState(t, dump, expected) { + const state = dump().kvEntries; const got = []; for (const key of Object.getOwnPropertyNames(state)) { if (!ignoredStateKeys.includes(key)) { @@ -36,7 +36,7 @@ function checkState(t, getState, expected) { t.deepEqual(got.sort(compareStrings), expected.sort(compareStrings)); } -async function testStorage(t, s, getState, commit) { +async function testStorage(t, s, dump, commit) { t.falsy(s.has('missing')); t.is(s.get('missing'), undefined); @@ -59,10 +59,10 @@ async function testStorage(t, s, getState, commit) { t.deepEqual(Array.from(s.getKeys('foo1', 'foo4')), ['foo1', 'foo3']); if (commit) { - checkState(t, getState, []); + checkState(t, dump, []); await commit(); } - checkState(t, getState, [ + checkState(t, dump, [ ['foo', 'f'], ['foo1', 'f1'], ['foo3', 'f3'], @@ -70,17 +70,12 @@ async function testStorage(t, s, getState, commit) { } test('storageInMemory', async t => { - const kernelStorage = initSwingStore(null).kernelStorage; - await testStorage( - t, - kernelStorage.kvStore, - () => getAllState(kernelStorage).kvStuff, - null, - ); + const { kernelStorage, debug } = initSwingStore(null); + await testStorage(t, kernelStorage.kvStore, debug.dump, null); }); test('storage helpers', t => { - const kernelStorage = initSwingStore(null).kernelStorage; + const { kernelStorage, debug } = initSwingStore(null); const kv = kernelStorage.kvStore; kv.set('foo.0', 'f0'); @@ -89,7 +84,7 @@ test('storage helpers', t => { kv.set('foo.3', 'f3'); // omit foo.4 kv.set('foo.5', 'f5'); - checkState(t, () => getAllState(kernelStorage).kvStuff, [ + checkState(t, debug.dump, [ ['foo.0', 'f0'], ['foo.1', 'f1'], ['foo.2', 'f2'], @@ -114,20 +109,20 @@ test('storage helpers', t => { // zero, so if there is a gap in the key sequence (e.g., 'foo.4' in the // above), they stop counting when they hit it t.truthy(kv.has('foo.5')); - checkState(t, () => getAllState(kernelStorage).kvStuff, [['foo.5', 'f5']]); + checkState(t, debug.dump, [['foo.5', 'f5']]); }); function buildKeeperStorageInMemory() { - const kernelStorage = initSwingStore(null).kernelStorage; + const { kernelStorage, debug } = initSwingStore(null); return { - getState: () => getAllState(kernelStorage).kvStuff, + ...debug, // serialize, dump ...kernelStorage, }; } -function duplicateKeeper(getState) { - const kernelStorage = initSwingStore(null).kernelStorage; - setAllState(kernelStorage, { kvStuff: getState(), streamStuff: new Map() }); +function duplicateKeeper(serialize) { + const serialized = serialize(); + const { kernelStorage } = initSwingStore(null, { serialized }); const kernelKeeper = makeKernelKeeper(kernelStorage, null); kernelKeeper.loadStats(); return kernelKeeper; @@ -147,14 +142,13 @@ test('kernelStorage param guards', async t => { test('kernel state', async t => { const store = buildKeeperStorageInMemory(); - const { getState } = store; const k = makeKernelKeeper(store, null); t.truthy(!k.getInitialized()); k.createStartingKernelState({ defaultManagerType: 'local' }); k.setInitialized(); k.emitCrankHashes(); - checkState(t, getState, [ + checkState(t, store.dump, [ ['crankNumber', '0'], ['initialized', 'true'], ['gcActions', '[]'], @@ -180,7 +174,6 @@ test('kernel state', async t => { test('kernelKeeper vat names', async t => { const store = buildKeeperStorageInMemory(); - const { getState } = store; const k = makeKernelKeeper(store, null); k.createStartingKernelState({ defaultManagerType: 'local' }); @@ -190,7 +183,7 @@ test('kernelKeeper vat names', async t => { t.is(v2, 'v2'); k.emitCrankHashes(); - checkState(t, getState, [ + checkState(t, store.dump, [ ['crankNumber', '0'], ['gcActions', '[]'], ['runQueue', '[1,1]'], @@ -220,7 +213,7 @@ test('kernelKeeper vat names', async t => { t.is(k.getVatIDForName('Frank'), v2); t.is(k.allocateVatIDForNameIfNeeded('Frank'), v2); - const k2 = duplicateKeeper(getState); + const k2 = duplicateKeeper(store.serialize); t.deepEqual(k.getStaticVats(), [ ['Frank', 'v2'], ['vatname5', 'v1'], @@ -231,7 +224,6 @@ test('kernelKeeper vat names', async t => { test('kernelKeeper device names', async t => { const store = buildKeeperStorageInMemory(); - const { getState } = store; const k = makeKernelKeeper(store, null); k.createStartingKernelState({ defaultManagerType: 'local' }); @@ -241,7 +233,7 @@ test('kernelKeeper device names', async t => { t.is(d8, 'd8'); k.emitCrankHashes(); - checkState(t, getState, [ + checkState(t, store.dump, [ ['crankNumber', '0'], ['gcActions', '[]'], ['runQueue', '[1,1]'], @@ -271,7 +263,7 @@ test('kernelKeeper device names', async t => { t.is(k.getDeviceIDForName('Frank'), d8); t.is(k.allocateDeviceIDForNameIfNeeded('Frank'), d8); - const k2 = duplicateKeeper(getState); + const k2 = duplicateKeeper(store.serialize); t.deepEqual(k.getDevices(), [ ['Frank', 'd8'], ['devicename5', 'd7'], @@ -282,7 +274,6 @@ test('kernelKeeper device names', async t => { test('kernelKeeper runQueue', async t => { const store = buildKeeperStorageInMemory(); - const { getState } = store; const k = makeKernelKeeper(store, null); k.createStartingKernelState({ defaultManagerType: 'local' }); @@ -298,7 +289,7 @@ test('kernelKeeper runQueue', async t => { t.is(k.getRunQueueLength(), 2); k.emitCrankHashes(); - const k2 = duplicateKeeper(getState); + const k2 = duplicateKeeper(store.serialize); t.deepEqual(k.getNextRunQueueMsg(), { type: 'send', stuff: 'awesome' }); t.falsy(k.isRunQueueEmpty()); @@ -325,7 +316,6 @@ test('kernelKeeper runQueue', async t => { test('kernelKeeper promises', async t => { const store = buildKeeperStorageInMemory(); - const { getState } = store; const k = makeKernelKeeper(store, null); k.createStartingKernelState({ defaultManagerType: 'local' }); @@ -342,7 +332,7 @@ test('kernelKeeper promises', async t => { t.falsy(k.hasKernelPromise('kp99')); k.emitCrankHashes(); - let k2 = duplicateKeeper(getState); + let k2 = duplicateKeeper(store.serialize); t.deepEqual(k2.getKernelPromise(p1), { state: 'unresolved', @@ -365,7 +355,7 @@ test('kernelKeeper promises', async t => { }); k.emitCrankHashes(); - k2 = duplicateKeeper(getState); + k2 = duplicateKeeper(store.serialize); t.deepEqual(k2.getKernelPromise(p1), { state: 'unresolved', policy: 'ignore', @@ -405,7 +395,7 @@ test('kernelKeeper promises', async t => { expectedAcceptanceQueue.push({ type: 'send', target: 'kp40', msg: m2 }); k.emitCrankHashes(); - k2 = duplicateKeeper(getState); + k2 = duplicateKeeper(store.serialize); t.deepEqual(k2.getKernelPromise(p1).queue, [m1, m2]); const ko = k.addKernelObject('v1'); @@ -423,7 +413,7 @@ test('kernelKeeper promises', async t => { // all the subscriber/queue stuff should be gone k.emitCrankHashes(); - checkState(t, getState, [ + checkState(t, store.dump, [ ['crankNumber', '0'], ['device.nextID', '7'], ['vat.nextID', '1'], @@ -491,7 +481,6 @@ test('kernelKeeper promise reject', async t => { test('vatKeeper', async t => { const store = buildKeeperStorageInMemory(); - const { getState } = store; const k = makeKernelKeeper(store, null); k.createStartingKernelState({ defaultManagerType: 'local' }); @@ -509,7 +498,7 @@ test('vatKeeper', async t => { t.is(vk.nextDeliveryNum(), 1n); k.emitCrankHashes(); - let vk2 = duplicateKeeper(getState).provideVatKeeper(v1); + let vk2 = duplicateKeeper(store.serialize).provideVatKeeper(v1); t.is(vk2.mapVatSlotToKernelSlot(vatExport1), kernelExport1); t.is(vk2.mapKernelSlotToVatSlot(kernelExport1), vatExport1); t.is(vk2.nextDeliveryNum(), 2n); @@ -522,7 +511,7 @@ test('vatKeeper', async t => { t.is(vk.mapVatSlotToKernelSlot(vatImport2), kernelImport2); k.emitCrankHashes(); - vk2 = duplicateKeeper(getState).provideVatKeeper(v1); + vk2 = duplicateKeeper(store.serialize).provideVatKeeper(v1); t.is(vk2.mapKernelSlotToVatSlot(kernelImport2), vatImport2); t.is(vk2.mapVatSlotToKernelSlot(vatImport2), kernelImport2); }); diff --git a/packages/SwingSet/test/test-transcript-light.js b/packages/SwingSet/test/test-transcript-light.js index 98e421486bb..c595d7d41e6 100644 --- a/packages/SwingSet/test/test-transcript-light.js +++ b/packages/SwingSet/test/test-transcript-light.js @@ -1,35 +1,41 @@ // eslint-disable-next-line import/order import { test } from '../tools/prepare-test-env-ava.js'; // eslint-disable-next-line import/order -import { initSwingStore, getAllState, setAllState } from '@agoric/swing-store'; +import { initSwingStore } from '@agoric/swing-store'; import { buildVatController, loadBasedir } from '../src/index.js'; +// this test wants to compare the swing-store state from one run to +// another, so we need to dump the state in a deterministic fashion + test('transcript-light load', async t => { const config = await loadBasedir( new URL('basedir-transcript', import.meta.url).pathname, ); - const kernelStorage = initSwingStore().kernelStorage; + const { kernelStorage, debug } = initSwingStore(); const c = await buildVatController(config, ['one'], { kernelStorage }); t.teardown(c.shutdown); - const state0 = getAllState(kernelStorage); - t.is(state0.kvStuff.initialized, 'true'); - t.is(state0.kvStuff.runQueue, '[1,1]'); - t.not(state0.kvStuff.acceptanceQueue, '[]'); + const serialized0 = debug.serialize(); + const kvstate0 = debug.dump().kvEntries; + t.is(kvstate0.initialized, 'true'); + t.is(kvstate0.runQueue, '[1,1]'); + t.not(kvstate0.acceptanceQueue, '[]'); await c.step(); - const state1 = getAllState(kernelStorage); + const state1 = debug.dump(); + const serialized1 = debug.serialize(); await c.step(); - const state2 = getAllState(kernelStorage); + const state2 = debug.dump(); + const serialized2 = debug.serialize(); await c.step(); - const state3 = getAllState(kernelStorage); + const state3 = debug.dump(); await c.step(); - const state4 = getAllState(kernelStorage); + const state4 = debug.dump(); await c.step(); - const state5 = getAllState(kernelStorage); + const state5 = debug.dump(); // build from loaded state // Step 0 @@ -37,74 +43,71 @@ test('transcript-light load', async t => { const cfg0 = await loadBasedir( new URL('basedir-transcript', import.meta.url).pathname, ); - const kernelStorage0 = initSwingStore().kernelStorage; - setAllState(kernelStorage0, state0); + const ss0 = initSwingStore(null, { serialized: serialized0 }); const c0 = await buildVatController(cfg0, ['one'], { - kernelStorage: kernelStorage0, + kernelStorage: ss0.kernelStorage, }); t.teardown(c0.shutdown); await c0.step(); - t.deepEqual(state1, getAllState(kernelStorage0), `p1`); + t.deepEqual(state1, ss0.debug.dump(), `p1`); await c0.step(); - t.deepEqual(state2, getAllState(kernelStorage0), `p2`); + t.deepEqual(state2, ss0.debug.dump(), `p2`); await c0.step(); - t.deepEqual(state3, getAllState(kernelStorage0), `p3`); + t.deepEqual(state3, ss0.debug.dump(), `p3`); await c0.step(); - t.deepEqual(state4, getAllState(kernelStorage0), `p4`); + t.deepEqual(state4, ss0.debug.dump(), `p4`); await c0.step(); - t.deepEqual(state5, getAllState(kernelStorage0), `p5`); + t.deepEqual(state5, ss0.debug.dump(), `p5`); // Step 1 const cfg1 = await loadBasedir( new URL('basedir-transcript', import.meta.url).pathname, ); - const kernelStorage1 = initSwingStore().kernelStorage; - setAllState(kernelStorage1, state1); + const ss1 = initSwingStore(null, { serialized: serialized1 }); const c1 = await buildVatController(cfg1, ['one'], { - kernelStorage: kernelStorage1, + kernelStorage: ss1.kernelStorage, }); t.teardown(c1.shutdown); - t.deepEqual(state1, getAllState(kernelStorage1), `p6`); // actual, expected + t.deepEqual(state1, ss1.debug.dump(), `p6`); // actual, expected await c1.step(); - t.deepEqual(state2, getAllState(kernelStorage1), `p7`); + t.deepEqual(state2, ss1.debug.dump(), `p7`); await c1.step(); - t.deepEqual(state3, getAllState(kernelStorage1), `p8`); + t.deepEqual(state3, ss1.debug.dump(), `p8`); await c1.step(); - t.deepEqual(state4, getAllState(kernelStorage1), `p9`); + t.deepEqual(state4, ss1.debug.dump(), `p9`); await c1.step(); - t.deepEqual(state5, getAllState(kernelStorage1), `p10`); + t.deepEqual(state5, ss1.debug.dump(), `p10`); // Step 2 const cfg2 = await loadBasedir( new URL('basedir-transcript', import.meta.url).pathname, ); - const kernelStorage2 = initSwingStore().kernelStorage; - setAllState(kernelStorage2, state2); + const ss2 = initSwingStore(null, { serialized: serialized2 }); const c2 = await buildVatController(cfg2, ['one'], { - kernelStorage: kernelStorage2, + kernelStorage: ss2.kernelStorage, }); t.teardown(c2.shutdown); - t.deepEqual(state2, getAllState(kernelStorage2), `p11`); + t.deepEqual(state2, ss2.debug.dump(), `p11`); await c2.step(); - t.deepEqual(state3, getAllState(kernelStorage2), `p12`); + t.deepEqual(state3, ss2.debug.dump(), `p12`); await c2.step(); - t.deepEqual(state4, getAllState(kernelStorage2), `p13`); + t.deepEqual(state4, ss2.debug.dump(), `p13`); await c2.step(); - t.deepEqual(state5, getAllState(kernelStorage2), `p14`); + t.deepEqual(state5, ss2.debug.dump(), `p14`); }); diff --git a/packages/SwingSet/test/test-transcript.js b/packages/SwingSet/test/test-transcript.js index 8583bd4cc64..ee1acdb2421 100644 --- a/packages/SwingSet/test/test-transcript.js +++ b/packages/SwingSet/test/test-transcript.js @@ -1,19 +1,19 @@ // eslint-disable-next-line import/order import { test } from '../tools/prepare-test-env-ava.js'; // eslint-disable-next-line import/order -import { initSwingStore, getAllState, setAllState } from '@agoric/swing-store'; +import { initSwingStore } from '@agoric/swing-store'; // import fs from 'fs'; import { buildVatController, loadBasedir } from '../src/index.js'; -async function buildTrace(c, kernelStorage) { - const states = []; +async function buildTrace(c, debug) { + const states = []; // list of { dump, serialized } while (c.dump().runQueue.length && c.dump().gcActions.length) { - states.push(getAllState(kernelStorage)); + states.push({ dump: debug.dump(), serialized: debug.serialize() }); // eslint-disable-next-line no-await-in-loop await c.step(); } - states.push(getAllState(kernelStorage)); + states.push({ dump: debug.dump(), serialized: debug.serialize() }); await c.shutdown(); return states; } @@ -22,40 +22,27 @@ test('transcript-one save', async t => { const config = await loadBasedir( new URL('basedir-transcript', import.meta.url).pathname, ); - const kernelStorage = initSwingStore().kernelStorage; + const { kernelStorage, debug } = initSwingStore(); const c1 = await buildVatController(config, ['one'], { kernelStorage, }); - const states1 = await buildTrace(c1, kernelStorage); + const states1 = await buildTrace(c1, debug); /* states1.forEach( (s, i) => - fs.writeFileSync(`kdata-${i}.json`, JSON.stringify(s)) + fs.writeFileSync(`kdata-${i}.json`, JSON.stringify(s.dump)) ); */ const config2 = await loadBasedir( new URL('basedir-transcript', import.meta.url).pathname, ); - const kernelStorage2 = initSwingStore().kernelStorage; + const { kernelStorage: kernelStorage2, debug: debug2 } = initSwingStore(); const c2 = await buildVatController(config2, ['one'], { kernelStorage: kernelStorage2, }); - const states2 = await buildTrace(c2, kernelStorage2); + const states2 = await buildTrace(c2, debug2); states1.forEach((s, i) => { - // Too expensive! If there is a difference in the 3MB data, AVA will spin - // for a long time trying to compute a "minimal" diff. - // t.deepEqual(s, states2[i]); - - // Instead, we just do simple comparison. Leave investigation to the - // experts. - const s2 = states2[i]; - const extra = new Set(Object.keys(s2.kvStuff)); - for (const k of Object.keys(s.kvStuff)) { - t.assert(s.kvStuff[k] === s2.kvStuff[k], `states[${i}][${k}] differs`); - extra.delete(k); - } - t.deepEqual([...extra.keys()], [], `states2[${i}] has missing keys`); - t.deepEqual(s.streamStuff, s2.streamStuff); + t.deepEqual(s.dump, states2[i].dump); }); }); @@ -63,27 +50,27 @@ test('transcript-one load', async t => { const config = await loadBasedir( new URL('basedir-transcript', import.meta.url).pathname, ); - const s0 = initSwingStore().kernelStorage; + const { kernelStorage: s0, debug: d0 } = initSwingStore(); const c0 = await buildVatController(config, ['one'], { kernelStorage: s0 }); - const states = await buildTrace(c0, s0); + const states = await buildTrace(c0, d0); // states.forEach((s,j) => // fs.writeFileSync(`kdata-${j}.json`, - // JSON.stringify(states[j]))); + // JSON.stringify(states[j].dump))); for (let i = 0; i < states.length; i += 1) { // eslint-disable-next-line no-await-in-loop const cfg = await loadBasedir( new URL('basedir-transcript', import.meta.url).pathname, ); - const s = initSwingStore().kernelStorage; - setAllState(s, states[i]); + const { serialized } = states[i]; + const { kernelStorage: s, debug: d } = initSwingStore(null, { serialized }); // eslint-disable-next-line no-await-in-loop const c = await buildVatController(cfg, ['one'], { kernelStorage: s }); // eslint-disable-next-line no-await-in-loop - const newstates = await buildTrace(c, s); + const newstates = await buildTrace(c, d); // newstates.forEach((s,j) => // fs.writeFileSync(`kdata-${i+j}-${i}+${j}.json`, - // JSON.stringify(newstates[j]))); + // JSON.stringify(newstates[j].dump))); t.deepEqual(states.slice(i), newstates); } }); diff --git a/packages/SwingSet/test/upgrade/test-upgrade-replay.js b/packages/SwingSet/test/upgrade/test-upgrade-replay.js index ed5219521fd..7e26181248d 100644 --- a/packages/SwingSet/test/upgrade/test-upgrade-replay.js +++ b/packages/SwingSet/test/upgrade/test-upgrade-replay.js @@ -2,7 +2,7 @@ import { test } from '../../tools/prepare-test-env-ava.js'; import { assert } from '@agoric/assert'; -import { initSwingStore, getAllState, setAllState } from '@agoric/swing-store'; +import { initSwingStore } from '@agoric/swing-store'; import { buildKernelBundles, initializeSwingset, @@ -45,10 +45,10 @@ test('replay after upgrade', async t => { }; const { initOpts, runtimeOpts } = bundleOpts(t.context.data); - const kernelStorage1 = initSwingStore().kernelStorage; + const ss1 = initSwingStore(); { - await initializeSwingset(copy(config), [], kernelStorage1, initOpts); - const c1 = await makeSwingsetController(kernelStorage1, {}, runtimeOpts); + await initializeSwingset(copy(config), [], ss1.kernelStorage, initOpts); + const c1 = await makeSwingsetController(ss1.kernelStorage, {}, runtimeOpts); t.teardown(c1.shutdown); c1.pinVatRoot('bootstrap'); await c1.run(); @@ -66,11 +66,10 @@ test('replay after upgrade', async t => { } // copy the store just to be sure - const state1 = getAllState(kernelStorage1); - const kernelStorage2 = initSwingStore().kernelStorage; - setAllState(kernelStorage2, state1); + const serialized = ss1.debug.serialize(); + const ss2 = initSwingStore(null, { serialized }); { - const c2 = await makeSwingsetController(kernelStorage2, {}, runtimeOpts); + const c2 = await makeSwingsetController(ss2.kernelStorage, {}, runtimeOpts); t.teardown(c2.shutdown); c2.pinVatRoot('bootstrap'); await c2.run(); diff --git a/packages/SwingSet/test/upgrade/test-upgrade.js b/packages/SwingSet/test/upgrade/test-upgrade.js index d81af6231d5..cf8d14c1190 100644 --- a/packages/SwingSet/test/upgrade/test-upgrade.js +++ b/packages/SwingSet/test/upgrade/test-upgrade.js @@ -4,7 +4,7 @@ import { test } from '../../tools/prepare-test-env-ava.js'; // eslint-disable-next-line import/order import { assert } from '@agoric/assert'; import bundleSource from '@endo/bundle-source'; -import { initSwingStore, getAllState } from '@agoric/swing-store'; +import { initSwingStore } from '@agoric/swing-store'; import { parseReachableAndVatSlot } from '../../src/kernel/state/reachable.js'; import { parseVatSlot } from '../../src/lib/parseVatSlots.js'; import { kunser, krefOf } from '../../src/lib/kmarshal.js'; @@ -24,8 +24,8 @@ test.before(async t => { }); // eslint-disable-next-line no-unused-vars -const dumpState = (kernelStorage, vatID) => { - const s = getAllState(kernelStorage).kvStuff; +const dumpState = (debug, vatID) => { + const s = debug.dump().kvEntries; const keys = Array.from(Object.keys(s)).sort(); for (const k of keys) { if (k.startsWith(`${vatID}.vs.`)) { @@ -130,6 +130,7 @@ const testUpgrade = async ( }, }; + // const { kernelStorage, debug } = initSwingStore(); const { kernelStorage } = initSwingStore(); const { kvStore } = kernelStorage; const { initOpts, runtimeOpts } = bundleOpts(t.context.data); @@ -195,7 +196,7 @@ const testUpgrade = async ( return kvStore.has(`${vatID}.vs.vom.${vref}`); }; - // dumpState(kernelStorage, vatID); + // dumpState(debug, vatID); // deduce exporter vrefs for all durable/virtual objects, and assert // that they're still in DB @@ -261,7 +262,7 @@ const testUpgrade = async ( t.is(c.kpStatus(v1p2Kref), 'rejected'); t.deepEqual(kunser(c.kpResolution(v1p2Kref)), vatUpgradedError); - // dumpState(kernelStorage, vatID); + // dumpState(debug, vatID); // all the merely-virtual exports should be gone // for (let i = 1; i < NUM_SENSORS + 1; i += 1) { diff --git a/packages/SwingSet/test/vat-admin/terminate/test-terminate-replay.js b/packages/SwingSet/test/vat-admin/terminate/test-terminate-replay.js index 00ddbd48738..180b92c58a4 100644 --- a/packages/SwingSet/test/vat-admin/terminate/test-terminate-replay.js +++ b/packages/SwingSet/test/vat-admin/terminate/test-terminate-replay.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/order import { test } from '../../../tools/prepare-test-env-ava.js'; // eslint-disable-next-line import/order -import { initSwingStore, getAllState, setAllState } from '@agoric/swing-store'; +import { initSwingStore } from '@agoric/swing-store'; import { buildVatController, @@ -20,10 +20,10 @@ test.serial('replay does not resurrect dead vat', async t => { .pathname; const config = await loadSwingsetConfigFile(configPath); - const kernelStorage1 = initSwingStore().kernelStorage; + const ss1 = initSwingStore(); { const c1 = await buildVatController(config, [], { - kernelStorage: kernelStorage1, + kernelStorage: ss1.kernelStorage, kernelBundles: t.context.data.kernelBundles, }); await c1.run(); @@ -32,13 +32,11 @@ test.serial('replay does not resurrect dead vat', async t => { t.deepEqual(c1.dump().log, [`w: I ate'nt dead`]); } - const state1 = getAllState(kernelStorage1); - const kernelStorage2 = initSwingStore().kernelStorage; - // XXX TODO also copy transcripts - setAllState(kernelStorage2, state1); + const serialized = ss1.debug.serialize(); + const ss2 = initSwingStore(null, { serialized }); { const c2 = await buildVatController(config, [], { - kernelStorage: kernelStorage2, + kernelStorage: ss2.kernelStorage, kernelBundles: t.context.data.kernelBundles, }); await c2.run(); diff --git a/packages/SwingSet/test/vat-admin/terminate/test-terminate.js b/packages/SwingSet/test/vat-admin/terminate/test-terminate.js index 8e1f4068638..25062ef835d 100644 --- a/packages/SwingSet/test/vat-admin/terminate/test-terminate.js +++ b/packages/SwingSet/test/vat-admin/terminate/test-terminate.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/order import { test } from '../../../tools/prepare-test-env-ava.js'; // eslint-disable-next-line import/order -import { initSwingStore, getAllState, setAllState } from '@agoric/swing-store'; +import { initSwingStore } from '@agoric/swing-store'; import { buildVatController, @@ -389,10 +389,10 @@ test.serial('dispatches to the dead do not harm kernel', async t => { .pathname; const config = await loadSwingsetConfigFile(configPath); - const kernelStorage1 = initSwingStore().kernelStorage; + const ss1 = initSwingStore(); { const c1 = await buildVatController(config, [], { - kernelStorage: kernelStorage1, + kernelStorage: ss1.kernelStorage, kernelBundles: t.context.data.kernelBundles, }); t.teardown(c1.shutdown); @@ -407,13 +407,11 @@ test.serial('dispatches to the dead do not harm kernel', async t => { 'done: Error: arbitrary reason', ]); } - const state1 = getAllState(kernelStorage1); - const kernelStorage2 = initSwingStore().kernelStorage; - // XXX TODO also copy transcripts - setAllState(kernelStorage2, state1); + const serialized = ss1.debug.serialize(); + const ss2 = initSwingStore(null, { serialized }); { const c2 = await buildVatController(config, [], { - kernelStorage: kernelStorage2, + kernelStorage: ss2.kernelStorage, kernelBundles: t.context.data.kernelBundles, }); t.teardown(c2.shutdown); diff --git a/packages/SwingSet/test/vat-admin/test-replay.js b/packages/SwingSet/test/vat-admin/test-replay.js index 05ea20f34cf..c8c9cf7f3c0 100644 --- a/packages/SwingSet/test/vat-admin/test-replay.js +++ b/packages/SwingSet/test/vat-admin/test-replay.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/order import { test } from '../../tools/prepare-test-env-ava.js'; // eslint-disable-next-line import/order -import { initSwingStore, getAllState, setAllState } from '@agoric/swing-store'; +import { initSwingStore } from '@agoric/swing-store'; import { buildKernelBundles, buildVatController } from '../../src/index.js'; import { kser } from '../../src/lib/kmarshal.js'; @@ -30,11 +30,10 @@ test.serial('replay dynamic vat', async t => { bootstrap: 'bootstrap', }; - // XXX TODO: also copy and check transcripts - const kernelStorage1 = initSwingStore().kernelStorage; + const ss1 = initSwingStore(); { const c1 = await buildVatController(copy(config), [], { - kernelStorage: kernelStorage1, + kernelStorage: ss1.kernelStorage, kernelBundles: t.context.data.kernelBundles, }); t.teardown(c1.shutdown); @@ -48,12 +47,11 @@ test.serial('replay dynamic vat', async t => { // we could re-use the Storage object, but I'll be paranoid and create a // new one. - const state1 = getAllState(kernelStorage1); - const kernelStorage2 = initSwingStore().kernelStorage; - setAllState(kernelStorage2, state1); + const serialized = ss1.debug.serialize(); + const ss2 = initSwingStore(null, { serialized }); { const c2 = await buildVatController(copy(config), [], { - kernelStorage: kernelStorage2, + kernelStorage: ss2.kernelStorage, }); t.teardown(c2.shutdown); const r2 = c2.queueToVatRoot('bootstrap', 'check', [], 'panic'); diff --git a/packages/swing-store/src/snapStore.js b/packages/swing-store/src/snapStore.js index b804921fbaa..aca169daaa2 100644 --- a/packages/swing-store/src/snapStore.js +++ b/packages/swing-store/src/snapStore.js @@ -38,6 +38,11 @@ import { * deleteSnapshotByHash: (vatID: string, hash: string) => void, * getSnapshotInfo: (vatID: string) => SnapshotInfo, * }} SnapStore + * + * @typedef {{ + * dumpActiveSnapshots: () => {}, + * }} SnapStoreDebug + * */ /** @@ -75,7 +80,7 @@ const noPath = /** @type {import('fs').PathLike} */ ( * }} io * @param {object} [options] * @param {boolean | undefined} [options.keepSnapshots] - * @returns {SnapStore} + * @returns {SnapStore & SnapStoreDebug} */ export function makeSnapStore( db, @@ -354,6 +359,25 @@ export function makeSnapStore( sqlDeleteSnapshotByHash.run(vatID, hash); } + const sqlDumpActiveSnapshots = db.prepare(` + SELECT vatID, endPos, hash, compressedSnapshot + FROM snapshots + WHERE inUse = 1 + ORDER BY vatID, endPos + `); + + /** + * debug function to dump active snapshots + */ + function dumpActiveSnapshots() { + const dump = {}; + for (const row of sqlDumpActiveSnapshots.iterate()) { + const { vatID, endPos, hash, compressedSnapshot } = row; + dump[vatID] = { endPos, hash, compressedSnapshot }; + } + return dump; + } + return harden({ saveSnapshot, loadSnapshot, @@ -363,5 +387,7 @@ export function makeSnapStore( hasHash, deleteSnapshotByHash, + + dumpActiveSnapshots, }); } diff --git a/packages/swing-store/src/streamStore.js b/packages/swing-store/src/streamStore.js index 642a0e17374..1c978d4eb78 100644 --- a/packages/swing-store/src/streamStore.js +++ b/packages/swing-store/src/streamStore.js @@ -3,7 +3,19 @@ import { assert, Fail, q } from '@agoric/assert'; const STREAM_START = 0; /** - * @typedef { import('./swingStore').StreamPosition } StreamPosition + * @typedef { number } StreamPosition + * + * @typedef {{ + * writeStreamItem: (streamName: string, item: string, position: StreamPosition) => StreamPosition, + * readStream: (streamName: string, startPosition: StreamPosition, endPosition: StreamPosition) => IterableIterator, + * closeStream: (streamName: string) => void, + * STREAM_START: StreamPosition, + * }} StreamStore + * + * @typedef {{ + * dumpStreams: () => any, + * }} StreamStoreDebug + * */ function* empty() { @@ -33,6 +45,7 @@ function insistStreamPosition(position) { /** * @param {*} db * @param {() => void} ensureTxn + * @returns { StreamStore & StreamStoreDebug } */ export function makeStreamStore(db, ensureTxn) { db.exec(` @@ -49,21 +62,20 @@ export function makeStreamStore(db, ensureTxn) { const sqlDumpStreamsQuery = db.prepare(` SELECT streamName, position, item FROM streamItem - ORDER BY position + ORDER BY streamName, position `); function dumpStreams() { - const dump = new Map(); + // debug function to return: dump[streamName][position] = item + const streams = {}; for (const row of sqlDumpStreamsQuery.iterate()) { const { streamName, position, item } = row; - let entry = dump.get(streamName); - if (!entry) { - entry = []; - dump.set(streamName, entry); + if (!streams[streamName]) { + streams[streamName] = []; } - entry.push([position, item]); + streams[streamName][position] = item; } - return dump; + return streams; } const sqlReadStreamQuery = db.prepare(` @@ -144,7 +156,7 @@ export function makeStreamStore(db, ensureTxn) { writeStreamItem, readStream, closeStream, - dumpStreams, STREAM_START, + dumpStreams, }); } diff --git a/packages/swing-store/src/swingStore.js b/packages/swing-store/src/swingStore.js index 301f12334c3..7a7162dc316 100644 --- a/packages/swing-store/src/swingStore.js +++ b/packages/swing-store/src/swingStore.js @@ -1,4 +1,5 @@ // @ts-check +/* global Buffer */ import fs from 'fs'; import path from 'path'; import { performance } from 'perf_hooks'; @@ -38,18 +39,11 @@ export function makeSnapStoreIO() { * }} KVStore * * @typedef { import('./snapStore').SnapStore } SnapStore - * * @typedef { import('./snapStore').SnapshotResult } SnapshotResult * - * @typedef { number } StreamPosition - * - * @typedef {{ - * writeStreamItem: (streamName: string, item: string, position: StreamPosition) => StreamPosition, - * readStream: (streamName: string, startPosition: StreamPosition, endPosition: StreamPosition) => IterableIterator, - * closeStream: (streamName: string) => void, - * dumpStreams: () => any, - * STREAM_START: StreamPosition, - * }} StreamStore + * @typedef { import('./streamStore').StreamPosition } StreamPosition + * @typedef { import('./streamStore').StreamStore } StreamStore + * @typedef { import('./streamStore').StreamStoreDebug } StreamStoreDebug * * @typedef {{ * kvStore: KVStore, // a key-value StorageAPI object to load and store data on behalf of the kernel @@ -71,8 +65,20 @@ export function makeSnapStoreIO() { * }} SwingStoreHostStorage * * @typedef {{ + * kvEntries: {}, + * streams: {}, + * snapshots: {}, + * }} SwingStoreDebugDump + * + * @typedef {{ + * dump: () => SwingStoreDebugDump, + * serialize: () => Buffer, + * }} SwingStoreDebugTools + * + * @typedef {{ * kernelStorage: SwingStoreKernelStorage, * hostStorage: SwingStoreHostStorage, + * debug: SwingStoreDebugTools, * }} SwingStore */ @@ -132,7 +138,12 @@ export function makeSnapStoreIO() { * * @returns {SwingStore} */ -function makeSwingStore(dirPath, forceReset, options) { +function makeSwingStore(dirPath, forceReset, options = {}) { + const { serialized } = options; + if (serialized) { + assert(Buffer.isBuffer(serialized), `options.serialized must be Buffer`); + assert.equal(dirPath, null, `options.serialized makes :memory: DB`); + } let crankhasher; function resetCrankhash() { crankhasher = createSHA256(); @@ -185,9 +196,10 @@ function makeSwingStore(dirPath, forceReset, options) { /** @type {*} */ let db = sqlite3( - filePath, + serialized || filePath, // { verbose: console.log }, ); + db.exec(`PRAGMA journal_mode=WAL`); db.exec(`PRAGMA synchronous=FULL`); db.exec(` @@ -424,10 +436,14 @@ function makeSwingStore(dirPath, forceReset, options) { }, }; - const streamStore = makeStreamStore(db, ensureTxn); - const snapStore = makeSnapStore(db, makeSnapStoreIO(), { - keepSnapshots, - }); + const { dumpStreams, ...streamStore } = makeStreamStore(db, ensureTxn); + const { dumpActiveSnapshots, ...snapStore } = makeSnapStore( + db, + makeSnapStoreIO(), + { + keepSnapshots, + }, + ); const savepoints = []; const sqlReleaseSavepoints = db.prepare('RELEASE SAVEPOINT t0'); @@ -525,6 +541,37 @@ function makeSwingStore(dirPath, forceReset, options) { stopTrace(); } + /** + * Return a Buffer with the entire DB state, useful for cloning a + * small swingstore in unit tests. + * + * @returns {Buffer} + */ + function serialize() { + // An on-disk DB with WAL mode enabled seems to produce a + // serialized Buffer that can be unserialized, but the resulting + // 'db' object fails all operations with SQLITE_CANTOPEN. So + // pre-emptively throw. + if (filePath !== ':memory:') { + throw Error('on-disk DBs with WAL mode enabled do not serialize well'); + } + return db.serialize(); + } + + function dumpKVEntries() { + const s = db.prepare('SELECT key,value FROM kvStore ORDER BY key').raw(); + return Object.fromEntries(s.all()); + } + + function dump() { + // return comparable JS object graph with entire DB state + return harden({ + kvEntries: dumpKVEntries(), + streams: dumpStreams(), + snapshots: dumpActiveSnapshots(), + }); + } + const kernelStorage = { kvStore: kernelKVStore, streamStore, @@ -542,10 +589,15 @@ function makeSwingStore(dirPath, forceReset, options) { close, diskUsage, }; + const debug = { + serialize, + dump, + }; return harden({ kernelStorage, hostStorage, + debug, }); } @@ -609,57 +661,3 @@ export function isSwingStore(dirPath) { } return false; } - -/** - * Produce a representation of all the state found in a swing store. - * - * WARNING: This is a helper function intended for use in testing and debugging. - * It extracts *everything*, and does so in the simplest and stupidest possible - * way, hence it is likely to be a performance and memory hog if you attempt to - * use it on anything real. - * - * @param {SwingStoreKernelStorage} kernelStorage The swing store whose state is to be extracted. - * - * @returns {{ - * kvStuff: Record, - * streamStuff: Map>, - * }} A crude representation of all of the state of `kernelStorage` - */ -export function getAllState(kernelStorage) { - const { kvStore, streamStore } = kernelStorage; - /** @type { Record } */ - const kvStuff = {}; - for (const key of Array.from(kvStore.getKeys('', ''))) { - // @ts-expect-error get(key) of key from getKeys() is not undefined - kvStuff[key] = kvStore.get(key); - } - const streamStuff = streamStore.dumpStreams(); - return { kvStuff, streamStuff }; -} - -/** - * Stuff a bunch of state into a swing store. - * - * WARNING: This is intended to support testing and should not be used as a - * general store initialization mechanism. In particular, note that it does not - * bother to remove any pre-existing state from the store that it is given. - * - * @param {SwingStoreKernelStorage} kernelStorage The swing store whose state is to be set. - * @param {{ - * kvStuff: Record, - * streamStuff: Map>, - * }} stuff The state to stuff into `kernelStorage` - */ -export function setAllState(kernelStorage, stuff) { - const { kvStore, streamStore } = kernelStorage; - const { kvStuff, streamStuff } = stuff; - for (const k of Object.getOwnPropertyNames(kvStuff)) { - kvStore.set(k, kvStuff[k], true); - } - for (const [streamName, stream] of streamStuff.entries()) { - for (const [pos, item] of stream) { - streamStore.writeStreamItem(streamName, item, pos); - } - streamStore.closeStream(streamName); - } -} diff --git a/packages/swing-store/test/test-state.js b/packages/swing-store/test/test-state.js index 252f6d43475..bbcbc15f3bf 100644 --- a/packages/swing-store/test/test-state.js +++ b/packages/swing-store/test/test-state.js @@ -8,7 +8,6 @@ import test from 'ava'; import { initSwingStore, openSwingStore, - getAllState, isSwingStore, } from '../src/swingStore.js'; @@ -27,6 +26,14 @@ const tmpDir = prefix => }); }); +function checkKVState(t, kvStore) { + const keys = [...kvStore.getKeys('', '{')]; // basically everything + t.deepEqual(keys, ['foo', 'foo1', 'foo3']); + t.is(kvStore.get('foo'), 'f'); + t.is(kvStore.get('foo1'), 'f1'); + t.is(kvStore.get('foo3'), 'f3'); +} + function testKVStore(t, kernelStorage) { const kvStore = kernelStorage.kvStore; t.falsy(kvStore.has('missing')); @@ -49,45 +56,31 @@ function testKVStore(t, kernelStorage) { kvStore.delete('foo2'); t.falsy(kvStore.has('foo2')); t.is(kvStore.get('foo2'), undefined); - t.deepEqual(Array.from(kvStore.getKeys('foo1', 'foo4')), ['foo1', 'foo3']); - - const reference = { - kvStuff: { - foo: 'f', - foo1: 'f1', - foo3: 'f3', - }, - streamStuff: new Map(), - }; - t.deepEqual( - getAllState(kernelStorage), - reference, - 'check state after changes', - ); + checkKVState(t, kernelStorage.kvStore); } test('in-memory kvStore read/write', t => { - testKVStore(t, initSwingStore(null).kernelStorage); + const ss1 = initSwingStore(null); + testKVStore(t, ss1.kernelStorage); + const serialized = ss1.debug.serialize(); + const ss2 = initSwingStore(null, { serialized }); + checkKVState(t, ss2.kernelStorage.kvStore); }); test('persistent kvStore read/write/re-open', async t => { const [dbDir, cleanup] = await tmpDir('testdb'); t.teardown(cleanup); t.is(isSwingStore(dbDir), false); - const { kernelStorage, hostStorage } = initSwingStore(dbDir); - const { commit, close } = hostStorage; - testKVStore(t, kernelStorage); - await commit(); - const before = getAllState(kernelStorage); - await close(); + const ss1 = initSwingStore(dbDir); + testKVStore(t, ss1.kernelStorage); + await ss1.hostStorage.commit(); + await ss1.hostStorage.close(); t.is(isSwingStore(dbDir), true); - const { kernelStorage: kernelStorage2, hostStorage: hostStorage2 } = - openSwingStore(dbDir); - const { close: close2 } = hostStorage2; - t.deepEqual(getAllState(kernelStorage2), before, 'check state after reread'); + const ss2 = openSwingStore(dbDir); + checkKVState(t, ss2.kernelStorage.kvStore); + await ss2.hostStorage.close(); t.is(isSwingStore(dbDir), true); - await close2(); }); test('persistent kvStore maxKeySize write', async t => { From c6ecf28dd47c73ca132c6d44da86a9af66051739 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 19 Jan 2023 23:17:17 -0800 Subject: [PATCH 2/2] fix: make test-activityhash.. use xs-worker I'm seeing CI failures under Node-18 in this test, presumeably because we get GC variation between the first run and the replay. Super annoying, and not germane to what this test is supposed to be executing. The `test.serial` wrapper wasn't enough to fix it, so I've just switched the test to only use xs-worker. refs #3240 --- .../SwingSet/test/test-activityhash-vs-start.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/SwingSet/test/test-activityhash-vs-start.js b/packages/SwingSet/test/test-activityhash-vs-start.js index 018f5dc9d13..e512793c672 100644 --- a/packages/SwingSet/test/test-activityhash-vs-start.js +++ b/packages/SwingSet/test/test-activityhash-vs-start.js @@ -11,14 +11,16 @@ const TimerSrc = new URL( import.meta.url, ).pathname; -// all tests that are sensitive to GC timing (which means anything that -// exercises transcript replay or looks at activityHash) need to use -// test.serial or config.defaultManagerType='xsnap', until we figure out why -// gcAndFinalize sometimes doesn't work (details in #3240 and #4617) +// all tests that are sensitive to GC timing (which means anything +// that exercises transcript replay or looks at activityHash) need to +// use test.serial or config.defaultManagerType='xs-worker', until we +// figure out why gcAndFinalize sometimes doesn't work (details in +// #3240 and #4617). And even test.serial doesn't seem to be enough. -test.serial('restarting kernel does not change activityhash', async t => { +test('restarting kernel does not change activityhash', async t => { const sourceSpec = new URL('vat-empty-setup.js', import.meta.url).pathname; const config = { + defaultManagerType: 'xs-worker', bootstrap: 'bootstrap', vats: { bootstrap: { @@ -91,14 +93,14 @@ test.serial('restarting kernel does not change activityhash', async t => { t.is(c1ah, c2ah); }); -test.serial('comms initialize is deterministic', async t => { +test('comms initialize is deterministic', async t => { // bug #3726: comms was calling vatstoreGet('initialize') and // vatstoreSet('meta.o+0') during the first message after process restart, // which makes it a nondeterministic function of the input events. const sourceSpec = new URL('vat-activityhash-comms.js', import.meta.url) .pathname; - const config = {}; + const config = { defaultManagerType: 'xs-worker' }; config.bootstrap = 'bootstrap'; config.vats = { bootstrap: { sourceSpec } }; const { kernelStorage: ks1, debug: debug1 } = initSwingStore();