Skip to content

Commit

Permalink
Add strict liveslots test env with upgrade tools (#10126)
Browse files Browse the repository at this point in the history
closes: #9126

Best reviewed commit-by-commit

## Description
This PR add a new `prepare-strict-test-env.js` which enforces durable requirements on the baggage and provide helpers to simulate upgrades based on the helper from the `async-flow` and `zone` package tests.

In particular it adds a new `startLife` helper which enforces kind redefinition rules for a `build` step, and rejects previously watched promises (assuming they were all decided by the previous incarnation).

### Security Considerations

None, test infra only

### Scaling Considerations

None

### Documentation Considerations

Some types added and clarified. Internal tooling

### Testing Considerations

Adds tests of the new strict env and `startLife` helper.
Add liveslots based test in `vow` using the new `startLife` helper.
Updated the test environments in the `zone` and `async-flow` package to use the new liveslots env, but does not update the `async-flow` tests to use the stricter `startLife` (will be done in #9933 or #9383)

### Upgrade Considerations
Better upgrade testing without needing a full swingset kernel
  • Loading branch information
mergify[bot] authored Sep 25, 2024
2 parents b282e92 + 9300ae8 commit 0cd32a5
Show file tree
Hide file tree
Showing 17 changed files with 429 additions and 133 deletions.
19 changes: 19 additions & 0 deletions packages/SwingSet/tools/prepare-strict-test-env-ava.js
Original file line number Diff line number Diff line change
@@ -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);
3 changes: 1 addition & 2 deletions packages/async-flow/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
22 changes: 2 additions & 20 deletions packages/async-flow/test/prepare-test-env-ava.js
Original file line number Diff line number Diff line change
@@ -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<typeof reincarnate>} */
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.
Expand Down
70 changes: 70 additions & 0 deletions packages/swingset-liveslots/test/exo-utils.js
Original file line number Diff line number Diff line change
@@ -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,
};
};
72 changes: 2 additions & 70 deletions packages/swingset-liveslots/test/handled-promises.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,87 +2,19 @@ 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';
// import { makeExoUtils } from '@agoric/vat-data/src/exo-utils.js';
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;
Expand Down
94 changes: 94 additions & 0 deletions packages/swingset-liveslots/test/strict-test-env-upgrade.test.js
Original file line number Diff line number Diff line change
@@ -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));
},
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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)}`,
Expand Down Expand Up @@ -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"}`);
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions packages/swingset-liveslots/tools/fakeVirtualObjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export function makeFakeVirtualObjectManager(vrm, fakeStuff) {
VirtualObjectAwareWeakSet,
flushStateCache,
canBeDurable,
insistAllDurableKindsReconnected,
} = makeVirtualObjectManager(
fakeStuff.syscall,
vrm,
Expand All @@ -43,6 +44,7 @@ export function makeFakeVirtualObjectManager(vrm, fakeStuff) {
defineDurableKindMulti,
makeKindHandle,
canBeDurable,
insistAllDurableKindsReconnected,
VirtualObjectAwareWeakMap,
VirtualObjectAwareWeakSet,
};
Expand Down
Loading

0 comments on commit 0cd32a5

Please sign in to comment.