diff --git a/.travis.yml b/.travis.yml index 4884f204e1..f64f437830 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,5 @@ sudo: true node_js: - "0.10" - "0.12" - - "iojs" -script: npm run mocha + - "iojs-v2" after_success: npm run coverage diff --git a/package.json b/package.json index a25f796235..2e7d9ea181 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,7 @@ "main": "./index.js", "scripts": { "coverage": "nyc report --reporter=text-lcov | coveralls", - "test": "nyc ./test/run.sh", - "mocha": "nyc ./node_modules/.bin/_mocha ./test/mocha/*.js ./test/mocha/commands/*.js" + "test": "nyc ./node_modules/.bin/_mocha ./test/*.js ./test/commands/*.js ./test/parser/*.js --timeout=8000" }, "devDependencies": { "async": "^1.3.0", @@ -23,14 +22,11 @@ "mocha": "^2.2.5", "nyc": "^3.0.0", "tcp-port-used": "^0.1.2", - "underscore": "~1.4.4" + "underscore": "~1.4.4", + "uuid": "^2.0.1" }, "repository": { "type": "git", "url": "git://github.com/mranney/node_redis.git" - }, - "dependencies": { - "sinon": "^1.15.4", - "uuid": "^2.0.1" } } diff --git a/test/auth.spec.js b/test/auth.spec.js new file mode 100644 index 0000000000..a149f4b9cf --- /dev/null +++ b/test/auth.spec.js @@ -0,0 +1,90 @@ +var assert = require("assert"); +var config = require("./lib/config"); +var helper = require('./helper') +var path = require('path'); +var redis = config.redis; + +describe("client authentication", function () { + before(function (done) { + helper.stopRedis(function () { + helper.startRedis('./conf/password.conf', done); + }); + }); + + function allTests(parser, ip) { + describe("using " + parser + " and " + ip, function () { + var args = config.configureClient(parser, ip); + var auth = 'porkchopsandwiches'; + var client = null; + + afterEach(function () { + client.end(); + }); + + it("allows auth to be provided with 'auth' method", function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.auth(auth, function (err, res) { + assert.strictEqual(null, err); + assert.strictEqual("OK", res.toString()); + return done(err); + }); + }); + + it("raises error when auth is bad", function (done) { + client = redis.createClient.apply(redis.createClient, args); + + client.once('error', function (error) { + assert.ok(/ERR invalid password/.test(error)) + return done(); + }); + + client.auth(auth + 'bad'); + }); + + if (ip === 'IPv4') + it('allows auth to be provided as config option for client', function (done) { + client = redis.createClient('redis://foo:' + auth + '@' + config.HOST[ip] + ':' + config.PORT); + client.on("ready", function () { + return done(); + }); + }); + + it('allows auth to be provided as part of redis url', function (done) { + var args = config.configureClient(parser, ip, { + auth_pass: auth + }); + client = redis.createClient.apply(redis.createClient, args); + client.on("ready", function () { + return done(); + }); + }); + + it('reconnects with appropriate authentication', function (done) { + var readyCount = 0; + client = redis.createClient.apply(redis.createClient, args); + client.auth(auth); + client.on("ready", function () { + readyCount++; + if (readyCount === 1) { + client.stream.destroy(); + } else { + return done(); + } + }); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); + + after(function (done) { + helper.stopRedis(function () { + helper.startRedis('./conf/redis.conf', done); + }); + }); +}); diff --git a/test/commands/client.spec.js b/test/commands/client.spec.js new file mode 100644 index 0000000000..3710bdc9ed --- /dev/null +++ b/test/commands/client.spec.js @@ -0,0 +1,61 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'client' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + var pattern = /addr=/; + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(function (err) { + if (!helper.serverVersionAtLeast(client, [2, 4, 0])) { + err = Error('script not supported in redis <= 2.4.0') + } + return done(err); + + }) + }); + }); + + afterEach(function () { + client.end(); + }); + + describe('list', function () { + it('lists connected clients', function (done) { + client.client("list", helper.match(pattern, done)); + }); + + it("lists connected clients when invoked with multi's chaining syntax", function (done) { + client.multi().client("list").exec(function(err, results) { + assert(pattern.test(results[0]), "expected string '" + results + "' to match " + pattern.toString()); + return done() + }) + }); + + it("lists connected clients when invoked with multi's array syntax", function (done) { + client.multi().client("list").exec(function(err, results) { + assert(pattern.test(results[0]), "expected string '" + results + "' to match " + pattern.toString()); + return done() + }) + }); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/mocha/commands/dbsize.spec.js b/test/commands/dbsize.spec.js similarity index 75% rename from test/mocha/commands/dbsize.spec.js rename to test/commands/dbsize.spec.js index df1b1431bd..70c7eb178c 100644 --- a/test/mocha/commands/dbsize.spec.js +++ b/test/commands/dbsize.spec.js @@ -1,27 +1,12 @@ var async = require('async'); var assert = require('assert'); -var config = require("../../lib/config"); -var nodeAssert = require('../../lib/nodeify-assertions'); +var config = require("../lib/config"); +var helper = require('../helper'); var redis = config.redis; -var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'dbsize' method", function () { - var rp; - before(function (done) { - RedisProcess.start(function (err, _rp) { - rp = _rp; - return done(err); - }); - }) - - function removeMochaListener () { - var mochaListener = process.listeners('uncaughtException').pop(); - process.removeListener('uncaughtException', mochaListener); - return mochaListener; - } - function allTests(parser, ip) { var args = config.configureClient(parser, ip); @@ -63,7 +48,7 @@ describe("The 'dbsize' method", function () { client.once("error", done); client.once("connect", function () { client.flushdb(function (err, res) { - nodeAssert.isString("OK")(err, res); + helper.isString("OK")(err, res); done(); }); }); @@ -75,8 +60,8 @@ describe("The 'dbsize' method", function () { it("returns a zero db size", function (done) { client.dbsize([], function (err, res) { - nodeAssert.isNotError()(err, res); - nodeAssert.isType.number()(err, res); + helper.isNotError()(err, res); + helper.isType.number()(err, res); assert.strictEqual(res, 0, "Initial db size should be 0"); done(); }); @@ -87,13 +72,13 @@ describe("The 'dbsize' method", function () { beforeEach(function (done) { client.dbsize([], function (err, res) { - nodeAssert.isType.number()(err, res); + helper.isType.number()(err, res); assert.strictEqual(res, 0, "Initial db size should be 0"); oldSize = res; client.set(key, value, function (err, res) { - nodeAssert.isNotError()(err, res); + helper.isNotError()(err, res); done(); }); }); @@ -101,8 +86,8 @@ describe("The 'dbsize' method", function () { it("returns a larger db size", function (done) { client.dbsize([], function (err, res) { - nodeAssert.isNotError()(err, res); - nodeAssert.isType.positiveNumber()(err, res); + helper.isNotError()(err, res); + helper.isType.positiveNumber()(err, res); assert.strictEqual(true, (oldSize < res), "Adding data should increase db size."); done(); }); @@ -118,8 +103,4 @@ describe("The 'dbsize' method", function () { allTests(parser, ip); }) }); - - after(function (done) { - if (rp) rp.stop(done); - }); }); diff --git a/test/commands/del.spec.js b/test/commands/del.spec.js new file mode 100644 index 0000000000..b2b2995c48 --- /dev/null +++ b/test/commands/del.spec.js @@ -0,0 +1,51 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'del' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('allows a single key to be deleted', function (done) { + client.set('foo', 'bar'); + client.del('foo', helper.isNumber(1)); + client.get('foo', helper.isNull(done)); + }); + + it('allows del to be called on a key that does not exist', function (done) { + client.del('foo', helper.isNumber(0, done)); + }); + + it('allows multiple keys to be deleted', function (done) { + client.mset('foo', 'bar', 'apple', 'banana'); + client.del('foo', 'apple', helper.isNumber(2)); + client.get('foo', helper.isNull()); + client.get('apple', helper.isNull(done)); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/eval.spec.js b/test/commands/eval.spec.js new file mode 100644 index 0000000000..a98efa032e --- /dev/null +++ b/test/commands/eval.spec.js @@ -0,0 +1,199 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var crypto = require("crypto"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'eval' method", function () { + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(function (err) { + if (!helper.serverVersionAtLeast(client, [2, 5, 0])) { + err = Error('exec not supported in redis <= 2.5.0') + } + return done(err); + }) + }); + }); + + afterEach(function () { + client.end(); + }); + + it('converts a float to an integer when evaluated', function (done) { + client.eval("return 100.5", 0, helper.isNumber(100, done)); + }); + + it('returns a string', function (done) { + client.eval("return 'hello world'", 0, helper.isString('hello world', done)); + }); + + it('converts boolean true to integer 1', function (done) { + client.eval("return true", 0, helper.isNumber(1, done)); + }); + + it('converts boolean false to null', function (done) { + client.eval("return false", 0, helper.isNull(done)); + }); + + it('converts lua status code to string representation', function (done) { + client.eval("return {ok='fine'}", 0, helper.isString('fine', done)); + }); + + it('converts lua error to an error response', function (done) { + client.eval("return {err='this is an error'}", 0, helper.isError(done)); + }); + + it('represents a lua table appropritely', function (done) { + client.eval("return {1,2,3,'ciao',{1,2}}", 0, function (err, res) { + assert.strictEqual(5, res.length); + assert.strictEqual(1, res[0]); + assert.strictEqual(2, res[1]); + assert.strictEqual(3, res[2]); + assert.strictEqual("ciao", res[3]); + assert.strictEqual(2, res[4].length); + assert.strictEqual(1, res[4][0]); + assert.strictEqual(2, res[4][1]); + return done(); + }); + }); + + it('populates keys and argv correctly', function (done) { + client.eval("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", 2, "a", "b", "c", "d", function (err, res) { + assert.strictEqual(4, res.length); + assert.strictEqual("a", res[0]); + assert.strictEqual("b", res[1]); + assert.strictEqual("c", res[2]); + assert.strictEqual("d", res[3]); + return done(); + }); + }); + + it('allows arguments to be provided in array rather than as multiple parameters', function (done) { + client.eval(["return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", 2, "a", "b", "c", "d"], function (err, res) { + assert.strictEqual(4, res.length); + assert.strictEqual("a", res[0]); + assert.strictEqual("b", res[1]); + assert.strictEqual("c", res[2]); + assert.strictEqual("d", res[3]); + return done(); + }); + }); + + describe('evalsha', function () { + var source = "return redis.call('get', 'sha test')"; + var sha = crypto.createHash('sha1').update(source).digest('hex'); + + beforeEach(function (done) { + client.set("sha test", "eval get sha test", function (err, res) { + return done(err); + }); + }); + + it('allows a script to be executed that accesses the redis API', function (done) { + client.eval(source, 0, helper.isString('eval get sha test', done)); + }); + + it('can execute a script if the SHA exists', function (done) { + client.evalsha(sha, 0, helper.isString('eval get sha test', done)); + }); + + it('throws an error if SHA does not exist', function (done) { + client.evalsha('ffffffffffffffffffffffffffffffffffffffff', 0, helper.isError(done)); + }); + }); + + it('allows a key to be incremented, and performs appropriate conversion from LUA type', function (done) { + client.set("incr key", 0, function (err, reply) { + if (err) return done(err); + client.eval("local foo = redis.call('incr','incr key')\n" + "return {type(foo),foo}", 0, function (err, res) { + assert.strictEqual(2, res.length); + assert.strictEqual("number", res[0]); + assert.strictEqual(1, res[1]); + return done(err); + }); + }); + }); + + it('allows a bulk operation to be performed, and performs appropriate conversion from LUA type', function (done) { + client.set("bulk reply key", "bulk reply value", function (err, res) { + client.eval("local foo = redis.call('get','bulk reply key'); return {type(foo),foo}", 0, function (err, res) { + assert.strictEqual(2, res.length); + assert.strictEqual("string", res[0]); + assert.strictEqual("bulk reply value", res[1]); + return done(err); + }); + }); + }); + + it('allows a multi mulk operation to be performed, with the appropriate type conversion', function (done) { + client.multi() + .del("mylist") + .rpush("mylist", "a") + .rpush("mylist", "b") + .rpush("mylist", "c") + .exec(function (err, replies) { + if (err) return done(err); + client.eval("local foo = redis.call('lrange','mylist',0,-1); return {type(foo),foo[1],foo[2],foo[3],# foo}", 0, function (err, res) { + assert.strictEqual(5, res.length); + assert.strictEqual("table", res[0]); + assert.strictEqual("a", res[1]); + assert.strictEqual("b", res[2]); + assert.strictEqual("c", res[3]); + assert.strictEqual(3, res[4]); + return done(err); + }); + }); + }); + + it('returns an appropriate representation of Lua status reply', function (done) { + client.eval("local foo = redis.call('set','mykey','myval'); return {type(foo),foo['ok']}", 0, function (err, res) { + assert.strictEqual(2, res.length); + assert.strictEqual("table", res[0]); + assert.strictEqual("OK", res[1]); + return done(err); + }); + }); + + it('returns an appropriate representation of a Lua error reply', function (done) { + client.set("error reply key", "error reply value", function (err, res) { + if (err) return done(err); + client.eval("local foo = redis.pcall('incr','error reply key'); return {type(foo),foo['err']}", 0, function (err, res) { + assert.strictEqual(2, res.length); + assert.strictEqual("table", res[0]); + assert.strictEqual("ERR value is not an integer or out of range", res[1]); + return done(err); + }); + }); + }); + + it('returns an appropriate representation of a Lua nil reply', function (done) { + client.del("nil reply key", function (err, res) { + if (err) return done(err); + client.eval("local foo = redis.call('get','nil reply key'); return {type(foo),foo == false}", 0, function (err, res) { + if (err) throw err; + assert.strictEqual(2, res.length); + assert.strictEqual("boolean", res[0]); + assert.strictEqual(1, res[1]); + return done(err); + }); + }); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/exits.spec.js b/test/commands/exits.spec.js new file mode 100644 index 0000000000..b3f04badcc --- /dev/null +++ b/test/commands/exits.spec.js @@ -0,0 +1,43 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'exits' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('returns 1 if the key exists', function (done) { + client.set('foo', 'bar'); + client.exists('foo', helper.isNumber(1, done)); + }); + + it('returns 0 if the key does not exist', function (done) { + client.exists('bar', helper.isNumber(0, done)); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/mocha/commands/flushdb.spec.js b/test/commands/flushdb.spec.js similarity index 81% rename from test/mocha/commands/flushdb.spec.js rename to test/commands/flushdb.spec.js index de325a800d..8df39f4752 100644 --- a/test/mocha/commands/flushdb.spec.js +++ b/test/commands/flushdb.spec.js @@ -1,27 +1,12 @@ var async = require('async'); var assert = require('assert'); -var config = require("../../lib/config"); -var nodeAssert = require('../../lib/nodeify-assertions'); +var config = require("../lib/config"); +var helper = require('../helper'); var redis = config.redis; -var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'flushdb' method", function () { - var rp; - before(function (done) { - RedisProcess.start(function (err, _rp) { - rp = _rp; - return done(err); - }); - }) - - function removeMochaListener () { - var mochaListener = process.listeners('uncaughtException').pop(); - process.removeListener('uncaughtException', mochaListener); - return mochaListener; - } - function allTests(parser, ip) { var args = config.configureClient(parser, ip); @@ -76,12 +61,12 @@ describe("The 'flushdb' method", function () { beforeEach(function (done) { async.parallel([function (next) { client.mset(key, uuid.v4(), key2, uuid.v4(), function (err, res) { - nodeAssert.isNotError()(err, res); + helper.isNotError()(err, res); next(err); }); }, function (next) { client.dbsize([], function (err, res) { - nodeAssert.isType.positiveNumber()(err, res); + helper.isType.positiveNumber()(err, res); oldSize = res; next(err); }); @@ -91,7 +76,7 @@ describe("The 'flushdb' method", function () { } client.flushdb(function (err, res) { - nodeAssert.isString("OK")(err, res); + helper.isString("OK")(err, res); done(err); }); }); @@ -110,8 +95,8 @@ describe("The 'flushdb' method", function () { it("results in a db size of zero", function (done) { client.dbsize([], function (err, res) { - nodeAssert.isNotError()(err, res); - nodeAssert.isType.number()(err, res); + helper.isNotError()(err, res); + helper.isType.number()(err, res); assert.strictEqual(0, res, "Flushing db should result in db size 0"); done(); }); @@ -127,8 +112,4 @@ describe("The 'flushdb' method", function () { allTests(parser, ip); }) }); - - after(function (done) { - if (rp) rp.stop(done); - }); }); diff --git a/test/mocha/commands/get.spec.js b/test/commands/get.spec.js similarity index 78% rename from test/mocha/commands/get.spec.js rename to test/commands/get.spec.js index d232a6ecb1..31502c2c4b 100644 --- a/test/mocha/commands/get.spec.js +++ b/test/commands/get.spec.js @@ -1,27 +1,12 @@ var async = require('async'); var assert = require('assert'); -var config = require("../../lib/config"); -var nodeAssert = require('../../lib/nodeify-assertions'); +var config = require("../lib/config"); +var helper = require('../helper'); var redis = config.redis; -var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'get' method", function () { - var rp; - before(function (done) { - RedisProcess.start(function (err, _rp) { - rp = _rp; - return done(err); - }); - }) - - function removeMochaListener () { - var mochaListener = process.listeners('uncaughtException').pop(); - process.removeListener('uncaughtException', mochaListener); - return mochaListener; - } - function allTests(parser, ip) { var args = config.configureClient(parser, ip); @@ -73,14 +58,14 @@ describe("The 'get' method", function () { describe("when the key exists in Redis", function () { beforeEach(function (done) { client.set(key, value, function (err, res) { - nodeAssert.isNotError()(err, res); + helper.isNotError()(err, res); done(); }); }); it("gets the value correctly", function (done) { client.get(key, function (err, res) { - nodeAssert.isString(value)(err, res); + helper.isString(value)(err, res); done(err); }); }); @@ -89,7 +74,7 @@ describe("The 'get' method", function () { describe("when the key does not exist in Redis", function () { it("gets a null value", function (done) { client.get(key, function (err, res) { - nodeAssert.isNull()(err, res); + helper.isNull()(err, res); done(err); }); }); @@ -104,8 +89,4 @@ describe("The 'get' method", function () { allTests(parser, ip); }) }); - - after(function (done) { - if (rp) rp.stop(done); - }); }); diff --git a/test/mocha/commands/getset.spec.js b/test/commands/getset.spec.js similarity index 77% rename from test/mocha/commands/getset.spec.js rename to test/commands/getset.spec.js index ba76384f2c..a8e90ecfa2 100644 --- a/test/mocha/commands/getset.spec.js +++ b/test/commands/getset.spec.js @@ -1,27 +1,12 @@ var async = require('async'); var assert = require('assert'); -var config = require("../../lib/config"); -var nodeAssert = require('../../lib/nodeify-assertions'); +var config = require("../lib/config"); +var helper = require('../helper'); var redis = config.redis; -var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'getset' method", function () { - var rp; - before(function (done) { - RedisProcess.start(function (err, _rp) { - rp = _rp; - return done(err); - }); - }) - - function removeMochaListener () { - var mochaListener = process.listeners('uncaughtException').pop(); - process.removeListener('uncaughtException', mochaListener); - return mochaListener; - } - function allTests(parser, ip) { var args = config.configureClient(parser, ip); @@ -74,16 +59,16 @@ describe("The 'getset' method", function () { describe("when the key exists in Redis", function () { beforeEach(function (done) { client.set(key, value, function (err, res) { - nodeAssert.isNotError()(err, res); + helper.isNotError()(err, res); done(); }); }); it("gets the value correctly", function (done) { client.getset(key, value2, function (err, res) { - nodeAssert.isString(value)(err, res); + helper.isString(value)(err, res); client.get(key, function (err, res) { - nodeAssert.isString(value2)(err, res); + helper.isString(value2)(err, res); done(err); }); }); @@ -93,7 +78,7 @@ describe("The 'getset' method", function () { describe("when the key does not exist in Redis", function () { it("gets a null value", function (done) { client.getset(key, value, function (err, res) { - nodeAssert.isNull()(err, res); + helper.isNull()(err, res); done(err); }); }); @@ -108,8 +93,4 @@ describe("The 'getset' method", function () { allTests(parser, ip); }) }); - - after(function (done) { - if (rp) rp.stop(done); - }); }); diff --git a/test/commands/hgetall.spec.js b/test/commands/hgetall.spec.js new file mode 100644 index 0000000000..c49988157e --- /dev/null +++ b/test/commands/hgetall.spec.js @@ -0,0 +1,91 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'hgetall' method", function () { + + function allTests(parser, ip) { + describe("using " + parser + " and " + ip, function () { + var client; + + describe('regular client', function () { + var args = config.configureClient(parser, ip); + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('handles simple keys and values', function (done) { + client.hmset(["hosts", "mjr", "1", "another", "23", "home", "1234"], helper.isString("OK")); + client.HGETALL(["hosts"], function (err, obj) { + assert.strictEqual(3, Object.keys(obj).length); + assert.strictEqual("1", obj.mjr.toString()); + assert.strictEqual("23", obj.another.toString()); + assert.strictEqual("1234", obj.home.toString()); + return done(err); + }); + }); + + it('handles fetching keys set using an object', function (done) { + client.hmset("msg_test", {message: "hello"}, helper.isString("OK")); + client.hgetall("msg_test", function (err, obj) { + assert.strictEqual(1, Object.keys(obj).length); + assert.strictEqual(obj.message, "hello"); + return done(err); + }); + }); + + it('handles fetching a messing key', function (done) { + client.hgetall("missing", function (err, obj) { + assert.strictEqual(null, obj); + return done(err); + }); + }); + }); + + describe('binary client', function () { + var client; + var args = config.configureClient(parser, ip, { + return_buffers: true + }); + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('returns binary results', function (done) { + client.hmset(["bhosts", "mjr", "1", "another", "23", "home", "1234", new Buffer([0xAA, 0xBB, 0x00, 0xF0]), new Buffer([0xCC, 0xDD, 0x00, 0xF0])], helper.isString("OK")); + client.HGETALL(["bhosts"], function (err, obj) { + assert.strictEqual(4, Object.keys(obj).length); + assert.strictEqual("1", obj.mjr.toString()); + assert.strictEqual("23", obj.another.toString()); + assert.strictEqual("1234", obj.home.toString()); + assert.strictEqual((new Buffer([0xAA, 0xBB, 0x00, 0xF0])).toString('binary'), Object.keys(obj)[3]); + assert.strictEqual((new Buffer([0xCC, 0xDD, 0x00, 0xF0])).toString('binary'), obj[(new Buffer([0xAA, 0xBB, 0x00, 0xF0])).toString('binary')].toString('binary')); + return done(err); + }); + }); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/hincrby.spec.js b/test/commands/hincrby.spec.js new file mode 100644 index 0000000000..f137405f78 --- /dev/null +++ b/test/commands/hincrby.spec.js @@ -0,0 +1,48 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'hincrby' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + var hash = "test hash"; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('increments a key that has already been set', function (done) { + var field = "field 1"; + + client.HSET(hash, field, 33); + client.HINCRBY(hash, field, 10, helper.isNumber(43, done)); + }); + + it('increments a key that has not been set', function (done) { + var field = "field 2"; + + client.HINCRBY(hash, field, 10, helper.isNumber(10, done)); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/hlen.spec.js b/test/commands/hlen.spec.js new file mode 100644 index 0000000000..11905809c1 --- /dev/null +++ b/test/commands/hlen.spec.js @@ -0,0 +1,46 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'hlen' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('reports the count of keys', function (done) { + var hash = "test hash"; + var field1 = new Buffer("0123456789"); + var value1 = new Buffer("abcdefghij"); + var field2 = new Buffer(0); + var value2 = new Buffer(0); + + client.HSET(hash, field1, value1, helper.isNumber(1)); + client.HSET(hash, field2, value2, helper.isNumber(1)); + client.HLEN(hash, helper.isNumber(2, done)); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/hmget.spec.js b/test/commands/hmget.spec.js new file mode 100644 index 0000000000..fbced263bb --- /dev/null +++ b/test/commands/hmget.spec.js @@ -0,0 +1,68 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'hmget' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + var hash = 'test hash'; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(); + client.HMSET(hash, {"0123456789": "abcdefghij", "some manner of key": "a type of value"}, helper.isString('OK')); + return done(); + }); + }); + + it('allows keys to be specified using multiple arguments', function (done) { + client.HMGET(hash, "0123456789", "some manner of key", function (err, reply) { + assert.strictEqual("abcdefghij", reply[0].toString()); + assert.strictEqual("a type of value", reply[1].toString()); + return done(err); + }); + }); + + it('allows keys to be specified by passing an array', function (done) { + client.HMGET(hash, ["0123456789", "some manner of key"], function (err, reply) { + assert.strictEqual("abcdefghij", reply[0].toString()); + assert.strictEqual("a type of value", reply[1].toString()); + return done(err); + }); + }); + + it('allows a single key to be specified in an array', function (done) { + client.HMGET(hash, ["0123456789"], function (err, reply) { + assert.strictEqual("abcdefghij", reply[0].toString()); + return done(err); + }); + }); + + it('allows keys to be specified that have not yet been set', function (done) { + client.HMGET(hash, "missing thing", "another missing thing", function (err, reply) { + assert.strictEqual(null, reply[0]); + assert.strictEqual(null, reply[1]); + return done(err); + }); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/hmset.spec.js b/test/commands/hmset.spec.js new file mode 100644 index 0000000000..d0f23eb066 --- /dev/null +++ b/test/commands/hmset.spec.js @@ -0,0 +1,61 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'hmset' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + var hash = 'test hash'; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('handles redis-style syntax', function (done) { + client.HMSET(hash, "0123456789", "abcdefghij", "some manner of key", "a type of value", helper.isString('OK')); + client.HGETALL(hash, function (err, obj) { + assert.equal(obj['0123456789'], 'abcdefghij'); + assert.equal(obj['some manner of key'], 'a type of value'); + return done(err); + }) + }); + + it('handles object-style syntax', function (done) { + client.HMSET(hash, {"0123456789": "abcdefghij", "some manner of key": "a type of value"}, helper.isString('OK')); + client.HGETALL(hash, function (err, obj) { + assert.equal(obj['0123456789'], 'abcdefghij'); + assert.equal(obj['some manner of key'], 'a type of value'); + return done(err); + }) + }); + + it('allows a numeric key', function (done) { + client.HMSET(hash, 99, 'banana', helper.isString('OK')); + client.HGETALL(hash, function (err, obj) { + assert.equal(obj['99'], 'banana'); + return done(err); + }); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/hset.spec.js b/test/commands/hset.spec.js new file mode 100644 index 0000000000..0ce2432ea9 --- /dev/null +++ b/test/commands/hset.spec.js @@ -0,0 +1,70 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'hset' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + var hash = 'test hash'; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('allows a value to be set in a hash', function (done) { + var field = new Buffer("0123456789"); + var value = new Buffer("abcdefghij"); + + client.HSET(hash, field, value, helper.isNumber(1)); + client.HGET(hash, field, helper.isString(value.toString(), done)); + }); + + it('handles an empty value', function (done) { + var field = new Buffer("0123456789"); + var value = new Buffer(0); + + client.HSET(hash, field, value, helper.isNumber(1)); + client.HGET([hash, field], helper.isString("", done)); + }); + + it('handles empty key and value', function (done) { + var field = new Buffer(0); + var value = new Buffer(0); + client.HSET([hash, field, value], function (err, res) { + assert.strictEqual(res, 1); + client.HSET(hash, field, value, helper.isNumber(0, done)); + }); + }); + + it('does not error when a buffer and array are set as fields on the same hash', function (done) { + var hash = "test hash" + var field1 = "buffer" + var value1 = new Buffer("abcdefghij") + var field2 = "array" + var value2 = ["array contents"] + + client.HMSET(hash, field1, value1, field2, value2, helper.isString("OK", done)); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/mocha/commands/incr.spec.js b/test/commands/incr.spec.js similarity index 79% rename from test/mocha/commands/incr.spec.js rename to test/commands/incr.spec.js index ae4534dd30..bdd22bacf1 100644 --- a/test/mocha/commands/incr.spec.js +++ b/test/commands/incr.spec.js @@ -1,27 +1,12 @@ var async = require('async'); var assert = require('assert'); -var config = require("../../lib/config"); -var nodeAssert = require('../../lib/nodeify-assertions'); +var config = require("../lib/config"); +var helper = require('../helper'); var redis = config.redis; -var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'incr' method", function () { - var rp; - before(function (done) { - RedisProcess.start(function (err, _rp) { - rp = _rp; - return done(err); - }); - }) - - function removeMochaListener () { - var mochaListener = process.listeners('uncaughtException').pop(); - process.removeListener('uncaughtException', mochaListener); - return mochaListener; - } - function allTests(parser, ip) { var args = config.configureClient(parser, ip); @@ -36,7 +21,7 @@ describe("The 'incr' method", function () { client.once("error", done); client.once("connect", function () { client.set(key, "9007199254740992", function (err, res) { - nodeAssert.isNotError()(err, res); + helper.isNotError()(err, res); client.quit(); }); }); @@ -45,6 +30,10 @@ describe("The 'incr' method", function () { }); }); + afterEach(function () { + client.end(); + }); + it("reports an error", function (done) { client.incr(function (err, res) { assert.equal(err.message, 'Redis connection gone from end event.'); @@ -70,7 +59,7 @@ describe("The 'incr' method", function () { client.once("error", done); client.once("connect", function () { client.set(key, "9007199254740992", function (err, res) { - nodeAssert.isNotError()(err, res); + helper.isNotError()(err, res); done(); }); }); @@ -82,7 +71,7 @@ describe("The 'incr' method", function () { it("changes the last digit from 2 to 3", function (done) { client.incr(key, function (err, res) { - nodeAssert.isString("9007199254740993")(err, res); + helper.isString("9007199254740993")(err, res); done(err); }); }); @@ -90,7 +79,7 @@ describe("The 'incr' method", function () { describe("and we call it again", function () { it("changes the last digit from 3 to 4", function (done) { client.incr(key, function (err, res) { - nodeAssert.isString("9007199254740994")(err, res); + helper.isString("9007199254740994")(err, res); done(err); }); }); @@ -98,7 +87,7 @@ describe("The 'incr' method", function () { describe("and again", function () { it("changes the last digit from 4 to 5", function (done) { client.incr(key, function (err, res) { - nodeAssert.isString("9007199254740995")(err, res); + helper.isString("9007199254740995")(err, res); done(err); }); }); @@ -106,7 +95,7 @@ describe("The 'incr' method", function () { describe("and again", function () { it("changes the last digit from 5 to 6", function (done) { client.incr(key, function (err, res) { - nodeAssert.isString("9007199254740996")(err, res); + helper.isString("9007199254740996")(err, res); done(err); }); }); @@ -114,7 +103,7 @@ describe("The 'incr' method", function () { describe("and again", function () { it("changes the last digit from 6 to 7", function (done) { client.incr(key, function (err, res) { - nodeAssert.isString("9007199254740997")(err, res); + helper.isString("9007199254740997")(err, res); done(err); }); }); @@ -132,8 +121,4 @@ describe("The 'incr' method", function () { allTests(parser, ip); }) }); - - after(function (done) { - if (rp) rp.stop(done); - }); }); diff --git a/test/commands/keys.spec.js b/test/commands/keys.spec.js new file mode 100644 index 0000000000..b1f5f9cb51 --- /dev/null +++ b/test/commands/keys.spec.js @@ -0,0 +1,76 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var crypto = require("crypto"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'keys' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('returns matching keys', function (done) { + client.mset(["test keys 1", "test val 1", "test keys 2", "test val 2"], helper.isString("OK")); + client.KEYS(["test keys*"], function (err, results) { + assert.strictEqual(2, results.length); + assert.ok(~results.indexOf("test keys 1")); + assert.ok(~results.indexOf("test keys 2")); + return done(err); + }); + }); + + it('handles a large packet size', function (done) { + var keys_values = []; + + for (var i = 0; i < 200; i++) { + var key_value = [ + "multibulk:" + crypto.randomBytes(256).toString("hex"), // use long strings as keys to ensure generation of large packet + "test val " + i + ]; + keys_values.push(key_value); + } + + client.mset(keys_values.reduce(function(a, b) { + return a.concat(b); + }), helper.isString("OK")); + + client.KEYS("multibulk:*", function(err, results) { + assert.deepEqual(keys_values.map(function(val) { + return val[0]; + }).sort(), results.sort()); + return done(err); + }); + }); + + it('handles an empty response', function (done) { + client.KEYS(['users:*'], function (err, results) { + assert.strictEqual(results.length, 0); + assert.ok(Array.isArray(results)); + return done(err); + }); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/mget.spec.js b/test/commands/mget.spec.js new file mode 100644 index 0000000000..1169b5949f --- /dev/null +++ b/test/commands/mget.spec.js @@ -0,0 +1,66 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'mget' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(); + client.mset(["mget keys 1", "mget val 1", "mget keys 2", "mget val 2", "mget keys 3", "mget val 3"], done); + }); + }); + + it('handles fetching multiple keys in argument form', function (done) { + client.mset(["mget keys 1", "mget val 1", "mget keys 2", "mget val 2", "mget keys 3", "mget val 3"], helper.isString("OK")); + client.MGET("mget keys 1", "mget keys 2", "mget keys 3", function (err, results) { + assert.strictEqual(3, results.length); + assert.strictEqual("mget val 1", results[0].toString()); + assert.strictEqual("mget val 2", results[1].toString()); + assert.strictEqual("mget val 3", results[2].toString()); + return done(err); + }); + }); + + it('handles fetching multiple keys via an array', function (done) { + client.MGET(["mget keys 1", "mget keys 2", "mget keys 3"], function (err, results) { + assert.strictEqual("mget val 1", results[0].toString()); + assert.strictEqual("mget val 2", results[1].toString()); + assert.strictEqual("mget val 3", results[2].toString()); + return done(err); + }); + }); + + it('handles fetching multiple keys, when some keys do not exist', function (done) { + client.MGET(["mget keys 1", "some random shit", "mget keys 2", "mget keys 3"], function (err, results) { + assert.strictEqual(4, results.length); + assert.strictEqual("mget val 1", results[0].toString()); + assert.strictEqual(null, results[1]); + assert.strictEqual("mget val 2", results[2].toString()); + assert.strictEqual("mget val 3", results[3].toString()); + return done(err); + }); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/mocha/commands/mset.spec.js b/test/commands/mset.spec.js similarity index 85% rename from test/mocha/commands/mset.spec.js rename to test/commands/mset.spec.js index b1f4f367b0..1523797a3c 100644 --- a/test/mocha/commands/mset.spec.js +++ b/test/commands/mset.spec.js @@ -1,21 +1,12 @@ var async = require('async'); var assert = require('assert'); -var config = require("../../lib/config"); -var nodeAssert = require('../../lib/nodeify-assertions'); +var config = require("../lib/config"); +var helper = require('../helper'); var redis = config.redis; -var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'mset' method", function () { - var rp; - before(function (done) { - RedisProcess.start(function (err, _rp) { - rp = _rp; - return done(err); - }); - }) - function removeMochaListener () { var mochaListener = process.listeners('uncaughtException').pop(); process.removeListener('uncaughtException', mochaListener); @@ -76,15 +67,15 @@ describe("The 'mset' method", function () { describe("with valid parameters", function () { it("sets the value correctly", function (done) { client.mset(key, value, key2, value2, function (err, res) { - nodeAssert.isNotError()(err, res); + helper.isNotError()(err, res); async.parallel([function (next) { client.get(key, function (err, res) { - nodeAssert.isString(value)(err, res); + helper.isString(value)(err, res); next(); }); }, function (next) { client.get(key2, function (err, res) { - nodeAssert.isString(value2)(err, res); + helper.isString(value2)(err, res); next(); }); }], function (err) { @@ -97,7 +88,7 @@ describe("The 'mset' method", function () { describe("with undefined 'key' parameter and missing 'value' parameter", function () { it("reports an error", function (done) { client.mset(undefined, function (err, res) { - nodeAssert.isError()(err, null); + helper.isError()(err, null); done(); }); }); @@ -106,7 +97,7 @@ describe("The 'mset' method", function () { describe("with undefined 'key' and defined 'value' parameters", function () { it("reports an error", function () { client.mset(undefined, value, undefined, value2, function (err, res) { - nodeAssert.isError()(err, null); + helper.isError()(err, null); done(); }); }); @@ -121,12 +112,12 @@ describe("The 'mset' method", function () { setTimeout(function () { async.parallel([function (next) { client.get(key, function (err, res) { - nodeAssert.isString(value)(err, res); + helper.isString(value)(err, res); next(); }); }, function (next) { client.get(key2, function (err, res) { - nodeAssert.isString(value2)(err, res); + helper.isString(value2)(err, res); next(); }); }], function (err) { @@ -143,7 +134,7 @@ describe("The 'mset' method", function () { process.once('uncaughtException', function (err) { process.on('uncaughtException', mochaListener); - nodeAssert.isError()(err, null); + helper.isError()(err, null); return done(); }); @@ -157,7 +148,7 @@ describe("The 'mset' method", function () { process.once('uncaughtException', function (err) { process.on('uncaughtException', mochaListener); - nodeAssert.isError()(err, null); + helper.isError()(err, null); }); client.mset(undefined, value, undefined, value2); @@ -174,8 +165,4 @@ describe("The 'mset' method", function () { allTests(parser, ip); }) }); - - after(function (done) { - if (rp) rp.stop(done); - }); }); diff --git a/test/commands/msetnx.spec.js b/test/commands/msetnx.spec.js new file mode 100644 index 0000000000..ddbb893c1a --- /dev/null +++ b/test/commands/msetnx.spec.js @@ -0,0 +1,46 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'msetnx' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('if any keys exist entire operation fails', function (done) { + client.mset(["mset1", "val1", "mset2", "val2", "mset3", "val3"], helper.isString("OK")); + client.MSETNX(["mset3", "val3", "mset4", "val4"], helper.isNumber(0)); + client.exists(["mset4"], helper.isNumber(0, done)); + }); + + it('sets multiple keys if all keys are not set', function (done) { + client.MSETNX(["mset3", "val3", "mset4", "val4"], helper.isNumber(1)); + client.exists(["mset3"], helper.isNumber(1)); + client.exists(["mset3"], helper.isNumber(1, done)); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/mocha/commands/multi.spec.js b/test/commands/multi.spec.js similarity index 82% rename from test/mocha/commands/multi.spec.js rename to test/commands/multi.spec.js index ba8be4fcbf..21526334a1 100644 --- a/test/mocha/commands/multi.spec.js +++ b/test/commands/multi.spec.js @@ -1,27 +1,12 @@ var async = require('async'); var assert = require('assert'); -var config = require("../../lib/config"); -var nodeAssert = require('../../lib/nodeify-assertions'); +var config = require("../lib/config"); +var helper = require('../helper'); var redis = config.redis; -var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'multi' method", function () { - var rp; - before(function (done) { - RedisProcess.start(function (err, _rp) { - rp = _rp; - return done(err); - }); - }) - - function removeMochaListener () { - var mochaListener = process.listeners('uncaughtException').pop(); - process.removeListener('uncaughtException', mochaListener); - return mochaListener; - } - function allTests(parser, ip) { var args = config.configureClient(parser, ip); @@ -63,7 +48,7 @@ describe("The 'multi' method", function () { client = redis.createClient.apply(redis.createClient, args); client.once("error", done); client.once("connect", function () { - client.del('multifoo', 'multibar', 'multifoo_8', 'multibar_8', function (err) { + client.flushdb(function (err) { return done(err); }) }); @@ -78,24 +63,24 @@ describe("The 'multi' method", function () { // Provoke an error at queue time multi1 = client.multi(); - multi1.mset("multifoo", "10", "multibar", "20", nodeAssert.isString("OK")); - multi1.set("foo2", nodeAssert.isError()); - multi1.incr("multifoo", nodeAssert.isNumber(11)); - multi1.incr("multibar", nodeAssert.isNumber(21)); + multi1.mset("multifoo", "10", "multibar", "20", helper.isString("OK")); + multi1.set("foo2", helper.isError()); + multi1.incr("multifoo", helper.isNumber(11)); + multi1.incr("multibar", helper.isNumber(21)); multi1.exec(function () { // Redis 2.6.5+ will abort transactions with errors // see: http://redis.io/topics/transactions var multibar_expected = 22; var multifoo_expected = 12; - if (nodeAssert.serverVersionAtLeast(client, [2, 6, 5])) { + if (helper.serverVersionAtLeast(client, [2, 6, 5])) { multibar_expected = 1; multifoo_expected = 1; } // Confirm that the previous command, while containing an error, still worked. multi2 = client.multi(); - multi2.incr("multibar", nodeAssert.isNumber(multibar_expected)); - multi2.incr("multifoo", nodeAssert.isNumber(multifoo_expected)); + multi2.incr("multibar", helper.isNumber(multibar_expected)); + multi2.incr("multifoo", helper.isNumber(multifoo_expected)); multi2.exec(function (err, replies) { assert.strictEqual(multibar_expected, replies[0]); assert.strictEqual(multifoo_expected, replies[1]); @@ -108,25 +93,25 @@ describe("The 'multi' method", function () { // perhaps @mranney can clarify? it('roles back a transaction when an error was provoked at queue time', function (done) { multi1 = client.multi(); - multi1.mset("multifoo_8", "10", "multibar_8", "20", nodeAssert.isString("OK")); - multi1.set("foo2", nodeAssert.isError()); - multi1.set("foo3", nodeAssert.isError()); - multi1.incr("multifoo_8", nodeAssert.isNumber(11)); - multi1.incr("multibar_8", nodeAssert.isNumber(21)); + multi1.mset("multifoo_8", "10", "multibar_8", "20", helper.isString("OK")); + multi1.set("foo2", helper.isError()); + multi1.set("foo3", helper.isError()); + multi1.incr("multifoo_8", helper.isNumber(11)); + multi1.incr("multibar_8", helper.isNumber(21)); multi1.exec(function () { // Redis 2.6.5+ will abort transactions with errors // see: http://redis.io/topics/transactions var multibar_expected = 22; var multifoo_expected = 12; - if (nodeAssert.serverVersionAtLeast(client, [2, 6, 5])) { + if (helper.serverVersionAtLeast(client, [2, 6, 5])) { multibar_expected = 1; multifoo_expected = 1; } // Confirm that the previous command, while containing an error, still worked. multi2 = client.multi(); - multi2.incr("multibar_8", nodeAssert.isNumber(multibar_expected)); - multi2.incr("multifoo_8", nodeAssert.isNumber(multifoo_expected)); + multi2.incr("multibar_8", helper.isNumber(multibar_expected)); + multi2.incr("multifoo_8", helper.isNumber(multifoo_expected)); multi2.exec(function (err, replies) { assert.strictEqual(multibar_expected, replies[0]); assert.strictEqual(multifoo_expected, replies[1]); @@ -143,11 +128,11 @@ describe("The 'multi' method", function () { assert.strictEqual("0", res[0].toString()); assert.strictEqual("0", res[1].toString()); }], - ["set", "foo2", nodeAssert.isError()], - ["incr", "multifoo", nodeAssert.isNumber(1)], - ["incr", "multibar", nodeAssert.isNumber(1)] + ["set", "foo2", helper.isError()], + ["incr", "multifoo", helper.isNumber(1)], + ["incr", "multibar", helper.isNumber(1)] ]).exec(function (err, replies) { - if (nodeAssert.serverVersionAtLeast(client, [2, 6, 5])) { + if (helper.serverVersionAtLeast(client, [2, 6, 5])) { assert.notEqual(err, null); assert.equal(replies, undefined); } else { @@ -241,7 +226,7 @@ describe("The 'multi' method", function () { }); it('reports multiple exceptions when they occur', function (done) { - if (!nodeAssert.serverVersionAtLeast(client, [2, 6, 5])) return done(); + if (!helper.serverVersionAtLeast(client, [2, 6, 5])) return done(); client.multi().set("foo").exec(function (err, reply) { assert(Array.isArray(err), "err should be an array"); @@ -262,8 +247,4 @@ describe("The 'multi' method", function () { allTests(parser, ip); }) }); - - after(function (done) { - if (rp) rp.stop(done); - }); }); diff --git a/test/commands/randomkey.test.js b/test/commands/randomkey.test.js new file mode 100644 index 0000000000..7549800394 --- /dev/null +++ b/test/commands/randomkey.test.js @@ -0,0 +1,42 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'randomkey' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('returns a random key', function (done) { + client.mset(["test keys 1", "test val 1", "test keys 2", "test val 2"], helper.isString('OK')); + client.RANDOMKEY([], function (err, results) { + assert.strictEqual(true, /test keys.+/.test(results)); + return done(err); + }); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/rename.spec.js b/test/commands/rename.spec.js new file mode 100644 index 0000000000..be2145923d --- /dev/null +++ b/test/commands/rename.spec.js @@ -0,0 +1,46 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'rename' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('populates the new key', function (done) { + client.set(['foo', 'bar'], helper.isString("OK")); + client.RENAME(["foo", "new foo"], helper.isString("OK")); + client.exists(["new foo"], helper.isNumber(1, done)); + }); + + it('removes the old key', function (done) { + client.set(['foo', 'bar'], helper.isString("OK")); + client.RENAME(["foo", "new foo"], helper.isString("OK")); + client.exists(["foo"], helper.isNumber(0, done)); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/renamenx.spec.js b/test/commands/renamenx.spec.js new file mode 100644 index 0000000000..6dd1a1b0d6 --- /dev/null +++ b/test/commands/renamenx.spec.js @@ -0,0 +1,49 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'renamenx' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('renames the key if target does not yet exist', function (done) { + client.set('foo', 'bar', helper.isString('OK')); + client.renamenx('foo', 'foo2', helper.isNumber(1)); + client.exists('foo', helper.isNumber(0)); + client.exists(['foo2'], helper.isNumber(1, done)); + }); + + it('does not rename the key if the target exists', function (done) { + client.set('foo', 'bar', helper.isString('OK')); + client.set('foo2', 'apple', helper.isString('OK')); + client.renamenx('foo', 'foo2', helper.isNumber(0)); + client.exists('foo', helper.isNumber(1)); + client.exists(['foo2'], helper.isNumber(1, done)); + }) + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/sadd.spec.js b/test/commands/sadd.spec.js new file mode 100644 index 0000000000..ea63a637ea --- /dev/null +++ b/test/commands/sadd.spec.js @@ -0,0 +1,58 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'sadd' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('allows a single value to be added to the set', function (done) { + client.SADD('set0', 'member0', helper.isNumber(1)); + client.smembers('set0', function (err, res) { + assert.ok(~res.indexOf('member0')); + return done(err); + }); + }); + + it('does not add the same value to the set twice', function (done) { + client.sadd('set0', 'member0', helper.isNumber(1)); + client.SADD('set0', 'member0', helper.isNumber(0, done)); + }); + + it('allows multiple values to be added to the set', function (done) { + client.sadd("set0", ["member0", "member1", "member2"], helper.isNumber(3)); + client.smembers("set0", function (err, res) { + assert.strictEqual(res.length, 3); + assert.ok(~res.indexOf("member0")); + assert.ok(~res.indexOf("member1")); + assert.ok(~res.indexOf("member2")); + return done(err); + }); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/scard.spec.js b/test/commands/scard.spec.js new file mode 100644 index 0000000000..4030e27b95 --- /dev/null +++ b/test/commands/scard.spec.js @@ -0,0 +1,39 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'scard' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('returns the number of values in a set', function (done) { + client.sadd('foo', [1, 2, 3], helper.isNumber(3)); + client.scard('foo', helper.isNumber(3, done)); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/script.spec.js b/test/commands/script.spec.js new file mode 100644 index 0000000000..df3d8aa486 --- /dev/null +++ b/test/commands/script.spec.js @@ -0,0 +1,68 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var crypto = require("crypto"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'script' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + var command = "return 99"; + var commandSha = crypto.createHash('sha1').update(command).digest('hex'); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(function (err) { + if (!helper.serverVersionAtLeast(client, [2, 6, 0])) { + err = Error('script not supported in redis <= 2.6.0') + } + return done(err); + + }) + }); + }); + + afterEach(function () { + client.end(); + }); + + it("loads script with client.script('load')", function (done) { + client.script("load", command, function(err, result) { + assert.strictEqual(result, commandSha); + return done(); + }); + }); + + it('allows a loaded script to be evaluated', function (done) { + client.evalsha(commandSha, 0, helper.isString('99', done)); + }) + + it('allows a script to be loaded as part of a chained transaction', function (done) { + client.multi().script("load", command).exec(function(err, result) { + assert.strictEqual(result[0], commandSha); + return done() + }) + }) + + it("allows a script to be loaded using a transaction's array syntax", function (done) { + client.multi([['script', 'load', command]]).exec(function(err, result) { + assert.strictEqual(result[0], commandSha); + return done() + }) + }) + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/mocha/commands/select.spec.js b/test/commands/select.spec.js similarity index 92% rename from test/mocha/commands/select.spec.js rename to test/commands/select.spec.js index 24c1d159b8..1be845dc00 100644 --- a/test/mocha/commands/select.spec.js +++ b/test/commands/select.spec.js @@ -1,20 +1,11 @@ var async = require('async'); var assert = require('assert'); -var config = require("../../lib/config"); -var nodeAssert = require('../../lib/nodeify-assertions'); +var config = require("../lib/config"); +var helper = require('../helper'); var redis = config.redis; -var RedisProcess = require("../../lib/redis-process"); describe("The 'select' method", function () { - var rp; - before(function (done) { - RedisProcess.start(function (err, _rp) { - rp = _rp; - return done(err); - }); - }) - function removeMochaListener () { var mochaListener = process.listeners('uncaughtException').pop(); process.removeListener('uncaughtException', mochaListener); @@ -64,7 +55,7 @@ describe("The 'select' method", function () { // default value of null means database 0 will be used. assert.strictEqual(client.selected_db, null, "default db should be null"); client.select(1, function (err, res) { - nodeAssert.isNotError()(err, res); + helper.isNotError()(err, res); assert.strictEqual(client.selected_db, 1, "db should be 1 after select"); done(); }); @@ -129,8 +120,4 @@ describe("The 'select' method", function () { allTests(parser, ip); }) }); - - after(function (done) { - if (rp) rp.stop(done); - }); }); diff --git a/test/mocha/commands/set.spec.js b/test/commands/set.spec.js similarity index 87% rename from test/mocha/commands/set.spec.js rename to test/commands/set.spec.js index 72df4467fb..b9bbe79c85 100644 --- a/test/mocha/commands/set.spec.js +++ b/test/commands/set.spec.js @@ -1,21 +1,12 @@ var async = require('async'); var assert = require('assert'); -var config = require("../../lib/config"); -var nodeAssert = require('../../lib/nodeify-assertions'); +var config = require("../lib/config"); +var helper = require('../helper'); var redis = config.redis; -var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'set' method", function () { - var rp; - before(function (done) { - RedisProcess.start(function (err, _rp) { - rp = _rp; - return done(err); - }); - }) - function removeMochaListener () { var mochaListener = process.listeners('uncaughtException').pop(); process.removeListener('uncaughtException', mochaListener); @@ -74,9 +65,9 @@ describe("The 'set' method", function () { describe("with valid parameters", function () { it("sets the value correctly", function (done) { client.set(key, value, function (err, res) { - nodeAssert.isNotError()(err, res); + helper.isNotError()(err, res); client.get(key, function (err, res) { - nodeAssert.isString(value)(err, res); + helper.isString(value)(err, res); done(); }); }); @@ -86,7 +77,7 @@ describe("The 'set' method", function () { describe("with undefined 'key' and missing 'value' parameter", function () { it("reports an error", function (done) { client.set(undefined, function (err, res) { - nodeAssert.isError()(err, null); + helper.isError()(err, null); done(); }); }); @@ -95,7 +86,7 @@ describe("The 'set' method", function () { describe("with undefined 'key' and defined 'value' parameters", function () { it("reports an error", function () { client.set(undefined, value, function (err, res) { - nodeAssert.isError()(err, null); + helper.isError()(err, null); done(); }); }); @@ -108,7 +99,7 @@ describe("The 'set' method", function () { client.set(key, value); setTimeout(function () { client.get(key, function (err, res) { - nodeAssert.isString(value)(err, res); + helper.isString(value)(err, res); done(); }); }, 100); @@ -120,7 +111,7 @@ describe("The 'set' method", function () { this.timeout(200); client.once("error", function (err) { - nodeAssert.isError()(err, null); + helper.isError()(err, null); return done(err); }); @@ -154,7 +145,7 @@ describe("The 'set' method", function () { process.once('uncaughtException', function (err) { process.on('uncaughtException', mochaListener); - nodeAssert.isError()(err, null); + helper.isError()(err, null); }); client.set(undefined, value); @@ -171,8 +162,4 @@ describe("The 'set' method", function () { allTests(parser, ip); }) }); - - after(function (done) { - if (rp) rp.stop(done); - }); }); diff --git a/test/commands/setex.spec.js b/test/commands/setex.spec.js new file mode 100644 index 0000000000..d66366785e --- /dev/null +++ b/test/commands/setex.spec.js @@ -0,0 +1,47 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'setex' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('sets a key with an expiry', function (done) { + client.SETEX(["setex key", "100", "setex val"], helper.isString("OK")); + client.exists(["setex key"], helper.isNumber(1)); + client.ttl(['setex key'], function (err, ttl) { + assert.ok(ttl > 0); + return done(); + }); + }); + + it('returns an error if no value is provided', function (done) { + client.SETEX(["setex key", "100", undefined], helper.isError(done)); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/setnx.spec.js b/test/commands/setnx.spec.js new file mode 100644 index 0000000000..92f892516a --- /dev/null +++ b/test/commands/setnx.spec.js @@ -0,0 +1,46 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'setnx' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('sets key if it does not have a value', function (done) { + client.setnx('foo', 'banana', helper.isNumber(1)); + client.get('foo', helper.isString('banana', done)); + }); + + it('does not set key if it already has a value', function (done) { + client.set('foo', 'bar', helper.isString('OK')); + client.setnx('foo', 'banana', helper.isNumber(0)); + client.get('foo', helper.isString('bar', done)); + return done(); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/sinter.spec.js b/test/commands/sinter.spec.js new file mode 100644 index 0000000000..c1b89051cf --- /dev/null +++ b/test/commands/sinter.spec.js @@ -0,0 +1,70 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'sinter' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('handles two sets being intersected', function (done) { + client.sadd('sa', 'a', helper.isNumber(1)); + client.sadd('sa', 'b', helper.isNumber(1)); + client.sadd('sa', 'c', helper.isNumber(1)); + + client.sadd('sb', 'b', helper.isNumber(1)); + client.sadd('sb', 'c', helper.isNumber(1)); + client.sadd('sb', 'd', helper.isNumber(1)); + + client.sinter('sa', 'sb', function (err, intersection) { + assert.equal(intersection.length, 2); + assert.deepEqual(intersection.sort(), [ 'b', 'c' ]); + return done(err); + }); + }); + + it('handles three sets being intersected', function (done) { + client.sadd('sa', 'a', helper.isNumber(1)); + client.sadd('sa', 'b', helper.isNumber(1)); + client.sadd('sa', 'c', helper.isNumber(1)); + + client.sadd('sb', 'b', helper.isNumber(1)); + client.sadd('sb', 'c', helper.isNumber(1)); + client.sadd('sb', 'd', helper.isNumber(1)); + + client.sadd('sc', 'c', helper.isNumber(1)); + client.sadd('sc', 'd', helper.isNumber(1)); + client.sadd('sc', 'e', helper.isNumber(1)); + + client.sinter('sa', 'sb', 'sc', function (err, intersection) { + assert.equal(intersection.length, 1); + assert.equal(intersection[0], 'c'); + return done(err); + }); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/sismember.spec.js b/test/commands/sismember.spec.js new file mode 100644 index 0000000000..7277692c46 --- /dev/null +++ b/test/commands/sismember.spec.js @@ -0,0 +1,43 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'sismember' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('returns 0 if the value is not in the set', function (done) { + client.sismember('foo', 'banana', helper.isNumber(0, done)); + }); + + it('returns 1 if the value is in the set', function (done) { + client.sadd('foo', 'banana', helper.isNumber(1)); + client.sismember('foo', 'banana', helper.isNumber(1, done)); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/sort.spec.js b/test/commands/sort.spec.js new file mode 100644 index 0000000000..b40b63cf32 --- /dev/null +++ b/test/commands/sort.spec.js @@ -0,0 +1,128 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'sort' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(); + setupData(client, done) + }); + }); + + describe('alphabetical', function () { + it('sorts in ascending alphabetical order', function (done) { + client.sort('y', 'asc', 'alpha', function (err, sorted) { + assert.deepEqual(sorted, ['a', 'b', 'c', 'd']); + return done(err); + }); + }); + + it('sorts in descending alphabetical order', function (done) { + client.sort('y', 'desc', 'alpha', function (err, sorted) { + assert.deepEqual(sorted, ['d', 'c', 'b', 'a']); + return done(err); + }); + }); + }); + + describe('numeric', function () { + it('sorts in ascending numeric order', function (done) { + client.sort('x', 'asc', function (err, sorted) { + assert.deepEqual(sorted, [2, 3, 4, 9]); + return done(err); + }); + }); + + it('sorts in descending numeric order', function (done) { + client.sort('x', 'desc', function (err, sorted) { + assert.deepEqual(sorted, [9, 4, 3, 2]); + return done(err); + }); + }); + }); + + describe('pattern', function () { + it('handles sorting with a pattern', function (done) { + client.sort('x', 'by', 'w*', 'asc', function (err, sorted) { + assert.deepEqual(sorted, [3, 9, 4, 2]); + return done(err); + }); + }); + + it("handles sorting with a 'by' pattern and 1 'get' pattern", function (done) { + client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', function (err, sorted) { + assert.deepEqual(sorted, ['foo', 'bar', 'baz', 'buz']); + return done(err); + }); + }); + + it("handles sorting with a 'by' pattern and 2 'get' patterns", function (done) { + client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', function (err, sorted) { + assert.deepEqual(sorted, ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']); + return done(err); + }); + }); + + it("sorting with a 'by' pattern and 2 'get' patterns and stores results", function (done) { + client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', 'store', 'bacon', function (err) { + if (err) return done(err); + }); + + client.lrange('bacon', 0, -1, function (err, values) { + assert.deepEqual(values, ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']); + return done(err); + }); + }); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + function setupData(client, done) { + client.rpush('y', 'd'); + client.rpush('y', 'b'); + client.rpush('y', 'a'); + client.rpush('y', 'c'); + + client.rpush('x', '3'); + client.rpush('x', '9'); + client.rpush('x', '2'); + client.rpush('x', '4'); + + client.set('w3', '4'); + client.set('w9', '5'); + client.set('w2', '12'); + client.set('w4', '6'); + + client.set('o2', 'buz'); + client.set('o3', 'foo'); + client.set('o4', 'baz'); + client.set('o9', 'bar'); + + client.set('p2', 'qux'); + client.set('p3', 'bux'); + client.set('p4', 'lux'); + client.set('p9', 'tux', done); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/srem.spec.js b/test/commands/srem.spec.js new file mode 100644 index 0000000000..f0a140e39a --- /dev/null +++ b/test/commands/srem.spec.js @@ -0,0 +1,66 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'srem' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('removes a value', function (done) { + client.sadd('set0', 'member0', helper.isNumber(1)); + client.srem('set0', 'member0', helper.isNumber(1)); + client.scard('set0', helper.isNumber(0, done)); + }); + + it('handles attempting to remove a missing value', function (done) { + client.srem('set0', 'member0', helper.isNumber(0, done)); + }); + + it('allows multiple values to be removed', function (done) { + client.sadd("set0", ["member0", "member1", "member2"], helper.isNumber(3)); + client.SREM("set0", ["member1", "member2"], helper.isNumber(2)); + client.smembers("set0", function (err, res) { + assert.strictEqual(res.length, 1); + assert.ok(~res.indexOf("member0")); + return done(err); + }); + }); + + it('handles a value missing from the set of values being removed', function (done) { + client.sadd("set0", ["member0", "member1", "member2"], helper.isNumber(3)); + client.SREM("set0", ["member3", "member4"], helper.isNumber(0)); + client.smembers("set0", function (err, res) { + assert.strictEqual(res.length, 3); + assert.ok(~res.indexOf("member0")); + assert.ok(~res.indexOf("member1")); + assert.ok(~res.indexOf("member2")); + return done(err); + }); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/type.spec.js b/test/commands/type.spec.js new file mode 100644 index 0000000000..57aa745da1 --- /dev/null +++ b/test/commands/type.spec.js @@ -0,0 +1,63 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'type' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('reports string type', function (done) { + client.set(["string key", "should be a string"], helper.isString("OK")); + client.TYPE(["string key"], helper.isString("string", done)); + }); + + it('reports list type', function (done) { + client.rpush(["list key", "should be a list"], helper.isNumber(1)); + client.TYPE(["list key"], helper.isString("list", done)); + }); + + it('reports set type', function (done) { + client.sadd(["set key", "should be a set"], helper.isNumber(1)); + client.TYPE(["set key"], helper.isString("set", done)); + }); + + it('reports zset type', function (done) { + client.zadd(["zset key", "10.0", "should be a zset"], helper.isNumber(1)); + client.TYPE(["zset key"], helper.isString("zset", done)); + }); + + it('reports hash type', function (done) { + client.hset(["hash key", "hashtest", "should be a hash"], helper.isNumber(1)); + client.TYPE(["hash key"], helper.isString("hash", done)); + }); + + it('reports none for null key', function (done) { + client.TYPE("not here yet", helper.isString("none", done)); + }); + + afterEach(function () { + client.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/watch.spec.js b/test/commands/watch.spec.js new file mode 100644 index 0000000000..8494d35dd5 --- /dev/null +++ b/test/commands/watch.spec.js @@ -0,0 +1,68 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'watch' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + var watched = 'foobar' + + describe("using " + parser + " and " + ip, function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(function (err) { + if (!helper.serverVersionAtLeast(client, [2, 2, 0])) { + err = Error('some watch commands not supported in redis <= 2.2.0') + } + return done(err); + + }) + }); + }); + + afterEach(function () { + client.end(); + }); + + it('does not execute transaction if watched key was modified prior to execution', function (done) { + client.watch(watched); + client.incr(watched); + multi = client.multi(); + multi.incr(watched); + multi.exec(helper.isNull(done)); + }) + + it('successfully modifies other keys independently of transaction', function (done) { + client.set("unwatched", 200); + + client.set(watched, 0); + client.watch(watched); + client.incr(watched); + + var multi = client.multi() + .incr(watched) + .exec(function (err, replies) { + assert.strictEqual(replies, null, "Aborted transaction multi-bulk reply should be null."); + + client.get("unwatched", function (err, reply) { + assert.equal(reply, 200, "Expected 200, got " + reply); + return done(err) + }); + }); + }) + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/conf/password.conf b/test/conf/password.conf new file mode 100644 index 0000000000..8351cb455f --- /dev/null +++ b/test/conf/password.conf @@ -0,0 +1,5 @@ +requirepass porkchopsandwiches +port 6378 +bind ::1 127.0.0.1 +unixsocket /tmp/redis.sock +unixsocketperm 755 diff --git a/test/lib/nodeify-assertions.js b/test/helper.js similarity index 55% rename from test/lib/nodeify-assertions.js rename to test/helper.js index 73000e67d0..8aaa4d68d4 100644 --- a/test/lib/nodeify-assertions.js +++ b/test/helper.js @@ -1,63 +1,86 @@ -var assert = require('assert'); +var assert = require("assert"); +var path = require('path'); +var RedisProcess = require("./lib/redis-process"); +var rp; + +// don't start redis every time we +// include this helper file! +if (!process.env.REDIS_TESTS_STARTED) { + process.env.REDIS_TESTS_STARTED = true; + + before(function (done) { + startRedis('./conf/redis.conf', done); + }) + + after(function (done) { + if (rp) rp.stop(done); + }); +} module.exports = { - isNumber: function (expected) { + stopRedis: function (done) { + rp.stop(done); + }, + startRedis: function (conf, done) { + startRedis(conf, done); + }, + isNumber: function (expected, done) { return function (err, results) { assert.strictEqual(null, err, "expected " + expected + ", got error: " + err); assert.strictEqual(expected, results, expected + " !== " + results); assert.strictEqual(typeof results, "number", "expected a number, got " + typeof results); - return true; + if (done) return done(); }; }, - - isString: function (str) { + isString: function (str, done) { return function (err, results) { assert.strictEqual(null, err, "expected string '" + str + "', got error: " + err); assert.equal(str, results, str + " does not match " + results); - return true; + if (done) return done(); }; }, - - isNull: function () { + isNull: function (done) { return function (err, results) { assert.strictEqual(null, err, "expected null, got error: " + err); assert.strictEqual(null, results, results + " is not null"); - return true; + if (done) return done(); }; }, - - isError: function () { + isError: function (done) { return function (err, results) { assert.notEqual(err, null, "err is null, but an error is expected here."); - return true; + if (done) return done(); }; }, - - isNotError: function () { + isNotError: function (done) { return function (err, results) { assert.strictEqual(err, null, "expected success, got an error: " + err); - return true; + if (done) return done(); }; }, - isType: { - number: function () { + number: function (done) { return function (err, results) { assert.strictEqual(null, err, "expected any number, got error: " + err); assert.strictEqual(typeof results, "number", results + " is not a number"); - return true; + if (done) return done(); }; }, - - positiveNumber: function () { + positiveNumber: function (done) { return function (err, results) { assert.strictEqual(null, err, "expected positive number, got error: " + err); assert.strictEqual(true, (results > 0), results + " is not a positive number"); - return true; + if (done) return done(); }; } }, - + match: function (pattern, done) { + return function (err, results) { + assert.strictEqual(null, err, "expected " + pattern.toString() + ", got error: " + err); + assert(pattern.test(results), "expected string '" + results + "' to match " + pattern.toString()); + if (done) return done(); + }; + }, serverVersionAtLeast: function (connection, desired_version) { // Return true if the server version >= desired_version var version = connection.server_info.versions; @@ -67,4 +90,11 @@ module.exports = { } return true; } -}; +} + +function startRedis (conf, done) { + RedisProcess.start(function (err, _rp) { + rp = _rp; + return done(err); + }, path.resolve(__dirname, conf)); +} diff --git a/test/lib/config.js b/test/lib/config.js index 475f2733ce..0ddef51447 100644 --- a/test/lib/config.js +++ b/test/lib/config.js @@ -1,3 +1,5 @@ +// helpers for configuring a redis client in +// its various modes, ipV6, ipV4, socket. module.exports = (function () { var redis = require('../../index'); redis.debug_mode = process.env.DEBUG ? JSON.parse(process.env.DEBUG) : false; @@ -11,18 +13,21 @@ module.exports = (function () { } }; - config.configureClient = function (parser, ip, isSocket) { + config.configureClient = function (parser, ip, opts) { var args = []; + opts = opts || {}; - if (!isSocket) { + if (ip.match(/\.sock/)) { + args.push(ip) + } else { args.push(config.PORT); args.push(config.HOST[ip]); - args.push({ family: ip, parser: parser }); - } else { - args.push(ip); - args.push({ parser: parser }); + opts.family = ip; } + opts.parser = parser; + args.push(opts); + return args; }; diff --git a/test/lib/redis-process.js b/test/lib/redis-process.js index 24d7f04f60..212cf359c0 100644 --- a/test/lib/redis-process.js +++ b/test/lib/redis-process.js @@ -1,42 +1,55 @@ +// helper to start and stop the redis process. var cp = require('child_process'); var config = require('./config'); +var fs = require('fs'); var path = require('path'); var tcpPortUsed = require('tcp-port-used'); module.exports = { - start: function (done) { + start: function (done, conf) { // spawn redis with our testing configuration. - var confFile = path.resolve(__dirname, '../conf/redis.conf'); + var confFile = conf || path.resolve(__dirname, '../conf/redis.conf'); var rp = cp.spawn("redis-server", [confFile], {}); // wait for redis to become available, by // checking the port we bind on. - var id = setInterval(function () { - tcpPortUsed.check(config.PORT, '127.0.0.1') - .then(function (inUse) { - if (inUse) { - clearInterval(id); - - // return an object that can be used in - // an after() block to shutdown redis. - return done(null, { - stop: function (done) { - rp.once("exit", function (code) { - var error = null; - if (code !== null && code !== 0) { - error = Error('Redis shutdown failed with code ' + code); - } - return done(error); - }); - rp.kill("SIGINT"); - } - }); - } - }) - .catch(function (err) { - clearInterval(id); - return done(err); - }) - }, 100); + waitForRedis(true, function () { + // return an object that can be used in + // an after() block to shutdown redis. + return done(null, { + stop: function (done) { + rp.once("exit", function (code) { + var error = null; + if (code !== null && code !== 0) { + error = Error('Redis shutdown failed with code ' + code); + } + waitForRedis(false, function () { + return done(error); + }) + }); + rp.kill("SIGTERM"); + } + }); + }); } }; + +// wait for redis to be listening in +// all three modes (ipv4, ipv6, socket). +function waitForRedis (available, cb) { + var ipV4 = false; + var id = setInterval(function () { + tcpPortUsed.check(config.PORT, '127.0.0.1') + .then(function (_ipV4) { + ipV4 = _ipV4; + return tcpPortUsed.check(config.PORT, '::1'); + }) + .then(function (ipV6) { + if (ipV6 === available && ipV4 === available && + fs.existsSync('/tmp/redis.sock') === available) { + clearInterval(id); + return cb(); + } + }); + }, 100); +} diff --git a/test/lib/unref.js b/test/lib/unref.js new file mode 100644 index 0000000000..4ccf51f203 --- /dev/null +++ b/test/lib/unref.js @@ -0,0 +1,18 @@ +// spawned by the unref tests in node_redis.spec.js. +// when configured, unref causes the client to exit +// as soon as there are no outstanding commands. +'use strict'; + +var redis = require("../../"); +//redis.debug_mode = true +var HOST = process.argv[2] || '127.0.0.1'; +var PORT = process.argv[3] +var args = PORT ? [PORT, HOST] : [HOST] + +var c = redis.createClient.apply(redis, args); +c.unref(); +c.info(function (err, reply) { + if (err) process.exit(-1); + if (!reply.length) process.exit(-1); + process.stdout.write(reply.length.toString()); +}); diff --git a/test/mocha/commands/generated-commands.spec.js b/test/mocha/commands/generated-commands.spec.js deleted file mode 100644 index bff15f4839..0000000000 --- a/test/mocha/commands/generated-commands.spec.js +++ /dev/null @@ -1,119 +0,0 @@ -var async = require('async'); -var assert = require('assert'); -var config = require("../../lib/config"); -var nodeAssert = require('../../lib/nodeify-assertions'); -var redis = config.redis; -var RedisProcess = require("../../lib/redis-process"); -var uuid = require('uuid'); -var commands = require("../../../lib/commands"); -var sinon = require('sinon').sandbox.create(); - -var overwritten = ['eval', 'hmset', 'multi', 'select']; - -describe("The auto-generated methods", function () { - - var rp; - before(function (done) { - RedisProcess.start(function (err, _rp) { - rp = _rp; - return done(err); - }); - }) - - function removeMochaListener () { - var mochaListener = process.listeners('uncaughtException').pop(); - process.removeListener('uncaughtException', mochaListener); - return mochaListener; - } - - function allTests(parser, ip) { - var args = config.configureClient(parser, ip); - - describe("using " + parser + " and " + ip, function () { - var key, value; - var client; - - beforeEach(function (done) { - client = redis.createClient.apply(redis.createClient, args); - client.once("error", function onError(err) { - done(err); - }); - client.once("ready", function onReady() { - done(); - }); - }); - - afterEach(function () { - client.end(); - }); - - commands.forEach(function (method) { - if (overwritten.indexOf(method) > -1) { - // these are in the list of generated commands but are later overwritten with - // different behavior by node_redis - return; - } - - describe("the " + method + " method", function () { - var methodArgs; - var noop = function () { }; - - it("calls sendCommand with whatever arguments it receives", function () { - key = uuid.v4(); - value = uuid.v4(); - - var parts = method.split(' '); - var argNum = 0; - - client.send_command = sinon.spy(); - methodArgs = [key, value, noop]; - - client[parts[0]].apply(client, methodArgs); - - assert.strictEqual(client.send_command.called, true, - "Client.send_command should have been called."); - assert.strictEqual(parts[0], client.send_command.args[0][argNum], - "Command name '" + parts[0] + "' should be passed as arg " + - argNum + " to send_command"); - argNum++; - /* - * Um, except this doesn't work? The second part of the command is never sent???? - if (parts[1]) { - assert.strictEqual(parts[1], client.send_command.args[0][argNum], - "Second command '" + parts[1] + "' should be passed as arg " + - argNum + " to send_command"); - argNum++; - } - */ - assert.strictEqual(methodArgs.length, client.send_command.args[0][argNum].length, - "The rest of the args to " + method + " should be passed as arg an array to send_command"); - assert.strictEqual(methodArgs[0], client.send_command.args[0][argNum][0], - "Arg " + argNum + " to " + method + " should be passed as arg " + - argNum + " to send_command"); - assert.strictEqual(methodArgs[1], client.send_command.args[0][argNum][1], - "Arg " + argNum + " to " + method + " should be passed as arg " + - argNum + " to send_command"); - assert.strictEqual(methodArgs[2], client.send_command.args[0][argNum][2], - "Arg " + argNum + " to " + method + " should be passed as arg " + - argNum + " to send_command"); - }); - }); - }); - }); - } - - ['javascript', 'hiredis'].forEach(function (parser) { - allTests(parser, "/tmp/redis.sock"); - ['IPv4', 'IPv6'].forEach(function (ip) { - allTests(parser, ip); - }) - }); - - afterEach(function () { - sinon.restore(); - }); - - after(function (done) { - if (rp) rp.stop(done); - }); -}); diff --git a/test/mocha/node_redis.spec.js b/test/mocha/node_redis.spec.js deleted file mode 100644 index 2319c2173d..0000000000 --- a/test/mocha/node_redis.spec.js +++ /dev/null @@ -1,182 +0,0 @@ -var async = require("async"); -var config = require("../lib/config"); -var nodeAssert = require("../lib/nodeify-assertions"); -var redis = config.redis; -var RedisProcess = require("../lib/redis-process"); -var uuid = require("uuid"); - -describe("A node_redis client", function () { - - var rp; - before(function (done) { - RedisProcess.start(function (err, _rp) { - rp = _rp; - return done(err); - }); - }) - - function allTests(parser, ip) { - var args = config.configureClient(parser, ip); - - describe("using " + parser + " and " + ip, function () { - var client; - - describe("when not connected", function () { - afterEach(function () { - client.end(); - }); - - it("connects correctly", 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); - }); - }); - }); - }); - - describe("when connected", function () { - var client; - - beforeEach(function (done) { - client = redis.createClient.apply(redis.createClient, args); - client.once("error", function onError(err) { - done(err); - }); - client.once("ready", function onReady() { - done(); - }); - }); - - afterEach(function () { - client.end(); - }); - - describe("when redis closes unexpectedly", function () { - it("reconnects and can retrieve the pre-existing data", function (done) { - client.on("reconnecting", function on_recon(params) { - client.on("connect", function on_connect() { - async.parallel([function (cb) { - client.get("recon 1", function (err, res) { - nodeAssert.isString("one")(err, res); - cb(); - }); - }, function (cb) { - client.get("recon 1", function (err, res) { - nodeAssert.isString("one")(err, res); - cb(); - }); - }, function (cb) { - client.get("recon 2", function (err, res) { - nodeAssert.isString("two")(err, res); - cb(); - }); - }, function (cb) { - client.get("recon 2", function (err, res) { - nodeAssert.isString("two")(err, res); - cb(); - }); - }], function (err, results) { - client.removeListener("connect", on_connect); - client.removeListener("reconnecting", on_recon); - done(err); - }); - }); - }); - - client.set("recon 1", "one"); - client.set("recon 2", "two", function (err, res) { - // Do not do this in normal programs. This is to simulate the server closing on us. - // For orderly shutdown in normal programs, do client.quit() - client.stream.destroy(); - }); - }); - - describe("and it's subscribed to a channel", function () { - // reconnect_select_db_after_pubsub - // Does not pass. - // "Connection in subscriber mode, only subscriber commands may be used" - xit("reconnects, unsubscribes, and can retrieve the pre-existing data", function (done) { - client.on("reconnecting", function on_recon(params) { - client.on("ready", function on_connect() { - async.parallel([function (cb) { - client.unsubscribe("recon channel", function (err, res) { - nodeAssert.isNotError()(err, res); - cb(); - }); - }, function (cb) { - client.get("recon 1", function (err, res) { - nodeAssert.isString("one")(err, res); - cb(); - }); - }], function (err, results) { - client.removeListener("connect", on_connect); - client.removeListener("reconnecting", on_recon); - done(err); - }); - }); - }); - - client.set("recon 1", "one"); - client.subscribe("recon channel", function (err, res) { - // Do not do this in normal programs. This is to simulate the server closing on us. - // For orderly shutdown in normal programs, do client.quit() - client.stream.destroy(); - }); - }); - - it("remains subscribed", function () { - var client2 = redis.createClient.apply(redis.createClient, args); - - client.on("reconnecting", function on_recon(params) { - client.on("ready", function on_connect() { - async.parallel([function (cb) { - client.on("message", function (channel, message) { - try { - nodeAssert.isString("recon channel")(null, channel); - nodeAssert.isString("a test message")(null, message); - } catch (err) { - cb(err); - } - }); - - client2.subscribe("recon channel", function (err, res) { - if (err) { - cb(err); - return; - } - client2.publish("recon channel", "a test message"); - }); - }], function (err, results) { - done(err); - }); - }); - }); - - client.subscribe("recon channel", function (err, res) { - // Do not do this in normal programs. This is to simulate the server closing on us. - // For orderly shutdown in normal programs, do client.quit() - client.stream.destroy(); - }); - }); - }); - }); - }); - }); - } - - ['javascript', 'hiredis'].forEach(function (parser) { - allTests(parser, "/tmp/redis.sock"); - ['IPv4', 'IPv6'].forEach(function (ip) { - allTests(parser, ip); - }) - }); - - after(function (done) { - if (rp) rp.stop(done); - }) -}); diff --git a/test/node_redis.spec.js b/test/node_redis.spec.js new file mode 100644 index 0000000000..cd3d86da55 --- /dev/null +++ b/test/node_redis.spec.js @@ -0,0 +1,640 @@ +var async = require("async"); +var assert = require("assert"); +var config = require("./lib/config"); +var helper = require('./helper') +var fork = require("child_process").fork; +var redis = config.redis; + +describe("a node_redis client", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + + describe("when not connected", function () { + afterEach(function () { + client.end(); + }); + + it("connects correctly", 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); + }); + }); + }); + }); + + 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("when redis closes unexpectedly", function () { + it("reconnects and can retrieve the pre-existing data", function (done) { + client.on("reconnecting", function on_recon(params) { + client.on("connect", function on_connect() { + async.parallel([function (cb) { + client.get("recon 1", function (err, res) { + helper.isString("one")(err, res); + cb(); + }); + }, function (cb) { + client.get("recon 1", function (err, res) { + helper.isString("one")(err, res); + cb(); + }); + }, function (cb) { + client.get("recon 2", function (err, res) { + helper.isString("two")(err, res); + cb(); + }); + }, function (cb) { + client.get("recon 2", function (err, res) { + helper.isString("two")(err, res); + cb(); + }); + }], function (err, results) { + client.removeListener("connect", on_connect); + client.removeListener("reconnecting", on_recon); + done(err); + }); + }); + }); + + client.set("recon 1", "one"); + client.set("recon 2", "two", function (err, res) { + // Do not do this in normal programs. This is to simulate the server closing on us. + // For orderly shutdown in normal programs, do client.quit() + client.stream.destroy(); + }); + }); + + describe("and it's subscribed to a channel", function () { + // reconnect_select_db_after_pubsub + // Does not pass. + // "Connection in subscriber mode, only subscriber commands may be used" + xit("reconnects, unsubscribes, and can retrieve the pre-existing data", function (done) { + client.on("reconnecting", function on_recon(params) { + client.on("ready", function on_connect() { + async.parallel([function (cb) { + client.unsubscribe("recon channel", function (err, res) { + helper.isNotError()(err, res); + cb(); + }); + }, function (cb) { + client.get("recon 1", function (err, res) { + helper.isString("one")(err, res); + cb(); + }); + }], function (err, results) { + client.removeListener("connect", on_connect); + client.removeListener("reconnecting", on_recon); + done(err); + }); + }); + }); + + client.set("recon 1", "one"); + client.subscribe("recon channel", function (err, res) { + // Do not do this in normal programs. This is to simulate the server closing on us. + // For orderly shutdown in normal programs, do client.quit() + client.stream.destroy(); + }); + }); + + it("remains subscribed", function () { + var client2 = redis.createClient.apply(redis.createClient, args); + + client.on("reconnecting", function on_recon(params) { + client.on("ready", function on_connect() { + async.parallel([function (cb) { + client.on("message", function (channel, message) { + try { + helper.isString("recon channel")(null, channel); + helper.isString("a test message")(null, message); + } catch (err) { + cb(err); + } + }); + + client2.subscribe("recon channel", function (err, res) { + if (err) { + cb(err); + return; + } + client2.publish("recon channel", "a test message"); + }); + }], function (err, results) { + done(err); + }); + }); + }); + + client.subscribe("recon channel", function (err, res) { + // Do not do this in normal programs. This is to simulate the server closing on us. + // For orderly shutdown in normal programs, do client.quit() + client.stream.destroy(); + }); + }); + }); + + describe('domain', function () { + it('allows client to be executed from within domain', function (done) { + var domain; + + try { + domain = require('domain').create(); + } catch (err) { + console.log("Skipping " + name + " because this version of node doesn't have domains."); + return done(); + } + + if (domain) { + domain.run(function () { + client.set('domain', 'value', function (err, res) { + assert.ok(process.domain); + var notFound = res.not.existing.thing; // ohhh nooooo + }); + }); + + // this is the expected and desired behavior + domain.on('error', function (err) { + domain.exit(); + return done() + }); + } + }) + }) + + }); + + it('emits errors thrown from within an on("message") handler', function (done) { + var client2 = redis.createClient.apply(redis.createClient, args); + var name = 'channel'; + + client2.subscribe(name, function () { + client.publish(name, "some message"); + }); + + client2.on("message", function (channel, data) { + if (channel == name) { + assert.equal(data, "some message"); + throw Error('forced exception'); + } + return done(); + }); + + client2.once("error", function (err) { + client2.end(); + assert.equal(err.message, 'forced exception'); + return done(); + }); + }); + + describe('idle', function () { + it('emits idle as soon as there are no outstanding commands', function (done) { + client.on('idle', function onIdle () { + client.removeListener("idle", onIdle); + client.get('foo', helper.isString('bar', done)); + }); + client.set('foo', 'bar'); + }); + }); + + describe('utf8', function () { + it('handles utf-8 keys', function (done) { + var utf8_sample = "ಠ_ಠ"; + client.set(["utf8test", utf8_sample], helper.isString("OK")); + client.get(["utf8test"], function (err, obj) { + assert.strictEqual(utf8_sample, obj); + return done(err); + }); + }); + }); + }); + + describe('detect_buffers', function () { + var client; + var args = config.configureClient(parser, ip, { + detect_buffers: true + }); + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(function (err) { + client.hmset("hash key 2", "key 1", "val 1", "key 2", "val 2"); + client.set("string key 1", "string value"); + return done(err); + }); + }); + }); + + describe('get', function () { + describe('first argument is a string', function () { + it('returns a string', function (done) { + client.get("string key 1", helper.isString("string value", done)); + }); + + it('returns a string when executed as part of transaction', function (done) { + client.multi().get("string key 1").exec(helper.isString("string value", done)); + }); + }); + + describe('first argument is a buffer', function () { + it('returns a buffer', function (done) { + client.get(new Buffer("string key 1"), function (err, reply) { + assert.strictEqual(true, Buffer.isBuffer(reply)); + assert.strictEqual("", reply.inspect()); + return done(err); + }); + }); + + it('returns a bufffer when executed as part of transaction', function (done) { + client.multi().get(new Buffer("string key 1")).exec(function (err, reply) { + assert.strictEqual(1, reply.length); + assert.strictEqual(true, Buffer.isBuffer(reply[0])); + assert.strictEqual("", reply[0].inspect()); + return done(err); + }); + }); + }); + }); + + describe('multi.hget', function () { + it('can interleave string and buffer results', function (done) { + client.multi() + .hget("hash key 2", "key 1") + .hget(new Buffer("hash key 2"), "key 1") + .hget("hash key 2", new Buffer("key 2")) + .hget("hash key 2", "key 2") + .exec(function (err, reply) { + assert.strictEqual(true, Array.isArray(reply)); + assert.strictEqual(4, reply.length); + assert.strictEqual("val 1", reply[0]); + assert.strictEqual(true, Buffer.isBuffer(reply[1])); + assert.strictEqual("", reply[1].inspect()); + assert.strictEqual(true, Buffer.isBuffer(reply[2])); + assert.strictEqual("", reply[2].inspect()); + assert.strictEqual("val 2", reply[3]); + return done(err); + }); + }); + }); + + describe('hmget', function () { + describe('first argument is a string', function () { + it('returns strings for keys requested', function (done) { + client.hmget("hash key 2", "key 1", "key 2", function (err, reply) { + assert.strictEqual(true, Array.isArray(reply)); + assert.strictEqual(2, reply.length); + assert.strictEqual("val 1", reply[0]); + assert.strictEqual("val 2", reply[1]); + return done(err); + }); + }); + + it('returns strings for keys requested in transaction', function (done) { + client.multi().hmget("hash key 2", "key 1", "key 2").exec(function (err, reply) { + assert.strictEqual(true, Array.isArray(reply)); + assert.strictEqual(1, reply.length); + assert.strictEqual(2, reply[0].length); + assert.strictEqual("val 1", reply[0][0]); + assert.strictEqual("val 2", reply[0][1]); + return done(err); + }); + }); + + it('handles array of strings with undefined values (repro #344)', function (done) { + client.hmget("hash key 2", "key 3", "key 4", function(err, reply) { + assert.strictEqual(true, Array.isArray(reply)); + assert.strictEqual(2, reply.length); + assert.equal(null, reply[0]); + assert.equal(null, reply[1]); + return done(err); + }); + }); + + it('handles array of strings with undefined values in transaction (repro #344)', function (done) { + client.multi().hmget("hash key 2", "key 3", "key 4").exec(function(err, reply) { + assert.strictEqual(true, Array.isArray(reply)); + assert.strictEqual(1, reply.length); + assert.strictEqual(2, reply[0].length); + assert.equal(null, reply[0][0]); + assert.equal(null, reply[0][1]); + return done(err); + }); + }); + }); + + describe('first argument is a buffer', function () { + it('returns buffers for keys requested', function (done) { + client.hmget(new Buffer("hash key 2"), "key 1", "key 2", function (err, reply) { + assert.strictEqual(true, Array.isArray(reply)); + assert.strictEqual(2, reply.length); + assert.strictEqual(true, Buffer.isBuffer(reply[0])); + assert.strictEqual(true, Buffer.isBuffer(reply[1])); + assert.strictEqual("", reply[0].inspect()); + assert.strictEqual("", reply[1].inspect()); + return done(err); + }); + }); + + it("returns buffers for keys requested in transaction", function (done) { + client.multi().hmget(new Buffer("hash key 2"), "key 1", "key 2").exec(function (err, reply) { + assert.strictEqual(true, Array.isArray(reply)); + assert.strictEqual(1, reply.length); + assert.strictEqual(2, reply[0].length); + assert.strictEqual(true, Buffer.isBuffer(reply[0][0])); + assert.strictEqual(true, Buffer.isBuffer(reply[0][1])); + assert.strictEqual("", reply[0][0].inspect()); + assert.strictEqual("", reply[0][1].inspect()); + return done(err); + }); + }); + }); + }); + + describe('hgetall', function (done) { + describe('first argument is a string', function () { + it('returns string values', function (done) { + client.hgetall("hash key 2", function (err, reply) { + assert.strictEqual("object", typeof reply); + assert.strictEqual(2, Object.keys(reply).length); + assert.strictEqual("val 1", reply["key 1"]); + assert.strictEqual("val 2", reply["key 2"]); + return done(err); + }); + }); + + it('returns string values when executed in transaction', function (done) { + client.multi().hgetall("hash key 2").exec(function (err, reply) { + assert.strictEqual(1, reply.length); + assert.strictEqual("object", typeof reply[0]); + assert.strictEqual(2, Object.keys(reply[0]).length); + assert.strictEqual("val 1", reply[0]["key 1"]); + assert.strictEqual("val 2", reply[0]["key 2"]); + return done(err); + }); + }); + }); + + describe('first argument is a buffer', function () { + it('returns buffer values', function (done) { + client.hgetall(new Buffer("hash key 2"), function (err, reply) { + assert.strictEqual(null, err); + assert.strictEqual("object", typeof reply); + assert.strictEqual(2, Object.keys(reply).length); + assert.strictEqual(true, Buffer.isBuffer(reply["key 1"])); + assert.strictEqual(true, Buffer.isBuffer(reply["key 2"])); + assert.strictEqual("", reply["key 1"].inspect()); + assert.strictEqual("", reply["key 2"].inspect()); + return done(err); + }); + }); + + it('returns buffer values when executed in transaction', function (done) { + client.multi().hgetall(new Buffer("hash key 2")).exec(function (err, reply) { + assert.strictEqual(1, reply.length); + assert.strictEqual("object", typeof reply); + assert.strictEqual(2, Object.keys(reply[0]).length); + assert.strictEqual(true, Buffer.isBuffer(reply[0]["key 1"])); + assert.strictEqual(true, Buffer.isBuffer(reply[0]["key 2"])); + assert.strictEqual("", reply[0]["key 1"].inspect()); + assert.strictEqual("", reply[0]["key 2"].inspect()); + return done(err); + }); + }); + }); + }); + }); + + describe('unref', function () { + it('exits subprocess as soon as final command is processed', function (done) { + var args = config.HOST[ip] ? [config.HOST[ip], config.PORT] : [ip]; + var external = fork("./test/lib/unref.js", args); + var id = setTimeout(function () { + external.kill(); + return done(Error('unref subprocess timed out')); + }, 5000); + + external.on("close", function (code) { + clearTimeout(id); + assert.strictEqual(code, 0); + return done(); + }); + }); + }); + + describe('socket_nodelay', function () { + describe('true', function () { + var client; + var args = config.configureClient(parser, ip, { + socket_nodelay: true + }); + + it("fires client.on('ready')", function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.on("ready", function () { + assert.strictEqual(true, client.options.socket_nodelay); + client.quit(); + + client.once('end', function () { + return done(); + }); + }); + }); + + it('client is functional', function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.on("ready", function () { + assert.strictEqual(true, client.options.socket_nodelay); + client.set(["set key 1", "set val"], helper.isString("OK")); + client.set(["set key 2", "set val"], helper.isString("OK")); + client.get(["set key 1"], helper.isString("set val")); + client.get(["set key 2"], helper.isString("set val")); + client.quit(); + + client.once('end', function () { + return done(); + }); + }); + }); + }); + + describe('false', function () { + var client; + var args = config.configureClient(parser, ip, { + socket_nodelay: false + }); + + it("fires client.on('ready')", function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.on("ready", function () { + assert.strictEqual(false, client.options.socket_nodelay); + client.quit(); + + client.once('end', function () { + return done(); + }); + }); + }); + + it('client is functional', function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.on("ready", function () { + assert.strictEqual(false, client.options.socket_nodelay); + client.set(["set key 1", "set val"], helper.isString("OK")); + client.set(["set key 2", "set val"], helper.isString("OK")); + client.get(["set key 1"], helper.isString("set val")); + client.get(["set key 2"], helper.isString("set val")); + client.quit(); + + client.once('end', function () { + return done(); + }); + }); + }); + }); + + describe('defaults to true', function () { + var client; + var args = config.configureClient(parser, ip); + + it("fires client.on('ready')", function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.on("ready", function () { + assert.strictEqual(true, client.options.socket_nodelay); + client.quit(); + + client.once('end', function () { + return done(); + }); + }); + }); + + it('client is functional', function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.on("ready", function () { + assert.strictEqual(true, client.options.socket_nodelay); + client.set(["set key 1", "set val"], helper.isString("OK")); + client.set(["set key 2", "set val"], helper.isString("OK")); + client.get(["set key 1"], helper.isString("set val")); + client.get(["set key 2"], helper.isString("set val")); + client.quit(); + + client.once('end', function () { + return done(); + }); + }); + }); + }); + }); + + describe('retry_max_delay', function () { + var client; + var args = config.configureClient(parser, ip, { + retry_max_delay: 1 + }); + + it("sets upper bound on how long client waits before reconnecting", function (done) { + var time = new Date().getTime() + var reconnecting = false; + + client = redis.createClient.apply(redis.createClient, args); + client.on('ready', function() { + if (!reconnecting) { + reconnecting = true; + client.retry_delay = 1000; + client.retry_backoff = 1; + client.stream.end(); + } else { + client.end(); + var lasted = new Date().getTime() - time; + assert.ok(lasted < 1000); + return done(); + } + }); + }); + }); + + describe('enable_offline_queue', function () { + describe('true', function () { + it("does not throw an error and enqueues operation", function (done) { + var client = redis.createClient(9999, null, { + max_attempts: 1, + 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) { + if (err) return done(err); + }); + + return setTimeout(function(){ + assert.strictEqual(client.offline_queue.length, 1); + return done(); + }, 25); + }, 50); + }); + }); + + describe('false', function () { + it("does not throw an error and enqueues operation", function (done) { + var client = redis.createClient(9999, null, { + parser: parser, + max_attempts: 1, + enable_offline_queue: false + }); + + client.on('error', function() { + // ignore, b/c expecting a "can't connect" error + }); + + assert.throws(function () { + cli.set('foo', 'bar'); + }); + + assert.doesNotThrow(function () { + client.set('foo', 'bar', function (err) { + // should callback with an error + assert.ok(err); + setTimeout(function () { + return done(); + }, 50); + }); + }); + }); + }); + }); + + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }); + }); +}); diff --git a/test/parser/javascript.spec.js b/test/parser/javascript.spec.js new file mode 100644 index 0000000000..445964371b --- /dev/null +++ b/test/parser/javascript.spec.js @@ -0,0 +1,27 @@ +/* global describe, it */ + +var assert = require('assert'); +var Parser = require("../../lib/parser/javascript").Parser; + +describe('javascript parser', function () { + it('handles multi-bulk reply', function (done) { + var parser = new Parser(false); + var reply_count = 0; + function check_reply(reply) { + assert.deepEqual(reply, [['a']], "Expecting multi-bulk reply of [['a']]"); + reply_count++; + } + parser.on("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(); + }); +}); diff --git a/test/pubsub.spec.js b/test/pubsub.spec.js new file mode 100644 index 0000000000..dc50246151 --- /dev/null +++ b/test/pubsub.spec.js @@ -0,0 +1,240 @@ +var assert = require("assert"); +var config = require("./lib/config"); +var helper = require("./helper"); +var redis = config.redis; + +describe("publish/subscribe", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var pub = null; + var sub = null; + var channel = "test channel" + var channel2 = "test channel 2" + var message = "test message" + var hash = "test hash"; + + beforeEach(function (done) { + var pubConnected; + var subConnected; + + pub = redis.createClient.apply(redis.createClient, args); + sub = redis.createClient.apply(redis.createClient, args); + pub.once("error", done); + pub.once("connect", function () { + pub.flushdb(function () { + pubConnected = true; + }); + }); + + sub.once("error", done); + sub.once("connect", function () { + subConnected = true; + }); + + var id = setInterval(function () { + if (pubConnected && subConnected) { + clearInterval(id); + return done(); + } + }, 50); + }); + + describe('subscribe', function () { + it('fires a subscribe event for each channel subscribed to', function (done) { + sub.on("subscribe", function (chnl, count) { + if (chnl === channel2) { + assert.equal(2, count) + return done(); + } + }); + + sub.subscribe(channel, channel2); + }); + + it('receives messages on subscribed channel', function (done) { + sub.on("subscribe", function (chnl, count) { + pub.publish(channel, message, helper.isNumber(1)); + }); + + sub.on("message", function (chnl, msg) { + assert.equal(chnl, channel); + assert.equal(msg, message); + return done(); + }); + + sub.subscribe(channel); + }); + + it('receives messages if subscribe is called after unsubscribe', function (done) { + if (!helper.serverVersionAtLeast(pub, [2, 6, 11])) return done(); + + sub.once("subscribe", function (chnl, count) { + pub.publish(channel, message, helper.isNumber(1)); + }); + + sub.on("message", function (chnl, msg) { + assert.equal(chnl, channel); + assert.equal(msg, message); + return done(); + }); + + sub.subscribe(channel); + sub.unsubscribe(channel); + sub.subscribe(channel); + }); + + it('handles SUB_UNSUB_MSG_SUB', function (done) { + if (!helper.serverVersionAtLeast(pub, [2, 6, 11])) return done(); + + sub.subscribe('chan8'); + sub.subscribe('chan9'); + sub.unsubscribe('chan9'); + pub.publish('chan8', 'something'); + sub.subscribe('chan9', function () { + return done(); + }); + }); + + it('handles SUB_UNSUB_MSG_SUB', function (done) { + if (!helper.serverVersionAtLeast(pub, [2, 6, 11])) return done(); + + sub.psubscribe('abc*'); + sub.subscribe('xyz'); + sub.unsubscribe('xyz'); + pub.publish('abcd', 'something'); + sub.subscribe('xyz', function () { + return done(); + }); + }); + + it('emits end event if quit is called from within subscribe', function (done) { + sub.on("end", function () { + return done(); + }); + sub.on("subscribe", function (chnl, count) { + sub.quit(); + }); + sub.subscribe(channel); + }); + + it('handles SUBSCRIBE_CLOSE_RESUBSCRIBE', function (done) { + var count = 0; + /* Create two clients. c1 subscribes to two channels, c2 will publish to them. + c2 publishes the first message. + c1 gets the message and drops its connection. It must resubscribe itself. + When it resubscribes, c2 publishes the second message, on the same channel + c1 gets the message and drops its connection. It must resubscribe itself, again. + When it resubscribes, c2 publishes the third message, on the second channel + c1 gets the message and drops its connection. When it reconnects, the test ends. + */ + sub.on("message", function(channel, message) { + if (channel === "chan1") { + assert.strictEqual(message, "hi on channel 1"); + sub.stream.end(); + } else if (channel === "chan2") { + assert.strictEqual(message, "hi on channel 2"); + sub.stream.end(); + } else { + sub.quit(); + pub.quit(); + assert.fail("test failed"); + } + }); + + sub.subscribe("chan1", "chan2"); + + sub.on("ready", function(err, results) { + count++; + if (count === 1) { + pub.publish("chan1", "hi on channel 1"); + return; + } else if (count === 2) { + pub.publish("chan2", "hi on channel 2"); + } else { + sub.quit(function() { + pub.quit(function() { + return done(); + }); + }); + } + }); + + pub.publish("chan1", "hi on channel 1"); + }); + }); + + describe('unsubscribe', function () { + it('fires an unsubscribe event', function () { + sub.on("subscribe", function (chnl, count) { + sub.unsubscribe(channel) + }); + + sub.subscribe(channel); + + sub.on("unsubscribe", function (chnl, count) { + assert.equal(chnl, channel); + assert.strictEqual(count, 0); + return done(); + }); + }); + + it('puts client back into write mode', function (done) { + sub.on("subscribe", function (chnl, count) { + sub.unsubscribe(channel) + }); + + sub.subscribe(channel); + + sub.on("unsubscribe", function (chnl, count) { + pub.incr("foo", helper.isNumber(1, done)); + }); + }) + + it('does not complain when unsubscribe is called and there are no subscriptions', function () { + sub.unsubscribe() + }); + + it('executes callback when unsubscribe is called and there are no subscriptions', function (done) { + // test hangs on older versions of redis, so skip + if (!helper.serverVersionAtLeast(pub, [2, 6, 11])) return done(); + + pub.unsubscribe(function (err, results) { + assert.strictEqual(null, results); + return done(err); + }); + }); + }); + + describe('punsubscribe', function () { + it('does not complain when punsubscribe is called and there are no subscriptions', function () { + sub.punsubscribe() + }) + + it('executes callback when punsubscribe is called and there are no subscriptions', function (done) { + // test hangs on older versions of redis, so skip + if (!helper.serverVersionAtLeast(pub, [2, 6, 11])) return done(); + + pub.punsubscribe(function (err, results) { + assert.strictEqual(null, results); + return done(err); + }); + }); + }); + + afterEach(function () { + sub.end(); + pub.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/queue-test.js b/test/queue-test.js deleted file mode 100644 index dbc9771a0a..0000000000 --- a/test/queue-test.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -var assert = require("assert"); -var Queue = require('../lib/queue'); - -module.exports = function (tests, next) { - var q = new Queue(); - - tests.push = function () { - q.push('a'); - q.push(3); - assert.equal(q.length, 2); - return next(); - }; - - tests.shift = function () { - assert.equal(q.shift(), 'a'); - return next(); - }; - - tests.forEach = function () { - q.forEach(function (v) { - assert.equal(v, 3); - }); - - return next(); - }; - - tests.forEachWithScope = function () { - q.forEach(function (v) { - assert.equal(this.foo, 'bar'); - assert.equal(v, 3); - }, {foo: 'bar'}); - - return next(); - }; -}; diff --git a/test/queue.spec.js b/test/queue.spec.js new file mode 100644 index 0000000000..f0b3c31648 --- /dev/null +++ b/test/queue.spec.js @@ -0,0 +1,37 @@ +var assert = require("assert"); +var Queue = require('../lib/queue'); + +describe('queue', function () { + var q = new Queue(); + + describe('push', function () { + it('places values on end of queue', function () { + q.push('a'); + q.push(3); + assert.equal(q.length, 2); + }); + }); + + describe('shift', function () { + it('removes values from front of queue', function () { + assert.equal(q.shift(), 'a'); + }); + }); + + describe('forEach', function () { + it('iterates over values in queue', function () { + q.forEach(function (v) { + assert.equal(v, 3); + }); + }); + }); + + describe('forEachWithScope', function () { + it('provides a scope to the iteration function', function () { + q.forEach(function (v) { + assert.equal(this.foo, 'bar'); + assert.equal(v, 3); + }, {foo: 'bar'}); + }); + }); +}); diff --git a/test/run.sh b/test/run.sh deleted file mode 100755 index ab030ba48f..0000000000 --- a/test/run.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -node ./test/test.js false hiredis -node ./test/test.js false javascript diff --git a/test/test-unref.js b/test/test-unref.js deleted file mode 100644 index c7dc930004..0000000000 --- a/test/test-unref.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -var redis = require("../"); -//redis.debug_mode = true -var PORT = process.argv[2] || 6379; -var HOST = process.argv[3] || '127.0.0.1'; - -var c = redis.createClient(PORT, HOST); -c.unref(); -c.info(function (err, reply) { - if (err) process.exit(-1); - if (!reply.length) process.exit(-1); - process.stdout.write(reply.length.toString()); -}); diff --git a/test/test.js b/test/test.js index 7b3cb1cc72..53a2b663bb 100644 --- a/test/test.js +++ b/test/test.js @@ -1,1521 +1,4 @@ -'use strict'; - -/*global require console setTimeout process Buffer */ -var PORT = process.env.REDIS_PORT_6379_TCP_PORT || 6379; -var HOST = process.env.REDIS_PORT_6379_TCP_ADDR || '127.0.0.1'; -var parser = process.argv[3]; - -var redis = require("../index"), - client = redis.createClient(PORT, HOST, { parser: parser }), - client2 = redis.createClient(PORT, HOST, { parser: parser }), - client3 = redis.createClient(PORT, HOST, { parser: parser }), - bclient = redis.createClient(PORT, HOST, { return_buffers: true, parser: parser }), - assert = require("assert"), - crypto = require("crypto"), - util = require("../lib/util"), - fork = require("child_process").fork, - test_db_num = 15, // this DB will be flushed and used for testing - tests = {}, - connected = false, - ended = false, - next, cur_start, run_next_test, all_tests, all_start, test_count; - -// Set this to truthy to see the wire protocol and other debugging info -redis.debug_mode = process.argv[2] ? JSON.parse(process.argv[2]) : false; - -function server_version_at_least(connection, desired_version) { - // Return true if the server version >= desired_version - var version = connection.server_info.versions; - for (var i = 0; i < 3; i++) { - if (version[i] > desired_version[i]) return true; - if (version[i] < desired_version[i]) return false; - } - return true; -} - -function buffers_to_strings(arr) { - return arr.map(function (val) { - return val.toString(); - }); -} - -function require_number(expected, label) { - return function (err, results) { - assert.strictEqual(null, err, label + " expected " + expected + ", got error: " + err); - assert.strictEqual(expected, results, label + " " + expected + " !== " + results); - assert.strictEqual(typeof results, "number", label); - return true; - }; -} - -function require_number_any(label) { - return function (err, results) { - assert.strictEqual(null, err, label + " expected any number, got error: " + err); - assert.strictEqual(typeof results, "number", label + " " + results + " is not a number"); - return true; - }; -} - -function require_number_pos(label) { - return function (err, results) { - assert.strictEqual(null, err, label + " expected positive number, got error: " + err); - assert.strictEqual(true, (results > 0), label + " " + results + " is not a positive number"); - return true; - }; -} - -function require_string(str, label) { - return function (err, results) { - assert.strictEqual(null, err, label + " expected string '" + str + "', got error: " + err); - assert.equal(str, results, label + " " + str + " does not match " + results); - return true; - }; -} - -function require_null(label) { - return function (err, results) { - assert.strictEqual(null, err, label + " expected null, got error: " + err); - assert.strictEqual(null, results, label + ": " + results + " is not null"); - return true; - }; -} - -function require_error(label) { - return function (err, results) { - assert.notEqual(err, null, label + " err is null, but an error is expected here."); - return true; - }; -} - -function is_empty_array(obj) { - return Array.isArray(obj) && obj.length === 0; -} - -function last(name, fn) { - return function (err, results) { - fn(err, results); - next(name); - }; -} - -// Wraps the given callback in a timeout. If the returned function -// is not called within the timeout period, we fail the named test. -function with_timeout(name, cb, millis) { - var timeoutId = setTimeout(function() { - assert.fail("Callback timed out!", name); - }, millis); - return function() { - clearTimeout(timeoutId); - cb.apply(this, arguments); - }; -} - -next = function next(name) { - console.log(" \x1b[33m" + (Date.now() - cur_start) + "\x1b[0m ms"); - run_next_test(); -}; - -// Tests are run in the order they are defined, so FLUSHDB should always be first. - -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -tests.IPV4 = function () { - var ipv4addr = process.env.REDIS_PORT_6379_TCP_ADDR || "127.0.0.1"; - var ipv4Client = redis.createClient( PORT, ipv4addr, { family : "IPv4", parser: parser } ); - - ipv4Client.once("ready", function start_tests() { - console.log("Connected to " + ipv4Client.address + ", Redis server version " + ipv4Client.server_info.redis_version + "\n"); - console.log("Using reply parser " + ipv4Client.reply_parser.name); - - ipv4Client.quit(); - run_next_test(); - }); - - ipv4Client.on('end', function () { - - }); - - // Exit immediately on connection failure, which triggers "exit", below, which fails the test - ipv4Client.on("error", function (err) { - console.error("client: " + err.stack); - process.exit(); - }); -}; - -tests.IPV6 = function () { - if (!server_version_at_least(client, [2, 8, 0])) { - console.log("Skipping IPV6 for old Redis server version < 2.8.0"); - return run_next_test(); - } - var ipv6addr = process.env.REDIS_PORT_6379_TCP_ADDR || "::1"; - var ipv6Client = redis.createClient( PORT, ipv6addr, { family: "IPv6", parser: parser } ); - - ipv6Client.once("ready", function start_tests() { - console.log("Connected to " + ipv6Client.address + ", Redis server version " + ipv6Client.server_info.redis_version + "\n"); - console.log("Using reply parser " + ipv6Client.reply_parser.name); - - ipv6Client.quit(); - run_next_test(); - }); - - ipv6Client.on('end', function () { - - }); - - // Exit immediately on connection failure, which triggers "exit", below, which fails the test - ipv6Client.on("error", function (err) { - console.error("client: " + err.stack); - process.exit(); - }); -}; - -tests.UNIX_SOCKET = function () { - var unixClient = redis.createClient('/tmp/redis.sock', { parser: parser }); - - // if this fails, check the permission of unix socket. - // unixsocket /tmp/redis.sock - // unixsocketperm 777 - - unixClient.once('ready', function start_tests(){ - console.log("Connected to " + unixClient.address + ", Redis server version " + unixClient.server_info.redis_version + "\n"); - console.log("Using reply parser " + unixClient.reply_parser.name); - - unixClient.quit(); - run_next_test(); - }); - - unixClient.on( 'end', function(){ - - }); - - // Exit immediately on connection failure, which triggers "exit", below, which fails the test - unixClient.on("error", function (err) { - console.error("client: " + err.stack); - process.exit(); - }); -}; - -tests.FLUSHDB = function () { - var name = "FLUSHDB"; - client.select(test_db_num, require_string("OK", name)); - client2.select(test_db_num, require_string("OK", name)); - client3.select(test_db_num, require_string("OK", name)); - client.mset("flush keys 1", "flush val 1", "flush keys 2", "flush val 2", require_string("OK", name)); - client.FLUSHDB(require_string("OK", name)); - client.dbsize(last(name, require_number(0, name))); -}; - -tests.INCR = function () { - var name = "INCR"; - - if (bclient.reply_parser.name === "hiredis") { - console.log("Skipping INCR buffer test with hiredis"); - return next(name); - } - - // Test incr with the maximum JavaScript number value. Since we are - // returning buffers we should get back one more as a Buffer. - bclient.set("seq", "9007199254740992", function (err, result) { - assert.strictEqual(result.toString(), "OK"); - bclient.incr("seq", function (err, result) { - assert.strictEqual("9007199254740993", result.toString()); - next(name); - }); - }); -}; - -tests.MULTI_1 = function () { - var name = "MULTI_1", multi1, multi2; - - // Provoke an error at queue time - multi1 = client.multi(); - multi1.mset("multifoo", "10", "multibar", "20", require_string("OK", name)); - multi1.set("foo2", require_error(name)); - multi1.incr("multifoo", require_number(11, name)); - multi1.incr("multibar", require_number(21, name)); - multi1.exec(function () { - require_error(name); - - // Redis 2.6.5+ will abort transactions with errors - // see: http://redis.io/topics/transactions - var multibar_expected = 22; - var multifoo_expected = 12; - if (server_version_at_least(client, [2, 6, 5])) { - multibar_expected = 1; - multifoo_expected = 1; - } - - // Confirm that the previous command, while containing an error, still worked. - multi2 = client.multi(); - multi2.incr("multibar", require_number(multibar_expected, name)); - multi2.incr("multifoo", require_number(multifoo_expected, name)); - multi2.exec(function (err, replies) { - assert.strictEqual(multibar_expected, replies[0]); - assert.strictEqual(multifoo_expected, replies[1]); - next(name); - }); - }); -}; - -tests.MULTI_2 = function () { - var name = "MULTI_2"; - - // test nested multi-bulk replies - client.multi([ - ["mget", "multifoo", "multibar", function (err, res) { - assert.strictEqual(2, res.length, name); - assert.strictEqual("12", res[0].toString(), name); - assert.strictEqual("22", res[1].toString(), name); - }], - ["set", "foo2", require_error(name)], - ["incr", "multifoo", require_number(13, name)], - ["incr", "multibar", require_number(23, name)] - - ]).exec(function (err, replies) { - - if (server_version_at_least(client, [2, 6, 5])) { - assert.notEqual(err, null, name); - assert.equal(replies, undefined, name); - } else { - assert.strictEqual(2, replies[0].length, name); - assert.strictEqual("12", replies[0][0].toString(), name); - assert.strictEqual("22", replies[0][1].toString(), name); - - assert.strictEqual("13", replies[1].toString()); - assert.strictEqual("23", replies[2].toString()); - } - next(name); - }); -}; - -tests.MULTI_3 = function () { - var name = "MULTI_3"; - - client.sadd("some set", "mem 1"); - client.sadd("some set", "mem 2"); - client.sadd("some set", "mem 3"); - client.sadd("some set", "mem 4"); - - // make sure empty mb reply works - client.del("some missing set"); - client.smembers("some missing set", function (err, reply) { - // make sure empty mb reply works - assert.strictEqual(true, is_empty_array(reply), name); - }); - - // test nested multi-bulk replies with empty mb elements. - client.multi([ - ["smembers", "some set"], - ["del", "some set"], - ["smembers", "some set"] - ]) - .scard("some set") - .exec(function (err, replies) { - assert.strictEqual(true, is_empty_array(replies[2]), name); - next(name); - }); -}; - -tests.MULTI_4 = function () { - var name = "MULTI_4"; - - client.multi() - .mset('some', '10', 'keys', '20') - .incr('some') - .incr('keys') - .mget('some', 'keys') - .exec(function (err, replies) { - assert.strictEqual(null, err); - assert.equal('OK', replies[0]); - assert.equal(11, replies[1]); - assert.equal(21, replies[2]); - assert.equal(11, replies[3][0].toString()); - assert.equal(21, replies[3][1].toString()); - next(name); - }); -}; - -tests.MULTI_5 = function () { - var name = "MULTI_5"; - - // test nested multi-bulk replies with nulls. - client.multi([ - ["mget", ["multifoo", "some", "random value", "keys"]], - ["incr", "multifoo"] - ]) - .exec(function (err, replies) { - assert.strictEqual(replies.length, 2, name); - assert.strictEqual(replies[0].length, 4, name); - next(name); - }); -}; - -tests.MULTI_6 = function () { - var name = "MULTI_6"; - - client.multi() - .hmset("multihash", "a", "foo", "b", 1) - .hmset("multihash", { - extra: "fancy", - things: "here" - }) - .hgetall("multihash") - .exec(function (err, replies) { - assert.strictEqual(null, err); - assert.equal("OK", replies[0]); - assert.equal(Object.keys(replies[2]).length, 4); - assert.equal("foo", replies[2].a); - assert.equal("1", replies[2].b); - assert.equal("fancy", replies[2].extra); - assert.equal("here", replies[2].things); - next(name); - }); -}; - -// THIS TEST SHOULD BE MOVED IN TO A PARSER -// SPECIFIC TESTING FILE. -tests.MULTI_7 = function () { - var name = "MULTI_7"; - - if (bclient.reply_parser.name !== "javascript") { - console.log("Skipping wire-protocol test for 3rd-party parser"); - return next(name); - } - - var p = require("../lib/parser/javascript"); - var parser = new p.Parser(false); - var reply_count = 0; - function check_reply(reply) { - assert.deepEqual(reply, [['a']], "Expecting multi-bulk reply of [['a']]"); - reply_count++; - assert.notEqual(reply_count, 4, "Should only parse 3 replies"); - } - parser.on("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')); - - next(name); -}; - -tests.FWD_ERRORS_1 = function () { - var name = "FWD_ERRORS_1"; - - var toThrow = new Error("Forced exception"); - var recordedError = null; - - var originalHandlers = { - "error": client3.listeners("error"), - "message": client3.listeners("message") - }; - client3.removeAllListeners("error"); - client3.removeAllListeners("message"); - client3.once("error", function (err) { - recordedError = err; - }); - - client3.on("message", function (channel, data) { - console.log("incoming"); - if (channel === name) { - assert.equal(data, "Some message"); - throw toThrow; - } - }); - client3.subscribe(name); - - client.publish(name, "Some message"); - setTimeout(function () { - assert.equal(recordedError, toThrow, "Should have caught our forced exception"); - client3.unsubscribe(name); - client3.removeAllListeners("message"); - originalHandlers.error.forEach(function (fn) { - client3.on("error", fn); - }); - originalHandlers.message.forEach(function (fn) { - client3.on("message", fn); - }); - next(name); - }, 150); -}; - -tests.EVAL_1 = function () { - var name = "EVAL_1"; - - if (!server_version_at_least(client, [2, 5, 0])) { - console.log("Skipping " + name + " for old Redis server version < 2.5.x"); - return next(name); - } - - // test {EVAL - Lua integer -> Redis protocol type conversion} - client.eval("return 100.5", 0, require_number(100, name)); - // test {EVAL - Lua string -> Redis protocol type conversion} - client.eval("return 'hello world'", 0, require_string("hello world", name)); - // test {EVAL - Lua true boolean -> Redis protocol type conversion} - client.eval("return true", 0, require_number(1, name)); - // test {EVAL - Lua false boolean -> Redis protocol type conversion} - client.eval("return false", 0, require_null(name)); - // test {EVAL - Lua status code reply -> Redis protocol type conversion} - client.eval("return {ok='fine'}", 0, require_string("fine", name)); - // test {EVAL - Lua error reply -> Redis protocol type conversion} - client.eval("return {err='this is an error'}", 0, require_error(name)); - // test {EVAL - Lua table -> Redis protocol type conversion} - client.eval("return {1,2,3,'ciao',{1,2}}", 0, function (err, res) { - assert.strictEqual(5, res.length, name); - assert.strictEqual(1, res[0], name); - assert.strictEqual(2, res[1], name); - assert.strictEqual(3, res[2], name); - assert.strictEqual("ciao", res[3], name); - assert.strictEqual(2, res[4].length, name); - assert.strictEqual(1, res[4][0], name); - assert.strictEqual(2, res[4][1], name); - }); - // test {EVAL - Are the KEYS and ARGS arrays populated correctly?} - client.eval("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", 2, "a", "b", "c", "d", function (err, res) { - assert.strictEqual(4, res.length, name); - assert.strictEqual("a", res[0], name); - assert.strictEqual("b", res[1], name); - assert.strictEqual("c", res[2], name); - assert.strictEqual("d", res[3], name); - }); - - // test {EVAL - parameters in array format gives same result} - client.eval(["return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", 2, "a", "b", "c", "d"], function (err, res) { - assert.strictEqual(4, res.length, name); - assert.strictEqual("a", res[0], name); - assert.strictEqual("b", res[1], name); - assert.strictEqual("c", res[2], name); - assert.strictEqual("d", res[3], name); - }); - - // prepare sha sum for evalsha cache test - var source = "return redis.call('get', 'sha test')", - sha = crypto.createHash('sha1').update(source).digest('hex'); - - client.set("sha test", "eval get sha test", function (err, res) { - if (err) throw err; - // test {EVAL - is Lua able to call Redis API?} - client.eval(source, 0, function (err, res) { - require_string("eval get sha test", name)(err, res); - // test {EVALSHA - Can we call a SHA1 if already defined?} - client.evalsha(sha, 0, require_string("eval get sha test", name)); - // test {EVALSHA - Do we get an error on non defined SHA1?} - client.evalsha("ffffffffffffffffffffffffffffffffffffffff", 0, require_error(name)); - }); - }); - - // test {EVAL - Redis integer -> Lua type conversion} - client.set("incr key", 0, function (err, reply) { - if (err) throw err; - client.eval("local foo = redis.call('incr','incr key')\n" + "return {type(foo),foo}", 0, function (err, res) { - if (err) throw err; - assert.strictEqual(2, res.length, name); - assert.strictEqual("number", res[0], name); - assert.strictEqual(1, res[1], name); - }); - }); - - client.set("bulk reply key", "bulk reply value", function (err, res) { - // test {EVAL - Redis bulk -> Lua type conversion} - client.eval("local foo = redis.call('get','bulk reply key'); return {type(foo),foo}", 0, function (err, res) { - if (err) throw err; - assert.strictEqual(2, res.length, name); - assert.strictEqual("string", res[0], name); - assert.strictEqual("bulk reply value", res[1], name); - }); - }); - - // test {EVAL - Redis multi bulk -> Lua type conversion} - client.multi() - .del("mylist") - .rpush("mylist", "a") - .rpush("mylist", "b") - .rpush("mylist", "c") - .exec(function (err, replies) { - if (err) throw err; - client.eval("local foo = redis.call('lrange','mylist',0,-1); return {type(foo),foo[1],foo[2],foo[3],# foo}", 0, function (err, res) { - assert.strictEqual(5, res.length, name); - assert.strictEqual("table", res[0], name); - assert.strictEqual("a", res[1], name); - assert.strictEqual("b", res[2], name); - assert.strictEqual("c", res[3], name); - assert.strictEqual(3, res[4], name); - }); - }); - // test {EVAL - Redis status reply -> Lua type conversion} - client.eval("local foo = redis.call('set','mykey','myval'); return {type(foo),foo['ok']}", 0, function (err, res) { - if (err) throw err; - assert.strictEqual(2, res.length, name); - assert.strictEqual("table", res[0], name); - assert.strictEqual("OK", res[1], name); - }); - // test {EVAL - Redis error reply -> Lua type conversion} - client.set("error reply key", "error reply value", function (err, res) { - if (err) throw err; - client.eval("local foo = redis.pcall('incr','error reply key'); return {type(foo),foo['err']}", 0, function (err, res) { - if (err) throw err; - assert.strictEqual(2, res.length, name); - assert.strictEqual("table", res[0], name); - assert.strictEqual("ERR value is not an integer or out of range", res[1], name); - }); - }); - // test {EVAL - Redis nil bulk reply -> Lua type conversion} - client.del("nil reply key", function (err, res) { - if (err) throw err; - client.eval("local foo = redis.call('get','nil reply key'); return {type(foo),foo == false}", 0, function (err, res) { - if (err) throw err; - assert.strictEqual(2, res.length, name); - assert.strictEqual("boolean", res[0], name); - assert.strictEqual(1, res[1], name); - next(name); - }); - }); -}; - -tests.SCRIPT_LOAD = function() { - var name = "SCRIPT_LOAD", - command = "return 1", - commandSha = crypto.createHash('sha1').update(command).digest('hex'); - - if (!server_version_at_least(client, [2, 6, 0])) { - console.log("Skipping " + name + " for old Redis server version < 2.6.x"); - return next(name); - } - - bclient.script("load", command, function(err, result) { - assert.strictEqual(result.toString(), commandSha); - client.multi().script("load", command).exec(function(err, result) { - assert.strictEqual(result[0].toString(), commandSha); - client.multi([['script', 'load', command]]).exec(function(err, result) { - assert.strictEqual(result[0].toString(), commandSha); - next(name); - }); - }); - }); -}; - -tests.CLIENT_LIST = function() { - var name = "CLIENT_LIST"; - - if (!server_version_at_least(client, [2, 4, 0])) { - console.log("Skipping " + name + " for old Redis server version < 2.4.x"); - return next(name); - } - - var pattern = /^addr=/; - if ( server_version_at_least(client, [2, 8, 12])) { - pattern = /^id=\d+ addr=/; - } - - function checkResult(result) { - var lines = result.toString().split('\n').slice(0, -1); - assert.strictEqual(lines.length, 4); - assert(lines.every(function(line) { - return line.match(pattern); - })); - } - - bclient.client("list", function(err, result) { - console.log(result.toString()); - checkResult(result); - client.multi().client("list").exec(function(err, result) { - console.log(result.toString()); - checkResult(result); - client.multi([['client', 'list']]).exec(function(err, result) { - console.log(result.toString()); - checkResult(result); - next(name); - }); - }); - }); -}; - -tests.WATCH_MULTI = function () { - var name = 'WATCH_MULTI', multi; - if (!server_version_at_least(client, [2, 2, 0])) { - console.log("Skipping " + name + " for old Redis server version < 2.2.x"); - return next(name); - } - - client.watch(name); - client.incr(name); - multi = client.multi(); - multi.incr(name); - multi.exec(last(name, require_null(name))); -}; - -tests.WATCH_TRANSACTION = function () { - var name = "WATCH_TRANSACTION"; - - if (!server_version_at_least(client, [2, 1, 0])) { - console.log("Skipping " + name + " because server version isn't new enough."); - return next(name); - } - - // Test WATCH command aborting transactions, look for parser offset errors. - - client.set("unwatched", 200); - - client.set(name, 0); - client.watch(name); - client.incr(name); - var multi = client.multi() - .incr(name) - .exec(function (err, replies) { - // Failure expected because of pre-multi incr - assert.strictEqual(replies, null, "Aborted transaction multi-bulk reply should be null."); - - client.get("unwatched", function (err, reply) { - assert.equal(err, null, name); - assert.equal(reply, 200, "Expected 200, got " + reply); - next(name); - }); - }); - - client.set("unrelated", 100, function (err, reply) { - assert.equal(err, null, name); - assert.equal(reply, "OK", "Expected 'OK', got " + reply); - }); -}; - - -tests.detect_buffers = function () { - var name = "detect_buffers", detect_client = redis.createClient(PORT, HOST, { detect_buffers: true, parser: parser }); - - detect_client.on("ready", function () { - // single Buffer or String - detect_client.set("string key 1", "string value"); - detect_client.get("string key 1", require_string("string value", name)); - detect_client.get(new Buffer("string key 1"), function (err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual(true, Buffer.isBuffer(reply), name); - assert.strictEqual("", reply.inspect(), name); - }); - - detect_client.hmset("hash key 2", "key 1", "val 1", "key 2", "val 2"); - // array of Buffers or Strings - detect_client.hmget("hash key 2", "key 1", "key 2", function (err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual(true, Array.isArray(reply), name); - assert.strictEqual(2, reply.length, name); - assert.strictEqual("val 1", reply[0], name); - assert.strictEqual("val 2", reply[1], name); - }); - detect_client.hmget(new Buffer("hash key 2"), "key 1", "key 2", function (err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual(true, Array.isArray(reply)); - assert.strictEqual(2, reply.length, name); - assert.strictEqual(true, Buffer.isBuffer(reply[0])); - assert.strictEqual(true, Buffer.isBuffer(reply[1])); - assert.strictEqual("", reply[0].inspect(), name); - assert.strictEqual("", reply[1].inspect(), name); - }); - - // array of strings with undefined values (repro #344) - detect_client.hmget("hash key 2", "key 3", "key 4", function(err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual(true, Array.isArray(reply), name); - assert.strictEqual(2, reply.length, name); - assert.equal(null, reply[0], name); - assert.equal(null, reply[1], name); - }); - - // Object of Buffers or Strings - detect_client.hgetall("hash key 2", function (err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual("object", typeof reply, name); - assert.strictEqual(2, Object.keys(reply).length, name); - assert.strictEqual("val 1", reply["key 1"], name); - assert.strictEqual("val 2", reply["key 2"], name); - }); - detect_client.hgetall(new Buffer("hash key 2"), function (err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual("object", typeof reply, name); - assert.strictEqual(2, Object.keys(reply).length, name); - assert.strictEqual(true, Buffer.isBuffer(reply["key 1"])); - assert.strictEqual(true, Buffer.isBuffer(reply["key 2"])); - assert.strictEqual("", reply["key 1"].inspect(), name); - assert.strictEqual("", reply["key 2"].inspect(), name); - }); - - detect_client.quit(function (err, res) { - next(name); - }); - }); -}; - -tests.detect_buffers_multi = function () { - var name = "detect_buffers_multi", detect_client = redis.createClient(PORT, HOST, {detect_buffers: true}); - - detect_client.on("ready", function () { - // single Buffer or String - detect_client.set("string key 1", "string value"); - detect_client.multi().get("string key 1").exec(require_string("string value", name)); - detect_client.multi().get(new Buffer("string key 1")).exec(function (err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual(1, reply.length, name); - assert.strictEqual(true, Buffer.isBuffer(reply[0]), name); - assert.strictEqual("", reply[0].inspect(), name); - }); - - detect_client.hmset("hash key 2", "key 1", "val 1", "key 2", "val 2"); - // array of Buffers or Strings - detect_client.multi().hmget("hash key 2", "key 1", "key 2").exec(function (err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual(true, Array.isArray(reply), name); - assert.strictEqual(1, reply.length, name); - assert.strictEqual(2, reply[0].length, name); - assert.strictEqual("val 1", reply[0][0], name); - assert.strictEqual("val 2", reply[0][1], name); - }); - detect_client.multi().hmget(new Buffer("hash key 2"), "key 1", "key 2").exec(function (err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual(true, Array.isArray(reply)); - assert.strictEqual(1, reply.length, name); - assert.strictEqual(2, reply[0].length, name); - assert.strictEqual(true, Buffer.isBuffer(reply[0][0])); - assert.strictEqual(true, Buffer.isBuffer(reply[0][1])); - assert.strictEqual("", reply[0][0].inspect(), name); - assert.strictEqual("", reply[0][1].inspect(), name); - }); - - // array of strings with undefined values (repro #344) - detect_client.multi().hmget("hash key 2", "key 3", "key 4").exec(function(err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual(true, Array.isArray(reply), name); - assert.strictEqual(1, reply.length, name); - assert.strictEqual(2, reply[0].length, name); - assert.equal(null, reply[0][0], name); - assert.equal(null, reply[0][1], name); - }); - - // Object of Buffers or Strings - detect_client.multi().hgetall("hash key 2").exec(function (err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual(1, reply.length, name); - assert.strictEqual("object", typeof reply[0], name); - assert.strictEqual(2, Object.keys(reply[0]).length, name); - assert.strictEqual("val 1", reply[0]["key 1"], name); - assert.strictEqual("val 2", reply[0]["key 2"], name); - }); - detect_client.multi().hgetall(new Buffer("hash key 2")).exec(function (err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual(1, reply.length, name); - assert.strictEqual("object", typeof reply, name); - assert.strictEqual(2, Object.keys(reply[0]).length, name); - assert.strictEqual(true, Buffer.isBuffer(reply[0]["key 1"])); - assert.strictEqual(true, Buffer.isBuffer(reply[0]["key 2"])); - assert.strictEqual("", reply[0]["key 1"].inspect(), name); - assert.strictEqual("", reply[0]["key 2"].inspect(), name); - }); - - // Can interleave string and buffer results: - detect_client.multi() - .hget("hash key 2", "key 1") - .hget(new Buffer("hash key 2"), "key 1") - .hget("hash key 2", new Buffer("key 2")) - .hget("hash key 2", "key 2") - .exec(function (err, reply) { - assert.strictEqual(null, err, name); - assert.strictEqual(true, Array.isArray(reply)); - assert.strictEqual(4, reply.length, name); - assert.strictEqual("val 1", reply[0], name); - assert.strictEqual(true, Buffer.isBuffer(reply[1])); - assert.strictEqual("", reply[1].inspect(), name); - assert.strictEqual(true, Buffer.isBuffer(reply[2])); - assert.strictEqual("", reply[2].inspect(), name); - assert.strictEqual("val 2", reply[3], name); - }); - - detect_client.quit(function (err, res) { - next(name); - }); - }); -}; - -tests.socket_nodelay = function () { - var name = "socket_nodelay", c1, c2, c3, ready_count = 0, quit_count = 0; - - c1 = redis.createClient(PORT, HOST, { socket_nodelay: true, parser: parser }); - c2 = redis.createClient(PORT, HOST, { socket_nodelay: false, parser: parser }); - c3 = redis.createClient(PORT, HOST, { parser: parser }); - - function quit_check() { - quit_count++; - - if (quit_count === 3) { - next(name); - } - } - - function run() { - assert.strictEqual(true, c1.options.socket_nodelay, name); - assert.strictEqual(false, c2.options.socket_nodelay, name); - assert.strictEqual(true, c3.options.socket_nodelay, name); - - c1.set(["set key 1", "set val"], require_string("OK", name)); - c1.set(["set key 2", "set val"], require_string("OK", name)); - c1.get(["set key 1"], require_string("set val", name)); - c1.get(["set key 2"], require_string("set val", name)); - - c2.set(["set key 3", "set val"], require_string("OK", name)); - c2.set(["set key 4", "set val"], require_string("OK", name)); - c2.get(["set key 3"], require_string("set val", name)); - c2.get(["set key 4"], require_string("set val", name)); - - c3.set(["set key 5", "set val"], require_string("OK", name)); - c3.set(["set key 6", "set val"], require_string("OK", name)); - c3.get(["set key 5"], require_string("set val", name)); - c3.get(["set key 6"], require_string("set val", name)); - - c1.quit(quit_check); - c2.quit(quit_check); - c3.quit(quit_check); - } - - function ready_check() { - ready_count++; - if (ready_count === 3) { - run(); - } - } - - c1.on("ready", ready_check); - c2.on("ready", ready_check); - c3.on("ready", ready_check); -}; - - -tests.idle = function () { - var name = "idle"; - - client.on("idle", function on_idle() { - client.removeListener("idle", on_idle); - next(name); - }); - - client.set("idle", "test"); -}; - -tests.HSET = function () { - var key = "test hash", - field1 = new Buffer("0123456789"), - value1 = new Buffer("abcdefghij"), - field2 = new Buffer(0), - value2 = new Buffer(0), - name = "HSET"; - - client.HSET(key, field1, value1, require_number(1, name)); - client.HGET(key, field1, require_string(value1.toString(), name)); - - // Empty value - client.HSET(key, field1, value2, require_number(0, name)); - client.HGET([key, field1], require_string("", name)); - - // Empty key, empty value - client.HSET([key, field2, value1], require_number(1, name)); - client.HSET(key, field2, value2, last(name, require_number(0, name))); -}; - -tests.HLEN = function () { - var key = "test hash", - field1 = new Buffer("0123456789"), - value1 = new Buffer("abcdefghij"), - field2 = new Buffer(0), - value2 = new Buffer(0), - name = "HSET", - timeout = 1000; - - client.HSET(key, field1, value1, function (err, results) { - client.HLEN(key, function (err, len) { - assert.ok(2 === +len); - next(name); - }); - }); -}; - -tests.HMSET_BUFFER_AND_ARRAY = function () { - // Saving a buffer and an array to the same key should not error - var key = "test hash", - field1 = "buffer", - value1 = new Buffer("abcdefghij"), - field2 = "array", - value2 = ["array contents"], - name = "HSET"; - - client.HMSET(key, field1, value1, field2, value2, last(name, require_string("OK", name))); -}; - -// TODO - add test for HMSET with optional callbacks - -tests.HMGET = function () { - var key1 = "test hash 1", key2 = "test hash 2", key3 = 123456789, name = "HMGET"; - - // redis-like hmset syntax - client.HMSET(key1, "0123456789", "abcdefghij", "some manner of key", "a type of value", require_string("OK", name)); - - // fancy hmset syntax - client.HMSET(key2, { - "0123456789": "abcdefghij", - "some manner of key": "a type of value" - }, require_string("OK", name)); - - // test for numeric key - client.HMSET(key3, { - "0123456789": "abcdefghij", - "some manner of key": "a type of value" - }, require_string("OK", name)); - - client.HMGET(key1, "0123456789", "some manner of key", function (err, reply) { - assert.strictEqual("abcdefghij", reply[0].toString(), name); - assert.strictEqual("a type of value", reply[1].toString(), name); - }); - - client.HMGET(key2, "0123456789", "some manner of key", function (err, reply) { - assert.strictEqual("abcdefghij", reply[0].toString(), name); - assert.strictEqual("a type of value", reply[1].toString(), name); - }); - - client.HMGET(key3, "0123456789", "some manner of key", function (err, reply) { - assert.strictEqual("abcdefghij", reply[0].toString(), name); - assert.strictEqual("a type of value", reply[1].toString(), name); - }); - - client.HMGET(key1, ["0123456789"], function (err, reply) { - assert.strictEqual("abcdefghij", reply[0], name); - }); - - client.HMGET(key1, ["0123456789", "some manner of key"], function (err, reply) { - assert.strictEqual("abcdefghij", reply[0], name); - assert.strictEqual("a type of value", reply[1], name); - }); - - client.HMGET(key1, "missing thing", "another missing thing", function (err, reply) { - assert.strictEqual(null, reply[0], name); - assert.strictEqual(null, reply[1], name); - next(name); - }); -}; - -tests.HINCRBY = function () { - var name = "HINCRBY"; - client.hset("hash incr", "value", 10, require_number(1, name)); - client.HINCRBY("hash incr", "value", 1, require_number(11, name)); - client.HINCRBY("hash incr", "value 2", 1, last(name, require_number(1, name))); -}; - -tests.SUBSCRIBE = function () { - var client1 = client, msg_count = 0, name = "SUBSCRIBE"; - - client1.on("subscribe", function (channel, count) { - if (channel === "chan1") { - client2.publish("chan1", "message 1", require_number(1, name)); - client2.publish("chan2", "message 2", require_number(1, name)); - client2.publish("chan1", "message 3", require_number(1, name)); - } - }); - - client1.on("unsubscribe", function (channel, count) { - if (count === 0) { - // make sure this connection can go into and out of pub/sub mode - client1.incr("did a thing", last(name, require_number(2, name))); - } - }); - - client1.on("message", function (channel, message) { - msg_count += 1; - assert.strictEqual("message " + msg_count, message.toString()); - if (msg_count === 3) { - client1.unsubscribe("chan1", "chan2"); - } - }); - - client1.set("did a thing", 1, require_string("OK", name)); - client1.subscribe("chan1", "chan2", function (err, results) { - assert.strictEqual(null, err, "result sent back unexpected error: " + err); - assert.strictEqual("chan1", results.toString(), name); - }); -}; - -tests.UNSUB_EMPTY = function () { - // test situation where unsubscribe reply[1] is null - var name = "UNSUB_EMPTY"; - client3.unsubscribe(); // unsubscribe from all so can test null - client3.unsubscribe(); // reply[1] will be null - next(name); -}; - -tests.PUNSUB_EMPTY = function () { - // test situation where punsubscribe reply[1] is null - var name = "PUNSUB_EMPTY"; - client3.punsubscribe(); // punsubscribe from all so can test null - client3.punsubscribe(); // reply[1] will be null - next(name); -}; - -tests.UNSUB_EMPTY_CB = function () { - var name = "UNSUB_EMPTY_CB"; - // test hangs on older versions of redis, so skip - if (!server_version_at_least(client, [2, 6, 11])) return next(name); - - // test situation where unsubscribe reply[1] is null - client3.unsubscribe(); // unsubscribe from all so can test null - client3.unsubscribe(function (err, results) { - // reply[1] will be null - assert.strictEqual(null, err, "unexpected error: " + err); - next(name); - }); -}; - -tests.PUNSUB_EMPTY_CB = function () { - var name = "PUNSUB_EMPTY_CB"; - // test hangs on older versions of redis, so skip - if (!server_version_at_least(client, [2, 6, 11])) return next(name); - - // test situation where punsubscribe reply[1] is null - client3.punsubscribe(); // punsubscribe from all so can test null - client3.punsubscribe(function (err, results) { - // reply[1] will be null - assert.strictEqual(null, err, "unexpected error: " + err); - next(name); - }); -}; - -tests.SUB_UNSUB_SUB = function () { - var name = "SUB_UNSUB_SUB"; - // test hangs on older versions of redis, so skip - if (!server_version_at_least(client, [2, 6, 11])) return next(name); - - client3.subscribe('chan3'); - client3.unsubscribe('chan3'); - client3.subscribe('chan3', function (err, results) { - assert.strictEqual(null, err, "unexpected error: " + err); - client2.publish('chan3', 'foo'); - }); - client3.on('message', function (channel, message) { - assert.strictEqual(channel, 'chan3'); - assert.strictEqual(message, 'foo'); - client3.removeAllListeners(); - next(name); - }); -}; - -tests.SUB_UNSUB_MSG_SUB = function () { - var name = "SUB_UNSUB_MSG_SUB"; - // test hangs on older versions of redis, so skip - if (!server_version_at_least(client, [2, 6, 11])) return next(name); - - client3.subscribe('chan8'); - client3.subscribe('chan9'); - client3.unsubscribe('chan9'); - client2.publish('chan8', 'something'); - client3.subscribe('chan9', with_timeout(name, function (err, results) { - next(name); - }, 2000)); -}; - -tests.PSUB_UNSUB_PMSG_SUB = function () { - var name = "PSUB_UNSUB_PMSG_SUB"; - // test hangs on older versions of redis, so skip - if (!server_version_at_least(client, [2, 6, 11])) return next(name); - - client3.psubscribe('abc*'); - client3.subscribe('xyz'); - client3.unsubscribe('xyz'); - client2.publish('abcd', 'something'); - client3.subscribe('xyz', with_timeout(name, function (err, results) { - next(name); - }, 2000)); -}; - -tests.SUBSCRIBE_QUIT = function () { - var name = "SUBSCRIBE_QUIT"; - client3.on("end", function () { - next(name); - }); - client3.on("subscribe", function (channel, count) { - client3.quit(); - }); - client3.subscribe("chan3"); -}; - -tests.SUBSCRIBE_CLOSE_RESUBSCRIBE = function () { - var name = "SUBSCRIBE_CLOSE_RESUBSCRIBE"; - var c1 = redis.createClient(PORT, HOST, { parser: parser }); - var c2 = redis.createClient(PORT, HOST, { parser: parser }); - var count = 0; - - /* Create two clients. c1 subscribes to two channels, c2 will publish to them. - c2 publishes the first message. - c1 gets the message and drops its connection. It must resubscribe itself. - When it resubscribes, c2 publishes the second message, on the same channel - c1 gets the message and drops its connection. It must resubscribe itself, again. - When it resubscribes, c2 publishes the third message, on the second channel - c1 gets the message and drops its connection. When it reconnects, the test ends. - */ - - c1.on("message", function(channel, message) { - if (channel === "chan1") { - assert.strictEqual(message, "hi on channel 1"); - c1.stream.end(); - - } else if (channel === "chan2") { - assert.strictEqual(message, "hi on channel 2"); - c1.stream.end(); - - } else { - c1.quit(); - c2.quit(); - assert.fail("test failed"); - } - }); - - c1.subscribe("chan1", "chan2"); - - c2.once("ready", function() { - console.log("c2 is ready"); - c1.on("ready", function(err, results) { - console.log("c1 is ready", count); - - count++; - if (count === 1) { - c2.publish("chan1", "hi on channel 1"); - return; - - } else if (count === 2) { - c2.publish("chan2", "hi on channel 2"); - - } else { - c1.quit(function() { - c2.quit(function() { - next(name); - }); - }); - } - }); - - c2.publish("chan1", "hi on channel 1"); - - }); -}; - -tests.EXISTS = function () { - var name = "EXISTS"; - client.del("foo", "foo2", require_number_any(name)); - client.set("foo", "bar", require_string("OK", name)); - client.EXISTS("foo", require_number(1, name)); - client.EXISTS("foo2", last(name, require_number(0, name))); -}; - -tests.DEL = function () { - var name = "DEL"; - client.DEL("delkey", require_number_any(name)); - client.set("delkey", "delvalue", require_string("OK", name)); - client.DEL("delkey", require_number(1, name)); - client.exists("delkey", require_number(0, name)); - client.DEL("delkey", require_number(0, name)); - client.mset("delkey", "delvalue", "delkey2", "delvalue2", require_string("OK", name)); - client.DEL("delkey", "delkey2", last(name, require_number(2, name))); -}; - -tests.TYPE = function () { - var name = "TYPE"; - client.set(["string key", "should be a string"], require_string("OK", name)); - client.rpush(["list key", "should be a list"], require_number_pos(name)); - client.sadd(["set key", "should be a set"], require_number_any(name)); - client.zadd(["zset key", "10.0", "should be a zset"], require_number_any(name)); - client.hset(["hash key", "hashtest", "should be a hash"], require_number_any(0, name)); - - client.TYPE(["string key"], require_string("string", name)); - client.TYPE(["list key"], require_string("list", name)); - client.TYPE(["set key"], require_string("set", name)); - client.TYPE(["zset key"], require_string("zset", name)); - client.TYPE("not here yet", require_string("none", name)); - client.TYPE(["hash key"], last(name, require_string("hash", name))); -}; - -tests.KEYS = function () { - var name = "KEYS"; - client.mset(["test keys 1", "test val 1", "test keys 2", "test val 2"], require_string("OK", name)); - client.KEYS(["test keys*"], function (err, results) { - assert.strictEqual(null, err, "result sent back unexpected error: " + err); - assert.strictEqual(2, results.length, name); - assert.ok(~results.indexOf("test keys 1")); - assert.ok(~results.indexOf("test keys 2")); - next(name); - }); -}; - -tests.MULTIBULK = function() { - var name = "MULTIBULK", - keys_values = []; - - for (var i = 0; i < 200; i++) { - var key_value = [ - "multibulk:" + crypto.randomBytes(256).toString("hex"), // use long strings as keys to ensure generation of large packet - "test val " + i - ]; - keys_values.push(key_value); - } - - client.mset(keys_values.reduce(function(a, b) { - return a.concat(b); - }), require_string("OK", name)); - - client.KEYS("multibulk:*", function(err, results) { - assert.strictEqual(null, err, "result sent back unexpected error: " + err); - assert.deepEqual(keys_values.map(function(val) { - return val[0]; - }).sort(), results.sort(), name); - }); - - next(name); -}; - -tests.MULTIBULK_ZERO_LENGTH = function () { - var name = "MULTIBULK_ZERO_LENGTH"; - client.KEYS(['users:*'], function (err, results) { - assert.strictEqual(null, err, 'error on empty multibulk reply'); - assert.strictEqual(true, is_empty_array(results), "not an empty array"); - next(name); - }); -}; - -tests.RANDOMKEY = function () { - var name = "RANDOMKEY"; - client.mset(["test keys 1", "test val 1", "test keys 2", "test val 2"], require_string("OK", name)); - client.RANDOMKEY([], function (err, results) { - assert.strictEqual(null, err, name + " result sent back unexpected error: " + err); - assert.strictEqual(true, /\w+/.test(results), name); - next(name); - }); -}; - -tests.RENAME = function () { - var name = "RENAME"; - client.set(['foo', 'bar'], require_string("OK", name)); - client.RENAME(["foo", "new foo"], require_string("OK", name)); - client.exists(["foo"], require_number(0, name)); - client.exists(["new foo"], last(name, require_number(1, name))); -}; - -tests.RENAMENX = function () { - var name = "RENAMENX"; - client.set(['foo', 'bar'], require_string("OK", name)); - client.set(['foo2', 'bar2'], require_string("OK", name)); - client.RENAMENX(["foo", "foo2"], require_number(0, name)); - client.exists(["foo"], require_number(1, name)); - client.exists(["foo2"], require_number(1, name)); - client.del(["foo2"], require_number(1, name)); - client.RENAMENX(["foo", "foo2"], require_number(1, name)); - client.exists(["foo"], require_number(0, name)); - client.exists(["foo2"], last(name, require_number(1, name))); -}; - - -tests.MGET = function () { - var name = "MGET"; - client.mset(["mget keys 1", "mget val 1", "mget keys 2", "mget val 2", "mget keys 3", "mget val 3"], require_string("OK", name)); - client.MGET("mget keys 1", "mget keys 2", "mget keys 3", function (err, results) { - assert.strictEqual(null, err, "result sent back unexpected error: " + err); - assert.strictEqual(3, results.length, name); - assert.strictEqual("mget val 1", results[0].toString(), name); - assert.strictEqual("mget val 2", results[1].toString(), name); - assert.strictEqual("mget val 3", results[2].toString(), name); - }); - client.MGET(["mget keys 1", "mget keys 2", "mget keys 3"], function (err, results) { - assert.strictEqual(null, err, "result sent back unexpected error: " + err); - assert.strictEqual(3, results.length, name); - assert.strictEqual("mget val 1", results[0].toString(), name); - assert.strictEqual("mget val 2", results[1].toString(), name); - assert.strictEqual("mget val 3", results[2].toString(), name); - }); - client.MGET(["mget keys 1", "some random shit", "mget keys 2", "mget keys 3"], function (err, results) { - assert.strictEqual(null, err, "result sent back unexpected error: " + err); - assert.strictEqual(4, results.length, name); - assert.strictEqual("mget val 1", results[0].toString(), name); - assert.strictEqual(null, results[1], name); - assert.strictEqual("mget val 2", results[2].toString(), name); - assert.strictEqual("mget val 3", results[3].toString(), name); - next(name); - }); -}; - -tests.SETNX = function () { - var name = "SETNX"; - client.set(["setnx key", "setnx value"], require_string("OK", name)); - client.SETNX(["setnx key", "new setnx value"], require_number(0, name)); - client.del(["setnx key"], require_number(1, name)); - client.exists(["setnx key"], require_number(0, name)); - client.SETNX(["setnx key", "new setnx value"], require_number(1, name)); - client.exists(["setnx key"], last(name, require_number(1, name))); -}; - -tests.SETEX = function () { - var name = "SETEX"; - client.SETEX(["setex key", "100", "setex val"], require_string("OK", name)); - client.exists(["setex key"], require_number(1, name)); - client.ttl(["setex key"], last(name, require_number_pos(name))); - client.SETEX(["setex key", "100", undefined], require_error(name)); -}; - -tests.MSETNX = function () { - var name = "MSETNX"; - client.mset(["mset1", "val1", "mset2", "val2", "mset3", "val3"], require_string("OK", name)); - client.MSETNX(["mset3", "val3", "mset4", "val4"], require_number(0, name)); - client.del(["mset3"], require_number(1, name)); - client.MSETNX(["mset3", "val3", "mset4", "val4"], require_number(1, name)); - client.exists(["mset3"], require_number(1, name)); - client.exists(["mset4"], last(name, require_number(1, name))); -}; - -tests.HGETALL = function () { - var name = "HGETALL"; - client.hmset(["hosts", "mjr", "1", "another", "23", "home", "1234"], require_string("OK", name)); - client.HGETALL(["hosts"], function (err, obj) { - assert.strictEqual(null, err, name + " result sent back unexpected error: " + err); - assert.strictEqual(3, Object.keys(obj).length, name); - assert.strictEqual("1", obj.mjr.toString(), name); - assert.strictEqual("23", obj.another.toString(), name); - assert.strictEqual("1234", obj.home.toString(), name); - next(name); - }); -}; - -tests.HGETALL_2 = function () { - var name = "HGETALL (Binary client)"; - bclient.hmset(["bhosts", "mjr", "1", "another", "23", "home", "1234", new Buffer([0xAA, 0xBB, 0x00, 0xF0]), new Buffer([0xCC, 0xDD, 0x00, 0xF0])], require_string("OK", name)); - bclient.HGETALL(["bhosts"], function (err, obj) { - assert.strictEqual(null, err, name + " result sent back unexpected error: " + err); - assert.strictEqual(4, Object.keys(obj).length, name); - assert.strictEqual("1", obj.mjr.toString(), name); - assert.strictEqual("23", obj.another.toString(), name); - assert.strictEqual("1234", obj.home.toString(), name); - assert.strictEqual((new Buffer([0xAA, 0xBB, 0x00, 0xF0])).toString('binary'), Object.keys(obj)[3], name); - assert.strictEqual((new Buffer([0xCC, 0xDD, 0x00, 0xF0])).toString('binary'), obj[(new Buffer([0xAA, 0xBB, 0x00, 0xF0])).toString('binary')].toString('binary'), name); - next(name); - }); -}; - -tests.HGETALL_MESSAGE = function () { - var name = "HGETALL_MESSAGE"; - client.hmset("msg_test", {message: "hello"}, require_string("OK", name)); - client.hgetall("msg_test", function (err, obj) { - assert.strictEqual(null, err, name + " result sent back unexpected error: " + err); - assert.strictEqual(1, Object.keys(obj).length, name); - assert.strictEqual(obj.message, "hello"); - next(name); - }); -}; - -tests.HGETALL_NULL = function () { - var name = "HGETALL_NULL"; - - client.hgetall("missing", function (err, obj) { - assert.strictEqual(null, err); - assert.strictEqual(null, obj); - next(name); - }); -}; - -tests.UTF8 = function () { - var name = "UTF8", - utf8_sample = "ಠ_ಠ"; - - client.set(["utf8test", utf8_sample], require_string("OK", name)); - client.get(["utf8test"], function (err, obj) { - assert.strictEqual(null, err); - assert.strictEqual(utf8_sample, obj); - next(name); - }); -}; - -// Set tests were adapted from Brian Hammond's redis-node-client.js, which has a comprehensive test suite - -tests.SADD = function () { - var name = "SADD"; - - client.del('set0'); - client.SADD('set0', 'member0', require_number(1, name)); - client.sadd('set0', 'member0', last(name, require_number(0, name))); -}; - -tests.SADD2 = function () { - var name = "SADD2"; - - client.del("set0"); - client.sadd("set0", ["member0", "member1", "member2"], require_number(3, name)); - client.smembers("set0", function (err, res) { - assert.strictEqual(res.length, 3); - assert.ok(~res.indexOf("member0")); - assert.ok(~res.indexOf("member1")); - assert.ok(~res.indexOf("member2")); - }); - client.SADD("set1", ["member0", "member1", "member2"], require_number(3, name)); - client.smembers("set1", function (err, res) { - assert.strictEqual(res.length, 3); - assert.ok(~res.indexOf("member0")); - assert.ok(~res.indexOf("member1")); - assert.ok(~res.indexOf("member2")); - next(name); - }); -}; - -tests.SISMEMBER = function () { - var name = "SISMEMBER"; - - client.del('set0'); - client.sadd('set0', 'member0', require_number(1, name)); - client.sismember('set0', 'member0', require_number(1, name)); - client.sismember('set0', 'member1', last(name, require_number(0, name))); -}; - -tests.SCARD = function () { - var name = "SCARD"; - - client.del('set0'); - client.sadd('set0', 'member0', require_number(1, name)); - client.scard('set0', require_number(1, name)); - client.sadd('set0', 'member1', require_number(1, name)); - client.scard('set0', last(name, require_number(2, name))); -}; - -tests.SREM = function () { - var name = "SREM"; - - client.del('set0'); - client.sadd('set0', 'member0', require_number(1, name)); - client.srem('set0', 'foobar', require_number(0, name)); - client.srem('set0', 'member0', require_number(1, name)); - client.scard('set0', last(name, require_number(0, name))); -}; - - -tests.SREM2 = function () { - var name = "SREM2"; - client.del("set0"); - client.sadd("set0", ["member0", "member1", "member2"], require_number(3, name)); - client.SREM("set0", ["member1", "member2"], require_number(2, name)); - client.smembers("set0", function (err, res) { - assert.strictEqual(res.length, 1); - assert.ok(~res.indexOf("member0")); - }); - client.sadd("set0", ["member3", "member4", "member5"], require_number(3, name)); - client.srem("set0", ["member0", "member6"], require_number(1, name)); - client.smembers("set0", function (err, res) { - assert.strictEqual(res.length, 3); - assert.ok(~res.indexOf("member3")); - assert.ok(~res.indexOf("member4")); - assert.ok(~res.indexOf("member5")); - next(name); - }); -}; +return; tests.SPOP = function () { var name = "SPOP"; @@ -1633,61 +116,6 @@ tests.SMOVE = function () { client.smove('foo', 'bar', 'x', last(name, require_number(0, name))); }; -tests.SINTER = function () { - var name = "SINTER"; - - client.del('sa'); - client.del('sb'); - client.del('sc'); - - client.sadd('sa', 'a', require_number(1, name)); - client.sadd('sa', 'b', require_number(1, name)); - client.sadd('sa', 'c', require_number(1, name)); - - client.sadd('sb', 'b', require_number(1, name)); - client.sadd('sb', 'c', require_number(1, name)); - client.sadd('sb', 'd', require_number(1, name)); - - client.sadd('sc', 'c', require_number(1, name)); - client.sadd('sc', 'd', require_number(1, name)); - client.sadd('sc', 'e', require_number(1, name)); - - client.sinter('sa', 'sb', function (err, intersection) { - if (err) { - assert.fail(err, name); - } - assert.equal(intersection.length, 2, name); - assert.deepEqual(buffers_to_strings(intersection).sort(), [ 'b', 'c' ], name); - }); - - client.sinter('sb', 'sc', function (err, intersection) { - if (err) { - assert.fail(err, name); - } - assert.equal(intersection.length, 2, name); - assert.deepEqual(buffers_to_strings(intersection).sort(), [ 'c', 'd' ], name); - }); - - client.sinter('sa', 'sc', function (err, intersection) { - if (err) { - assert.fail(err, name); - } - assert.equal(intersection.length, 1, name); - assert.equal(intersection[0], 'c', name); - }); - - // 3-way - - client.sinter('sa', 'sb', 'sc', function (err, intersection) { - if (err) { - assert.fail(err, name); - } - assert.equal(intersection.length, 1, name); - assert.equal(intersection[0], 'c', name); - next(name); - }); -}; - tests.SINTERSTORE = function () { var name = "SINTERSTORE"; @@ -1784,126 +212,6 @@ tests.SUNIONSTORE = function () { }); }; -// SORT test adapted from Brian Hammond's redis-node-client.js, which has a comprehensive test suite - -tests.SORT = function () { - var name = "SORT"; - - client.del('y'); - client.del('x'); - - client.rpush('y', 'd', require_number(1, name)); - client.rpush('y', 'b', require_number(2, name)); - client.rpush('y', 'a', require_number(3, name)); - client.rpush('y', 'c', require_number(4, name)); - - client.rpush('x', '3', require_number(1, name)); - client.rpush('x', '9', require_number(2, name)); - client.rpush('x', '2', require_number(3, name)); - client.rpush('x', '4', require_number(4, name)); - - client.set('w3', '4', require_string("OK", name)); - client.set('w9', '5', require_string("OK", name)); - client.set('w2', '12', require_string("OK", name)); - client.set('w4', '6', require_string("OK", name)); - - client.set('o2', 'buz', require_string("OK", name)); - client.set('o3', 'foo', require_string("OK", name)); - client.set('o4', 'baz', require_string("OK", name)); - client.set('o9', 'bar', require_string("OK", name)); - - client.set('p2', 'qux', require_string("OK", name)); - client.set('p3', 'bux', require_string("OK", name)); - client.set('p4', 'lux', require_string("OK", name)); - client.set('p9', 'tux', require_string("OK", name)); - - // Now the data has been setup, we can test. - - // But first, test basic sorting. - - // y = [ d b a c ] - // sort y ascending = [ a b c d ] - // sort y descending = [ d c b a ] - - client.sort('y', 'asc', 'alpha', function (err, sorted) { - if (err) { - assert.fail(err, name); - } - assert.deepEqual(buffers_to_strings(sorted), ['a', 'b', 'c', 'd'], name); - }); - - client.sort('y', 'desc', 'alpha', function (err, sorted) { - if (err) { - assert.fail(err, name); - } - assert.deepEqual(buffers_to_strings(sorted), ['d', 'c', 'b', 'a'], name); - }); - - // Now try sorting numbers in a list. - // x = [ 3, 9, 2, 4 ] - - client.sort('x', 'asc', function (err, sorted) { - if (err) { - assert.fail(err, name); - } - assert.deepEqual(buffers_to_strings(sorted), [2, 3, 4, 9], name); - }); - - client.sort('x', 'desc', function (err, sorted) { - if (err) { - assert.fail(err, name); - } - assert.deepEqual(buffers_to_strings(sorted), [9, 4, 3, 2], name); - }); - - // Try sorting with a 'by' pattern. - - client.sort('x', 'by', 'w*', 'asc', function (err, sorted) { - if (err) { - assert.fail(err, name); - } - assert.deepEqual(buffers_to_strings(sorted), [3, 9, 4, 2], name); - }); - - // Try sorting with a 'by' pattern and 1 'get' pattern. - - client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', function (err, sorted) { - if (err) { - assert.fail(err, name); - } - assert.deepEqual(buffers_to_strings(sorted), ['foo', 'bar', 'baz', 'buz'], name); - }); - - // Try sorting with a 'by' pattern and 2 'get' patterns. - - client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', function (err, sorted) { - if (err) { - assert.fail(err, name); - } - assert.deepEqual(buffers_to_strings(sorted), ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux'], name); - }); - - // Try sorting with a 'by' pattern and 2 'get' patterns. - // Instead of getting back the sorted set/list, store the values to a list. - // Then check that the values are there in the expected order. - - client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', 'store', 'bacon', function (err) { - if (err) { - assert.fail(err, name); - } - }); - - client.lrange('bacon', 0, -1, function (err, values) { - if (err) { - assert.fail(err, name); - } - assert.deepEqual(buffers_to_strings(values), ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux'], name); - next(name); - }); - - // TODO - sort by hash value -}; - tests.MONITOR = function () { var name = "MONITOR", responses = [], monitor_client; @@ -2010,53 +318,6 @@ tests.OPTIONAL_CALLBACK_UNDEFINED = function () { client.set("op_cb_undefined", undefined, undefined); }; -tests.ENABLE_OFFLINE_QUEUE_TRUE = function () { - var name = "ENABLE_OFFLINE_QUEUE_TRUE"; - var cli = redis.createClient(9999, null, { - max_attempts: 1, - parser: parser - // default :) - // enable_offline_queue: true - }); - cli.on('error', function(e) { - // ignore, b/c expecting a "can't connect" error - }); - return setTimeout(function() { - cli.set(name, name, function(err, result) { - assert.ifError(err); - }); - - return setTimeout(function(){ - assert.strictEqual(cli.offline_queue.length, 1); - return next(name); - }, 25); - }, 50); -}; - -tests.ENABLE_OFFLINE_QUEUE_FALSE = function () { - var name = "ENABLE_OFFLINE_QUEUE_FALSE"; - var cli = redis.createClient(9999, null, { - parser: parser, - max_attempts: 1, - enable_offline_queue: false - }); - cli.on('error', function() { - // ignore, see above - }); - assert.throws(function () { - cli.set(name, name); - }); - assert.doesNotThrow(function () { - cli.set(name, name, function (err) { - // should callback with an error - assert.ok(err); - setTimeout(function () { - next(name); - }, 50); - }); - }); -}; - tests.SLOWLOG = function () { var name = "SLOWLOG"; client.config("set", "slowlog-log-slower-than", 0, require_string("OK", name)); @@ -2072,198 +333,3 @@ tests.SLOWLOG = function () { next(name); }); }; - -tests.DOMAIN = function () { - var name = "DOMAIN"; - - var domain; - try { - domain = require('domain').create(); - } catch (err) { - console.log("Skipping " + name + " because this version of node doesn't have domains."); - next(name); - } - - if (domain) { - domain.run(function () { - client.set('domain', 'value', function (err, res) { - assert.ok(process.domain); - var notFound = res.not.existing.thing; // ohhh nooooo - }); - }); - - // this is the expected and desired behavior - domain.on('error', function (err) { - domain.exit(); - next(name); - }); - } -}; - -// TODO - need a better way to test auth, maybe auto-config a local Redis server or something. -// Yes, this is the real password. Please be nice, thanks. -tests.auth = function () { - var name = "AUTH", client4, ready_count = 0; - - client4 = redis.createClient(9006, "filefish.redistogo.com", { parser: parser }); - client4.auth("664b1b6aaf134e1ec281945a8de702a9", function (err, res) { - assert.strictEqual(null, err, name); - assert.strictEqual("OK", res.toString(), name); - }); - - // test auth, then kill the connection so it'll auto-reconnect and auto-re-auth - client4.on("ready", function () { - ready_count++; - if (ready_count === 1) { - client4.stream.destroy(); - } else { - client4.quit(function (err, res) { - next(name); - }); - } - }); -}; - -tests.auth2 = function () { - var name = "AUTH2", client4, ready_count = 0; - - client4 = redis.createClient(9006, "filefish.redistogo.com", { auth_pass: "664b1b6aaf134e1ec281945a8de702a9", parser: parser }); - - // test auth, then kill the connection so it'll auto-reconnect and auto-re-auth - client4.on("ready", function () { - ready_count++; - if (ready_count === 1) { - client4.stream.destroy(); - } else { - client4.quit(function (err, res) { - next(name); - }); - } - }); -}; - -// auth password specified by URL string. -tests.auth3 = function () { - var name = "AUTH3", client4, ready_count = 0; - - client4 = redis.createClient('redis://redistogo:664b1b6aaf134e1ec281945a8de702a9@filefish.redistogo.com:9006/'); - - // test auth, then kill the connection so it'll auto-reconnect and auto-re-auth - client4.on("ready", function () { - ready_count++; - if (ready_count === 1) { - client4.stream.destroy(); - } else { - client4.quit(function (err, res) { - next(name); - }); - } - }); -}; - -tests.reconnectRetryMaxDelay = function() { - var time = new Date().getTime(), - name = 'reconnectRetryMaxDelay', - reconnecting = false; - var client = redis.createClient(PORT, HOST, { - retry_max_delay: 1, - parser: parser - }); - client.on('ready', function() { - if (!reconnecting) { - reconnecting = true; - client.retry_delay = 1000; - client.retry_backoff = 1; - client.stream.end(); - } else { - client.end(); - var lasted = new Date().getTime() - time; - assert.ok(lasted < 1000); - next(name); - } - }); -}; - -tests.unref = function () { - var name = "unref"; - var external = fork("./test/test-unref.js", [PORT, HOST]); - var done = false; - external.on("close", function (code) { - assert(code === 0, "test-unref.js failed"); - done = true; - }); - setTimeout(function () { - if (!done) { - external.kill(); - } - assert(done, "test-unref.js didn't finish in time."); - next(name); - }, 1500); -}; - -// starting to split tests into multiple files. -require('./queue-test')(tests, next); - -all_tests = Object.keys(tests); -all_start = new Date(); -test_count = 0; - -run_next_test = function run_next_test() { - var test_name = all_tests.shift(); - if (typeof tests[test_name] === "function") { - console.log('- \x1b[1m' + test_name.toLowerCase() + '\x1b[0m:'); - cur_start = new Date(); - test_count += 1; - tests[test_name](); - } else { - console.log('\n completed \x1b[32m%d\x1b[0m tests in \x1b[33m%d\x1b[0m ms\n', test_count, new Date() - all_start); - client.quit(); - client2.quit(); - bclient.quit(); - } -}; - -client.once("ready", function start_tests() { - console.log("Connected to " + client.address + ", Redis server version " + client.server_info.redis_version + "\n"); - console.log("Using reply parser " + client.reply_parser.name); - - run_next_test(); - - connected = true; -}); - -client.on('end', function () { - ended = true; -}); - -// Exit immediately on connection failure, which triggers "exit", below, which fails the test -client.on("error", function (err) { - console.error("client: " + err.stack); - process.exit(); -}); -client2.on("error", function (err) { - console.error("client2: " + err.stack); - process.exit(); -}); -client3.on("error", function (err) { - console.error("client3: " + err.stack); - process.exit(); -}); -bclient.on("error", function (err) { - console.error("bclient: " + err.stack); - process.exit(); -}); - -client.on("reconnecting", function (params) { - console.log("reconnecting: " + util.inspect(params)); -}); - -process.on('uncaughtException', function (err) { - console.error("Uncaught exception: " + err.stack); - process.exit(1); -}); - -process.on('exit', function (code) { - assert.equal(true, connected); - assert.equal(true, ended); -});