Ruben Bridgewater
9 years ago
36 changed files with 1988 additions and 1156 deletions
@ -1,9 +1,10 @@ |
|||
examples/ |
|||
benches/ |
|||
benchmarks/ |
|||
test/ |
|||
diff_multi_bench_output.js |
|||
generate_commands.js |
|||
multi_bench.js |
|||
test-unref.js |
|||
changelog.md |
|||
.nyc_output/ |
|||
coverage/ |
|||
.tern-port |
|||
*.log |
|||
*.rdb |
|||
*.out |
|||
*.yml |
|||
|
@ -1,91 +0,0 @@ |
|||
'use strict'; |
|||
|
|||
var source = new Buffer(100), |
|||
dest = new Buffer(100), i, j, k, tmp, count = 1000000, bytes = 100; |
|||
|
|||
for (i = 99 ; i >= 0 ; i--) { |
|||
source[i] = 120; |
|||
} |
|||
|
|||
var str = 'This is a nice String.', |
|||
buf = new Buffer('This is a lovely Buffer.'); |
|||
|
|||
var start = new Date(); |
|||
for (i = count * 100; i > 0 ; i--) { |
|||
if (Buffer.isBuffer(str)) {} |
|||
} |
|||
var end = new Date(); |
|||
console.log('Buffer.isBuffer(str) ' + (end - start) + ' ms'); |
|||
|
|||
var start = new Date(); |
|||
for (i = count * 100; i > 0 ; i--) { |
|||
if (Buffer.isBuffer(buf)) {} |
|||
} |
|||
var end = new Date(); |
|||
console.log('Buffer.isBuffer(buf) ' + (end - start) + ' ms'); |
|||
|
|||
var start = new Date(); |
|||
for (i = count * 100; i > 0 ; i--) { |
|||
if (str instanceof Buffer) {} |
|||
} |
|||
var end = new Date(); |
|||
console.log('str instanceof Buffer ' + (end - start) + ' ms'); |
|||
|
|||
var start = new Date(); |
|||
for (i = count * 100; i > 0 ; i--) { |
|||
if (buf instanceof Buffer) {} |
|||
} |
|||
var end = new Date(); |
|||
console.log('buf instanceof Buffer ' + (end - start) + ' ms'); |
|||
|
|||
for (i = bytes ; i > 0 ; i --) { |
|||
var start = new Date(); |
|||
for (j = count ; j > 0; j--) { |
|||
tmp = source.toString('ascii', 0, bytes); |
|||
} |
|||
var end = new Date(); |
|||
console.log('toString() ' + i + ' bytes ' + (end - start) + ' ms'); |
|||
} |
|||
|
|||
for (i = bytes ; i > 0 ; i --) { |
|||
var start = new Date(); |
|||
for (j = count ; j > 0; j--) { |
|||
tmp = ''; |
|||
for (k = 0; k <= i ; k++) { |
|||
tmp += String.fromCharCode(source[k]); |
|||
} |
|||
} |
|||
var end = new Date(); |
|||
console.log('manual string ' + i + ' bytes ' + (end - start) + ' ms'); |
|||
} |
|||
|
|||
for (i = bytes ; i > 0 ; i--) { |
|||
var start = new Date(); |
|||
for (j = count ; j > 0 ; j--) { |
|||
for (k = i ; k > 0 ; k--) { |
|||
dest[k] = source[k]; |
|||
} |
|||
} |
|||
var end = new Date(); |
|||
console.log('Manual copy ' + i + ' bytes ' + (end - start) + ' ms'); |
|||
} |
|||
|
|||
for (i = bytes ; i > 0 ; i--) { |
|||
var start = new Date(); |
|||
for (j = count ; j > 0 ; j--) { |
|||
for (k = i ; k > 0 ; k--) { |
|||
dest[k] = 120; |
|||
} |
|||
} |
|||
var end = new Date(); |
|||
console.log('Direct assignment ' + i + ' bytes ' + (end - start) + ' ms'); |
|||
} |
|||
|
|||
for (i = bytes ; i > 0 ; i--) { |
|||
var start = new Date(); |
|||
for (j = count ; j > 0 ; j--) { |
|||
source.copy(dest, 0, 0, i); |
|||
} |
|||
var end = new Date(); |
|||
console.log('Buffer.copy() ' + i + ' bytes ' + (end - start) + ' ms'); |
|||
} |
File diff suppressed because it is too large
@ -0,0 +1,86 @@ |
|||
'use strict'; |
|||
|
|||
var commands = require('redis-commands'); |
|||
var Multi = require('./multi'); |
|||
var RedisClient = require('../').RedisClient; |
|||
|
|||
// TODO: Rewrite this including the invidual commands into a Commands class
|
|||
// that provided a functionality to add new commands to the client
|
|||
|
|||
commands.list.forEach(function (command) { |
|||
|
|||
// Do not override existing functions
|
|||
if (!RedisClient.prototype[command]) { |
|||
RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command] = function () { |
|||
var arr; |
|||
var len = arguments.length; |
|||
var callback; |
|||
var i = 0; |
|||
if (Array.isArray(arguments[0])) { |
|||
arr = arguments[0]; |
|||
if (len === 2) { |
|||
callback = arguments[1]; |
|||
} |
|||
} else if (len > 1 && Array.isArray(arguments[1])) { |
|||
if (len === 3) { |
|||
callback = arguments[2]; |
|||
} |
|||
len = arguments[1].length; |
|||
arr = new Array(len + 1); |
|||
arr[0] = arguments[0]; |
|||
for (; i < len; i += 1) { |
|||
arr[i + 1] = arguments[1][i]; |
|||
} |
|||
} else { |
|||
// The later should not be the average use case
|
|||
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) { |
|||
len--; |
|||
callback = arguments[len]; |
|||
} |
|||
arr = new Array(len); |
|||
for (; i < len; i += 1) { |
|||
arr[i] = arguments[i]; |
|||
} |
|||
} |
|||
return this.send_command(command, arr, callback); |
|||
}; |
|||
} |
|||
|
|||
// Do not override existing functions
|
|||
if (!Multi.prototype[command]) { |
|||
Multi.prototype[command.toUpperCase()] = Multi.prototype[command] = function () { |
|||
var arr; |
|||
var len = arguments.length; |
|||
var callback; |
|||
var i = 0; |
|||
if (Array.isArray(arguments[0])) { |
|||
arr = arguments[0]; |
|||
if (len === 2) { |
|||
callback = arguments[1]; |
|||
} |
|||
} else if (len > 1 && Array.isArray(arguments[1])) { |
|||
if (len === 3) { |
|||
callback = arguments[2]; |
|||
} |
|||
len = arguments[1].length; |
|||
arr = new Array(len + 1); |
|||
arr[0] = arguments[0]; |
|||
for (; i < len; i += 1) { |
|||
arr[i + 1] = arguments[1][i]; |
|||
} |
|||
} else { |
|||
// The later should not be the average use case
|
|||
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) { |
|||
len--; |
|||
callback = arguments[len]; |
|||
} |
|||
arr = new Array(len); |
|||
for (; i < len; i += 1) { |
|||
arr[i] = arguments[i]; |
|||
} |
|||
} |
|||
this.queue.push([command, arr, callback]); |
|||
return this; |
|||
}; |
|||
} |
|||
}); |
@ -0,0 +1,79 @@ |
|||
'use strict'; |
|||
|
|||
var utils = require('./utils'); |
|||
var URL = require('url'); |
|||
|
|||
module.exports = function createClient (port_arg, host_arg, options) { |
|||
|
|||
if (typeof port_arg === 'number' || typeof port_arg === 'string' && /^\d+$/.test(port_arg)) { |
|||
|
|||
var host; |
|||
if (typeof host_arg === 'string') { |
|||
host = host_arg; |
|||
} else { |
|||
if (options && host_arg) { |
|||
throw new Error('Unknown type of connection in createClient()'); |
|||
} |
|||
options = options || host_arg; |
|||
} |
|||
options = utils.clone(options); |
|||
options.host = host || options.host; |
|||
options.port = port_arg; |
|||
|
|||
} else if (typeof port_arg === 'string' || port_arg && port_arg.url) { |
|||
|
|||
options = utils.clone(port_arg.url ? port_arg : host_arg || options); |
|||
var parsed = URL.parse(port_arg.url || port_arg, true, true); |
|||
|
|||
// [redis:]//[[user][:password]@][host][:port][/db-number][?db=db-number[&password=bar[&option=value]]]
|
|||
if (parsed.slashes) { // We require slashes
|
|||
if (parsed.auth) { |
|||
options.password = parsed.auth.split(':')[1]; |
|||
} |
|||
if (parsed.protocol && parsed.protocol !== 'redis:') { |
|||
console.warn('node_redis: WARNING: You passed "' + parsed.protocol.substring(0, parsed.protocol.length - 1) + '" as protocol instead of the "redis" protocol!'); |
|||
} |
|||
if (parsed.pathname && parsed.pathname !== '/') { |
|||
options.db = parsed.pathname.substr(1); |
|||
} |
|||
if (parsed.hostname) { |
|||
options.host = parsed.hostname; |
|||
} |
|||
if (parsed.port) { |
|||
options.port = parsed.port; |
|||
} |
|||
if (parsed.search !== '') { |
|||
var elem; |
|||
for (elem in parsed.query) { // jshint ignore: line
|
|||
// If options are passed twice, only the parsed options will be used
|
|||
if (elem in options) { |
|||
if (options[elem] === parsed.query[elem]) { |
|||
console.warn('node_redis: WARNING: You passed the ' + elem + ' option twice!'); |
|||
} else { |
|||
throw new Error('The ' + elem + ' option is added twice and does not match'); |
|||
} |
|||
} |
|||
options[elem] = parsed.query[elem]; |
|||
} |
|||
} |
|||
} else if (parsed.hostname) { |
|||
throw new Error('The redis url must begin with slashes "//" or contain slashes after the redis protocol'); |
|||
} else { |
|||
options.path = port_arg; |
|||
} |
|||
|
|||
} else if (typeof port_arg === 'object' || port_arg === undefined) { |
|||
options = utils.clone(port_arg || options); |
|||
options.host = options.host || host_arg; |
|||
|
|||
if (port_arg && arguments.length !== 1) { |
|||
throw new Error('To many arguments passed to createClient. Please only pass the options object'); |
|||
} |
|||
} |
|||
|
|||
if (!options) { |
|||
throw new Error('Unknown type of connection in createClient()'); |
|||
} |
|||
|
|||
return options; |
|||
}; |
@ -0,0 +1,11 @@ |
|||
'use strict'; |
|||
|
|||
var index = require('../'); |
|||
|
|||
function debug (msg) { |
|||
if (index.debug_mode) { |
|||
console.error(msg); |
|||
} |
|||
} |
|||
|
|||
module.exports = debug; |
@ -0,0 +1,137 @@ |
|||
'use strict'; |
|||
|
|||
var utils = require('./utils'); |
|||
var debug = require('./debug'); |
|||
var Multi = require('./multi'); |
|||
var no_password_is_set = /no password is set/; |
|||
var RedisClient = require('../').RedisClient; |
|||
|
|||
/******************************** |
|||
Replace built-in redis functions |
|||
********************************/ |
|||
|
|||
RedisClient.prototype.multi = RedisClient.prototype.MULTI = function multi (args) { |
|||
var multi = new Multi(this, args); |
|||
multi.exec = multi.EXEC = multi.exec_transaction; |
|||
return multi; |
|||
}; |
|||
|
|||
// ATTENTION: This is not a native function but is still handled as a individual command as it behaves just the same as multi
|
|||
RedisClient.prototype.batch = RedisClient.prototype.BATCH = function batch (args) { |
|||
return new Multi(this, args); |
|||
}; |
|||
|
|||
// Store db in this.select_db to restore it on reconnect
|
|||
RedisClient.prototype.select = RedisClient.prototype.SELECT = function select (db, callback) { |
|||
var self = this; |
|||
return this.send_command('select', [db], function (err, res) { |
|||
if (err === null) { |
|||
self.selected_db = db; |
|||
} |
|||
utils.callback_or_emit(self, callback, err, res); |
|||
}); |
|||
}; |
|||
|
|||
// Store info in this.server_info after each call
|
|||
RedisClient.prototype.info = RedisClient.prototype.INFO = function info (callback) { |
|||
var self = this; |
|||
var ready = this.ready; |
|||
this.ready = ready || this.offline_queue.length === 0; // keep the execution order intakt
|
|||
var tmp = this.send_command('info', [], function (err, res) { |
|||
if (res) { |
|||
var obj = {}; |
|||
var lines = res.toString().split('\r\n'); |
|||
var line, parts, sub_parts; |
|||
|
|||
for (var i = 0; i < lines.length; i++) { |
|||
parts = lines[i].split(':'); |
|||
if (parts[1]) { |
|||
if (parts[0].indexOf('db') === 0) { |
|||
sub_parts = parts[1].split(','); |
|||
obj[parts[0]] = {}; |
|||
while (line = sub_parts.pop()) { |
|||
line = line.split('='); |
|||
obj[parts[0]][line[0]] = +line[1]; |
|||
} |
|||
} else { |
|||
obj[parts[0]] = parts[1]; |
|||
} |
|||
} |
|||
} |
|||
obj.versions = []; |
|||
/* istanbul ignore else: some redis servers do not send the version */ |
|||
if (obj.redis_version) { |
|||
obj.redis_version.split('.').forEach(function (num) { |
|||
obj.versions.push(+num); |
|||
}); |
|||
} |
|||
// Expose info key/vals to users
|
|||
self.server_info = obj; |
|||
} else { |
|||
self.server_info = {}; |
|||
} |
|||
utils.callback_or_emit(self, callback, err, res); |
|||
}); |
|||
this.ready = ready; |
|||
return tmp; |
|||
}; |
|||
|
|||
RedisClient.prototype.auth = RedisClient.prototype.AUTH = function auth (pass, callback) { |
|||
var self = this; |
|||
var ready = this.ready; |
|||
debug('Sending auth to ' + self.address + ' id ' + self.connection_id); |
|||
|
|||
// Stash auth for connect and reconnect.
|
|||
this.auth_pass = pass; |
|||
this.ready = this.offline_queue.length === 0; // keep the execution order intakt
|
|||
var tmp = this.send_command('auth', [pass], function (err, res) { |
|||
if (err && no_password_is_set.test(err.message)) { |
|||
self.warn('Warning: Redis server does not require a password, but a password was supplied.'); |
|||
err = null; |
|||
res = 'OK'; |
|||
} |
|||
|
|||
utils.callback_or_emit(self, callback, err, res); |
|||
}); |
|||
this.ready = ready; |
|||
return tmp; |
|||
}; |
|||
|
|||
RedisClient.prototype.hmset = RedisClient.prototype.HMSET = function hmset () { |
|||
var arr, |
|||
len = arguments.length, |
|||
callback, |
|||
i = 0; |
|||
if (Array.isArray(arguments[0])) { |
|||
arr = arguments[0]; |
|||
callback = arguments[1]; |
|||
} else if (Array.isArray(arguments[1])) { |
|||
if (len === 3) { |
|||
callback = arguments[2]; |
|||
} |
|||
len = arguments[1].length; |
|||
arr = new Array(len + 1); |
|||
arr[0] = arguments[0]; |
|||
for (; i < len; i += 1) { |
|||
arr[i + 1] = arguments[1][i]; |
|||
} |
|||
} else if (typeof arguments[1] === 'object' && (arguments.length === 2 || arguments.length === 3 && typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined')) { |
|||
arr = [arguments[0]]; |
|||
for (var field in arguments[1]) { // jshint ignore: line
|
|||
arr.push(field, arguments[1][field]); |
|||
} |
|||
callback = arguments[2]; |
|||
} else { |
|||
len = arguments.length; |
|||
// The later should not be the average use case
|
|||
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) { |
|||
len--; |
|||
callback = arguments[len]; |
|||
} |
|||
arr = new Array(len); |
|||
for (; i < len; i += 1) { |
|||
arr[i] = arguments[i]; |
|||
} |
|||
} |
|||
return this.send_command('hmset', arr, callback); |
|||
}; |
@ -0,0 +1,224 @@ |
|||
'use strict'; |
|||
|
|||
var Queue = require('double-ended-queue'); |
|||
var utils = require('./utils'); |
|||
|
|||
function Multi(client, args) { |
|||
this._client = client; |
|||
this.queue = new Queue(); |
|||
var command, tmp_args; |
|||
if (args) { // Either undefined or an array. Fail hard if it's not an array
|
|||
for (var i = 0; i < args.length; i++) { |
|||
command = args[i][0]; |
|||
tmp_args = args[i].slice(1); |
|||
if (Array.isArray(command)) { |
|||
this[command[0]].apply(this, command.slice(1).concat(tmp_args)); |
|||
} else { |
|||
this[command].apply(this, tmp_args); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
Multi.prototype.hmset = Multi.prototype.HMSET = function hmset () { |
|||
var arr, |
|||
len = 0, |
|||
callback, |
|||
i = 0; |
|||
if (Array.isArray(arguments[0])) { |
|||
arr = arguments[0]; |
|||
callback = arguments[1]; |
|||
} else if (Array.isArray(arguments[1])) { |
|||
len = arguments[1].length; |
|||
arr = new Array(len + 1); |
|||
arr[0] = arguments[0]; |
|||
for (; i < len; i += 1) { |
|||
arr[i + 1] = arguments[1][i]; |
|||
} |
|||
callback = arguments[2]; |
|||
} else if (typeof arguments[1] === 'object' && (typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined')) { |
|||
arr = [arguments[0]]; |
|||
for (var field in arguments[1]) { // jshint ignore: line
|
|||
arr.push(field, arguments[1][field]); |
|||
} |
|||
callback = arguments[2]; |
|||
} else { |
|||
len = arguments.length; |
|||
// The later should not be the average use case
|
|||
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) { |
|||
len--; |
|||
callback = arguments[len]; |
|||
} |
|||
arr = new Array(len); |
|||
for (; i < len; i += 1) { |
|||
arr[i] = arguments[i]; |
|||
} |
|||
} |
|||
this.queue.push(['hmset', arr, callback]); |
|||
return this; |
|||
}; |
|||
|
|||
function pipeline_transaction_command (self, command, args, index, cb) { |
|||
self._client.send_command(command, args, function (err, reply) { |
|||
if (err) { |
|||
if (cb) { |
|||
cb(err); |
|||
} |
|||
err.position = index; |
|||
self.errors.push(err); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
Multi.prototype.exec_atomic = function exec_atomic (callback) { |
|||
if (this.queue.length < 2) { |
|||
return this.exec_batch(callback); |
|||
} |
|||
return this.exec(callback); |
|||
}; |
|||
|
|||
function multi_callback (self, err, replies) { |
|||
var i = 0, args; |
|||
|
|||
if (err) { |
|||
// The errors would be circular
|
|||
var connection_error = ['CONNECTION_BROKEN', 'UNCERTAIN_STATE'].indexOf(err.code) !== -1; |
|||
err.errors = connection_error ? [] : self.errors; |
|||
if (self.callback) { |
|||
self.callback(err); |
|||
// Exclude connection errors so that those errors won't be emitted twice
|
|||
} else if (!connection_error) { |
|||
self._client.emit('error', err); |
|||
} |
|||
return; |
|||
} |
|||
|
|||
if (replies) { |
|||
while (args = self.queue.shift()) { |
|||
if (replies[i] instanceof Error) { |
|||
var match = replies[i].message.match(utils.err_code); |
|||
// LUA script could return user errors that don't behave like all other errors!
|
|||
if (match) { |
|||
replies[i].code = match[1]; |
|||
} |
|||
replies[i].command = args[0].toUpperCase(); |
|||
if (typeof args[2] === 'function') { |
|||
args[2](replies[i]); |
|||
} |
|||
} else { |
|||
// If we asked for strings, even in detect_buffers mode, then return strings:
|
|||
replies[i] = self._client.handle_reply(replies[i], args[0], self.wants_buffers[i]); |
|||
if (typeof args[2] === 'function') { |
|||
args[2](null, replies[i]); |
|||
} |
|||
} |
|||
i++; |
|||
} |
|||
} |
|||
|
|||
if (self.callback) { |
|||
self.callback(null, replies); |
|||
} |
|||
} |
|||
|
|||
Multi.prototype.exec_transaction = function exec_transaction (callback) { |
|||
var self = this; |
|||
var len = self.queue.length; |
|||
self.errors = []; |
|||
self.callback = callback; |
|||
self._client.cork(len + 2); |
|||
self.wants_buffers = new Array(len); |
|||
pipeline_transaction_command(self, 'multi', []); |
|||
// Drain queue, callback will catch 'QUEUED' or error
|
|||
for (var index = 0; index < len; index++) { |
|||
var args = self.queue.get(index); |
|||
var command = args[0]; |
|||
var cb = args[2]; |
|||
// Keep track of who wants buffer responses:
|
|||
if (self._client.options.detect_buffers) { |
|||
self.wants_buffers[index] = false; |
|||
for (var i = 0; i < args[1].length; i += 1) { |
|||
if (args[1][i] instanceof Buffer) { |
|||
self.wants_buffers[index] = true; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
pipeline_transaction_command(self, command, args[1], index, cb); |
|||
} |
|||
|
|||
self._client.send_command('exec', [], function(err, replies) { |
|||
multi_callback(self, err, replies); |
|||
}); |
|||
self._client.uncork(); |
|||
self._client.writeDefault = self._client.writeStrings; |
|||
return !self._client.should_buffer; |
|||
}; |
|||
|
|||
function batch_callback (self, cb, i) { |
|||
return function batch_callback (err, res) { |
|||
if (err) { |
|||
self.results[i] = err; |
|||
// Add the position to the error
|
|||
self.results[i].position = i; |
|||
} else { |
|||
self.results[i] = res; |
|||
} |
|||
cb(err, res); |
|||
}; |
|||
} |
|||
|
|||
Multi.prototype.exec = Multi.prototype.EXEC = Multi.prototype.exec_batch = function exec_batch (callback) { |
|||
var self = this; |
|||
var len = self.queue.length; |
|||
var index = 0; |
|||
var args; |
|||
var args_len = 1; |
|||
var callback_without_own_cb = function (err, res) { |
|||
if (err) { |
|||
self.results.push(err); |
|||
// Add the position to the error
|
|||
var i = self.results.length - 1; |
|||
self.results[i].position = i; |
|||
} else { |
|||
self.results.push(res); |
|||
} |
|||
// Do not emit an error here. Otherwise each error would result in one emit.
|
|||
// The errors will be returned in the result anyway
|
|||
}; |
|||
var last_callback = function (cb) { |
|||
return function (err, res) { |
|||
cb(err, res); |
|||
callback(null, self.results); |
|||
}; |
|||
}; |
|||
if (len === 0) { |
|||
if (callback) { |
|||
utils.reply_in_order(self._client, callback, null, []); |
|||
} |
|||
return true; |
|||
} |
|||
self.results = []; |
|||
self._client.cork(len); |
|||
while (args = self.queue.shift()) { |
|||
var command = args[0]; |
|||
var cb; |
|||
args_len = args[1].length - 1; |
|||
if (typeof args[2] === 'function') { |
|||
cb = batch_callback(self, args[2], index); |
|||
} else { |
|||
cb = callback_without_own_cb; |
|||
} |
|||
if (callback && index === len - 1) { |
|||
cb = last_callback(cb); |
|||
} |
|||
self._client.send_command(command, args[1], cb); |
|||
index++; |
|||
} |
|||
self.queue = new Queue(); |
|||
self._client.uncork(); |
|||
self._client.writeDefault = self._client.writeStrings; |
|||
return !self._client.should_buffer; |
|||
}; |
|||
|
|||
module.exports = Multi; |
@ -0,0 +1,99 @@ |
|||
'use strict'; |
|||
|
|||
var assert = require("assert"); |
|||
var config = require("./lib/config"); |
|||
var helper = require('./helper'); |
|||
var RedisProcess = require("./lib/redis-process"); |
|||
var rp; |
|||
var path = require('path'); |
|||
var redis = config.redis; |
|||
|
|||
if (process.platform === 'win32') { |
|||
// TODO: Fix redis process spawn on windows
|
|||
return; |
|||
} |
|||
|
|||
describe('master slave sync', function () { |
|||
var master = null; |
|||
var slave = null; |
|||
|
|||
before(function (done) { |
|||
helper.stopRedis(function () { |
|||
helper.startRedis('./conf/password.conf', done); |
|||
}); |
|||
}); |
|||
|
|||
before(function (done) { |
|||
if (helper.redisProcess().spawnFailed()) return done(); |
|||
master = redis.createClient({ |
|||
password: 'porkchopsandwiches' |
|||
}); |
|||
var multi = master.multi(); |
|||
var i = 0; |
|||
while (i < 1000) { |
|||
i++; |
|||
// Write some data in the redis instance, so there's something to sync
|
|||
multi.set('foo' + i, 'bar' + new Array(500).join(Math.random())); |
|||
} |
|||
multi.exec(done); |
|||
}); |
|||
|
|||
it("sync process and no master should delay ready being emitted for slaves", function (done) { |
|||
if (helper.redisProcess().spawnFailed()) this.skip(); |
|||
|
|||
var port = 6381; |
|||
var firstInfo; |
|||
slave = redis.createClient({ |
|||
port: port, |
|||
retry_strategy: function (options) { |
|||
// Try to reconnect in very small intervals to catch the master_link_status down before the sync completes
|
|||
return 10; |
|||
} |
|||
}); |
|||
|
|||
var tmp = slave.info.bind(slave); |
|||
var i = 0; |
|||
slave.info = function (err, res) { |
|||
i++; |
|||
tmp(err, res); |
|||
if (!firstInfo || Object.keys(firstInfo).length === 0) { |
|||
firstInfo = slave.server_info; |
|||
} |
|||
}; |
|||
|
|||
slave.on('connect', function () { |
|||
assert.strictEqual(i, 0); |
|||
}); |
|||
|
|||
var end = helper.callFuncAfter(done, 2); |
|||
|
|||
slave.on('ready', function () { |
|||
assert.strictEqual(this.server_info.master_link_status, 'up'); |
|||
assert.strictEqual(firstInfo.master_link_status, 'down'); |
|||
assert(i > 1); |
|||
this.get('foo300', function (err, res) { |
|||
assert.strictEqual(res.substr(0, 3), 'bar'); |
|||
end(err); |
|||
}); |
|||
}); |
|||
|
|||
RedisProcess.start(function (err, _rp) { |
|||
rp = _rp; |
|||
end(err); |
|||
}, path.resolve(__dirname, './conf/slave.conf'), port); |
|||
}); |
|||
|
|||
after(function (done) { |
|||
if (helper.redisProcess().spawnFailed()) return done(); |
|||
var end = helper.callFuncAfter(done, 3); |
|||
rp.stop(end); |
|||
slave.end(true); |
|||
master.flushdb(function (err) { |
|||
end(err); |
|||
master.end(true); |
|||
}); |
|||
helper.stopRedis(function () { |
|||
helper.startRedis('./conf/redis.conf', end); |
|||
}); |
|||
}); |
|||
}); |
@ -0,0 +1,19 @@ |
|||
-----BEGIN CERTIFICATE----- |
|||
MIIDATCCAemgAwIBAgIJALkMmVkQOERnMA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV |
|||
BAMMDHJlZGlzLmpzLm9yZzAeFw0xNTEwMTkxMjIzMjRaFw0yNTEwMTYxMjIzMjRa |
|||
MBcxFTATBgNVBAMMDHJlZGlzLmpzLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEP |
|||
ADCCAQoCggEBAJ/DmMTJHf7kyspxI1A/JmOc+KI9vxEcN5qn7IiZuGN7ghE43Q3q |
|||
XB2GUkMAuW1POkmM5yi3SuT1UXDR/4Gk7KlbHKMs37AV6PgJXX6oX0zu12LTAT7V |
|||
5byNrYtehSo42l1188dGEMCGaaf0cDntc7A3aW0ZtzrJt+2pu31Uatl2SEJCMra6 |
|||
+v6O0c9aHMF1cArKeawGqR+jHw6vXFZQbUd06nW5nQlUA6wVt1JjlLPwBwYsWLsi |
|||
YQxMC8NqpgAIg5tULSCpKwx5isL/CeotVVGDNZ/G8R1nTrxuygPlc3Qskj57hmV4 |
|||
tZK4JJxQFi7/9ehvjAvHohKrEPeqV5XL87cCAwEAAaNQME4wHQYDVR0OBBYEFCn/ |
|||
5hB+XY4pVOnaqvrmZMxrLFjLMB8GA1UdIwQYMBaAFCn/5hB+XY4pVOnaqvrmZMxr |
|||
LFjLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEduPyTHpXkCVZRQ |
|||
v6p+Ug4iVeXpxGCVr34y7EDUMgmuDdqsz1SrmqeDd0VmjZT8htbWw7QBKDPEBsbi |
|||
wl606aAn01iM+oUrwbtXxid1xfZj/j6pIhQVkGu7e/8A7Pr4QOP4OMdHB7EmqkAo |
|||
d/OLHa9LdKv2UtJHD6U7oVQbdBHrRV62125GMmotpQuSkEfZM6edKNzHPlqV/zJc |
|||
2kGCw3lZC21mTrsSMIC/FQiobPnig4kAvfh0of2rK/XAntlwT8ie1v1aK+jERsfm |
|||
uzMihl6XXBdzheq6KdIlf+5STHBIIRcvBoRKr5Va7EhnO03tTzeJowtqDv47yPC6 |
|||
w4kLcP8= |
|||
-----END CERTIFICATE----- |
@ -0,0 +1,6 @@ |
|||
port 6381 |
|||
bind ::1 127.0.0.1 |
|||
unixsocket /tmp/redis6381.sock |
|||
unixsocketperm 755 |
|||
slaveof localhost 6379 |
|||
masterauth porkchopsandwiches |
@ -0,0 +1,241 @@ |
|||
'use strict'; |
|||
|
|||
var assert = require('assert'); |
|||
var unifyOptions = require('../lib/createClient'); |
|||
var intercept = require('intercept-stdout'); |
|||
|
|||
describe('createClient options', function () { |
|||
|
|||
describe('port as first parameter', function () { |
|||
it('pass the options in the second parameter after a port', function () { |
|||
var options = unifyOptions(1234, { |
|||
option1: true, |
|||
option2: function () {} |
|||
}); |
|||
assert.strictEqual(Object.keys(options).length, 4); |
|||
assert(options.option1); |
|||
assert.strictEqual(options.port, 1234); |
|||
assert.strictEqual(options.host, undefined); |
|||
assert.strictEqual(typeof options.option2, 'function'); |
|||
}); |
|||
|
|||
it('pass the options in the third parameter after a port and host being set to null', function () { |
|||
var options = unifyOptions(1234, null, { |
|||
option1: true, |
|||
option2: function () {} |
|||
}); |
|||
assert.strictEqual(Object.keys(options).length, 4); |
|||
assert(options.option1); |
|||
assert.strictEqual(options.port, 1234); |
|||
assert.strictEqual(options.host, undefined); |
|||
assert.strictEqual(typeof options.option2, 'function'); |
|||
}); |
|||
|
|||
it('pass the options in the third parameter after a port and host being set to undefined', function () { |
|||
var options = unifyOptions(1234, undefined, { |
|||
option1: true, |
|||
option2: function () {} |
|||
}); |
|||
assert.strictEqual(Object.keys(options).length, 4); |
|||
assert(options.option1); |
|||
assert.strictEqual(options.port, 1234); |
|||
assert.strictEqual(options.host, undefined); |
|||
assert.strictEqual(typeof options.option2, 'function'); |
|||
}); |
|||
|
|||
it('pass the options in the third parameter after a port and host', function () { |
|||
var options = unifyOptions('1234', 'localhost', { |
|||
option1: true, |
|||
option2: function () {} |
|||
}); |
|||
assert.strictEqual(Object.keys(options).length, 4); |
|||
assert(options.option1); |
|||
assert.strictEqual(options.port, '1234'); |
|||
assert.strictEqual(options.host, 'localhost'); |
|||
assert.strictEqual(typeof options.option2, 'function'); |
|||
}); |
|||
|
|||
it('should throw with three parameters all set to a truthy value', function () { |
|||
try { |
|||
unifyOptions(1234, {}, {}); |
|||
throw new Error('failed'); |
|||
} catch (err) { |
|||
assert.strictEqual(err.message, 'Unknown type of connection in createClient()'); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
describe('unix socket as first parameter', function () { |
|||
it('pass the options in the second parameter after a port', function () { |
|||
var options = unifyOptions('/tmp/redis.sock', { |
|||
option1: true, |
|||
option2: function () {}, |
|||
option3: [1, 2, 3] |
|||
}); |
|||
assert.strictEqual(Object.keys(options).length, 4); |
|||
assert(options.option1); |
|||
assert.strictEqual(options.path, '/tmp/redis.sock'); |
|||
assert.strictEqual(typeof options.option2, 'function'); |
|||
assert.strictEqual(options.option3.length, 3); |
|||
}); |
|||
|
|||
it('pass the options in the third parameter after a port and host being set to null', function () { |
|||
var options = unifyOptions('/tmp/redis.sock', null, { |
|||
option1: true, |
|||
option2: function () {} |
|||
}); |
|||
assert.strictEqual(Object.keys(options).length, 3); |
|||
assert(options.option1); |
|||
assert.strictEqual(options.path, '/tmp/redis.sock'); |
|||
assert.strictEqual(typeof options.option2, 'function'); |
|||
}); |
|||
}); |
|||
|
|||
describe('redis url as first parameter', function () { |
|||
it('empty redis url including options as second parameter', function () { |
|||
var options = unifyOptions('redis://', { |
|||
option: [1, 2, 3] |
|||
}); |
|||
assert.strictEqual(Object.keys(options).length, 1); |
|||
assert.strictEqual(options.option.length, 3); |
|||
}); |
|||
|
|||
it('begin with two slashes including options as third parameter', function () { |
|||
var options = unifyOptions('//:abc@/3?port=123', { |
|||
option: [1, 2, 3] |
|||
}); |
|||
assert.strictEqual(Object.keys(options).length, 4); |
|||
assert.strictEqual(options.option.length, 3); |
|||
assert.strictEqual(options.port, '123'); |
|||
assert.strictEqual(options.db, '3'); |
|||
assert.strictEqual(options.password, 'abc'); |
|||
}); |
|||
|
|||
it('duplicated, identical query options including options obj', function () { |
|||
var text = ''; |
|||
var unhookIntercept = intercept(function(data) { |
|||
text += data; |
|||
return ''; |
|||
}); |
|||
var options = unifyOptions('//:abc@localhost:123/3?db=3&port=123&password=abc', null, { |
|||
option: [1, 2, 3] |
|||
}); |
|||
unhookIntercept(); |
|||
assert.strictEqual(text, |
|||
'node_redis: WARNING: You passed the db option twice!\n' + |
|||
'node_redis: WARNING: You passed the port option twice!\n' + |
|||
'node_redis: WARNING: You passed the password option twice!\n' |
|||
); |
|||
assert.strictEqual(Object.keys(options).length, 5); |
|||
assert.strictEqual(options.option.length, 3); |
|||
assert.strictEqual(options.host, 'localhost'); |
|||
assert.strictEqual(options.port, '123'); |
|||
assert.strictEqual(options.db, '3'); |
|||
assert.strictEqual(options.password, 'abc'); |
|||
}); |
|||
|
|||
it('should throw on duplicated, non-identical query options', function () { |
|||
try { |
|||
unifyOptions('//:abc@localhost:1234/3?port=123&password=abc'); |
|||
throw new Error('failed'); |
|||
} catch (err) { |
|||
assert.equal(err.message, 'The port option is added twice and does not match'); |
|||
} |
|||
}); |
|||
|
|||
it('should throw without protocol slashes', function () { |
|||
try { |
|||
unifyOptions('redis:abc@localhost:123/3?db=3&port=123&password=abc'); |
|||
throw new Error('failed'); |
|||
} catch (err) { |
|||
assert.equal(err.message, 'The redis url must begin with slashes "//" or contain slashes after the redis protocol'); |
|||
} |
|||
}); |
|||
|
|||
it("warns on protocol other than redis in the redis url", function () { |
|||
var text = ''; |
|||
var unhookIntercept = intercept(function (data) { |
|||
text += data; |
|||
return ''; |
|||
}); |
|||
var options = unifyOptions('http://abc'); |
|||
unhookIntercept(); |
|||
assert.strictEqual(Object.keys(options).length, 1); |
|||
assert.strictEqual(options.host, 'abc'); |
|||
assert.strictEqual(text, 'node_redis: WARNING: You passed "http" as protocol instead of the "redis" protocol!\n'); |
|||
}); |
|||
}); |
|||
|
|||
describe('no parameters or set to null / undefined', function () { |
|||
it('no parameters', function () { |
|||
var options = unifyOptions(); |
|||
assert.strictEqual(Object.keys(options).length, 1); |
|||
assert.strictEqual(options.host, undefined); |
|||
}); |
|||
|
|||
it('set to null', function () { |
|||
var options = unifyOptions(null, null); |
|||
assert.strictEqual(Object.keys(options).length, 1); |
|||
assert.strictEqual(options.host, null); |
|||
}); |
|||
|
|||
it('set to undefined', function () { |
|||
var options = unifyOptions(undefined, undefined); |
|||
assert.strictEqual(Object.keys(options).length, 1); |
|||
assert.strictEqual(options.host, undefined); |
|||
}); |
|||
}); |
|||
|
|||
describe('only an options object is passed', function () { |
|||
it('with options', function () { |
|||
var options = unifyOptions({ |
|||
option: true |
|||
}); |
|||
assert.strictEqual(Object.keys(options).length, 2); |
|||
assert.strictEqual(options.host, undefined); |
|||
assert.strictEqual(options.option, true); |
|||
}); |
|||
|
|||
it('without options', function () { |
|||
var options = unifyOptions({}); |
|||
assert.strictEqual(Object.keys(options).length, 1); |
|||
assert.strictEqual(options.host, undefined); |
|||
}); |
|||
|
|||
it('should throw with more parameters', function () { |
|||
try { |
|||
unifyOptions({ |
|||
option: true |
|||
}, undefined); |
|||
throw new Error('failed'); |
|||
} catch (err) { |
|||
assert.strictEqual(err.message, 'To many arguments passed to createClient. Please only pass the options object'); |
|||
} |
|||
}); |
|||
|
|||
it('including url as option', function () { |
|||
var options = unifyOptions({ |
|||
option: [1, 2, 3], |
|||
url: '//hm:abc@localhost:123/3' |
|||
}); |
|||
assert.strictEqual(Object.keys(options).length, 6); |
|||
assert.strictEqual(options.option.length, 3); |
|||
assert.strictEqual(options.host, 'localhost'); |
|||
assert.strictEqual(options.port, '123'); |
|||
assert.strictEqual(options.db, '3'); |
|||
assert.strictEqual(options.url, '//hm:abc@localhost:123/3'); |
|||
assert.strictEqual(options.password, 'abc'); |
|||
}); |
|||
}); |
|||
|
|||
describe('faulty data', function () { |
|||
it("throws on strange connection info", function () { |
|||
try { |
|||
unifyOptions(true); |
|||
throw new Error('failed'); |
|||
} catch (err) { |
|||
assert.equal(err.message, 'Unknown type of connection in createClient()'); |
|||
} |
|||
}); |
|||
}); |
|||
}); |
@ -0,0 +1,154 @@ |
|||
'use strict'; |
|||
|
|||
var assert = require('assert'); |
|||
var Queue = require('double-ended-queue'); |
|||
var utils = require('../lib/utils'); |
|||
var intercept = require('intercept-stdout'); |
|||
|
|||
describe('utils.js', function () { |
|||
|
|||
describe('clone', function () { |
|||
it('ignore the object prototype and clone a nested array / object', function () { |
|||
var obj = { |
|||
a: [null, 'foo', ['bar'], { |
|||
"I'm special": true |
|||
}], |
|||
number: 5, |
|||
fn: function noop () {} |
|||
}; |
|||
var clone = utils.clone(obj); |
|||
assert.deepEqual(clone, obj); |
|||
assert.strictEqual(obj.fn, clone.fn); |
|||
assert(typeof clone.fn === 'function'); |
|||
}); |
|||
|
|||
it('replace faulty values with an empty object as return value', function () { |
|||
var a = utils.clone(); |
|||
var b = utils.clone(null); |
|||
assert.strictEqual(Object.keys(a).length, 0); |
|||
assert.strictEqual(Object.keys(b).length, 0); |
|||
}); |
|||
|
|||
it('throws on circular data', function () { |
|||
try { |
|||
var a = {}; |
|||
a.b = a; |
|||
utils.clone(a); |
|||
throw new Error('failed'); |
|||
} catch (e) { |
|||
assert(e.message !== 'failed'); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
describe('print helper', function () { |
|||
it('callback with reply', function () { |
|||
var text = ''; |
|||
var unhookIntercept = intercept(function(data) { |
|||
text += data; |
|||
return ''; |
|||
}); |
|||
utils.print(null, 'abc'); |
|||
unhookIntercept(); |
|||
assert.strictEqual(text, 'Reply: abc\n'); |
|||
}); |
|||
|
|||
it('callback with error', function () { |
|||
var text = ''; |
|||
var unhookIntercept = intercept(function(data) { |
|||
text += data; |
|||
return ''; |
|||
}); |
|||
utils.print(new Error('Wonderful exception')); |
|||
unhookIntercept(); |
|||
assert.strictEqual(text, 'Error: Wonderful exception\n'); |
|||
}); |
|||
}); |
|||
|
|||
describe('reply_in_order', function () { |
|||
|
|||
var err_count = 0; |
|||
var res_count = 0; |
|||
var emitted = false; |
|||
var clientMock = { |
|||
emit: function () { emitted = true; }, |
|||
offline_queue: new Queue(), |
|||
command_queue: new Queue() |
|||
}; |
|||
var create_command_obj = function () { |
|||
return { |
|||
callback: function (err, res) { |
|||
if (err) err_count++; |
|||
else res_count++; |
|||
} |
|||
}; |
|||
}; |
|||
|
|||
beforeEach(function () { |
|||
clientMock.offline_queue.clear(); |
|||
clientMock.command_queue.clear(); |
|||
err_count = 0; |
|||
res_count = 0; |
|||
emitted = false; |
|||
}); |
|||
|
|||
it('no elements in either queue. Reply in the next tick', function (done) { |
|||
var called = false; |
|||
utils.reply_in_order(clientMock, function () { |
|||
called = true; |
|||
done(); |
|||
}, null, null); |
|||
assert(!called); |
|||
}); |
|||
|
|||
it('no elements in either queue. Reply in the next tick', function (done) { |
|||
assert(!emitted); |
|||
utils.reply_in_order(clientMock, null, new Error('tada')); |
|||
assert(!emitted); |
|||
setTimeout(function () { |
|||
assert(emitted); |
|||
done(); |
|||
}, 1); |
|||
}); |
|||
|
|||
it('elements in the offline queue. Reply after the offline queue is empty and respect the command_obj callback', function (done) { |
|||
clientMock.offline_queue.push(create_command_obj(), create_command_obj()); |
|||
utils.reply_in_order(clientMock, function () { |
|||
assert.strictEqual(clientMock.offline_queue.length, 0); |
|||
assert.strictEqual(res_count, 2); |
|||
done(); |
|||
}, null, null); |
|||
while (clientMock.offline_queue.length) clientMock.offline_queue.shift().callback(null, 'foo'); |
|||
}); |
|||
|
|||
it('elements in the offline queue. Reply after the offline queue is empty and respect the command_obj error emit', function (done) { |
|||
clientMock.command_queue.push({}, create_command_obj(), {}); |
|||
utils.reply_in_order(clientMock, function () { |
|||
assert.strictEqual(clientMock.command_queue.length, 0); |
|||
assert(emitted); |
|||
assert.strictEqual(err_count, 1); |
|||
assert.strictEqual(res_count, 0); |
|||
done(); |
|||
}, null, null); |
|||
while (clientMock.command_queue.length) { |
|||
var command_obj = clientMock.command_queue.shift(); |
|||
if (command_obj.callback) { |
|||
command_obj.callback(new Error('tada')); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
it('elements in the offline queue. Reply after the offline queue is empty and respect the command_obj', function (done) { |
|||
clientMock.command_queue.push(create_command_obj(), {}); |
|||
utils.reply_in_order(clientMock, function () { |
|||
assert.strictEqual(clientMock.command_queue.length, 0); |
|||
assert(!emitted); |
|||
assert.strictEqual(res_count, 1); |
|||
done(); |
|||
}, null, null); |
|||
while (clientMock.command_queue.length) { |
|||
clientMock.command_queue.shift().callback(null, 'bar'); |
|||
} |
|||
}); |
|||
}); |
|||
}); |
Loading…
Reference in new issue