-
Notifications
You must be signed in to change notification settings - Fork 212
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
liveslots: add new test of durable collections across upgrades
Check that the refcounts are maintained correctly
- Loading branch information
Showing
1 changed file
with
135 additions
and
0 deletions.
There are no files selected for viewing
135 changes: 135 additions & 0 deletions
135
packages/swingset-liveslots/test/test-collection-upgrade.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |