yjs/src/Helper.spec.js

261 lines
6.8 KiB
JavaScript

/* global Y */
/* eslint-env browser, jasmine */
/*
This is just a compilation of functions that help to test this library!
*/
// When testing, you store everything on the global object. We call it g
var g
if (typeof global !== 'undefined') {
g = global
} else if (typeof window !== 'undefined') {
g = window
} else {
throw new Error('No global object?')
}
g.g = g
g.YConcurrency_TestingMode = true
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000
g.describeManyTimes = function describeManyTimes (times, name, f) {
for (var i = 0; i < times; i++) {
describe(name, f)
}
}
/*
Wait for a specified amount of time (in ms). defaults to 5ms
*/
function wait (t) {
if (t == null) {
t = 5
}
return new Promise(function (resolve) {
setTimeout(function () {
resolve()
}, t)
})
}
g.wait = wait
/*
returns a random element of o.
works on Object, and Array
*/
function getRandom (o) {
if (o instanceof Array) {
return o[Math.floor(Math.random() * o.length)]
} else if (o.constructor === Object) {
var ks = []
for (var key in o) {
ks.push(key)
}
return o[getRandom(ks)]
}
}
g.getRandom = getRandom
function getRandomNumber (n) {
if (n == null) {
n = 9999
}
return Math.floor(Math.random() * n)
}
g.getRandomNumber = getRandomNumber
g.applyRandomTransactions = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
function randomTransaction (root) {
var f = getRandom(transactions)
f(root)
}
function * applyTransactions (relAmount) {
for (var i = 0; i < numberOfTransactions * relAmount + 1; i++) {
var r = Math.random()
if (r >= 0.9) {
// 10% chance to flush
users[0].connector.flushOne() // flushes for some user.. (not necessarily 0)
} else if (r >= 0.1) {
// 80% chance to create operation
randomTransaction(getRandom(objects))
} else {
// 10% chance to disconnect/reconnect
var u = getRandom(users)
if (u.connector.isDisconnected()) {
u.reconnect()
} else {
u.disconnect()
}
}
yield wait()
}
}
yield* applyTransactions(0.5)
yield users[0].connector.flushAll()
yield g.garbageCollectAllUsers(users)
yield wait()
users[0].disconnect()
yield wait()
yield* applyTransactions(0.5)
yield users[0].connector.flushAll()
yield wait(50)
for (var u in users) {
users[u].reconnect()
}
yield wait(100)
yield users[0].connector.flushAll()
})
g.garbageCollectAllUsers = async(function * garbageCollectAllUsers (users) {
return yield wait(100)// TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
for (var i in users) {
yield users[i].db.garbageCollect()
yield users[i].db.garbageCollect()
}
})
g.compareAllUsers = async(function * compareAllUsers (users) {
var s1, s2 // state sets
var ds1, ds2 // delete sets
var allDels1, allDels2 // all deletions
var db1 = [] // operation store of user1
// t1 and t2 basically do the same. They define t[1,2], ds[1,2], and allDels[1,2]
function * t1 () {
s1 = yield* this.getStateSet()
ds1 = yield* this.getDeleteSet()
allDels1 = []
this.ds.iterate(null, null, function (d) {
allDels1.push(d)
})
}
function * t2 () {
s2 = yield* this.getStateSet()
ds2 = yield* this.getDeleteSet()
allDels2 = []
this.ds.iterate(null, null, function (d) {
allDels2.push(d)
})
}
yield users[0].connector.flushAll()
// gc two times because of the two gc phases (really collect everything)
yield g.garbageCollectAllUsers(users)
for (var uid = 0; uid < users.length; uid++) {
var u = users[uid]
// compare deleted ops against deleteStore
u.db.os.iterate(null, null, function (o) {
if (o.deleted === true) {
expect(u.db.ds.isDeleted(o.id)).toBeTruthy()
}
})
// compare deleteStore against deleted ops
u.db.requestTransaction(function * () {
var ds = []
u.db.ds.iterate(null, null, function (d) {
ds.push(d)
})
for (var j in ds) {
var d = ds[j]
for (var i = 0; i < d.len; i++) {
var o = yield* this.getOperation([d.id[0], d.id[1] + i])
// gc'd or deleted
if (d.gc) {
expect(o).toBeUndefined()
} else {
expect(o.deleted).toBeTruthy()
}
}
}
})
// compare allDels tree
yield wait()
if (s1 == null) {
u.db.requestTransaction(t1)
yield wait()
u.db.os.iterate(null, null, function (o) {
db1.push(o)
})
} else {
u.db.requestTransaction(t2)
yield wait()
expect(s1).toEqual(s2)
expect(allDels1).toEqual(allDels2) // inner structure
expect(ds1).toEqual(ds2) // exported structure
var count = 0
u.db.os.iterate(null, null, function (o) {
expect(db1[count++]).toEqual(o)
})
}
}
})
g.createUsers = async(function * createUsers (self, numberOfUsers) {
if (Y.utils.globalRoom.users[0] != null) {
yield Y.utils.globalRoom.users[0].flushAll()
}
// destroy old users
for (var u in Y.utils.globalRoom.users) {
Y.utils.globalRoom.users[u].y.destroy()
}
self.users = null
var promises = []
for (var i = 0; i < numberOfUsers; i++) {
promises.push(Y({
db: {
name: 'Memory',
gcTimeout: -1
},
connector: {
name: 'Test',
debug: false
}
}))
}
self.users = yield Promise.all(promises)
return self.users
})
/*
Until async/await arrives in js, we use this function to wait for promises
by yielding them.
*/
function async (makeGenerator) {
return function (arg) {
var generator = makeGenerator.apply(this, arguments)
function handle (result) {
if (result.done) return Promise.resolve(result.value)
return Promise.resolve(result.value).then(function (res) {
return handle(generator.next(res))
}, function (err) {
return handle(generator.throw(err))
})
}
try {
return handle(generator.next())
} catch (ex) {
generator.throw(ex) // TODO: check this out
// return Promise.reject(ex)
}
}
}
g.async = async
function logUsers (self) {
if (self.constructor === Array) {
self = {users: self}
}
console.log('User 1: ', self.users[0].connector.userId, "=============================================") // eslint-disable-line
self.users[0].db.logTable() // eslint-disable-line
console.log('User 2: ', self.users[1].connector.userId, "=============================================") // eslint-disable-line
self.users[1].db.logTable() // eslint-disable-line
console.log('User 3: ', self.users[2].connector.userId, "=============================================") // eslint-disable-line
self.users[2].db.logTable() // eslint-disable-line
}
g.logUsers = logUsers