-
Notifications
You must be signed in to change notification settings - Fork 214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
framework to unit-test vat upgrade and durable data #6523
Comments
This sounds like the right framework. Questions I don't see addressed above (I might have missed something)
I think the tests want to re-use kind identifiers, and associate new behavior with old baggage. I don't know if they do this by copying state into new baggage, or continuing to interact with the baggage that was previously there.
I'm okay with the choice to require I'm hopeful that read-only interaction with the previous generation of baggage after |
Yes, when you call your
The V2 side uses a new Nothing must touch the V1 Representatives or test('name', async t => {
await simulateUpgrade(async baggage1 => {
const apiV1 = initV1(baggage1);
// interact with apiV1, t.is(), etc
});
await simulateUpgrade(async baggage2 => {
const apiV2 = initV2(baggage2); // note: not the same object as above
// interact with apiV2, etc
});
}); (so the only way to retain a V1-world object long enough to use in the V2 clause would be to stash it inside a higher-level We could also maybe wrap
Within the window/scope of the V1 baggage, sure. Once the next
Yeah, I think it's unfortunate, because these sorts of unit-ish tests will run a lot faster, so you should feel free to use a lot more of them, and that makes it more ergnomic/readable to put a lot of them in a single file. And it's not obvious why they need to be run serially (at least if they're sharing a process, and I believe AVA won't share a process between test cases unless they appear in the same
The problem is that the old baggage is still backed by a DB (well, really, the retained I know a bunch of the feature requirements won't be obvious until we have some code to play with, but can you think of a case where you'd want to read from the old baggage after starting up the new stuff? I can imagine code in the trailing end of the V1 clause where you'd compare stuff in baggage against expectations (lots of Oh, huh.. ok, what we might consider is cloning that If we did that, and it seemed usable/not-confusing enough, then we might be comfortable removing the thunk-based isolation, and go back to: test('name', async t => {
const baggage1 = simulateUpgrade();
doV1Stuff(baggage1);
const baggage2 = simulateUpgrade();
doV2Stuff(baggage2); // but `t.is` assertions could read from baggage1 safely
}); |
I don't have a case in mind. Give me a few days to think about how I'd use it. My instincts say I want to look, but it's quite possible that being able to I agree that I think at this point, I'm convinced enough that the version that doesn't want tests to look back at baggage1 after |
This is still useful and should be landing in the near future, but the kernel team have been wondering about the severity of its limitation to simulating a single vat. It's also worth noting that much of the burden involved with full-kernel tests appears to derive from file separation and bundling rather than just having a kernel (although there is some friction from value translation as well, e.g. |
What is the Problem Being Solved?
Currently it is a hassle to write tests of vat/library upgradability. The one scheme we have is more of an integration test, where you use a real swingset kernel and build a pair of vats (one pre- and one post- upgrade), import the library you're testing into those, have a controller/bootstrap vat drive an upgrade and a bunch of queries, log the output, and then the only real
t.deepEquals()
-style AVA assertion is to compare the output log against a "golden master".These tend to be slow, coarse, hard to run under a debugger, and do not provide useful coverage data.
It would be great if we could write more "unit-ish" unit tests: smaller, faster, exercising finer details.
Description of the Design
This kind of code is importing from
@agoric/vat-data
to get the primary virtual/durable tools:defineKind
(timesDurable
andMulti
variations)makeKindHandle
providePromiseWatcher
/watchPromise
makeScalarBigMapStore
(timesSet
andWeak
variations)canBeDurable
Those all come from
globalThis.VatData
, which is provided to real vats by the swingsetliveslots
layer (since they must all integrate tightly with the Presences and Representatives that liveslots manages).(
@agoric/vat-data
also exports secondary provide/vivify tools, likeprovideDurableMapStore
,defineVirtualFarClass
,vivifyKind
, andvivifyFarClass
, but these are all wrappers around the core functions that come fromVatData
)We have a "fake virtual stuff" utility already, to unit-test libraries that import from
@agoric/vat-data
(or code that uses such libraries). Your unit test starts withimport '@agoric/swingset-vat/tools/prepare-test-env.js'
(orprepare-test-env-ava.js
, since we always use AVA), and that use a function namedmakeFakeVirtualStuff
to populateglobalThis.VatData
with enough code to supportdefineKind
/etc.The backing store for
makeFakeVirtualStuff()
is a plainMap
, which takes the place of thesyscall.vatstoreSet(key,value)
that liveslots would use to keep data in the DB.makeFakeVirtualStuff()
then creates a VirtualObjectManager and CollectionManager around this Map, asks them fordefineKind
and friends, and populatesglobalThis.VatData
with those utilities. As long as this happens before any user-level code is imported (and thus imports@agoric/vat-data
), the utilities will be in place on the global in time to be imported by user code.My plan is:
fakeVirtualSupport.js
:Map
instead of always creating a local/private oneprepare-test-env-ava.js
:makeFakeVirtualStuff
, so the Map can be reused latersimulateUpgrade
function, which callsmakeFakeVirtualStuff()
again but with the same Map as before, to represent the DB that is retained across an upgradebaggage
object (a BigMapStore), which uses a known/stable ID within the DB/MapsimulateUpgrade
is called, it creates a newbaggage
, backed by the new VOM/etc, but because it uses the same ID, it will return equivalent contentsVatData
so they can be replaced bysimulateUpgrade()
VatData.defineKind = (...args) => currentVOM.defineKind(...args)
, etc@agoric/vat-data
re-exports the pieces ofglobalThis.VatData
, and downstream code imports those pieces fromvat-data
, so their identities are immutableA unit test that wants to exercise an upgrade would import the code-under-test and
prepare-test-env-ava.js
as usual, then in the test case:baggage1 = simulateUpgrade()
baggage1
) which will dodefineKind
/provide/etc, and return someapiV1
object to interact withapiV1
to build up some durable statebaggage2 = simulateUpgrade()
baggage2
), which will re-do the define/provides/etc, and return a newapiV2
objectprovide()
calls will observe the pre-existing state, and refrain from repeatingdefineKind
and the other "once per vat, not once per incarnation" actionsapiV2
to confirm that the durable state is still presentI think this approach will work, but it has some significant limitations, most of which derive from our (justified) choice to expose these stateful utilities (
defineKind
/makeScalarBigMapStore
/etc) as imports, rather than passing them into library code as arguments:simulateUpgrade
, since they all reference the same global statetest.serial
on every testtest.serial
will cause hard-to-debug random failures, or worseapiV1
again after thesimulateUpgrade()
, because that version will be accessing stale dataprepare-test-env.js
wrappers to stop working aftersimulateUpgrade()
, but I think that would be tricky: instead of only wrappingdefineKind
/etc, we'd need to wrap the values they return, i.e. a full membrane instead of a shallow single-level wrapperSecurity Considerations
None, this is only to support unit tests
Test Plan
Write an example test in
packages/SwingSet/
, advertise it to authors of other libraries that use virtual/durable data.The text was updated successfully, but these errors were encountered: