Skip to content

Commit

Permalink
add tests to snapshots case and fix the case of empty ranges
Browse files Browse the repository at this point in the history
  • Loading branch information
dmonad committed Sep 28, 2020
1 parent be8cc8a commit e1a2ccd
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 43 deletions.
61 changes: 22 additions & 39 deletions src/utils/Snapshot.js
Expand Up @@ -3,7 +3,6 @@ import {
isDeleted,
createDeleteSetFromStructStore,
getStateVector,
getItem,
getItemCleanStart,
iterateDeletedStructs,
writeDeleteSet,
Expand All @@ -12,22 +11,18 @@ import {
readStateVector,
createDeleteSet,
createID,
ID,
getState,
findIndexCleanStart,
AbstractStruct,
writeClientsStructs,
findIndexSS,
readUpdateV2,
UpdateEncoderV2, UpdateDecoderV2,
AbstractDSDecoder, AbstractDSEncoder, DSEncoderV1, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line
UpdateEncoderV2,
DefaultDSEncoder,
applyUpdateV2,
AbstractDSDecoder, AbstractDSEncoder, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line
} from '../internals.js'

import * as map from 'lib0/map.js'
import * as set from 'lib0/set.js'
import * as decoding from 'lib0/decoding.js'
import * as encoding from 'lib0/encoding.js'
import { DefaultDSEncoder } from './encoding.js'

export class Snapshot {
/**
Expand Down Expand Up @@ -161,59 +156,47 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => {
/**
* @param {Doc} originDoc
* @param {Snapshot} snapshot
* @param {Doc} [newDoc] Optionally, you may define the Yjs document that receives the data from originDoc
* @return {Doc}
*/
export const createDocFromSnapshot = (originDoc, snapshot) => {
export const createDocFromSnapshot = (originDoc, snapshot, newDoc = new Doc()) => {
if (originDoc.gc) {
// we should not try to restore a GC-ed document, because some of the restored items might have their content deleted
throw new Error('originDoc must not be garbage collected')
}
const { sv, ds } = snapshot
const needState = new Map(sv)

let len = 0
const tempStructs = []
/**
* State Map
* @type any[]
*/
const itemsToIntegrate = []

/**
* @type Uint8Array
*/
let updateBuffer = new Uint8Array()
const encoder = new UpdateEncoderV2()
originDoc.transact(transaction => {
const encoder = new UpdateEncoderV2()

encoding.writeVarUint(encoder.restEncoder, sv.size)
let size = 0
sv.forEach(clock => {
if (clock > 0) {
size++
}
})
encoding.writeVarUint(encoder.restEncoder, size)
// splitting the structs before writing them to the encoder
for (const [client, clock] of sv) {
if (clock === 0) {
continue
}
if (clock < getState(originDoc.store, client)) {
getItemCleanStart(transaction, createID(client, clock))
}

const structs = originDoc.store.clients.get(client) || []
const lastStructIndex = findIndexSS(structs, clock - 1)
// write # encoded structs
encoding.writeVarUint(encoder.restEncoder, lastStructIndex + 1)
encoder.writeClient(client)
encoding.writeVarUint(encoder.restEncoder, structs[0].id.clock)
const firstStruct = structs[0]
firstStruct.write(encoder, 0)
for (let i = 1; i <= lastStructIndex; i++) {
// first clock written is 0
encoding.writeVarUint(encoder.restEncoder, 0)
for (let i = 0; i <= lastStructIndex; i++) {
structs[i].write(encoder, 0)
}
}

writeDeleteSet(encoder, ds)

updateBuffer = encoder.toUint8Array()
})

const newDoc = new Doc()

readUpdateV2(decoding.createDecoder(updateBuffer), newDoc, 'snapshot')

applyUpdateV2(newDoc, encoder.toUint8Array(), 'snapshot')
return newDoc
}
}
24 changes: 20 additions & 4 deletions tests/snapshot.tests.js
Expand Up @@ -17,6 +17,26 @@ export const testBasicRestoreSnapshot = tc => {
t.compare(doc.getArray('array').toArray(), ['hello', 'world'])
}

/**
* @param {t.TestCase} tc
*/
export const testEmptyRestoreSnapshot = tc => {
const doc = new Doc({ gc: false })
const snap = snapshot(doc)
snap.sv.set(9999, 0)
doc.getArray().insert(0, ['world'])

const docRestored = createDocFromSnapshot(doc, snap)

t.compare(docRestored.getArray().toArray(), [])
t.compare(doc.getArray().toArray(), ['world'])

// now this snapshot reflects the latest state. It shoult still work.
const snap2 = snapshot(doc)
const docRestored2 = createDocFromSnapshot(doc, snap2)
t.compare(docRestored2.getArray().toArray(), ['world'])
}

/**
* @param {t.TestCase} tc
*/
Expand Down Expand Up @@ -74,7 +94,6 @@ export const testRestoreLeftItem = tc => {
t.compare(doc.getArray('array').toArray(), ['item0'])
}


/**
* @param {t.TestCase} tc
*/
Expand All @@ -91,7 +110,6 @@ export const testDeletedItemsBase = tc => {
t.compare(doc.getArray('array').toArray(), ['item0'])
}


/**
* @param {t.TestCase} tc
*/
Expand All @@ -108,7 +126,6 @@ export const testDeletedItems2 = tc => {
t.compare(doc.getArray('array').toArray(), ['item0', 'item1', 'item3'])
}


/**
* @param {t.TestCase} tc
*/
Expand Down Expand Up @@ -152,4 +169,3 @@ export const testDependentChanges = tc => {
const docRestored1 = createDocFromSnapshot(array1.doc, snap)
t.compare(docRestored1.getArray('array').toArray(), ['user1item1', 'user2item1'])
}

0 comments on commit e1a2ccd

Please sign in to comment.