You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-06 02:15:48 +03:00
Fix some minor issues and add more tests
Do not mutate the options object and add some more tests
This commit is contained in:
16
README.md
16
README.md
@@ -175,8 +175,7 @@ port and host are probably fine and you don't need to supply any arguments. `cre
|
||||
* `redis.createClient('redis://user:pass@host:port', options)`
|
||||
* `redis.createClient(port, host, options)`
|
||||
|
||||
`options` is an object with the following possible properties:
|
||||
|
||||
### `options` is an object with the following possible properties:
|
||||
* `host`: *127.0.0.1*; The host to connect to
|
||||
* `port`: *6370*; The port to connect to
|
||||
* `parser`: *hiredis*; Which Redis protocol reply parser to use. If `hiredis` is not installed it will fallback to `javascript`.
|
||||
@@ -229,7 +228,7 @@ client.get(new Buffer("foo_rand000000000000"), function (err, reply) {
|
||||
client.end();
|
||||
```
|
||||
|
||||
## client.auth(password, callback)
|
||||
## client.auth(password[, callback])
|
||||
|
||||
When connecting to a Redis server that requires authentication, the `AUTH` command must be sent as the
|
||||
first command after connecting. This can be tricky to coordinate with reconnections, the ready check,
|
||||
@@ -290,7 +289,7 @@ client.get("foo", function (err, value){
|
||||
Most Redis commands take a single String or an Array of Strings as arguments, and replies are sent back as a single String or an Array of Strings.
|
||||
When dealing with hash values, there are a couple of useful exceptions to this.
|
||||
|
||||
### client.hgetall(hash)
|
||||
### client.hgetall(hash, callback)
|
||||
|
||||
The reply from an HGETALL command will be converted into a JavaScript Object by `node_redis`. That way you can interact
|
||||
with the responses using JavaScript syntax.
|
||||
@@ -310,7 +309,7 @@ Output:
|
||||
{ mjr: '1', another: '23', home: '1234' }
|
||||
```
|
||||
|
||||
### client.hmset(hash, obj, [callback])
|
||||
### client.hmset(hash, obj[, callback])
|
||||
|
||||
Multiple values in a hash can be set by supplying an object:
|
||||
|
||||
@@ -440,7 +439,7 @@ client.multi()
|
||||
});
|
||||
```
|
||||
|
||||
### Multi.exec( callback )
|
||||
### Multi.exec([callback])
|
||||
|
||||
`client.multi()` is a constructor that returns a `Multi` object. `Multi` objects share all of the
|
||||
same command methods as `client` objects do. Commands are queued up inside the `Multi` object
|
||||
@@ -492,7 +491,7 @@ client.multi([
|
||||
});
|
||||
```
|
||||
|
||||
### Multi.exec_atomic( callback )
|
||||
### Multi.exec_atomic([callback])
|
||||
|
||||
Identical to Multi.exec but with the difference that executing a single command will not use transactions.
|
||||
|
||||
@@ -577,7 +576,7 @@ the second word as first parameter:
|
||||
client.multi().script('load', 'return 1').exec(...);
|
||||
client.multi([['script', 'load', 'return 1']]).exec(...);
|
||||
|
||||
## client.send_command(command_name, args, callback)
|
||||
## client.send_command(command_name[, [args][, callback]])
|
||||
|
||||
Used internally to send commands to Redis. Nearly all Redis commands have been added to the `client` object.
|
||||
However, if new commands are introduced before this library is updated, you can use `send_command()` to send arbitrary commands to Redis.
|
||||
@@ -713,6 +712,7 @@ To get debug output run your `node_redis` application with `NODE_DEBUG=redis`.
|
||||
|
||||
## How to Contribute
|
||||
- Open a pull request or an issue about what you want to implement / change. We're glad for any help!
|
||||
- Please be aware that we'll only accept fully tested code.
|
||||
|
||||
## Contributors
|
||||
Many [people](https://github.com/NodeRedis/node_redis/graphs/contributors) have have added features and fixed bugs in `node_redis`. Thanks to all of them!
|
||||
|
@@ -10,6 +10,9 @@ var metrics = require('metrics');
|
||||
var num_clients = parseInt(process.argv[2], 10) || 5;
|
||||
var num_requests = 50000;
|
||||
var tests = [];
|
||||
// var bluebird = require('bluebird');
|
||||
// bluebird.promisifyAll(redis.RedisClient.prototype);
|
||||
// bluebird.promisifyAll(redis.Multi.prototype);
|
||||
var versions_logged = false;
|
||||
var client_options = {
|
||||
return_buffers: false,
|
||||
|
19
changelog.md
19
changelog.md
@@ -1,17 +1,28 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
## v.2.2.4 - 17 Oct, 2015
|
||||
|
||||
Bugfixes
|
||||
|
||||
- Fixed unspecific error message for unresolvable commands ([@BridgeAR](https://github.com/BridgeAR))
|
||||
- Fixed not allowed command error in pubsub mode not being returned in a provided callback ([@BridgeAR](https://github.com/BridgeAR))
|
||||
- Fixed to many commands forbidden in pub sub mode ([@BridgeAR](https://github.com/BridgeAR))
|
||||
- Fixed mutation of the arguments array passed to .multi / .batch constructor ([@BridgeAR](https://github.com/BridgeAR))
|
||||
- Fixed mutation of the options object passed to createClient ([@BridgeAR](https://github.com/BridgeAR))
|
||||
- Fixed error callback in .multi not called if connection in broken mode ([@BridgeAR](https://github.com/BridgeAR))
|
||||
|
||||
## v.2.2.3 - 14 Oct, 2015
|
||||
|
||||
Bugfixes
|
||||
|
||||
- Fix multi not being executed on Node 0.10.x if node_redis not yet ready ([@BridgeAR](https://github.com/BridgeAR))
|
||||
- Fixed multi not being executed on Node 0.10.x if node_redis not yet ready ([@BridgeAR](https://github.com/BridgeAR))
|
||||
|
||||
## v.2.2.2 - 14 Oct, 2015
|
||||
|
||||
Bugfixes
|
||||
|
||||
- Fix regular commands not being executed after a .multi until .exec was called ([@BridgeAR](https://github.com/BridgeAR))
|
||||
- Fixed regular commands not being executed after a .multi until .exec was called ([@BridgeAR](https://github.com/BridgeAR))
|
||||
|
||||
## v.2.2.1 - 12 Oct, 2015
|
||||
|
||||
@@ -34,7 +45,7 @@ Features
|
||||
|
||||
Bugfixes
|
||||
|
||||
- Fix a javascript parser regression introduced in 2.0 that could result in timeouts on high load. ([@BridgeAR](https://github.com/BridgeAR))
|
||||
- Fixed a javascript parser regression introduced in 2.0 that could result in timeouts on high load. ([@BridgeAR](https://github.com/BridgeAR))
|
||||
- I was not able to write a regression test for this, since the error seems to only occur under heavy load with special conditions. So please have a look for timeouts with the js parser, if you use it and report all issues and switch to the hiredis parser in the meanwhile. If you're able to come up with a reproducable test case, this would be even better :)
|
||||
- Fixed should_buffer boolean for .exec, .select and .auth commands not being returned and fix a couple special conditions ([@BridgeAR](https://github.com/BridgeAR))
|
||||
|
||||
@@ -141,7 +152,7 @@ This is the biggest release that node_redis had since it was released in 2010. A
|
||||
- Do not wrap errors into other errors (@BridgeAR)
|
||||
- Authentication failures are now returned in the callback instead of being emitted (@BridgeAR)
|
||||
- Fix a memory leak on reconnect (@rahar)
|
||||
- Using `send_command` directly may no also be called without the args as stated in the [README.md](./README.md) (@BridgeAR)
|
||||
- Using `send_command` directly may now also be called without the args as stated in the [README.md](./README.md) (@BridgeAR)
|
||||
- Fix the multi.exec error handling (@BridgeAR)
|
||||
- Fix commands being inconsistent and behaving wrong (@BridgeAR)
|
||||
- Channel names with spaces are now properly resubscribed after a reconnection (@pbihler)
|
||||
|
128
index.js
128
index.js
@@ -20,6 +20,8 @@ var debug = function(msg) {
|
||||
}
|
||||
};
|
||||
|
||||
function noop () {}
|
||||
|
||||
exports.debug_mode = /\bredis\b/i.test(process.env.NODE_DEBUG);
|
||||
|
||||
// hiredis might not be installed
|
||||
@@ -33,24 +35,24 @@ try {
|
||||
parsers.push(require('./lib/parsers/javascript'));
|
||||
|
||||
function RedisClient(stream, options) {
|
||||
options = options || {};
|
||||
// Copy the options so they are not mutated
|
||||
options = JSON.parse(JSON.stringify(options));
|
||||
var self = this;
|
||||
|
||||
if (!stream.cork) {
|
||||
this.pipeline = 0;
|
||||
this.cork = function noop (len) {};
|
||||
this.cork = noop;
|
||||
this.once('ready', function () {
|
||||
self.cork = function (len) {
|
||||
self.pipeline = len;
|
||||
self.pipeline_queue = new Queue(len);
|
||||
};
|
||||
});
|
||||
stream.uncork = function noop() {};
|
||||
stream.uncork = noop;
|
||||
this.write = this.writeStream;
|
||||
}
|
||||
|
||||
this.stream = stream;
|
||||
this.options = options;
|
||||
this.connection_id = ++connection_id;
|
||||
this.connected = false;
|
||||
this.ready = false;
|
||||
@@ -62,26 +64,23 @@ function RedisClient(stream, options) {
|
||||
options.socket_keepalive = true;
|
||||
}
|
||||
if (options.rename_commands) {
|
||||
for (var command in options.rename_commands) {
|
||||
if (options.rename_commands.hasOwnProperty(command)) {
|
||||
options.rename_commands[command.toLowerCase()] = options.rename_commands[command];
|
||||
}
|
||||
for (var command in options.rename_commands) { // jshint ignore: line
|
||||
options.rename_commands[command.toLowerCase()] = options.rename_commands[command];
|
||||
}
|
||||
}
|
||||
this.options.return_buffers = !!this.options.return_buffers;
|
||||
this.options.detect_buffers = !!this.options.detect_buffers;
|
||||
options.return_buffers = !!options.return_buffers;
|
||||
options.detect_buffers = !!options.detect_buffers;
|
||||
// Override the detect_buffers setting if return_buffers is active and print a warning
|
||||
if (this.options.return_buffers && this.options.detect_buffers) {
|
||||
if (options.return_buffers && options.detect_buffers) {
|
||||
console.warn('>> WARNING: You activated return_buffers and detect_buffers at the same time. The return value is always going to be a buffer.');
|
||||
this.options.detect_buffers = false;
|
||||
options.detect_buffers = false;
|
||||
}
|
||||
this.should_buffer = false;
|
||||
this.command_queue_high_water = options.command_queue_high_water || 1000;
|
||||
this.command_queue_low_water = options.command_queue_low_water || 0;
|
||||
this.max_attempts = +options.max_attempts || 0;
|
||||
this.command_queue_high_water = +options.command_queue_high_water || 1000;
|
||||
this.command_queue_low_water = options.command_queue_low_water | 0;
|
||||
this.max_attempts = options.max_attempts | 0;
|
||||
this.command_queue = new Queue(); // Holds sent commands to de-pipeline them
|
||||
this.offline_queue = new Queue(); // Holds commands issued but not able to be sent
|
||||
this.commands_sent = 0;
|
||||
this.connect_timeout = +options.connect_timeout || 86400000; // 24 * 60 * 60 * 1000 ms
|
||||
this.enable_offline_queue = options.enable_offline_queue === false ? false : true;
|
||||
this.retry_max_delay = +options.retry_max_delay || null;
|
||||
@@ -95,6 +94,7 @@ function RedisClient(stream, options) {
|
||||
this.parser_module = null;
|
||||
this.selected_db = null; // Save the selected db here, used when reconnecting
|
||||
this.old_state = null;
|
||||
this.options = options;
|
||||
|
||||
this.install_stream_listeners();
|
||||
events.EventEmitter.call(this);
|
||||
@@ -118,17 +118,16 @@ RedisClient.prototype.install_stream_listeners = function() {
|
||||
self.on_error(err);
|
||||
});
|
||||
|
||||
this.stream.on('close', function () {
|
||||
this.stream.once('close', function () {
|
||||
self.connection_gone('close');
|
||||
});
|
||||
|
||||
this.stream.on('end', function () {
|
||||
this.stream.once('end', function () {
|
||||
self.connection_gone('end');
|
||||
});
|
||||
|
||||
this.stream.on('drain', function () {
|
||||
self.should_buffer = false;
|
||||
self.emit('drain');
|
||||
self.drain();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -450,11 +449,6 @@ RedisClient.prototype.send_offline_queue = function () {
|
||||
}
|
||||
// Even though items were shifted off, Queue backing store still uses memory until next add, so just get a new Queue
|
||||
this.offline_queue = new Queue();
|
||||
|
||||
if (buffered_writes === 0) {
|
||||
this.should_buffer = false;
|
||||
this.emit('drain');
|
||||
}
|
||||
};
|
||||
|
||||
var retry_connection = function (self) {
|
||||
@@ -558,6 +552,11 @@ RedisClient.prototype.return_error = function (err) {
|
||||
}
|
||||
};
|
||||
|
||||
RedisClient.prototype.drain = function () {
|
||||
this.emit('drain');
|
||||
this.should_buffer = false;
|
||||
};
|
||||
|
||||
RedisClient.prototype.emit_drain_idle = function (queue_len) {
|
||||
if (this.pub_sub_mode === false && queue_len === 0) {
|
||||
// Free the queue capacity memory by using a new queue
|
||||
@@ -566,8 +565,7 @@ RedisClient.prototype.emit_drain_idle = function (queue_len) {
|
||||
}
|
||||
|
||||
if (this.should_buffer && queue_len <= this.command_queue_low_water) {
|
||||
this.emit('drain');
|
||||
this.should_buffer = false;
|
||||
this.drain();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -688,11 +686,7 @@ RedisClient.prototype.send_command = function (command, args, callback) {
|
||||
command = command.toUpperCase();
|
||||
err = new Error('send_command: ' + command + ' value must not be undefined or null');
|
||||
err.command = command;
|
||||
if (callback) {
|
||||
callback(err);
|
||||
} else {
|
||||
this.emit('error', err);
|
||||
}
|
||||
this.callback_emit_error(callback, err);
|
||||
// Singal no buffering
|
||||
return true;
|
||||
}
|
||||
@@ -714,16 +708,15 @@ RedisClient.prototype.send_command = function (command, args, callback) {
|
||||
if (this.closing || !this.enable_offline_queue) {
|
||||
command = command.toUpperCase();
|
||||
if (!this.closing) {
|
||||
err = new Error(command + " can't be processed. Stream not writeable and enable_offline_queue is deactivated.");
|
||||
var msg = !stream.writable ?
|
||||
'Stream not writeable.' :
|
||||
'The connection is not yet established and the offline queue is deactivated.';
|
||||
err = new Error(command + " can't be processed. " + msg);
|
||||
} else {
|
||||
err = new Error(command + " can't be processed. The connection has already been closed.");
|
||||
}
|
||||
err.command = command;
|
||||
if (callback) {
|
||||
callback(err);
|
||||
} else {
|
||||
this.emit('error', err);
|
||||
}
|
||||
this.callback_emit_error(callback, err);
|
||||
} else {
|
||||
debug('Queueing ' + command + ' for next server connection.');
|
||||
this.offline_queue.push(command_obj);
|
||||
@@ -739,14 +732,8 @@ RedisClient.prototype.send_command = function (command, args, callback) {
|
||||
this.monitoring = true;
|
||||
} else if (command === 'quit') {
|
||||
this.closing = true;
|
||||
} else if (this.pub_sub_mode === true) {
|
||||
err = new Error('Connection in subscriber mode, only subscriber commands may be used');
|
||||
err.command = command.toUpperCase();
|
||||
this.emit('error', err);
|
||||
return true;
|
||||
}
|
||||
this.command_queue.push(command_obj);
|
||||
this.commands_sent += 1;
|
||||
|
||||
if (typeof this.options.rename_commands !== 'undefined' && this.options.rename_commands[command]) {
|
||||
command = this.options.rename_commands[command];
|
||||
@@ -864,7 +851,7 @@ RedisClient.prototype.end = function (flush) {
|
||||
clearTimeout(this.retry_timer);
|
||||
this.retry_timer = null;
|
||||
}
|
||||
this.stream.on('error', function noop(){});
|
||||
this.stream.on('error', noop);
|
||||
|
||||
// Flush queue if wanted
|
||||
if (flush) {
|
||||
@@ -882,9 +869,9 @@ function Multi(client, args) {
|
||||
this.queue = new Queue();
|
||||
var command, tmp_args;
|
||||
if (Array.isArray(args)) {
|
||||
while (tmp_args = args.shift()) {
|
||||
command = tmp_args[0];
|
||||
tmp_args = tmp_args.slice(1);
|
||||
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 {
|
||||
@@ -972,18 +959,22 @@ RedisClient.prototype.select = RedisClient.prototype.SELECT = function (db, call
|
||||
});
|
||||
};
|
||||
|
||||
RedisClient.prototype.callback_emit_error = function (callback, err) {
|
||||
if (callback) {
|
||||
setImmediate(function () {
|
||||
callback(err);
|
||||
});
|
||||
} else {
|
||||
this.emit('error', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Stash auth for connect and reconnect. Send immediately if already connected.
|
||||
RedisClient.prototype.auth = RedisClient.prototype.AUTH = function (pass, callback) {
|
||||
if (typeof pass !== 'string') {
|
||||
var err = new Error('The password has to be of type "string"');
|
||||
err.command = 'AUTH';
|
||||
if (callback) {
|
||||
setImmediate(function () {
|
||||
callback(err);
|
||||
});
|
||||
} else {
|
||||
this.emit('error', err);
|
||||
}
|
||||
this.callback_emit_error(callback, err);
|
||||
return true;
|
||||
}
|
||||
this.auth_pass = pass;
|
||||
@@ -1121,14 +1112,13 @@ Multi.prototype.execute_callback = function (err, replies) {
|
||||
var i = 0, args;
|
||||
|
||||
if (err) {
|
||||
if (err.code !== 'CONNECTION_BROKEN') {
|
||||
err.errors = this.errors;
|
||||
if (this.callback) {
|
||||
this.callback(err);
|
||||
} else {
|
||||
// Exclude CONNECTION_BROKEN so that error won't be emitted twice
|
||||
this._client.emit('error', err);
|
||||
}
|
||||
// The errors would be circular
|
||||
err.errors = err.code !== 'CONNECTION_BROKEN' ? this.errors : [];
|
||||
if (this.callback) {
|
||||
this.callback(err);
|
||||
} else if (err.code !== 'CONNECTION_BROKEN') {
|
||||
// Exclude CONNECTION_BROKEN so that error won't be emitted twice
|
||||
this._client.emit('error', err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1169,7 +1159,7 @@ Multi.prototype.execute_callback = function (err, replies) {
|
||||
}
|
||||
};
|
||||
|
||||
Multi.prototype.callback = function (cb, command, i) {
|
||||
Multi.prototype.callback = function (cb, i) {
|
||||
var self = this;
|
||||
return function (err, res) {
|
||||
if (err) {
|
||||
@@ -1210,9 +1200,9 @@ Multi.prototype.exec = Multi.prototype.EXEC = Multi.prototype.exec_batch = funct
|
||||
var command = args.shift();
|
||||
var cb;
|
||||
if (typeof args[args.length - 1] === 'function') {
|
||||
cb = this.callback(args.pop(), command, index);
|
||||
cb = this.callback(args.pop(), index);
|
||||
} else {
|
||||
cb = this.callback(undefined, command, index);
|
||||
cb = this.callback(undefined, index);
|
||||
}
|
||||
if (callback && index === len - 1) {
|
||||
cb = lastCallback(cb);
|
||||
@@ -1241,7 +1231,7 @@ var createClient_tcp = function (port_arg, host_arg, options) {
|
||||
var cnxOptions = {
|
||||
port : port_arg || default_port,
|
||||
host : host_arg || default_host,
|
||||
family : options && options.family === 'IPv6' ? 6 : 4
|
||||
family : options.family === 'IPv6' ? 6 : 4
|
||||
};
|
||||
var net_client = net.createConnection(cnxOptions);
|
||||
var redis_client = new RedisClient(net_client, options);
|
||||
@@ -1258,10 +1248,10 @@ var createClient = function (port_arg, host_arg, options) {
|
||||
return createClient_tcp(+options.port, options.host, options);
|
||||
}
|
||||
if (typeof port_arg === 'number' || typeof port_arg === 'string' && /^\d+$/.test(port_arg)) {
|
||||
return createClient_tcp(port_arg, host_arg, options);
|
||||
return createClient_tcp(port_arg, host_arg, options || {});
|
||||
}
|
||||
if (typeof port_arg === 'string') {
|
||||
options = host_arg || {};
|
||||
options = host_arg || options || {};
|
||||
|
||||
var parsed = URL.parse(port_arg, true, true);
|
||||
if (parsed.hostname) {
|
||||
|
@@ -2,12 +2,10 @@
|
||||
|
||||
var util = require('util');
|
||||
|
||||
function ReplyParser() {
|
||||
function JavascriptReplyParser() {
|
||||
this.name = exports.name;
|
||||
|
||||
this._buffer = new Buffer(0);
|
||||
this._offset = 0;
|
||||
this._encoding = 'utf-8';
|
||||
}
|
||||
|
||||
function IncompleteReadBuffer(message) {
|
||||
@@ -16,13 +14,13 @@ function IncompleteReadBuffer(message) {
|
||||
}
|
||||
util.inherits(IncompleteReadBuffer, Error);
|
||||
|
||||
ReplyParser.prototype._parseResult = function (type) {
|
||||
JavascriptReplyParser.prototype._parseResult = function (type) {
|
||||
var start = 0,
|
||||
end = 0,
|
||||
offset = 0,
|
||||
packetHeader = 0;
|
||||
|
||||
if (type === 43 || type === 45) { // + or -
|
||||
if (type === 43 || type === 58 || type === 45) { // + or : or -
|
||||
// up to the delimiter
|
||||
end = this._packetEndOffset() - 1;
|
||||
start = this._offset;
|
||||
@@ -34,24 +32,13 @@ ReplyParser.prototype._parseResult = function (type) {
|
||||
// include the delimiter
|
||||
this._offset = end + 2;
|
||||
|
||||
if (type === 45) {
|
||||
return new Error(this._buffer.toString(this._encoding, start, end));
|
||||
if (type === 43) {
|
||||
return this._buffer.slice(start, end);
|
||||
} else if (type === 58) {
|
||||
// return the coerced numeric value
|
||||
return +this._buffer.toString('ascii', start, end);
|
||||
}
|
||||
return this._buffer.slice(start, end);
|
||||
} else if (type === 58) { // :
|
||||
// up to the delimiter
|
||||
end = this._packetEndOffset() - 1;
|
||||
start = this._offset;
|
||||
|
||||
if (end > this._buffer.length) {
|
||||
throw new IncompleteReadBuffer('Wait for more data.');
|
||||
}
|
||||
|
||||
// include the delimiter
|
||||
this._offset = end + 2;
|
||||
|
||||
// return the coerced numeric value
|
||||
return +this._buffer.toString('ascii', start, end);
|
||||
return new Error(this._buffer.toString('utf-8', start, end));
|
||||
} else if (type === 36) { // $
|
||||
// set a rewind point, as the packet could be larger than the
|
||||
// buffer in memory
|
||||
@@ -107,7 +94,7 @@ ReplyParser.prototype._parseResult = function (type) {
|
||||
}
|
||||
};
|
||||
|
||||
ReplyParser.prototype.execute = function (buffer) {
|
||||
JavascriptReplyParser.prototype.execute = function (buffer) {
|
||||
this.append(buffer);
|
||||
|
||||
var type, ret, offset;
|
||||
@@ -161,7 +148,7 @@ ReplyParser.prototype.execute = function (buffer) {
|
||||
}
|
||||
};
|
||||
|
||||
ReplyParser.prototype.append = function (newBuffer) {
|
||||
JavascriptReplyParser.prototype.append = function (newBuffer) {
|
||||
|
||||
// out of data
|
||||
if (this._offset >= this._buffer.length) {
|
||||
@@ -174,7 +161,7 @@ ReplyParser.prototype.append = function (newBuffer) {
|
||||
this._offset = 0;
|
||||
};
|
||||
|
||||
ReplyParser.prototype.parseHeader = function () {
|
||||
JavascriptReplyParser.prototype.parseHeader = function () {
|
||||
var end = this._packetEndOffset(),
|
||||
value = this._buffer.toString('ascii', this._offset, end - 1) | 0;
|
||||
|
||||
@@ -183,7 +170,7 @@ ReplyParser.prototype.parseHeader = function () {
|
||||
return value;
|
||||
};
|
||||
|
||||
ReplyParser.prototype._packetEndOffset = function () {
|
||||
JavascriptReplyParser.prototype._packetEndOffset = function () {
|
||||
var offset = this._offset;
|
||||
|
||||
while (this._buffer[offset] !== 0x0d && this._buffer[offset + 1] !== 0x0a) {
|
||||
@@ -199,9 +186,9 @@ ReplyParser.prototype._packetEndOffset = function () {
|
||||
return offset;
|
||||
};
|
||||
|
||||
ReplyParser.prototype._bytesRemaining = function () {
|
||||
JavascriptReplyParser.prototype._bytesRemaining = function () {
|
||||
return (this._buffer.length - this._offset) < 0 ? 0 : (this._buffer.length - this._offset);
|
||||
};
|
||||
|
||||
exports.Parser = ReplyParser;
|
||||
exports.Parser = JavascriptReplyParser;
|
||||
exports.name = 'javascript';
|
||||
|
@@ -4,7 +4,9 @@
|
||||
"description": "Redis client library",
|
||||
"keywords": [
|
||||
"database",
|
||||
"redis"
|
||||
"redis",
|
||||
"transaction",
|
||||
"pipelining"
|
||||
],
|
||||
"author": "Matt Ranney <mjr@ranney.com>",
|
||||
"license": "MIT",
|
||||
@@ -13,13 +15,16 @@
|
||||
"coveralls": "nyc report --reporter=text-lcov | coveralls",
|
||||
"coverage": "nyc report --reporter=html",
|
||||
"benchmark": "node benchmarks/multi_bench.js",
|
||||
"test": "nyc ./node_modules/.bin/_mocha ./test/*.js ./test/commands/*.js ./test/parser/*.js --timeout=8000",
|
||||
"test": "nyc ./node_modules/.bin/_mocha ./test/*.js ./test/commands/*.js --timeout=8000",
|
||||
"pretest": "optional-dev-dependency hiredis",
|
||||
"posttest": "jshint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"double-ended-queue": "^2.1.0-0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"coveralls": "^2.11.2",
|
||||
"jshint": "^2.8.0",
|
||||
|
@@ -31,7 +31,7 @@ describe("The 'expire' method", function () {
|
||||
client.EXPIRE(["expiry key", "1"], helper.isNumber(1));
|
||||
setTimeout(function () {
|
||||
client.exists(["expiry key"], helper.isNumber(0, done));
|
||||
}, 3000);
|
||||
}, 1100);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
|
@@ -37,6 +37,12 @@ describe("The 'get' method", function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("reports an error promisified", function () {
|
||||
return client.getAsync(key).then(assert, function (err) {
|
||||
assert(err.message.match(/The connection has already been closed/));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when connected", function () {
|
||||
@@ -76,7 +82,7 @@ describe("The 'get' method", function () {
|
||||
}]);
|
||||
});
|
||||
|
||||
it("should not throw on a get without callback (even if it's not useful", function (done) {
|
||||
it("should not throw on a get without callback (even if it's not useful)", function (done) {
|
||||
client.GET(key);
|
||||
client.on('error', function(err) {
|
||||
throw err;
|
||||
|
@@ -67,6 +67,50 @@ describe("The 'multi' method", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when connection is broken", function () {
|
||||
var client;
|
||||
|
||||
afterEach(function () {
|
||||
client.end();
|
||||
});
|
||||
|
||||
it("return an error even if connection is in broken mode if callback is present", function (done) {
|
||||
client = redis.createClient({
|
||||
host: 'somewhere',
|
||||
port: 6379,
|
||||
max_attempts: 1
|
||||
});
|
||||
|
||||
client.on('error', function(err) {
|
||||
if (/Redis connection in broken state/.test(err.message)) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec(function (err, res) {
|
||||
assert(/Redis connection in broken state/.test(err.message));
|
||||
assert.strictEqual(err.errors.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
it("does not emit an error twice if connection is in broken mode with no callback", function (done) {
|
||||
client = redis.createClient({
|
||||
host: 'somewhere',
|
||||
port: 6379,
|
||||
max_attempts: 1
|
||||
});
|
||||
|
||||
client.on('error', function(err) {
|
||||
// Results in multiple done calls if test fails
|
||||
if (/Redis connection in broken state/.test(err.message)) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when ready", function () {
|
||||
var client;
|
||||
|
||||
@@ -481,6 +525,18 @@ describe("The 'multi' method", function () {
|
||||
assert(!test);
|
||||
});
|
||||
|
||||
it("do not mutate arguments in the multi constructor", function (done) {
|
||||
helper.serverVersionAtLeast.call(this, client, [2, 6, 5]);
|
||||
|
||||
var input = [['set', 'foo', 'bar'], ['get', 'foo']];
|
||||
client.multi(input).exec(function (err, res) {
|
||||
assert.strictEqual(input.length, 2);
|
||||
assert.strictEqual(input[0].length, 3);
|
||||
assert.strictEqual(input[1].length, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -5,106 +5,247 @@ var config = require("./lib/config");
|
||||
var helper = require('./helper');
|
||||
var redis = config.redis;
|
||||
|
||||
describe("on lost connection", function () {
|
||||
describe("connection tests", function () {
|
||||
helper.allTests(function(parser, ip, args) {
|
||||
|
||||
describe("using " + parser + " and " + ip, function () {
|
||||
|
||||
it("emit an error after max retry attempts and do not try to reconnect afterwards", function (done) {
|
||||
var max_attempts = 4;
|
||||
var client = redis.createClient({
|
||||
parser: parser,
|
||||
max_attempts: max_attempts
|
||||
});
|
||||
var calls = 0;
|
||||
var client;
|
||||
|
||||
client.once('ready', function() {
|
||||
helper.killConnection(client);
|
||||
});
|
||||
|
||||
client.on("reconnecting", function (params) {
|
||||
calls++;
|
||||
});
|
||||
|
||||
client.on('error', function(err) {
|
||||
if (/Redis connection in broken state: maximum connection attempts.*?exceeded./.test(err.message)) {
|
||||
setTimeout(function () {
|
||||
assert.strictEqual(calls, max_attempts - 1);
|
||||
done();
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("emit an error after max retry timeout and do not try to reconnect afterwards", function (done) {
|
||||
var connect_timeout = 1000; // in ms
|
||||
var client = redis.createClient({
|
||||
parser: parser,
|
||||
connect_timeout: connect_timeout
|
||||
});
|
||||
var time = 0;
|
||||
|
||||
client.once('ready', function() {
|
||||
helper.killConnection(client);
|
||||
});
|
||||
|
||||
client.on("reconnecting", function (params) {
|
||||
time += params.delay;
|
||||
});
|
||||
|
||||
client.on('error', function(err) {
|
||||
if (/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message)) {
|
||||
setTimeout(function () {
|
||||
assert(time === connect_timeout);
|
||||
done();
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("end connection while retry is still ongoing", function (done) {
|
||||
var connect_timeout = 1000; // in ms
|
||||
var client = redis.createClient({
|
||||
parser: parser,
|
||||
connect_timeout: connect_timeout
|
||||
});
|
||||
|
||||
client.once('ready', function() {
|
||||
helper.killConnection(client);
|
||||
});
|
||||
|
||||
client.on("reconnecting", function (params) {
|
||||
afterEach(function () {
|
||||
if (client) {
|
||||
client.end();
|
||||
setTimeout(done, 100);
|
||||
}
|
||||
});
|
||||
|
||||
describe("on lost connection", function () {
|
||||
it("emit an error after max retry attempts and do not try to reconnect afterwards", function (done) {
|
||||
var max_attempts = 4;
|
||||
var options = {
|
||||
parser: parser,
|
||||
max_attempts: max_attempts
|
||||
};
|
||||
client = redis.createClient(options);
|
||||
assert.strictEqual(Object.keys(options).length, 2);
|
||||
var calls = 0;
|
||||
|
||||
client.once('ready', function() {
|
||||
helper.killConnection(client);
|
||||
});
|
||||
|
||||
client.on("reconnecting", function (params) {
|
||||
calls++;
|
||||
});
|
||||
|
||||
client.on('error', function(err) {
|
||||
if (/Redis connection in broken state: maximum connection attempts.*?exceeded./.test(err.message)) {
|
||||
setTimeout(function () {
|
||||
assert.strictEqual(calls, max_attempts - 1);
|
||||
done();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("emit an error after max retry timeout and do not try to reconnect afterwards", function (done) {
|
||||
var connect_timeout = 500; // in ms
|
||||
client = redis.createClient({
|
||||
parser: parser,
|
||||
connect_timeout: connect_timeout
|
||||
});
|
||||
var time = 0;
|
||||
|
||||
client.once('ready', function() {
|
||||
helper.killConnection(client);
|
||||
});
|
||||
|
||||
client.on("reconnecting", function (params) {
|
||||
time += params.delay;
|
||||
});
|
||||
|
||||
client.on('error', function(err) {
|
||||
if (/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message)) {
|
||||
setTimeout(function () {
|
||||
assert(time === connect_timeout);
|
||||
done();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("end connection while retry is still ongoing", function (done) {
|
||||
var connect_timeout = 1000; // in ms
|
||||
client = redis.createClient({
|
||||
parser: parser,
|
||||
connect_timeout: connect_timeout
|
||||
});
|
||||
|
||||
client.once('ready', function() {
|
||||
helper.killConnection(client);
|
||||
});
|
||||
|
||||
client.on("reconnecting", function (params) {
|
||||
client.end();
|
||||
setTimeout(done, 100);
|
||||
});
|
||||
});
|
||||
|
||||
it("can not connect with wrong host / port in the options object", function (done) {
|
||||
var options = {
|
||||
host: 'somewhere',
|
||||
port: 6379,
|
||||
max_attempts: 1
|
||||
};
|
||||
client = redis.createClient(options);
|
||||
assert.strictEqual(Object.keys(options).length, 3);
|
||||
var end = helper.callFuncAfter(done, 2);
|
||||
|
||||
client.on('error', function (err) {
|
||||
assert(/CONNECTION_BROKEN|ENOTFOUND|EAI_AGAIN/.test(err.code));
|
||||
end();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it("can not connect with wrong host / port in the options object", function (done) {
|
||||
var client = redis.createClient({
|
||||
host: 'somewhere',
|
||||
port: 6379,
|
||||
max_attempts: 1
|
||||
});
|
||||
var end = helper.callFuncAfter(done, 2);
|
||||
describe("when not connected", function () {
|
||||
|
||||
client.on('error', function (err) {
|
||||
assert(/CONNECTION_BROKEN|ENOTFOUND|EAI_AGAIN/.test(err.code));
|
||||
end();
|
||||
it("connect with host and port provided in the options object", function (done) {
|
||||
client = redis.createClient({
|
||||
host: 'localhost',
|
||||
port: '6379',
|
||||
parser: parser,
|
||||
connect_timeout: 1000
|
||||
});
|
||||
|
||||
client.once('ready', function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
it("connects correctly with args", function (done) {
|
||||
client = redis.createClient.apply(redis.createClient, args);
|
||||
client.on("error", done);
|
||||
|
||||
it("connect with host and port provided in the options object", function (done) {
|
||||
var client = redis.createClient({
|
||||
host: 'localhost',
|
||||
port: '6379',
|
||||
parser: parser,
|
||||
connect_timeout: 1000
|
||||
client.once("ready", function () {
|
||||
client.removeListener("error", done);
|
||||
client.get("recon 1", function (err, res) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
client.once('ready', function() {
|
||||
done();
|
||||
it("connects correctly with default values", function (done) {
|
||||
client = redis.createClient();
|
||||
client.on("error", done);
|
||||
|
||||
client.once("ready", function () {
|
||||
client.removeListener("error", done);
|
||||
client.get("recon 1", function (err, res) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("connects with a port only", function (done) {
|
||||
client = redis.createClient(6379);
|
||||
client.on("error", done);
|
||||
|
||||
client.once("ready", function () {
|
||||
client.removeListener("error", done);
|
||||
client.get("recon 1", function (err, res) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("connects correctly to localhost", function (done) {
|
||||
client = redis.createClient(null, null);
|
||||
client.on("error", done);
|
||||
|
||||
client.once("ready", function () {
|
||||
client.removeListener("error", done);
|
||||
client.get("recon 1", function (err, res) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("connects correctly to localhost and no ready check", function (done) {
|
||||
client = redis.createClient(undefined, undefined, {
|
||||
no_ready_check: true
|
||||
});
|
||||
client.on("error", done);
|
||||
|
||||
client.once("ready", function () {
|
||||
client.set('foo', 'bar');
|
||||
client.get('foo', function(err, res) {
|
||||
assert.strictEqual(res, 'bar');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("buffer commands and flush them after ", function (done) {
|
||||
client = redis.createClient(9999, null, {
|
||||
parser: parser
|
||||
});
|
||||
|
||||
client.on('error', function(e) {
|
||||
// ignore, b/c expecting a "can't connect" error
|
||||
});
|
||||
|
||||
return setTimeout(function() {
|
||||
client.set('foo', 'bar', function(err, result) {
|
||||
// This should never be called
|
||||
return done(err);
|
||||
});
|
||||
|
||||
return setTimeout(function() {
|
||||
assert.strictEqual(client.offline_queue.length, 1);
|
||||
return done();
|
||||
}, 25);
|
||||
}, 50);
|
||||
});
|
||||
|
||||
it("throws on strange connection info", function () {
|
||||
try {
|
||||
redis.createClient(true);
|
||||
throw new Error('failed');
|
||||
} catch (err) {
|
||||
assert.equal(err.message, 'Unknown type of connection in createClient()');
|
||||
}
|
||||
});
|
||||
|
||||
if (ip === 'IPv4') {
|
||||
it('allows connecting with the redis url and the default port', function (done) {
|
||||
client = redis.createClient('redis://foo:porkchopsandwiches@' + config.HOST[ip]);
|
||||
client.on("ready", function () {
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows connecting with the redis url and no auth and options as second parameter', function (done) {
|
||||
var options = {
|
||||
detect_buffers: false
|
||||
};
|
||||
client = redis.createClient('redis://' + config.HOST[ip] + ':' + config.PORT, options);
|
||||
assert.strictEqual(Object.keys(options).length, 1);
|
||||
client.on("ready", function () {
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows connecting with the redis url and no auth and options as third parameter', function (done) {
|
||||
client = redis.createClient('redis://' + config.HOST[ip] + ':' + config.PORT, null, {
|
||||
detect_buffers: false
|
||||
});
|
||||
client.on("ready", function () {
|
||||
return done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -122,9 +122,9 @@ module.exports = {
|
||||
}
|
||||
var options = [{
|
||||
detect_buffers: true
|
||||
// Somehow we need a undefined here - otherwise the parsers return_buffers value is always true
|
||||
// Investigate this further
|
||||
}, undefined];
|
||||
}, {
|
||||
detect_buffers: false
|
||||
}];
|
||||
options.forEach(function (options) {
|
||||
var strOptions = '';
|
||||
var key;
|
||||
|
@@ -8,132 +8,41 @@ var redis = config.redis;
|
||||
|
||||
describe("The node_redis client", function () {
|
||||
|
||||
describe("testing parser existence", function () {
|
||||
it('throws on non-existence', function (done) {
|
||||
var mochaListener = helper.removeMochaListener();
|
||||
|
||||
process.once('uncaughtException', function (err) {
|
||||
process.on('uncaughtException', mochaListener);
|
||||
assert.equal(err.message, 'Couldn\'t find named parser nonExistingParser on this system');
|
||||
return done();
|
||||
});
|
||||
|
||||
redis.createClient({
|
||||
parser: 'nonExistingParser'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
helper.allTests({
|
||||
allConnections: true
|
||||
}, function(parser, ip, args) {
|
||||
|
||||
if (args[2]) { // skip if options are undefined
|
||||
describe("testing parser existence", function () {
|
||||
it('throws on non-existence', function (done) {
|
||||
var mochaListener = helper.removeMochaListener();
|
||||
|
||||
process.once('uncaughtException', function (err) {
|
||||
process.on('uncaughtException', mochaListener);
|
||||
assert.equal(err.message, 'Couldn\'t find named parser nonExistingParser on this system');
|
||||
return done();
|
||||
});
|
||||
|
||||
// Don't pollute the args for the other connections
|
||||
var tmp = JSON.parse(JSON.stringify(args));
|
||||
tmp[2].parser = 'nonExistingParser';
|
||||
redis.createClient.apply(redis.createClient, tmp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("using " + parser + " and " + ip, function () {
|
||||
var client;
|
||||
|
||||
describe("when not connected", function () {
|
||||
afterEach(function () {
|
||||
if (client) {
|
||||
client.end();
|
||||
}
|
||||
});
|
||||
|
||||
it("connects correctly with args", function (done) {
|
||||
client = redis.createClient.apply(redis.createClient, args);
|
||||
client.on("error", done);
|
||||
|
||||
client.once("ready", function () {
|
||||
client.removeListener("error", done);
|
||||
client.get("recon 1", function (err, res) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("connects correctly with default values", function (done) {
|
||||
client = redis.createClient();
|
||||
client.on("error", done);
|
||||
|
||||
client.once("ready", function () {
|
||||
client.removeListener("error", done);
|
||||
client.get("recon 1", function (err, res) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("connects correctly to localhost", function (done) {
|
||||
client = redis.createClient(null, null);
|
||||
client.on("error", done);
|
||||
|
||||
client.once("ready", function () {
|
||||
client.removeListener("error", done);
|
||||
client.get("recon 1", function (err, res) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("connects correctly to localhost and no ready check", function (done) {
|
||||
client = redis.createClient(undefined, undefined, {
|
||||
no_ready_check: true
|
||||
});
|
||||
client.on("error", done);
|
||||
|
||||
client.once("ready", function () {
|
||||
client.set('foo', 'bar');
|
||||
client.get('foo', function(err, res) {
|
||||
assert.strictEqual(res, 'bar');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("throws on strange connection info", function () {
|
||||
try {
|
||||
redis.createClient(true);
|
||||
throw new Error('failed');
|
||||
} catch (err) {
|
||||
assert.equal(err.message, 'Unknown type of connection in createClient()');
|
||||
}
|
||||
});
|
||||
|
||||
if (ip === 'IPv4') {
|
||||
it('allows connecting with the redis url and the default port', function (done) {
|
||||
client = redis.createClient('redis://foo:porkchopsandwiches@' + config.HOST[ip]);
|
||||
client.on("ready", function () {
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows connecting with the redis url and no auth', function (done) {
|
||||
client = redis.createClient('redis://' + config.HOST[ip] + ':' + config.PORT, {
|
||||
detect_buffers: false
|
||||
});
|
||||
client.on("ready", function () {
|
||||
return done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(function () {
|
||||
client.end();
|
||||
});
|
||||
|
||||
describe("when connected", function () {
|
||||
beforeEach(function (done) {
|
||||
client = redis.createClient.apply(redis.createClient, args);
|
||||
client.once("error", done);
|
||||
client.once("connect", function () {
|
||||
client.flushdb(done);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
client.end();
|
||||
});
|
||||
|
||||
describe("send_command", function () {
|
||||
|
||||
it("omitting args should be fine in some cases", function (done) {
|
||||
@@ -211,7 +120,9 @@ describe("The node_redis client", function () {
|
||||
it("return an error in the callback", function (done) {
|
||||
if (helper.redisProcess().spawnFailed()) this.skip();
|
||||
|
||||
var client = redis.createClient();
|
||||
// TODO: Investigate why this test is failing hard and killing mocha if the client is created with .apply
|
||||
// Seems like something is wrong while passing a socket connection to create client! args[1]
|
||||
client = redis.createClient();
|
||||
client.quit(function() {
|
||||
client.get("foo", function(err, res) {
|
||||
assert(err.message.indexOf('Redis connection gone') !== -1);
|
||||
@@ -224,7 +135,6 @@ describe("The node_redis client", function () {
|
||||
it("return an error in the callback version two", function (done) {
|
||||
if (helper.redisProcess().spawnFailed()) this.skip();
|
||||
|
||||
var client = redis.createClient();
|
||||
client.quit();
|
||||
setTimeout(function() {
|
||||
client.get("foo", function(err, res) {
|
||||
@@ -239,7 +149,6 @@ describe("The node_redis client", function () {
|
||||
it("emit an error", function (done) {
|
||||
if (helper.redisProcess().spawnFailed()) this.skip();
|
||||
|
||||
var client = redis.createClient();
|
||||
client.quit();
|
||||
client.on('error', function(err) {
|
||||
assert.strictEqual(err.message, 'SET can\'t be processed. The connection has already been closed.');
|
||||
@@ -339,20 +248,19 @@ describe("The node_redis client", function () {
|
||||
return done();
|
||||
}
|
||||
|
||||
if (domain) {
|
||||
domain.run(function () {
|
||||
client.set('domain', 'value', function (err, res) {
|
||||
assert.ok(process.domain);
|
||||
throw new Error('ohhhh noooo');
|
||||
});
|
||||
domain.run(function () {
|
||||
client.set('domain', 'value', function (err, res) {
|
||||
assert.ok(process.domain);
|
||||
throw new Error('ohhhh noooo');
|
||||
});
|
||||
});
|
||||
|
||||
// this is the expected and desired behavior
|
||||
domain.on('error', function (err) {
|
||||
domain.exit();
|
||||
return done();
|
||||
});
|
||||
}
|
||||
// this is the expected and desired behavior
|
||||
domain.on('error', function (err) {
|
||||
assert.strictEqual(err.message, 'ohhhh noooo');
|
||||
domain.exit();
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -435,7 +343,6 @@ describe("The node_redis client", function () {
|
||||
|
||||
describe('socket_nodelay', function () {
|
||||
describe('true', function () {
|
||||
var client;
|
||||
var args = config.configureClient(parser, ip, {
|
||||
socket_nodelay: true
|
||||
});
|
||||
@@ -470,7 +377,6 @@ describe("The node_redis client", function () {
|
||||
});
|
||||
|
||||
describe('false', function () {
|
||||
var client;
|
||||
var args = config.configureClient(parser, ip, {
|
||||
socket_nodelay: false
|
||||
});
|
||||
@@ -505,7 +411,6 @@ describe("The node_redis client", function () {
|
||||
});
|
||||
|
||||
describe('defaults to true', function () {
|
||||
var client;
|
||||
|
||||
it("fires client.on('ready')", function (done) {
|
||||
client = redis.createClient.apply(redis.createClient, args);
|
||||
@@ -538,7 +443,6 @@ describe("The node_redis client", function () {
|
||||
});
|
||||
|
||||
describe('retry_max_delay', function () {
|
||||
var client;
|
||||
var args = config.configureClient(parser, ip, {
|
||||
retry_max_delay: 1 // ms
|
||||
});
|
||||
@@ -566,8 +470,36 @@ describe("The node_redis client", function () {
|
||||
|
||||
describe('enable_offline_queue', function () {
|
||||
describe('true', function () {
|
||||
it("should emit drain after info command and nothing to buffer", function (done) {
|
||||
client = redis.createClient({
|
||||
parser: parser
|
||||
});
|
||||
client.set('foo', 'bar');
|
||||
client.get('foo', function () {
|
||||
assert(!client.should_buffer);
|
||||
setTimeout(done, 25);
|
||||
});
|
||||
client.on('drain', function() {
|
||||
assert(client.offline_queue.length === 2);
|
||||
});
|
||||
});
|
||||
|
||||
it("should emit drain if offline queue is flushed and nothing to buffer", function (done) {
|
||||
client = redis.createClient({
|
||||
parser: parser,
|
||||
no_ready_check: true
|
||||
});
|
||||
var end = helper.callFuncAfter(done, 2);
|
||||
client.set('foo', 'bar');
|
||||
client.get('foo', end);
|
||||
client.on('drain', function() {
|
||||
assert(client.offline_queue.length === 0);
|
||||
end();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not return an error and enqueues operation", function (done) {
|
||||
var client = redis.createClient(9999, null, {
|
||||
client = redis.createClient(9999, null, {
|
||||
max_attempts: 0,
|
||||
parser: parser
|
||||
});
|
||||
@@ -578,10 +510,8 @@ describe("The node_redis client", function () {
|
||||
|
||||
return setTimeout(function() {
|
||||
client.set('foo', 'bar', function(err, result) {
|
||||
// TODO: figure out why we emit an error on
|
||||
// even though we've enabled the offline queue.
|
||||
if (process.platform === 'win32') return;
|
||||
if (err) return done(err);
|
||||
// This should never be called
|
||||
return done(err);
|
||||
});
|
||||
|
||||
return setTimeout(function() {
|
||||
@@ -592,7 +522,7 @@ describe("The node_redis client", function () {
|
||||
});
|
||||
|
||||
it("enqueues operation and keep the queue while trying to reconnect", function (done) {
|
||||
var client = redis.createClient(9999, null, {
|
||||
client = redis.createClient(9999, null, {
|
||||
max_attempts: 4,
|
||||
parser: parser
|
||||
});
|
||||
@@ -627,8 +557,23 @@ describe("The node_redis client", function () {
|
||||
});
|
||||
|
||||
describe('false', function () {
|
||||
|
||||
it('stream not writable', function(done) {
|
||||
client = redis.createClient({
|
||||
parser: parser,
|
||||
enable_offline_queue: false
|
||||
});
|
||||
client.on('ready', function () {
|
||||
client.stream.writable = false;
|
||||
client.set('foo', 'bar', function (err, res) {
|
||||
assert.strictEqual(err.message, "SET can't be processed. Stream not writeable.");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("emit an error and does not enqueues operation", function (done) {
|
||||
var client = redis.createClient(9999, null, {
|
||||
client = redis.createClient(9999, null, {
|
||||
parser: parser,
|
||||
max_attempts: 0,
|
||||
enable_offline_queue: false
|
||||
@@ -636,7 +581,7 @@ describe("The node_redis client", function () {
|
||||
var end = helper.callFuncAfter(done, 3);
|
||||
|
||||
client.on('error', function(err) {
|
||||
assert(/Stream not writeable|ECONNREFUSED/.test(err.message));
|
||||
assert(/offline queue is deactivated|ECONNREFUSED/.test(err.message));
|
||||
assert.equal(client.command_queue.length, 0);
|
||||
end();
|
||||
});
|
||||
@@ -653,7 +598,7 @@ describe("The node_redis client", function () {
|
||||
});
|
||||
|
||||
it("flushes the command queue connection if in broken connection mode", function (done) {
|
||||
var client = redis.createClient({
|
||||
client = redis.createClient({
|
||||
parser: parser,
|
||||
max_attempts: 2,
|
||||
enable_offline_queue: false
|
||||
|
113
test/parser.spec.js
Normal file
113
test/parser.spec.js
Normal file
@@ -0,0 +1,113 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var config = require("./lib/config");
|
||||
var utils = require("../lib/utils");
|
||||
var redis = config.redis;
|
||||
var parsers = [
|
||||
require("../lib/parsers/javascript").Parser
|
||||
];
|
||||
try {
|
||||
// Test the hiredis parser if available
|
||||
parsers.push(require("../lib/parsers/hiredis").Parser);
|
||||
} catch (e) {}
|
||||
|
||||
describe('parsers', function () {
|
||||
|
||||
parsers.forEach(function (Parser) {
|
||||
|
||||
describe(Parser.name, function () {
|
||||
|
||||
it('handles multi-bulk reply', function (done) {
|
||||
var parser = new Parser();
|
||||
var reply_count = 0;
|
||||
function check_reply(reply) {
|
||||
reply = utils.reply_to_strings(reply);
|
||||
assert.deepEqual(reply, [['a']], "Expecting multi-bulk reply of [['a']]");
|
||||
reply_count++;
|
||||
}
|
||||
parser.send_reply = check_reply;
|
||||
|
||||
parser.execute(new Buffer('*1\r\n*1\r\n$1\r\na\r\n'));
|
||||
|
||||
parser.execute(new Buffer('*1\r\n*1\r'));
|
||||
parser.execute(new Buffer('\n$1\r\na\r\n'));
|
||||
|
||||
parser.execute(new Buffer('*1\r\n*1\r\n'));
|
||||
parser.execute(new Buffer('$1\r\na\r\n'));
|
||||
|
||||
assert.equal(reply_count, 3, "check reply should have been called three times");
|
||||
return done();
|
||||
});
|
||||
|
||||
it('parser error', function (done) {
|
||||
var parser = new Parser();
|
||||
var reply_count = 0;
|
||||
function check_reply(reply) {
|
||||
assert.strictEqual(reply.message, 'Protocol error, got "a" as reply type byte');
|
||||
reply_count++;
|
||||
}
|
||||
parser.send_error = check_reply;
|
||||
|
||||
parser.execute(new Buffer('a*1\r*1\r$1`zasd\r\na'));
|
||||
|
||||
assert.equal(reply_count, 1, "check reply should have been called one time");
|
||||
return done();
|
||||
});
|
||||
|
||||
it('line breaks in the beginning', function (done) {
|
||||
var parser = new Parser();
|
||||
var reply_count = 0;
|
||||
function check_reply(reply) {
|
||||
reply = utils.reply_to_strings(reply);
|
||||
assert.deepEqual(reply, [['a']], "Expecting multi-bulk reply of [['a']]");
|
||||
reply_count++;
|
||||
}
|
||||
parser.send_reply = check_reply;
|
||||
|
||||
parser.execute(new Buffer('*1\r\n*1\r\n$1\r\na'));
|
||||
|
||||
parser.execute(new Buffer('\r\n*1\r\n*1\r'));
|
||||
parser.execute(new Buffer('\n$1\r\na\r\n'));
|
||||
|
||||
parser.execute(new Buffer('*1\r\n*1\r\n'));
|
||||
parser.execute(new Buffer('$1\r\na\r\n'));
|
||||
|
||||
assert.equal(reply_count, 3, "check reply should have been called three times");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Activate this if you want to fry your cpu / memory
|
||||
describe.skip("test out of memory", function () {
|
||||
var args = config.configureClient('javascript', '127.0.0.1');
|
||||
var clients = new Array(300).join(" ").split(" ");
|
||||
var client;
|
||||
beforeEach(function (done) {
|
||||
client = redis.createClient.apply(redis.createClient, args);
|
||||
client.once("connect", function () {
|
||||
client.flushdb(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('reach limit and wait for further data', function (done) {
|
||||
setTimeout(done, 5000);
|
||||
clients.forEach(function(entry, a) {
|
||||
var max = 0;
|
||||
var client = redis.createClient.apply(redis.createClient, args);
|
||||
client.on('ready', function() {
|
||||
while (++max < 50) {
|
||||
var item = [];
|
||||
for (var i = 0; i < 100; ++i) {
|
||||
item.push('aaa' + (Math.random() * 1000000 | 0));
|
||||
}
|
||||
client.del('foo' + a);
|
||||
client.lpush('foo' + a, item);
|
||||
client.lrange('foo' + a, 0, 99);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,63 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var Parser = require("../../lib/parsers/javascript").Parser;
|
||||
var config = require("../lib/config");
|
||||
var utils = require("../../lib/utils");
|
||||
var redis = config.redis;
|
||||
|
||||
describe('javascript parser', function () {
|
||||
it('handles multi-bulk reply', function (done) {
|
||||
var parser = new Parser();
|
||||
var reply_count = 0;
|
||||
function check_reply(reply) {
|
||||
reply = utils.reply_to_strings(reply);
|
||||
assert.deepEqual(reply, [['a']], "Expecting multi-bulk reply of [['a']]");
|
||||
reply_count++;
|
||||
}
|
||||
parser.send_reply = check_reply;
|
||||
|
||||
parser.execute(new Buffer('*1\r\n*1\r\n$1\r\na\r\n'));
|
||||
|
||||
parser.execute(new Buffer('*1\r\n*1\r'));
|
||||
parser.execute(new Buffer('\n$1\r\na\r\n'));
|
||||
|
||||
parser.execute(new Buffer('*1\r\n*1\r\n'));
|
||||
parser.execute(new Buffer('$1\r\na\r\n'));
|
||||
|
||||
assert.equal(reply_count, 3, "check reply should have been called three times");
|
||||
return done();
|
||||
});
|
||||
|
||||
// Activate this if you want to fry your cpu / memory
|
||||
describe.skip("test out of memory", function () {
|
||||
var args = config.configureClient('javascript', '127.0.0.1');
|
||||
var clients = new Array(300).join(" ").split(" ");
|
||||
var client;
|
||||
beforeEach(function (done) {
|
||||
client = redis.createClient.apply(redis.createClient, args);
|
||||
client.once("connect", function () {
|
||||
client.flushdb(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('reach limit and wait for further data', function (done) {
|
||||
setTimeout(done, 5000);
|
||||
clients.forEach(function(entry, a) {
|
||||
var max = 0;
|
||||
var client = redis.createClient.apply(redis.createClient, args);
|
||||
client.on('ready', function() {
|
||||
while (++max < 50) {
|
||||
var item = [];
|
||||
for (var i = 0; i < 100; ++i) {
|
||||
item.push('aaa' + (Math.random() * 1000000 | 0));
|
||||
}
|
||||
client.del('foo' + a);
|
||||
client.lpush('foo' + a, item);
|
||||
client.lrange('foo' + a, 0, 99);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -242,8 +242,12 @@ describe("publish/subscribe", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('does not complain when unsubscribe is called and there are no subscriptions', function () {
|
||||
sub.unsubscribe();
|
||||
it('does not complain when unsubscribe is called and there are no subscriptions', function (done) {
|
||||
sub.unsubscribe(function (err, res) {
|
||||
assert.strictEqual(err, null);
|
||||
assert.strictEqual(res, null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('executes callback when unsubscribe is called and there are no subscriptions', function (done) {
|
||||
@@ -285,6 +289,34 @@ describe("publish/subscribe", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('fail for other commands while in pub sub mode', function () {
|
||||
it('return error if only pub sub commands are allowed', function (done) {
|
||||
sub.subscribe('channel');
|
||||
// Ping is allowed even if not listed as such!
|
||||
sub.ping(function (err, res) {
|
||||
assert.strictEqual(err, null);
|
||||
assert.strictEqual(res[0], 'pong');
|
||||
});
|
||||
// Get is forbidden
|
||||
sub.get('foo', function (err, res) {
|
||||
assert.strictEqual(err.message, 'ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context');
|
||||
assert.strictEqual(err.command, 'GET');
|
||||
});
|
||||
// Quit is allowed
|
||||
sub.quit(done);
|
||||
});
|
||||
|
||||
it('emit error if only pub sub commands are allowed without callback', function (done) {
|
||||
sub.subscribe('channel');
|
||||
sub.on('error', function (err) {
|
||||
assert.strictEqual(err.message, 'ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context');
|
||||
assert.strictEqual(err.command, 'GET');
|
||||
done();
|
||||
});
|
||||
sub.get('foo');
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Fix pub sub
|
||||
// And there's more than just those two issues
|
||||
describe.skip('FIXME: broken pub sub', function () {
|
||||
@@ -297,7 +329,7 @@ describe("publish/subscribe", function () {
|
||||
pub.on('message', function (msg) {
|
||||
done(new Error('This message should not have been published: ' + msg));
|
||||
});
|
||||
setTimeout(done, 500);
|
||||
setTimeout(done, 200);
|
||||
});
|
||||
|
||||
it("should not publish a message multiple times per command", function (done) {
|
||||
@@ -325,8 +357,8 @@ describe("publish/subscribe", function () {
|
||||
sub.unsubscribe();
|
||||
setTimeout(function () {
|
||||
subscribe('world');
|
||||
}, 400);
|
||||
}, 400);
|
||||
}, 40);
|
||||
}, 40);
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -19,6 +19,8 @@ describe("return_buffers", function () {
|
||||
beforeEach(function (done) {
|
||||
client = redis.createClient.apply(redis.createClient, args);
|
||||
if (args[2].detect_buffers) {
|
||||
// Test if detect_buffer option was deactivated
|
||||
assert.strictEqual(client.options.detect_buffers, false);
|
||||
args[2].detect_buffers = false;
|
||||
}
|
||||
client.once("error", done);
|
||||
|
Reference in New Issue
Block a user