test('apply snapshot to new', function (t) { var self = this; var oldRaft = self.oldRaft; var snapshotter = oldRaft.snapshotter; var newRaft = self.newRaft; var snapshot; vasync.pipeline({ funcs: [ function (_, subcb) { vasync.forEachPipeline({ 'func': oldRaft.clientRequest.bind(oldRaft), 'inputs': [ { 'command': 'foo' }, { 'command': 'bar' }, { 'command': 'baz' }, { 'command': 'bang' } ] }, subcb); }, function (_, subcb) { snapshotter.getLatest(function (err, snap) { if (err) { return (subcb(err)); } snapshot = snap; subcb(); }); }, function (_, subcb) { newRaft.installSnapshot({ 'snapshot': snapshot }, function (err, result) { t.ok(result.success); //The configuration + the 4 client requests above. t.equal(4, result.index); subcb(err); }); }, function (_, subcb) { //Check everything we can with the newraft instance. t.equal('raft-1', newRaft.id); t.equal(undefined, newRaft.leaderId); t.equal(0, newRaft.currentTerm()); t.equal(undefined, newRaft.votedFor()); //This is a little wonky. In "real life" the entry for newRaft // would have been a part of r0's peer list. But here we're // just checking that it copies over the peer list. t.deepEqual([ 'raft-0' ], newRaft.cluster.allPeerIds); t.equal(4, newRaft.stateMachine.commitIndex); t.equal('bang', newRaft.stateMachine.data); t.ok(Object.keys(newRaft.outstandingMessages).length === 0); helper.readClog(newRaft.clog, function (err, entries) { if (err) { return (subcb(err)); } t.equals(5, entries.length); //This is a bit wonky... but it is what it should be. var firstEntryCommand = helper.e(createClusterConfig( 'raft-0'))(0, 0).command; t.deepEqual([ firstEntryCommand, 'foo', 'bar', 'baz', 'bang' ], entries.map(function (x) { return (x.command); })); subcb(); }); } ] }, function (err) { if (err) { t.fail(err); } t.done(); }); });
test('single raft, not bootstrapped on init', function (t) { vasync.pipeline({ arg: {}, funcs: [ function newRaft(_, subcb) { memraft.raft({ 'log': LOG, 'id': 'raft-0' }, function (err, r) { if (err) { return (subcb(err)); } _.raft = r; subcb(); }); }, function tryTick(_, subcb) { t.equal('follower', _.raft.state); _.raft.tick(); var newTimeout = _.raft.leaderTimeout; _.raft.tick(); //Ticks should only reset a raft instance that has no // configuration. t.equal(newTimeout, _.raft.leaderTimeout); subcb(); }, function tryAppend(_, subcb) { _.raft.appendEntries({ 'term': 0, 'commitIndex': 0, 'leaderId': 'raft-1', 'entries': entryStream([ 0, 0 ]) }, function (err) { if (!err) { t.fail(); return (subcb()); } t.equal('NotBootstrappedError', err.name); subcb(); }); }, function tryRequestVote(_, subcb) { _.raft.requestVote({ 'term': 0, 'candidateId': 'raft-1', 'lastLogIndex': 0, 'lastLogTerm': 0 }, function (err) { if (!err) { t.fail(); return (subcb()); } t.equal('InvalidPeerError', err.name); subcb(); }); } ] }, function (err) { if (err) { t.fail(err.toString()); } t.done(); }); });
test('consistency check on 0, success', function (t) { var self = this; var funcs = [ function (_, subcb) { self.clog.append({ 'commitIndex': 0, 'term': 0, 'entries': entryStream([ 0, 0 ]) }, subcb); }, function (_, subcb) { t.equal(1, self.clog.nextIndex); t.deepEqual(e(0, 0), self.clog.last()); readClog(self.clog, function (err, clog) { t.ok(clog.length === 1); t.deepEqual(e(0, 0), clog[0]); subcb(); }); } ]; vasync.pipeline({ funcs: funcs, arg: {} }, function (err) { if (err) { t.fail(err); } t.done(); }); });
test('crud', function (t) { var props; vasync.pipeline({ args: {}, funcs: [ function mkTmpDir(_, subcb) { fs.mkdir(TMP_DIR, function (err) { if (err && err.code !== 'EEXIST') { return (subcb(err)); } return (subcb()); }); }, function removeOldLevelDb(_, subcb) { helper.rmrf(DB_FILE, subcb); }, function init(_, subcb) { props = new lib.Properties({ 'log': LOG, 'location': DB_FILE }); props.on('ready', subcb); }, function write(_, subcb) { t.equal(0, props.get('currentTerm')); t.ok(props.get('foo') === undefined); t.ok(props.get('bar') === undefined); var p = { 'foo': 'fval', 'bar': 'bval'}; props.write(p, subcb); }, function read(_, subcb) { t.equal('fval', props.get('foo')); t.equal('bval', props.get('bar')); subcb(); }, function update(_, subcb) { var p = { 'bar': 'bval2'}; props.write(p, subcb); }, function checkUpdate(_, subcb) { t.equal('fval', props.get('foo')); t.equal('bval2', props.get('bar')); subcb(); }, function del(_, subcb) { props.delete('bar', subcb); }, function checkDel(_, subcb) { t.equal('fval', props.get('foo')); t.ok(props.get('bar') === undefined); subcb(); }, function closeLeveDb(_, subcb) { props.db.close(subcb); }, //Reopen and get again. function openNew(_, subcb) { props = new lib.Properties({ 'log': LOG, 'location': DB_FILE }); props.on('ready', subcb); }, function readAgain(_, subcb) { t.equal('fval', props.get('foo')); t.ok(props.get('bar') === undefined); subcb(); }, function finalCloseLeveDb(_, subcb) { props.db.close(subcb); } ] }, function (err) { if (err) { t.fail(err); } t.done(); }); });
test('test assumptions', function (t) { var db; vasync.pipeline({ 'funcs': [ function mkTmpDir(_, cb) { fs.mkdir(TMP_DIR, function (err) { if (err && err.code !== 'EEXIST') { return (cb(err)); } return (cb()); }); }, function initDb(_, cb) { var levelOpts = { 'keyEncoding': 'binary', 'valueEncoding': 'json' }; levelup(DB_FILE, levelOpts, function (err, d) { if (err) { return (cb(err)); } db = d; cb(); }); }, function writeStuff(_, cb) { db.batch([ { 'type': 'put', 'key': le(0), 'value': e(0, 0) }, { 'type': 'put', 'key': le(1), 'value': e(1, 0) }, { 'type': 'put', 'key': le(2), 'value': e(2, 1) }, { 'type': 'put', 'key': le(3), 'value': e(3, 1) }, { 'type': 'put', 'key': le(4), 'value': e(4, 2) } ], cb); }, function iterMiddle(_, cb) { var rs = db.createReadStream({ 'start': le(1), 'end': le(3) }); var res = []; //Note: non-flowing! rs.on('readable', function () { var d; while (null !== (d = rs.read())) { res.push(d.value.index); } }); rs.on('close', function () { t.deepEqual([1, 2, 3], res); cb(); }); }, function iterMidToEnd(_, cb) { var rs = db.createReadStream({ 'start': le(1), 'end': new Buffer('ffffffff', 'hex') }); var res = []; //Note: non-flowing! rs.on('readable', function () { var d; while (null !== (d = rs.read())) { res.push(d.value.index); } }); rs.on('close', function () { cb(); }); }, function iterAfterEnd(_, cb) { var rs = db.createReadStream({ 'start': le(10), 'end': new Buffer('ffffffff', 'hex') }); var res = []; rs.on('readable', function () { var d; while (null !== (d = rs.read())) { res.push(d.value.index); } }); rs.on('close', function () { t.deepEqual([], res); cb(); }); }, function iterStartEndEqual(_, cb) { var rs = db.createReadStream({ 'start': le(2), 'end': le(2) }); var res = []; rs.on('readable', function () { var d; while (null !== (d = rs.read())) { res.push(d.value.index); } }); rs.on('close', function () { t.deepEqual([ 2 ], res); cb(); }); }, function oneThenFlowingThenDestroy(_, cb) { var rs = db.createReadStream({ 'start': le(1), 'end': le(5) }); var res = []; rs.once('readable', function () { var d = rs.read(); t.equal(1, d.value.index); var read = 0; rs.on('data', function (data) { res.push(data.value.index); if (++read === 2) { //Note the destroy function here! rs.destroy(); } }); }); rs.on('close', function () { t.deepEqual([2, 3], res); cb(); }); }, function close(_, cb) { db.close(cb); }, function deleteDb(_, cb) { rmrf(DB_FILE, cb); } ] }, function (err) { if (err) { t.fail(err); } t.done(); }); });
///--- Helpers function runLogKeyTest(t, i) { var enc = lib.key.log(i); var dec = lib.key.logDecode(enc); t.equal(i, dec); } ///--- Tests test('property key', function (t) { var s = 'foobarbaz'; var enc = lib.key.property(s); var dec = lib.key.propertyDecode(enc); t.equal(s, dec); t.done(); }); test('internal property key', function (t) { var s = 'foobarbaz'; var enc = lib.key.internalProperty(s); var dec = lib.key.propertyDecode(enc); t.equal(s, dec); t.done(); }); test('log key', function (t) {
runFlowingTest(opts, function () { opts.flowing = true; runFlowingTest(opts, function () { opts.t.done(); }); }); } ///--- Tests for the PairsStream class test('no elements in streams', function (t) { runTest({ 't': t, 'left': [], 'right': [], 'result': makeResult() }); }); test('one element in streams', function (t) { runTest({ 't': t, 'left': [1], 'right': [2], 'result': makeResult([1, 2]) }); }); test('end at same time', function (t) {
test('request/reply', function (t) { var mb; var peers = getTopology(2); var funcs = [ function init(_, subcb) { mb = new lib.MessageBus({ 'log': LOG, 'peers': peers }); mb.on('ready', subcb); }, function reqrep(_, subcb) { var messageId; var responseCalled = false; function onResponse(err, gMessageId, from, res) { t.equal(1, peers['0'].appendEntriesCalled); t.equal(messageId, gMessageId); t.equal('0', from); responseCalled = true; } t.equal(0, peers['0'].appendEntriesCalled); messageId = mb.send( 'me', '0', { 'operation': 'appendEntries' }, onResponse); setImmediate(function () { t.equal(1, Object.keys(mb.messages).length); mb.tick(function () { t.ok(responseCalled); subcb(); }); }); } ]; vasync.pipeline({ funcs: funcs }, function (err) { if (err) { t.fail(err); } t.done(); }); });