diff --git a/.gitignore b/.gitignore index 6a59b3d64c..f351fac36f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules .tern-port +.nyc_output +coverage diff --git a/.npmignore b/.npmignore index 61755a69fc..4df6949973 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,6 @@ examples/ benches/ -test.js +test/ diff_multi_bench_output.js generate_commands.js multi_bench.js diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..f64f437830 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +sudo: true +node_js: + - "0.10" + - "0.12" + - "iojs-v2" +after_success: npm run coverage diff --git a/README.md b/README.md index be0daaca52..c9900bbbef 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,17 @@ redis - a node.js redis client =========================== -This is a complete Redis client for node.js. It supports all Redis commands, including many recently added commands like EVAL from -experimental Redis server branches. +[![Build Status](https://travis-ci.org/NodeRedis/node_redis.png)](https://travis-ci.org/NodeRedis/node_redis) +[![Coverage Status](https://coveralls.io/repos/NodeRedis/node_redis/badge.svg?branch=)](https://coveralls.io/r/NodeRedis/node_redis?branch=) +This is a complete Redis client for node.js. It supports all Redis commands, +including many recently added commands like EVAL from experimental Redis server +branches. Install with: npm install redis -Pieter Noordhuis has provided a binding to the official `hiredis` C library, which is non-blocking and fast. To use `hiredis`, do: - - npm install hiredis redis - -If `hiredis` is installed, `node_redis` will use it by default. Otherwise, a pure JavaScript parser will be used. - -If you use `hiredis`, be sure to rebuild it whenever you upgrade your version of node. There are mysterious failures that can -happen between node and native code modules after a node upgrade. - - ## Usage Simple example, included as `examples/simple.js`: @@ -57,34 +50,9 @@ This will display: 1: hashtest 2 mjr:~/work/node_redis (master)$ - -## Performance - -Here are typical results of `multi_bench.js` which is similar to `redis-benchmark` from the Redis distribution. -It uses 50 concurrent connections with no pipelining. - -JavaScript parser: - - PING: 20000 ops 42283.30 ops/sec 0/5/1.182 - SET: 20000 ops 32948.93 ops/sec 1/7/1.515 - GET: 20000 ops 28694.40 ops/sec 0/9/1.740 - INCR: 20000 ops 39370.08 ops/sec 0/8/1.269 - LPUSH: 20000 ops 36429.87 ops/sec 0/8/1.370 - LRANGE (10 elements): 20000 ops 9891.20 ops/sec 1/9/5.048 - LRANGE (100 elements): 20000 ops 1384.56 ops/sec 10/91/36.072 - -hiredis parser: - - PING: 20000 ops 46189.38 ops/sec 1/4/1.082 - SET: 20000 ops 41237.11 ops/sec 0/6/1.210 - GET: 20000 ops 39682.54 ops/sec 1/7/1.257 - INCR: 20000 ops 40080.16 ops/sec 0/8/1.242 - LPUSH: 20000 ops 41152.26 ops/sec 0/3/1.212 - LRANGE (10 elements): 20000 ops 36563.07 ops/sec 1/8/1.363 - LRANGE (100 elements): 20000 ops 21834.06 ops/sec 0/9/2.287 - -The performance of `node_redis` improves dramatically with pipelining, which happens automatically in most normal programs. - +Note that the API is entire asynchronous. To get data back from the server, +you'll need to use a callback. The return value from most of the API is a +backpressure indicator. ### Sending Commands @@ -176,15 +144,16 @@ resume sending when you get `drain`. `client` will emit `idle` when there are no outstanding commands that are awaiting a response. ## redis.createClient() +If you have `redis-server` running on the same computer as node, then the defaults for +port and host are probably fine and you don't need to supply any arguments. `createClient()` returns a `RedisClient` object. ### overloading -* redis.createClient() = redis.createClient(6379, '127.0.0.1', {}) -* redis.createClient(options) = redis.createClient(6379, '127.0.0.1', options) -* redis.createClient(unix_socket, options) -* redis.createClient(port, host, options) +* `redis.createClient(port,host,options)` +* `redis.createClient()` is equivalent to `redis.createClient(6379, '127.0.0.1', {})` +* `redis.createClient(options)` is equivalent to `redis.createClient(6379, '127.0.0.1', options)` +* `redis.createClient(unix_socket, options)` -If you have `redis-server` running on the same computer as node, then the defaults for -port and host are probably fine. `options` in an object with the following possible properties: + `options` is an object with the following possible properties: * `parser`: which Redis protocol reply parser to use. Defaults to `hiredis` if that module is installed. This may also be set to `javascript`. @@ -197,7 +166,7 @@ every command on a client. * `socket_nodelay`: defaults to `true`. Whether to call setNoDelay() on the TCP stream, which disables the Nagle algorithm on the underlying socket. Setting this option to `false` can result in additional throughput at the cost of more latency. Most applications will want this set to `true`. -* `socket_keepalive` defaults to `true`. Whether the keep-alive functionality is enabled on the underlying socket. +* `socket_keepalive` defaults to `true`. Whether the keep-alive functionality is enabled on the underlying socket. * `no_ready_check`: defaults to `false`. When a connection is established to the Redis server, the server might still be loading the database from disk. While loading, the server not respond to any commands. To work around this, `node_redis` has a "ready check" which sends the `INFO` command to the server. The response from the `INFO` command @@ -216,8 +185,8 @@ limits total time for client to reconnect. Value is provided in milliseconds and * `max_attempts` defaults to `null`. By default client will try reconnecting until connected. Setting `max_attempts` limits total amount of reconnects. * `auth_pass` defaults to `null`. By default client will try connecting without auth. If set, client will run redis auth command on connect. -* `family` defaults to `IPv4`. The client connects in IPv4 if not specified or if the DNS resolution returns an IPv4 address. -You can force an IPv6 if you set the family to 'IPv6'. See nodejs net or dns modules how to use the family type. +* `family` defaults to `IPv4`. The client connects in IPv4 if not specified or if the DNS resolution returns an IPv4 address. +You can force an IPv6 if you set the family to 'IPv6'. See nodejs net or dns modules how to use the family type. ```js var redis = require("redis"), @@ -237,7 +206,6 @@ You can force an IPv6 if you set the family to 'IPv6'. See nodejs net or dns mod client.end(); ``` -`createClient()` returns a `RedisClient` object that is named `client` in all of the examples here. ## client.auth(password, callback) @@ -658,6 +626,105 @@ client.zadd(args, function (err, response) { }); ``` +## Performance + +Much effort has been spent to make `node_redis` as fast as possible for common +operations. As pipelining happens naturally from shared connections, overall +efficiency goes up. + +Here are typical results of `multi_bench.js` which is similar to +`redis-benchmark` from the Redis distribution. It uses 5 concurrent connections +and shows the difference between pipelines of 1 and 50. + +JavaScript parser: + + Client count: 5, node version: 0.10.32, server version: 2.8.18, parser: javascript + PING, 1/5 min/max/avg/p95: 0/ 3/ 0.05/ 1.00 1103ms total, 18132.37 ops/sec + PING, 50/5 min/max/avg/p95: 0/ 4/ 0.81/ 2.00 327ms total, 61162.08 ops/sec + SET 4B str, 1/5 min/max/avg/p95: 0/ 2/ 0.05/ 0.00 1104ms total, 18115.94 ops/sec + SET 4B str, 50/5 min/max/avg/p95: 0/ 3/ 0.83/ 2.00 333ms total, 60060.06 ops/sec + SET 4B buf, 1/5 min/max/avg/p95: 0/ 2/ 0.09/ 1.00 1876ms total, 10660.98 ops/sec + SET 4B buf, 50/5 min/max/avg/p95: 0/ 11/ 2.55/ 4.00 1025ms total, 19512.20 ops/sec + GET 4B str, 1/5 min/max/avg/p95: 0/ 1/ 0.05/ 1.00 1117ms total, 17905.10 ops/sec + GET 4B str, 50/5 min/max/avg/p95: 0/ 3/ 0.87/ 2.00 347ms total, 57636.89 ops/sec + GET 4B buf, 1/5 min/max/avg/p95: 0/ 1/ 0.05/ 1.00 1110ms total, 18018.02 ops/sec + GET 4B buf, 50/5 min/max/avg/p95: 0/ 2/ 0.85/ 2.00 342ms total, 58479.53 ops/sec + SET 4KiB str, 1/5 min/max/avg/p95: 0/ 1/ 0.05/ 1.00 1119ms total, 17873.10 ops/sec + SET 4KiB str, 50/5 min/max/avg/p95: 0/ 3/ 0.89/ 2.00 358ms total, 55865.92 ops/sec + SET 4KiB buf, 1/5 min/max/avg/p95: 0/ 1/ 0.09/ 1.00 1894ms total, 10559.66 ops/sec + SET 4KiB buf, 50/5 min/max/avg/p95: 0/ 7/ 2.57/ 4.00 1031ms total, 19398.64 ops/sec + GET 4KiB str, 1/5 min/max/avg/p95: 0/ 6/ 0.06/ 1.00 1248ms total, 16025.64 ops/sec + GET 4KiB str, 50/5 min/max/avg/p95: 0/ 3/ 1.03/ 2.00 415ms total, 48192.77 ops/sec + GET 4KiB buf, 1/5 min/max/avg/p95: 0/ 1/ 0.06/ 1.00 1177ms total, 16992.35 ops/sec + GET 4KiB buf, 50/5 min/max/avg/p95: 0/ 10/ 1.02/ 2.00 409ms total, 48899.76 ops/sec + INCR, 1/5 min/max/avg/p95: 0/ 2/ 0.05/ 0.55 1137ms total, 17590.15 ops/sec + INCR, 50/5 min/max/avg/p95: 0/ 2/ 0.85/ 2.00 343ms total, 58309.04 ops/sec + LPUSH, 1/5 min/max/avg/p95: 0/ 1/ 0.06/ 1.00 1143ms total, 17497.81 ops/sec + LPUSH, 50/5 min/max/avg/p95: 0/ 3/ 0.87/ 2.00 350ms total, 57142.86 ops/sec + LRANGE 10, 1/5 min/max/avg/p95: 0/ 2/ 0.06/ 1.00 1283ms total, 15588.46 ops/sec + LRANGE 10, 50/5 min/max/avg/p95: 0/ 3/ 1.12/ 2.00 449ms total, 44543.43 ops/sec + LRANGE 100, 1/5 min/max/avg/p95: 0/ 1/ 0.09/ 1.00 1932ms total, 10351.97 ops/sec + LRANGE 100, 50/5 min/max/avg/p95: 0/ 5/ 2.46/ 4.00 985ms total, 20304.57 ops/sec + SET 4MiB buf, 1/5 min/max/avg/p95: 1/ 4/ 1.37/ 2.00 691ms total, 723.59 ops/sec + SET 4MiB buf, 50/5 min/max/avg/p95: 3/ 166/ 57.66/ 116.00 601ms total, 831.95 ops/sec + GET 4MiB str, 1/5 min/max/avg/p95: 84/ 110/ 93.18/ 106.95 9320ms total, 10.73 ops/sec + GET 4MiB str, 50/5 min/max/avg/p95: 156/7375/3400.10/6840.40 8928ms total, 11.20 ops/sec + GET 4MiB buf, 1/5 min/max/avg/p95: 84/ 105/ 91.21/ 99.00 9129ms total, 10.95 ops/sec + GET 4MiB buf, 50/5 min/max/avg/p95: 424/5704/3518.94/5626.65 9145ms total, 10.93 ops/sec + +If you use very large responses in your application, the JavaScript parser +performs badly. Until the JS parser is fixed, you can use the C-based `hiredis` +parser bound to the official `hiredis` C library. To use `hiredis`, do: + + npm install hiredis redis + +If the `hiredis` npm module is installed, `node_redis` will use it by default. +Otherwise, the pure JavaScript parser will be used. + +If you use `hiredis`, be sure to rebuild it whenever you upgrade your version of +node. There are mysterious failures that can happen between node and native +code modules after a node upgrade. + +Most users find that the JS parser is faster than the `hiredis` parser. Because +of the pain associated with upgrading native code modules, you should only use +`hiredis` if your application needs it. + +hiredis parser: + + Client count: 5, node version: 0.10.32, server version: 2.8.18, parser: hiredis + PING, 1/5 min/max/avg/p95: 0/ 3/ 0.05/ 1.00 1092ms total, 18315.02 ops/sec + PING, 50/5 min/max/avg/p95: 0/ 5/ 0.87/ 2.00 347ms total, 57636.89 ops/sec + SET 4B str, 1/5 min/max/avg/p95: 0/ 2/ 0.06/ 1.00 1151ms total, 17376.19 ops/sec + SET 4B str, 50/5 min/max/avg/p95: 0/ 3/ 0.83/ 2.00 334ms total, 59880.24 ops/sec + SET 4B buf, 1/5 min/max/avg/p95: 0/ 3/ 0.09/ 1.00 1932ms total, 10351.97 ops/sec + SET 4B buf, 50/5 min/max/avg/p95: 0/ 9/ 2.64/ 4.00 1059ms total, 18885.74 ops/sec + GET 4B str, 1/5 min/max/avg/p95: 0/ 1/ 0.06/ 0.00 1185ms total, 16877.64 ops/sec + GET 4B str, 50/5 min/max/avg/p95: 0/ 3/ 0.85/ 2.00 341ms total, 58651.03 ops/sec + GET 4B buf, 1/5 min/max/avg/p95: 0/ 1/ 0.06/ 0.00 1179ms total, 16963.53 ops/sec + GET 4B buf, 50/5 min/max/avg/p95: 0/ 3/ 0.85/ 2.00 340ms total, 58823.53 ops/sec + SET 4KiB str, 1/5 min/max/avg/p95: 0/ 1/ 0.06/ 1.00 1210ms total, 16528.93 ops/sec + SET 4KiB str, 50/5 min/max/avg/p95: 0/ 3/ 0.93/ 2.00 372ms total, 53763.44 ops/sec + SET 4KiB buf, 1/5 min/max/avg/p95: 0/ 1/ 0.10/ 1.00 1967ms total, 10167.77 ops/sec + SET 4KiB buf, 50/5 min/max/avg/p95: 0/ 6/ 2.63/ 4.00 1053ms total, 18993.35 ops/sec + GET 4KiB str, 1/5 min/max/avg/p95: 0/ 6/ 0.06/ 1.00 1176ms total, 17006.80 ops/sec + GET 4KiB str, 50/5 min/max/avg/p95: 0/ 4/ 1.00/ 2.00 399ms total, 50125.31 ops/sec + GET 4KiB buf, 1/5 min/max/avg/p95: 0/ 1/ 0.06/ 1.00 1158ms total, 17271.16 ops/sec + GET 4KiB buf, 50/5 min/max/avg/p95: 0/ 3/ 0.99/ 2.00 398ms total, 50251.26 ops/sec + INCR, 1/5 min/max/avg/p95: 0/ 1/ 0.05/ 0.00 1112ms total, 17985.61 ops/sec + INCR, 50/5 min/max/avg/p95: 0/ 3/ 0.84/ 2.00 339ms total, 58997.05 ops/sec + LPUSH, 1/5 min/max/avg/p95: 0/ 1/ 0.05/ 1.00 1131ms total, 17683.47 ops/sec + LPUSH, 50/5 min/max/avg/p95: 0/ 3/ 0.86/ 2.00 345ms total, 57971.01 ops/sec + LRANGE 10, 1/5 min/max/avg/p95: 0/ 1/ 0.06/ 1.00 1228ms total, 16286.64 ops/sec + LRANGE 10, 50/5 min/max/avg/p95: 0/ 3/ 0.95/ 2.00 382ms total, 52356.02 ops/sec + LRANGE 100, 1/5 min/max/avg/p95: 0/ 1/ 0.08/ 1.00 1567ms total, 12763.24 ops/sec + LRANGE 100, 50/5 min/max/avg/p95: 0/ 6/ 1.68/ 3.00 675ms total, 29629.63 ops/sec + SET 4MiB buf, 1/5 min/max/avg/p95: 1/ 4/ 1.37/ 2.00 692ms total, 722.54 ops/sec + SET 4MiB buf, 50/5 min/max/avg/p95: 3/ 183/ 57.79/ 125.00 605ms total, 826.45 ops/sec + GET 4MiB str, 1/5 min/max/avg/p95: 5/ 16/ 8.14/ 12.95 816ms total, 122.55 ops/sec + GET 4MiB str, 50/5 min/max/avg/p95: 24/ 323/ 202.98/ 309.00 519ms total, 192.68 ops/sec + GET 4MiB buf, 1/5 min/max/avg/p95: 6/ 13/ 8.01/ 11.95 802ms total, 124.69 ops/sec + GET 4MiB buf, 50/5 min/max/avg/p95: 16/ 480/ 203.85/ 435.70 531ms total, 188.32 ops/sec + ## TODO Better tests for auth, disconnect/reconnect, and all combinations thereof. @@ -674,7 +741,7 @@ I think there are more performance improvements left in there for smaller values comment again with indignation!) ## Contributors -Some people have have added features and fixed bugs in `node_redis` other than me. +Many people have have added features and fixed bugs in `node_redis`. Ordered by date of first contribution. [Auto-generated](http://github.com/dtrejo/node-authors) on Wed Jul 25 2012 19:14:59 GMT-0700 (PDT). @@ -740,5 +807,3 @@ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -![spacer](http://ranney.com/1px.gif) diff --git a/benches/buffer_bench.js b/benches/buffer_bench.js index a504fbc087..64347e583f 100644 --- a/benches/buffer_bench.js +++ b/benches/buffer_bench.js @@ -1,6 +1,8 @@ +'use strict'; + var source = new Buffer(100), dest = new Buffer(100), i, j, k, tmp, count = 1000000, bytes = 100; - + for (i = 99 ; i >= 0 ; i--) { source[i] = 120; } diff --git a/benches/hiredis_parser.js b/benches/hiredis_parser.js index f1515b110b..489a4912e2 100644 --- a/benches/hiredis_parser.js +++ b/benches/hiredis_parser.js @@ -1,3 +1,5 @@ +'use strict'; + var Parser = require('../lib/parser/hiredis').Parser; var assert = require('assert'); diff --git a/benches/re_sub_test.js b/benches/re_sub_test.js index 64b8f31287..256d0d0d1d 100644 --- a/benches/re_sub_test.js +++ b/benches/re_sub_test.js @@ -1,3 +1,5 @@ +'use strict'; + var client = require('../index').createClient() , client2 = require('../index').createClient() , assert = require('assert'); diff --git a/benches/reconnect_test.js b/benches/reconnect_test.js index 7abdd51665..7556ea430a 100644 --- a/benches/reconnect_test.js +++ b/benches/reconnect_test.js @@ -1,3 +1,5 @@ +'use strict'; + var redis = require("../index").createClient(null, null, { // max_attempts: 4 }); diff --git a/benches/stress/codec.js b/benches/stress/codec.js index 7d764f6072..5a3cc6daa4 100644 --- a/benches/stress/codec.js +++ b/benches/stress/codec.js @@ -1,16 +1,8 @@ +'use strict'; + var json = { encode: JSON.stringify, decode: JSON.parse }; -var MsgPack = require('node-msgpack'); -msgpack = { - encode: MsgPack.pack, - decode: function(str) { return MsgPack.unpack(new Buffer(str)); } -}; - -bison = require('bison'); - module.exports = json; -//module.exports = msgpack; -//module.exports = bison; diff --git a/benches/stress/speed/speed.js b/benches/stress/speed/speed.js index 8e43cbc03b..93d54f0c13 100644 --- a/benches/stress/speed/speed.js +++ b/benches/stress/speed/speed.js @@ -1,3 +1,5 @@ +'use strict'; + var msgpack = require('node-msgpack'); var bison = require('bison'); var codec = { diff --git a/benches/sub_quit_test.js b/benches/sub_quit_test.js index ad1f413228..ae3725a999 100644 --- a/benches/sub_quit_test.js +++ b/benches/sub_quit_test.js @@ -1,3 +1,5 @@ +'use strict'; + var client = require("redis").createClient(), client2 = require("redis").createClient(); diff --git a/changelog.md b/changelog.md index 817031da7a..89c1692a79 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,7 @@ Changelog ========= +* Refactor tests, and improve test coverage (Ben Coe) * Fix extraneous error output due to pubsub tests (Mikael Kohlmyr) ## v0.12.1 - Aug 10, 2014 diff --git a/connection_breaker.js b/connection_breaker.js index 489f5d5645..f2db0eb1b6 100644 --- a/connection_breaker.js +++ b/connection_breaker.js @@ -1,3 +1,5 @@ +'use strict'; + var net = require('net'); var proxyPort = 6379; diff --git a/diff_multi_bench_output.js b/diff_multi_bench_output.js index 7396472019..89b6be479c 100755 --- a/diff_multi_bench_output.js +++ b/diff_multi_bench_output.js @@ -1,5 +1,7 @@ #!/usr/bin/env node +'use strict'; + var colors = require('colors'), fs = require('fs'), _ = require('underscore'), @@ -35,8 +37,8 @@ before_lines.forEach(function(b, i) { return; } - b_words = b.split(' ').filter(is_whitespace); - a_words = a.split(' ').filter(is_whitespace); + var b_words = b.split(' ').filter(is_whitespace); + var a_words = a.split(' ').filter(is_whitespace); var ops = [b_words, a_words] @@ -47,7 +49,7 @@ before_lines.forEach(function(b, i) { var isNaN = !num && num !== 0; return !isNaN; }); - if (ops.length != 2) return + if (ops.length !== 2) return; var delta = ops[1] - ops[0]; var pct = ((delta / ops[0]) * 100).toPrecision(3); @@ -58,7 +60,7 @@ before_lines.forEach(function(b, i) { pct = humanize_diff(pct, '%'); console.log( // name of test - command_name(a_words) == command_name(b_words) + command_name(a_words) === command_name(b_words) ? command_name(a_words) + ':' : '404:', // results of test diff --git a/examples/auth.js b/examples/auth.js index 6c0a563cd8..275c40961e 100644 --- a/examples/auth.js +++ b/examples/auth.js @@ -1,3 +1,5 @@ +'use strict'; + var redis = require("redis"), client = redis.createClient(); diff --git a/examples/backpressure_drain.js b/examples/backpressure_drain.js index 3488ef4d3f..74107bb6a0 100644 --- a/examples/backpressure_drain.js +++ b/examples/backpressure_drain.js @@ -1,3 +1,5 @@ +'use strict'; + var redis = require("../index"), client = redis.createClient(null, null, { command_queue_high_water: 5, diff --git a/examples/eval.js b/examples/eval.js index a3ff6b0793..0ec1c215e9 100644 --- a/examples/eval.js +++ b/examples/eval.js @@ -1,3 +1,5 @@ +'use strict'; + var redis = require("../index"), client = redis.createClient(); diff --git a/examples/extend.js b/examples/extend.js index 488b8c2dc5..22566082d4 100644 --- a/examples/extend.js +++ b/examples/extend.js @@ -1,3 +1,5 @@ +'use strict'; + var redis = require("redis"), client = redis.createClient(); @@ -14,7 +16,7 @@ redis.RedisClient.prototype.parse_info = function (callback) { obj[parts[0]] = parts[1]; } }); - callback(obj) + callback(obj); }); }; diff --git a/examples/file.js b/examples/file.js index 4d2b5d1c98..620b0a1055 100644 --- a/examples/file.js +++ b/examples/file.js @@ -1,3 +1,5 @@ +'use strict'; + // Read a file from disk, store it in Redis, then read it back from Redis. var redis = require("redis"), @@ -11,9 +13,9 @@ var redis = require("redis"), // Read a file from fs, store it in Redis, get it back from Redis, write it back to fs. fs.readFile(filename, function (err, data) { - if (err) throw err + if (err) throw err; console.log("Read " + data.length + " bytes from filesystem."); - + client.set(filename, data, redis.print); // set entire file client.get(filename, function (err, reply) { // get entire file if (err) { @@ -21,7 +23,7 @@ fs.readFile(filename, function (err, data) { } else { fs.writeFile("duplicate_" + filename, reply, function (err) { if (err) { - console.log("Error on write: " + err) + console.log("Error on write: " + err); } else { console.log("File written."); } diff --git a/examples/mget.js b/examples/mget.js index 936740d32f..30f2bce90a 100644 --- a/examples/mget.js +++ b/examples/mget.js @@ -1,3 +1,5 @@ +'use strict'; + var client = require("redis").createClient(); client.mget(["sessions started", "sessions started", "foo"], function (err, res) { diff --git a/examples/monitor.js b/examples/monitor.js index 2cb6a4e1ec..c67f596294 100644 --- a/examples/monitor.js +++ b/examples/monitor.js @@ -1,3 +1,5 @@ +'use strict'; + var client = require("../index").createClient(), util = require("util"); diff --git a/examples/multi.js b/examples/multi.js index 35c08e1840..31bc14ffb0 100644 --- a/examples/multi.js +++ b/examples/multi.js @@ -1,3 +1,5 @@ +'use strict'; + var redis = require("redis"), client = redis.createClient(), set_size = 20; diff --git a/examples/multi2.js b/examples/multi2.js index 8be4d7313c..da722b23bb 100644 --- a/examples/multi2.js +++ b/examples/multi2.js @@ -1,3 +1,5 @@ +'use strict'; + var redis = require("redis"), client = redis.createClient(), multi; diff --git a/examples/psubscribe.js b/examples/psubscribe.js index c57117b8a6..f46d235bed 100644 --- a/examples/psubscribe.js +++ b/examples/psubscribe.js @@ -1,3 +1,5 @@ +'use strict'; + var redis = require("redis"), client1 = redis.createClient(), client2 = redis.createClient(), diff --git a/examples/pub_sub.js b/examples/pub_sub.js index aa508d6c9d..3446a552d1 100644 --- a/examples/pub_sub.js +++ b/examples/pub_sub.js @@ -1,3 +1,5 @@ +'use strict'; + var redis = require("redis"), client1 = redis.createClient(), msg_count = 0, client2 = redis.createClient(); diff --git a/examples/scan.js b/examples/scan.js new file mode 100644 index 0000000000..f4aac2ef4d --- /dev/null +++ b/examples/scan.js @@ -0,0 +1,38 @@ +'use strict'; + +var redis = require("redis"), + client = redis.createClient(); + +var cursor = '0'; + +function scan() { + client.scan( + cursor, + "MATCH", "q:job:*", + "COUNT", "10", + function(err, res) { + if (err) throw err; + + // Update the cursor position for the next scan + cursor = res[0]; + + // From : + // "An iteration starts when the cursor is set to 0, + // and terminates when the cursor returned by the server is 0." + if (cursor === '0') { + return console.log('Iteration complete'); + } else { + // Remember: more or less than COUNT or no keys may be returned + // See http://redis.io/commands/scan#the-count-option + // Also, SCAN may return the same key multiple times + // See http://redis.io/commands/scan#scan-guarantees + + if (res[1].length > 0) { + console.log('Array of matching keys', res[1]); + } + + return scan(); + } + } + ); +} diff --git a/examples/simple.js b/examples/simple.js index f1f2e3209b..e832749895 100644 --- a/examples/simple.js +++ b/examples/simple.js @@ -1,3 +1,5 @@ +'use strict'; + var redis = require("redis"), client = redis.createClient(); diff --git a/examples/sort.js b/examples/sort.js index e7c6249e40..4c804bee1d 100644 --- a/examples/sort.js +++ b/examples/sort.js @@ -1,3 +1,5 @@ +'use strict'; + var redis = require("redis"), client = redis.createClient(); diff --git a/examples/subqueries.js b/examples/subqueries.js index 560db2404e..ebae6d4855 100644 --- a/examples/subqueries.js +++ b/examples/subqueries.js @@ -1,3 +1,5 @@ +'use strict'; + // Sending commands in response to other commands. // This example runs "type" against every key in the database // diff --git a/examples/subquery.js b/examples/subquery.js index 861657e1f3..ffb7eca382 100644 --- a/examples/subquery.js +++ b/examples/subquery.js @@ -1,3 +1,5 @@ +'use strict'; + var client = require("redis").createClient(); function print_results(obj) { @@ -7,7 +9,7 @@ function print_results(obj) { // build a map of all keys and their types client.keys("*", function (err, all_keys) { var key_types = {}; - + all_keys.forEach(function (key, pos) { // use second arg of forEach to get pos client.type(key, function (err, type) { key_types[key] = type; diff --git a/examples/unix_socket.js b/examples/unix_socket.js index 4a5e0bb0e8..460b78e857 100644 --- a/examples/unix_socket.js +++ b/examples/unix_socket.js @@ -1,9 +1,11 @@ +'use strict'; + var redis = require("redis"), client = redis.createClient("/tmp/redis.sock"), profiler = require("v8-profiler"); client.on("connect", function () { - console.log("Got Unix socket connection.") + console.log("Got Unix socket connection."); }); client.on("error", function (err) { diff --git a/examples/web_server.js b/examples/web_server.js index 9fd85923de..dfd420bd1a 100644 --- a/examples/web_server.js +++ b/examples/web_server.js @@ -1,3 +1,5 @@ +'use strict'; + // A simple web server that generates dyanmic content based on responses from Redis var http = require("http"), server, @@ -7,9 +9,9 @@ server = http.createServer(function (request, response) { response.writeHead(200, { "Content-Type": "text/plain" }); - + var redis_info, total_requests; - + redis_client.info(function (err, reply) { redis_info = reply; // stash response in outer scope }); diff --git a/generate_commands.js b/generate_commands.js index e6949d3a14..3ad420edc3 100644 --- a/generate_commands.js +++ b/generate_commands.js @@ -1,3 +1,5 @@ +'use strict'; + var http = require("http"), fs = require("fs"); diff --git a/index.js b/index.js index 44322e692b..a39bf7f9db 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,9 @@ +'use strict'; + /*global Buffer require exports console setTimeout */ var net = require("net"), + URL = require("url"), util = require("./lib/util"), Queue = require("./lib/queue"), to_array = require("./lib/to_array"), @@ -14,10 +17,10 @@ var net = require("net"), // can set this to true to enable for all connections exports.debug_mode = false; -var arraySlice = Array.prototype.slice +var arraySlice = Array.prototype.slice; function trace() { if (!exports.debug_mode) return; - console.log.apply(null, arraySlice.call(arguments)) + console.log.apply(null, arraySlice.call(arguments)); } // hiredis might not be installed @@ -138,7 +141,7 @@ RedisClient.prototype.unref = function () { trace("Not connected yet, will unref later"); this.once("connect", function () { this.unref(); - }) + }); } }; @@ -216,7 +219,7 @@ RedisClient.prototype.do_auth = function () { }, 2000); // TODO - magic number alert return; } else if (err.toString().match("no password is set")) { - console.log("Warning: Redis server does not require a password, but a password was supplied.") + console.log("Warning: Redis server does not require a password, but a password was supplied."); err = null; res = "OK"; } else { @@ -609,7 +612,7 @@ function try_callback(callback, reply) { function reply_to_object(reply) { var obj = {}, j, jl, key, val; - if (reply.length === 0) { + if (reply.length === 0 || !Array.isArray(reply)) { return null; } @@ -647,7 +650,7 @@ RedisClient.prototype.return_reply = function (reply) { // If the "reply" here is actually a message received asynchronously due to a // pubsub subscription, don't pop the command queue as we'll only be consuming // the head command prematurely. - if (Array.isArray(reply) && reply.length > 0 && reply[0]) { + if (this.pub_sub_mode && Array.isArray(reply) && reply.length > 0 && reply[0]) { type = reply[0].toString(); } @@ -671,7 +674,7 @@ RedisClient.prototype.return_reply = function (reply) { if (command_obj && !command_obj.sub_command) { if (typeof command_obj.callback === "function") { - if (this.options.detect_buffers && command_obj.buffer_args === false) { + if (this.options.detect_buffers && command_obj.buffer_args === false && 'exec' !== command_obj.command.toLowerCase()) { // If detect_buffers option was specified, then the reply from the parser will be Buffers. // If this command did not use Buffer arguments, then convert the reply to Strings here. reply = reply_to_strings(reply); @@ -1105,15 +1108,24 @@ Multi.prototype.HMSET = Multi.prototype.hmset; Multi.prototype.exec = function (callback) { var self = this; var errors = []; + var wants_buffers = []; // drain queue, callback will catch "QUEUED" or error // TODO - get rid of all of these anonymous functions which are elegant but slow this.queue.forEach(function (args, index) { - var command = args[0], obj; + var command = args[0], obj, i, il, buffer_args; if (typeof args[args.length - 1] === "function") { args = args.slice(1, -1); } else { args = args.slice(1); } + // Keep track of who wants buffer responses: + buffer_args = false; + for (i = 0, il = args.length; i < il; i += 1) { + if (Buffer.isBuffer(args[i])) { + buffer_args = true; + } + } + wants_buffers.push(buffer_args); if (args.length === 1 && Array.isArray(args[0])) { args = args[0]; } @@ -1148,12 +1160,18 @@ Multi.prototype.exec = function (callback) { } } - var i, il, reply, args; + var i, il, reply, to_buffer, args; if (replies) { for (i = 1, il = self.queue.length; i < il; i += 1) { reply = replies[i - 1]; args = self.queue[i]; + to_buffer = wants_buffers[i]; + + // If we asked for strings, even in detect_buffers mode, then return strings: + if (self._client.options.detect_buffers && to_buffer === false) { + replies[i - 1] = reply = reply_to_strings(reply); + } // TODO - confusing and error-prone that hgetall is special cased in two places if (reply && args[0].toLowerCase() === "hgetall") { @@ -1212,40 +1230,29 @@ RedisClient.prototype.eval = RedisClient.prototype.EVAL = function () { }); }; - -exports.createClient = function(arg0, arg1, arg2){ - if( arguments.length === 0 ){ - - // createClient() - return createClient_tcp(default_port, default_host, {}); - - } else if( typeof arg0 === 'number' || - typeof arg0 === 'string' && arg0.match(/^\d+$/) ){ - - // createClient( 3000, host, options) - // createClient('3000', host, options) - return createClient_tcp(arg0, arg1, arg2); - - } else if( typeof arg0 === 'string' ){ - - // createClient( '/tmp/redis.sock', options) - return createClient_unix(arg0,arg1); - - } else if( arg0 !== null && typeof arg0 === 'object' ){ - - // createClient(options) - return createClient_tcp(default_port, default_host, arg0 ); - - } else if( arg0 === null && arg1 === null ){ - - // for backward compatibility - // createClient(null,null,options) - return createClient_tcp(default_port, default_host, arg2); - - } else { - throw new Error('unknown type of connection in createClient()'); +exports.createClient = function(arg0, arg1, options) { + if (typeof arg0 === 'object' || arg0 === undefined) { + options = arg0 || options; + return createClient_tcp(default_port, default_host, options); } -} + if (typeof arg0 === 'number' || typeof arg0 === 'string' && arg0.match(/^\d+$/)){ + return createClient_tcp(arg0, arg1, options); + } + if (typeof arg0 === 'string') { + options = arg1 || {}; + + var parsed = URL.parse(arg0, true, true); + if (parsed.hostname) { + if (parsed.auth) { + options.auth_pass = parsed.auth.split(':')[1]; + } + return createClient_tcp((parsed.port || default_port), parsed.hostname, options); + } + + return createClient_unix(arg0, options); + } + throw new Error('unknown type of connection in createClient()'); +}; var createClient_unix = function(path, options){ var cnxOptions = { @@ -1258,7 +1265,7 @@ var createClient_unix = function(path, options){ redis_client.address = path; return redis_client; -} +}; var createClient_tcp = function (port_arg, host_arg, options) { var cnxOptions = { diff --git a/lib/commands.js b/lib/commands.js index b0365350cd..3b01543d99 100644 --- a/lib/commands.js +++ b/lib/commands.js @@ -1,3 +1,5 @@ +'use strict'; + // This file was generated by ./generate_commands.js on Wed Apr 23 2014 14:51:21 GMT-0700 (PDT) module.exports = [ "append", diff --git a/lib/parser/hiredis.js b/lib/parser/hiredis.js index 940bfeeb76..29dbfd59a9 100644 --- a/lib/parser/hiredis.js +++ b/lib/parser/hiredis.js @@ -1,3 +1,5 @@ +'use strict'; + var events = require("events"), util = require("../util"), hiredis = require("hiredis"); diff --git a/lib/parser/javascript.js b/lib/parser/javascript.js index 0990cc098d..5ad0436215 100644 --- a/lib/parser/javascript.js +++ b/lib/parser/javascript.js @@ -1,3 +1,5 @@ +'use strict'; + var events = require("events"), util = require("../util"); diff --git a/lib/queue.js b/lib/queue.js index 3fc87ab102..d2eb17e56c 100644 --- a/lib/queue.js +++ b/lib/queue.js @@ -1,3 +1,5 @@ +'use strict'; + // Queue class adapted from Tim Caswell's pattern library // http://github.com/creationix/pattern/blob/master/lib/pattern/queue.js @@ -46,7 +48,7 @@ Queue.prototype.forEach = function (fn, thisv) { Queue.prototype.getLength = function () { return this.head.length - this.offset + this.tail.length; }; - + Object.defineProperty(Queue.prototype, "length", { get: function () { return this.getLength(); diff --git a/lib/to_array.js b/lib/to_array.js index 88a57e18a4..87804f8c3b 100644 --- a/lib/to_array.js +++ b/lib/to_array.js @@ -1,3 +1,5 @@ +'use strict'; + function to_array(args) { var len = args.length, arr = new Array(len), i; diff --git a/lib/util.js b/lib/util.js index fc255ae953..359cd7e948 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,3 +1,5 @@ +'use strict'; + // Support for very old versions of node where the module was called "sys". At some point, we should abandon this. var util; diff --git a/multi_bench.js b/multi_bench.js index 3a0d92da14..9f210a76d4 100644 --- a/multi_bench.js +++ b/multi_bench.js @@ -1,3 +1,5 @@ +'use strict'; + var redis = require("./index"), metrics = require("metrics"), num_clients = parseInt(process.argv[2], 10) || 5, @@ -7,7 +9,7 @@ var redis = require("./index"), client_options = { return_buffers: false }, - small_str, large_str, small_buf, large_buf; + small_str, large_str, small_buf, large_buf, very_large_str, very_large_buf; redis.debug_mode = false; @@ -23,13 +25,13 @@ function lpad(input, len, chr) { metrics.Histogram.prototype.print_line = function () { var obj = this.printObj(); - + return lpad(obj.min, 4) + "/" + lpad(obj.max, 4) + "/" + lpad(obj.mean.toFixed(2), 7) + "/" + lpad(obj.p95.toFixed(2), 7); }; function Test(args) { this.args = args; - + this.callback = null; this.clients = []; this.clients_ready = 0; @@ -37,7 +39,8 @@ function Test(args) { this.commands_completed = 0; this.max_pipeline = this.args.pipeline || num_requests; this.client_options = args.client_options || client_options; - + this.num_requests = args.reqs || num_requests; + this.connect_latency = new metrics.Histogram(); this.ready_latency = new metrics.Histogram(); this.command_latency = new metrics.Histogram(); @@ -55,7 +58,7 @@ Test.prototype.run = function (callback) { Test.prototype.new_client = function (id) { var self = this, new_client; - + new_client = redis.createClient(6379, "127.0.0.1", this.client_options); new_client.create_time = Date.now(); @@ -89,13 +92,13 @@ Test.prototype.on_clients_ready = function () { Test.prototype.fill_pipeline = function () { var pipeline = this.commands_sent - this.commands_completed; - while (this.commands_sent < num_requests && pipeline < this.max_pipeline) { + while (this.commands_sent < this.num_requests && pipeline < this.max_pipeline) { this.commands_sent++; pipeline++; this.send_next(); } - - if (this.commands_completed === num_requests) { + + if (this.commands_completed === this.num_requests) { this.print_stats(); this.stop_clients(); } @@ -103,7 +106,7 @@ Test.prototype.fill_pipeline = function () { Test.prototype.stop_clients = function () { var self = this; - + this.clients.forEach(function (client, pos) { if (pos === self.clients.length - 1) { client.quit(function (err, res) { @@ -132,80 +135,65 @@ Test.prototype.send_next = function () { Test.prototype.print_stats = function () { var duration = Date.now() - this.test_start; - + console.log("min/max/avg/p95: " + this.command_latency.print_line() + " " + lpad(duration, 6) + "ms total, " + - lpad((num_requests / (duration / 1000)).toFixed(2), 8) + " ops/sec"); + lpad((this.num_requests / (duration / 1000)).toFixed(2), 8) + " ops/sec"); }; small_str = "1234"; small_buf = new Buffer(small_str); -large_str = (new Array(4097).join("-")); +large_str = (new Array(4096 + 1).join("-")); large_buf = new Buffer(large_str); +very_large_str = (new Array((4 * 1024 * 1024) + 1).join("-")); +very_large_buf = new Buffer(very_large_str); tests.push(new Test({descr: "PING", command: "ping", args: [], pipeline: 1})); tests.push(new Test({descr: "PING", command: "ping", args: [], pipeline: 50})); -tests.push(new Test({descr: "PING", command: "ping", args: [], pipeline: 200})); -tests.push(new Test({descr: "PING", command: "ping", args: [], pipeline: 20000})); -tests.push(new Test({descr: "SET small str", command: "set", args: ["foo_rand000000000000", small_str], pipeline: 1})); -tests.push(new Test({descr: "SET small str", command: "set", args: ["foo_rand000000000000", small_str], pipeline: 50})); -tests.push(new Test({descr: "SET small str", command: "set", args: ["foo_rand000000000000", small_str], pipeline: 200})); -tests.push(new Test({descr: "SET small str", command: "set", args: ["foo_rand000000000000", small_str], pipeline: 20000})); +tests.push(new Test({descr: "SET 4B str", command: "set", args: ["foo_rand000000000000", small_str], pipeline: 1})); +tests.push(new Test({descr: "SET 4B str", command: "set", args: ["foo_rand000000000000", small_str], pipeline: 50})); -tests.push(new Test({descr: "SET small buf", command: "set", args: ["foo_rand000000000000", small_buf], pipeline: 1})); -tests.push(new Test({descr: "SET small buf", command: "set", args: ["foo_rand000000000000", small_buf], pipeline: 50})); -tests.push(new Test({descr: "SET small buf", command: "set", args: ["foo_rand000000000000", small_buf], pipeline: 200})); -tests.push(new Test({descr: "SET small buf", command: "set", args: ["foo_rand000000000000", small_buf], pipeline: 20000})); +tests.push(new Test({descr: "SET 4B buf", command: "set", args: ["foo_rand000000000000", small_buf], pipeline: 1})); +tests.push(new Test({descr: "SET 4B buf", command: "set", args: ["foo_rand000000000000", small_buf], pipeline: 50})); -tests.push(new Test({descr: "GET small str", command: "get", args: ["foo_rand000000000000"], pipeline: 1})); -tests.push(new Test({descr: "GET small str", command: "get", args: ["foo_rand000000000000"], pipeline: 50})); -tests.push(new Test({descr: "GET small str", command: "get", args: ["foo_rand000000000000"], pipeline: 200})); -tests.push(new Test({descr: "GET small str", command: "get", args: ["foo_rand000000000000"], pipeline: 20000})); +tests.push(new Test({descr: "GET 4B str", command: "get", args: ["foo_rand000000000000"], pipeline: 1})); +tests.push(new Test({descr: "GET 4B str", command: "get", args: ["foo_rand000000000000"], pipeline: 50})); -tests.push(new Test({descr: "GET small buf", command: "get", args: ["foo_rand000000000000"], pipeline: 1, client_opts: { return_buffers: true} })); -tests.push(new Test({descr: "GET small buf", command: "get", args: ["foo_rand000000000000"], pipeline: 50, client_opts: { return_buffers: true} })); -tests.push(new Test({descr: "GET small buf", command: "get", args: ["foo_rand000000000000"], pipeline: 200, client_opts: { return_buffers: true} })); -tests.push(new Test({descr: "GET small buf", command: "get", args: ["foo_rand000000000000"], pipeline: 20000, client_opts: { return_buffers: true} })); +tests.push(new Test({descr: "GET 4B buf", command: "get", args: ["foo_rand000000000000"], pipeline: 1, client_opts: { return_buffers: true} })); +tests.push(new Test({descr: "GET 4B buf", command: "get", args: ["foo_rand000000000000"], pipeline: 50, client_opts: { return_buffers: true} })); -tests.push(new Test({descr: "SET large str", command: "set", args: ["foo_rand000000000001", large_str], pipeline: 1})); -tests.push(new Test({descr: "SET large str", command: "set", args: ["foo_rand000000000001", large_str], pipeline: 50})); -tests.push(new Test({descr: "SET large str", command: "set", args: ["foo_rand000000000001", large_str], pipeline: 200})); -tests.push(new Test({descr: "SET large str", command: "set", args: ["foo_rand000000000001", large_str], pipeline: 20000})); +tests.push(new Test({descr: "SET 4KiB str", command: "set", args: ["foo_rand000000000001", large_str], pipeline: 1})); +tests.push(new Test({descr: "SET 4KiB str", command: "set", args: ["foo_rand000000000001", large_str], pipeline: 50})); -tests.push(new Test({descr: "SET large buf", command: "set", args: ["foo_rand000000000001", large_buf], pipeline: 1})); -tests.push(new Test({descr: "SET large buf", command: "set", args: ["foo_rand000000000001", large_buf], pipeline: 50})); -tests.push(new Test({descr: "SET large buf", command: "set", args: ["foo_rand000000000001", large_buf], pipeline: 200})); -tests.push(new Test({descr: "SET large buf", command: "set", args: ["foo_rand000000000001", large_buf], pipeline: 20000})); +tests.push(new Test({descr: "SET 4KiB buf", command: "set", args: ["foo_rand000000000001", large_buf], pipeline: 1})); +tests.push(new Test({descr: "SET 4KiB buf", command: "set", args: ["foo_rand000000000001", large_buf], pipeline: 50})); -tests.push(new Test({descr: "GET large str", command: "get", args: ["foo_rand000000000001"], pipeline: 1})); -tests.push(new Test({descr: "GET large str", command: "get", args: ["foo_rand000000000001"], pipeline: 50})); -tests.push(new Test({descr: "GET large str", command: "get", args: ["foo_rand000000000001"], pipeline: 200})); -tests.push(new Test({descr: "GET large str", command: "get", args: ["foo_rand000000000001"], pipeline: 20000})); +tests.push(new Test({descr: "GET 4KiB str", command: "get", args: ["foo_rand000000000001"], pipeline: 1})); +tests.push(new Test({descr: "GET 4KiB str", command: "get", args: ["foo_rand000000000001"], pipeline: 50})); -tests.push(new Test({descr: "GET large buf", command: "get", args: ["foo_rand000000000001"], pipeline: 1, client_opts: { return_buffers: true} })); -tests.push(new Test({descr: "GET large buf", command: "get", args: ["foo_rand000000000001"], pipeline: 50, client_opts: { return_buffers: true} })); -tests.push(new Test({descr: "GET large buf", command: "get", args: ["foo_rand000000000001"], pipeline: 200, client_opts: { return_buffers: true} })); -tests.push(new Test({descr: "GET large buf", command: "get", args: ["foo_rand000000000001"], pipeline: 20000, client_opts: { return_buffers: true} })); +tests.push(new Test({descr: "GET 4KiB buf", command: "get", args: ["foo_rand000000000001"], pipeline: 1, client_opts: { return_buffers: true} })); +tests.push(new Test({descr: "GET 4KiB buf", command: "get", args: ["foo_rand000000000001"], pipeline: 50, client_opts: { return_buffers: true} })); tests.push(new Test({descr: "INCR", command: "incr", args: ["counter_rand000000000000"], pipeline: 1})); tests.push(new Test({descr: "INCR", command: "incr", args: ["counter_rand000000000000"], pipeline: 50})); -tests.push(new Test({descr: "INCR", command: "incr", args: ["counter_rand000000000000"], pipeline: 200})); -tests.push(new Test({descr: "INCR", command: "incr", args: ["counter_rand000000000000"], pipeline: 20000})); tests.push(new Test({descr: "LPUSH", command: "lpush", args: ["mylist", small_str], pipeline: 1})); tests.push(new Test({descr: "LPUSH", command: "lpush", args: ["mylist", small_str], pipeline: 50})); -tests.push(new Test({descr: "LPUSH", command: "lpush", args: ["mylist", small_str], pipeline: 200})); -tests.push(new Test({descr: "LPUSH", command: "lpush", args: ["mylist", small_str], pipeline: 20000})); tests.push(new Test({descr: "LRANGE 10", command: "lrange", args: ["mylist", "0", "9"], pipeline: 1})); tests.push(new Test({descr: "LRANGE 10", command: "lrange", args: ["mylist", "0", "9"], pipeline: 50})); -tests.push(new Test({descr: "LRANGE 10", command: "lrange", args: ["mylist", "0", "9"], pipeline: 200})); -tests.push(new Test({descr: "LRANGE 10", command: "lrange", args: ["mylist", "0", "9"], pipeline: 20000})); tests.push(new Test({descr: "LRANGE 100", command: "lrange", args: ["mylist", "0", "99"], pipeline: 1})); tests.push(new Test({descr: "LRANGE 100", command: "lrange", args: ["mylist", "0", "99"], pipeline: 50})); -tests.push(new Test({descr: "LRANGE 100", command: "lrange", args: ["mylist", "0", "99"], pipeline: 200})); -tests.push(new Test({descr: "LRANGE 100", command: "lrange", args: ["mylist", "0", "99"], pipeline: 20000})); + +tests.push(new Test({descr: "SET 4MiB buf", command: "set", args: ["foo_rand000000000002", very_large_buf], pipeline: 1, reqs: 500})); +tests.push(new Test({descr: "SET 4MiB buf", command: "set", args: ["foo_rand000000000002", very_large_buf], pipeline: 50, reqs: 500})); + +tests.push(new Test({descr: "GET 4MiB str", command: "get", args: ["foo_rand000000000002"], pipeline: 1, reqs: 100})); +tests.push(new Test({descr: "GET 4MiB str", command: "get", args: ["foo_rand000000000002"], pipeline: 50, reqs: 100})); + +tests.push(new Test({descr: "GET 4MiB buf", command: "get", args: ["foo_rand000000000002"], pipeline: 1, reqs: 100, client_opts: { return_buffers: true} })); +tests.push(new Test({descr: "GET 4MiB buf", command: "get", args: ["foo_rand000000000002"], pipeline: 50, reqs: 100, client_opts: { return_buffers: true} })); function next() { var test = tests.shift(); diff --git a/package.json b/package.json index dc1c39abd2..2e7d9ea181 100644 --- a/package.json +++ b/package.json @@ -3,18 +3,27 @@ "version": "0.12.1", "description": "Redis client library", "keywords": [ - "redis", - "database" + "database", + "redis" ], "author": "Matt Ranney ", + "license": "MIT", "main": "./index.js", "scripts": { - "test": "node ./test.js" + "coverage": "nyc report --reporter=text-lcov | coveralls", + "test": "nyc ./node_modules/.bin/_mocha ./test/*.js ./test/commands/*.js ./test/parser/*.js --timeout=8000" }, "devDependencies": { - "metrics": ">=0.1.5", + "async": "^1.3.0", "colors": "~0.6.0-1", - "underscore": "~1.4.4" + "coveralls": "^2.11.2", + "hiredis": "^0.4.0", + "metrics": ">=0.1.5", + "mocha": "^2.2.5", + "nyc": "^3.0.0", + "tcp-port-used": "^0.1.2", + "underscore": "~1.4.4", + "uuid": "^2.0.1" }, "repository": { "type": "git", diff --git a/test-unref.js b/test-unref.js deleted file mode 100644 index 4a3cc3622d..0000000000 --- a/test-unref.js +++ /dev/null @@ -1,12 +0,0 @@ -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()) -}) \ No newline at end of file diff --git a/test.js b/test.js deleted file mode 100644 index b6df1b5171..0000000000 --- a/test.js +++ /dev/null @@ -1,2284 +0,0 @@ -/*global require console setTimeout process Buffer */ -var PORT = 6379; -var HOST = '127.0.0.1'; - -var redis = require("./index"), - client = redis.createClient(PORT, HOST), - client2 = redis.createClient(PORT, HOST), - client3 = redis.createClient(PORT, HOST), - bclient = redis.createClient(PORT, HOST, { return_buffers: true }), - 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]; - -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. - -tests.IPV4 = function () { - var ipv4Client = redis.createClient( PORT, "127.0.0.1", { "family" : "IPv4" } ); - - 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 ipv6Client = redis.createClient( PORT, "::1", { "family" : "IPv6" } ); - - 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'); - - // 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); - }); -}; - -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.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"; - - 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({detect_buffers: true}); - - 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.socket_nodelay = function () { - var name = "socket_nodelay", c1, c2, c3, ready_count = 0, quit_count = 0; - - c1 = redis.createClient({socket_nodelay: true}); - c2 = redis.createClient({socket_nodelay: false}); - c3 = redis.createClient(); - - 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.reconnect = function () { - var name = "reconnect"; - - 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(); - }); - - client.on("reconnecting", function on_recon(params) { - client.on("connect", function on_connect() { - client.select(test_db_num, require_string("OK", name)); - client.get("recon 1", require_string("one", name)); - client.get("recon 1", require_string("one", name)); - client.get("recon 2", require_string("two", name)); - client.get("recon 2", require_string("two", name)); - client.removeListener("connect", on_connect); - client.removeListener("reconnecting", on_recon); - next(name); - }); - }); -}; - -tests.reconnect_select_db_after_pubsub = function() { - var name = "reconnect_select_db_after_pubsub"; - - client.select(test_db_num); - client.set(name, "one"); - client.subscribe('ChannelV', function (err, res) { - client.stream.destroy(); - }); - - client.on("reconnecting", function on_recon(params) { - client.on("ready", function on_connect() { - client.unsubscribe('ChannelV', function (err, res) { - client.get(name, require_string("one", name)); - client.removeListener("connect", on_connect); - client.removeListener("reconnecting", on_recon); - next(name); - }); - }); - }); -}; - -tests.select_error_emits_if_no_callback = function () { - var prev = client.listeners("error")[0]; - client.removeListener("error", prev); - var name = "select_error_emits_if_no_callback"; - var handler = with_timeout(name, function (err) { - require_error(name)(err); - client.removeListener('error', handler); - client.on("error", prev); - next(name); - }, 500); - client.on('error', handler); - client.select(9999); -}; - -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(); - var c2 = redis.createClient(); - 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.DBSIZE = function () { - var name = "DBSIZE"; - client.set(['foo', 'bar'], require_string("OK", name)); - client.DBSIZE([], last(name, require_number_pos("DBSIZE"))); -}; - -tests.GET_1 = function () { - var name = "GET_1"; - client.set(["get key", "get val"], require_string("OK", name)); - client.GET(["get key"], last(name, require_string("get val", name))); -}; - -tests.GET_2 = function() { - var name = "GET_2"; - - // tests handling of non-existent keys - client.GET('this_key_shouldnt_exist', last(name, require_null(name))); -}; - -tests.SET = function () { - var name = "SET"; - client.SET(["set key", "set val"], require_string("OK", name)); - client.get(["set key"], last(name, require_string("set val", name))); - client.SET(["set key", undefined], require_error(name)); -}; - -tests.GETSET = function () { - var name = "GETSET"; - client.set(["getset key", "getset val"], require_string("OK", name)); - client.GETSET(["getset key", "new getset val"], require_string("getset val", name)); - client.get(["getset key"], last(name, require_string("new getset val", 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); - }); -}; - -tests.SPOP = function () { - var name = "SPOP"; - - client.del('zzz'); - client.sadd('zzz', 'member0', require_number(1, name)); - client.scard('zzz', require_number(1, name)); - - client.spop('zzz', function (err, value) { - if (err) { - assert.fail(err); - } - assert.equal(value, 'member0', name); - }); - - client.scard('zzz', last(name, require_number(0, name))); -}; - -tests.SDIFF = function () { - var name = "SDIFF"; - - client.del('foo'); - client.sadd('foo', 'x', require_number(1, name)); - client.sadd('foo', 'a', require_number(1, name)); - client.sadd('foo', 'b', require_number(1, name)); - client.sadd('foo', 'c', require_number(1, name)); - - client.sadd('bar', 'c', require_number(1, name)); - - client.sadd('baz', 'a', require_number(1, name)); - client.sadd('baz', 'd', require_number(1, name)); - - client.sdiff('foo', 'bar', 'baz', function (err, values) { - if (err) { - assert.fail(err, name); - } - values.sort(); - assert.equal(values.length, 2, name); - assert.equal(values[0], 'b', name); - assert.equal(values[1], 'x', name); - next(name); - }); -}; - -tests.SDIFFSTORE = function () { - var name = "SDIFFSTORE"; - - client.del('foo'); - client.del('bar'); - client.del('baz'); - client.del('quux'); - - client.sadd('foo', 'x', require_number(1, name)); - client.sadd('foo', 'a', require_number(1, name)); - client.sadd('foo', 'b', require_number(1, name)); - client.sadd('foo', 'c', require_number(1, name)); - - client.sadd('bar', 'c', require_number(1, name)); - - client.sadd('baz', 'a', require_number(1, name)); - client.sadd('baz', 'd', require_number(1, name)); - - // NB: SDIFFSTORE returns the number of elements in the dstkey - - client.sdiffstore('quux', 'foo', 'bar', 'baz', require_number(2, name)); - - client.smembers('quux', function (err, values) { - if (err) { - assert.fail(err, name); - } - var members = buffers_to_strings(values).sort(); - - assert.deepEqual(members, [ 'b', 'x' ], name); - next(name); - }); -}; - -tests.SMEMBERS = function () { - var name = "SMEMBERS"; - - client.del('foo'); - client.sadd('foo', 'x', require_number(1, name)); - - client.smembers('foo', function (err, members) { - if (err) { - assert.fail(err, name); - } - assert.deepEqual(buffers_to_strings(members), [ 'x' ], name); - }); - - client.sadd('foo', 'y', require_number(1, name)); - - client.smembers('foo', function (err, values) { - if (err) { - assert.fail(err, name); - } - assert.equal(values.length, 2, name); - var members = buffers_to_strings(values).sort(); - - assert.deepEqual(members, [ 'x', 'y' ], name); - next(name); - }); -}; - -tests.SMOVE = function () { - var name = "SMOVE"; - - client.del('foo'); - client.del('bar'); - - client.sadd('foo', 'x', require_number(1, name)); - client.smove('foo', 'bar', 'x', require_number(1, name)); - client.sismember('foo', 'x', require_number(0, name)); - client.sismember('bar', 'x', require_number(1, name)); - 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"; - - client.del('sa'); - client.del('sb'); - client.del('sc'); - client.del('foo'); - - 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.sinterstore('foo', 'sa', 'sb', 'sc', require_number(1, name)); - - client.smembers('foo', function (err, members) { - if (err) { - assert.fail(err, name); - } - assert.deepEqual(buffers_to_strings(members), [ 'c' ], name); - next(name); - }); -}; - -tests.SUNION = function () { - var name = "SUNION"; - - 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.sunion('sa', 'sb', 'sc', function (err, union) { - if (err) { - assert.fail(err, name); - } - assert.deepEqual(buffers_to_strings(union).sort(), ['a', 'b', 'c', 'd', 'e'], name); - next(name); - }); -}; - -tests.SUNIONSTORE = function () { - var name = "SUNIONSTORE"; - - client.del('sa'); - client.del('sb'); - client.del('sc'); - client.del('foo'); - - 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.sunionstore('foo', 'sa', 'sb', 'sc', function (err, cardinality) { - if (err) { - assert.fail(err, name); - } - assert.equal(cardinality, 5, name); - }); - - client.smembers('foo', function (err, members) { - if (err) { - assert.fail(err, name); - } - assert.equal(members.length, 5, name); - assert.deepEqual(buffers_to_strings(members).sort(), ['a', 'b', 'c', 'd', 'e'], name); - next(name); - }); -}; - -// 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; - - if (!server_version_at_least(client, [2, 6, 0])) { - console.log("Skipping " + name + " for old Redis server version < 2.6.x"); - return next(name); - } - - monitor_client = redis.createClient(); - monitor_client.monitor(function (err, res) { - client.mget("some", "keys", "foo", "bar"); - client.set("json", JSON.stringify({ - foo: "123", - bar: "sdflkdfsjk", - another: false - })); - }); - monitor_client.on("monitor", function (time, args) { - // skip monitor command for Redis <= 2.4.16 - if (args[0] === "monitor") return; - - responses.push(args); - if (responses.length === 2) { - assert.strictEqual(5, responses[0].length); - assert.strictEqual("mget", responses[0][0]); - assert.strictEqual("some", responses[0][1]); - assert.strictEqual("keys", responses[0][2]); - assert.strictEqual("foo", responses[0][3]); - assert.strictEqual("bar", responses[0][4]); - assert.strictEqual(3, responses[1].length); - assert.strictEqual("set", responses[1][0]); - assert.strictEqual("json", responses[1][1]); - assert.strictEqual('{"foo":"123","bar":"sdflkdfsjk","another":false}', responses[1][2]); - monitor_client.quit(function (err, res) { - next(name); - }); - } - }); -}; - -tests.BLPOP = function () { - var name = "BLPOP"; - - client.rpush("blocking list", "initial value", function (err, res) { - client2.BLPOP("blocking list", 0, function (err, res) { - assert.strictEqual("blocking list", res[0].toString()); - assert.strictEqual("initial value", res[1].toString()); - - client.rpush("blocking list", "wait for this value"); - }); - client2.BLPOP("blocking list", 0, function (err, res) { - assert.strictEqual("blocking list", res[0].toString()); - assert.strictEqual("wait for this value", res[1].toString()); - next(name); - }); - }); -}; - -tests.BLPOP_TIMEOUT = function () { - var name = "BLPOP_TIMEOUT"; - - // try to BLPOP the list again, which should be empty. This should timeout and return null. - client2.BLPOP("blocking list", 1, function (err, res) { - if (err) { - throw err; - } - - assert.strictEqual(res, null); - next(name); - }); -}; - -tests.EXPIRE = function () { - var name = "EXPIRE"; - client.set(['expiry key', 'bar'], require_string("OK", name)); - client.EXPIRE(["expiry key", "1"], require_number_pos(name)); - setTimeout(function () { - client.exists(["expiry key"], last(name, require_number(0, name))); - }, 2000); -}; - -tests.TTL = function () { - var name = "TTL"; - client.set(["ttl key", "ttl val"], require_string("OK", name)); - client.expire(["ttl key", "100"], require_number_pos(name)); - setTimeout(function () { - client.TTL(["ttl key"], last(name, require_number_pos(0, name))); - }, 500); -}; - -tests.OPTIONAL_CALLBACK = function () { - var name = "OPTIONAL_CALLBACK"; - client.del("op_cb1"); - client.set("op_cb1", "x"); - client.get("op_cb1", last(name, require_string("x", name))); -}; - -tests.OPTIONAL_CALLBACK_UNDEFINED = function () { - var name = "OPTIONAL_CALLBACK_UNDEFINED"; - client.del("op_cb2"); - client.set("op_cb2", "y", undefined); - client.get("op_cb2", last(name, require_string("y", name))); - - 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 - // 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, { - 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)); - client.slowlog("reset", require_string("OK", name)); - client.set("foo", "bar", require_string("OK", name)); - client.get("foo", require_string("bar", name)); - client.slowlog("get", function (err, res) { - assert.equal(res.length, 3, name); - assert.equal(res[0][3].length, 2, name); - assert.deepEqual(res[1][3], ["set", "foo", "bar"], name); - assert.deepEqual(res[2][3], ["slowlog", "reset"], name); - client.config("set", "slowlog-log-slower-than", 10000, require_string("OK", name)); - 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) { 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"); - 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"}); - - // 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 - }); - 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-unref.js"); - 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); - }, 500); -}; - -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") { - util.print('- \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); -}); diff --git a/test/auth.spec.js b/test/auth.spec.js new file mode 100644 index 0000000000..1854a84744 --- /dev/null +++ b/test/auth.spec.js @@ -0,0 +1,91 @@ +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/blpop.spec.js b/test/commands/blpop.spec.js new file mode 100644 index 0000000000..1e344e023b --- /dev/null +++ b/test/commands/blpop.spec.js @@ -0,0 +1,64 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'blpop' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var client; + var bclient; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.flushdb(done); + }); + }); + + it('pops value immediately if list contains values', function (done) { + bclient = redis.createClient.apply(redis.createClient, args); + client.rpush("blocking list", "initial value", helper.isNumber(1)); + bclient.blpop("blocking list", 0, function (err, value) { + assert.strictEqual(value[0], "blocking list"); + assert.strictEqual(value[1], "initial value"); + return done(err); + }); + }); + + it('waits for value if list is not yet populated', function (done) { + bclient = redis.createClient.apply(redis.createClient, args); + bclient.blpop("blocking list 2", 5, function (err, value) { + assert.strictEqual(value[0], "blocking list 2"); + assert.strictEqual(value[1], "initial value"); + return done(err); + }); + client.rpush("blocking list 2", "initial value", helper.isNumber(1)); + }); + + it('times out after specified time', function (done) { + bclient = redis.createClient.apply(redis.createClient, args); + bclient.BLPOP("blocking list", 1, function (err, res) { + assert.strictEqual(res, null); + return done(err); + }); + }); + + afterEach(function () { + client.end(); + bclient.end(); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); 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/commands/dbsize.spec.js b/test/commands/dbsize.spec.js new file mode 100644 index 0000000000..70c7eb178c --- /dev/null +++ b/test/commands/dbsize.spec.js @@ -0,0 +1,106 @@ +var async = require('async'); +var assert = require('assert'); +var config = require("../lib/config"); +var helper = require('../helper'); +var redis = config.redis; +var uuid = require('uuid'); + +describe("The 'dbsize' method", function () { + + 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.dbsize([], 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.flushdb(function (err, res) { + helper.isString("OK")(err, res); + done(); + }); + }); + }); + + afterEach(function () { + client.end(); + }); + + it("returns a zero db size", function (done) { + client.dbsize([], function (err, res) { + helper.isNotError()(err, res); + helper.isType.number()(err, res); + assert.strictEqual(res, 0, "Initial db size should be 0"); + done(); + }); + }); + + describe("when more data is added to Redis", function () { + var oldSize; + + beforeEach(function (done) { + client.dbsize([], function (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) { + helper.isNotError()(err, res); + done(); + }); + }); + }); + + it("returns a larger db size", function (done) { + client.dbsize([], function (err, res) { + helper.isNotError()(err, res); + helper.isType.positiveNumber()(err, res); + assert.strictEqual(true, (oldSize < res), "Adding data should increase db size."); + done(); + }); + }); + }); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); 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/commands/expire.spec.js b/test/commands/expire.spec.js new file mode 100644 index 0000000000..eab08468e9 --- /dev/null +++ b/test/commands/expire.spec.js @@ -0,0 +1,42 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'expire' 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('expires key after timeout', function (done) { + client.set(['expiry key', 'bar'], helper.isString("OK")); + client.EXPIRE(["expiry key", "1"], helper.isNumber(1)); + setTimeout(function () { + client.exists(["expiry key"], helper.isNumber(0, done)); + }, 1500); + }); + + 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/flushdb.spec.js b/test/commands/flushdb.spec.js new file mode 100644 index 0000000000..8df39f4752 --- /dev/null +++ b/test/commands/flushdb.spec.js @@ -0,0 +1,115 @@ +var async = require('async'); +var assert = require('assert'); +var config = require("../lib/config"); +var helper = require('../helper'); +var redis = config.redis; +var uuid = require('uuid'); + +describe("The 'flushdb' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var key, key2; + + beforeEach(function () { + key = uuid.v4(); + key2 = 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.flushdb(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 () { + done(); + }); + }); + + afterEach(function () { + client.end(); + }); + + describe("when there is data in Redis", function () { + var oldSize; + + beforeEach(function (done) { + async.parallel([function (next) { + client.mset(key, uuid.v4(), key2, uuid.v4(), function (err, res) { + helper.isNotError()(err, res); + next(err); + }); + }, function (next) { + client.dbsize([], function (err, res) { + helper.isType.positiveNumber()(err, res); + oldSize = res; + next(err); + }); + }], function (err) { + if (err) { + return done(err); + } + + client.flushdb(function (err, res) { + helper.isString("OK")(err, res); + done(err); + }); + }); + }); + + it("deletes all the keys", function (done) { + client.mget(key, key2, function (err, res) { + assert.strictEqual(null, err, "Unexpected error returned"); + assert.strictEqual(true, Array.isArray(res), "Results object should be an array."); + assert.strictEqual(2, res.length, "Results array should have length 2."); + assert.strictEqual(null, res[0], "Redis key should have been flushed."); + assert.strictEqual(null, res[1], "Redis key should have been flushed."); + done(err); + }); + }); + + it("results in a db size of zero", function (done) { + client.dbsize([], function (err, res) { + helper.isNotError()(err, res); + helper.isType.number()(err, res); + assert.strictEqual(0, res, "Flushing db should result in db size 0"); + done(); + }); + }); + }); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/get.spec.js b/test/commands/get.spec.js new file mode 100644 index 0000000000..31502c2c4b --- /dev/null +++ b/test/commands/get.spec.js @@ -0,0 +1,92 @@ +var async = require('async'); +var assert = require('assert'); +var config = require("../lib/config"); +var helper = require('../helper'); +var redis = config.redis; +var uuid = require('uuid'); + +describe("The 'get' method", function () { + + 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.get(key, 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 () { + done(); + }); + }); + + afterEach(function () { + client.end(); + }); + + describe("when the key exists in Redis", function () { + beforeEach(function (done) { + client.set(key, value, function (err, res) { + helper.isNotError()(err, res); + done(); + }); + }); + + it("gets the value correctly", function (done) { + client.get(key, function (err, res) { + helper.isString(value)(err, res); + done(err); + }); + }); + }); + + describe("when the key does not exist in Redis", function () { + it("gets a null value", function (done) { + client.get(key, function (err, res) { + helper.isNull()(err, res); + 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/getset.spec.js b/test/commands/getset.spec.js new file mode 100644 index 0000000000..a8e90ecfa2 --- /dev/null +++ b/test/commands/getset.spec.js @@ -0,0 +1,96 @@ +var async = require('async'); +var assert = require('assert'); +var config = require("../lib/config"); +var helper = require('../helper'); +var redis = config.redis; +var uuid = require('uuid'); + +describe("The 'getset' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var key, value, value2; + + beforeEach(function () { + key = uuid.v4(); + value = uuid.v4(); + value2 = 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.get(key, 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 () { + done(); + }); + }); + + afterEach(function () { + client.end(); + }); + + describe("when the key exists in Redis", function () { + beforeEach(function (done) { + client.set(key, value, function (err, res) { + helper.isNotError()(err, res); + done(); + }); + }); + + it("gets the value correctly", function (done) { + client.getset(key, value2, function (err, res) { + helper.isString(value)(err, res); + client.get(key, function (err, res) { + helper.isString(value2)(err, res); + done(err); + }); + }); + }); + }); + + describe("when the key does not exist in Redis", function () { + it("gets a null value", function (done) { + client.getset(key, value, function (err, res) { + helper.isNull()(err, res); + 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/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/commands/incr.spec.js b/test/commands/incr.spec.js new file mode 100644 index 0000000000..bdd22bacf1 --- /dev/null +++ b/test/commands/incr.spec.js @@ -0,0 +1,124 @@ +var async = require('async'); +var assert = require('assert'); +var config = require("../lib/config"); +var helper = require('../helper'); +var redis = config.redis; +var uuid = require('uuid'); + +describe("The 'incr' method", function () { + + function allTests(parser, ip) { + var args = config.configureClient(parser, ip); + + describe("using " + parser + " and " + ip, function () { + var key = "sequence"; + + 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.set(key, "9007199254740992", function (err, res) { + helper.isNotError()(err, res); + client.quit(); + }); + }); + client.on('end', function () { + return done(); + }); + }); + + 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.'); + done(); + }); + }); + }); + + describe("when connected and a value in Redis", function () { + var client; + + // Also, why tf were these disabled for hiredis? They work just fine. + before(function (done) { + /* + 9007199254740992 -> 9007199254740992 + 9007199254740993 -> 9007199254740992 + 9007199254740994 -> 9007199254740994 + 9007199254740995 -> 9007199254740996 + 9007199254740996 -> 9007199254740996 + 9007199254740997 -> 9007199254740996 + */ + client = redis.createClient.apply(redis.createClient, args); + client.once("error", done); + client.once("connect", function () { + client.set(key, "9007199254740992", function (err, res) { + helper.isNotError()(err, res); + done(); + }); + }); + }); + + after(function () { + client.end(); + }); + + it("changes the last digit from 2 to 3", function (done) { + client.incr(key, function (err, res) { + helper.isString("9007199254740993")(err, res); + done(err); + }); + }); + + describe("and we call it again", function () { + it("changes the last digit from 3 to 4", function (done) { + client.incr(key, function (err, res) { + helper.isString("9007199254740994")(err, res); + done(err); + }); + }); + + describe("and again", function () { + it("changes the last digit from 4 to 5", function (done) { + client.incr(key, function (err, res) { + helper.isString("9007199254740995")(err, res); + done(err); + }); + }); + + describe("and again", function () { + it("changes the last digit from 5 to 6", function (done) { + client.incr(key, function (err, res) { + helper.isString("9007199254740996")(err, res); + done(err); + }); + }); + + describe("and again", function () { + it("changes the last digit from 6 to 7", function (done) { + client.incr(key, function (err, res) { + helper.isString("9007199254740997")(err, res); + 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/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/commands/mset.spec.js b/test/commands/mset.spec.js new file mode 100644 index 0000000000..d15c70c1a4 --- /dev/null +++ b/test/commands/mset.spec.js @@ -0,0 +1,141 @@ +var async = require('async'); +var assert = require('assert'); +var config = require("../lib/config"); +var helper = require('../helper'); +var redis = config.redis; +var uuid = require('uuid'); + +describe("The 'mset' method", function () { + + 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, key2, value2; + + beforeEach(function () { + key = uuid.v4(); + value = uuid.v4(); + key2 = uuid.v4(); + value2 = 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.mset(key, value, key2, value2, 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 () { + done(); + }); + }); + + afterEach(function () { + client.end(); + }); + + describe("and a callback is specified", function () { + describe("with valid parameters", function () { + it("sets the value correctly", function (done) { + client.mset(key, value, key2, value2); + client.get(key, helper.isString(value)); + client.get(key2, helper.isString(value2, done)); + }); + }); + + describe("with undefined 'key' parameter and missing 'value' parameter", function () { + it("reports an error", function (done) { + client.mset(undefined, function (err, res) { + helper.isError()(err, null); + done(); + }); + }); + }); + + describe("with undefined 'key' and defined 'value' parameters", function () { + it("reports an error", function () { + client.mset(undefined, value, undefined, value2, function (err, res) { + helper.isError()(err, null); + done(); + }); + }); + }); + }); + + describe("and no callback is specified", function () { + describe("with valid parameters", function () { + it("sets the value correctly", function (done) { + client.mset(key, value, key2, value2); + client.get(key, helper.isString(value)); + client.get(key2, helper.isString(value2, done)); + }); + }); + + describe("with undefined 'key' and missing 'value' parameter", function () { + // this behavior is different from the 'set' behavior. + it("throws an error", function (done) { + var mochaListener = removeMochaListener(); + + process.once('uncaughtException', function (err) { + process.on('uncaughtException', mochaListener); + helper.isError()(err, null); + return done(); + }); + + client.mset(); + }); + }); + + describe("with undefined 'key' and defined 'value' parameters", function () { + it("throws an error", function () { + var mochaListener = removeMochaListener(); + + process.once('uncaughtException', function (err) { + process.on('uncaughtException', mochaListener); + helper.isError()(err, null); + }); + + client.mset(undefined, value, undefined, value2); + }); + }); + }); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); 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/commands/multi.spec.js b/test/commands/multi.spec.js new file mode 100644 index 0000000000..21526334a1 --- /dev/null +++ b/test/commands/multi.spec.js @@ -0,0 +1,250 @@ +var async = require('async'); +var assert = require('assert'); +var config = require("../lib/config"); +var helper = require('../helper'); +var redis = config.redis; +var uuid = require('uuid'); + +describe("The 'multi' method", function () { + + 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.flushdb(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", 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 (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", 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]); + 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", 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 (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", 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]); + 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", helper.isError()], + ["incr", "multifoo", helper.isNumber(1)], + ["incr", "multibar", helper.isNumber(1)] + ]).exec(function (err, replies) { + if (helper.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 (!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"); + 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); + }) + }); +}); 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/commands/sdiff.spec.js b/test/commands/sdiff.spec.js new file mode 100644 index 0000000000..deb0628326 --- /dev/null +++ b/test/commands/sdiff.spec.js @@ -0,0 +1,54 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'sdiff' 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 set difference', function (done) { + client.sadd('foo', 'x', helper.isNumber(1)); + client.sadd('foo', 'a', helper.isNumber(1)); + client.sadd('foo', 'b', helper.isNumber(1)); + client.sadd('foo', 'c', helper.isNumber(1)); + + client.sadd('bar', 'c', helper.isNumber(1)); + + client.sadd('baz', 'a', helper.isNumber(1)); + client.sadd('baz', 'd', helper.isNumber(1)); + + client.sdiff('foo', 'bar', 'baz', function (err, values) { + values.sort(); + assert.equal(values.length, 2); + assert.equal(values[0], 'b'); + assert.equal(values[1], 'x'); + 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/sdiffstore.spec.js b/test/commands/sdiffstore.spec.js new file mode 100644 index 0000000000..2aac531b4f --- /dev/null +++ b/test/commands/sdiffstore.spec.js @@ -0,0 +1,54 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'sdiffstore' 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('calculates set difference ands stores it in a key', function (done) { + client.sadd('foo', 'x', helper.isNumber(1)); + client.sadd('foo', 'a', helper.isNumber(1)); + client.sadd('foo', 'b', helper.isNumber(1)); + client.sadd('foo', 'c', helper.isNumber(1)); + + client.sadd('bar', 'c', helper.isNumber(1)); + + client.sadd('baz', 'a', helper.isNumber(1)); + client.sadd('baz', 'd', helper.isNumber(1)); + + client.sdiffstore('quux', 'foo', 'bar', 'baz', helper.isNumber(2)); + + client.smembers('quux', function (err, values) { + var members = values.sort(); + assert.deepEqual(members, [ 'b', 'x' ]); + 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/select.spec.js b/test/commands/select.spec.js new file mode 100644 index 0000000000..1be845dc00 --- /dev/null +++ b/test/commands/select.spec.js @@ -0,0 +1,123 @@ +var async = require('async'); +var assert = require('assert'); +var config = require("../lib/config"); +var helper = require('../helper'); +var redis = config.redis; + +describe("The 'select' method", function () { + + 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 () { + 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("throws an error if redis is not connected", function (done) { + client.select(1, 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 () { done(); }); + }); + + afterEach(function () { + client.end(); + }); + + it("changes the database and calls the callback", function (done) { + // 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) { + helper.isNotError()(err, res); + assert.strictEqual(client.selected_db, 1, "db should be 1 after select"); + done(); + }); + }); + + describe("and a callback is specified", function () { + describe("with a valid db index", function () { + it("selects the appropriate database", function (done) { + assert.strictEqual(client.selected_db, null, "default db should be null"); + client.select(1, function () { + assert.equal(client.selected_db, 1, "we should have selected the new valid DB"); + return done(); + }); + }); + }); + + describe("with an invalid db index", function () { + it("emits an error", function (done) { + assert.strictEqual(client.selected_db, null, "default db should be null"); + client.select(9999, function (err) { + assert.equal(err.message, 'ERR invalid DB index') + return done(); + }); + }); + }); + }); + + describe("and no callback is specified", function () { + describe("with a valid db index", function () { + it("selects the appropriate database", function (done) { + assert.strictEqual(client.selected_db, null, "default db should be null"); + client.select(1); + setTimeout(function () { + assert.equal(client.selected_db, 1, "we should have selected the new valid DB"); + return done(); + }, 100); + }); + }); + + describe("with an invalid db index", function () { + it("throws an error when callback not provided", function (done) { + var mochaListener = removeMochaListener(); + assert.strictEqual(client.selected_db, null, "default db should be null"); + + process.once('uncaughtException', function (err) { + process.on('uncaughtException', mochaListener); + assert.equal(err.message, 'ERR invalid DB index'); + return done(); + }); + + client.select(9999); + }); + }); + }); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); diff --git a/test/commands/set.spec.js b/test/commands/set.spec.js new file mode 100644 index 0000000000..b9bbe79c85 --- /dev/null +++ b/test/commands/set.spec.js @@ -0,0 +1,165 @@ +var async = require('async'); +var assert = require('assert'); +var config = require("../lib/config"); +var helper = require('../helper'); +var redis = config.redis; +var uuid = require('uuid'); + +describe("The 'set' method", function () { + + 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.set(key, value, 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 () { + done(); + }); + }); + + afterEach(function () { + client.end(); + }); + + describe("and a callback is specified", function () { + describe("with valid parameters", function () { + it("sets the value correctly", function (done) { + client.set(key, value, function (err, res) { + helper.isNotError()(err, res); + client.get(key, function (err, res) { + helper.isString(value)(err, res); + done(); + }); + }); + }); + }); + + describe("with undefined 'key' and missing 'value' parameter", function () { + it("reports an error", function (done) { + client.set(undefined, function (err, res) { + helper.isError()(err, null); + done(); + }); + }); + }); + + describe("with undefined 'key' and defined 'value' parameters", function () { + it("reports an error", function () { + client.set(undefined, value, function (err, res) { + helper.isError()(err, null); + done(); + }); + }); + }); + }); + + describe("and no callback is specified", function () { + describe("with valid parameters", function () { + it("sets the value correctly", function (done) { + client.set(key, value); + setTimeout(function () { + client.get(key, function (err, res) { + helper.isString(value)(err, res); + done(); + }); + }, 100); + }); + }); + + describe("with undefined 'key' and missing 'value' parameter", function () { + it("does not emit an error", function (done) { + this.timeout(200); + + client.once("error", function (err) { + helper.isError()(err, null); + return done(err); + }); + + client.set(); + + setTimeout(function () { + done(); + }, 100); + }); + + it("does not throw an error", function (done) { + this.timeout(200); + var mochaListener = removeMochaListener(); + + process.once('uncaughtException', function (err) { + process.on('uncaughtException', mochaListener); + return done(err); + }); + + client.set(); + + setTimeout(function () { + done(); + }, 100); + }); + }); + + describe("with undefined 'key' and defined 'value' parameters", function () { + it("throws an error", function () { + var mochaListener = removeMochaListener(); + + process.once('uncaughtException', function (err) { + process.on('uncaughtException', mochaListener); + helper.isError()(err, null); + }); + + client.set(undefined, value); + }); + }); + }); + }); + }); + } + + ['javascript', 'hiredis'].forEach(function (parser) { + allTests(parser, "/tmp/redis.sock"); + ['IPv4', 'IPv6'].forEach(function (ip) { + allTests(parser, ip); + }) + }); +}); 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/sinterstore.spec.js b/test/commands/sinterstore.spec.js new file mode 100644 index 0000000000..f27d47eea6 --- /dev/null +++ b/test/commands/sinterstore.spec.js @@ -0,0 +1,55 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'sinterstore' 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('calculates set intersection and stores it in a key', 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.sinterstore('foo', 'sa', 'sb', 'sc', helper.isNumber(1)); + + client.smembers('foo', function (err, members) { + assert.deepEqual(members, [ '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/slowlog.spec.js b/test/commands/slowlog.spec.js new file mode 100644 index 0000000000..d09442d529 --- /dev/null +++ b/test/commands/slowlog.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 'slowlog' 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('logs operations in slowlog', function (done) { + client.config("set", "slowlog-log-slower-than", 0, helper.isString("OK")); + client.slowlog("reset", helper.isString("OK")); + client.set("foo", "bar", helper.isString("OK")); + client.get("foo", helper.isString("bar")); + client.slowlog("get", function (err, res) { + assert.equal(res.length, 3); + assert.equal(res[0][3].length, 2); + assert.deepEqual(res[1][3], ["set", "foo", "bar"]); + assert.deepEqual(res[2][3], ["slowlog", "reset"]); + 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/smembers.spec.js b/test/commands/smembers.spec.js new file mode 100644 index 0000000000..0832742ec0 --- /dev/null +++ b/test/commands/smembers.spec.js @@ -0,0 +1,45 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'smembers' 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 all values in a set', function (done) { + client.sadd('foo', 'x', helper.isNumber(1)); + client.sadd('foo', 'y', helper.isNumber(1)); + client.smembers('foo', function (err, values) { + assert.equal(values.length, 2); + var members = values.sort(); + assert.deepEqual(members, [ 'x', 'y' ]); + 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/smove.spec.js b/test/commands/smove.spec.js new file mode 100644 index 0000000000..598be28860 --- /dev/null +++ b/test/commands/smove.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 'smove' 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('moves a value to a set that does not yet exist', function (done) { + client.sadd('foo', 'x', helper.isNumber(1)); + client.smove('foo', 'bar', 'x', helper.isNumber(1)); + client.sismember('foo', 'x', helper.isNumber(0)); + client.sismember('bar', 'x', helper.isNumber(1, done)); + }); + + it("does not move a value if it does not exist in the first set", function (done) { + client.sadd('foo', 'x', helper.isNumber(1)); + client.smove('foo', 'bar', 'y', helper.isNumber(0)); + client.sismember('foo', 'y', helper.isNumber(0)); + client.sismember('bar', 'y', 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/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/spop.spec.js b/test/commands/spop.spec.js new file mode 100644 index 0000000000..43b31442e2 --- /dev/null +++ b/test/commands/spop.spec.js @@ -0,0 +1,45 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'spop' 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 element from the set', function (done) { + client.sadd('zzz', 'member0', helper.isNumber(1)); + client.scard('zzz', helper.isNumber(1)); + + client.spop('zzz', function (err, value) { + if (err) return done(err); + assert.equal(value, 'member0'); + client.scard('zzz', 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/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/sunion.spec.js b/test/commands/sunion.spec.js new file mode 100644 index 0000000000..92cd81d08f --- /dev/null +++ b/test/commands/sunion.spec.js @@ -0,0 +1,53 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'sunion' 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 union of a group of sets', 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.sunion('sa', 'sb', 'sc', function (err, union) { + assert.deepEqual(union.sort(), ['a', 'b', 'c', 'd', 'e']); + 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/sunionstore.spec.js b/test/commands/sunionstore.spec.js new file mode 100644 index 0000000000..702ad131c1 --- /dev/null +++ b/test/commands/sunionstore.spec.js @@ -0,0 +1,56 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'sunionstore' 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('stores the result of a union', 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.sunionstore('foo', 'sa', 'sb', 'sc', helper.isNumber(5)); + + client.smembers('foo', function (err, members) { + assert.equal(members.length, 5); + assert.deepEqual(members.sort(), ['a', 'b', 'c', 'd', 'e']); + 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/ttl.spec.js b/test/commands/ttl.spec.js new file mode 100644 index 0000000000..cc93d231e1 --- /dev/null +++ b/test/commands/ttl.spec.js @@ -0,0 +1,45 @@ +var assert = require("assert"); +var config = require("../lib/config"); +var helper = require("../helper"); +var redis = config.redis; + +describe("The 'ttl' 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 current ttl on a key', function (done) { + client.set(["ttl key", "ttl val"], helper.isString("OK")); + client.expire(["ttl key", "100"], helper.isNumber(1)); + setTimeout(function () { + client.TTL(["ttl key"], function (err, ttl) { + assert.ok(ttl > 50 && ttl <= 100); + return done(err); + }); + }, 500); + }); + + 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/conf/redis.conf b/test/conf/redis.conf new file mode 100644 index 0000000000..dd9a4b6572 --- /dev/null +++ b/test/conf/redis.conf @@ -0,0 +1,4 @@ +port 6378 +bind ::1 127.0.0.1 +unixsocket /tmp/redis.sock +unixsocketperm 755 diff --git a/test/helper.js b/test/helper.js new file mode 100644 index 0000000000..8aaa4d68d4 --- /dev/null +++ b/test/helper.js @@ -0,0 +1,100 @@ +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 = { + 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); + if (done) return done(); + }; + }, + 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); + if (done) return done(); + }; + }, + isNull: function (done) { + return function (err, results) { + assert.strictEqual(null, err, "expected null, got error: " + err); + assert.strictEqual(null, results, results + " is not null"); + if (done) return done(); + }; + }, + isError: function (done) { + return function (err, results) { + assert.notEqual(err, null, "err is null, but an error is expected here."); + if (done) return done(); + }; + }, + isNotError: function (done) { + return function (err, results) { + assert.strictEqual(err, null, "expected success, got an error: " + err); + if (done) return done(); + }; + }, + isType: { + 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"); + if (done) return done(); + }; + }, + 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"); + 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; + 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 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 new file mode 100644 index 0000000000..0ddef51447 --- /dev/null +++ b/test/lib/config.js @@ -0,0 +1,35 @@ +// 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; + + var config = { + redis: redis, + PORT: 6378, + HOST: { + IPv4: "127.0.0.1", + IPv6: "::1" + } + }; + + config.configureClient = function (parser, ip, opts) { + var args = []; + opts = opts || {}; + + if (ip.match(/\.sock/)) { + args.push(ip) + } else { + args.push(config.PORT); + args.push(config.HOST[ip]); + opts.family = ip; + } + + opts.parser = parser; + args.push(opts); + + return args; + }; + + return config; +})(); diff --git a/test/lib/redis-process.js b/test/lib/redis-process.js new file mode 100644 index 0000000000..212cf359c0 --- /dev/null +++ b/test/lib/redis-process.js @@ -0,0 +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, conf) { + // spawn redis with our testing configuration. + 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. + 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/node_redis.spec.js b/test/node_redis.spec.js new file mode 100644 index 0000000000..41e3a47dcf --- /dev/null +++ b/test/node_redis.spec.js @@ -0,0 +1,670 @@ +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("The 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(); + }); + }); + + // TODO: we should only have a single subscription in this this + // test but unsubscribing from the single channel indicates + // that one subscriber still exists, let's dig into this. + 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" + it("reconnects, unsubscribes, and can retrieve the pre-existing data", function (done) { + client.on("reconnecting", function on_recon(params) { + client.on("ready", function on_connect() { + client.unsubscribe(helper.isNotError()); + + client.on('unsubscribe', function (channel, count) { + // we should now be out of subscriber mode. + client.set('foo', 'bar', helper.isNumber(1)); + return done(); + }); + }); + }); + + 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() + }); + } + }); + }); + + describe('monitor', function () { + it('monitors commands on all other redis clients', function (done) { + if (!helper.serverVersionAtLeast(client, [2, 6, 0])) return done(); + + var monitorClient = redis.createClient.apply(redis.createClient, args); + var responses = []; + + monitorClient.monitor(function (err, res) { + client.mget("some", "keys", "foo", "bar"); + client.set("json", JSON.stringify({ + foo: "123", + bar: "sdflkdfsjk", + another: false + })); + }); + + monitorClient.on("monitor", function (time, args) { + responses.push(args); + if (responses.length === 2) { + assert.strictEqual(5, responses[0].length); + assert.strictEqual("mget", responses[0][0]); + assert.strictEqual("some", responses[0][1]); + assert.strictEqual("keys", responses[0][2]); + assert.strictEqual("foo", responses[0][3]); + assert.strictEqual("bar", responses[0][4]); + assert.strictEqual(3, responses[1].length); + assert.strictEqual("set", responses[1][0]); + assert.strictEqual("json", responses[1][1]); + assert.strictEqual('{"foo":"123","bar":"sdflkdfsjk","another":false}', responses[1][2]); + monitorClient.quit(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..7678fd45bf --- /dev/null +++ b/test/pubsub.spec.js @@ -0,0 +1,254 @@ +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('psubscribe', function () { + // test motivated by issue #753 + it('allows all channels to be subscribed to using a * pattern', function (done) { + sub.psubscribe('*'); + sub.on("pmessage", function(pattern, channel, message) { + assert.strictEqual(pattern, '*'); + assert.strictEqual(channel, '/foo'); + assert.strictEqual(message, 'hello world'); + return done(); + }) + pub.publish('/foo', 'hello world'); + }); + }); + + 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.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'}); + }); + }); +});