@agoric/swingset-liveslots 0.10.3-u16.1 → 0.10.3-u17.1

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.
@@ -0,0 +1,586 @@
1
+ import test from 'ava';
2
+
3
+ import { Far } from '@endo/marshal';
4
+ import { kser, kslot } from '@agoric/kmarshal';
5
+ import { makeLiveSlots } from '../src/liveslots.js';
6
+ import { parseVatSlot } from '../src/parseVatSlots.js';
7
+ import { buildSyscall } from './liveslots-helpers.js';
8
+ import { makeMessage, makeStartVat, makeBringOutYourDead } from './util.js';
9
+ import { makeMockGC } from './mock-gc.js';
10
+
11
+ const getPrefixedKeys = (map, prefix) => {
12
+ const keys = [];
13
+ for (const key of map.keys()) {
14
+ if (key.startsWith(prefix)) {
15
+ keys.push(key.substring(prefix.length));
16
+ }
17
+ }
18
+ return keys;
19
+ };
20
+
21
+ const collectionMetaKeys = new Set([
22
+ '|nextOrdinal',
23
+ '|entryCount',
24
+ '|schemata',
25
+ ]);
26
+
27
+ const scanCollection = (kvStore, collectionID) => {
28
+ const collectionPrefix = `vc.${collectionID}.`;
29
+ const ordinalAssignments = [];
30
+ const entries = [];
31
+ const metaKeys = [];
32
+ let totalKeys = 0;
33
+ for (const key of getPrefixedKeys(kvStore, collectionPrefix)) {
34
+ totalKeys += 1;
35
+ if (key.startsWith('|')) {
36
+ if (collectionMetaKeys.has(key)) {
37
+ metaKeys.push(key);
38
+ } else {
39
+ ordinalAssignments.push(key);
40
+ }
41
+ } else {
42
+ entries.push(key);
43
+ }
44
+ }
45
+ const keyVrefs = [];
46
+ const refcounts = {};
47
+ for (const ordinalKey of ordinalAssignments) {
48
+ const vref = ordinalKey.substring(1);
49
+ keyVrefs.push(vref);
50
+ const rcKey = `vom.rc.${vref}`;
51
+ refcounts[vref] = kvStore.get(rcKey);
52
+ }
53
+ return {
54
+ totalKeys,
55
+ metaKeys,
56
+ ordinalAssignments,
57
+ entries,
58
+ keyVrefs,
59
+ refcounts,
60
+ };
61
+ };
62
+
63
+ const GC = ['dropImports', 'retireImports', 'retireExports'];
64
+
65
+ const doTest = async (t, mode) => {
66
+ assert(['strong-clear', 'strong-delete', 'weak-delete'].includes(mode));
67
+ const kvStore = new Map();
68
+ const { syscall, log } = buildSyscall({ kvStore });
69
+ const gcTools = makeMockGC();
70
+ const COUNT = 5;
71
+
72
+ // we'll either call holder.clear() to exercise manual clearing, or
73
+ // gcTools.kill(holder) to exercise the collection itself being
74
+ // deleted
75
+
76
+ let holder;
77
+
78
+ function build(vatPowers) {
79
+ const { defineKind, makeScalarBigSetStore } = vatPowers.VatData;
80
+ const make = defineKind('target', () => ({}), {});
81
+ holder = makeScalarBigSetStore('holder');
82
+ const root = Far('root', {
83
+ create() {
84
+ for (let i = 0; i < COUNT; i += 1) {
85
+ // vrefs are all `o+v10/${N}`, N=1..10
86
+ const target = make();
87
+ holder.add(target);
88
+ // we immediately delete the presence, but the finalizers
89
+ // won't run until gcTools.flushAllFRs()
90
+ gcTools.kill(target);
91
+ }
92
+ },
93
+ clear() {
94
+ holder.clear();
95
+ },
96
+ });
97
+ return root;
98
+ }
99
+
100
+ const ls = makeLiveSlots(syscall, 'vatA', {}, {}, gcTools, undefined, () => ({
101
+ buildRootObject: build,
102
+ }));
103
+ const { dispatch, testHooks } = ls;
104
+ const { valToSlot } = testHooks;
105
+ await dispatch(makeStartVat(kser()));
106
+ log.length = 0;
107
+
108
+ const rootA = 'o+0';
109
+
110
+ await dispatch(makeMessage(rootA, 'create', []));
111
+ log.length = 0;
112
+
113
+ const holderVref = valToSlot.get(holder);
114
+ const collectionID = Number(parseVatSlot(holderVref).subid);
115
+ const populated = scanCollection(kvStore, collectionID);
116
+ t.is(populated.ordinalAssignments.length, COUNT);
117
+ t.is(populated.entries.length, COUNT);
118
+ t.is(populated.keyVrefs.length, COUNT);
119
+ t.true(populated.keyVrefs.every(vref => populated.refcounts[vref] === '1'));
120
+
121
+ // Collect the representatives, leaving only the virtual-data
122
+ // pillar. This BOYD finds non-zero virtual-data refcounts for all
123
+ // five VOs, so they are not deleted.
124
+ gcTools.flushAllFRs();
125
+ const boyd1 = await dispatch(makeBringOutYourDead());
126
+ t.is(boyd1.possiblyDeadSet, 0);
127
+ t.is(boyd1.possiblyRetiredSet, 0);
128
+ log.length = 0;
129
+ t.is(scanCollection(kvStore, collectionID).totalKeys, populated.totalKeys);
130
+
131
+ if (mode === 'strong-clear') {
132
+ // clearing the collections should delete both the data key and the
133
+ // ordinal key for each entry, but it won't delete the values, that
134
+ // is deferred until BOYD. The metadata is retained, because the
135
+ // collection has been cleared, not deleted.
136
+ await dispatch(makeMessage(rootA, 'clear', []));
137
+ } else if (mode === 'strong-delete') {
138
+ gcTools.kill(holder);
139
+ gcTools.flushAllFRs();
140
+ await dispatch(makeBringOutYourDead());
141
+ // that should clear everything, both the holder and the referenced
142
+ // targets
143
+ }
144
+
145
+ const scan2 = scanCollection(kvStore, collectionID);
146
+
147
+ // all entries should be gone
148
+ t.is(scan2.ordinalAssignments.length, 0);
149
+ t.is(scan2.entries.length, 0);
150
+ t.is(scan2.keyVrefs.length, 0);
151
+
152
+ if (mode === 'strong-clear') {
153
+ // but the collection itself is still present
154
+ t.is(scan2.metaKeys.length, populated.metaKeys.length);
155
+ for (const vref of populated.keyVrefs) {
156
+ const rcKey = `vom.rc.${vref}`;
157
+ const rc = kvStore.get(rcKey);
158
+ // the target refcounts should be zero (= undefined)
159
+ t.is(rc, undefined);
160
+ // but the data should still be present
161
+ const dataKey = `vom.${vref}`;
162
+ const data = kvStore.get(dataKey);
163
+ t.is(data, '{}');
164
+ }
165
+ // and we need one more BOYD to notice the zero refcounts and
166
+ // delete the data
167
+ await dispatch(makeBringOutYourDead());
168
+ } else if (mode === 'strong-delete') {
169
+ // the collection should be gone
170
+ t.is(scan2.metaKeys.length, 0);
171
+ t.is(scan2.totalKeys, 0);
172
+ }
173
+
174
+ // all the targets should be collected now
175
+ for (const vref of populated.keyVrefs) {
176
+ const rcKey = `vom.rc.${vref}`;
177
+ const rc = kvStore.get(rcKey);
178
+ // the target refcounts should be zero (= undefined)
179
+ t.is(rc, undefined);
180
+ const dataKey = `vom.${vref}`;
181
+ const data = kvStore.get(dataKey);
182
+ t.is(data, undefined);
183
+ }
184
+
185
+ // none of the Presences were exported, so no GC syscalls
186
+ const gcCalls1 = log.filter(l => GC.includes(l.type));
187
+ t.deepEqual(gcCalls1, []);
188
+ };
189
+
190
+ // When a virtual collection's keys are the only reference to a
191
+ // virtual object, collection.clear() should let them be deleted. Bug
192
+ // #8756 caused the keys to be retained by mistake.
193
+
194
+ test('collection.clear() deletes keys', async t => {
195
+ await doTest(t, 'strong-clear');
196
+ });
197
+
198
+ // Allowing GC to delete a strong collection should delete/release the
199
+ // keys too
200
+
201
+ test('deleting a strong collection will delete the keys', async t => {
202
+ await doTest(t, 'strong-delete');
203
+ });
204
+
205
+ // Allowing GC to delete a weak collection should retire the keys, and
206
+ // delete/release the contents.
207
+
208
+ test('deleting a weak collection will retire the keys', async t => {
209
+ const kvStore = new Map();
210
+ const { syscall, log } = buildSyscall({ kvStore });
211
+ const gcTools = makeMockGC();
212
+ const COUNT = 5;
213
+ const allVrefs = [];
214
+ const allKslots = [];
215
+ for (let i = 0; i < COUNT; i += 1) {
216
+ const vref = `o-${i + 1}`;
217
+ allVrefs.push(vref);
218
+ allKslots.push(kslot(vref, 'imported'));
219
+ }
220
+
221
+ let recognizer;
222
+
223
+ // Import a bunch of Presences and hold them in a weakset. Drop the
224
+ // imports, but retain recognition, until we drop the weakset, which
225
+ // should delete the collection and notify the kernel that we aren't
226
+ // recognizing the keys (syscall.retireImports)
227
+ function build(vatPowers) {
228
+ const { makeScalarBigWeakSetStore } = vatPowers.VatData;
229
+ recognizer = makeScalarBigWeakSetStore('recognizer');
230
+ const root = Far('root', {
231
+ create(presences) {
232
+ for (const p of presences) {
233
+ recognizer.add(p);
234
+ // we immediately delete the presence, but the finalizers
235
+ // won't run until gcTools.flushAllFRs()
236
+ gcTools.kill(p);
237
+ }
238
+ },
239
+ });
240
+ return root;
241
+ }
242
+
243
+ const ls = makeLiveSlots(syscall, 'vatA', {}, {}, gcTools, undefined, () => ({
244
+ buildRootObject: build,
245
+ }));
246
+ const { dispatch, testHooks } = ls;
247
+ const { valToSlot } = testHooks;
248
+ await dispatch(makeStartVat(kser()));
249
+ log.length = 0;
250
+
251
+ const rootA = 'o+0';
252
+
253
+ await dispatch(makeMessage(rootA, 'create', [allKslots]));
254
+ log.length = 0;
255
+
256
+ const recognizerVref = valToSlot.get(recognizer);
257
+ const collectionID = Number(parseVatSlot(recognizerVref).subid);
258
+
259
+ // all the Presences should be recognized by the collection, but not
260
+ // referenced
261
+ const populated = scanCollection(kvStore, collectionID);
262
+ t.is(populated.ordinalAssignments.length, COUNT);
263
+ t.is(populated.entries.length, COUNT);
264
+ t.is(populated.keyVrefs.length, COUNT);
265
+ t.true(
266
+ populated.keyVrefs.every(vref => populated.refcounts[vref] === undefined),
267
+ );
268
+ // and there should be recognizer (.ir) entries for each vref|collection pair
269
+ t.true(
270
+ populated.keyVrefs.every(vref =>
271
+ kvStore.has(`vom.ir.${vref}|${collectionID}`),
272
+ ),
273
+ );
274
+
275
+ // collect the Presences, which was the only remaining reachability
276
+ // pillar, leaving just the recognizers
277
+ gcTools.flushAllFRs();
278
+ await dispatch(makeBringOutYourDead());
279
+ // no changes to the collection
280
+ t.deepEqual(scanCollection(kvStore, collectionID), populated);
281
+ // but the Presence vrefs should be dropped
282
+ const gcCalls1 = log.filter(l => GC.includes(l.type));
283
+ t.deepEqual(gcCalls1, [{ type: 'dropImports', slots: allVrefs }]);
284
+ log.length = 0;
285
+
286
+ // now free the whole collection
287
+ gcTools.kill(recognizer);
288
+ gcTools.flushAllFRs();
289
+ await dispatch(makeBringOutYourDead());
290
+
291
+ // the collection should be gone
292
+ const scan2 = scanCollection(kvStore, collectionID);
293
+ t.is(scan2.totalKeys, 0);
294
+
295
+ // and the .ir entries
296
+ t.true(
297
+ populated.keyVrefs.every(
298
+ vref => !kvStore.has(`vom.ir.${vref}|${collectionID}`),
299
+ ),
300
+ );
301
+
302
+ // and the kernel should be notified that we don't care anymore
303
+ const gcCalls2 = log.filter(l => GC.includes(l.type));
304
+ t.deepEqual(gcCalls2, [{ type: 'retireImports', slots: allVrefs }]);
305
+ });
306
+
307
+ // Allowing GC to delete a voAwareWeakSet (or Map) should retire the
308
+ // keys, and delete/release the contents.
309
+
310
+ test('deleting a voAwareWeakSet will retire the keys', async t => {
311
+ const kvStore = new Map();
312
+ const { syscall, log } = buildSyscall({ kvStore });
313
+ const gcTools = makeMockGC();
314
+ const COUNT = 5;
315
+ const allVrefs = [];
316
+ const allKslots = [];
317
+ for (let i = 0; i < COUNT; i += 1) {
318
+ const vref = `o-${i + 1}`;
319
+ allVrefs.push(vref);
320
+ allKslots.push(kslot(vref, 'imported'));
321
+ }
322
+
323
+ let recognizer;
324
+
325
+ // Import a bunch of Presences and hold them in a weakset. Drop the
326
+ // imports, but retain recognition, until we drop the weakset, which
327
+ // should delete the collection and notify the kernel that we aren't
328
+ // recognizing the keys (syscall.retireImports)
329
+ function build(vatPowers) {
330
+ recognizer = new vatPowers.WeakSet();
331
+ const root = Far('root', {
332
+ create(presences) {
333
+ for (const p of presences) {
334
+ recognizer.add(p);
335
+ // we immediately delete the presence, but the finalizers
336
+ // won't run until gcTools.flushAllFRs()
337
+ gcTools.kill(p);
338
+ }
339
+ },
340
+ });
341
+ return root;
342
+ }
343
+
344
+ const ls = makeLiveSlots(syscall, 'vatA', {}, {}, gcTools, undefined, () => ({
345
+ buildRootObject: build,
346
+ }));
347
+ const { dispatch, testHooks } = ls;
348
+ const { vrefRecognizers } = testHooks;
349
+ await dispatch(makeStartVat(kser()));
350
+ log.length = 0;
351
+
352
+ const rootA = 'o+0';
353
+
354
+ await dispatch(makeMessage(rootA, 'create', [allKslots]));
355
+ log.length = 0;
356
+
357
+ // the WeakSet has no vref, and doesn't store anything like ".ir"
358
+ // entries in vatstore, but we can snoop on its internal
359
+ // tables. vrefRecognizers is a Map, keyed by vref, with an entry
360
+ // for every vref that is tracked by any voAwareWeakMap/Set. The
361
+ // value is a Set of virtualObjectMaps, the internal/hidden Set used
362
+ // by voAwareWeakMap/Sets.
363
+
364
+ const vrefKeys = [...vrefRecognizers.keys()].sort();
365
+
366
+ // we should be tracking all the presences
367
+ t.is(vrefKeys.length, COUNT);
368
+ // each vref should have a single recognizer
369
+ t.true(vrefKeys.every(vref => vrefRecognizers.get(vref).size === 1));
370
+ // that single recognizer should be the virtualObjectMap for our voAwareWeakSet
371
+ const virtualObjectMap = [...vrefRecognizers.get(vrefKeys[0])][0];
372
+ // they should all point to the same one
373
+ t.true(
374
+ vrefKeys.every(
375
+ vref => [...vrefRecognizers.get(vref)][0] === virtualObjectMap,
376
+ ),
377
+ );
378
+
379
+ // collect the Presences, which was the only remaining reachability
380
+ // pillar, leaving just the recognizers
381
+ gcTools.flushAllFRs();
382
+ await dispatch(makeBringOutYourDead());
383
+ // no changes to the collection
384
+ t.is(vrefKeys.length, COUNT);
385
+ t.true(vrefKeys.every(vref => vrefRecognizers.get(vref).size === 1));
386
+ t.true(
387
+ vrefKeys.every(
388
+ vref => [...vrefRecognizers.get(vref)][0] === virtualObjectMap,
389
+ ),
390
+ );
391
+
392
+ // but the Presence vrefs should be dropped
393
+ const gcCalls1 = log.filter(l => GC.includes(l.type));
394
+ t.deepEqual(gcCalls1, [{ type: 'dropImports', slots: allVrefs }]);
395
+ log.length = 0;
396
+
397
+ // now free the whole collection
398
+ gcTools.kill(recognizer);
399
+ gcTools.flushAllFRs();
400
+ await dispatch(makeBringOutYourDead());
401
+
402
+ // the collection should be gone
403
+ t.is(vrefRecognizers.size, 0);
404
+
405
+ // and the kernel should be notified that we don't care anymore
406
+ const gcCalls2 = log.filter(l => GC.includes(l.type));
407
+ t.deepEqual(gcCalls2, [{ type: 'retireImports', slots: allVrefs }]);
408
+ });
409
+
410
+ // explore remediation/leftover problems from bugs #7355, #8756, #9956
411
+ // where the DB has corrupted data leftover from before they were fixed
412
+
413
+ test('missing recognition record during delete', async t => {
414
+ const kvStore = new Map();
415
+ const { syscall, log } = buildSyscall({ kvStore });
416
+ const gcTools = makeMockGC();
417
+
418
+ let recognizer;
419
+ let target;
420
+
421
+ // liveslots didn't always add "vom.ir." recognition-records for
422
+ // Remotable-style keys, nor remove them when the key was
423
+ // deleted. So a kernel which adds a key, upgrades to the current
424
+ // (fixed) version, then attempts to delete the key, will not see
425
+ // the record it is expecting. Make sure this doesn't cause
426
+ // problems.
427
+
428
+ function build(vatPowers) {
429
+ const { makeScalarBigWeakSetStore } = vatPowers.VatData;
430
+ recognizer = makeScalarBigWeakSetStore('recognizer');
431
+ target = Far('target', {});
432
+ const root = Far('root', {
433
+ store() {
434
+ recognizer.add(target);
435
+ },
436
+ delete() {
437
+ recognizer.delete(target);
438
+ },
439
+ });
440
+ return root;
441
+ }
442
+
443
+ const ls = makeLiveSlots(syscall, 'vatA', {}, {}, gcTools, undefined, () => ({
444
+ buildRootObject: build,
445
+ }));
446
+ const { dispatch, testHooks } = ls;
447
+ const { valToSlot } = testHooks;
448
+ await dispatch(makeStartVat(kser()));
449
+ log.length = 0;
450
+
451
+ const rootA = 'o+0';
452
+
453
+ await dispatch(makeMessage(rootA, 'store'));
454
+ log.length = 0;
455
+
456
+ const targetVref = valToSlot.get(target);
457
+ const recognizerVref = valToSlot.get(recognizer);
458
+ const collectionID = Number(parseVatSlot(recognizerVref).subid);
459
+ const ordinalAssignmentKey = `vc.${collectionID}.|${targetVref}`;
460
+ const ordinalNumber = kvStore.get(ordinalAssignmentKey);
461
+ t.is(ordinalNumber, '1');
462
+ const dataKey = `vc.${collectionID}.r0000000001:${targetVref}`;
463
+ const value = kvStore.get(dataKey);
464
+ t.deepEqual(JSON.parse(value), { body: '#null', slots: [] });
465
+
466
+ // the correct recognition record key
467
+ const rrKey = `vom.ir.${targetVref}|${collectionID}`;
468
+
469
+ // our fixed version creates one
470
+ t.is(kvStore.get(rrKey), '1');
471
+
472
+ // now simulate data from the broken version, by deleting the
473
+ // recognition record
474
+ kvStore.delete(rrKey);
475
+
476
+ // check that deleting the same Remotable doesn't break
477
+ await dispatch(makeMessage(rootA, 'delete'));
478
+ t.false(kvStore.has(ordinalAssignmentKey));
479
+ t.false(kvStore.has(dataKey));
480
+ });
481
+
482
+ // This test is marked as failing because we do not have any
483
+ // remediation code for #8756. Collections which were cleared before
484
+ // the fix will be corrupted, such that the old keys appear to still
485
+ // be present, even after the fix has been applied. This test
486
+ // demonstrates that we can still *not* handle the following sequence:
487
+ //
488
+ // * (in old version, without fix for #8756):
489
+ // * const c = makeScalarBigMapStore();
490
+ // * const key = Far(); // or any remotable
491
+ // * c.add(key, 'value');
492
+ // * c.clear();
493
+ // * (then in new version, with fix)
494
+ // * assert.equal(c.has(key), false);
495
+ // * c.init(key, 'new value');
496
+
497
+ test.failing('leftover ordinal-assignment record during init', async t => {
498
+ const kvStore = new Map();
499
+ const { syscall, log } = buildSyscall({ kvStore });
500
+ const gcTools = makeMockGC();
501
+
502
+ let store;
503
+ let target;
504
+ /** @type {any} */
505
+ let result;
506
+
507
+ // liveslots didn't always remove the "vc.${collectionID}.|${vref}"
508
+ // ordinal-assignment records when clearing or deleting a
509
+ // collection. So a kernel which adds a key, upgrades to the current
510
+ // (fixed) version, then clears the collection, will have a leftover
511
+ // record. See if this will cause problems when iterating keys or
512
+ // re-adding the same key later.
513
+
514
+ function build(vatPowers) {
515
+ const { makeScalarBigMapStore } = vatPowers.VatData;
516
+ store = makeScalarBigMapStore('store');
517
+ target = Far('target', {});
518
+ const root = Far('root', {
519
+ store() {
520
+ try {
521
+ store.init(target, 123);
522
+ result = 'ok';
523
+ } catch (e) {
524
+ result = e;
525
+ }
526
+ },
527
+ clear() {
528
+ store.clear();
529
+ },
530
+ has() {
531
+ result = store.has(target);
532
+ },
533
+ });
534
+ return root;
535
+ }
536
+
537
+ const ls = makeLiveSlots(syscall, 'vatA', {}, {}, gcTools, undefined, () => ({
538
+ buildRootObject: build,
539
+ }));
540
+ const { dispatch, testHooks } = ls;
541
+ const { valToSlot } = testHooks;
542
+ await dispatch(makeStartVat(kser()));
543
+ log.length = 0;
544
+
545
+ const rootA = 'o+0';
546
+
547
+ result = undefined;
548
+ await dispatch(makeMessage(rootA, 'store'));
549
+ t.is(result, 'ok');
550
+
551
+ const targetVref = valToSlot.get(target);
552
+ const storeVref = valToSlot.get(store);
553
+ const collectionID = Number(parseVatSlot(storeVref).subid);
554
+ const ordinalAssignmentKey = `vc.${collectionID}.|${targetVref}`;
555
+ const ordinalNumber = kvStore.get(ordinalAssignmentKey);
556
+ t.is(ordinalNumber, '1');
557
+ const dataKey = `vc.${collectionID}.r0000000001:${targetVref}`;
558
+ const value = kvStore.get(dataKey);
559
+ t.deepEqual(JSON.parse(value), { body: '#123', slots: [] });
560
+
561
+ result = undefined;
562
+ await dispatch(makeMessage(rootA, 'clear'));
563
+
564
+ // now simulate data from the broken version, by restoring the
565
+ // ordinal-assignment record, as if the code failed to delete it
566
+
567
+ kvStore.set(ordinalAssignmentKey, '1');
568
+
569
+ // problem 1: store.has() should report "false", but incorrectly
570
+ // returns "true"
571
+
572
+ result = undefined;
573
+ await dispatch(makeMessage(rootA, 'has'));
574
+ t.is(result, false);
575
+
576
+ // problem 2: store.init() to re-add the old key should succeed, but
577
+ // incorrectly fails (because the store thinks the key is already
578
+ // present)
579
+
580
+ result = undefined;
581
+ await dispatch(makeMessage(rootA, 'store'));
582
+ t.is(result, 'ok');
583
+
584
+ // other likely problems: store.keys() will report the old key,
585
+ // store.get(oldkey) will probably crash
586
+ });
@@ -145,6 +145,14 @@ function exerciseMapOperations(t, collectionName, testStore) {
145
145
  `key "[Alleged: something missing]" not found in collection "${collectionName}"`,
146
146
  ),
