diff --git a/package.json b/package.json index 0563794501..ce67d02b83 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "coverage": "nyc report --reporter=text-lcov | coveralls", "test": "nyc ./test/run.sh", - "mocha": "nyc ./node_modules/.bin/_mocha ./test/mocha/*.js" + "mocha": "nyc ./node_modules/.bin/_mocha ./test/mocha/*.js ./test/mocha/commands/*.js" }, "devDependencies": { "async": "^1.3.0", diff --git a/test/lib/nodeify-assertions.js b/test/lib/nodeify-assertions.js index c80cccb8e5..73000e67d0 100644 --- a/test/lib/nodeify-assertions.js +++ b/test/lib/nodeify-assertions.js @@ -56,5 +56,15 @@ module.exports = { return true; }; } + }, + + serverVersionAtLeast: function (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; } }; diff --git a/test/mocha/dbsize.spec.js b/test/mocha/commands/dbsize.spec.js similarity index 96% rename from test/mocha/dbsize.spec.js rename to test/mocha/commands/dbsize.spec.js index aa019c57cb..df1b1431bd 100644 --- a/test/mocha/dbsize.spec.js +++ b/test/mocha/commands/dbsize.spec.js @@ -1,9 +1,9 @@ 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 nodeAssert = require('../../lib/nodeify-assertions'); var redis = config.redis; -var RedisProcess = require("../lib/redis-process"); +var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'dbsize' method", function () { diff --git a/test/mocha/flushdb.spec.js b/test/mocha/commands/flushdb.spec.js similarity index 96% rename from test/mocha/flushdb.spec.js rename to test/mocha/commands/flushdb.spec.js index 693ff69ab5..de325a800d 100644 --- a/test/mocha/flushdb.spec.js +++ b/test/mocha/commands/flushdb.spec.js @@ -1,9 +1,9 @@ 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 nodeAssert = require('../../lib/nodeify-assertions'); var redis = config.redis; -var RedisProcess = require("../lib/redis-process"); +var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'flushdb' method", function () { diff --git a/test/mocha/get.spec.js b/test/mocha/commands/get.spec.js similarity index 95% rename from test/mocha/get.spec.js rename to test/mocha/commands/get.spec.js index 6c533f6a81..d232a6ecb1 100644 --- a/test/mocha/get.spec.js +++ b/test/mocha/commands/get.spec.js @@ -1,9 +1,9 @@ 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 nodeAssert = require('../../lib/nodeify-assertions'); var redis = config.redis; -var RedisProcess = require("../lib/redis-process"); +var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'get' method", function () { diff --git a/test/mocha/getset.spec.js b/test/mocha/commands/getset.spec.js similarity index 96% rename from test/mocha/getset.spec.js rename to test/mocha/commands/getset.spec.js index 847aa1e462..ba76384f2c 100644 --- a/test/mocha/getset.spec.js +++ b/test/mocha/commands/getset.spec.js @@ -1,9 +1,9 @@ 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 nodeAssert = require('../../lib/nodeify-assertions'); var redis = config.redis; -var RedisProcess = require("../lib/redis-process"); +var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'getset' method", function () { diff --git a/test/mocha/incr.spec.js b/test/mocha/commands/incr.spec.js similarity index 97% rename from test/mocha/incr.spec.js rename to test/mocha/commands/incr.spec.js index 34bacec70e..ae4534dd30 100644 --- a/test/mocha/incr.spec.js +++ b/test/mocha/commands/incr.spec.js @@ -1,9 +1,9 @@ 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 nodeAssert = require('../../lib/nodeify-assertions'); var redis = config.redis; -var RedisProcess = require("../lib/redis-process"); +var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'incr' method", function () { diff --git a/test/mocha/mset.spec.js b/test/mocha/commands/mset.spec.js similarity index 97% rename from test/mocha/mset.spec.js rename to test/mocha/commands/mset.spec.js index 90ffb32a74..b1f4f367b0 100644 --- a/test/mocha/mset.spec.js +++ b/test/mocha/commands/mset.spec.js @@ -1,9 +1,9 @@ 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 nodeAssert = require('../../lib/nodeify-assertions'); var redis = config.redis; -var RedisProcess = require("../lib/redis-process"); +var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'mset' method", function () { diff --git a/test/mocha/commands/multi.spec.js b/test/mocha/commands/multi.spec.js new file mode 100644 index 0000000000..ba8be4fcbf --- /dev/null +++ b/test/mocha/commands/multi.spec.js @@ -0,0 +1,269 @@ +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'); + +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); + + describe("using " + parser + " and " + ip, function () { + var key, value; + + beforeEach(function () { + key = uuid.v4(); + value = uuid.v4(); + }); + + describe("when not connected", function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.quit(); + }); + client.on('end', function () { + return done(); + }); + }); + + it("reports an error", function (done) { + client.multi(); + client.exec(function (err, res) { + assert.equal(err.message, 'Redis connection gone from end event.'); + done(); + }); + }); + }); + + describe("when connected", function () { + var client; + + beforeEach(function (done) { + 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) { + return done(err); + }) + }); + }); + + afterEach(function () { + client.end(); + }); + + it('roles back a transaction when one command in a sequence of commands fails', function (done) { + var name = "MULTI_1", multi1, multi2; + + // 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.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])) { + 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.exec(function (err, replies) { + assert.strictEqual(multibar_expected, replies[0]); + assert.strictEqual(multifoo_expected, replies[1]); + return done(); + }); + }); + }); + + // I'm unclear as to the difference between this test in the test above, + // 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.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])) { + 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.exec(function (err, replies) { + assert.strictEqual(multibar_expected, replies[0]); + assert.strictEqual(multifoo_expected, replies[1]); + return done(); + }); + }); + }); + + it('roles back a transaction when one command in an array of commands fails', function (done) { + // test nested multi-bulk replies + client.multi([ + ["mget", "multifoo", "multibar", function (err, res) { + assert.strictEqual(2, res.length); + 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)] + ]).exec(function (err, replies) { + if (nodeAssert.serverVersionAtLeast(client, [2, 6, 5])) { + assert.notEqual(err, null); + assert.equal(replies, undefined); + } else { + assert.strictEqual(2, replies[0].length); + assert.strictEqual("0", replies[0][0].toString()); + assert.strictEqual("0", replies[0][1].toString()); + + assert.strictEqual("1", replies[1].toString()); + assert.strictEqual("1", replies[2].toString()); + } + + return done(); + }); + }); + + it('handles multiple operations being applied to a set', function (done) { + 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(0, reply.length); + }); + + // 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(4, replies[0].length); + assert.strictEqual(0, replies[2].length); + return done(); + }); + }); + + it('allows multiple operations to be performed using a chaining API', function (done) { + 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()); + return done(); + }); + }); + + it('allows an array to be provided indicating multiple operations to perform', function (done) { + // 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); + assert.strictEqual(replies[0].length, 4); + return done(); + }); + }); + + it('allows multiple operations to be performed on a hash', function (done) { + 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); + return done(); + }); + }); + + it('reports multiple exceptions when they occur', function (done) { + if (!nodeAssert.serverVersionAtLeast(client, [2, 6, 5])) return done(); + + client.multi().set("foo").exec(function (err, reply) { + assert(Array.isArray(err), "err should be an array"); + assert.equal(2, err.length, "err should have 2 items"); + assert(err[0].message.match(/ERR/), "First error message should contain ERR"); + assert(err[1].message.match(/EXECABORT/), "First error message should contain EXECABORT"); + return done(); + }); + }); + + }); + }); + } + + ['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/mocha/select.spec.js b/test/mocha/commands/select.spec.js similarity index 97% rename from test/mocha/select.spec.js rename to test/mocha/commands/select.spec.js index bde75857f6..24c1d159b8 100644 --- a/test/mocha/select.spec.js +++ b/test/mocha/commands/select.spec.js @@ -1,9 +1,9 @@ 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 nodeAssert = require('../../lib/nodeify-assertions'); var redis = config.redis; -var RedisProcess = require("../lib/redis-process"); +var RedisProcess = require("../../lib/redis-process"); describe("The 'select' method", function () { diff --git a/test/mocha/set.spec.js b/test/mocha/commands/set.spec.js similarity index 95% rename from test/mocha/set.spec.js rename to test/mocha/commands/set.spec.js index ed5026710e..72df4467fb 100644 --- a/test/mocha/set.spec.js +++ b/test/mocha/commands/set.spec.js @@ -1,9 +1,9 @@ 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 nodeAssert = require('../../lib/nodeify-assertions'); var redis = config.redis; -var RedisProcess = require("../lib/redis-process"); +var RedisProcess = require("../../lib/redis-process"); var uuid = require('uuid'); describe("The 'set' method", function () { @@ -117,7 +117,7 @@ describe("The 'set' method", function () { describe("with undefined 'key' and missing 'value' parameter", function () { it("does not emit an error", function (done) { - this.timeout(50); + this.timeout(200); client.once("error", function (err) { nodeAssert.isError()(err, null); @@ -128,11 +128,11 @@ describe("The 'set' method", function () { setTimeout(function () { done(); - }, 45); + }, 100); }); it("does not throw an error", function (done) { - this.timeout(50); + this.timeout(200); var mochaListener = removeMochaListener(); process.once('uncaughtException', function (err) { @@ -144,7 +144,7 @@ describe("The 'set' method", function () { setTimeout(function () { done(); - }, 45); + }, 100); }); }); diff --git a/test/mocha/connecting.spec.js b/test/mocha/node_redis.spec.js similarity index 100% rename from test/mocha/connecting.spec.js rename to test/mocha/node_redis.spec.js diff --git a/test/test.js b/test/test.js index 530a75b19a..7b3cb1cc72 100644 --- a/test/test.js +++ b/test/test.js @@ -120,6 +120,7 @@ next = function next(name) { <<<<<<< 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 } ); @@ -373,6 +374,8 @@ tests.MULTI_6 = function () { }); }; +// THIS TEST SHOULD BE MOVED IN TO A PARSER +// SPECIFIC TESTING FILE. tests.MULTI_7 = function () { var name = "MULTI_7"; @@ -402,58 +405,6 @@ tests.MULTI_7 = function () { next(name); }; - -tests.MULTI_EXCEPTION_1 = function() { - var name = "MULTI_EXCEPTION_1"; - - if (!server_version_at_least(client, [2, 6, 5])) { - console.log("Skipping " + name + " for old Redis server version < 2.6.5"); - return next(name); - } - - client.multi().set("foo").exec(function (err, reply) { - assert(Array.isArray(err), "err should be an array"); - assert.equal(2, err.length, "err should have 2 items"); - assert(err[0].message.match(/ERR/), "First error message should contain ERR"); - assert(err[1].message.match(/EXECABORT/), "First error message should contain EXECABORT"); - next(name); - }); -}; - -tests.MULTI_8 = function () { - var name = "MULTI_8", multi1, multi2; - - // Provoke an error at queue time - multi1 = client.multi(); - multi1.mset("multifoo_8", "10", "multibar_8", "20", require_string("OK", name)); - multi1.set("foo2", require_error(name)); - multi1.set("foo3", require_error(name)); - multi1.incr("multifoo_8", require_number(11, name)); - multi1.incr("multibar_8", 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_8", require_number(multibar_expected, name)); - multi2.incr("multifoo_8", 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.FWD_ERRORS_1 = function () { var name = "FWD_ERRORS_1";