From 04139d3b7eb2113f5f5bb5888cf64ff88568364b Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Sat, 17 Oct 2015 23:02:51 +0200 Subject: [PATCH] implemented indexedDB database :shipit: --- src/Database.js | 10 +- src/Database.spec.js | 43 ++- src/Databases/IndexedDB.js | 183 ++++----- src/Databases/Memory.js | 2 - src/Databases/RedBlackTree.js | 20 +- src/Databases/RedBlackTree.spec.js | 2 +- src/Helper.spec.js | 15 +- src/Struct.js | 2 +- src/Transaction.js | 39 +- src/Types/Array.spec.js | 598 +++++++++++++++-------------- src/Types/Map.spec.js | 416 ++++++++++---------- 11 files changed, 656 insertions(+), 674 deletions(-) diff --git a/src/Database.js b/src/Database.js index b803a09a..cc6df6be 100644 --- a/src/Database.js +++ b/src/Database.js @@ -65,6 +65,8 @@ class AbstractDatabase { if (this.gcTimeout > 0) { garbageCollect() } + this.waitingTransactions = [] + this.transactionInProgress = false } addToDebug () { if (typeof YConcurrency_TestingMode !== 'undefined') { @@ -252,8 +254,8 @@ class AbstractDatabase { yield* Y.Struct.Delete.execute.call(this, op) } else if ((yield* this.getOperation(op.id)) == null && !(yield* this.isGarbageCollected(op.id))) { yield* Y.Struct[op.struct].execute.call(this, op) - var next = yield* this.addOperation(op) - yield* this.store.operationAdded(this, op, next) + yield* this.addOperation(op) + yield* this.store.operationAdded(this, op) // Delete if DS says this is actually deleted if (yield* this.isDeleted(op.id)) { @@ -262,7 +264,7 @@ class AbstractDatabase { } } // called by a transaction when an operation is added - * operationAdded (transaction, op, next) { + * operationAdded (transaction, op) { // increase SS var o = op var state = yield* transaction.getState(op.id[0]) @@ -270,7 +272,7 @@ class AbstractDatabase { // either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS state.clock++ yield* transaction.checkDeleteStoreForState(state) - o = next() + o = yield* transaction.os.findNext(o.id) } yield* transaction.setState(state) diff --git a/src/Database.spec.js b/src/Database.spec.js index 5abf9ede..7a64e313 100644 --- a/src/Database.spec.js +++ b/src/Database.spec.js @@ -1,6 +1,6 @@ -/* global Y, async */ +/* global Y, async, databases */ /* eslint-env browser,jasmine,console */ -var databases = ['Memory'] + for (var database of databases) { describe(`Database (${database})`, function () { var store @@ -8,7 +8,14 @@ for (var database of databases) { describe('Basic', function () { beforeEach(function () { store = new Y[database](null, { - gcTimeout: -1 + gcTimeout: -1, + namespace: 'testing' + }) + }) + afterEach(function (done) { + store.requestTransaction(function * () { + yield* this.store.destroy() + done() }) }) it('Deleted operation is deleted', async(function * (done) { @@ -151,7 +158,14 @@ for (var database of databases) { describe('Basic Tests', function () { beforeEach(function () { store = new Y[database](null, { - gcTimeout: -1 + gcTimeout: -1, + namespace: 'testing' + }) + }) + afterEach(function (done) { + store.requestTransaction(function * () { + yield* this.store.destroy() + done() }) }) it('debug #1', function (done) { @@ -160,9 +174,9 @@ for (var database of databases) { yield* this.os.put({id: [0]}) yield* this.os.delete([2]) yield* this.os.put({id: [1]}) - expect(yield* this.os.find([0])).not.toBeNull() - expect(yield* this.os.find([1])).not.toBeNull() - expect(yield* this.os.find([2])).toBeNull() + expect(yield* this.os.find([0])).toBeTruthy() + expect(yield* this.os.find([1])).toBeTruthy() + expect(yield* this.os.find([2])).toBeFalsy() done() }) }) @@ -207,14 +221,15 @@ for (var database of databases) { var elements = [] beforeAll(function (done) { store = new Y[database](null, { - gcTimeout: -1 + gcTimeout: -1, + namespace: 'testing' }) store.requestTransaction(function * () { for (var i = 0; i < numberOfOSTests; i++) { var r = Math.random() if (r < 0.8) { var obj = [Math.floor(Math.random() * numberOfOSTests * 10000)] - if (!(this.os.findNode(obj))) { + if (!(yield* this.os.find(obj))) { elements.push(obj) yield* this.os.put({id: obj}) } @@ -230,6 +245,12 @@ for (var database of databases) { done() }) }) + afterAll(function (done) { + store.requestTransaction(function * () { + yield* this.store.destroy() + done() + }) + }) it('can find every object', function (done) { store.requestTransaction(function * () { for (var id of elements) { @@ -242,8 +263,8 @@ for (var database of databases) { it('can find every object with lower bound search', function (done) { store.requestTransaction(function * () { for (var id of elements) { - var e = yield* this.os.findNodeWithLowerBound(id) - expect(e.val.id).toEqual(id) + var e = yield* this.os.findWithLowerBound(id) + expect(e.id).toEqual(id) } done() }) diff --git a/src/Databases/IndexedDB.js b/src/Databases/IndexedDB.js index c3570ac3..87331765 100644 --- a/src/Databases/IndexedDB.js +++ b/src/Databases/IndexedDB.js @@ -7,22 +7,26 @@ Y.IndexedDB = (function () { constructor (transaction, name) { this.store = transaction.objectStore(name) } - find (id) { - return this.store.get(id) + * find (id) { + return yield this.store.get(id) } - put (v) { - return this.store.put(v) + * put (v) { + yield this.store.put(v) } - delete (id) { - return this.store.delete(id) + * delete (id) { + yield this.store.delete(id) } - * findNodeWithLowerBound (start) { - var cursorResult = this.store.openCursor(window.IDBKeyRange.lowerBound(start)) - var cursor - while ((cursor = yield cursorResult) != null) { - // yield* gen.call(t, cursor.value) - cursor.continue() - } + * findWithLowerBound (start) { + return yield this.store.openCursor(window.IDBKeyRange.lowerBound(start)) + } + * findWithUpperBound (end) { + return yield this.store.openCursor(window.IDBKeyRange.upperBound(end), 'prev') + } + * findNext (id) { + return yield* this.findWithLowerBound([id[0], id[1] + 1]) + } + * findPrev (id) { + return yield* this.findWithUpperBound([id[0], id[1] - 1]) } * iterate (t, start, end, gen) { var range = null @@ -34,61 +38,22 @@ Y.IndexedDB = (function () { range = window.IDBKeyRange.upperBound(end) } var cursorResult = this.store.openCursor(range) - var cursor - while ((cursor = yield cursorResult) != null) { - yield* gen.call(t, cursor.value) - cursor.continue() + while ((yield cursorResult) != null) { + yield* gen.call(t, cursorResult.result.value) + cursorResult.result.continue() } } } - class Transaction { + class Transaction extends Y.Transaction { constructor (store) { + super(store) var transaction = store.db.transaction(['OperationStore', 'StateStore', 'DeleteStore'], 'readwrite') + this.store = store this.ss = new Store(transaction, 'StateStore') this.os = new Store(transaction, 'OperationStore') this.ds = new Store(transaction, 'DeleteStore') } - * getStateVector () { - var stateVector = [] - var cursorResult = this.sv.openCursor() - var cursor - while ((cursor = yield cursorResult) != null) { - stateVector.push(cursor.value) - cursor.continue() - } - return stateVector - } - * getStateSet () { - var sv = yield* this.getStateVector() - var ss = {} - for (var state of sv) { - ss[state.user] = state.clock - } - return ss - } - - * getOperations (startSS) { - if (startSS == null) { - startSS = {} - } - var ops = [] - - var endSV = yield* this.getStateVector() - for (var endState of endSV) { - var user = endState.user - var startPos = startSS[user] || 0 - var endPos = endState.clock - var range = window.IDBKeyRange.bound([user, startPos], [user, endPos]) - var cursorResult = this.os.openCursor(range) - var cursor - while ((cursor = yield cursorResult) != null) { - ops.push(cursor.value) - cursor.continue() - } - } - return ops - } } class OperationStore extends Y.AbstractDatabase { constructor (y, opts) { @@ -106,58 +71,64 @@ Y.IndexedDB = (function () { } else { this.idbVersion = 5 } - - this.transactionQueue = { - queue: [], - onRequest: null + var store = this + // initialize database! + this.requestTransaction(function * () { + store.db = yield window.indexedDB.open(opts.namespace, store.idbVersion) + }) + if (opts.cleanStart) { + this.requestTransaction(function * () { + yield this.os.store.clear() + yield this.ds.store.clear() + yield this.ss.store.clear() + }) } - + } + transact (makeGen) { + var transaction = this.db != null ? new Transaction(this) : null var store = this - var tGen = (function * transactionGen () { - store.db = yield window.indexedDB.open(opts.namespace, store.idbVersion) - var transactionQueue = store.transactionQueue + var gen = makeGen.call(transaction) + handleTransactions(gen.next()) - var transaction = null - var cont = true - while (cont) { - var request = yield transactionQueue - transaction = new Transaction(store) - - yield* request.call(transaction, request) /* - while (transactionQueue.queue.length > 0) { - yield* transactionQueue.queue.shift().call(transaction) - }*/ - } - })() - - function handleTransactions (t) { - var request = t.value - if (t.done) { + function handleTransactions (result) { + var request = result.value + if (result.done) { + makeGen = store.getNextRequest() + if (makeGen != null) { + if (transaction == null && store.db != null) { + transaction = new Transaction(store) + } + gen = makeGen.call(transaction) + handleTransactions(gen.next()) + } // else no transaction in progress! return - } else if (request.constructor === window.IDBRequest || request.constructor === window.IDBCursor) { + } + if (request.constructor === window.IDBRequest) { request.onsuccess = function () { - handleTransactions(tGen.next(request.result)) + var res = request.result + if (res != null && res.constructor === window.IDBCursorWithValue) { + res = res.value + } + handleTransactions(gen.next(res)) } request.onerror = function (err) { - tGen.throw(err) + gen.throw(err) } - } else if (request === store.transactionQueue) { - if (request.queue.length > 0) { - handleTransactions(tGen.next(request.queue.shift())) - } else { - request.onRequest = function () { - request.onRequest = null - handleTransactions(tGen.next(request.queue.shift())) - } + } else if (request.constructor === window.IDBCursor) { + request.onsuccess = function () { + handleTransactions(gen.next(request.result != null ? request.result.value : null)) + } + request.onerror = function (err) { + gen.throw(err) } } else if (request.constructor === window.IDBOpenDBRequest) { request.onsuccess = function (event) { var db = event.target.result - handleTransactions(tGen.next(db)) + handleTransactions(gen.next(db)) } request.onerror = function () { - tGen.throw("Couldn't open IndexedDB database!") + gen.throw("Couldn't open IndexedDB database!") } request.onupgradeneeded = function (event) { var db = event.target.result @@ -166,31 +137,13 @@ Y.IndexedDB = (function () { db.createObjectStore('DeleteStore', {keyPath: 'id'}) db.createObjectStore('StateStore', {keyPath: 'id'}) } catch (e) { - // console.log("Store already exists!") + console.log('Store already exists!') } } } else { - tGen.throw('You can not yield this type!') + gen.throw('You must not yield this type!') } } - handleTransactions(tGen.next()) - } - requestTransaction (makeGen) { - this.transactionQueue.queue.push(makeGen) - if (this.transactionQueue.onRequest != null) { - this.transactionQueue.onRequest() - } - } - transact (makeGen) { - var t = new Y.Transaction(this) - while (makeGen !== null) { - var gen = makeGen.call(t) - var res = gen.next() - while (!res.done) { - res = gen.next(res.value) - } - makeGen = this.getNextRequest() - } } // TODO: implement "free".. * destroy () { diff --git a/src/Databases/Memory.js b/src/Databases/Memory.js index ed3c0d0a..1dcfffa8 100644 --- a/src/Databases/Memory.js +++ b/src/Databases/Memory.js @@ -17,8 +17,6 @@ Y.Memory = (function () { this.os = new Y.utils.RBTree() this.ds = new Y.utils.RBTree() this.ss = new Y.utils.RBTree() - this.waitingTransactions = [] - this.transactionInProgress = false } logTable () { var self = this diff --git a/src/Databases/RedBlackTree.js b/src/Databases/RedBlackTree.js index ee80dc7a..a595cf03 100644 --- a/src/Databases/RedBlackTree.js +++ b/src/Databases/RedBlackTree.js @@ -131,14 +131,12 @@ class RBTree { this.length = 0 } * findNext (id) { - var n = yield* this.findNodeWithLowerBound([id[0], id[1] + 1]) - return n == null ? null : n.val + return yield* this.findWithLowerBound([id[0], id[1] + 1]) } * findPrev (id) { - var n = yield* this.findNodeWithUpperBound([id[0], id[1] - 1]) - return n == null ? null : n.val + return yield* this.findWithUpperBound([id[0], id[1] - 1]) } - * findNodeWithLowerBound (from) { + findNodeWithLowerBound (from) { if (from === void 0) { throw new Error('You must define from!') } @@ -166,7 +164,7 @@ class RBTree { } } } - * findNodeWithUpperBound (to) { + findNodeWithUpperBound (to) { if (to === void 0) { throw new Error('You must define from!') } @@ -194,8 +192,16 @@ class RBTree { } } } + * findWithLowerBound (from) { + var n = this.findNodeWithLowerBound(from) + return n == null ? null : n.val + } + * findWithUpperBound (to) { + var n = this.findNodeWithUpperBound(to) + return n == null ? null : n.val + } * iterate (t, from, to, f) { - var o = yield* this.findNodeWithLowerBound(from) + var o = this.findNodeWithLowerBound(from) while (o !== null && (to === null || Y.utils.smaller(o.val.id, to) || Y.utils.compareIds(o.val.id, to))) { yield* f.call(t, o.val) o = o.next() diff --git a/src/Databases/RedBlackTree.spec.js b/src/Databases/RedBlackTree.spec.js index 9039f9cb..905f7bec 100644 --- a/src/Databases/RedBlackTree.spec.js +++ b/src/Databases/RedBlackTree.spec.js @@ -124,7 +124,7 @@ describe('RedBlack Tree', function () { it('can find every object with lower bound search', function (done) { this.memory.requestTransaction(function * () { for (var id of elements) { - expect((yield* tree.findNodeWithLowerBound(id)).val.id).toEqual(id) + expect((yield* tree.findWithLowerBound(id)).id).toEqual(id) } done() }) diff --git a/src/Helper.spec.js b/src/Helper.spec.js index d5cd7238..bcb80b41 100644 --- a/src/Helper.spec.js +++ b/src/Helper.spec.js @@ -31,7 +31,7 @@ g.describeManyTimes = function describeManyTimes (times, name, f) { */ function wait (t) { if (t == null) { - t = 10 + t = 80 } return new Promise(function (resolve) { setTimeout(function () { @@ -41,6 +41,11 @@ function wait (t) { } g.wait = wait +g.databases = ['Memory'] +if (typeof window !== 'undefined') { + g.databases.push('IndexedDB') +} + /* returns a random element of o. works on Object, and Array @@ -177,7 +182,7 @@ g.compareAllUsers = async(function * compareAllUsers (users) { var o = yield* this.getOperation([d.id[0], d.id[1] + i]) // gc'd or deleted if (d.gc) { - expect(o).toBeNull() + expect(o).toBeFalsy() } else { expect(o.deleted).toBeTruthy() } @@ -215,7 +220,7 @@ g.compareAllUsers = async(function * compareAllUsers (users) { } }) -g.createUsers = async(function * createUsers (self, numberOfUsers) { +g.createUsers = async(function * createUsers (self, numberOfUsers, database) { if (Y.utils.globalRoom.users[0] != null) { yield Y.utils.globalRoom.users[0].flushAll() } @@ -229,7 +234,9 @@ g.createUsers = async(function * createUsers (self, numberOfUsers) { for (var i = 0; i < numberOfUsers; i++) { promises.push(Y({ db: { - name: 'Memory', + name: database, + namespace: 'User ' + i, + cleanStart: true, gcTimeout: -1 }, connector: { diff --git a/src/Struct.js b/src/Struct.js index 03910719..9f625417 100644 --- a/src/Struct.js +++ b/src/Struct.js @@ -278,7 +278,7 @@ var Struct = { map: function * (o, f) { o = o.start var res = [] - while (o !== null) { // TODO: change to != (at least some convention) + while (o != null) { // TODO: change to != (at least some convention) var operation = yield* this.getOperation(o) if (!operation.deleted) { res.push(f(operation)) diff --git a/src/Transaction.js b/src/Transaction.js index 4b2a602b..def2ef66 100644 --- a/src/Transaction.js +++ b/src/Transaction.js @@ -201,8 +201,8 @@ class Transaction { var newlen = n.len - (id[1] - n.id[1]) n.len -= newlen yield* this.ds.put(n) - n = yield* this.ds.put({id: id, len: newlen, gc: false}) - n = n.val + n = {id: id, len: newlen, gc: false} + yield* this.ds.put(n) } // get prev&next before adding a new operation var prev = yield* this.ds.findPrev(id) @@ -245,8 +245,7 @@ class Transaction { */ * markDeleted (id) { // this.mem.push(["del", id]); - var n = yield* this.ds.findNodeWithUpperBound(id) - n = n == null ? n : n.val + var n = yield* this.ds.findWithUpperBound(id) if (n != null && n.id[0] === id[0]) { if (n.id[1] <= id[1] && id[1] < n.id[1] + n.len) { // already deleted @@ -256,13 +255,13 @@ class Transaction { n.len++ } else { // cannot extend left - n = yield* this.ds.put({id: id, len: 1, gc: false}) - n = n.val + n = {id: id, len: 1, gc: false} + yield* this.ds.put(n) } } else { // cannot extend left - n = yield* this.ds.put({id: id, len: 1, gc: false}) - n = n.val + n = {id: id, len: 1, gc: false} + yield* this.ds.put(n) } // can extend right? var next = yield* this.ds.findNext(n.id) @@ -398,9 +397,9 @@ class Transaction { } } * checkDeleteStoreForState (state) { - var n = yield* this.ds.findNodeWithUpperBound([state.user, state.clock]) - if (n !== null && n.val.id[0] === state.user && n.val.gc) { - state.clock = Math.max(state.clock, n.val.id[1] + n.val.len) + var n = yield* this.ds.findWithUpperBound([state.user, state.clock]) + if (n != null && n.id[0] === state.user && n.gc) { + state.clock = Math.max(state.clock, n.id[1] + n.len) } } /* @@ -477,8 +476,8 @@ class Transaction { } } * isGarbageCollected (id) { - var n = yield* this.ds.findNodeWithUpperBound(id) - return n !== null && n.val.id[0] === id[0] && id[1] < n.val.id[1] + n.val.len && n.val.gc + var n = yield* this.ds.findWithUpperBound(id) + return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len && n.gc } /* A DeleteSet (ds) describes all the deleted ops in the OS @@ -500,23 +499,15 @@ class Transaction { return ds } * isDeleted (id) { - var n = yield* this.ds.findNodeWithUpperBound(id) - return n !== null && n.val.id[0] === id[0] && id[1] < n.val.id[1] + n.val.len + var n = yield* this.ds.findWithUpperBound(id) + return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len } * setOperation (op) { yield* this.os.put(op) return op } * addOperation (op) { - var n = yield* this.os.put(op) - return function () { - if (n != null) { - n = n.next() - return n != null ? n.val : null - } else { - return null - } - } + yield* this.os.put(op) } * getOperation (id) { return yield* this.os.find(id) diff --git a/src/Types/Array.spec.js b/src/Types/Array.spec.js index b633b085..21fc75ea 100644 --- a/src/Types/Array.spec.js +++ b/src/Types/Array.spec.js @@ -1,308 +1,310 @@ -/* global createUsers, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, garbageCollectAllUsers, describeManyTimes */ +/* global createUsers, databases, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, garbageCollectAllUsers, describeManyTimes */ /* eslint-env browser,jasmine */ var numberOfYArrayTests = 50 var repeatArrayTests = 2 -describe('Array Type', function () { - var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll +for (var database of databases) { + describe(`Array Type (DB: ${database})`, function () { + var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll - beforeEach(async(function * (done) { - yield createUsers(this, 3) - y1 = (yconfig1 = this.users[0]).root - y2 = (yconfig2 = this.users[1]).root - y3 = (yconfig3 = this.users[2]).root - flushAll = this.users[0].connector.flushAll - yield wait(10) - done() - })) - afterEach(async(function * (done) { - yield compareAllUsers(this.users) - done() - })) - - describe('Basic tests', function () { - it('insert three elements, try re-get property', async(function * (done) { - var array = yield y1.set('Array', Y.Array) - array.insert(0, [1, 2, 3]) - array = yield y1.get('Array') // re-get property - expect(array.toArray()).toEqual([1, 2, 3]) - done() - })) - it('Basic insert in array (handle three conflicts)', async(function * (done) { - yield y1.set('Array', Y.Array) - yield flushAll() - var l1 = yield y1.get('Array') - l1.insert(0, [0]) - var l2 = yield y2.get('Array') - l2.insert(0, [1]) - var l3 = yield y3.get('Array') - l3.insert(0, [2]) - yield flushAll() - expect(l1.toArray()).toEqual(l2.toArray()) - expect(l2.toArray()).toEqual(l3.toArray()) - done() - })) - it('Basic insert&delete in array (handle three conflicts)', async(function * (done) { - var l1, l2, l3 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y', 'z']) - yield flushAll() - l1.insert(1, [0]) - l2 = yield y2.get('Array') - l2.delete(0) - l2.delete(1) - l3 = yield y3.get('Array') - l3.insert(1, [2]) - yield flushAll() - expect(l1.toArray()).toEqual(l2.toArray()) - expect(l2.toArray()).toEqual(l3.toArray()) - expect(l2.toArray()).toEqual([0, 2, 'y']) - done() - })) - it('Handles getOperations ascending ids bug in late sync', async(function * (done) { - var l1, l2 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y']) - yield flushAll() - yconfig3.disconnect() - yconfig2.disconnect() - yield wait() - l2 = yield y2.get('Array') - l2.insert(1, [2]) - l2.insert(1, [3]) - yield yconfig2.reconnect() - yield yconfig3.reconnect() - expect(l1.toArray()).toEqual(l2.toArray()) - done() - })) - it('Handles deletions in late sync', async(function * (done) { - var l1, l2 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y']) - yield flushAll() - yield yconfig2.disconnect() - yield wait() - l2 = yield y2.get('Array') - l2.delete(1, 1) - l1.delete(0, 2) - yield yconfig2.reconnect() - expect(l1.toArray()).toEqual(l2.toArray()) - done() - })) - it('Handles deletions in late sync (2)', async(function * (done) { - var l1, l2 - l1 = yield y1.set('Array', Y.Array) - yield flushAll() - l2 = yield y2.get('Array') - l1.insert(0, ['x', 'y']) - l1.delete(0, 2) - yield flushAll() - expect(l1.toArray()).toEqual(l2.toArray()) - done() - })) - it('Basic insert. Then delete the whole array', async(function * (done) { - var l1, l2, l3 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y', 'z']) - yield flushAll() - l1.delete(0, 3) - l2 = yield y2.get('Array') - l3 = yield y3.get('Array') - yield flushAll() - expect(l1.toArray()).toEqual(l2.toArray()) - expect(l2.toArray()).toEqual(l3.toArray()) - expect(l2.toArray()).toEqual([]) - done() - })) - it('Basic insert. Then delete the whole array (merge listeners on late sync)', async(function * (done) { - var l1, l2, l3 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y', 'z']) - yield flushAll() - yconfig2.disconnect() - l1.delete(0, 3) - l2 = yield y2.get('Array') - yield wait() - yield yconfig2.reconnect() - yield wait() - l3 = yield y3.get('Array') - yield flushAll() - expect(l1.toArray()).toEqual(l2.toArray()) - expect(l2.toArray()).toEqual(l3.toArray()) - expect(l2.toArray()).toEqual([]) - done() - })) - // TODO? - /* it('Basic insert. Then delete the whole array (merge deleter on late sync)', async(function * (done) { - var l1, l2, l3 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y', 'z']) - yield flushAll() - yconfig1.disconnect() - l1.delete(0, 3) - l2 = yield y2.get('Array') - yield yconfig1.reconnect() - l3 = yield y3.get('Array') - yield flushAll() - expect(l1.toArray()).toEqual(l2.toArray()) - expect(l2.toArray()).toEqual(l3.toArray()) - expect(l2.toArray()).toEqual([]) - done() - })) */ - it('throw insert & delete events', async(function * (done) { - var array = yield this.users[0].root.set('array', Y.Array) - var event - array.observe(function (e) { - event = e - }) - array.insert(0, [0]) - expect(event).toEqual([{ - type: 'insert', - object: array, - index: 0, - length: 1 - }]) - array.delete(0) - expect(event).toEqual([{ - type: 'delete', - object: array, - index: 0, - length: 1 - }]) - yield wait(50) - done() - })) - it('garbage collects', async(function * (done) { - var l1, l2, l3 - l1 = yield y1.set('Array', Y.Array) - l1.insert(0, ['x', 'y', 'z']) - yield flushAll() - yconfig1.disconnect() - l1.delete(0, 3) - l2 = yield y2.get('Array') - yield wait() - yield yconfig1.reconnect() - yield wait() - l3 = yield y3.get('Array') - yield flushAll() - yield garbageCollectAllUsers(this.users) - expect(l1.toArray()).toEqual(l2.toArray()) - expect(l2.toArray()).toEqual(l3.toArray()) - expect(l2.toArray()).toEqual([]) - done() - })) - it('debug right not existend in Insert.execute', async(function * (done) { - yconfig1.db.requestTransaction(function * () { - var ops = [{'struct':'Map','type':'Map','id':['130',0],'map':{}},{'id':['130',1],'left':null,'right':null,'origin':null,'parent':['_',0],'struct':'Insert','parentSub':'Map','opContent':['130',0]},{'struct':'Map','type':'Map','id':['130',0],'map':{}},{'id':['130',1],'left':null,'right':null,'origin':null,'parent':['_',0],'struct':'Insert','parentSub':'Map','opContent':['130',0]},{'struct':'Map','type':'Map','id':['130',0],'map':{}},{'id':['130',1],'left':null,'right':null,'origin':null,'parent':['_',0],'struct':'Insert','parentSub':'Map','opContent':['130',0]},{'left':null,'right':null,'origin':null,'parent':['130',0],'parentSub':'somekey','struct':'Insert','content':512,'id':['133',0]},{'id':['130',2],'left':null,'right':null,'origin':null,'parent':['130',0],'struct':'Insert','parentSub':'somekey','content':1131},{'id':['130',3],'left':null,'right':['130',2],'origin':null,'parent':['130',0],'struct':'Insert','parentSub':'somekey','content':4196},{'id':['131',3],'left':null,'right':null,'origin':null,'parent':['130',0],'struct':'Insert','parentSub':'somekey','content':5022}]//eslint-disable-line - - for (var o of ops) { - yield* this.store.tryExecute.call(this, o) - } - }) - yield wait() - yield yconfig3.disconnect() - yield yconfig2.disconnect() - yield flushAll() - wait() - yield yconfig3.reconnect() - yield yconfig2.reconnect() - yield wait() - yield flushAll() - done() - })) - it('debug right not existend in Insert.execute (2)', async(function * (done) { - yconfig1.db.requestTransaction(function * () { - yield* this.store.tryExecute.call(this, {'struct': 'Map', 'type': 'Map', 'id': ['153', 0], 'map': {}}) - yield* this.store.tryExecute.call(this, {'id': ['153', 1], 'left': null, 'right': null, 'origin': null, 'parent': ['_', 0], 'struct': 'Insert', 'parentSub': 'Map', 'opContent': ['153', 0]}) - yield* this.store.tryExecute.call(this, {'struct': 'Map', 'type': 'Map', 'id': ['153', 0], 'map': {}}) - yield* this.store.tryExecute.call(this, {'id': ['153', 1], 'left': null, 'right': null, 'origin': null, 'parent': ['_', 0], 'struct': 'Insert', 'parentSub': 'Map', 'opContent': ['153', 0]}) - yield* this.store.tryExecute.call(this, {'struct': 'Map', 'type': 'Map', 'id': ['153', 0], 'map': {}}) - yield* this.store.tryExecute.call(this, {'id': ['153', 1], 'left': null, 'right': null, 'origin': null, 'parent': ['_', 0], 'struct': 'Insert', 'parentSub': 'Map', 'opContent': ['153', 0]}) - yield* this.store.tryExecute.call(this, {'left': null, 'right': null, 'origin': null, 'parent': ['153', 0], 'parentSub': 'somekey', 'struct': 'Insert', 'content': 3784, 'id': ['154', 0]}) - yield* this.store.tryExecute.call(this, {'left': null, 'right': ['154', 0], 'origin': null, 'parent': ['153', 0], 'parentSub': 'somekey', 'struct': 'Insert', 'content': 8217, 'id': ['154', 1]}) - yield* this.store.tryExecute.call(this, {'left': null, 'right': ['154', 1], 'origin': null, 'parent': ['153', 0], 'parentSub': 'somekey', 'struct': 'Insert', 'content': 5036, 'id': ['154', 2]}) - yield* this.store.tryExecute.call(this, {'id': ['153', 2], 'left': null, 'right': null, 'origin': null, 'parent': ['153', 0], 'struct': 'Insert', 'parentSub': 'somekey', 'content': 417}) - yield* this.store.tryExecute.call(this, {'id': ['155', 0], 'left': null, 'right': null, 'origin': null, 'parent': ['153', 0], 'struct': 'Insert', 'parentSub': 'somekey', 'content': 2202}) - yield* this.garbageCollectOperation(['153', 2]) - yield* this.garbageCollectOperation(['154', 0]) - yield* this.garbageCollectOperation(['154', 1]) - yield* this.garbageCollectOperation(['154', 2]) - yield* this.garbageCollectOperation(['155', 0]) - yield* this.garbageCollectOperation(['156', 0]) - yield* this.garbageCollectOperation(['157', 0]) - yield* this.garbageCollectOperation(['157', 1]) - yield* this.store.tryExecute.call(this, {'id': ['153', 3], 'left': null, 'right': null, 'origin': null, 'parent': ['153', 0], 'struct': 'Insert', 'parentSub': 'somekey', 'content': 4372}) - }) - yield wait() - yield yconfig3.disconnect() - yield yconfig2.disconnect() - yield flushAll() - wait() - yield yconfig3.reconnect() - yield yconfig2.reconnect() - yield wait() - yield flushAll() - done() - })) - }) - describeManyTimes(repeatArrayTests, `Random tests`, function () { - var randomArrayTransactions = [ - function insert (array) { - array.insert(getRandomNumber(array.toArray().length), [getRandomNumber()]) - }, - function _delete (array) { - var length = array.toArray().length - if (length > 0) { - array.delete(getRandomNumber(length - 1)) - } - } - ] - function compareArrayValues (arrays) { - var firstArray - for (var l of arrays) { - var val = l.toArray() - if (firstArray == null) { - firstArray = val - } else { - expect(val).toEqual(firstArray) - } - } - } beforeEach(async(function * (done) { - yield this.users[0].root.set('Array', Y.Array) - yield flushAll() + yield createUsers(this, 3, database) + y1 = (yconfig1 = this.users[0]).root + y2 = (yconfig2 = this.users[1]).root + y3 = (yconfig3 = this.users[2]).root + flushAll = this.users[0].connector.flushAll + yield wait(10) + done() + })) + afterEach(async(function * (done) { + yield compareAllUsers(this.users) + done() + })) - var promises = [] - for (var u = 0; u < this.users.length; u++) { - promises.push(this.users[u].root.get('Array')) + describe('Basic tests', function () { + it('insert three elements, try re-get property', async(function * (done) { + var array = yield y1.set('Array', Y.Array) + array.insert(0, [1, 2, 3]) + array = yield y1.get('Array') // re-get property + expect(array.toArray()).toEqual([1, 2, 3]) + done() + })) + it('Basic insert in array (handle three conflicts)', async(function * (done) { + yield y1.set('Array', Y.Array) + yield flushAll() + var l1 = yield y1.get('Array') + l1.insert(0, [0]) + var l2 = yield y2.get('Array') + l2.insert(0, [1]) + var l3 = yield y3.get('Array') + l3.insert(0, [2]) + yield flushAll() + expect(l1.toArray()).toEqual(l2.toArray()) + expect(l2.toArray()).toEqual(l3.toArray()) + done() + })) + it('Basic insert&delete in array (handle three conflicts)', async(function * (done) { + var l1, l2, l3 + l1 = yield y1.set('Array', Y.Array) + l1.insert(0, ['x', 'y', 'z']) + yield flushAll() + l1.insert(1, [0]) + l2 = yield y2.get('Array') + l2.delete(0) + l2.delete(1) + l3 = yield y3.get('Array') + l3.insert(1, [2]) + yield flushAll() + expect(l1.toArray()).toEqual(l2.toArray()) + expect(l2.toArray()).toEqual(l3.toArray()) + expect(l2.toArray()).toEqual([0, 2, 'y']) + done() + })) + it('Handles getOperations ascending ids bug in late sync', async(function * (done) { + var l1, l2 + l1 = yield y1.set('Array', Y.Array) + l1.insert(0, ['x', 'y']) + yield flushAll() + yconfig3.disconnect() + yconfig2.disconnect() + yield wait() + l2 = yield y2.get('Array') + l2.insert(1, [2]) + l2.insert(1, [3]) + yield yconfig2.reconnect() + yield yconfig3.reconnect() + expect(l1.toArray()).toEqual(l2.toArray()) + done() + })) + it('Handles deletions in late sync', async(function * (done) { + var l1, l2 + l1 = yield y1.set('Array', Y.Array) + l1.insert(0, ['x', 'y']) + yield flushAll() + yield yconfig2.disconnect() + yield wait() + l2 = yield y2.get('Array') + l2.delete(1, 1) + l1.delete(0, 2) + yield yconfig2.reconnect() + expect(l1.toArray()).toEqual(l2.toArray()) + done() + })) + it('Handles deletions in late sync (2)', async(function * (done) { + var l1, l2 + l1 = yield y1.set('Array', Y.Array) + yield flushAll() + l2 = yield y2.get('Array') + l1.insert(0, ['x', 'y']) + l1.delete(0, 2) + yield flushAll() + expect(l1.toArray()).toEqual(l2.toArray()) + done() + })) + it('Basic insert. Then delete the whole array', async(function * (done) { + var l1, l2, l3 + l1 = yield y1.set('Array', Y.Array) + l1.insert(0, ['x', 'y', 'z']) + yield flushAll() + l1.delete(0, 3) + l2 = yield y2.get('Array') + l3 = yield y3.get('Array') + yield flushAll() + expect(l1.toArray()).toEqual(l2.toArray()) + expect(l2.toArray()).toEqual(l3.toArray()) + expect(l2.toArray()).toEqual([]) + done() + })) + it('Basic insert. Then delete the whole array (merge listeners on late sync)', async(function * (done) { + var l1, l2, l3 + l1 = yield y1.set('Array', Y.Array) + l1.insert(0, ['x', 'y', 'z']) + yield flushAll() + yconfig2.disconnect() + l1.delete(0, 3) + l2 = yield y2.get('Array') + yield wait() + yield yconfig2.reconnect() + yield wait() + l3 = yield y3.get('Array') + yield flushAll() + expect(l1.toArray()).toEqual(l2.toArray()) + expect(l2.toArray()).toEqual(l3.toArray()) + expect(l2.toArray()).toEqual([]) + done() + })) + // TODO? + /* it('Basic insert. Then delete the whole array (merge deleter on late sync)', async(function * (done) { + var l1, l2, l3 + l1 = yield y1.set('Array', Y.Array) + l1.insert(0, ['x', 'y', 'z']) + yield flushAll() + yconfig1.disconnect() + l1.delete(0, 3) + l2 = yield y2.get('Array') + yield yconfig1.reconnect() + l3 = yield y3.get('Array') + yield flushAll() + expect(l1.toArray()).toEqual(l2.toArray()) + expect(l2.toArray()).toEqual(l3.toArray()) + expect(l2.toArray()).toEqual([]) + done() + })) */ + it('throw insert & delete events', async(function * (done) { + var array = yield this.users[0].root.set('array', Y.Array) + var event + array.observe(function (e) { + event = e + }) + array.insert(0, [0]) + expect(event).toEqual([{ + type: 'insert', + object: array, + index: 0, + length: 1 + }]) + array.delete(0) + expect(event).toEqual([{ + type: 'delete', + object: array, + index: 0, + length: 1 + }]) + yield wait(50) + done() + })) + it('garbage collects', async(function * (done) { + var l1, l2, l3 + l1 = yield y1.set('Array', Y.Array) + l1.insert(0, ['x', 'y', 'z']) + yield flushAll() + yconfig1.disconnect() + l1.delete(0, 3) + l2 = yield y2.get('Array') + yield wait() + yield yconfig1.reconnect() + yield wait() + l3 = yield y3.get('Array') + yield flushAll() + yield garbageCollectAllUsers(this.users) + expect(l1.toArray()).toEqual(l2.toArray()) + expect(l2.toArray()).toEqual(l3.toArray()) + expect(l2.toArray()).toEqual([]) + done() + })) + it('debug right not existend in Insert.execute', async(function * (done) { + yconfig1.db.requestTransaction(function * () { + var ops = [{'struct':'Map','type':'Map','id':['130',0],'map':{}},{'id':['130',1],'left':null,'right':null,'origin':null,'parent':['_',0],'struct':'Insert','parentSub':'Map','opContent':['130',0]},{'struct':'Map','type':'Map','id':['130',0],'map':{}},{'id':['130',1],'left':null,'right':null,'origin':null,'parent':['_',0],'struct':'Insert','parentSub':'Map','opContent':['130',0]},{'struct':'Map','type':'Map','id':['130',0],'map':{}},{'id':['130',1],'left':null,'right':null,'origin':null,'parent':['_',0],'struct':'Insert','parentSub':'Map','opContent':['130',0]},{'left':null,'right':null,'origin':null,'parent':['130',0],'parentSub':'somekey','struct':'Insert','content':512,'id':['133',0]},{'id':['130',2],'left':null,'right':null,'origin':null,'parent':['130',0],'struct':'Insert','parentSub':'somekey','content':1131},{'id':['130',3],'left':null,'right':['130',2],'origin':null,'parent':['130',0],'struct':'Insert','parentSub':'somekey','content':4196},{'id':['131',3],'left':null,'right':null,'origin':null,'parent':['130',0],'struct':'Insert','parentSub':'somekey','content':5022}]//eslint-disable-line + + for (var o of ops) { + yield* this.store.tryExecute.call(this, o) + } + }) + yield wait() + yield yconfig3.disconnect() + yield yconfig2.disconnect() + yield flushAll() + wait() + yield yconfig3.reconnect() + yield yconfig2.reconnect() + yield wait() + yield flushAll() + done() + })) + it('debug right not existend in Insert.execute (2)', async(function * (done) { + yconfig1.db.requestTransaction(function * () { + yield* this.store.tryExecute.call(this, {'struct': 'Map', 'type': 'Map', 'id': ['153', 0], 'map': {}}) + yield* this.store.tryExecute.call(this, {'id': ['153', 1], 'left': null, 'right': null, 'origin': null, 'parent': ['_', 0], 'struct': 'Insert', 'parentSub': 'Map', 'opContent': ['153', 0]}) + yield* this.store.tryExecute.call(this, {'struct': 'Map', 'type': 'Map', 'id': ['153', 0], 'map': {}}) + yield* this.store.tryExecute.call(this, {'id': ['153', 1], 'left': null, 'right': null, 'origin': null, 'parent': ['_', 0], 'struct': 'Insert', 'parentSub': 'Map', 'opContent': ['153', 0]}) + yield* this.store.tryExecute.call(this, {'struct': 'Map', 'type': 'Map', 'id': ['153', 0], 'map': {}}) + yield* this.store.tryExecute.call(this, {'id': ['153', 1], 'left': null, 'right': null, 'origin': null, 'parent': ['_', 0], 'struct': 'Insert', 'parentSub': 'Map', 'opContent': ['153', 0]}) + yield* this.store.tryExecute.call(this, {'left': null, 'right': null, 'origin': null, 'parent': ['153', 0], 'parentSub': 'somekey', 'struct': 'Insert', 'content': 3784, 'id': ['154', 0]}) + yield* this.store.tryExecute.call(this, {'left': null, 'right': ['154', 0], 'origin': null, 'parent': ['153', 0], 'parentSub': 'somekey', 'struct': 'Insert', 'content': 8217, 'id': ['154', 1]}) + yield* this.store.tryExecute.call(this, {'left': null, 'right': ['154', 1], 'origin': null, 'parent': ['153', 0], 'parentSub': 'somekey', 'struct': 'Insert', 'content': 5036, 'id': ['154', 2]}) + yield* this.store.tryExecute.call(this, {'id': ['153', 2], 'left': null, 'right': null, 'origin': null, 'parent': ['153', 0], 'struct': 'Insert', 'parentSub': 'somekey', 'content': 417}) + yield* this.store.tryExecute.call(this, {'id': ['155', 0], 'left': null, 'right': null, 'origin': null, 'parent': ['153', 0], 'struct': 'Insert', 'parentSub': 'somekey', 'content': 2202}) + yield* this.garbageCollectOperation(['153', 2]) + yield* this.garbageCollectOperation(['154', 0]) + yield* this.garbageCollectOperation(['154', 1]) + yield* this.garbageCollectOperation(['154', 2]) + yield* this.garbageCollectOperation(['155', 0]) + yield* this.garbageCollectOperation(['156', 0]) + yield* this.garbageCollectOperation(['157', 0]) + yield* this.garbageCollectOperation(['157', 1]) + yield* this.store.tryExecute.call(this, {'id': ['153', 3], 'left': null, 'right': null, 'origin': null, 'parent': ['153', 0], 'struct': 'Insert', 'parentSub': 'somekey', 'content': 4372}) + }) + yield wait() + yield yconfig3.disconnect() + yield yconfig2.disconnect() + yield flushAll() + wait() + yield yconfig3.reconnect() + yield yconfig2.reconnect() + yield wait() + yield flushAll() + done() + })) + }) + describeManyTimes(repeatArrayTests, `Random tests`, function () { + var randomArrayTransactions = [ + function insert (array) { + array.insert(getRandomNumber(array.toArray().length), [getRandomNumber()]) + }, + function _delete (array) { + var length = array.toArray().length + if (length > 0) { + array.delete(getRandomNumber(length - 1)) + } + } + ] + function compareArrayValues (arrays) { + var firstArray + for (var l of arrays) { + var val = l.toArray() + if (firstArray == null) { + firstArray = val + } else { + expect(val).toEqual(firstArray) + } + } } - this.arrays = yield Promise.all(promises) - done() - })) - it('arrays.length equals users.length', async(function * (done) { - expect(this.arrays.length).toEqual(this.users.length) - done() - })) - it(`succeed after ${numberOfYArrayTests} actions, no GC, all users disconnecting/reconnecting`, async(function * (done) { - for (var u of this.users) { - u.connector.debug = true - } - yield applyRandomTransactionsAllRejoinNoGC(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests) - yield flushAll() - yield compareArrayValues(this.arrays) - yield compareAllUsers(this.users) - done() - })) - it(`succeed after ${numberOfYArrayTests} actions, GC, user[0] is not disconnecting`, async(function * (done) { - for (var u of this.users) { - u.connector.debug = true - } - yield applyRandomTransactionsWithGC(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests) - yield flushAll() - yield compareArrayValues(this.arrays) - yield compareAllUsers(this.users) - done() - })) + beforeEach(async(function * (done) { + yield this.users[0].root.set('Array', Y.Array) + yield flushAll() + + var promises = [] + for (var u = 0; u < this.users.length; u++) { + promises.push(this.users[u].root.get('Array')) + } + this.arrays = yield Promise.all(promises) + done() + })) + it('arrays.length equals users.length', async(function * (done) { + expect(this.arrays.length).toEqual(this.users.length) + done() + })) + it(`succeed after ${numberOfYArrayTests} actions, no GC, all users disconnecting/reconnecting`, async(function * (done) { + for (var u of this.users) { + u.connector.debug = true + } + yield applyRandomTransactionsAllRejoinNoGC(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests) + yield flushAll() + yield compareArrayValues(this.arrays) + yield compareAllUsers(this.users) + done() + })) + it(`succeed after ${numberOfYArrayTests} actions, GC, user[0] is not disconnecting`, async(function * (done) { + for (var u of this.users) { + u.connector.debug = true + } + yield applyRandomTransactionsWithGC(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests) + yield flushAll() + yield compareArrayValues(this.arrays) + yield compareAllUsers(this.users) + done() + })) + }) }) -}) +} diff --git a/src/Types/Map.spec.js b/src/Types/Map.spec.js index 93abf875..4fd8e59a 100644 --- a/src/Types/Map.spec.js +++ b/src/Types/Map.spec.js @@ -1,217 +1,219 @@ -/* global createUsers, Y, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, describeManyTimes */ +/* global createUsers, Y, databases, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, describeManyTimes */ /* eslint-env browser,jasmine */ var numberOfYMapTests = 40 var repeatMapTeasts = 2 -describe('Map Type', function () { - var y1, y2, y3, y4, flushAll +for (var database of databases) { + describe(`Map Type (DB: ${database})`, function () { + var y1, y2, y3, y4, flushAll - beforeEach(async(function * (done) { - yield createUsers(this, 5) - y1 = this.users[0].root - y2 = this.users[1].root - y3 = this.users[2].root - y4 = this.users[3].root - flushAll = this.users[0].connector.flushAll - done() - })) - afterEach(async(function * (done) { - yield compareAllUsers(this.users) - done() - }), 5000) - - describe('Basic tests', function () { - it('Basic get&set of Map property (converge via sync)', async(function * (done) { - y1.set('stuff', 'stuffy') - expect(y1.get('stuff')).toEqual('stuffy') - yield flushAll() - for (var key in this.users) { - var u = this.users[key].root - expect(u.get('stuff')).toEqual('stuffy') - } - done() - })) - it('Map can set custom types (Map)', async(function * (done) { - var map = yield y1.set('Map', Y.Map) - map.set('one', 1) - map = yield y1.get('Map') - expect(map.get('one')).toEqual(1) - done() - })) - it('Map can set custom types (Array)', async(function * (done) { - var array = yield y1.set('Array', Y.Array) - array.insert(0, [1, 2, 3]) - array = yield y1.get('Array') - expect(array.toArray()).toEqual([1, 2, 3]) - done() - })) - it('Basic get&set of Map property (converge via update)', async(function * (done) { - yield flushAll() - y1.set('stuff', 'stuffy') - expect(y1.get('stuff')).toEqual('stuffy') - - yield flushAll() - for (var key in this.users) { - var r = this.users[key].root - expect(r.get('stuff')).toEqual('stuffy') - } - done() - })) - it('Basic get&set of Map property (handle conflict)', async(function * (done) { - yield flushAll() - y1.set('stuff', 'c0') - y2.set('stuff', 'c1') - - yield flushAll() - for (var key in this.users) { - var u = this.users[key] - expect(u.root.get('stuff')).toEqual('c0') - } - done() - })) - it('Basic get&set&delete of Map property (handle conflict)', async(function * (done) { - yield flushAll() - y1.set('stuff', 'c0') - y1.delete('stuff') - y2.set('stuff', 'c1') - yield flushAll() - - for (var key in this.users) { - var u = this.users[key] - expect(u.root.get('stuff')).toBeUndefined() - } - done() - })) - it('Basic get&set of Map property (handle three conflicts)', async(function * (done) { - yield flushAll() - y1.set('stuff', 'c0') - y2.set('stuff', 'c1') - y2.set('stuff', 'c2') - y3.set('stuff', 'c3') - yield flushAll() - - for (var key in this.users) { - var u = this.users[key] - expect(u.root.get('stuff')).toEqual('c0') - } - done() - })) - it('Basic get&set&delete of Map property (handle three conflicts)', async(function * (done) { - yield flushAll() - y1.set('stuff', 'c0') - y2.set('stuff', 'c1') - y2.set('stuff', 'c2') - y3.set('stuff', 'c3') - yield flushAll() - y1.set('stuff', 'deleteme') - y1.delete('stuff') - y2.set('stuff', 'c1') - y3.set('stuff', 'c2') - y4.set('stuff', 'c3') - yield flushAll() - - for (var key in this.users) { - var u = this.users[key] - expect(u.root.get('stuff')).toBeUndefined() - } - done() - })) - it('observePath properties', async(function * (done) { - y1.observePath(['map'], function (map) { - if (map != null) { - map.set('yay', 4) - } - }) - yield y2.set('map', Y.Map) - yield flushAll() - var map = yield y3.get('map') - expect(map.get('yay')).toEqual(4) - done() - })) - it('throws add & update & delete events (with type and primitive content)', async(function * (done) { - var event - yield flushAll() - y1.observe(function (e) { - event = e // just put it on event, should be thrown synchronously anyway - }) - y1.set('stuff', 4) - expect(event).toEqual([{ - type: 'add', - object: y1, - name: 'stuff' - }]) - // update, oldValue is in contents - yield y1.set('stuff', Y.Array) - expect(event).toEqual([{ - type: 'update', - object: y1, - name: 'stuff', - oldValue: 4 - }]) - y1.get('stuff').then(function (replacedArray) { - // update, oldValue is in opContents - y1.set('stuff', 5) - var getYArray = event[0].oldValue - expect(typeof getYArray.constructor === 'function').toBeTruthy() - getYArray().then(function (array) { - expect(array).toEqual(replacedArray) - - // delete - y1.delete('stuff') - expect(event).toEqual([{ - type: 'delete', - name: 'stuff', - object: y1, - oldValue: 5 - }]) - done() - }) - }) - })) - }) - describeManyTimes(repeatMapTeasts, `${numberOfYMapTests} Random tests`, function () { - var randomMapTransactions = [ - function set (map) { - map.set('somekey', getRandomNumber()) - }, - function delete_ (map) { - map.delete('somekey') - } - ] - function compareMapValues (maps) { - var firstMap - for (var map of maps) { - var val = map.getPrimitive() - if (firstMap == null) { - firstMap = val - } else { - expect(val).toEqual(firstMap) - } - } - } beforeEach(async(function * (done) { - yield y1.set('Map', Y.Map) - yield flushAll() + yield createUsers(this, 5, database) + y1 = this.users[0].root + y2 = this.users[1].root + y3 = this.users[2].root + y4 = this.users[3].root + flushAll = this.users[0].connector.flushAll + done() + })) + afterEach(async(function * (done) { + yield compareAllUsers(this.users) + done() + }), 5000) - var promises = [] - for (var u = 0; u < this.users.length; u++) { - promises.push(this.users[u].root.get('Map')) + describe('Basic tests', function () { + it('Basic get&set of Map property (converge via sync)', async(function * (done) { + y1.set('stuff', 'stuffy') + expect(y1.get('stuff')).toEqual('stuffy') + yield flushAll() + for (var key in this.users) { + var u = this.users[key].root + expect(u.get('stuff')).toEqual('stuffy') + } + done() + })) + it('Map can set custom types (Map)', async(function * (done) { + var map = yield y1.set('Map', Y.Map) + map.set('one', 1) + map = yield y1.get('Map') + expect(map.get('one')).toEqual(1) + done() + })) + it('Map can set custom types (Array)', async(function * (done) { + var array = yield y1.set('Array', Y.Array) + array.insert(0, [1, 2, 3]) + array = yield y1.get('Array') + expect(array.toArray()).toEqual([1, 2, 3]) + done() + })) + it('Basic get&set of Map property (converge via update)', async(function * (done) { + yield flushAll() + y1.set('stuff', 'stuffy') + expect(y1.get('stuff')).toEqual('stuffy') + + yield flushAll() + for (var key in this.users) { + var r = this.users[key].root + expect(r.get('stuff')).toEqual('stuffy') + } + done() + })) + it('Basic get&set of Map property (handle conflict)', async(function * (done) { + yield flushAll() + y1.set('stuff', 'c0') + y2.set('stuff', 'c1') + + yield flushAll() + for (var key in this.users) { + var u = this.users[key] + expect(u.root.get('stuff')).toEqual('c0') + } + done() + })) + it('Basic get&set&delete of Map property (handle conflict)', async(function * (done) { + yield flushAll() + y1.set('stuff', 'c0') + y1.delete('stuff') + y2.set('stuff', 'c1') + yield flushAll() + + for (var key in this.users) { + var u = this.users[key] + expect(u.root.get('stuff')).toBeUndefined() + } + done() + })) + it('Basic get&set of Map property (handle three conflicts)', async(function * (done) { + yield flushAll() + y1.set('stuff', 'c0') + y2.set('stuff', 'c1') + y2.set('stuff', 'c2') + y3.set('stuff', 'c3') + yield flushAll() + + for (var key in this.users) { + var u = this.users[key] + expect(u.root.get('stuff')).toEqual('c0') + } + done() + })) + it('Basic get&set&delete of Map property (handle three conflicts)', async(function * (done) { + yield flushAll() + y1.set('stuff', 'c0') + y2.set('stuff', 'c1') + y2.set('stuff', 'c2') + y3.set('stuff', 'c3') + yield flushAll() + y1.set('stuff', 'deleteme') + y1.delete('stuff') + y2.set('stuff', 'c1') + y3.set('stuff', 'c2') + y4.set('stuff', 'c3') + yield flushAll() + + for (var key in this.users) { + var u = this.users[key] + expect(u.root.get('stuff')).toBeUndefined() + } + done() + })) + it('observePath properties', async(function * (done) { + y1.observePath(['map'], function (map) { + if (map != null) { + map.set('yay', 4) + } + }) + yield y2.set('map', Y.Map) + yield flushAll() + var map = yield y3.get('map') + expect(map.get('yay')).toEqual(4) + done() + })) + it('throws add & update & delete events (with type and primitive content)', async(function * (done) { + var event + yield flushAll() + y1.observe(function (e) { + event = e // just put it on event, should be thrown synchronously anyway + }) + y1.set('stuff', 4) + expect(event).toEqual([{ + type: 'add', + object: y1, + name: 'stuff' + }]) + // update, oldValue is in contents + yield y1.set('stuff', Y.Array) + expect(event).toEqual([{ + type: 'update', + object: y1, + name: 'stuff', + oldValue: 4 + }]) + y1.get('stuff').then(function (replacedArray) { + // update, oldValue is in opContents + y1.set('stuff', 5) + var getYArray = event[0].oldValue + expect(typeof getYArray.constructor === 'function').toBeTruthy() + getYArray().then(function (array) { + expect(array).toEqual(replacedArray) + + // delete + y1.delete('stuff') + expect(event).toEqual([{ + type: 'delete', + name: 'stuff', + object: y1, + oldValue: 5 + }]) + done() + }) + }) + })) + }) + describeManyTimes(repeatMapTeasts, `${numberOfYMapTests} Random tests`, function () { + var randomMapTransactions = [ + function set (map) { + map.set('somekey', getRandomNumber()) + }, + function delete_ (map) { + map.delete('somekey') + } + ] + function compareMapValues (maps) { + var firstMap + for (var map of maps) { + var val = map.getPrimitive() + if (firstMap == null) { + firstMap = val + } else { + expect(val).toEqual(firstMap) + } + } } - this.maps = yield Promise.all(promises) - done() - })) - it(`succeed after ${numberOfYMapTests} actions, no GC, all users disconnecting/reconnecting`, async(function * (done) { - yield applyRandomTransactionsAllRejoinNoGC(this.users, this.maps, randomMapTransactions, numberOfYMapTests) - yield flushAll() - yield compareMapValues(this.maps) - done() - })) - it(`succeed after ${numberOfYMapTests} actions, GC, user[0] is not disconnecting`, async(function * (done) { - yield applyRandomTransactionsWithGC(this.users, this.maps, randomMapTransactions, numberOfYMapTests) - yield flushAll() - yield compareMapValues(this.maps) - done() - })) + beforeEach(async(function * (done) { + yield y1.set('Map', Y.Map) + yield flushAll() + + var promises = [] + for (var u = 0; u < this.users.length; u++) { + promises.push(this.users[u].root.get('Map')) + } + this.maps = yield Promise.all(promises) + done() + })) + it(`succeed after ${numberOfYMapTests} actions, no GC, all users disconnecting/reconnecting`, async(function * (done) { + yield applyRandomTransactionsAllRejoinNoGC(this.users, this.maps, randomMapTransactions, numberOfYMapTests) + yield flushAll() + yield compareMapValues(this.maps) + done() + })) + it(`succeed after ${numberOfYMapTests} actions, GC, user[0] is not disconnecting`, async(function * (done) { + yield applyRandomTransactionsWithGC(this.users, this.maps, randomMapTransactions, numberOfYMapTests) + yield flushAll() + yield compareMapValues(this.maps) + done() + })) + }) }) -}) +}