Skip to content

Commit

Permalink
liveslots: add new test of durable collections across upgrades
Browse files Browse the repository at this point in the history
Check that the refcounts are maintained correctly
  • Loading branch information
warner committed Apr 7, 2023
1 parent 2c342ea commit b147977
Showing 1 changed file with 135 additions and 0 deletions.
135 changes: 135 additions & 0 deletions packages/swingset-liveslots/test/test-collection-upgrade.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/* eslint-disable no-await-in-loop, @jessie.js/no-nested-await, no-shadow */
import test from 'ava';
import '@endo/init/debug.js';

import { Far } from '@endo/marshal';
import { M } from '@agoric/store';
import { makeLiveSlots } from '../src/liveslots.js';
import { parseVatSlot } from '../src/parseVatSlots.js';
import { kser } from './kmarshal.js';
import { buildSyscall } from './liveslots-helpers.js';
import { makeStartVat } from './util.js';
import { makeMockGC } from './mock-gc.js';

test('durable collections survive upgrade', async t => {
let map1;
let set1;
let thing1;
let thing2;
let thing3;

function build1(vatPowers, _vatParameters, baggage) {
const { VatData } = vatPowers;
const handle = VatData.makeKindHandle('thing');
baggage.init('handle', handle);
const initData = name => ({ name });
const how = { name: ({ state }) => state.name };
const makeDurableThing = VatData.defineDurableKind(handle, initData, how);
thing1 = makeDurableThing('thing1');
thing2 = makeDurableThing('thing2');
thing3 = makeDurableThing('thing3');
const valueShape = [thing1, M.any()];
const durable = true;
map1 = VatData.makeScalarBigMapStore('map', { valueShape, durable });
baggage.init('map', map1);
map1.init('string', harden([thing1, 'string']));
map1.init('number', harden([thing1, 123]));
map1.init('thing2', harden([thing1, thing2]));
// thing3 is only used as the valueShape
set1 = VatData.makeScalarBigSetStore('set', {
valueShape: thing3,
durable,
});
baggage.init('set', set1);
return Far('root', {});
}
const make1 = () => ({ buildRootObject: build1 });

const kvStore = new Map();
const { syscall: sc1 } = buildSyscall({ kvStore });
const gcTools1 = makeMockGC();
const ls1 = makeLiveSlots(sc1, 'vatA', {}, {}, gcTools1, undefined, make1);

const startVat = makeStartVat(kser());
await ls1.dispatch(startVat);

const mapVref = ls1.testHooks.valToSlot.get(map1);
const setVref = ls1.testHooks.valToSlot.get(set1);
const thing1Vref = ls1.testHooks.valToSlot.get(thing1);
const thing2Vref = ls1.testHooks.valToSlot.get(thing2);
const thing3Vref = ls1.testHooks.valToSlot.get(thing3);

// console.log(`-- map=${mapVref}, thing1=${thing1Vref}, thing2=${thing2Vref}`);

// map and set should have refcount 1, from baggage
t.is(ls1.testHooks.getReachableRefCount(mapVref), 1);
t.is(ls1.testHooks.getReachableRefCount(setVref), 1);
// thing1 should have a refcount of 4: one for valueShape, plus one
// for each entry
t.is(ls1.testHooks.getReachableRefCount(thing1Vref), 4);
// thing2 should have 1, only the 'thing2' entry
t.is(ls1.testHooks.getReachableRefCount(thing2Vref), 1);
// thing3 should have 1, just the Set's valueShape
t.is(ls1.testHooks.getReachableRefCount(thing3Vref), 1);

const collectionID = parseVatSlot(mapVref).subid;
const schemaKey = `vc.${collectionID}.|schemata`;
const schemataData = JSON.parse(kvStore.get(schemaKey));
t.deepEqual(schemataData.slots, [thing1Vref]);

// const compareEntriesByKey = ([ka], [kb]) => (ka < kb ? -1 : 1);
// t.log(Object.fromEntries([...kvStore.entries()].sort(compareEntriesByKey)));

// Simulate upgrade by starting from the non-empty kvStore.
const clonedStore = new Map(kvStore);

let map2;
let set2;
let newThing1;
let newThing2;

function build2(vatPowers, _vatParameters, baggage) {
const { VatData } = vatPowers;
const handle = baggage.get('handle');
const initData = name => ({ name });
const how = { name: ({ state }) => state.name };
VatData.defineDurableKind(handle, initData, how);
map2 = baggage.get('map');
let s;
[newThing1, s] = map2.get('string');
t.is(newThing1.name(), 'thing1');
t.is(s, 'string');
t.deepEqual(map2.get('string'), [newThing1, 'string']);
t.deepEqual(map2.get('number'), [newThing1, 123]);
newThing2 = map2.get('thing2')[1];
t.is(newThing2.name(), 'thing2');
set2 = baggage.get('set');
return Far('root', {});
}
const make2 = () => ({ buildRootObject: build2 });

const { syscall: sc2 } = buildSyscall({ kvStore: clonedStore });
const gcTools2 = makeMockGC();
const ls2 = makeLiveSlots(sc2, 'vatA', {}, {}, gcTools2, undefined, make2);

// restarting should work. this exercises valueShape being durable
// and being retrievable
await ls2.dispatch(startVat);

// [...ls2.testHooks.slotToVal.entries()].map(([slot,wr]) => console.log(slot, wr.deref()));

t.is(ls2.testHooks.slotToVal.get(thing1Vref).deref(), newThing1);

// the vrefs should all still be the same
t.is(ls2.testHooks.valToSlot.get(map2), mapVref);
t.is(ls2.testHooks.valToSlot.get(set2), setVref);
t.is(ls2.testHooks.valToSlot.get(newThing1), thing1Vref);
t.is(ls2.testHooks.valToSlot.get(newThing2), thing2Vref);

// refcounts should be the same
t.is(ls2.testHooks.getReachableRefCount(mapVref), 1);
t.is(ls1.testHooks.getReachableRefCount(setVref), 1);
t.is(ls2.testHooks.getReachableRefCount(thing1Vref), 4);
t.is(ls2.testHooks.getReachableRefCount(thing2Vref), 1);
t.is(ls2.testHooks.getReachableRefCount(thing3Vref), 1);
});

0 comments on commit b147977

Please sign in to comment.