From 1459529d84eac1b65f26302d8400adb6f9c5b279 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Thu, 19 Sep 2024 03:02:16 +0000 Subject: [PATCH 01/11] chore(liveslots-tools): export fake allocatePromiseID --- packages/swingset-liveslots/tools/fakeVirtualSupport.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/swingset-liveslots/tools/fakeVirtualSupport.js b/packages/swingset-liveslots/tools/fakeVirtualSupport.js index 9a4658c0163..f1690c5788d 100644 --- a/packages/swingset-liveslots/tools/fakeVirtualSupport.js +++ b/packages/swingset-liveslots/tools/fakeVirtualSupport.js @@ -275,6 +275,7 @@ export function makeFakeLiveSlotsStuff(options = {}) { return { syscall, allocateExportID, + allocatePromiseID, allocateCollectionID, getSlotForVal, requiredValForSlot, From fc02f305817706bc80e2a0b0cd2a4901c9927115 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Fri, 20 Sep 2024 03:49:54 +0000 Subject: [PATCH 02/11] feat(liveslots-tools): expose insistAllDurableKindsReconnected --- packages/swingset-liveslots/tools/fakeVirtualObjectManager.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/swingset-liveslots/tools/fakeVirtualObjectManager.js b/packages/swingset-liveslots/tools/fakeVirtualObjectManager.js index 2acee35e1ef..d02fcdba82f 100644 --- a/packages/swingset-liveslots/tools/fakeVirtualObjectManager.js +++ b/packages/swingset-liveslots/tools/fakeVirtualObjectManager.js @@ -23,6 +23,7 @@ export function makeFakeVirtualObjectManager(vrm, fakeStuff) { VirtualObjectAwareWeakSet, flushStateCache, canBeDurable, + insistAllDurableKindsReconnected, } = makeVirtualObjectManager( fakeStuff.syscall, vrm, @@ -43,6 +44,7 @@ export function makeFakeVirtualObjectManager(vrm, fakeStuff) { defineDurableKindMulti, makeKindHandle, canBeDurable, + insistAllDurableKindsReconnected, VirtualObjectAwareWeakMap, VirtualObjectAwareWeakSet, }; From 9df0317f78b8b7c0ae12dae42e304c02e6386ad4 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Fri, 20 Sep 2024 03:19:45 +0000 Subject: [PATCH 03/11] fix(liveslots-tools): init IDs --- .../test/virtual-objects/virtualObjectManager.test.js | 8 ++++---- packages/swingset-liveslots/tools/fakeVirtualSupport.js | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/swingset-liveslots/test/virtual-objects/virtualObjectManager.test.js b/packages/swingset-liveslots/test/virtual-objects/virtualObjectManager.test.js index 6730608f62b..c98c4e80ea8 100644 --- a/packages/swingset-liveslots/test/virtual-objects/virtualObjectManager.test.js +++ b/packages/swingset-liveslots/test/virtual-objects/virtualObjectManager.test.js @@ -127,8 +127,8 @@ test('multifaceted virtual objects', t => { flushStateCache(); t.deepEqual(log.splice(0), [ - `get kindIDID => undefined`, `get idCounters => undefined`, + `get kindIDID => undefined`, `set kindIDID 1`, `set vom.vkind.2.descriptor {"kindID":"2","tag":"multithing"}`, `set vom.${kid}/1 ${multiThingVal('foo', 1)}`, @@ -203,8 +203,8 @@ test('virtual object operations', t => { // t3-0: 'thing-3' 200 0 const thing4 = makeThing('thing-4', 300); // [t4-0* t3-0* t2-0* t1-0*] // t4-0: 'thing-4' 300 0 - t.is(log.shift(), `get kindIDID => undefined`); t.is(log.shift(), `get idCounters => undefined`); + t.is(log.shift(), `get kindIDID => undefined`); t.is(log.shift(), `set kindIDID 1`); t.is(log.shift(), `set vom.vkind.2.descriptor {"kindID":"2","tag":"thing"}`); t.is(log.shift(), `set vom.vkind.3.descriptor {"kindID":"3","tag":"zot"}`); @@ -468,8 +468,8 @@ test('symbol named methods', t => { // t1-0: 'thing-1' 0 0 const thing2 = makeThing('thing-2', 100); // [t1-0* t2-0*] // t2-0: 'thing-2' 100 0 - t.is(log.shift(), `get kindIDID => undefined`); t.is(log.shift(), `get idCounters => undefined`); + t.is(log.shift(), `get kindIDID => undefined`); t.is(log.shift(), `set kindIDID 1`); t.is( log.shift(), @@ -649,8 +649,8 @@ test('virtual object gc', t => { }, }); - t.is(log.shift(), `get kindIDID => undefined`); t.is(log.shift(), `get idCounters => undefined`); + t.is(log.shift(), `get kindIDID => undefined`); t.is(log.shift(), `set kindIDID 1`); const skit = [ 'storeKindIDTable', diff --git a/packages/swingset-liveslots/tools/fakeVirtualSupport.js b/packages/swingset-liveslots/tools/fakeVirtualSupport.js index f1690c5788d..7f3db7cc79e 100644 --- a/packages/swingset-liveslots/tools/fakeVirtualSupport.js +++ b/packages/swingset-liveslots/tools/fakeVirtualSupport.js @@ -41,6 +41,7 @@ export function makeFakeLiveSlotsStuff(options = {}) { let vrm; function setVrm(vrmToUse) { assert(!vrm, 'vrm already configured'); + vrmToUse.initializeIDCounters(); vrm = vrmToUse; } From 6cc61bf258316aeac79cf0f0df3f8a2f284d488c Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Thu, 19 Sep 2024 03:04:53 +0000 Subject: [PATCH 04/11] refactor(liveslots-tools): always store a WeakRef in slotToVal --- .../swingset-liveslots/tools/fakeVirtualSupport.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/swingset-liveslots/tools/fakeVirtualSupport.js b/packages/swingset-liveslots/tools/fakeVirtualSupport.js index 7f3db7cc79e..7853df2318f 100644 --- a/packages/swingset-liveslots/tools/fakeVirtualSupport.js +++ b/packages/swingset-liveslots/tools/fakeVirtualSupport.js @@ -28,12 +28,14 @@ class FakeFinalizationRegistry { } class FakeWeakRef { + #target; + constructor(target) { - this.target = target; + this.#target = target; } deref() { - return this.target; // strong ref + return this.#target; // strong ref } } @@ -175,6 +177,7 @@ export function makeFakeLiveSlotsStuff(options = {}) { // and the WeakRef may or may not contain the target value. Use // options={weak:true} to match that behavior, or the default weak:false to // keep strong references. + const WeakRefForSlot = weak ? RealWeakRef : FakeWeakRef; const valToSlot = new WeakMap(); const slotToVal = new Map(); @@ -184,7 +187,7 @@ export function makeFakeLiveSlotsStuff(options = {}) { function getValForSlot(slot) { const d = slotToVal.get(slot); - return d && (weak ? d.deref() : d); + return d && d.deref(); } function requiredValForSlot(slot) { @@ -194,7 +197,7 @@ export function makeFakeLiveSlotsStuff(options = {}) { } function setValForSlot(slot, val) { - slotToVal.set(slot, weak ? new RealWeakRef(val) : val); + slotToVal.set(slot, new WeakRefForSlot(val)); } function convertValToSlot(val) { From 005617330ed4f75c9b6b46a29edd6985e1f253a2 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Thu, 19 Sep 2024 04:34:10 +0000 Subject: [PATCH 05/11] type(liveslots-tools): reincarnate types --- .../tools/fakeVirtualSupport.js | 2 +- .../swingset-liveslots/tools/setup-vat-data.js | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/swingset-liveslots/tools/fakeVirtualSupport.js b/packages/swingset-liveslots/tools/fakeVirtualSupport.js index 7853df2318f..a8e745810ce 100644 --- a/packages/swingset-liveslots/tools/fakeVirtualSupport.js +++ b/packages/swingset-liveslots/tools/fakeVirtualSupport.js @@ -344,7 +344,7 @@ export function makeFakeWatchedPromiseManager( * @param {object} [options] * @param {number} [options.cacheSize] * @param {boolean} [options.relaxDurabilityRules] - * @param {Map} [options.fakeStore] + * @param {Map} [options.fakeStore] * @param {WeakMapConstructor} [options.WeakMap] * @param {WeakSetConstructor} [options.WeakSet] * @param {boolean} [options.weak] diff --git a/packages/swingset-liveslots/tools/setup-vat-data.js b/packages/swingset-liveslots/tools/setup-vat-data.js index c3781d904ce..f509e5a92b0 100644 --- a/packages/swingset-liveslots/tools/setup-vat-data.js +++ b/packages/swingset-liveslots/tools/setup-vat-data.js @@ -12,7 +12,9 @@ import { makeFakeVirtualStuff } from './fakeVirtualSupport.js'; const { WeakMap, WeakSet } = globalThis; -/** @type {ReturnType} */ +/** @typedef {ReturnType} FakeVomKit */ + +/** @type {FakeVomKit} */ let fakeVomKit; globalThis.VatData = harden({ @@ -44,10 +46,22 @@ globalThis.VatData = harden({ globalThis[PassStyleOfEndowmentSymbol] = passStyleOf; +/** + * @typedef {import("@agoric/internal").Simplify< + * Omit[0]>, 'WeakMap' | 'WeakSet'> & + * { fakeVomKit: FakeVomKit; fakeStore: Map } + * >} ReincarnateOptions + */ + +/** + * + * @param {Partial} options + * @returns {ReincarnateOptions} + */ export const reincarnate = (options = {}) => { const { fakeStore = new Map(), fakeVomKit: fvk } = options; - if (options.fakeVomKit) { + if (fvk) { fvk.vom.flushStateCache(); fvk.cm.flushSchemaCache(); fvk.vrm.flushIDCounters(); From 21b89e0a019c64b6bbf248c2156276f9d4798597 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Thu, 19 Sep 2024 04:45:20 +0000 Subject: [PATCH 06/11] fix(liveslots-tools): reincarnate clones fakeStore --- .../tools/setup-vat-data.js | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/swingset-liveslots/tools/setup-vat-data.js b/packages/swingset-liveslots/tools/setup-vat-data.js index f509e5a92b0..36244e56ac8 100644 --- a/packages/swingset-liveslots/tools/setup-vat-data.js +++ b/packages/swingset-liveslots/tools/setup-vat-data.js @@ -56,10 +56,10 @@ globalThis[PassStyleOfEndowmentSymbol] = passStyleOf; /** * * @param {Partial} options - * @returns {ReincarnateOptions} + * @returns {Omit} */ -export const reincarnate = (options = {}) => { - const { fakeStore = new Map(), fakeVomKit: fvk } = options; +export const flushIncarnation = (options = {}) => { + const { fakeVomKit: fvk = fakeVomKit, ...fakeStuffOptions } = options; if (fvk) { fvk.vom.flushStateCache(); @@ -67,9 +67,22 @@ export const reincarnate = (options = {}) => { fvk.vrm.flushIDCounters(); } + // Clone previous fakeStore (if any) to avoid mutations from previous incarnation + const fakeStore = new Map(options.fakeStore); + + return { ...fakeStuffOptions, fakeStore }; +}; + +/** + * + * @param {Partial} options + * @returns {ReincarnateOptions} + */ +export const reincarnate = (options = {}) => { + const clonedIncarnation = flushIncarnation(options); + fakeVomKit = makeFakeVirtualStuff({ - ...options, - fakeStore, + ...clonedIncarnation, WeakMap, WeakSet, }); @@ -79,5 +92,5 @@ export const reincarnate = (options = {}) => { // @ts-expect-error ditto globalThis.WeakSet = fakeVomKit.vom.VirtualObjectAwareWeakSet; - return { ...options, fakeStore, fakeVomKit }; + return { ...clonedIncarnation, fakeVomKit }; }; From d98d89449d4bfc1419cd4410edef813db0e4ec55 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Fri, 20 Sep 2024 23:56:30 +0000 Subject: [PATCH 07/11] feat(liveslots-tools): prepare-strict-test-env --- .../tools/prepare-strict-test-env-ava.js | 19 ++++++++++++ packages/async-flow/package.json | 3 +- .../async-flow/test/prepare-test-env-ava.js | 22 ++------------ .../tools/prepare-strict-test-env.js | 30 +++++++++++++++++++ packages/zone/package.json | 2 +- packages/zone/test/exos.test.js | 3 +- packages/zone/test/make-once.test.js | 2 +- packages/zone/test/prepare-test-env-ava.js | 21 ------------- 8 files changed, 55 insertions(+), 47 deletions(-) create mode 100644 packages/SwingSet/tools/prepare-strict-test-env-ava.js create mode 100644 packages/swingset-liveslots/tools/prepare-strict-test-env.js delete mode 100644 packages/zone/test/prepare-test-env-ava.js diff --git a/packages/SwingSet/tools/prepare-strict-test-env-ava.js b/packages/SwingSet/tools/prepare-strict-test-env-ava.js new file mode 100644 index 00000000000..c9c7907ad5b --- /dev/null +++ b/packages/SwingSet/tools/prepare-strict-test-env-ava.js @@ -0,0 +1,19 @@ +/** + * Like prepare-strict-test-env but also sets up ses-ava and provides + * the ses-ava `test` function to be used as if it is the ava + * `test` function. + */ + +import '@agoric/swingset-liveslots/tools/prepare-strict-test-env.js'; + +import { wrapTest } from '@endo/ses-ava'; +import rawTest from 'ava'; + +export * from '@agoric/swingset-liveslots/tools/prepare-strict-test-env.js'; + +export const test = wrapTest(rawTest); + +// Does not import from a module because we're testing the global env +/* global globalThis */ +export const VatData = globalThis.VatData; +assert(VatData); diff --git a/packages/async-flow/package.json b/packages/async-flow/package.json index e7fadad8be8..ddb03c1744e 100644 --- a/packages/async-flow/package.json +++ b/packages/async-flow/package.json @@ -37,10 +37,9 @@ "@endo/promise-kit": "^1.1.5" }, "devDependencies": { - "@agoric/swingset-liveslots": "^0.10.2", + "@agoric/swingset-vat": "^0.32.2", "@agoric/zone": "^0.2.2", "@endo/env-options": "^1.1.6", - "@endo/ses-ava": "^1.2.5", "ava": "^5.3.0", "tsd": "^0.31.1" }, diff --git a/packages/async-flow/test/prepare-test-env-ava.js b/packages/async-flow/test/prepare-test-env-ava.js index fd00b0531ba..a4feee7d003 100644 --- a/packages/async-flow/test/prepare-test-env-ava.js +++ b/packages/async-flow/test/prepare-test-env-ava.js @@ -1,26 +1,8 @@ -import '@agoric/swingset-liveslots/tools/prepare-test-env.js'; -import { wrapTest } from '@endo/ses-ava'; -import rawTest from 'ava'; +import '@agoric/swingset-vat/tools/prepare-strict-test-env-ava.js'; import { environmentOptionsListHas } from '@endo/env-options'; -import { reincarnate } from '@agoric/swingset-liveslots/tools/setup-vat-data.js'; -export const test = wrapTest(rawTest); - -/** @type {ReturnType} */ -let incarnation; - -export const annihilate = () => { - incarnation = reincarnate({ relaxDurabilityRules: false }); -}; - -export const getBaggage = () => { - return incarnation.fakeVomKit.cm.provideBaggage(); -}; - -export const nextLife = () => { - incarnation = reincarnate(incarnation); -}; +export * from '@agoric/swingset-vat/tools/prepare-strict-test-env-ava.js'; export const asyncFlowVerbose = () => { // TODO figure out how we really want to control this. diff --git a/packages/swingset-liveslots/tools/prepare-strict-test-env.js b/packages/swingset-liveslots/tools/prepare-strict-test-env.js new file mode 100644 index 00000000000..7d52e630b10 --- /dev/null +++ b/packages/swingset-liveslots/tools/prepare-strict-test-env.js @@ -0,0 +1,30 @@ +/** + * Prepare Agoric SwingSet vat global strict environment for testing. + * + * Installs Hardened JS (and does lockdown), plus adds mocks for virtual objects + * and stores. + * + * Exports tools for simulating upgrades. + */ + +import '@agoric/internal/src/install-ses-debug.js'; + +import { reincarnate } from './setup-vat-data.js'; + +/** @type {ReturnType} */ +let incarnation; + +export const annihilate = () => { + incarnation = reincarnate({ relaxDurabilityRules: false }); +}; + +export const getBaggage = () => { + return incarnation.fakeVomKit.cm.provideBaggage(); +}; + +export const nextLife = () => { + incarnation = reincarnate(incarnation); +}; + +// Setup the initial incarnation +annihilate(); diff --git a/packages/zone/package.json b/packages/zone/package.json index 29bfa7d5445..8ad93d6cbaf 100644 --- a/packages/zone/package.json +++ b/packages/zone/package.json @@ -34,7 +34,7 @@ "@endo/pass-style": "^1.4.3" }, "devDependencies": { - "@agoric/swingset-liveslots": "^0.10.2", + "@agoric/swingset-vat": "^0.32.2", "@endo/patterns": "^1.4.3", "ava": "^5.3.0" }, diff --git a/packages/zone/test/exos.test.js b/packages/zone/test/exos.test.js index 31121190694..cf14ba03edb 100644 --- a/packages/zone/test/exos.test.js +++ b/packages/zone/test/exos.test.js @@ -1,10 +1,9 @@ -// eslint-disable-next-line import/order import { annihilate, getBaggage, nextLife, test, -} from './prepare-test-env-ava.js'; +} from '@agoric/swingset-vat/tools/prepare-strict-test-env-ava.js'; import * as vatData from '@agoric/vat-data'; diff --git a/packages/zone/test/make-once.test.js b/packages/zone/test/make-once.test.js index 0cdd021e23a..b6940e50b5e 100644 --- a/packages/zone/test/make-once.test.js +++ b/packages/zone/test/make-once.test.js @@ -3,7 +3,7 @@ import { getBaggage, nextLife, test, -} from './prepare-test-env-ava.js'; +} from '@agoric/swingset-vat/tools/prepare-strict-test-env-ava.js'; import { makeDurableZone } from '../durable.js'; import { makeHeapZone } from '../heap.js'; diff --git a/packages/zone/test/prepare-test-env-ava.js b/packages/zone/test/prepare-test-env-ava.js deleted file mode 100644 index a3d049993ce..00000000000 --- a/packages/zone/test/prepare-test-env-ava.js +++ /dev/null @@ -1,21 +0,0 @@ -import '@agoric/swingset-liveslots/tools/prepare-test-env.js'; -import { reincarnate } from '@agoric/swingset-liveslots/tools/setup-vat-data.js'; - -import test from 'ava'; - -export { test }; - -/** @type {ReturnType} */ -let incarnation; - -export const annihilate = () => { - incarnation = reincarnate({ relaxDurabilityRules: false }); -}; - -export const getBaggage = () => { - return incarnation.fakeVomKit.cm.provideBaggage(); -}; - -export const nextLife = () => { - incarnation = reincarnate(incarnation); -}; From a049c7b364dea2622899bb33c72c4b5a81b64c62 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Sat, 21 Sep 2024 01:14:48 +0000 Subject: [PATCH 08/11] test(liveslots-tools): extract exo utils --- packages/swingset-liveslots/test/exo-utils.js | 70 ++++++++++++++++++ .../test/handled-promises.test.js | 72 +------------------ 2 files changed, 72 insertions(+), 70 deletions(-) create mode 100644 packages/swingset-liveslots/test/exo-utils.js diff --git a/packages/swingset-liveslots/test/exo-utils.js b/packages/swingset-liveslots/test/exo-utils.js new file mode 100644 index 00000000000..0001ce529c7 --- /dev/null +++ b/packages/swingset-liveslots/test/exo-utils.js @@ -0,0 +1,70 @@ +import { provideLazy as provide } from '@agoric/store'; + +// Partially duplicates @agoric/vat-data to avoid circular dependencies. +export const makeExoUtils = VatData => { + const { defineDurableKind, makeKindHandle, watchPromise } = VatData; + + const provideKindHandle = (baggage, kindName) => + provide(baggage, `${kindName}_kindHandle`, () => makeKindHandle(kindName)); + + const emptyRecord = harden({}); + const initEmpty = () => emptyRecord; + + const defineDurableExoClass = ( + kindHandle, + interfaceGuard, + init, + methods, + options, + ) => + defineDurableKind(kindHandle, init, methods, { + ...options, + thisfulMethods: true, + interfaceGuard, + }); + + const prepareExoClass = ( + baggage, + kindName, + interfaceGuard, + init, + methods, + options = undefined, + ) => + defineDurableExoClass( + provideKindHandle(baggage, kindName), + interfaceGuard, + init, + methods, + options, + ); + + const prepareExo = ( + baggage, + kindName, + interfaceGuard, + methods, + options = undefined, + ) => { + const makeSingleton = prepareExoClass( + baggage, + kindName, + interfaceGuard, + initEmpty, + methods, + options, + ); + return provide(baggage, `the_${kindName}`, () => makeSingleton()); + }; + + return { + defineDurableKind, + makeKindHandle, + watchPromise, + + provideKindHandle, + defineDurableExoClass, + prepareExoClass, + prepareExo, + }; +}; diff --git a/packages/swingset-liveslots/test/handled-promises.test.js b/packages/swingset-liveslots/test/handled-promises.test.js index e9e7bd3c29e..407ea25dae4 100644 --- a/packages/swingset-liveslots/test/handled-promises.test.js +++ b/packages/swingset-liveslots/test/handled-promises.test.js @@ -2,7 +2,7 @@ import test from 'ava'; import { Fail } from '@endo/errors'; import { Far } from '@endo/marshal'; -import { M, provideLazy as provide } from '@agoric/store'; +import { M } from '@agoric/store'; import { makePromiseKit } from '@endo/promise-kit'; // Disabled to avoid circular dependencies. // import { makeStoreUtils } from '@agoric/vat-data/src/vat-data-bindings.js'; @@ -10,79 +10,11 @@ import { makePromiseKit } from '@endo/promise-kit'; import { kslot, kser } from '@agoric/kmarshal'; import { setupTestLiveslots } from './liveslots-helpers.js'; import { makeResolve, makeReject } from './util.js'; +import { makeExoUtils } from './exo-utils.js'; // eslint-disable-next-line no-unused-vars const compareEntriesByKey = ([ka], [kb]) => (ka < kb ? -1 : 1); -// Paritally duplicates @agoric/vat-data to avoid circular dependencies. -const makeExoUtils = VatData => { - const { defineDurableKind, makeKindHandle, watchPromise } = VatData; - - const provideKindHandle = (baggage, kindName) => - provide(baggage, `${kindName}_kindHandle`, () => makeKindHandle(kindName)); - - const emptyRecord = harden({}); - const initEmpty = () => emptyRecord; - - const defineDurableExoClass = ( - kindHandle, - interfaceGuard, - init, - methods, - options, - ) => - defineDurableKind(kindHandle, init, methods, { - ...options, - thisfulMethods: true, - interfaceGuard, - }); - - const prepareExoClass = ( - baggage, - kindName, - interfaceGuard, - init, - methods, - options = undefined, - ) => - defineDurableExoClass( - provideKindHandle(baggage, kindName), - interfaceGuard, - init, - methods, - options, - ); - - const prepareExo = ( - baggage, - kindName, - interfaceGuard, - methods, - options = undefined, - ) => { - const makeSingleton = prepareExoClass( - baggage, - kindName, - interfaceGuard, - initEmpty, - methods, - options, - ); - return provide(baggage, `the_${kindName}`, () => makeSingleton()); - }; - - return { - defineDurableKind, - makeKindHandle, - watchPromise, - - provideKindHandle, - defineDurableExoClass, - prepareExoClass, - prepareExo, - }; -}; - // cf. packages/SwingSet/test/vat-durable-promise-watcher.js const buildPromiseWatcherRootObject = (vatPowers, vatParameters, baggage) => { const { VatData } = vatPowers; From ae0557309297e29b4ca8307f7a3aefc72799436b Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Sat, 21 Sep 2024 00:02:02 +0000 Subject: [PATCH 09/11] feat(liveslots-tools): add startLife test tool --- .../test/strict-test-env-upgrade.test.js | 94 +++++++++++++++++++ .../tools/prepare-strict-test-env.js | 69 ++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 packages/swingset-liveslots/test/strict-test-env-upgrade.test.js diff --git a/packages/swingset-liveslots/test/strict-test-env-upgrade.test.js b/packages/swingset-liveslots/test/strict-test-env-upgrade.test.js new file mode 100644 index 00000000000..e6734249972 --- /dev/null +++ b/packages/swingset-liveslots/test/strict-test-env-upgrade.test.js @@ -0,0 +1,94 @@ +/* global globalThis */ +// eslint-disable-next-line import/order +import { annihilate, startLife } from '../tools/prepare-strict-test-env.js'; + +import test from 'ava'; + +import { makeUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js'; +import { makeExoUtils } from './exo-utils.js'; + +test.serial('kind redefinition enforced', async t => { + annihilate(); + + const { prepareExoClass } = makeExoUtils(globalThis.VatData); + + await startLife(async baggage => { + const makeTestExo = prepareExoClass( + baggage, + 'TestExo', + undefined, + () => ({}), + { + foo() { + return 'bar'; + }, + }, + ); + + baggage.init('testExo', makeTestExo()); + }); + + await t.throwsAsync( + async () => + startLife(async () => { + // Not redefining the kind here + }), + { message: 'defineDurableKind not called for tags: [TestExo]' }, + ); + + await startLife(async baggage => { + prepareExoClass(baggage, 'TestExo', undefined, () => ({}), { + foo() { + return 'baz'; + }, + }); + + t.is(baggage.get('testExo').foo(), 'baz'); + }); +}); + +test.serial('decided promise rejected', async t => { + annihilate(); + + const { prepareExo } = makeExoUtils(globalThis.VatData); + const { watchPromise } = globalThis.VatData; + + t.plan(1); + + await startLife(async baggage => { + const watcher = prepareExo( + baggage, + 'DurablePromiseTestWatcher', + undefined, + { + onFulfilled(value) { + t.fail( + `First incarnation watcher onFulfilled triggered with value ${value}`, + ); + }, + onRejected(reason) { + t.fail( + `First incarnation watcher onRejected triggered with reason ${reason}`, + ); + }, + }, + ); + + const never = harden(new Promise(() => {})); + + watchPromise(never, watcher); + }); + + await startLife(async baggage => { + prepareExo(baggage, 'DurablePromiseTestWatcher', undefined, { + onFulfilled(value) { + t.fail( + `Second incarnation watcher onFulfilled triggered with value ${value}`, + ); + }, + onRejected(reason) { + t.deepEqual(reason, makeUpgradeDisconnection('vat upgraded', 1)); + }, + }); + }); +}); diff --git a/packages/swingset-liveslots/tools/prepare-strict-test-env.js b/packages/swingset-liveslots/tools/prepare-strict-test-env.js index 7d52e630b10..f2ab3f49206 100644 --- a/packages/swingset-liveslots/tools/prepare-strict-test-env.js +++ b/packages/swingset-liveslots/tools/prepare-strict-test-env.js @@ -9,13 +9,23 @@ import '@agoric/internal/src/install-ses-debug.js'; +// eslint-disable-next-line import/order import { reincarnate } from './setup-vat-data.js'; +import { makePromiseKit } from '@endo/promise-kit'; +import { Fail } from '@endo/errors'; +import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; +import { makeUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js'; + +export { eventLoopIteration as nextCrank }; + /** @type {ReturnType} */ let incarnation; +let incarnationNumber = 0; export const annihilate = () => { incarnation = reincarnate({ relaxDurabilityRules: false }); + incarnationNumber = 0; }; export const getBaggage = () => { @@ -24,6 +34,65 @@ export const getBaggage = () => { export const nextLife = () => { incarnation = reincarnate(incarnation); + incarnationNumber += 1; +}; + +/** + * @template {(baggage: import('@agoric/swingset-liveslots').Baggage) => Promise | any} B + * @param {B} build + * @param {(tools: Awaited>) => Promise | void} [run] + */ +export const startLife = async (build, run) => { + await eventLoopIteration(); + const oldIncarnationNumber = incarnationNumber; + const oldIncarnation = incarnation; + const disconnectionObject = makeUpgradeDisconnection( + 'vat upgraded', + oldIncarnationNumber, + ); + nextLife(); + const { fakeVomKit } = incarnation; + /** @type {Map>} */ + const previouslyWatchedPromises = new Map(); + let buildTools; + try { + buildTools = await build(getBaggage()); + fakeVomKit.wpm.loadWatchedPromiseTable(vref => { + // See revivePromise in liveslots.js + const { getValForSlot, valToSlot, setValForSlot } = fakeVomKit.fakeStuff; + // Assume all promises were decided by the previous incarnation + !getValForSlot(vref) || Fail`Attempting to revive known promise ${vref}`; + const pk = makePromiseKit(); + previouslyWatchedPromises.set(vref, pk); + const val = pk.promise; + valToSlot.set(val, vref); + setValForSlot(vref, val); + return val; + }); + + fakeVomKit.vom.insistAllDurableKindsReconnected(); + + await eventLoopIteration(); + // End of start crank + } catch (err) { + // Rollback upgrade + incarnation = oldIncarnation; + incarnationNumber = oldIncarnationNumber; + throw err; + } + + // Simulate a dispatch of previously decided promise rejections + // In real swingset this could happen after some deliveries + for (const { reject } of previouslyWatchedPromises.values()) { + reject(disconnectionObject); + } + await eventLoopIteration(); + // End of resolution dispatch crank + + if (run) { + await run(buildTools); + await eventLoopIteration(); + } }; // Setup the initial incarnation From 6c15cc99d1c8084b2d4e88e757c78dbd9c8497bd Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Sat, 21 Sep 2024 00:59:44 +0000 Subject: [PATCH 10/11] feat(liveslots-tools): return incarnation in test tools --- .../tools/prepare-strict-test-env.js | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/swingset-liveslots/tools/prepare-strict-test-env.js b/packages/swingset-liveslots/tools/prepare-strict-test-env.js index f2ab3f49206..c785f376d34 100644 --- a/packages/swingset-liveslots/tools/prepare-strict-test-env.js +++ b/packages/swingset-liveslots/tools/prepare-strict-test-env.js @@ -10,48 +10,68 @@ import '@agoric/internal/src/install-ses-debug.js'; // eslint-disable-next-line import/order -import { reincarnate } from './setup-vat-data.js'; +import { reincarnate, flushIncarnation } from './setup-vat-data.js'; import { makePromiseKit } from '@endo/promise-kit'; import { Fail } from '@endo/errors'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { makeUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js'; +export { flushIncarnation }; export { eventLoopIteration as nextCrank }; -/** @type {ReturnType} */ +/** + * @import { ReincarnateOptions } from './setup-vat-data.js' + */ + +/** @type {ReincarnateOptions} */ let incarnation; let incarnationNumber = 0; -export const annihilate = () => { - incarnation = reincarnate({ relaxDurabilityRules: false }); +/** @param {Omit} [options] */ +export const annihilate = (options = {}) => { + // @ts-expect-error fakeStore and fakeVomKit don't exist on type, but drop them if they do at runtime + const { fakeStore: _fs, fakeVomKit: _fvk, ...opts } = options; + incarnation = reincarnate({ relaxDurabilityRules: false, ...opts }); incarnationNumber = 0; + return incarnation; }; export const getBaggage = () => { return incarnation.fakeVomKit.cm.provideBaggage(); }; -export const nextLife = () => { - incarnation = reincarnate(incarnation); +/** + * @param {ReincarnateOptions} [fromIncarnation] + */ +export const nextLife = (fromIncarnation = incarnation) => { + incarnation = reincarnate(fromIncarnation); incarnationNumber += 1; + return incarnation; }; /** * @template {(baggage: import('@agoric/swingset-liveslots').Baggage) => Promise | any} B * @param {B} build * @param {(tools: Awaited>) => Promise | void} [run] + * @param {object} [options] + * @param {ReincarnateOptions} [options.fromIncarnation] + * @param {boolean} [options.cleanStart] */ -export const startLife = async (build, run) => { +export const startLife = async ( + build, + run, + { fromIncarnation, cleanStart } = {}, +) => { await eventLoopIteration(); + if (cleanStart) annihilate(); const oldIncarnationNumber = incarnationNumber; const oldIncarnation = incarnation; const disconnectionObject = makeUpgradeDisconnection( 'vat upgraded', oldIncarnationNumber, ); - nextLife(); - const { fakeVomKit } = incarnation; + const { fakeVomKit } = nextLife(fromIncarnation); /** @type {Map>} */ const previouslyWatchedPromises = new Map(); let buildTools; @@ -93,6 +113,8 @@ export const startLife = async (build, run) => { await run(buildTools); await eventLoopIteration(); } + + return incarnation; }; // Setup the initial incarnation From 9300ae853ce87010f800ee7be3a1b6d561667216 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Sat, 21 Sep 2024 03:05:23 +0000 Subject: [PATCH 11/11] test(vow): add fake liveslots upgrade test --- packages/vow/package.json | 2 + packages/vow/test/watch-upgrade.test.js | 65 +++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 packages/vow/test/watch-upgrade.test.js diff --git a/packages/vow/package.json b/packages/vow/package.json index 7acbfa1b4dc..7a21313c8ff 100755 --- a/packages/vow/package.json +++ b/packages/vow/package.json @@ -30,6 +30,8 @@ }, "devDependencies": { "@agoric/internal": "^0.3.2", + "@agoric/swingset-vat": "^0.32.2", + "@agoric/zone": "^0.2.2", "@endo/far": "^1.1.5", "@endo/init": "^1.1.4", "ava": "^5.3.0", diff --git a/packages/vow/test/watch-upgrade.test.js b/packages/vow/test/watch-upgrade.test.js new file mode 100644 index 00000000000..6fdccd4094d --- /dev/null +++ b/packages/vow/test/watch-upgrade.test.js @@ -0,0 +1,65 @@ +import { + annihilate, + startLife, + test, +} from '@agoric/swingset-vat/tools/prepare-strict-test-env-ava.js'; + +import { makeDurableZone } from '@agoric/zone/durable.js'; + +import { prepareVowTools } from '../vat.js'; + +test.serial('vow resolve across upgrade', async t => { + annihilate(); + + t.plan(1); + + await startLife(async baggage => { + const zone = makeDurableZone(baggage, 'durableRoot'); + const vowTools = prepareVowTools(zone); + const watcher = zone.exo('DurableVowTestWatcher', undefined, { + onFulfilled(value) { + t.fail( + `First incarnation watcher onFulfilled triggered with value ${value}`, + ); + }, + onRejected(reason) { + t.fail( + `First incarnation watcher onRejected triggered with reason ${reason}`, + ); + }, + }); + + const testVowKit = zone.makeOnce('testVowKit', () => vowTools.makeVowKit()); + + vowTools.watch(testVowKit.vow, watcher); + }); + + await startLife( + baggage => { + const zone = makeDurableZone(baggage, 'durableRoot'); + prepareVowTools(zone); + // We're only preparing the watcher exo with new behavior. The instance is + // already attached as a watcher for the test vow + zone.exo('DurableVowTestWatcher', undefined, { + onFulfilled(value) { + t.is(value, 42, 'vow resolved with value 42'); + }, + onRejected(reason) { + t.fail( + `Second incarnation watcher onRejected triggered with reason ${reason}`, + ); + }, + }); + + /** @type {import('../src/types.js').VowResolver} */ + const testVowKitResolver = zone.makeOnce('testVowKit', () => { + t.fail('testVowKit maker called'); + }).resolver; + + return { testVowKitResolver }; + }, + async ({ testVowKitResolver }) => { + testVowKitResolver.resolve(42); + }, + ); +});