@agoric/swing-store 0.9.2-dev-bf1e645.0 → 0.9.2-dev-07f6885.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.
@@ -1,11 +1,16 @@
1
1
  // @ts-check
2
2
  import test from 'ava';
3
- import path from 'path';
4
3
 
5
4
  import { Buffer } from 'node:buffer';
5
+ import path from 'node:path';
6
+ import fs from 'node:fs';
7
+ import zlib from 'node:zlib';
6
8
  import sqlite3 from 'better-sqlite3';
9
+ import tmp from 'tmp';
10
+ import { arrayIsLike } from '@agoric/internal/tools/ava-assertions.js';
7
11
  import { tmpDir } from './util.js';
8
12
  import { initSwingStore } from '../src/swingStore.js';
13
+ import { makeArchiveSnapshot, makeArchiveTranscript } from '../src/archiver.js';
9
14
  import { makeSwingStoreExporter } from '../src/exporter.js';
10
15
  import { importSwingStore } from '../src/importer.js';
11
16
 
@@ -50,32 +55,31 @@ test('delete snapshots with export callback', async t => {
50
55
 
51
56
  await commit();
52
57
 
53
- t.is(exportLog.length, 4);
54
- t.is(exportLog[0][0], 'snapshot.v1.10');
55
- t.is(exportLog[1][0], 'snapshot.v1.11');
56
- t.is(exportLog[2][0], 'snapshot.v1.12');
57
- t.is(exportLog[3][0], 'snapshot.v1.current');
58
58
  const hash = JSON.parse(exportLog[0][1]).hash;
59
+ arrayIsLike(t, exportLog.splice(0), [
60
+ ['snapshot.v1.10'],
61
+ ['snapshot.v1.11'],
62
+ ['snapshot.v1.12'],
63
+ ['snapshot.v1.current'],
64
+ ]);
59
65
  t.deepEqual(exportData, {
60
66
  'snapshot.v1.10': JSON.stringify({ vatID, snapPos: 10, hash, inUse: 0 }),
61
67
  'snapshot.v1.11': JSON.stringify({ vatID, snapPos: 11, hash, inUse: 0 }),
62
68
  'snapshot.v1.12': JSON.stringify({ vatID, snapPos: 12, hash, inUse: 1 }),
63
69
  'snapshot.v1.current': 'snapshot.v1.12',
64
70
  });
65
- exportLog.length = 0;
66
71
 
67
72
  // in a previous version, deleteVatSnapshots caused overlapping SQL
68
73
  // queries, and failed
69
74
  snapStore.deleteVatSnapshots(vatID);
70
75
  await commit();
71
76
 
72
- t.deepEqual(exportLog, [
77
+ t.deepEqual(exportLog.splice(0), [
73
78
  ['snapshot.v1.10', null],
74
79
  ['snapshot.v1.11', null],
75
80
  ['snapshot.v1.12', null],
76
81
  ['snapshot.v1.current', null],
77
82
  ]);
78
- exportLog.length = 0;
79
83
  t.deepEqual(exportData, {});
80
84
  });
81
85
 
@@ -95,7 +99,7 @@ test('delete transcripts with export callback', async t => {
95
99
  transcriptStore.addItem('v1', 'aaa');
96
100
  transcriptStore.addItem('v1', 'bbb');
97
101
  transcriptStore.addItem('v1', 'ccc');
98
- transcriptStore.rolloverSpan('v1');
102
+ await transcriptStore.rolloverSpan('v1');
99
103
  transcriptStore.addItem('v1', 'ddd');
100
104
  transcriptStore.addItem('v1', 'eee');
101
105
  transcriptStore.addItem('v1', 'fff');
@@ -104,22 +108,20 @@ test('delete transcripts with export callback', async t => {
104
108
 
105
109
  await commit();
106
110
 
107
- t.is(exportLog.length, 2);
108
- t.is(exportLog[0][0], 'transcript.v1.0');
109
- t.is(exportLog[1][0], 'transcript.v1.current');
110
- exportLog.length = 0;
111
+ arrayIsLike(t, exportLog.splice(0), [
112
+ ['transcript.v1.0'],
113
+ ['transcript.v1.current'],
114
+ ]);
111
115
 
112
116
  // in a previous version, deleteVatTranscripts caused overlapping SQL
113
117
  // queries, and failed
114
118
  transcriptStore.deleteVatTranscripts('v1');
115
119
  await commit();
116
120
 
117
- t.deepEqual(exportLog, [
121
+ t.deepEqual(exportLog.splice(0), [
118
122
  ['transcript.v1.0', null],
119
123
  ['transcript.v1.current', null],
120
124
  ]);
121
-
122
- exportLog.length = 0;
123
125
  });
124
126
 
125
127
  const getExport = async (dbDir, artifactMode) => {
@@ -145,16 +147,20 @@ const reImport = async (t, dbDir, artifactMode) => {
145
147
  return sqlite3(path.join(dbDir2, 'swingstore.sqlite'));
146
148
  };
147
149
 
148
- const compareNoHash = (t, obj1, obj2) => {
149
- const o1 = {};
150
- for (const [key, value] of Object.entries(obj1)) {
150
+ const stripHashes = exportData => {
151
+ const stripped = {};
152
+ const entries =
153
+ exportData instanceof Map
154
+ ? exportData.entries()
155
+ : Object.entries(exportData);
156
+ for (const [key, value] of entries) {
151
157
  const { hash: _, ...data } = JSON.parse(value);
152
- o1[key] = data;
158
+ stripped[key] = data;
153
159
  }
154
- return t.deepEqual(o1, obj2);
160
+ return stripped;
155
161
  };
156
162
 
157
- const setupTranscript = async t => {
163
+ const setupTranscript = async (t, keepTranscripts) => {
158
164
  const vatID = 'v1';
159
165
  const exportLog = [];
160
166
  const currentExportData = {};
@@ -166,7 +172,17 @@ const setupTranscript = async t => {
166
172
  };
167
173
  const [dbDir, cleanup] = await tmpDir('testdb');
168
174
  t.teardown(cleanup);
169
- const store = initSwingStore(dbDir, { exportCallback });
175
+ const [archiveDir, cleanupArchives] = await tmpDir('archives');
176
+ t.teardown(cleanupArchives);
177
+ const fsPowers = { fs, path, tmp };
178
+ const archiveSnapshot = makeArchiveSnapshot(archiveDir, fsPowers);
179
+ const archiveTranscript = makeArchiveTranscript(archiveDir, fsPowers);
180
+ const store = initSwingStore(dbDir, {
181
+ exportCallback,
182
+ keepTranscripts,
183
+ archiveSnapshot,
184
+ archiveTranscript,
185
+ });
170
186
  const { kernelStorage, hostStorage } = store;
171
187
  const { transcriptStore } = kernelStorage;
172
188
  const { commit } = hostStorage;
@@ -177,13 +193,13 @@ const setupTranscript = async t => {
177
193
  transcriptStore.initTranscript(vatID);
178
194
  transcriptStore.addItem(vatID, 'aaa');
179
195
  transcriptStore.addItem(vatID, 'bbb');
180
- transcriptStore.rolloverSpan(vatID);
196
+ await transcriptStore.rolloverSpan(vatID);
181
197
  transcriptStore.addItem(vatID, 'ccc');
182
198
  transcriptStore.addItem(vatID, 'ddd');
183
- transcriptStore.rolloverIncarnation(vatID);
199
+ await transcriptStore.rolloverIncarnation(vatID);
184
200
  transcriptStore.addItem(vatID, 'eee');
185
201
  transcriptStore.addItem(vatID, 'fff');
186
- transcriptStore.rolloverSpan(vatID);
202
+ await transcriptStore.rolloverSpan(vatID);
187
203
  transcriptStore.addItem(vatID, 'ggg');
188
204
  transcriptStore.addItem(vatID, 'hhh');
189
205
  await commit();
@@ -191,6 +207,7 @@ const setupTranscript = async t => {
191
207
  return {
192
208
  db,
193
209
  dbDir,
210
+ archiveDir,
194
211
  commit,
195
212
  transcriptStore,
196
213
  exportLog,
@@ -199,74 +216,93 @@ const setupTranscript = async t => {
199
216
  };
200
217
  };
201
218
 
202
- test('slow deletion of transcripts', async t => {
219
+ /**
220
+ * @param {import('ava').ExecutionContext} t
221
+ * @param {{ keepTranscripts: boolean }} config
222
+ */
223
+ const execSlowTranscriptDeletion = async (t, { keepTranscripts }) => {
203
224
  // slow transcript deletion should remove export-data as it removes
204
225
  // transcript spans and their items
205
226
 
206
227
  const {
207
228
  db,
208
229
  dbDir,
230
+ archiveDir,
209
231
  commit,
210
232
  transcriptStore,
211
233
  exportLog,
212
234
  currentExportData,
213
235
  vatID,
214
- } = await setupTranscript(t);
215
-
216
- t.is(exportLog.length, 4);
217
- t.is(exportLog[0][0], 'transcript.v1.0');
218
- t.is(exportLog[1][0], 'transcript.v1.2');
219
- t.is(exportLog[2][0], 'transcript.v1.4');
220
- t.is(exportLog[3][0], 'transcript.v1.current');
221
- exportLog.length = 0;
236
+ } = await setupTranscript(t, keepTranscripts);
237
+
238
+ arrayIsLike(t, exportLog.splice(0), [
239
+ ['transcript.v1.0'],
240
+ ['transcript.v1.2'],
241
+ ['transcript.v1.4'],
242
+ ['transcript.v1.current'],
243
+ ]);
222
244
  const t0 = { vatID, startPos: 0, endPos: 2, isCurrent: 0, incarnation: 0 };
223
245
  const t2 = { vatID, startPos: 2, endPos: 4, isCurrent: 0, incarnation: 0 };
224
246
  const t4 = { vatID, startPos: 4, endPos: 6, isCurrent: 0, incarnation: 1 };
225
- const tc = { vatID, startPos: 6, endPos: 8, isCurrent: 1, incarnation: 1 };
226
247
  const t6 = { vatID, startPos: 6, endPos: 8, isCurrent: 0, incarnation: 1 };
227
- compareNoHash(t, currentExportData, {
248
+ const expectedLiveExportData = {
228
249
  'transcript.v1.0': t0,
229
250
  'transcript.v1.2': t2,
230
251
  'transcript.v1.4': t4,
231
- 'transcript.v1.current': tc,
232
- });
252
+ 'transcript.v1.current': { ...t6, isCurrent: 1 },
253
+ };
254
+ const expectedStoppedExportData = {
255
+ 'transcript.v1.0': t0,
256
+ 'transcript.v1.2': t2,
257
+ 'transcript.v1.4': t4,
258
+ 'transcript.v1.6': t6,
259
+ };
260
+ const expectedArtifactContents = {
261
+ 'transcript.v1.0.2': 'aaa\nbbb\n',
262
+ 'transcript.v1.2.4': 'ccc\nddd\n',
263
+ 'transcript.v1.4.6': 'eee\nfff\n',
264
+ 'transcript.v1.6.8': 'ggg\nhhh\n',
265
+ };
266
+ const expectedArtifactNames = Object.keys(expectedArtifactContents);
233
267
 
234
- t.is(db.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 8);
268
+ t.deepEqual(stripHashes(currentExportData), expectedLiveExportData);
269
+ t.is(
270
+ db.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(),
271
+ keepTranscripts ? 8 : 2,
272
+ );
235
273
  t.is(db.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 4);
236
274
 
275
+ // verify archived transcripts
276
+ t.deepEqual(
277
+ fs.readdirSync(archiveDir),
278
+ expectedArtifactNames.slice(0, -1).map(name => `${name}.gz`),
279
+ );
280
+ for (const name of expectedArtifactNames.slice(0, -1)) {
281
+ const filePath = path.join(archiveDir, `${name}.gz`);
282
+ const contents = zlib.gunzipSync(fs.readFileSync(filePath)).toString();
283
+ t.is(contents, expectedArtifactContents[name], `${filePath} contents`);
284
+ }
285
+
237
286
  // an "operational"-mode export should list all spans, but only have
238
287
  // artifacts for the current one
239
288
  {
240
289
  const { exportData, artifactNames } = await getExport(dbDir, 'operational');
241
290
  t.deepEqual(currentExportData, mapToObj(exportData));
242
- compareNoHash(t, mapToObj(exportData), {
243
- 'transcript.v1.0': t0,
244
- 'transcript.v1.2': t2,
245
- 'transcript.v1.4': t4,
246
- 'transcript.v1.current': tc,
247
- });
248
- t.deepEqual(artifactNames, ['transcript.v1.6.8']);
291
+ t.deepEqual(stripHashes(exportData), expectedLiveExportData);
292
+ t.deepEqual(artifactNames, expectedArtifactNames.slice(-1));
249
293
  const db2 = await reImport(t, dbDir, 'operational');
250
294
  t.is(db2.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 2);
251
295
  t.is(db2.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 4);
252
296
  }
253
297
 
254
- // an "archival"-mode export should list all four spans, with
255
- // artifacts for each
256
- {
298
+ // an "archival"-mode export should list all four spans with
299
+ // artifacts for each, but is only available with keepTranscripts=true
300
+ if (!keepTranscripts) {
301
+ await t.throwsAsync(getExport(dbDir, 'archival'));
302
+ } else {
257
303
  const { exportData, artifactNames } = await getExport(dbDir, 'archival');
258
- compareNoHash(t, mapToObj(exportData), {
259
- 'transcript.v1.0': t0,
260
- 'transcript.v1.2': t2,
261
- 'transcript.v1.4': t4,
262
- 'transcript.v1.current': tc,
263
- });
264
- t.deepEqual(artifactNames, [
265
- 'transcript.v1.0.2',
266
- 'transcript.v1.2.4',
267
- 'transcript.v1.4.6',
268
- 'transcript.v1.6.8',
269
- ]);
304
+ t.deepEqual(stripHashes(exportData), expectedLiveExportData);
305
+ t.deepEqual(artifactNames, expectedArtifactNames);
270
306
  const db2 = await reImport(t, dbDir, 'archival');
271
307
  t.is(db2.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 8);
272
308
  t.is(db2.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 4);
@@ -276,60 +312,60 @@ test('slow deletion of transcripts', async t => {
276
312
  // deletes the .current record (i.e. it transforms .current into a
277
313
  // closed record)
278
314
  {
279
- transcriptStore.stopUsingTranscript(vatID);
315
+ await transcriptStore.stopUsingTranscript(vatID);
280
316
  await commit();
281
- compareNoHash(t, currentExportData, {
282
- 'transcript.v1.0': t0,
283
- 'transcript.v1.2': t2,
284
- 'transcript.v1.4': t4,
285
- 'transcript.v1.6': t6,
286
- });
317
+ t.deepEqual(stripHashes(currentExportData), expectedStoppedExportData);
287
318
  exportLog.length = 0;
288
319
  // stopUsingTranscript is idempotent
289
- transcriptStore.stopUsingTranscript(vatID);
320
+ await transcriptStore.stopUsingTranscript(vatID);
290
321
  await commit();
291
- t.is(exportLog.length, 0);
322
+ t.deepEqual(exportLog, []);
323
+ }
324
+ t.deepEqual(
325
+ fs.readdirSync(archiveDir),
326
+ expectedArtifactNames.map(name => `${name}.gz`),
327
+ );
328
+ for (const name of expectedArtifactNames) {
329
+ const filePath = path.join(archiveDir, `${name}.gz`);
330
+ const contents = zlib.gunzipSync(fs.readFileSync(filePath)).toString();
331
+ t.is(contents, expectedArtifactContents[name], `${filePath} contents`);
292
332
  }
293
333
 
294
334
  // All exports (debug and non-debug) in this "terminated but not
295
335
  // deleted" state will still have the export-data keys. Only
296
- // debug-mode will have artifacts.
336
+ // debug-mode will have artifacts (and only with keepTranscripts=true).
297
337
  for (const mode of ['operational', 'replay', 'archival', 'debug']) {
298
338
  const { exportData, artifactNames } = await getExport(dbDir, mode);
299
- compareNoHash(t, mapToObj(exportData), {
300
- 'transcript.v1.0': t0,
301
- 'transcript.v1.2': t2,
302
- 'transcript.v1.4': t4,
303
- 'transcript.v1.6': t6,
304
- });
305
- if (mode === 'debug') {
306
- t.deepEqual(artifactNames, [
307
- 'transcript.v1.0.2',
308
- 'transcript.v1.2.4',
309
- 'transcript.v1.4.6',
310
- 'transcript.v1.6.8',
311
- ]);
312
- } else {
313
- t.deepEqual(artifactNames, []);
314
- }
339
+ t.deepEqual(
340
+ stripHashes(exportData),
341
+ expectedStoppedExportData,
342
+ `${mode} stopped-vat export data`,
343
+ );
344
+ t.deepEqual(
345
+ artifactNames,
346
+ mode === 'debug' && keepTranscripts ? expectedArtifactNames : [],
347
+ `${mode} stopped-vat export artifacts`,
348
+ );
315
349
  const db2 = await reImport(t, dbDir, 'operational');
316
350
  t.is(db2.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 0);
317
351
  t.is(db2.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 4);
318
352
  }
319
353
 
320
354
  // first deletion
355
+ const expectedTruncatedExportData1 = Object.fromEntries(
356
+ Object.entries(expectedStoppedExportData).slice(0, -1),
357
+ );
321
358
  {
322
359
  // budget=1 will let it delete one span, the last one
323
360
  const dc = transcriptStore.deleteVatTranscripts(vatID, 1);
324
361
  t.false(dc.done);
325
362
  t.is(dc.cleanups, 1);
326
363
  await commit();
327
- compareNoHash(t, currentExportData, {
328
- 'transcript.v1.0': t0,
329
- 'transcript.v1.2': t2,
330
- 'transcript.v1.4': t4,
331
- });
332
- t.is(db.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 6);
364
+ t.deepEqual(stripHashes(currentExportData), expectedTruncatedExportData1);
365
+ t.is(
366
+ db.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(),
367
+ keepTranscripts ? 6 : 0,
368
+ );
333
369
  t.is(db.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 3);
334
370
  }
335
371
 
@@ -341,50 +377,53 @@ test('slow deletion of transcripts', async t => {
341
377
 
342
378
  for (const mode of ['operational', 'replay', 'archival', 'debug']) {
343
379
  const { exportData, artifactNames } = await getExport(dbDir, mode);
344
- compareNoHash(t, mapToObj(exportData), {
345
- 'transcript.v1.0': t0,
346
- 'transcript.v1.2': t2,
347
- 'transcript.v1.4': t4,
348
- });
349
- if (mode === 'debug') {
350
- t.deepEqual(artifactNames, [
351
- 'transcript.v1.0.2',
352
- 'transcript.v1.2.4',
353
- 'transcript.v1.4.6',
354
- ]);
355
- } else {
356
- t.deepEqual(artifactNames, []);
357
- }
380
+ t.deepEqual(
381
+ stripHashes(exportData),
382
+ expectedTruncatedExportData1,
383
+ `${mode} first-deletion export data`,
384
+ );
385
+ t.deepEqual(
386
+ artifactNames,
387
+ mode === 'debug' && keepTranscripts
388
+ ? expectedArtifactNames.slice(0, -1)
389
+ : [],
390
+ `${mode} first-deletion export artifacts`,
391
+ );
358
392
  const db2 = await reImport(t, dbDir, 'operational');
359
393
  t.is(db2.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 0);
360
394
  t.is(db2.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 3);
361
395
  }
362
396
 
363
397
  // second deletion
398
+ const expectedTruncatedExportData2 = Object.fromEntries(
399
+ Object.entries(expectedStoppedExportData).slice(0, -2),
400
+ );
364
401
  {
365
402
  const dc = transcriptStore.deleteVatTranscripts(vatID, 1);
366
403
  t.false(dc.done);
367
404
  t.is(dc.cleanups, 1);
368
405
  await commit();
369
- compareNoHash(t, currentExportData, {
370
- 'transcript.v1.0': t0,
371
- 'transcript.v1.2': t2,
372
- });
373
- t.is(db.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 4);
406
+ t.deepEqual(stripHashes(currentExportData), expectedTruncatedExportData2);
407
+ t.is(
408
+ db.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(),
409
+ keepTranscripts ? 4 : 0,
410
+ );
374
411
  t.is(db.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 2);
375
412
  }
376
-
377
413
  for (const mode of ['operational', 'replay', 'archival', 'debug']) {
378
414
  const { exportData, artifactNames } = await getExport(dbDir, mode);
379
- compareNoHash(t, mapToObj(exportData), {
380
- 'transcript.v1.0': t0,
381
- 'transcript.v1.2': t2,
382
- });
383
- if (mode === 'debug') {
384
- t.deepEqual(artifactNames, ['transcript.v1.0.2', 'transcript.v1.2.4']);
385
- } else {
386
- t.deepEqual(artifactNames, []);
387
- }
415
+ t.deepEqual(
416
+ stripHashes(exportData),
417
+ expectedTruncatedExportData2,
418
+ `${mode} second-deletion export data`,
419
+ );
420
+ t.deepEqual(
421
+ artifactNames,
422
+ mode === 'debug' && keepTranscripts
423
+ ? expectedArtifactNames.slice(0, -2)
424
+ : [],
425
+ `${mode} second-deletion export artifacts`,
426
+ );
388
427
  const db2 = await reImport(t, dbDir, 'operational');
389
428
  t.is(db2.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 0);
390
429
  t.is(db2.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 2);
@@ -396,15 +435,18 @@ test('slow deletion of transcripts', async t => {
396
435
  t.true(dc.done);
397
436
  t.is(dc.cleanups, 2);
398
437
  await commit();
399
- compareNoHash(t, currentExportData, {});
438
+ t.deepEqual(stripHashes(currentExportData), {});
400
439
  t.is(db.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 0);
401
440
  t.is(db.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 0);
402
441
  }
403
-
404
442
  for (const mode of ['operational', 'replay', 'archival', 'debug']) {
405
443
  const { exportData, artifactNames } = await getExport(dbDir, mode);
406
- compareNoHash(t, mapToObj(exportData), {});
407
- t.deepEqual(artifactNames, []);
444
+ t.deepEqual(
445
+ stripHashes(exportData),
446
+ {},
447
+ `${mode} final-deletion export data`,
448
+ );
449
+ t.deepEqual(artifactNames, [], `${mode} final-deletion export artifacts`);
408
450
  const db2 = await reImport(t, dbDir, 'operational');
409
451
  t.is(db2.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 0);
410
452
  t.is(db2.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 0);
@@ -417,9 +459,18 @@ test('slow deletion of transcripts', async t => {
417
459
  t.true(dc.done);
418
460
  t.is(dc.cleanups, 0);
419
461
  await commit();
420
- t.is(exportLog.length, 0);
462
+ t.deepEqual(exportLog, []);
421
463
  }
464
+ };
465
+ const testSlowTranscriptDeletion = test.macro({
466
+ title(extra = '', { keepTranscripts }) {
467
+ const detail = keepTranscripts ? 'with retention' : 'without retention';
468
+ return `slow deletion of transcripts ${detail}${extra.replace(/.$/, ' $&')}`;
469
+ },
470
+ exec: execSlowTranscriptDeletion,
422
471
  });
472
+ test(testSlowTranscriptDeletion, { keepTranscripts: true });
473
+ test(testSlowTranscriptDeletion, { keepTranscripts: false });
423
474
 
424
475
  test('slow deletion without stopUsingTranscript', async t => {
425
476
  // slow deletion should work even without stopUsingTranscript
@@ -438,7 +489,7 @@ test('slow deletion without stopUsingTranscript', async t => {
438
489
  const t0 = { vatID, startPos: 0, endPos: 2, isCurrent: 0, incarnation: 0 };
439
490
  const t2 = { vatID, startPos: 2, endPos: 4, isCurrent: 0, incarnation: 0 };
440
491
  const t4 = { vatID, startPos: 4, endPos: 6, isCurrent: 0, incarnation: 1 };
441
- compareNoHash(t, currentExportData, {
492
+ t.deepEqual(stripHashes(currentExportData), {
442
493
  'transcript.v1.0': t0,
443
494
  'transcript.v1.2': t2,
444
495
  'transcript.v1.4': t4,
@@ -556,7 +607,7 @@ test('slow deletion of snapshots', async t => {
556
607
  // stopUsingLastSnapshot is idempotent
557
608
  snapStore.stopUsingLastSnapshot(vatID);
558
609
  await commit();
559
- t.is(exportLog.length, 0);
610
+ t.deepEqual(exportLog, []);
560
611
  }
561
612
 
562
613
  // first deletion
@@ -47,7 +47,7 @@ const exportTest = test.macro(async (t, mode) => {
47
47
  // incarnation 0
48
48
  ks.transcriptStore.addItem('v1', 'start-worker'); // 0
49
49
  ks.transcriptStore.addItem('v1', 'shutdown-worker'); // 1
50
- ks.transcriptStore.rolloverIncarnation('v1');
50
+ await ks.transcriptStore.rolloverIncarnation('v1');
51
51
  const spanHash0 =
52
52
  '5bee0f44eca02f23eab03703e84ed2647d5d117fed99e1c30a3b424b7f082ab9';
53
53
 
@@ -56,7 +56,7 @@ const exportTest = test.macro(async (t, mode) => {
56
56
  ks.transcriptStore.addItem('v1', 'delivery1'); // 3
57
57
  await ks.snapStore.saveSnapshot('v1', 4, getSnapshotStream(snapshotData));
58
58
  ks.transcriptStore.addItem('v1', 'save-snapshot'); // 4
59
- ks.transcriptStore.rolloverSpan('v1'); // range= 2..5
59
+ await ks.transcriptStore.rolloverSpan('v1'); // range= 2..5
60
60
  const spanHash1 =
61
61
  '57152efdd7fdf75c03371d2b4f1088d5bf3eae7fe643babce527ff81df38998c';
62
62
 
@@ -64,7 +64,7 @@ const exportTest = test.macro(async (t, mode) => {
64
64
  ks.transcriptStore.addItem('v1', 'delivery2'); // 6
65
65
  await ks.snapStore.saveSnapshot('v1', 7, getSnapshotStream(snapshotData));
66
66
  ks.transcriptStore.addItem('v1', 'save-snapshot'); // 7
67
- ks.transcriptStore.rolloverSpan('v1'); // range= 5..8
67
+ await ks.transcriptStore.rolloverSpan('v1'); // range= 5..8
68
68
  const spanHash2 =
69
69
  '1947001e78e01bd1e773feb22b4ffc530447373b9de9274d5d5fbda3f23dbf2b';
70
70
 
@@ -251,7 +251,7 @@ test('export omits pruned span artifacts', async t => {
251
251
  ks.transcriptStore.addItem('v1', 'delivery1'); // 1
252
252
  await ks.snapStore.saveSnapshot('v1', 2, getSnapshotStream(snapshotData));
253
253
  ks.transcriptStore.addItem('v1', 'save-snapshot'); // 2
254
- ks.transcriptStore.rolloverSpan('v1'); // range= 0..3
254
+ await ks.transcriptStore.rolloverSpan('v1'); // range= 0..3
255
255
  const spanHash1 =
256
256
  '57152efdd7fdf75c03371d2b4f1088d5bf3eae7fe643babce527ff81df38998c';
257
257
  // rolloverSpan prunes the contents of the old span
@@ -87,7 +87,7 @@ async function fakeAVatSnapshot(vat, ks) {
87
87
  await ks.snapStore.saveSnapshot(vat.vatID, vat.endPos, getSnapshotStream());
88
88
  ks.transcriptStore.addItem(vat.vatID, 'save-snapshot');
89
89
  vat.endPos += 1;
90
- ks.transcriptStore.rolloverSpan(vat.vatID);
90
+ await ks.transcriptStore.rolloverSpan(vat.vatID);
91
91
  ks.transcriptStore.addItem(vat.vatID, 'load-snapshot');
92
92
  vat.endPos += 1;
93
93
  }