@agoric/swing-store 0.9.1 → 0.9.2-calypso-dev-84eb287.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/data-export.md +116 -74
- package/docs/swingstore.md +52 -0
- package/package.json +25 -16
- package/src/assertComplete.js +21 -0
- package/src/bundleStore.js +141 -48
- package/src/exporter.js +221 -0
- package/src/importer.js +133 -0
- package/src/index.js +11 -0
- package/src/internal.js +22 -0
- package/src/kvStore.js +172 -0
- package/src/repairMetadata.js +67 -0
- package/src/snapStore.js +173 -38
- package/src/snapStoreIO.js +8 -0
- package/src/swingStore.js +94 -597
- package/src/transcriptStore.js +265 -61
- package/src/types.d.ts +15 -0
- package/src/types.js +6 -0
- package/src/util.js +7 -1
- package/test/{test-bundles.js → bundles.test.js} +21 -10
- package/test/{test-deletion.js → deletion.test.js} +1 -1
- package/test/export.test.js +350 -0
- package/test/{test-exportImport.js → exportImport.test.js} +87 -67
- package/test/exports.js +102 -0
- package/test/{test-hasher.js → hasher.test.js} +0 -2
- package/test/import.test.js +413 -0
- package/test/repair-metadata.test.js +143 -0
- package/test/{test-snapstore.js → snapstore.test.js} +2 -5
- package/test/{test-state.js → state.test.js} +36 -3
- package/test/util.js +27 -0
- package/{jsconfig.json → tsconfig.json} +1 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
|
|
3
|
+
import { buffer } from '../src/util.js';
|
|
4
|
+
import { initSwingStore, makeSwingStoreExporter } from '../src/index.js';
|
|
5
|
+
|
|
6
|
+
import { tmpDir, getSnapshotStream, makeB0ID } from './util.js';
|
|
7
|
+
|
|
8
|
+
const rank = {
|
|
9
|
+
operational: 1,
|
|
10
|
+
replay: 2,
|
|
11
|
+
archival: 3,
|
|
12
|
+
debug: 4,
|
|
13
|
+
'debug-on-pruned': 4,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const snapshotData = 'snapshot data';
|
|
17
|
+
// this snapHash was computed manually
|
|
18
|
+
const snapHash =
|
|
19
|
+
'e7dee7266896538616b630a5da40a90e007726a383e005a9c9c5dd0c2daf9329';
|
|
20
|
+
|
|
21
|
+
/** @type {import('../src/bundleStore.js').Bundle} */
|
|
22
|
+
const bundle0 = { moduleFormat: 'nestedEvaluate', source: '1+1' };
|
|
23
|
+
const bundle0ID = makeB0ID(bundle0);
|
|
24
|
+
|
|
25
|
+
const exportTest = test.macro(async (t, mode) => {
|
|
26
|
+
const [dbDir, cleanup] = await tmpDir('testdb');
|
|
27
|
+
t.teardown(cleanup);
|
|
28
|
+
// const dbDir = 't-db';
|
|
29
|
+
|
|
30
|
+
const options = {};
|
|
31
|
+
if (mode === 'debug') {
|
|
32
|
+
options.keepSnapshots = true; // else old snapshots are deleted
|
|
33
|
+
}
|
|
34
|
+
const ss1 = initSwingStore(dbDir, options);
|
|
35
|
+
const ks = ss1.kernelStorage;
|
|
36
|
+
|
|
37
|
+
ss1.hostStorage.kvStore.set('host.h1', 'hostvalue1');
|
|
38
|
+
|
|
39
|
+
// build a DB with four spans (one in an old incarnation, two
|
|
40
|
+
// historical but current incarnation, only one inUse) and two
|
|
41
|
+
// snapshots (only one inUSe)
|
|
42
|
+
|
|
43
|
+
ks.kvStore.set('key1', 'value1');
|
|
44
|
+
ks.bundleStore.addBundle(bundle0ID, bundle0);
|
|
45
|
+
ks.transcriptStore.initTranscript('v1');
|
|
46
|
+
|
|
47
|
+
// incarnation 0
|
|
48
|
+
ks.transcriptStore.addItem('v1', 'start-worker'); // 0
|
|
49
|
+
ks.transcriptStore.addItem('v1', 'shutdown-worker'); // 1
|
|
50
|
+
ks.transcriptStore.rolloverIncarnation('v1');
|
|
51
|
+
const spanHash0 =
|
|
52
|
+
'5bee0f44eca02f23eab03703e84ed2647d5d117fed99e1c30a3b424b7f082ab9';
|
|
53
|
+
|
|
54
|
+
// incarnation 1
|
|
55
|
+
ks.transcriptStore.addItem('v1', 'start-worker'); // 2
|
|
56
|
+
ks.transcriptStore.addItem('v1', 'delivery1'); // 3
|
|
57
|
+
await ks.snapStore.saveSnapshot('v1', 4, getSnapshotStream(snapshotData));
|
|
58
|
+
ks.transcriptStore.addItem('v1', 'save-snapshot'); // 4
|
|
59
|
+
ks.transcriptStore.rolloverSpan('v1'); // range= 2..5
|
|
60
|
+
const spanHash1 =
|
|
61
|
+
'57152efdd7fdf75c03371d2b4f1088d5bf3eae7fe643babce527ff81df38998c';
|
|
62
|
+
|
|
63
|
+
ks.transcriptStore.addItem('v1', 'load-snapshot'); // 5
|
|
64
|
+
ks.transcriptStore.addItem('v1', 'delivery2'); // 6
|
|
65
|
+
await ks.snapStore.saveSnapshot('v1', 7, getSnapshotStream(snapshotData));
|
|
66
|
+
ks.transcriptStore.addItem('v1', 'save-snapshot'); // 7
|
|
67
|
+
ks.transcriptStore.rolloverSpan('v1'); // range= 5..8
|
|
68
|
+
const spanHash2 =
|
|
69
|
+
'1947001e78e01bd1e773feb22b4ffc530447373b9de9274d5d5fbda3f23dbf2b';
|
|
70
|
+
|
|
71
|
+
ks.transcriptStore.addItem('v1', 'load-snapshot'); // 8
|
|
72
|
+
ks.transcriptStore.addItem('v1', 'delivery3'); // 9
|
|
73
|
+
const spanHash3 =
|
|
74
|
+
'e6b42c6a3fb94285a93162f25a9fc0145fd4c5bb144917dc572c50ae2d02ee69';
|
|
75
|
+
// current range= 8..10
|
|
76
|
+
|
|
77
|
+
await ss1.hostStorage.commit();
|
|
78
|
+
|
|
79
|
+
// create an export, and assert that the pieces match what we
|
|
80
|
+
// expect. artifactMode='operational' means we get all metadata, no
|
|
81
|
+
// historical transcript spans, and no historical snapshots
|
|
82
|
+
|
|
83
|
+
assert.typeof(mode, 'string');
|
|
84
|
+
/** @import {ArtifactMode} from '../src/internal.js' */
|
|
85
|
+
let artifactMode = /** @type {ArtifactMode} */ (mode);
|
|
86
|
+
if (mode === 'debug-on-pruned') {
|
|
87
|
+
artifactMode = 'debug';
|
|
88
|
+
}
|
|
89
|
+
const exporter = makeSwingStoreExporter(dbDir, { artifactMode });
|
|
90
|
+
|
|
91
|
+
// hostKV
|
|
92
|
+
t.is(exporter.getHostKV('host.h1'), 'hostvalue1');
|
|
93
|
+
t.is(exporter.getHostKV('host.hmissing'), undefined);
|
|
94
|
+
t.throws(() => exporter.getHostKV('nonhost'), {
|
|
95
|
+
message: 'getHostKV requires host keys',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// exportData
|
|
99
|
+
{
|
|
100
|
+
const exportData = new Map();
|
|
101
|
+
for await (const [key, value] of exporter.getExportData()) {
|
|
102
|
+
exportData.set(key, value);
|
|
103
|
+
}
|
|
104
|
+
// console.log('exportData:', exportData);
|
|
105
|
+
|
|
106
|
+
const check = (key, expected) => {
|
|
107
|
+
t.true(exportData.has(key));
|
|
108
|
+
let value = exportData.get(key);
|
|
109
|
+
exportData.delete(key);
|
|
110
|
+
if (typeof expected === 'object') {
|
|
111
|
+
value = JSON.parse(value);
|
|
112
|
+
}
|
|
113
|
+
t.deepEqual(value, expected);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
check('kv.key1', 'value1');
|
|
117
|
+
check('snapshot.v1.4', {
|
|
118
|
+
vatID: 'v1',
|
|
119
|
+
snapPos: 4,
|
|
120
|
+
inUse: 0,
|
|
121
|
+
hash: snapHash,
|
|
122
|
+
});
|
|
123
|
+
check('snapshot.v1.7', {
|
|
124
|
+
vatID: 'v1',
|
|
125
|
+
snapPos: 7,
|
|
126
|
+
inUse: 1,
|
|
127
|
+
hash: snapHash,
|
|
128
|
+
});
|
|
129
|
+
check('snapshot.v1.current', 'snapshot.v1.7');
|
|
130
|
+
const base = { vatID: 'v1', isCurrent: 0 };
|
|
131
|
+
check('transcript.v1.0', {
|
|
132
|
+
...base,
|
|
133
|
+
incarnation: 0,
|
|
134
|
+
startPos: 0,
|
|
135
|
+
endPos: 2,
|
|
136
|
+
hash: spanHash0,
|
|
137
|
+
});
|
|
138
|
+
check('transcript.v1.2', {
|
|
139
|
+
...base,
|
|
140
|
+
incarnation: 1,
|
|
141
|
+
startPos: 2,
|
|
142
|
+
endPos: 5,
|
|
143
|
+
hash: spanHash1,
|
|
144
|
+
});
|
|
145
|
+
check('transcript.v1.5', {
|
|
146
|
+
...base,
|
|
147
|
+
incarnation: 1,
|
|
148
|
+
startPos: 5,
|
|
149
|
+
endPos: 8,
|
|
150
|
+
hash: spanHash2,
|
|
151
|
+
});
|
|
152
|
+
check('transcript.v1.current', {
|
|
153
|
+
...base,
|
|
154
|
+
incarnation: 1,
|
|
155
|
+
startPos: 8,
|
|
156
|
+
endPos: 10,
|
|
157
|
+
isCurrent: 1,
|
|
158
|
+
hash: spanHash3,
|
|
159
|
+
});
|
|
160
|
+
check(`bundle.${bundle0ID}`, bundle0ID);
|
|
161
|
+
|
|
162
|
+
// the above list is supposed to be exhaustive
|
|
163
|
+
if (exportData.size) {
|
|
164
|
+
console.log('unexpected exportData keys');
|
|
165
|
+
console.log(exportData);
|
|
166
|
+
t.fail('unexpected exportData keys');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// artifacts
|
|
171
|
+
{
|
|
172
|
+
const names = new Set();
|
|
173
|
+
const contents = new Map();
|
|
174
|
+
for await (const name of exporter.getArtifactNames()) {
|
|
175
|
+
names.add(name);
|
|
176
|
+
contents.set(name, (await buffer(exporter.getArtifact(name))).toString());
|
|
177
|
+
}
|
|
178
|
+
// console.log('artifacts:', contents);
|
|
179
|
+
|
|
180
|
+
const check = async (name, expected) => {
|
|
181
|
+
t.true(names.has(name));
|
|
182
|
+
names.delete(name);
|
|
183
|
+
let data = contents.get(name);
|
|
184
|
+
if (typeof expected === 'object') {
|
|
185
|
+
data = JSON.parse(data);
|
|
186
|
+
}
|
|
187
|
+
t.deepEqual(data, expected);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// export mode 'operational' means we omit historical snapshots and
|
|
191
|
+
// transcript spans
|
|
192
|
+
|
|
193
|
+
await check('snapshot.v1.7', 'snapshot data');
|
|
194
|
+
await check('transcript.v1.8.10', 'load-snapshot\ndelivery3\n');
|
|
195
|
+
await check(`bundle.${bundle0ID}`, bundle0);
|
|
196
|
+
|
|
197
|
+
t.true(rank[mode] > 0);
|
|
198
|
+
if (rank[mode] >= rank.replay) {
|
|
199
|
+
// add the old transcript spans of the current incarnation
|
|
200
|
+
await check(
|
|
201
|
+
'transcript.v1.2.5',
|
|
202
|
+
'start-worker\ndelivery1\nsave-snapshot\n',
|
|
203
|
+
);
|
|
204
|
+
await check(
|
|
205
|
+
'transcript.v1.5.8',
|
|
206
|
+
'load-snapshot\ndelivery2\nsave-snapshot\n',
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (rank[mode] >= rank.archival) {
|
|
211
|
+
// add the spans of the old incarnation
|
|
212
|
+
await check('transcript.v1.0.2', 'start-worker\nshutdown-worker\n');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (mode === 'debug') {
|
|
216
|
+
// adds the old snapshots, which are only present if
|
|
217
|
+
// initSwingStore() was given {keepSnapshots: true}
|
|
218
|
+
await check('snapshot.v1.4', 'snapshot data');
|
|
219
|
+
// mode='debug-on-pruned' exercises the keepSnapshots:false case
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (names.size) {
|
|
223
|
+
console.log(`unexpected artifacts:`);
|
|
224
|
+
console.log(names);
|
|
225
|
+
t.fail('unexpected artifacts');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('export operational', exportTest, 'operational');
|
|
231
|
+
test('export replay', exportTest, 'replay');
|
|
232
|
+
test('export archival', exportTest, 'archival');
|
|
233
|
+
test('export debug', exportTest, 'debug');
|
|
234
|
+
test('export debug-on-pruned', exportTest, 'debug-on-pruned');
|
|
235
|
+
|
|
236
|
+
test('export omits pruned span artifacts', async t => {
|
|
237
|
+
const [dbDir, cleanup] = await tmpDir('testdb');
|
|
238
|
+
t.teardown(cleanup);
|
|
239
|
+
// const dbDir = 't-db';
|
|
240
|
+
|
|
241
|
+
// use keepTranscripts=false to simulate an explicit prune of the
|
|
242
|
+
// old span
|
|
243
|
+
const options = { keepTranscripts: false };
|
|
244
|
+
const ss1 = initSwingStore(dbDir, options);
|
|
245
|
+
const ks = ss1.kernelStorage;
|
|
246
|
+
|
|
247
|
+
// build a DB with two spans, one is inUse, other is pruned
|
|
248
|
+
|
|
249
|
+
ks.transcriptStore.initTranscript('v1');
|
|
250
|
+
ks.transcriptStore.addItem('v1', 'start-worker'); // 0
|
|
251
|
+
ks.transcriptStore.addItem('v1', 'delivery1'); // 1
|
|
252
|
+
await ks.snapStore.saveSnapshot('v1', 2, getSnapshotStream(snapshotData));
|
|
253
|
+
ks.transcriptStore.addItem('v1', 'save-snapshot'); // 2
|
|
254
|
+
ks.transcriptStore.rolloverSpan('v1'); // range= 0..3
|
|
255
|
+
const spanHash1 =
|
|
256
|
+
'57152efdd7fdf75c03371d2b4f1088d5bf3eae7fe643babce527ff81df38998c';
|
|
257
|
+
// rolloverSpan prunes the contents of the old span
|
|
258
|
+
|
|
259
|
+
ks.transcriptStore.addItem('v1', 'load-snapshot'); // 3
|
|
260
|
+
ks.transcriptStore.addItem('v1', 'delivery2'); // 4
|
|
261
|
+
const spanHash2 =
|
|
262
|
+
'b26c8faf425c3c2738e0c5a5e9a7cd71075c68f0c9f2d6cdfd83c68204801dbb';
|
|
263
|
+
|
|
264
|
+
await ss1.hostStorage.commit();
|
|
265
|
+
|
|
266
|
+
const artifactMode = 'debug';
|
|
267
|
+
const exporter = makeSwingStoreExporter(dbDir, { artifactMode });
|
|
268
|
+
|
|
269
|
+
// exportData
|
|
270
|
+
{
|
|
271
|
+
const exportData = new Map();
|
|
272
|
+
for await (const [key, value] of exporter.getExportData()) {
|
|
273
|
+
exportData.set(key, value);
|
|
274
|
+
}
|
|
275
|
+
// console.log('exportData:', exportData);
|
|
276
|
+
|
|
277
|
+
const check = (key, expected) => {
|
|
278
|
+
t.true(exportData.has(key));
|
|
279
|
+
let value = exportData.get(key);
|
|
280
|
+
exportData.delete(key);
|
|
281
|
+
if (typeof expected === 'object') {
|
|
282
|
+
value = JSON.parse(value);
|
|
283
|
+
}
|
|
284
|
+
t.deepEqual(value, expected);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
check('snapshot.v1.2', {
|
|
288
|
+
vatID: 'v1',
|
|
289
|
+
snapPos: 2,
|
|
290
|
+
inUse: 1,
|
|
291
|
+
hash: snapHash,
|
|
292
|
+
});
|
|
293
|
+
check('snapshot.v1.current', 'snapshot.v1.2');
|
|
294
|
+
const base = { vatID: 'v1', incarnation: 0, isCurrent: 0 };
|
|
295
|
+
check('transcript.v1.0', {
|
|
296
|
+
...base,
|
|
297
|
+
startPos: 0,
|
|
298
|
+
endPos: 3,
|
|
299
|
+
hash: spanHash1,
|
|
300
|
+
});
|
|
301
|
+
check('transcript.v1.current', {
|
|
302
|
+
...base,
|
|
303
|
+
startPos: 3,
|
|
304
|
+
endPos: 5,
|
|
305
|
+
isCurrent: 1,
|
|
306
|
+
hash: spanHash2,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// the above list is supposed to be exhaustive
|
|
310
|
+
if (exportData.size) {
|
|
311
|
+
console.log('unexpected exportData keys');
|
|
312
|
+
console.log(exportData);
|
|
313
|
+
t.fail('unexpected exportData keys');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// artifacts
|
|
318
|
+
{
|
|
319
|
+
const names = new Set();
|
|
320
|
+
const contents = new Map();
|
|
321
|
+
for await (const name of exporter.getArtifactNames()) {
|
|
322
|
+
names.add(name);
|
|
323
|
+
contents.set(name, (await buffer(exporter.getArtifact(name))).toString());
|
|
324
|
+
}
|
|
325
|
+
// console.log('artifacts:', contents);
|
|
326
|
+
|
|
327
|
+
const check = async (name, expected) => {
|
|
328
|
+
t.true(names.has(name));
|
|
329
|
+
names.delete(name);
|
|
330
|
+
let data = contents.get(name);
|
|
331
|
+
if (typeof expected === 'object') {
|
|
332
|
+
data = JSON.parse(data);
|
|
333
|
+
}
|
|
334
|
+
t.deepEqual(data, expected);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// export mode 'archival' means we include all available
|
|
338
|
+
// historical snapshots and transcript spans
|
|
339
|
+
|
|
340
|
+
await check('snapshot.v1.2', 'snapshot data');
|
|
341
|
+
// no transcript.v1.0.3 because the contents were pruned
|
|
342
|
+
await check('transcript.v1.3.5', 'load-snapshot\ndelivery2\n');
|
|
343
|
+
|
|
344
|
+
if (names.size) {
|
|
345
|
+
console.log(`unexpected artifacts:`);
|
|
346
|
+
console.log(names);
|
|
347
|
+
t.fail('unexpected artifacts');
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import '@endo/init/debug.js';
|
|
4
3
|
import { Buffer } from 'node:buffer';
|
|
5
4
|
|
|
6
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
7
5
|
import test from 'ava';
|
|
8
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
9
6
|
import tmp from 'tmp';
|
|
10
7
|
import bundleSource from '@endo/bundle-source';
|
|
11
8
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
importSwingStore,
|
|
16
|
-
} from '../src/swingStore.js';
|
|
9
|
+
import { initSwingStore } from '../src/swingStore.js';
|
|
10
|
+
import { makeSwingStoreExporter } from '../src/exporter.js';
|
|
11
|
+
import { importSwingStore } from '../src/importer.js';
|
|
17
12
|
|
|
18
13
|
function makeExportLog() {
|
|
19
14
|
const exportLog = [];
|
|
@@ -57,7 +52,7 @@ async function embundle(filename) {
|
|
|
57
52
|
const bundleFile = new URL(filename, import.meta.url).pathname;
|
|
58
53
|
const bundle = await bundleSource(bundleFile);
|
|
59
54
|
const bundleID = `b1-${bundle.endoZipBase64Sha512}`;
|
|
60
|
-
return [bundleID, bundle];
|
|
55
|
+
return /** @type {const} */ ([bundleID, bundle]);
|
|
61
56
|
}
|
|
62
57
|
|
|
63
58
|
function actLikeAVatRunningACrank(vat, ks, crank, doFail) {
|
|
@@ -88,6 +83,7 @@ async function fakeAVatSnapshot(vat, ks) {
|
|
|
88
83
|
async function* getSnapshotStream() {
|
|
89
84
|
yield Buffer.from(`snapshot of vat ${vat.vatID} as of ${vat.endPos}`);
|
|
90
85
|
}
|
|
86
|
+
harden(getSnapshotStream);
|
|
91
87
|
await ks.snapStore.saveSnapshot(vat.vatID, vat.endPos, getSnapshotStream());
|
|
92
88
|
ks.transcriptStore.addItem(vat.vatID, 'save-snapshot');
|
|
93
89
|
vat.endPos += 1;
|
|
@@ -124,11 +120,11 @@ test('crank abort leaves no debris in export log', async t => {
|
|
|
124
120
|
crankNum % 3 === 0,
|
|
125
121
|
);
|
|
126
122
|
}
|
|
127
|
-
// eslint-disable-next-line no-await-in-loop
|
|
128
123
|
await ssOut.hostStorage.commit();
|
|
129
124
|
}
|
|
130
125
|
|
|
131
|
-
const
|
|
126
|
+
const artifactMode = 'operational';
|
|
127
|
+
const exporter = makeSwingStoreExporter(dbDir, { artifactMode });
|
|
132
128
|
|
|
133
129
|
const exportData = [];
|
|
134
130
|
for await (const elem of exporter.getExportData()) {
|
|
@@ -176,14 +172,14 @@ async function testExportImport(
|
|
|
176
172
|
runMode,
|
|
177
173
|
exportMode,
|
|
178
174
|
importMode,
|
|
179
|
-
failureMode,
|
|
180
175
|
expectedArtifactNames,
|
|
176
|
+
failureMode = 'none',
|
|
181
177
|
) {
|
|
182
178
|
const exportLog = makeExportLog();
|
|
183
179
|
const [dbDir, cleanup] = await tmpDir('testdb');
|
|
184
180
|
t.teardown(cleanup);
|
|
185
181
|
|
|
186
|
-
const keepTranscripts = runMode !== '
|
|
182
|
+
const keepTranscripts = runMode !== 'operational';
|
|
187
183
|
const keepSnapshots = runMode === 'debug';
|
|
188
184
|
const ssOut = initSwingStore(dbDir, {
|
|
189
185
|
exportCallback: exportLog.callback,
|
|
@@ -219,14 +215,21 @@ async function testExportImport(
|
|
|
219
215
|
actLikeAVatRunningACrank(vat, kernelStorage, crankNum);
|
|
220
216
|
}
|
|
221
217
|
if (block < 3) {
|
|
222
|
-
// eslint-disable-next-line no-await-in-loop
|
|
223
218
|
await fakeAVatSnapshot(vats[block % 2], kernelStorage);
|
|
224
219
|
}
|
|
225
|
-
// eslint-disable-next-line no-await-in-loop
|
|
226
220
|
await ssOut.hostStorage.commit();
|
|
227
221
|
}
|
|
228
222
|
|
|
229
|
-
|
|
223
|
+
// the exporter may be broken (the swingstore may be incomplete), but that is
|
|
224
|
+
// signaled during `getArtifactNames()`, not during construction (which must
|
|
225
|
+
// finish quickly, without scanning the whole DB)
|
|
226
|
+
const exporter = makeSwingStoreExporter(dbDir, { artifactMode: exportMode });
|
|
227
|
+
|
|
228
|
+
const incomplete = 'incomplete archival transcript: 3 vs 12';
|
|
229
|
+
if (failureMode === 'export') {
|
|
230
|
+
await t.throws(() => exporter.getArtifactNames(), { message: incomplete });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
230
233
|
|
|
231
234
|
const exportData = [];
|
|
232
235
|
for await (const elem of exporter.getExportData()) {
|
|
@@ -300,38 +303,47 @@ async function testExportImport(
|
|
|
300
303
|
],
|
|
301
304
|
]);
|
|
302
305
|
|
|
303
|
-
expectedArtifactNames =
|
|
304
|
-
expectedArtifactNames.
|
|
305
|
-
expectedArtifactNames.
|
|
306
|
+
expectedArtifactNames = new Set(expectedArtifactNames);
|
|
307
|
+
expectedArtifactNames.add(`bundle.${bundleIDA}`);
|
|
308
|
+
expectedArtifactNames.add(`bundle.${bundleIDB}`);
|
|
306
309
|
|
|
307
|
-
const artifactNames =
|
|
310
|
+
const artifactNames = new Set();
|
|
308
311
|
for await (const name of exporter.getArtifactNames()) {
|
|
309
|
-
artifactNames.
|
|
312
|
+
artifactNames.add(name);
|
|
310
313
|
}
|
|
311
314
|
t.deepEqual(artifactNames, expectedArtifactNames);
|
|
312
315
|
|
|
313
|
-
const includeHistorical = importMode !== 'current';
|
|
314
|
-
|
|
315
316
|
const beforeDump = debug.dump(keepSnapshots);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
t.is(e.message, 'artifact "transcript.vatA.0.3" is not available');
|
|
324
|
-
return;
|
|
325
|
-
} else if (failureMode === 'snapshot') {
|
|
326
|
-
t.is(e.message, 'artifact "snapshot.vatA.2" is not available');
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
throw e;
|
|
317
|
+
function doImport() {
|
|
318
|
+
return importSwingStore(exporter, null, { artifactMode: importMode });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (failureMode === 'import') {
|
|
322
|
+
await t.throwsAsync(doImport, { message: incomplete });
|
|
323
|
+
return;
|
|
330
324
|
}
|
|
331
325
|
t.is(failureMode, 'none');
|
|
326
|
+
const ssIn = await doImport();
|
|
327
|
+
t.teardown(ssIn.hostStorage.close);
|
|
332
328
|
await ssIn.hostStorage.commit();
|
|
333
|
-
|
|
334
|
-
|
|
329
|
+
let dumpsShouldMatch = true;
|
|
330
|
+
if (runMode === 'operational') {
|
|
331
|
+
dumpsShouldMatch = true; // there's no data to lose
|
|
332
|
+
} else if (runMode === 'archival') {
|
|
333
|
+
if (exportMode === 'current') {
|
|
334
|
+
dumpsShouldMatch = false; // export omits some data
|
|
335
|
+
}
|
|
336
|
+
if (importMode === 'current') {
|
|
337
|
+
dumpsShouldMatch = false; // import ignores some data
|
|
338
|
+
}
|
|
339
|
+
} else if (runMode === 'debug') {
|
|
340
|
+
if (exportMode !== 'debug') {
|
|
341
|
+
dumpsShouldMatch = false; // export omits some data
|
|
342
|
+
}
|
|
343
|
+
if (importMode !== 'debug') {
|
|
344
|
+
dumpsShouldMatch = false; // import ignores some data
|
|
345
|
+
}
|
|
346
|
+
}
|
|
335
347
|
if (dumpsShouldMatch) {
|
|
336
348
|
const afterDump = ssIn.debug.dump(keepSnapshots);
|
|
337
349
|
t.deepEqual(beforeDump, afterDump);
|
|
@@ -366,100 +378,108 @@ const expectedDebugArtifacts = [
|
|
|
366
378
|
'transcript.vatB.5.10',
|
|
367
379
|
];
|
|
368
380
|
|
|
369
|
-
const C = 'current'
|
|
381
|
+
const C = 'operational'; // nee 'current'
|
|
382
|
+
// we don't try to test 'replay' here: see test-import.js and test-export.js
|
|
370
383
|
const A = 'archival';
|
|
371
384
|
const D = 'debug';
|
|
372
385
|
|
|
386
|
+
// importMode='archival' requires a non-pruned DB
|
|
387
|
+
// (runMode!=='current'), with exportMode as 'archival' or 'debug'
|
|
388
|
+
|
|
389
|
+
// the expected artifacts are a function of the runMode and exportMode, not importMode
|
|
390
|
+
|
|
373
391
|
test('export and import data for state sync - current->current->current', async t => {
|
|
374
|
-
await testExportImport(t, C, C, C,
|
|
392
|
+
await testExportImport(t, C, C, C, expectedCurrentArtifacts);
|
|
375
393
|
});
|
|
394
|
+
// so this one fails during import
|
|
376
395
|
test('export and import data for state sync - current->current->archival', async t => {
|
|
377
|
-
await testExportImport(t, C, C, A, '
|
|
396
|
+
await testExportImport(t, C, C, A, expectedCurrentArtifacts, 'import');
|
|
378
397
|
});
|
|
379
398
|
test('export and import data for state sync - current->current->debug', async t => {
|
|
380
|
-
await testExportImport(t, C, C, D,
|
|
399
|
+
await testExportImport(t, C, C, D, expectedCurrentArtifacts);
|
|
381
400
|
});
|
|
382
401
|
|
|
402
|
+
// these all throw an error during export, because 'archival' requires a non-pruned DB
|
|
383
403
|
test('export and import data for state sync - current->archival->current', async t => {
|
|
384
|
-
await testExportImport(t, C, A, C, '
|
|
404
|
+
await testExportImport(t, C, A, C, [], 'export');
|
|
385
405
|
});
|
|
386
406
|
test('export and import data for state sync - current->archival->archival', async t => {
|
|
387
|
-
await testExportImport(t, C, A, A, '
|
|
407
|
+
await testExportImport(t, C, A, A, [], 'export');
|
|
388
408
|
});
|
|
389
409
|
test('export and import data for state sync - current->archival->debug', async t => {
|
|
390
|
-
await testExportImport(t, C, A, D, '
|
|
410
|
+
await testExportImport(t, C, A, D, [], 'export');
|
|
391
411
|
});
|
|
392
412
|
|
|
393
413
|
test('export and import data for state sync - current->debug->current', async t => {
|
|
394
|
-
await testExportImport(t, C, D, C,
|
|
414
|
+
await testExportImport(t, C, D, C, expectedCurrentArtifacts);
|
|
395
415
|
});
|
|
396
416
|
test('export and import data for state sync - current->debug->archival', async t => {
|
|
397
|
-
await testExportImport(t, C, D, A, '
|
|
417
|
+
await testExportImport(t, C, D, A, expectedCurrentArtifacts, 'import');
|
|
398
418
|
});
|
|
399
419
|
test('export and import data for state sync - current->debug->debug', async t => {
|
|
400
|
-
await testExportImport(t, C, D, D,
|
|
420
|
+
await testExportImport(t, C, D, D, expectedCurrentArtifacts);
|
|
401
421
|
});
|
|
402
422
|
|
|
403
423
|
// ------------------------------------------------------------
|
|
404
424
|
|
|
405
425
|
test('export and import data for state sync - archival->current->current', async t => {
|
|
406
|
-
await testExportImport(t, A, C, C,
|
|
426
|
+
await testExportImport(t, A, C, C, expectedCurrentArtifacts);
|
|
407
427
|
});
|
|
408
428
|
test('export and import data for state sync - archival->current->archival', async t => {
|
|
409
|
-
await testExportImport(t, A, C, A, '
|
|
429
|
+
await testExportImport(t, A, C, A, expectedCurrentArtifacts, 'import');
|
|
410
430
|
});
|
|
411
431
|
test('export and import data for state sync - archival->current->debug', async t => {
|
|
412
|
-
await testExportImport(t, A, C, D,
|
|
432
|
+
await testExportImport(t, A, C, D, expectedCurrentArtifacts);
|
|
413
433
|
});
|
|
414
434
|
|
|
415
435
|
test('export and import data for state sync - archival->archival->current', async t => {
|
|
416
|
-
await testExportImport(t, A, A, C,
|
|
436
|
+
await testExportImport(t, A, A, C, expectedArchivalArtifacts);
|
|
417
437
|
});
|
|
418
438
|
test('export and import data for state sync - archival->archival->archival', async t => {
|
|
419
|
-
await testExportImport(t, A, A, A,
|
|
439
|
+
await testExportImport(t, A, A, A, expectedArchivalArtifacts);
|
|
420
440
|
});
|
|
421
441
|
test('export and import data for state sync - archival->archival->debug', async t => {
|
|
422
|
-
await testExportImport(t, A, A, D,
|
|
442
|
+
await testExportImport(t, A, A, D, expectedArchivalArtifacts);
|
|
423
443
|
});
|
|
424
444
|
|
|
425
445
|
test('export and import data for state sync - archival->debug->current', async t => {
|
|
426
|
-
await testExportImport(t, A, D, C,
|
|
446
|
+
await testExportImport(t, A, D, C, expectedArchivalArtifacts);
|
|
427
447
|
});
|
|
428
448
|
test('export and import data for state sync - archival->debug->archival', async t => {
|
|
429
|
-
await testExportImport(t, A, D, A,
|
|
449
|
+
await testExportImport(t, A, D, A, expectedArchivalArtifacts);
|
|
430
450
|
});
|
|
431
451
|
test('export and import data for state sync - archival->debug->debug', async t => {
|
|
432
|
-
await testExportImport(t, A, D, D,
|
|
452
|
+
await testExportImport(t, A, D, D, expectedArchivalArtifacts);
|
|
433
453
|
});
|
|
434
454
|
|
|
435
455
|
// ------------------------------------------------------------
|
|
436
456
|
|
|
437
457
|
test('export and import data for state sync - debug->current->current', async t => {
|
|
438
|
-
await testExportImport(t, D, C, C,
|
|
458
|
+
await testExportImport(t, D, C, C, expectedCurrentArtifacts);
|
|
439
459
|
});
|
|
440
460
|
test('export and import data for state sync - debug->current->archival', async t => {
|
|
441
|
-
await testExportImport(t, D, C, A, '
|
|
461
|
+
await testExportImport(t, D, C, A, expectedCurrentArtifacts, 'import');
|
|
442
462
|
});
|
|
443
463
|
test('export and import data for state sync - debug->current->debug', async t => {
|
|
444
|
-
await testExportImport(t, D, C, D,
|
|
464
|
+
await testExportImport(t, D, C, D, expectedCurrentArtifacts);
|
|
445
465
|
});
|
|
446
466
|
|
|
447
467
|
test('export and import data for state sync - debug->archival->current', async t => {
|
|
448
|
-
await testExportImport(t, D, A, C,
|
|
468
|
+
await testExportImport(t, D, A, C, expectedArchivalArtifacts);
|
|
449
469
|
});
|
|
450
470
|
test('export and import data for state sync - debug->archival->archival', async t => {
|
|
451
|
-
await testExportImport(t, D, A, A,
|
|
471
|
+
await testExportImport(t, D, A, A, expectedArchivalArtifacts);
|
|
452
472
|
});
|
|
453
473
|
test('export and import data for state sync - debug->archival->debug', async t => {
|
|
454
|
-
await testExportImport(t, D, A, D,
|
|
474
|
+
await testExportImport(t, D, A, D, expectedArchivalArtifacts);
|
|
455
475
|
});
|
|
456
476
|
|
|
457
477
|
test('export and import data for state sync - debug->debug->current', async t => {
|
|
458
|
-
await testExportImport(t, D, D, C,
|
|
478
|
+
await testExportImport(t, D, D, C, expectedDebugArtifacts);
|
|
459
479
|
});
|
|
460
480
|
test('export and import data for state sync - debug->debug->archival', async t => {
|
|
461
|
-
await testExportImport(t, D, D, A,
|
|
481
|
+
await testExportImport(t, D, D, A, expectedDebugArtifacts);
|
|
462
482
|
});
|
|
463
483
|
test('export and import data for state sync - debug->debug->debug', async t => {
|
|
464
|
-
await testExportImport(t, D, D, D,
|
|
484
|
+
await testExportImport(t, D, D, D, expectedDebugArtifacts);
|
|
465
485
|
});
|