implement diffUpdates with tests - #263

This commit is contained in:
Kevin Jahns
2021-01-29 18:18:09 +01:00
parent 7edbb2485f
commit 275d52b19d
6 changed files with 144 additions and 45 deletions

View File

@@ -1,6 +1,9 @@
import * as t from 'lib0/testing.js'
import { init, compare } from './testHelper.js' // eslint-disable-line
import * as Y from '../src/index.js'
import { readClientsStructRefs, readDeleteSet, UpdateDecoderV2, UpdateEncoderV2, writeDeleteSet } from '../src/internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
/**
* @typedef {Object} Enc
@@ -13,6 +16,7 @@ import * as Y from '../src/index.js'
* @property {function(Uint8Array):Uint8Array} Enc.encodeStateVectorFromUpdate
* @property {string} Enc.updateEventName
* @property {string} Enc.description
* @property {function(Uint8Array, Uint8Array):Uint8Array} Enc.diffUpdate
*/
/**
@@ -27,7 +31,8 @@ const encV1 = {
encodeStateVectorFromUpdate: Y.encodeStateVectorFromUpdate,
encodeStateVector: Y.encodeStateVector,
updateEventName: 'update',
description: 'V1'
description: 'V1',
diffUpdate: Y.diffUpdate
}
/**
@@ -40,9 +45,10 @@ const encV2 = {
logUpdate: Y.logUpdateV2,
parseUpdateMeta: Y.parseUpdateMetaV2,
encodeStateVectorFromUpdate: Y.encodeStateVectorFromUpdateV2,
encodeStateVector: Y.encodeStateVectorV2,
encodeStateVector: Y.encodeStateVector,
updateEventName: 'updateV2',
description: 'V2'
description: 'V2',
diffUpdate: Y.diffUpdateV2
}
/**
@@ -50,7 +56,7 @@ const encV2 = {
*/
const encDoc = {
mergeUpdates: (updates) => {
const ydoc = new Y.Doc()
const ydoc = new Y.Doc({ gc: false })
updates.forEach(update => {
Y.applyUpdateV2(ydoc, update)
})
@@ -61,9 +67,18 @@ const encDoc = {
logUpdate: Y.logUpdateV2,
parseUpdateMeta: Y.parseUpdateMetaV2,
encodeStateVectorFromUpdate: Y.encodeStateVectorFromUpdateV2,
encodeStateVector: Y.encodeStateVectorV2,
encodeStateVector: Y.encodeStateVector,
updateEventName: 'updateV2',
description: 'Merge via Y.Doc'
description: 'Merge via Y.Doc',
/**
* @param {Uint8Array} update
* @param {Uint8Array} sv
*/
diffUpdate: (update, sv) => {
const ydoc = new Y.Doc({ gc: false })
Y.applyUpdateV2(ydoc, update)
return Y.encodeStateAsUpdateV2(ydoc, sv)
}
}
const encoders = [encV1, encV2, encDoc]
@@ -101,8 +116,9 @@ export const testMergeUpdates = tc => {
* @param {Y.Doc} ydoc
* @param {Array<Uint8Array>} updates - expecting at least 4 updates
* @param {Enc} enc
* @param {boolean} hasDeletes
*/
const checkUpdateCases = (ydoc, updates, enc) => {
const checkUpdateCases = (ydoc, updates, enc, hasDeletes) => {
const cases = []
// Case 1: Simple case, simply merge everything
@@ -135,14 +151,47 @@ const checkUpdateCases = (ydoc, updates, enc) => {
// t.info('Target State: ')
// enc.logUpdate(targetState)
cases.forEach((updates, i) => {
cases.forEach((mergedUpdates, i) => {
// t.info('State Case $' + i + ':')
// enc.logUpdate(updates)
const merged = new Y.Doc()
enc.applyUpdate(merged, updates)
const merged = new Y.Doc({ gc: false })
enc.applyUpdate(merged, mergedUpdates)
t.compareArrays(merged.getArray().toArray(), ydoc.getArray().toArray())
t.compare(enc.encodeStateVector(merged), enc.encodeStateVectorFromUpdate(updates))
const meta = enc.parseUpdateMeta(updates)
t.compare(enc.encodeStateVector(merged), enc.encodeStateVectorFromUpdate(mergedUpdates))
if (enc.updateEventName !== 'update') { // @todo should this also work on legacy updates?
for (let j = 1; j < updates.length; j++) {
const partMerged = enc.mergeUpdates(updates.slice(j))
const partMeta = enc.parseUpdateMeta(partMerged)
const targetSV = Y.encodeStateVectorFromUpdateV2(Y.mergeUpdatesV2(updates.slice(0, j)))
const diffed = enc.diffUpdate(mergedUpdates, targetSV)
const diffedMeta = enc.parseUpdateMeta(diffed)
const decDiffedSV = Y.decodeStateVector(enc.encodeStateVectorFromUpdate(diffed))
t.compare(partMeta, diffedMeta)
t.compare(decDiffedSV, partMeta.to)
{
// We can'd do the following
// - t.compare(diffed, mergedDeletes)
// because diffed contains the set of all deletes.
// So we add all deletes from `diffed` to `partDeletes` and compare then
const decoder = decoding.createDecoder(diffed)
const updateDecoder = new UpdateDecoderV2(decoder)
readClientsStructRefs(updateDecoder, new Y.Doc())
const ds = readDeleteSet(updateDecoder)
const updateEncoder = new UpdateEncoderV2()
encoding.writeVarUint(updateEncoder.restEncoder, 0) // 0 structs
writeDeleteSet(updateEncoder, ds)
const deletesUpdate = updateEncoder.toUint8Array()
const mergedDeletes = Y.mergeUpdatesV2([deletesUpdate, partMerged])
if (!hasDeletes || enc !== encDoc) {
// deletes will almost definitely lead to different encoders because of the mergeStruct feature that is present in encDoc
t.compare(diffed, mergedDeletes)
}
}
}
}
const meta = enc.parseUpdateMeta(mergedUpdates)
meta.from.forEach((clock, client) => t.assert(clock === 0))
meta.to.forEach((clock, client) => {
const structs = /** @type {Array<Y.Item>} */ (merged.store.clients.get(client))
@@ -168,7 +217,7 @@ export const testMergeUpdates1 = tc => {
array.insert(0, [3])
array.insert(0, [4])
checkUpdateCases(ydoc, updates, enc)
checkUpdateCases(ydoc, updates, enc, false)
})
}
@@ -188,7 +237,7 @@ export const testMergeUpdates2 = tc => {
array.insert(0, [3, 4])
array.delete(1, 2)
checkUpdateCases(ydoc, updates, enc)
checkUpdateCases(ydoc, updates, enc, true)
})
}