Skip to content
Snippets Groups Projects
Commit e47dee53 authored by Kevin Jahns's avatar Kevin Jahns
Browse files

random tests succeed on Map :)

parent 9b6183ea
No related branches found
No related tags found
No related merge requests found
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
"Y": true, "Y": true,
"setTimeout": true, "setTimeout": true,
"setInterval": true, "setInterval": true,
"Operation": true "Operation": true,
"getRandom": true
} }
} }
// 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 keys = [];
for (var key in o) {
keys.push(key);
}
return o[getRandom(keys)];
}
}
var globalRoom = { var globalRoom = {
users: {}, users: {},
buffers: {}, buffers: {},
...@@ -52,7 +38,7 @@ function flushOne(){ ...@@ -52,7 +38,7 @@ function flushOne(){
return false; return false;
} }
} }
setInterval(flushOne, 10); // setInterval(flushOne, 10);
var userIdCounter = 0; var userIdCounter = 0;
...@@ -85,6 +71,9 @@ class Test extends AbstractConnector { ...@@ -85,6 +71,9 @@ class Test extends AbstractConnector {
c = flushOne(); c = flushOne();
} }
} }
flushOne() {
flushOne();
}
} }
Y.Test = Test; Y.Test = Test;
...@@ -29,11 +29,19 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars ...@@ -29,11 +29,19 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars
this.os = store.os; this.os = store.os;
} }
*setOperation (op) { *setOperation (op) {
if (op.struct === "Insert" && op.right === undefined) {
throw new Error("here!");
}
this.os[JSON.stringify(op.id)] = op; this.os[JSON.stringify(op.id)] = op;
return op; return op;
} }
*getOperation (id) { *getOperation (id) {
return this.os[JSON.stringify(id)]; var op = this.os[JSON.stringify(id)];
if (op == null) {
throw new Error("Op does not exist..");
} else {
return op;
}
} }
*removeOperation (id) { *removeOperation (id) {
delete this.os[JSON.stringify(id)]; delete this.os[JSON.stringify(id)];
...@@ -82,7 +90,7 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars ...@@ -82,7 +90,7 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars
var endPos = endState.clock; var endPos = endState.clock;
for (var clock = startPos; clock <= endPos; clock++) { for (var clock = startPos; clock <= endPos; clock++) {
var op = yield* this.getOperation([user, clock]); var op = this.os[JSON.stringify([user, clock])];
if (op != null) { if (op != null) {
op = Struct[op.struct].encode(op); op = Struct[op.struct].encode(op);
ops.push(yield* this.makeOperationReady.call(this, startSS, op)); ops.push(yield* this.makeOperationReady.call(this, startSS, op));
...@@ -95,14 +103,11 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars ...@@ -95,14 +103,11 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars
// instead of ss, you could use currSS (a ss that increments when you add an operation) // instead of ss, you could use currSS (a ss that increments when you add an operation)
var clock; var clock;
var o = op; var o = op;
while (true){ while (o.right != null){
// while unknown, go to the right // while unknown, go to the right
o = yield* this.getOperation(o.right); o = yield* this.getOperation(o.right);
if (o == null) {
break;
}
clock = ss[o.id[0]]; clock = ss[o.id[0]];
if (clock != null && o.id[1] < clock ) { if (clock != null && o.id[1] < clock) {
break; break;
} }
} }
......
...@@ -20,10 +20,11 @@ type Insert = { ...@@ -20,10 +20,11 @@ type Insert = {
}; };
function compareIds(id1, id2) { function compareIds(id1, id2) {
if (id1 == null && id2 == null) {
return true;
}
if (id1 == null || id2 == null) { if (id1 == null || id2 == null) {
if (id1 == null && id2 == null) {
return true;
}
return false; return false;
} }
if (id1[0] === id2[0] && id1[1] === id2[1]) { if (id1[0] === id2[0] && id1[1] === id2[1]) {
...@@ -132,13 +133,21 @@ var Struct = { ...@@ -132,13 +133,21 @@ var Struct = {
return ids; return ids;
}, },
getDistanceToOrigin: function *(op){ getDistanceToOrigin: function *(op){
var d = 0; if (op.left == null) {
var o = yield* this.getOperation(op.left); return 0;
while (!compareIds(op.origin, (o ? o.id : null))) { } else {
d++; var d = 0;
o = yield* this.getOperation(o.left); var o = yield* this.getOperation(op.left);
while (!compareIds(op.origin, (o ? o.id : null))) {
d++;
if (o.left == null) {
break;
} else {
o = yield* this.getOperation(o.left);
}
}
return d;
} }
return d;
}, },
/* /*
# $this has to find a unique position between origin and the next known character # $this has to find a unique position between origin and the next known character
...@@ -159,28 +168,23 @@ var Struct = { ...@@ -159,28 +168,23 @@ var Struct = {
var i; // loop counter var i; // loop counter
var distanceToOrigin = i = yield* Struct.Insert.getDistanceToOrigin.call(this, op); // most cases: 0 (starts from 0) var distanceToOrigin = i = yield* Struct.Insert.getDistanceToOrigin.call(this, op); // most cases: 0 (starts from 0)
var o; var o;
var parent;
var start;
// find o. o is the first conflicting operation // find o. o is the first conflicting operation
if (op.left != null) { if (op.left != null) {
o = yield* this.getOperation(op.left); o = yield* this.getOperation(op.left);
o = yield* this.getOperation(o.right); o = (o.right == null) ? null : yield* this.getOperation(o.right);
} else if (op.right != null) { } else { // left == null
o = yield* this.getOperation(op.right); parent = yield* this.getOperation(op.parent);
while (o.left != null){ let startId = op.parentSub ? parent.map[op.parentSub] : parent.start;
o = yield* this.getOperation(o.left); start = startId == null ? null : yield* this.getOperation(startId);
} o = start;
} else { // left & right are null
var p = yield* this.getOperation(op.parent);
if (op.parentSub != null) {
o = yield* this.getOperation(p.map[op.parentSub]);
} else {
o = yield* this.getOperation(p.start);
}
} }
// handle conflicts // handle conflicts
while (true) { while (true) {
if (o != null && o.id !== op.right){ if (o != null && !compareIds(o.id, op.right)){
var oOriginDistance = yield* Struct.Insert.getDistanceToOrigin.call(this, o); var oOriginDistance = yield* Struct.Insert.getDistanceToOrigin.call(this, o);
if (oOriginDistance === i) { if (oOriginDistance === i) {
// case 1 // case 1
...@@ -198,7 +202,7 @@ var Struct = { ...@@ -198,7 +202,7 @@ var Struct = {
break; break;
} }
i++; i++;
o = yield* this.getOperation(o.next_cl); o = o.right ? yield* this.getOperation(o.right) : null;
} else { } else {
break; break;
} }
...@@ -207,7 +211,7 @@ var Struct = { ...@@ -207,7 +211,7 @@ var Struct = {
// reconnect.. // reconnect..
var left = null; var left = null;
var right = null; var right = null;
var parent = yield* this.getOperation(op.parent); parent = parent || (yield* this.getOperation(op.parent));
// NOTE: You you have to call addOperation before you set any other operation! // NOTE: You you have to call addOperation before you set any other operation!
...@@ -215,22 +219,16 @@ var Struct = { ...@@ -215,22 +219,16 @@ var Struct = {
if (op.left != null) { if (op.left != null) {
left = yield* this.getOperation(op.left); left = yield* this.getOperation(op.left);
op.right = left.right; op.right = left.right;
left.right = op.id;
if ((yield* this.addOperation(op)) === false) { // add here if ((yield* this.addOperation(op)) === false) { // add here
return; return;
} }
left.right = op.id;
yield* this.setOperation(left); yield* this.setOperation(left);
} else { } else {
op.right = op.parentSub ? (parent.map[op.parentSub] || null) : parent.start;
if ((yield* this.addOperation(op)) === false) { // or here if ((yield* this.addOperation(op)) === false) { // or here
return; return;
} }
// only set right, if possible
if (op.parentSub != null) {
var sub = parent[op.parentSub];
op.right = sub != null ? sub : null;
} else {
op.right = parent.start;
}
} }
// reconnect right // reconnect right
if (op.right != null) { if (op.right != null) {
...@@ -286,6 +284,8 @@ var Struct = { ...@@ -286,6 +284,8 @@ var Struct = {
return []; return [];
}, },
execute: function* (op) { execute: function* (op) {
op.start = null;
op.end = null;
if ((yield* this.addOperation(op)) === false) { if ((yield* this.addOperation(op)) === false) {
return; return;
} }
......
/* @flow */ /* @flow */
/*eslint-env browser,jasmine */ /*eslint-env browser,jasmine */
describe("Yjs (basic)", function(){ // 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) {
keys.push(key);
}
return o[getRandom(ks)];
}
}
function getRandomNumber(n) {
if (n == null) {
n = 9999;
}
return Math.floor(Math.random() * n);
}
var keys = ["a", "b", "c", "d", "e", "f", 1, 2, 3, 4, 5, 6];
function compareAllUsers(users){
var s1, s2;
function* t1(){
s1 = yield* this.getStateSet();
}
function* t2(){
s2 = yield* this.getStateSet();
}
users[0].connector.flushAll();
for (var uid = 0; uid + 1 < users.length; uid++) {
var u1 = users[uid];
var u2 = users[uid + 1];
u1.transact(t1);
u2.transact(t2);
expect(s1).toEqual(s2);
var db1 = u1.db.os;
var db2 = u2.db.os;
for (var key in db1) {
expect(db1[key]).toEqual(db2[key]);
}
}
}
describe("Yjs", function(){
jasmine.DEFAULT_TIMEOUT_INTERVAL = 500; jasmine.DEFAULT_TIMEOUT_INTERVAL = 500;
var numberOfTests = 400;
beforeEach(function(){ beforeEach(function(){
this.users = []; this.users = [];
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
...@@ -12,7 +57,7 @@ describe("Yjs (basic)", function(){ ...@@ -12,7 +57,7 @@ describe("Yjs (basic)", function(){
}, },
connector: { connector: {
name: "Test", name: "Test",
debug: true debug: false
} }
})); }));
} }
...@@ -23,91 +68,94 @@ describe("Yjs (basic)", function(){ ...@@ -23,91 +68,94 @@ describe("Yjs (basic)", function(){
} }
this.users = []; this.users = [];
}); });
it("There is an initial Map type", function(){
var y = this.users[0]; describe("Basic tests", function(){
y.transact(function*(root){ it("There is an initial Map type", function(){
expect(root).not.toBeUndefined(); var y = this.users[0];
}); y.transact(function*(root){
}); expect(root).not.toBeUndefined();
it("Basic get&set of Map property (converge via sync)", function(){ });
var y = this.users[0];
y.transact(function*(root){
yield* root.val("stuff", "stuffy");
expect(yield* root.val("stuff")).toEqual("stuffy");
}); });
it("Basic get&set of Map property (converge via sync)", function(){
var y = this.users[0];
y.transact(function*(root){
yield* root.val("stuff", "stuffy");
expect(yield* root.val("stuff")).toEqual("stuffy");
});
y.connector.flushAll(); y.connector.flushAll();
var transaction = function*(root){ var transaction = function*(root){
expect(yield* root.val("stuff")).toEqual("stuffy"); expect(yield* root.val("stuff")).toEqual("stuffy");
}; };
for (var key in this.users) { for (var key in this.users) {
var u = this.users[key]; var u = this.users[key];
u.transact(transaction); u.transact(transaction);
} }
});
it("Basic get&set of Map property (converge via update)", function(){
var y = this.users[0];
y.connector.flushAll();
y.transact(function*(root){
yield* root.val("stuff", "stuffy");
expect(yield* root.val("stuff")).toEqual("stuffy");
}); });
it("Basic get&set of Map property (converge via update)", function(){
var y = this.users[0];
y.connector.flushAll();
y.transact(function*(root){
yield* root.val("stuff", "stuffy");
expect(yield* root.val("stuff")).toEqual("stuffy");
});
var transaction = function*(root){ var transaction = function*(root){
expect(yield* root.val("stuff")).toEqual("stuffy"); expect(yield* root.val("stuff")).toEqual("stuffy");
}; };
y.connector.flushAll(); y.connector.flushAll();
for (var key in this.users) { for (var key in this.users) {
var u = this.users[key]; var u = this.users[key];
u.transact(transaction); u.transact(transaction);
} }
});
it("Basic get&set of Map property (handle conflict)", function(){
var y = this.users[0];
y.connector.flushAll();
this.users[0].transact(function*(root){
yield* root.val("stuff", "c0");
});
this.users[1].transact(function*(root){
yield* root.val("stuff", "c1");
}); });
it("Basic get&set of Map property (handle conflict)", function(){
var y = this.users[0];
y.connector.flushAll();
this.users[0].transact(function*(root){
yield* root.val("stuff", "c0");
});
this.users[1].transact(function*(root){
yield* root.val("stuff", "c1");
});
var transaction = function*(root){ var transaction = function*(root){
expect(yield* root.val("stuff")).toEqual("c0"); expect(yield* root.val("stuff")).toEqual("c0");
}; };
y.connector.flushAll(); y.connector.flushAll();
for (var key in this.users) { for (var key in this.users) {
var u = this.users[key]; var u = this.users[key];
u.transact(transaction); u.transact(transaction);
} }
});
it("Basic get&set of Map property (handle three conflicts)", function(){
var y = this.users[0];
y.connector.flushAll();
this.users[0].transact(function*(root){
yield* root.val("stuff", "c0");
});
this.users[1].transact(function*(root){
yield* root.val("stuff", "c1");
});
this.users[2].transact(function*(root){
yield* root.val("stuff", "c2");
}); });
this.users[3].transact(function*(root){ it("Basic get&set of Map property (handle three conflicts)", function(){
yield* root.val("stuff", "c3"); var y = this.users[0];
}); y.connector.flushAll();
y.connector.flushAll(); this.users[0].transact(function*(root){
var transaction = function*(root){ yield* root.val("stuff", "c0");
expect(yield* root.val("stuff")).toEqual("c0"); });
}; this.users[1].transact(function*(root){
yield* root.val("stuff", "c1");
});
this.users[2].transact(function*(root){
yield* root.val("stuff", "c2");
});
this.users[3].transact(function*(root){
yield* root.val("stuff", "c3");
});
y.connector.flushAll();
var transaction = function*(root){
expect(yield* root.val("stuff")).toEqual("c0");
};
for (var key in this.users) { for (var key in this.users) {
var u = this.users[key]; var u = this.users[key];
u.transact(transaction); u.transact(transaction);
} }
});
}); });
it("can create a List type", function(){ it("can create a List type", function(){
var y = this.users[0]; var y = this.users[0];
...@@ -126,4 +174,47 @@ describe("Yjs (basic)", function(){ ...@@ -126,4 +174,47 @@ describe("Yjs (basic)", function(){
u.transact(transaction); u.transact(transaction);
} }
}); });
describe("Map random tests", function(){
var randomMapTransactions = [
function* set (map) {
yield* map.val("getRandom(keys)", getRandomNumber());
}
];
it(`succeed after ${numberOfTests} actions`, function(){
this.users[0].connector.flushAll(); // TODO: Remove!!
function* randomTransaction (root) {
var f = getRandom(randomMapTransactions);
yield* f(root);
}
for(var i = 0; i < numberOfTests; i++) {
var r = getRandomNumber(100);
if (r >= 50) {
this.users[0].connector.flushOne();
} else {
getRandom(this.users).transact(randomTransaction);
}
}
compareAllUsers(this.users);
});
});
describe("Map debug tests", function(){
beforeEach(function(){
this.u1 = this.users[0];
this.u2 = this.users[1];
this.u3 = this.users[2];
});
it("concurrent insertions #1", function(){
this.u1.transact(function*(root){
var op = {
content: 1,
left: null,
right: null,
parent: root._model,
parentSub: "a"
};
Struct.Insert.create.call(this, op);
});
compareAllUsers(this.users);
});
});
}); });
This diff is collapsed.
Source diff could not be displayed: it is too large. Options to address this: view the blob.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment