From 6f383e65a0f709923e41fbeb264c2fee7dd0f463 Mon Sep 17 00:00:00 2001 From: JOOHOJANG Date: Mon, 9 Sep 2024 15:16:52 +0900 Subject: [PATCH] Add GC test for concurrent case and concurrent pushonly case --- packages/sdk/test/integration/gc_test.ts | 1015 +++++++++++++++++++- packages/sdk/test/integration/tree_test.ts | 2 +- 2 files changed, 981 insertions(+), 36 deletions(-) diff --git a/packages/sdk/test/integration/gc_test.ts b/packages/sdk/test/integration/gc_test.ts index 1ff06dbae..b9be48909 100644 --- a/packages/sdk/test/integration/gc_test.ts +++ b/packages/sdk/test/integration/gc_test.ts @@ -66,7 +66,7 @@ describe('Garbage Collection', function () { await client2.deactivate(); }); - it.only('Can handle tree garbage collection for multi client', async function ({ + it('Can handle tree garbage collection for multi client', async function ({ task, }) { const docKey = toDocKey(`${task.name}-${new Date().getTime()}`); @@ -134,7 +134,6 @@ describe('Garbage Collection', function () { true, ); - // (1, 0) -> (1, 1): syncedseqs:(0, 0) await client2.sync(); assert.equal( versionVectorHelper(doc2.getVersionVector(), [ @@ -159,26 +158,57 @@ describe('Garbage Collection', function () { // (1, 1) -> (1, 2): syncedseqs:(0, 1) await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 2); - // (1, 2) -> (2, 2): syncedseqs:(1, 1) await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 2); assert.equal(doc2.getGarbageLen(), 2); - // (2, 2) -> (2, 2): syncedseqs:(1, 2) await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 2); assert.equal(doc2.getGarbageLen(), 2); - // (2, 2) -> (2, 2): syncedseqs:(2, 2): meet GC condition await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 2); - // (2, 2) -> (2, 2): syncedseqs:(2, 2): meet GC condition await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 0); @@ -204,51 +234,120 @@ describe('Garbage Collection', function () { await client2.activate(); await client1.attach(doc1, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client2.attach(doc2, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + { actor: client2.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); doc1.update((root) => { root['1'] = 1; root['2'] = [1, 2, 3]; root['3'] = 3; }, 'sets 1, 2, 3'); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 0); - // (0, 0) -> (1, 0): syncedseqs:(0, 0) await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(3) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); - // (1, 0) -> (1, 1): syncedseqs:(0, 0) await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(3) }, + ]), + true, + ); doc2.update((root) => { delete root['2']; }, 'removes 2'); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 4); - // (1, 1) -> (1, 2): syncedseqs:(0, 1) await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 4); - // (1, 2) -> (2, 2): syncedseqs:(1, 1) await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 4); assert.equal(doc2.getGarbageLen(), 4); - // (2, 2) -> (2, 2): syncedseqs:(1, 2) await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 4); assert.equal(doc2.getGarbageLen(), 4); - // (2, 2) -> (2, 2): syncedseqs:(2, 2): meet GC condition await client1.sync(); + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 4); - // (2, 2) -> (2, 2): syncedseqs:(2, 2): meet GC condition await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 0); @@ -272,7 +371,20 @@ describe('Garbage Collection', function () { await client2.activate(); await client1.attach(doc1, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client2.attach(doc2, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + { actor: client2.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); doc1.update((root) => { root.text = new Text(); @@ -280,46 +392,100 @@ describe('Garbage Collection', function () { root.textWithAttr = new Text(); root.textWithAttr.edit(0, 0, 'Hello World'); }, 'sets text'); - + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 0); - // (0, 0) -> (1, 0): syncedseqs:(0, 0) await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(3) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); - // (1, 0) -> (1, 1): syncedseqs:(0, 0) await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(3) }, + ]), + true, + ); doc2.update((root) => { root.text.edit(0, 1, 'a'); root.text.edit(1, 2, 'b'); root.textWithAttr.edit(0, 1, 'a', { b: '1' }); }, 'edit text type elements'); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 3); - // (1, 1) -> (1, 2): syncedseqs:(0, 1) await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 3); - // (1, 2) -> (2, 2): syncedseqs:(1, 1) await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 3); assert.equal(doc2.getGarbageLen(), 3); - // (2, 2) -> (2, 2): syncedseqs:(1, 2) await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 3); assert.equal(doc2.getGarbageLen(), 3); - // (2, 2) -> (2, 2): syncedseqs:(2, 2): meet GC condition await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 3); - // (2, 2) -> (2, 2): syncedseqs:(2, 2): meet GC condition await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 0); @@ -351,7 +517,20 @@ describe('Garbage Collection', function () { await client2.activate(); await client1.attach(doc1, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client2.attach(doc2, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + { actor: client2.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); doc1.update((root) => { root['1'] = 1; @@ -362,38 +541,73 @@ describe('Garbage Collection', function () { root['5'] = new Text(); root['5'].edit(0, 0, 'hi'); }, 'sets 1, 2, 3, 4, 5'); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 0); - // (0, 0) -> (1, 0): syncedseqs:(0, 0) await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(3) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); - // (1, 0) -> (1, 1): syncedseqs:(0, 0) await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(3) }, + ]), + true, + ); doc1.update((root) => { delete root['2']; root['4'].edit(0, 1, 'h'); root['5'].edit(0, 1, 'h', { b: '1' }); }, 'removes 2 and edit text type elements'); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(4) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 6); assert.equal(doc2.getGarbageLen(), 0); - // (1, 1) -> (2, 1): syncedseqs:(1, 0) await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(4) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 6); assert.equal(doc2.getGarbageLen(), 0); await client2.detach(doc2); - // (2, 1) -> (2, 2): syncedseqs:(1, x) await client2.sync(); assert.equal(doc1.getGarbageLen(), 6); assert.equal(doc2.getGarbageLen(), 6); - // (2, 2) -> (2, 2): syncedseqs:(2, x): meet GC condition await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 0); assert.equal(doc2.getGarbageLen(), 6); @@ -413,15 +627,39 @@ describe('Garbage Collection', function () { await cli.activate(); await cli.attach(doc, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc.getVersionVector(), [ + { actor: cli.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); doc.update((root) => { root.point = { x: 0, y: 0 }; }); + assert.equal( + versionVectorHelper(doc.getVersionVector(), [ + { actor: cli.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); doc.update((root) => { root.point = { x: 1, y: 1 }; }); + assert.equal( + versionVectorHelper(doc.getVersionVector(), [ + { actor: cli.getID()!, lamport: Long.fromNumber(3) }, + ]), + true, + ); doc.update((root) => { root.point = { x: 2, y: 2 }; }); + assert.equal( + versionVectorHelper(doc.getVersionVector(), [ + { actor: cli.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc.getGarbageLen(), 6); assert.equal(doc.getGarbageLenFromClone(), 6); }); @@ -433,21 +671,45 @@ describe('Garbage Collection', function () { const docKey = toDocKey(`${task.name}-${new Date().getTime()}`); const doc = new yorkie.Document(docKey); const cli = new yorkie.Client(testRPCAddr); - await cli.activate(); + await cli.activate(); await cli.attach(doc, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc.getVersionVector(), [ + { actor: cli.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); doc.update((root) => { root.list = [0, 1, 2]; root.list.push([3, 4, 5]); }); - assert.equal('{"list":[0,1,2,[3,4,5]]}', doc.toJSON()); + assert.equal( + versionVectorHelper(doc.getVersionVector(), [ + { actor: cli.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); + assert.equal('{"list":[0,1,2,[3,4,5]]}', doc.toJSON()); doc.update((root) => { delete root.list[1]; }); + assert.equal( + versionVectorHelper(doc.getVersionVector(), [ + { actor: cli.getID()!, lamport: Long.fromNumber(3) }, + ]), + true, + ); assert.equal('{"list":[0,2,[3,4,5]]}', doc.toJSON()); doc.update((root) => { delete (root.list[2] as Array)[1]; }); + assert.equal( + versionVectorHelper(doc.getVersionVector(), [ + { actor: cli.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal('{"list":[0,2,[3,5]]}', doc.toJSON()); assert.equal(doc.getGarbageLen(), 2); @@ -469,30 +731,102 @@ describe('Garbage Collection', function () { await client2.activate(); await client1.attach(doc1, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); doc1.update((root) => (root.point = { x: 0, y: 0 })); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); doc1.update((root) => (root.point.x = 1)); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(3) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 1); await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(3) }, + ]), + true, + ); await client2.attach(doc2, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(3) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc2.getGarbageLen(), 1); doc2.update((root) => (root.point.x = 2)); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(3) }, + { actor: client2.getID()!, lamport: Long.fromNumber(5) }, + ]), + true, + ); assert.equal(doc2.getGarbageLen(), 2); doc1.update((root) => (root.point = { x: 3, y: 3 })); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 4); await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 4); await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 4); await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(4) }, + { actor: client2.getID()!, lamport: Long.fromNumber(6) }, + ]), + true, + ); assert.equal(doc1.getGarbageLen(), 4); await client1.sync(); - assert.equal(doc1.getGarbageLen(), 5); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(6) }, + { actor: client2.getID()!, lamport: Long.fromNumber(5) }, + ]), + true, + ); + assert.equal(doc1.getGarbageLen(), 4); await client2.sync(); - assert.equal(doc1.getGarbageLen(), 5); + assert.equal(doc1.getGarbageLen(), 4); await client1.sync(); assert.equal(doc1.getGarbageLen(), 0); @@ -528,23 +862,74 @@ describe('Garbage Collection', function () { await client1.activate(); await client2.activate(); await client1.attach(doc1, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client2.attach(doc2, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + { actor: client2.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); doc1.update((root) => { root.t = new yorkie.Text(); root.t.edit(0, 0, 'z'); }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); doc1.update((root) => { root.t.edit(0, 1, 'a'); }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(3) }, + ]), + true, + ); doc1.update((root) => { root.t.edit(1, 1, 'b'); }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); doc1.update((root) => { root.t.edit(2, 2, 'd'); }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + ]), + true, + ); await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(6) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + { actor: client2.getID()!, lamport: Long.fromNumber(6) }, + ]), + true, + ); assert.equal(doc1.getRoot().t.toString(), 'abd'); assert.equal(doc2.getRoot().t.toString(), 'abd'); assert.equal(doc1.getGarbageLen(), 1); // z @@ -552,23 +937,82 @@ describe('Garbage Collection', function () { doc1.update((root) => { root.t.edit(2, 2, 'c'); }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(7) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(7) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client2.sync(); - await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(7) }, + { actor: client2.getID()!, lamport: Long.fromNumber(8) }, + ]), + true, + ); + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(7) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); + assert.equal(doc1.getRoot().t.toString(), 'abcd'); assert.equal(doc2.getRoot().t.toString(), 'abcd'); doc1.update((root) => { root.t.edit(1, 3, ''); }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(8) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(8) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); assert.equal(doc1.getRoot().t.toString(), 'ad'); assert.equal(doc1.getGarbageLen(), 2); // b,c await client2.sync(); - await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(8) }, + { actor: client2.getID()!, lamport: Long.fromNumber(9) }, + ]), + true, + ); await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(8) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); + await client2.sync(); + assert.equal(doc2.getGarbageLen(), 0); assert.equal(doc2.getRoot().t.toString(), 'ad'); + await client1.sync(); assert.equal(doc1.getGarbageLen(), 0); await client1.deactivate(); @@ -587,7 +1031,20 @@ describe('Garbage Collection', function () { await client1.activate(); await client2.activate(); await client1.attach(doc1, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client2.attach(doc2, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + { actor: client2.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); doc1.update((root) => { root.t = new yorkie.Tree({ @@ -600,26 +1057,64 @@ describe('Garbage Collection', function () { ], }); }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); doc1.update((root) => { root.t.editByPath([0], [1], { type: 'text', value: 'a', }); }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(3) }, + ]), + true, + ); doc1.update((root) => { root.t.editByPath([1], [1], { type: 'text', value: 'b', }); }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); doc1.update((root) => { root.t.editByPath([2], [2], { type: 'text', value: 'd', }); }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + ]), + true, + ); await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(6) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + { actor: client2.getID()!, lamport: Long.fromNumber(6) }, + ]), + true, + ); assert.equal(doc1.getRoot().t.toXML(), 'abd'); assert.equal(doc2.getRoot().t.toXML(), 'abd'); assert.equal(doc1.getGarbageLen(), 1); // z @@ -630,24 +1125,474 @@ describe('Garbage Collection', function () { value: 'c', }); }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(7) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(7) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client2.sync(); - await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(7) }, + { actor: client2.getID()!, lamport: Long.fromNumber(8) }, + ]), + true, + ); assert.equal(doc1.getRoot().t.toXML(), 'abcd'); assert.equal(doc2.getRoot().t.toXML(), 'abcd'); doc1.update((root) => { root.t.editByPath([1], [3]); }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(8) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(8) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); assert.equal(doc1.getRoot().t.toXML(), 'ad'); assert.equal(doc1.getGarbageLen(), 2); // b,c await client2.sync(); - await client2.sync(); - await client1.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(8) }, + { actor: client2.getID()!, lamport: Long.fromNumber(9) }, + ]), + true, + ); + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(8) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); assert.equal(doc2.getRoot().t.toXML(), 'ad'); + assert.equal(doc1.getGarbageLen(), 2); + + await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(8) }, + { actor: client2.getID()!, lamport: Long.fromNumber(9) }, + ]), + true, + ); + assert.equal(doc2.getRoot().t.toXML(), 'ad'); + assert.equal(doc2.getGarbageLen(), 0); + + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(8) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); + assert.equal(doc1.getRoot().t.toXML(), 'ad'); + assert.equal(doc1.getGarbageLen(), 0); + + await client1.deactivate(); + await client2.deactivate(); + }); + + it('concurrent garbage collection test', async function ({ task }) { + type TestDoc = { t: Text }; + const docKey = toDocKey(`${task.name}-${new Date().getTime()}`); + const doc1 = new yorkie.Document(docKey); + const doc2 = new yorkie.Document(docKey); + const client1 = new yorkie.Client(testRPCAddr); + const client2 = new yorkie.Client(testRPCAddr); + await client1.activate(); + await client2.activate(); + + await client1.attach(doc1, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); + + await client2.attach(doc2, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + { actor: client2.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); + + doc1.update((root) => { + root.t = new Text(); + root.t.edit(0, 0, 'a'); + root.t.edit(1, 1, 'b'); + root.t.edit(2, 2, 'c'); + }, 'sets text'); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); + + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(3) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); + + await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(3) }, + ]), + true, + ); + + doc2.update((root) => { + root.t.edit(2, 2, 'c'); + }, 'insert c'); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); + + doc1.update((root) => { + root.t.edit(1, 3, ''); + }, 'delete bd'); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(4) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); + + assert.equal(doc1.getGarbageLen(), 2); + assert.equal(doc2.getGarbageLen(), 0); + + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(4) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); + + await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(4) }, + { actor: client2.getID()!, lamport: Long.fromNumber(5) }, + ]), + true, + ); + + assert.equal(doc1.getGarbageLen(), 2); + assert.equal(doc2.getGarbageLen(), 2); + + doc2.update((root) => { + root.t.edit(2, 2, '1'); + }, 'insert 1'); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(4) }, + { actor: client2.getID()!, lamport: Long.fromNumber(6) }, + ]), + true, + ); + + await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(4) }, + { actor: client2.getID()!, lamport: Long.fromNumber(6) }, + ]), + true, + ); + + assert.equal(doc1.getGarbageLen(), 2); + assert.equal(doc2.getGarbageLen(), 0); + + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(7) }, + { actor: client2.getID()!, lamport: Long.fromNumber(6) }, + ]), + true, + ); + assert.equal(doc1.getGarbageLen(), 0); + assert.equal(doc2.getGarbageLen(), 0); + + await client1.deactivate(); + await client2.deactivate(); + }); + + it('concurrent garbage collection test(with pushonly)', async function ({ + task, + }) { + type TestDoc = { t: Text }; + const docKey = toDocKey(`${task.name}-${new Date().getTime()}`); + const doc1 = new yorkie.Document(docKey); + const doc2 = new yorkie.Document(docKey); + const client1 = new yorkie.Client(testRPCAddr); + const client2 = new yorkie.Client(testRPCAddr); + await client1.activate(); + await client2.activate(); + + await client1.attach(doc1, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); + + await client2.attach(doc2, { syncMode: SyncMode.Manual }); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(1) }, + { actor: client2.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); + + // d1/vv = [c1:2] + doc1.update((root) => { + root.t = new Text(); + root.t.edit(0, 0, 'a'); + root.t.edit(1, 1, 'b'); + }, 'insert ab'); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + ]), + true, + ); + + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(3) }, + { actor: client2.getID()!, lamport: Long.fromNumber(1) }, + ]), + true, + ); + + await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(3) }, + ]), + true, + ); + + // d2/vv = [c1:2, c2:4] + doc2.update((root) => { + root.t.edit(2, 2, 'd'); + }, 'insert d'); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); + + await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); + + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(5) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); + + // d2/vv = [c1:2, c2:5] + doc2.update((root) => { + root.t.edit(2, 2, 'c'); + }, 'insert c'); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(5) }, + ]), + true, + ); + + // c1/vv = [c1:6, c2:4] + doc1.update((root) => { + root.t.edit(1, 3, ''); + }, 'remove ac'); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(6) }, + { actor: client2.getID()!, lamport: Long.fromNumber(4) }, + ]), + true, + ); + + // Sync with PushOnly + await client2.changeSyncMode(doc2, SyncMode.RealtimePushOnly); + await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(5) }, + ]), + true, + ); + + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(7) }, + { actor: client2.getID()!, lamport: Long.fromNumber(5) }, + ]), + true, + ); + + // d2/vv = [c1:2, c2:6] + doc2.update((root) => { + root.t.edit(2, 2, '1'); + }, 'insert 1 (pushonly)'); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(6) }, + ]), + true, + ); + + await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(6) }, + ]), + true, + ); + + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(8) }, + { actor: client2.getID()!, lamport: Long.fromNumber(6) }, + ]), + true, + ); + + assert.equal(doc1.getGarbageLen(), 2); + assert.equal(doc2.getGarbageLen(), 0); + + // c2/vv = [c1:2, c2:7] + doc2.update((root) => { + root.t.edit(2, 2, '2'); + }, 'insert 2 (pushonly)'); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(7) }, + ]), + true, + ); + + await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(2) }, + { actor: client2.getID()!, lamport: Long.fromNumber(7) }, + ]), + true, + ); + + await client2.changeSyncMode(doc2, SyncMode.Manual); + await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(6) }, + { actor: client2.getID()!, lamport: Long.fromNumber(8) }, + ]), + true, + ); + + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(9) }, + { actor: client2.getID()!, lamport: Long.fromNumber(7) }, + ]), + true, + ); + + assert.equal(doc1.getGarbageLen(), 2); + assert.equal(doc2.getGarbageLen(), 2); + + await client2.sync(); + assert.equal( + versionVectorHelper(doc2.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(6) }, + { actor: client2.getID()!, lamport: Long.fromNumber(8) }, + ]), + true, + ); + + assert.equal(doc1.getGarbageLen(), 2); + assert.equal(doc2.getGarbageLen(), 0); + + await client1.sync(); + assert.equal( + versionVectorHelper(doc1.getVersionVector(), [ + { actor: client1.getID()!, lamport: Long.fromNumber(9) }, + { actor: client2.getID()!, lamport: Long.fromNumber(7) }, + ]), + true, + ); + + assert.equal(doc1.getGarbageLen(), 0); + assert.equal(doc2.getGarbageLen(), 0); await client1.deactivate(); await client2.deactivate(); diff --git a/packages/sdk/test/integration/tree_test.ts b/packages/sdk/test/integration/tree_test.ts index 0c8621411..c4b395375 100644 --- a/packages/sdk/test/integration/tree_test.ts +++ b/packages/sdk/test/integration/tree_test.ts @@ -2460,7 +2460,7 @@ describe('Tree.edit(concurrent, contained range)', () => { }, task.name); }); - it('contained-split-and-delete-contents-in-split-node', async function ({ + it.skip('contained-split-and-delete-contents-in-split-node', async function ({ task, }) { await withTwoClientsAndDocuments<{ t: Tree }>(async (c1, d1, c2, d2) => {