blob: 42f4ca5044185532638394576f61efd9f6539cc0 [file] [log] [blame]
'use strict';
// This is my idea of something between a property test and chaos monkey
// We are testing the one basic property of PouchDB which is whatever we
// do to databases, locally or remotely after they have synced they should
// end up with the same data.
// To do this we define a set of 'actions' that represent what an end user
// could do to a database, create / update / delete documents, perform
// replications or queries etc. We pick a number (actionCount) and randomly
// perform those actions on a random database. Once we have run enough actions
// we sync the 2 databases together and assert that they have the same contents
// We can take an optional seed from the url (?seed=hello) otherwise we just
// generate an arbitrary one. With any given seed the 'random' generations
// are deteministic so if we find a seed that fails the test we can provide
// that seed again to rerun the same actions and generate the same failure
// This is the amount of random actions we do
var actionCount = 100;
// We pick an action from this list at random, it gets given a
// db to operate on if it only uses one database
var actions = {
// Create a random document
'create': function (a) {
return a.post({'a': 'newdoc'});
},
// Pick from an existing document and updated it
'update': function (a) {
return randomDoc(a).then(function (doc) {
if (doc) {
doc.updated = Date.now();
return a.put(doc);
}
});
},
// Remove a random document
'remove': function (a) {
return randomDoc(a).then(function (doc) {
if (doc) {
return a.remove(doc);
}
});
},
// Generate a conflict by writing a document with the same id to
// both databases
'conflict': function (a, b) {
var doc = {
_id: 'random-' + Date.now(),
foo: 'bar'
};
return a.put(doc).then(function () {
doc.baz = 'fubar';
return b.put(doc);
});
},
// Perform a one off replication
'replicate': function (a, b) {
return a.replicate.to(b);
}
};
// Utilities
function randomDoc(db) {
return db.allDocs({include_docs: true}).then(function (res) {
var row = arrayRandom(res.rows);
if (row) {
return row.doc;
}
});
}
function randomNumber(min, max) {
min = parseInt(min, 10);
max = parseInt(max, 10);
if (min !== min) {
min = 0;
}
if (max !== max || max <= min) {
max = (min || 1) << 1; //doubling
} else {
max = max + 1;
}
var ratio = Math.random();
var range = max - min;
return ~~(range * ratio + min); // ~~ coerces to an int, but fast.
}
function arrayRandom(arr) {
var keys = Object.keys(arr);
var i = randomNumber(0, keys.length - 1);
return arr[i];
}
describe('chaos-monkey', function () {
var Promise;
var a, b;
beforeEach(function (done) {
Promise = testUtils.Promise;
var aname = testUtils.adapterUrl('local', 'testdb');
var bname = testUtils.adapterUrl('http', 'test_repl_remote');
testUtils.cleanup([aname, bname], function () {
a = new PouchDB(aname);
b = new PouchDB(bname);
done();
});
});
after(function () {
return a.destroy().then(function () {
return b.destroy();
});
});
function dbActions() {
return new Promise(function doit(resolve) {
if (!actionCount) {
return resolve();
}
var action = arrayRandom(Object.keys(actions));
actionCount--;
// Give the databases in random order
var dbs = Math.round(Math.random()) ? [a, b] : [b, a];
var called = actions[action].apply(null, dbs);
// This is probably making a big stack, should fix
called.then(function () {
doit(resolve);
});
});
}
// Sync the databases up
function finish() {
return a.sync(b);
}
function compare() {
return Promise.all([
a.allDocs({include_docs: true}),
b.allDocs({include_docs: true})
]).then(function (res) {
res[0].should.deep.equal(res[1]);
});
}
it('Do a fuzzy replication run', function () {
// This gives us 100ms for each action, should hopefully be enough
this.timeout(actionCount * 1000);
return dbActions()
.then(finish)
.then(compare);
});
});