fix encoding and rb tree tests

This commit is contained in:
Kevin Jahns 2017-10-15 12:17:25 +02:00
parent 0e426f8928
commit 4eec8ecdd3
15 changed files with 151 additions and 175 deletions

5
package-lock.json generated
View File

@ -4206,6 +4206,11 @@
"string.prototype.codepointat": "0.2.0" "string.prototype.codepointat": "0.2.0"
} }
}, },
"utf8": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz",
"integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY="
},
"util-deprecate": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@ -64,6 +64,8 @@
"tag-dist-files": "^0.1.6" "tag-dist-files": "^0.1.6"
}, },
"dependencies": { "dependencies": {
"debug": "^2.6.8" "debug": "^2.6.8",
"utf-8": "^1.0.0",
"utf8": "^2.1.2"
} }
} }

View File

@ -3,7 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'
import multiEntry from 'rollup-plugin-multi-entry' import multiEntry from 'rollup-plugin-multi-entry'
export default { export default {
entry: 'test/*.js', entry: 'test/{encode-decode,red-black-tree}.js',
moduleName: 'y-tests', moduleName: 'y-tests',
format: 'umd', format: 'umd',
plugins: [ plugins: [

View File

@ -1,4 +1,4 @@
import '../../node_modules/utf8/utf8.js' import utf8 from 'utf-8'
export default class BinaryDecoder { export default class BinaryDecoder {
constructor (buffer) { constructor (buffer) {

View File

@ -1,4 +1,4 @@
import '../../node_modules/utf8/utf8.js' import utf8 from 'utf-8'
const bits7 = 0b1111111 const bits7 = 0b1111111
const bits8 = 0b11111111 const bits8 = 0b11111111

View File

@ -65,7 +65,7 @@ export function readDeleteSet (y, decoder) {
} }
var pos = 0 var pos = 0
var d = dv[pos] var d = dv[pos]
y.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function (n) { y.ds.iterate([user, 0], [user, Number.MAX_VALUE], function (n) {
// cases: // cases:
// 1. d deletes something to the right of n // 1. d deletes something to the right of n
// => go to next n (break) // => go to next n (break)

View File

@ -1,4 +1,4 @@
import { getStruct } from '../Util/StructReferences.js' import { getStruct } from '../Util/structReferences.js'
import BinaryDecoder from '../Binary/Decoder.js' import BinaryDecoder from '../Binary/Decoder.js'
class MissingEntry { class MissingEntry {

View File

@ -14,7 +14,7 @@ export function writeStateSet (encoder) {
let lenPosition = encoder.pos let lenPosition = encoder.pos
let len = 0 let len = 0
encoder.writeUint32(0) encoder.writeUint32(0)
this.ss.iterate(this, null, null, function (n) { this.ss.iterate(null, null, function (n) {
encoder.writeVarUint(n.id[0]) encoder.writeVarUint(n.id[0])
encoder.writeVarUint(n.clock) encoder.writeVarUint(n.clock)
len++ len++

View File

@ -1,5 +1,5 @@
import { getStruct } from '../Util/StructReferences.js' import { getStruct } from '../Util/structReferences.js'
export function stringifyUpdate (decoder, strBuilder) { export function stringifyUpdate (decoder, strBuilder) {
while (decoder.length !== decoder.pos) { while (decoder.length !== decoder.pos) {

View File

@ -10,7 +10,7 @@ export default class ID {
return new ID(this.user, this.clock) return new ID(this.user, this.clock)
} }
equals (id) { equals (id) {
return id !== null && id.user === this.user && id.clock === this.user return id !== null && id.user === this.user && id.clock === this.clock
} }
lessThan (id) { lessThan (id) {
return this.user < id.user || (this.user === id.user && this.clock < id.clock) return this.user < id.user || (this.user === id.user && this.clock < id.clock)

View File

@ -232,11 +232,11 @@ export default class Tree {
findNode (id) { findNode (id) {
var o = this.root var o = this.root
if (o === null) { if (o === null) {
return false return null
} else { } else {
while (true) { while (true) {
if (o === null) { if (o === null) {
return false return null
} }
if (id.lessThan(o.val.id)) { if (id.lessThan(o.val.id)) {
o = o.left o = o.left
@ -249,9 +249,6 @@ export default class Tree {
} }
} }
delete (id) { delete (id) {
if (id == null || id.constructor !== Array) {
throw new Error('id is expected to be an Array!')
}
var d = this.findNode(id) var d = this.findNode(id)
if (d == null) { if (d == null) {
// throw new Error('Element does not exist!') // throw new Error('Element does not exist!')

View File

@ -1,11 +1,3 @@
// import debug from 'debug'
export function debug (namespace) {
return function log (message) {
console.log(namespace, message)
}
}
import DeleteStore from './Store/DeleteStore.js' import DeleteStore from './Store/DeleteStore.js'
import OperationStore from './Store/OperationStore.js' import OperationStore from './Store/OperationStore.js'
import StateStore from './Store/StateStore.js' import StateStore from './Store/StateStore.js'
@ -21,6 +13,8 @@ import YMap from './Type/YMap.js'
import YText from './Type/YText.js' import YText from './Type/YText.js'
import YXml from './Type/YXml.js' import YXml from './Type/YXml.js'
import debug from 'debug'
export default class Y { export default class Y {
constructor (opts) { constructor (opts) {
this.userID = generateUserID() this.userID = generateUserID()
@ -101,6 +95,8 @@ Y.Map = YMap
Y.Text = YText Y.Text = YText
Y.Xml = YXml Y.Xml = YXml
export { default as debug } from 'debug'
Y.debug = debug Y.debug = debug
debug.formatters.Y = messageToString debug.formatters.Y = messageToString
debug.formatters.y = messageToRoomname debug.formatters.y = messageToRoomname

View File

@ -1,9 +1,9 @@
import { test } from '../node_modules/cutest/cutest.js' import { test } from '../node_modules/cutest/cutest.mjs'
import '../node_modules/chance/chance.js' import '../node_modules/chance/chance.js'
import Y from '../src/y.js'
import BinaryEncoder from '../src/Binary/Encoder.js' import BinaryEncoder from '../src/Binary/Encoder.js'
import BinaryDecoder from '../src/Binary/Decoder.js' import BinaryDecoder from '../src/Binary/Decoder.js'
import { generateUserID } from '../src/Util/generateUserID.js' import { generateUserID } from '../src/Util/generateUserID.js'
import Chance from 'chance'
function testEncoding (t, write, read, val) { function testEncoding (t, write, read, val) {
let encoder = new BinaryEncoder() let encoder = new BinaryEncoder()

View File

@ -1,4 +1,5 @@
import Y from '../src/y.js' import RedBlackTree from '../src/Util/Tree.js'
import ID from '../src/Util/ID.js'
import Chance from 'chance' import Chance from 'chance'
import { test, proxyConsole } from 'cutest' import { test, proxyConsole } from 'cutest'
@ -53,23 +54,16 @@ function checkRootNodeIsBlack (t, tree) {
} }
test('RedBlack Tree', async function redBlackTree (t) { test('RedBlack Tree', async function redBlackTree (t) {
let memory = new Y.memory(null, { // eslint-disable-line let tree = new RedBlackTree()
name: 'Memory', tree.put({id: new ID(8433, 0)})
gcTimeout: -1 tree.put({id: new ID(12844, 0)})
}) tree.put({id: new ID(1795, 0)})
let tree = memory.os tree.put({id: new ID(30302, 0)})
memory.requestTransaction(function () { tree.put({id: new ID(64287)})
tree.put({id: [8433]}) tree.delete(new ID(8433, 0))
tree.put({id: [12844]}) tree.put({id: new ID(28996)})
tree.put({id: [1795]}) tree.delete(new ID(64287))
tree.put({id: [30302]}) tree.put({id: new ID(22721)})
tree.put({id: [64287]})
tree.delete([8433])
tree.put({id: [28996]})
tree.delete([64287])
tree.put({id: [22721]})
})
await memory.whenTransactionsFinished()
checkRootNodeIsBlack(t, tree) checkRootNodeIsBlack(t, tree)
checkBlackHeightOfSubTreesAreEqual(t, tree) checkBlackHeightOfSubTreesAreEqual(t, tree)
checkRedNodesDoNotHaveBlackChildren(t, tree) checkRedNodesDoNotHaveBlackChildren(t, tree)
@ -77,141 +71,122 @@ test('RedBlack Tree', async function redBlackTree (t) {
test(`random tests (${numberOfRBTreeTests})`, async function random (t) { test(`random tests (${numberOfRBTreeTests})`, async function random (t) {
let chance = new Chance(t.getSeed() * 1000000000) let chance = new Chance(t.getSeed() * 1000000000)
let memory = new Y.memory(null, { // eslint-disable-line let tree = new RedBlackTree()
name: 'Memory',
gcTimeout: -1
})
let tree = memory.os
let elements = [] let elements = []
memory.requestTransaction(function () { for (var i = 0; i < numberOfRBTreeTests; i++) {
for (var i = 0; i < numberOfRBTreeTests; i++) { if (chance.bool({likelihood: 80})) {
if (chance.bool({likelihood: 80})) { // 80% chance to insert an element
// 80% chance to insert an element let obj = new ID(chance.integer({min: 0, max: numberOfRBTreeTests}), chance.integer({min: 0, max: 1}))
let obj = [chance.integer({min: 0, max: numberOfRBTreeTests})] let nodeExists = tree.find(obj)
let nodeExists = tree.find(obj) if (nodeExists === null) {
if (!nodeExists) { if (elements.some(e => e.equals(obj))) {
if (elements.some(e => e[0] === obj[0])) { t.assert(false, 'tree and elements contain different results')
t.assert(false, 'tree and elements contain different results')
}
elements.push(obj)
tree.put({id: obj})
} }
} else if (elements.length > 0) { elements.push(obj)
// ~20% chance to delete an element tree.put({id: obj})
var elem = chance.pickone(elements)
elements = elements.filter(function (e) {
return !Y.utils.compareIds(e, elem)
})
tree.delete(elem)
} }
} else if (elements.length > 0) {
// ~20% chance to delete an element
var elem = chance.pickone(elements)
elements = elements.filter(function (e) {
return !e.equals(elem)
})
tree.delete(elem)
} }
}) }
await memory.whenTransactionsFinished()
checkRootNodeIsBlack(t, tree) checkRootNodeIsBlack(t, tree)
checkBlackHeightOfSubTreesAreEqual(t, tree) checkBlackHeightOfSubTreesAreEqual(t, tree)
checkRedNodesDoNotHaveBlackChildren(t, tree) checkRedNodesDoNotHaveBlackChildren(t, tree)
memory.requestTransaction(function () { // TEST if all nodes exist
let allNodesExist = true let allNodesExist = true
for (let id of elements) { for (let id of elements) {
let node = tree.find(id) let node = tree.find(id)
if (!Y.utils.compareIds(node.id, id)) { if (!node.id.equals(id)) {
allNodesExist = false allNodesExist = false
}
} }
t.assert(allNodesExist, 'All inserted nodes exist') }
}) t.assert(allNodesExist, 'All inserted nodes exist')
memory.requestTransaction(function () { // TEST lower bound search
let findAllNodesWithLowerBoundSerach = true let findAllNodesWithLowerBoundSerach = true
for (let id of elements) { for (let id of elements) {
let node = tree.findWithLowerBound(id) let node = tree.findWithLowerBound(id)
if (!Y.utils.compareIds(node.id, id)) { if (!node.id.equals(id)) {
findAllNodesWithLowerBoundSerach = false findAllNodesWithLowerBoundSerach = false
}
} }
t.assert( }
findAllNodesWithLowerBoundSerach, t.assert(
'Find every object with lower bound search' findAllNodesWithLowerBoundSerach,
) 'Find every object with lower bound search'
}) )
// TEST iteration (with lower bound search)
memory.requestTransaction(function () { let lowerBound = chance.pickone(elements)
let lowerBound = chance.pickone(elements) let expectedResults = elements.filter((e, pos) =>
let expectedResults = elements.filter((e, pos) => (lowerBound.lessThan(e) || e.equals(lowerBound)) &&
(Y.utils.smaller(lowerBound, e) || Y.utils.compareIds(e, lowerBound)) && elements.indexOf(e) === pos
elements.indexOf(e) === pos ).length
).length let actualResults = 0
let actualResults = 0 tree.iterate(lowerBound, null, function (val) {
tree.iterate(this, lowerBound, null, function (val) { if (val == null) {
if (val == null) { t.assert(false, 'val is undefined!')
t.assert(false, 'val is undefined!')
}
actualResults++
})
t.assert(
expectedResults === actualResults,
'Iterating over a tree with lower bound yields the right amount of results'
)
})
memory.requestTransaction(function () {
let expectedResults = elements.filter((e, pos) =>
elements.indexOf(e) === pos
).length
let actualResults = 0
tree.iterate(this, null, null, function (val) {
if (val == null) {
t.assert(false, 'val is undefined!')
}
actualResults++
})
t.assert(
expectedResults === actualResults,
'iterating over a tree without bounds yields the right amount of results'
)
})
memory.requestTransaction(function () {
let upperBound = chance.pickone(elements)
let expectedResults = elements.filter((e, pos) =>
(Y.utils.smaller(e, upperBound) || Y.utils.compareIds(e, upperBound)) &&
elements.indexOf(e) === pos
).length
let actualResults = 0
tree.iterate(this, null, upperBound, function (val) {
if (val == null) {
t.assert(false, 'val is undefined!')
}
actualResults++
})
t.assert(
expectedResults === actualResults,
'iterating over a tree with upper bound yields the right amount of results'
)
})
memory.requestTransaction(function () {
let upperBound = chance.pickone(elements)
let lowerBound = chance.pickone(elements)
if (Y.utils.smaller(upperBound, lowerBound)) {
[lowerBound, upperBound] = [upperBound, lowerBound]
} }
let expectedResults = elements.filter((e, pos) => actualResults++
(Y.utils.smaller(lowerBound, e) || Y.utils.compareIds(e, lowerBound)) &&
(Y.utils.smaller(e, upperBound) || Y.utils.compareIds(e, upperBound)) &&
elements.indexOf(e) === pos
).length
let actualResults = 0
tree.iterate(this, lowerBound, upperBound, function (val) {
if (val == null) {
t.assert(false, 'val is undefined!')
}
actualResults++
})
t.assert(
expectedResults === actualResults,
'iterating over a tree with upper bound yields the right amount of results'
)
}) })
t.assert(
expectedResults === actualResults,
'Iterating over a tree with lower bound yields the right amount of results'
)
await memory.whenTransactionsFinished() expectedResults = elements.filter((e, pos) =>
elements.indexOf(e) === pos
).length
actualResults = 0
tree.iterate(null, null, function (val) {
if (val == null) {
t.assert(false, 'val is undefined!')
}
actualResults++
})
t.assert(
expectedResults === actualResults,
'iterating over a tree without bounds yields the right amount of results'
)
let upperBound = chance.pickone(elements)
expectedResults = elements.filter((e, pos) =>
(e.lessThan(upperBound) || e.equals(upperBound)) &&
elements.indexOf(e) === pos
).length
actualResults = 0
tree.iterate(null, upperBound, function (val) {
if (val == null) {
t.assert(false, 'val is undefined!')
}
actualResults++
})
t.assert(
expectedResults === actualResults,
'iterating over a tree with upper bound yields the right amount of results'
)
upperBound = chance.pickone(elements)
lowerBound = chance.pickone(elements)
if (upperBound.lessThan(lowerBound)) {
[lowerBound, upperBound] = [upperBound, lowerBound]
}
expectedResults = elements.filter((e, pos) =>
(lowerBound.lessThan(e) || e.equals(lowerBound)) &&
(e.lessThan(upperBound) || e.equals(upperBound)) &&
elements.indexOf(e) === pos
).length
actualResults = 0
tree.iterate(lowerBound, upperBound, function (val) {
if (val == null) {
t.assert(false, 'val is undefined!')
}
actualResults++
})
t.assert(
expectedResults === actualResults,
'iterating over a tree with upper bound yields the right amount of results'
)
}) })

View File

@ -1,13 +1,15 @@
import _Y from '../../yjs/src/y.js' import _Y from '../src/Y.js'
import yTest from './test-connector.js' import yTest from './test-connector.js'
import Chance from 'chance' import Chance from 'chance'
export let Y = _Y export const Y = _Y
export var database = { name: 'memory' } Y.extend(yTest)
export var connector = { name: 'test', url: 'http://localhost:1234' }
export const database = { name: 'memory' }
export const connector = { name: 'test', url: 'http://localhost:1234' }
function getStateSet (y) { function getStateSet (y) {
let ss = {} let ss = {}
@ -19,7 +21,7 @@ function getStateSet (y) {
function getDeleteSet (y) { function getDeleteSet (y) {
var ds = {} var ds = {}
y.ds.iterate(this, null, null, function (n) { y.ds.iterate(null, null, function (n) {
var user = n.id[0] var user = n.id[0]
var counter = n.id[1] var counter = n.id[1]
var len = n.len var len = n.len
@ -108,7 +110,7 @@ export async function compareUsers (t, users) {
var data = users.forEach(u => { var data = users.forEach(u => {
var data = {} var data = {}
let ops = [] let ops = []
y.os.iterate(null, null, function (op) { u.os.iterate(null, null, function (op) {
if (!op._deleted) { if (!op._deleted) {
ops.push({ ops.push({
left: op._left, left: op._left,
@ -132,8 +134,7 @@ export async function compareUsers (t, users) {
for (var i = 0; i < data.length - 1; i++) { for (var i = 0; i < data.length - 1; i++) {
await t.asyncGroup(async () => { await t.asyncGroup(async () => {
t.compare(userArrayValues[i], userArrayValues[i + 1], 'array types') t.compare(userArrayValues[i], userArrayValues[i + 1], 'array types')
t.compare(userMapOneValues[i], userMapOneValues[i + 1], 'map types (propery "one")') t.compare(userMapValues[i], userMapValues[i + 1], 'map types')
t.compare(userMapTwoValues[i], userMapTwoValues[i + 1], 'map types (propery "two")')
t.compare(userXmlValues[i], userXmlValues[i + 1], 'xml types') t.compare(userXmlValues[i], userXmlValues[i + 1], 'xml types')
t.compare(data[i].os, data[i + 1].os, 'os') t.compare(data[i].os, data[i + 1].os, 'os')
t.compare(data[i].ds, data[i + 1].ds, 'ds') t.compare(data[i].ds, data[i + 1].ds, 'ds')