147
147
  );
148
+ if (collectionName === 'map') {
149
+ // strong map, so we can .clear
150
+ testStore.clear();
151
+ for (const [key, _value] of stuff) {
152
+ t.false(testStore.has(key));
153
+ }
154
+ fillBasicMapStore(testStore);
155
+ }
148
156
  }
149
157
 
150
158
  function exerciseSetOperations(t, collectionName, testStore) {
@@ -172,6 +180,14 @@ function exerciseSetOperations(t, collectionName, testStore) {
172
180
  `key "[Alleged: something missing]" not found in collection "${collectionName}"`,
173
181
  ),
174
182
  );
183
+ if (collectionName === 'set') {
184
+ // strong set, so we can .clear
185
+ testStore.clear();
186
+ for (const [key, _value] of stuff) {
187
+ t.false(testStore.has(key));
188
+ }
189
+ fillBasicSetStore(testStore);
190
+ }
175
191
  }
176
192
 
177
193
  test('basic map operations', t => {
@@ -487,6 +503,19 @@ test('map clear', t => {
487
503
  t.is(testStore.getSize(), 0);
488
504
  });
489
505
 
506
+ test('map clear with pattern', t => {
507
+ const testStore = makeScalarBigMapStore('cmap', { keyShape: M.any() });
508
+ testStore.init('a', 'ax');
509
+ testStore.init('b', 'bx');
510
+ testStore.init('c', 'cx');
511
+ console.log(`M is`, M);
512
+ testStore.clear(M.eq('c'));
513
+ t.true(testStore.has('a'));
514
+ t.true(testStore.has('b'));
515
+ t.false(testStore.has('c'));
516
+ t.is(testStore.getSize(), 2);
517
+ });
518
+
490
519
  test('set clear', t => {
491
520
  const testStore = makeScalarBigSetStore('cset', { keyShape: M.any() });
492
521
  testStore.add('a');
@@ -499,6 +528,18 @@ test('set clear', t => {
499
528
  t.is(testStore.getSize(), 0);
500
529
  });
501
530
 
531
+ test('set clear with pattern', t => {
532
+ const testStore = makeScalarBigSetStore('cset', { keyShape: M.any() });
533
+ testStore.add('a');
534
+ testStore.add('b');
535
+ testStore.add('c');
536
+ testStore.clear(M.eq('c'));
537
+ t.true(testStore.has('a'));
538
+ t.true(testStore.has('b'));
539
+ t.false(testStore.has('c'));
540
+ t.is(testStore.getSize(), 2);
541
+ });
542
+
502
543
  test('map fail on concurrent modification', t => {
503
544
  const primeMap = makeScalarBigMapStore('fmap', {
504
545
  keyShape: M.number(),