Repository: luin/ioredis
Pull request #659 (2018-07-07 09:20:18)

refactor: move from JavaScript to TypeScript


Patch:
diff --git a/.gitignore b/.gitignore
index 4cd0929..caca135 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 node_modules
 *.cpuprofile
 /test.js
+built
diff --git a/index.js b/lib/index.js
similarity index 65%
rename from index.js
rename to lib/index.js
index a246cf2..9f51435 100644
--- a/index.js
+++ b/lib/index.js
@@ -1,12 +1,12 @@
 'use strict';
 
-exports = module.exports = require('./lib/redis');
+exports = module.exports = require('./redis');
 
 exports.ReplyError = require('redis-errors').ReplyError;
-exports.Cluster = require('./lib/cluster');
-exports.Command = require('./lib/command');
+exports.Cluster = require('./cluster');
+exports.Command = require('./command');
 
-var PromiseContainer = require('./lib/promise_container');
+var PromiseContainer = require('./promise_container');
 Object.defineProperty(exports, 'Promise', {
   get: function() {
     return PromiseContainer.get();
diff --git a/package-lock.json b/package-lock.json
index 96078ee..d57d2db 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -64,6 +64,12 @@
         "typical": "2.6.1"
       }
     },
+    "arrify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+      "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+      "dev": true
+    },
     "assertion-error": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz",
@@ -110,6 +116,12 @@
       "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
       "dev": true
     },
+    "buffer-from": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz",
+      "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==",
+      "dev": true
+    },
     "cache-point": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/cache-point/-/cache-point-0.4.1.tgz",
@@ -878,6 +890,12 @@
       "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
       "dev": true
     },
+    "make-error": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz",
+      "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==",
+      "dev": true
+    },
     "marked": {
       "version": "0.3.19",
       "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz",
@@ -1236,6 +1254,24 @@
         "amdefine": "1.0.1"
       }
     },
+    "source-map-support": {
+      "version": "0.5.6",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz",
+      "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "1.1.0",
+        "source-map": "0.6.1"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
     "sprintf-js": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -1331,6 +1367,30 @@
         "typical": "2.6.1"
       }
     },
+    "ts-node": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.0.tgz",
+      "integrity": "sha512-klJsfswHP0FuOLsvBZ/zzCfUvakOSSxds78mVeK7I+qP76YWtxf16hEZsp3U+b0kIo82R5UatGFeblYMqabb2Q==",
+      "dev": true,
+      "requires": {
+        "arrify": "1.0.1",
+        "buffer-from": "1.1.0",
+        "diff": "3.5.0",
+        "make-error": "1.3.4",
+        "minimist": "1.2.0",
+        "mkdirp": "0.5.1",
+        "source-map-support": "0.5.6",
+        "yn": "2.0.0"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
     "type-check": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
@@ -1346,6 +1406,12 @@
       "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=",
       "dev": true
     },
+    "typescript": {
+      "version": "2.9.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
+      "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
+      "dev": true
+    },
     "typical": {
       "version": "2.6.1",
       "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
@@ -1488,6 +1554,12 @@
         "decamelize": "1.2.0",
         "window-size": "0.1.0"
       }
+    },
+    "yn": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
+      "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=",
+      "dev": true
     }
   }
 }
diff --git a/package.json b/package.json
index 7b4c5b5..b8f3bb0 100644
--- a/package.json
+++ b/package.json
@@ -2,15 +2,17 @@
   "name": "ioredis",
   "version": "4.0.0-2",
   "description": "A delightful, performance-focused Redis client for Node and io.js",
-  "main": "index.js",
+  "main": "built/index.js",
   "files": [
     "index.js",
-    "lib/"
+    "built/"
   ],
   "scripts": {
-    "test": "NODE_ENV=test mocha --timeout 8000 --exit",
-    "test:cov": "NODE_ENV=test node ./node_modules/istanbul/lib/cli.js cover --preserve-comments ./node_modules/mocha/bin/_mocha -- -R spec --exit",
+    "test": "NODE_ENV=test mocha --timeout 8000 -r ts-node/register --exit",
+    "test:cov": "NODE_ENV=test node ./node_modules/istanbul/lib/cli.js cover --preserve-comments ./node_modules/mocha/bin/_mocha -- -r ts-node/register -R spec --exit",
+    "build": "tsc",
     "generate-docs": "jsdoc2md lib/redis.js lib/cluster/index.js lib/commander.js > API.md",
+    "prepublish": "npm run build && npm test",
     "bench": "matcha benchmarks/*.js"
   },
   "repository": {
@@ -57,7 +59,9 @@
     "jsdoc-to-markdown": "^4.0.1",
     "mocha": "^5.2.0",
     "server-destroy": "^1.0.1",
-    "sinon": "^1.17.3"
+    "sinon": "^1.17.3",
+    "ts-node": "^7.0.0",
+    "typescript": "^2.9.2"
   },
   "engines": {
     "node": ">=4"
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..f73ef4b
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,11 @@
+{
+  "compilerOptions": {
+    "outDir": "./built",
+    "allowJs": true,
+    "target": "es6"
+  },
+  "include": [
+    "./lib/**/*"
+    ]
+}
+


Test patch:
diff --git a/test/functional/auth.js b/test/functional/auth.js
index 6d384e5..c7a38b4 100644
--- a/test/functional/auth.js
+++ b/test/functional/auth.js
@@ -3,13 +3,12 @@
 describe('auth', function () {
   it('should send auth before other commands', function (done) {
     var authed = false;
-    var server = new MockServer(17379, function (argv) {
+    new MockServer(17379, function (argv) {
       if (argv[0] === 'auth' && argv[1] === 'pass') {
         authed = true;
       } else if (argv[0] === 'get' && argv[1] === 'foo') {
         expect(authed).to.eql(true);
         redis.disconnect();
-        server.disconnect();
         done();
       }
     });
@@ -20,7 +19,7 @@ describe('auth', function () {
   it('should resend auth after reconnect', function (done) {
     var begin = false;
     var authed = false;
-    var server = new MockServer(17379, function (argv) {
+    new MockServer(17379, function (argv) {
       if (!begin) {
         return;
       }
@@ -29,7 +28,6 @@ describe('auth', function () {
       } else if (argv[0] === 'get' && argv[1] === 'foo') {
         expect(authed).to.eql(true);
         redis.disconnect();
-        server.disconnect();
         done();
       }
     });
@@ -58,7 +56,6 @@ describe('auth', function () {
         setTimeout(function () {
           expect(errorEmited).to.eql(false);
           redis.disconnect();
-          server.disconnect();
           done();
         }, 0);
       }
@@ -66,7 +63,7 @@ describe('auth', function () {
   });
 
   it('should emit "error" when the password is wrong', function (done) {
-    var server = new MockServer(17379, function (argv) {
+    new MockServer(17379, function (argv) {
       if (argv[0] === 'auth' && argv[1] === 'pass') {
         return new Error('ERR invalid password');
       }
@@ -76,7 +73,6 @@ describe('auth', function () {
     function check() {
       if (!--pending) {
         redis.disconnect();
-        server.disconnect();
         done();
       }
     }
@@ -91,7 +87,7 @@ describe('auth', function () {
   });
 
   it('should emit "error" when password is not provided', function (done) {
-    var server = new MockServer(17379, function (argv) {
+    new MockServer(17379, function (argv) {
       if (argv[0] === 'info') {
         return new Error('NOAUTH Authentication required.');
       }
@@ -100,7 +96,6 @@ describe('auth', function () {
     redis.on('error', function (error) {
       expect(error).to.have.property('message', 'NOAUTH Authentication required.');
       redis.disconnect();
-      server.disconnect();
       done();
     });
   });
diff --git a/test/functional/cluster/_helpers.js b/test/functional/cluster/_helpers.js
deleted file mode 100644
index b9f6df6..0000000
--- a/test/functional/cluster/_helpers.js
+++ /dev/null
@@ -1,14 +0,0 @@
-exports.disconnect = function (clients, callback) {
-  var pending = 0;
-
-  for (var i = 0; i < clients.length; ++i) {
-    pending += 1;
-    clients[i].disconnect(check);
-  }
-
-  function check() {
-    if (!--pending && callback) {
-      callback();
-    }
-  }
-}
diff --git a/test/functional/cluster/ask.js b/test/functional/cluster/ask.js
index f5cbfc6..d494f39 100644
--- a/test/functional/cluster/ask.js
+++ b/test/functional/cluster/ask.js
@@ -1,5 +1,4 @@
 var calculateSlot = require('cluster-key-slot');
-var disconnect = require('./_helpers').disconnect;
 
 describe('cluster:ASK', function () {
   it('should support ASK', function (done) {
@@ -9,7 +8,7 @@ describe('cluster:ASK', function () {
       [0, 1, ['127.0.0.1', 30001]],
       [2, 16383, ['127.0.0.1', 30002]]
     ];
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -19,7 +18,7 @@ describe('cluster:ASK', function () {
         asked = true;
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -27,7 +26,7 @@ describe('cluster:ASK', function () {
         if (++times === 2) {
           process.nextTick(function () {
             cluster.disconnect();
-            disconnect([node1, node2], done);
+            done();
           });
         } else {
           return new Error('ASK ' + calculateSlot('foo') + ' 127.0.0.1:30001');
@@ -48,15 +47,15 @@ describe('cluster:ASK', function () {
     var slotTable = [
       [0, 16383, ['127.0.0.1', 30002]]
     ];
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'get' && argv[1] === 'foo') {
         expect(asked).to.eql(true);
-        return "bar"
+        return 'bar';
       } else if (argv[0] === 'asking') {
         asked = true;
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -71,7 +70,7 @@ describe('cluster:ASK', function () {
     cluster.get('foo', function (err, res) {
       expect(res).to.eql('bar');
       cluster.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 });
diff --git a/test/functional/cluster/clusterdown.js b/test/functional/cluster/clusterdown.js
index 76ba046..3a23401 100644
--- a/test/functional/cluster/clusterdown.js
+++ b/test/functional/cluster/clusterdown.js
@@ -1,12 +1,10 @@
-var disconnect = require('./_helpers').disconnect;
-
 describe('cluster:CLUSTERDOWN', function () {
   it('should redirect the command to a random node', function (done) {
     var slotTable = [
       [0, 1, ['127.0.0.1', 30001]],
       [2, 16383, ['127.0.0.1', 30002]]
     ];
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -14,7 +12,7 @@ describe('cluster:CLUSTERDOWN', function () {
         return 'bar';
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -32,7 +30,7 @@ describe('cluster:CLUSTERDOWN', function () {
     cluster.get('foo', function (_, res) {
       expect(res).to.eql('bar');
       cluster.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 });
diff --git a/test/functional/cluster/connect.js b/test/functional/cluster/connect.js
index f20da4e..4971461 100644
--- a/test/functional/cluster/connect.js
+++ b/test/functional/cluster/connect.js
@@ -1,5 +1,3 @@
-var disconnect = require('./_helpers').disconnect;
-
 describe('cluster:connect', function () {
   it('should flush the queue when all startup nodes are unreachable', function (done) {
     var cluster = new Redis.Cluster([
@@ -42,8 +40,8 @@ describe('cluster:connect', function () {
         return new Error('CLUSTERDOWN');
       }
     };
-    var node1 = new MockServer(30001, argvHandler);
-    var node2 = new MockServer(30002, argvHandler);
+    new MockServer(30001, argvHandler);
+    new MockServer(30002, argvHandler);
 
     var t = 0;
     var cluster = new Redis.Cluster([
@@ -54,7 +52,7 @@ describe('cluster:connect', function () {
         expect(times).to.eql(++t);
         if (times === 3) {
           cluster.disconnect();
-          disconnect([node1, node2], done);
+          done();
           return;
         }
         return 0;
@@ -71,7 +69,7 @@ describe('cluster:connect', function () {
 
     node.once('connect', function () {
       cluster.disconnect();
-      disconnect([node], done);
+      done();
     });
   });
 
@@ -90,7 +88,7 @@ describe('cluster:connect', function () {
         return 'cluster_state:ok';
       }
     };
-    var node = new MockServer(30001, argvHandler);
+    new MockServer(30001, argvHandler);
 
     var cluster = new Redis.Cluster([
       { host: '127.0.0.1', port: '30001' }
@@ -99,7 +97,7 @@ describe('cluster:connect', function () {
     cluster.connect().then(function () {
       expect(cluster.status).to.eql('ready');
       cluster.disconnect();
-      disconnect([node], done);
+      done();
     });
   });
 
@@ -112,7 +110,7 @@ describe('cluster:connect', function () {
 
     node.once('connect', function () {
       cluster.disconnect();
-      disconnect([node], done);
+      done();
     });
   });
 
@@ -123,7 +121,7 @@ describe('cluster:connect', function () {
 
     node.once('connect', function () {
       cluster.disconnect();
-      disconnect([node], done);
+      done();
     });
   });
 
@@ -138,9 +136,9 @@ describe('cluster:connect', function () {
         return slotTable;
       }
     };
-    var node1 = new MockServer(30001, argvHandler);
-    var node2 = new MockServer(30002, argvHandler);
-    var node3 = new MockServer(30003, argvHandler);
+    new MockServer(30001, argvHandler);
+    new MockServer(30002, argvHandler);
+    new MockServer(30003, argvHandler);
 
     stub(Redis.Cluster.prototype, 'connect', function () {
       return Promise.resolve();
@@ -152,7 +150,7 @@ describe('cluster:connect', function () {
 
     cluster.connect().then(function () {
       cluster.disconnect();
-      disconnect([node1, node2, node3], done);
+      done();
     });
   });
 
@@ -217,13 +215,13 @@ describe('cluster:connect', function () {
     function check() {
       if (!--pending) {
         cluster.disconnect();
-        disconnect([node1, node2, node3], done);
+        done();
       }
     }
   });
 
   it('should send command to the correct node', function (done) {
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return [
           [0, 1, ['127.0.0.1', 30001]],
@@ -231,11 +229,11 @@ describe('cluster:connect', function () {
         ];
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'get' && argv[1] === 'foo') {
         process.nextTick(function () {
           cluster.disconnect();
-          disconnect([node1, node2], done);
+          done();
         });
       }
     });
@@ -253,8 +251,8 @@ describe('cluster:connect', function () {
         return new Error(errorMessage);
       }
     };
-    var node1 = new MockServer(30001, argvHandler);
-    var node2 = new MockServer(30002, argvHandler);
+    new MockServer(30001, argvHandler);
+    new MockServer(30002, argvHandler);
 
     var pending = 2;
     var retry = 0;
@@ -281,13 +279,13 @@ describe('cluster:connect', function () {
     function checkDone() {
       if (!--pending) {
         cluster.disconnect();
-        disconnect([node1, node2], done);
+        done();
       }
     }
   });
 
   it('should using the specified password', function (done) {
-    var node1, node2, node3;
+    var cluster;
     var slotTable = [
       [0, 5460, ['127.0.0.1', 30001]],
       [5461, 10922, ['127.0.0.1', 30002]],
@@ -306,15 +304,15 @@ describe('cluster:connect', function () {
         } else if (port === 30003) {
           expect(password).to.eql('default password');
           cluster.disconnect();
-          disconnect([node1, node2, node3], done);
+          done();
         }
       }
     };
-    node1 = new MockServer(30001, argvHandler.bind(null, 30001));
-    node2 = new MockServer(30002, argvHandler.bind(null, 30002));
-    node3 = new MockServer(30003, argvHandler.bind(null, 30003));
+    new MockServer(30001, argvHandler.bind(null, 30001));
+    new MockServer(30002, argvHandler.bind(null, 30002));
+    new MockServer(30003, argvHandler.bind(null, 30003));
 
-    var cluster = new Redis.Cluster([
+    cluster = new Redis.Cluster([
       { host: '127.0.0.1', port: '30001', password: 'other password' },
       { host: '127.0.0.1', port: '30002', password: null }
     ], { redisOptions: { lazyConnect: false, password: 'default password' } });
@@ -322,6 +320,7 @@ describe('cluster:connect', function () {
 
   it('should discover other nodes automatically every slotsRefreshInterval', function (done) {
     var times = 0;
+    var cluster;
     var argvHandler = function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         times++;
@@ -343,14 +342,14 @@ describe('cluster:connect', function () {
     var node1 = new MockServer(30001, argvHandler);
     var node2 = new MockServer(30002, argvHandler);
 
-    node1.once('connect', function() {
+    node1.once('connect', function () {
       node2.once('connect', function () {
         cluster.disconnect();
-        disconnect([node1, node2], done);
+        done();
       });
     });
 
-    var cluster = new Redis.Cluster([
+    cluster = new Redis.Cluster([
       { host: '127.0.0.1', port: '30001' }
     ], { slotsRefreshInterval: 100, redisOptions: { lazyConnect: false } });
   });
diff --git a/test/functional/cluster/index.js b/test/functional/cluster/index.js
index 4bb8952..c66a361 100644
--- a/test/functional/cluster/index.js
+++ b/test/functional/cluster/index.js
@@ -1,13 +1,11 @@
 'use strict';
 
 var utils = require('../../../lib/utils');
-var calculateSlot = require('cluster-key-slot');
-var disconnect = require('./_helpers').disconnect;
 
 describe('cluster', function () {
   it('should return the error successfully', function (done) {
     var called = false;
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return [
           [0, 16383, ['127.0.0.1', 30001]]
@@ -26,12 +24,12 @@ describe('cluster', function () {
       expect(called).to.eql(true);
       expect(err.message).to.match(/Wrong arguments count/);
       cluster.disconnect();
-      disconnect([node1], done);
+      done();
     });
   });
 
   it('should get value successfully', function (done) {
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return [
           [0, 1, ['127.0.0.1', 30001]],
@@ -39,7 +37,7 @@ describe('cluster', function () {
         ];
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'get' && argv[1] === 'foo') {
         return 'bar';
       }
@@ -51,14 +49,14 @@ describe('cluster', function () {
     cluster.get('foo', function (err, result) {
       expect(result).to.eql('bar');
       cluster.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 
   describe('enableReadyCheck', function () {
     it('should reconnect when cluster state is not ok', function (done) {
       var state = 'fail';
-      var server = new MockServer(30001, function (argv) {
+      new MockServer(30001, function (argv) {
         if (argv[0] === 'cluster' && argv[1] === 'slots') {
           return [
             [0, 16383, ['127.0.0.1', 30001]]
@@ -81,14 +79,14 @@ describe('cluster', function () {
       });
       client.on('ready', function () {
         client.disconnect();
-        disconnect([server], done);
+        done();
       });
     });
   });
 
   describe('startupNodes', function () {
     it('should allow updating startupNodes', function (done) {
-      var node1 = new MockServer(30001, function (argv) {
+      new MockServer(30001, function (argv) {
         if (argv[0] === 'cluster' && argv[1] === 'slots') {
           return [
             [0, 16383, ['127.0.0.1', 30001]]
@@ -106,9 +104,9 @@ describe('cluster', function () {
           return 0;
         }
       });
-      var node2 = new MockServer(30002, function () {
+      new MockServer(30002, function () {
         client.disconnect();
-        disconnect([node1, node2], done);
+        done();
       });
     });
   });
@@ -130,10 +128,6 @@ describe('cluster', function () {
       this.node4 = new MockServer(30004, handler.bind(null, 30004));
     });
 
-    afterEach(function (done) {
-      disconnect([this.node1, this.node2, this.node3, this.node4], done);
-    });
-
     context('master', function () {
       it('should only send reads to master', function (done) {
         var cluster = new Redis.Cluster([{ host: '127.0.0.1', port: '30001' }]);
@@ -259,18 +253,18 @@ describe('cluster', function () {
         [0, 5460, ['127.0.0.1', 30001], ['127.0.0.1', 30003]],
         [5461, 10922, ['127.0.0.1', 30002]]
       ];
-      var node1 = new MockServer(30001, function (argv) {
+      var node = new MockServer(30001, function (argv) {
         if (argv[0] === 'cluster' && argv[1] === 'slots') {
           return slotTable;
         }
       });
-      var node2 = new MockServer(30002, function (argv) {
+      new MockServer(30002, function (argv) {
         if (argv[0] === 'cluster' && argv[1] === 'slots') {
           return slotTable;
         }
       });
 
-      var node3 = new MockServer(30003, function (argv) {
+      new MockServer(30003, function (argv) {
         if (argv[0] === 'cluster' && argv[1] === 'slots') {
           return slotTable;
         }
@@ -289,9 +283,9 @@ describe('cluster', function () {
           expect(cluster.nodes('master')).to.have.lengthOf(1);
           expect(cluster.nodes('slave')).to.have.lengthOf(1);
           cluster.disconnect();
-          disconnect([node2, node3], done);
+          done();
         });
-        disconnect([node1]);
+        node.disconnect();
       });
 
     });
@@ -303,18 +297,18 @@ describe('cluster', function () {
         [0, 5460, ['127.0.0.1', 30001], ['127.0.0.1', 30003]],
         [5461, 10922, ['127.0.0.1', 30002]]
       ];
-      var node1 = new MockServer(30001, function (argv) {
+      new MockServer(30001, function (argv) {
         if (argv[0] === 'cluster' && argv[1] === 'slots') {
           return slotTable;
         }
       });
-      var node2 = new MockServer(30002, function (argv) {
+      new MockServer(30002, function (argv) {
         if (argv[0] === 'cluster' && argv[1] === 'slots') {
           return slotTable;
         }
       });
 
-      var node3 = new MockServer(30003, function (argv) {
+      new MockServer(30003, function (argv) {
         if (argv[0] === 'cluster' && argv[1] === 'slots') {
           return slotTable;
         }
@@ -341,7 +335,7 @@ describe('cluster', function () {
               expect(node.options).to.have.property('readOnly', false);
             });
             cluster.disconnect();
-            disconnect([node1, node2, node3], done);
+            done();
           });
         });
       });
@@ -359,9 +353,9 @@ describe('cluster', function () {
           return slotTable;
         }
       };
-      var node1 = new MockServer(30001, argvHandler);
-      var node2 = new MockServer(30002, argvHandler);
-      var node3 = new MockServer(30003, argvHandler);
+      new MockServer(30001, argvHandler);
+      new MockServer(30002, argvHandler);
+      new MockServer(30003, argvHandler);
 
       var cluster = new Redis.Cluster([
         { host: '127.0.0.1', port: '30001' }
@@ -376,7 +370,7 @@ describe('cluster', function () {
           expect(setCommandHandled).to.eql(true);
           expect(state).to.eql('OK');
           cluster.disconnect();
-          disconnect([node1, node2, node3], done);
+          done();
         });
       });
     });
diff --git a/test/functional/cluster/maxRedirections.js b/test/functional/cluster/maxRedirections.js
index 79bc744..afcf44b 100644
--- a/test/functional/cluster/maxRedirections.js
+++ b/test/functional/cluster/maxRedirections.js
@@ -1,5 +1,4 @@
 var calculateSlot = require('cluster-key-slot');
-var disconnect = require('./_helpers').disconnect;
 
 describe('cluster:maxRedirections', function () {
   it('should return error when reached max redirection', function (done) {
@@ -15,8 +14,8 @@ describe('cluster:maxRedirections', function () {
         return new Error('ASK ' + calculateSlot('foo') + ' 127.0.0.1:30001');
       }
     };
-    var node1 = new MockServer(30001, argvHandler);
-    var node2 = new MockServer(30002, argvHandler);
+    new MockServer(30001, argvHandler);
+    new MockServer(30002, argvHandler);
 
     var cluster = new Redis.Cluster([
       { host: '127.0.0.1', port: '30001' }
@@ -25,7 +24,7 @@ describe('cluster:maxRedirections', function () {
       expect(redirectTimes).to.eql(6);
       expect(err.message).to.match(/Too many Cluster redirections/);
       cluster.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 });
diff --git a/test/functional/cluster/moved.js b/test/functional/cluster/moved.js
index 5d831fb..3046e41 100644
--- a/test/functional/cluster/moved.js
+++ b/test/functional/cluster/moved.js
@@ -1,15 +1,15 @@
 var calculateSlot = require('cluster-key-slot');
-var disconnect = require('./_helpers').disconnect;
 
 describe('cluster:MOVED', function () {
   it('should auto redirect the command to the correct nodes', function (done) {
+    var cluster;
     var moved = false;
     var times = 0;
     var slotTable = [
       [0, 1, ['127.0.0.1', 30001]],
       [2, 16383, ['127.0.0.1', 30002]]
     ];
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -18,12 +18,12 @@ describe('cluster:MOVED', function () {
           expect(moved).to.eql(true);
           process.nextTick(function () {
             cluster.disconnect();
-            disconnect([node1, node2], done);
+            done();
           });
         }
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -34,7 +34,7 @@ describe('cluster:MOVED', function () {
       }
     });
 
-    var cluster = new Redis.Cluster([
+    cluster = new Redis.Cluster([
       { host: '127.0.0.1', port: '30001' }
     ]);
     cluster.get('foo', function () {
@@ -43,7 +43,7 @@ describe('cluster:MOVED', function () {
   });
 
   it('should be able to redirect a command to a unknown node', function (done) {
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return [
           [0, 16383, ['127.0.0.1', 30001]]
@@ -53,7 +53,7 @@ describe('cluster:MOVED', function () {
         return new Error('MOVED ' + calculateSlot('foo') + ' 127.0.0.1:30002');
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return [
           [0, 16381, ['127.0.0.1', 30001]],
@@ -70,18 +70,19 @@ describe('cluster:MOVED', function () {
     cluster.get('foo', function (err, res) {
       expect(res).to.eql('bar');
       cluster.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 
   it('should auto redirect the command within a pipeline', function (done) {
+    var cluster;
     var moved = false;
     var times = 0;
     var slotTable = [
       [0, 1, ['127.0.0.1', 30001]],
       [2, 16383, ['127.0.0.1', 30002]]
     ];
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -90,12 +91,12 @@ describe('cluster:MOVED', function () {
           expect(moved).to.eql(true);
           process.nextTick(function () {
             cluster.disconnect();
-            disconnect([node1, node2], done);
+            done();
           });
         }
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -106,7 +107,7 @@ describe('cluster:MOVED', function () {
       }
     });
 
-    var cluster = new Redis.Cluster([
+    cluster = new Redis.Cluster([
       { host: '127.0.0.1', port: '30001' }
     ], { lazyConnect: false });
     cluster.get('foo', function () {
diff --git a/test/functional/cluster/pipeline.js b/test/functional/cluster/pipeline.js
index c487ede..3968940 100644
--- a/test/functional/cluster/pipeline.js
+++ b/test/functional/cluster/pipeline.js
@@ -1,5 +1,4 @@
 var calculateSlot = require('cluster-key-slot');
-var disconnect = require('./_helpers').disconnect;
 
 describe('cluster:pipeline', function () {
   it('should throw when not all keys belong to the same slot', function (done) {
@@ -8,12 +7,12 @@ describe('cluster:pipeline', function () {
       [12182, 12183, ['127.0.0.1', 30002]],
       [12184, 16383, ['127.0.0.1', 30001]]
     ];
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -25,7 +24,7 @@ describe('cluster:pipeline', function () {
     cluster.pipeline().set('foo', 'bar').get('foo2').exec().catch(function (err) {
       expect(err.message).to.match(/All keys in the pipeline should belong to the same slot/);
       cluster.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 
@@ -36,7 +35,7 @@ describe('cluster:pipeline', function () {
       [12182, 12183, ['127.0.0.1', 30002]],
       [12184, 16383, ['127.0.0.1', 30001]]
     ];
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -44,7 +43,7 @@ describe('cluster:pipeline', function () {
         return 'bar';
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -65,7 +64,7 @@ describe('cluster:pipeline', function () {
       expect(result[0]).to.eql([null, 'bar']);
       expect(result[1]).to.eql([null, 'OK']);
       cluster.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 
@@ -76,7 +75,7 @@ describe('cluster:pipeline', function () {
       [12182, 12183, ['127.0.0.1', 30002]],
       [12184, 16383, ['127.0.0.1', 30001]]
     ];
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -91,7 +90,7 @@ describe('cluster:pipeline', function () {
         asked = false;
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -108,7 +107,7 @@ describe('cluster:pipeline', function () {
       expect(result[0]).to.eql([null, 'bar']);
       expect(result[1]).to.eql([null, 'OK']);
       cluster.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 
@@ -117,7 +116,7 @@ describe('cluster:pipeline', function () {
     var slotTable = [
       [0, 16383, ['127.0.0.1', 30001]]
     ];
-    var server = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -135,7 +134,7 @@ describe('cluster:pipeline', function () {
       expect(result[0][1]).to.eql('OK');
       expect(result[1][1]).to.eql('OK');
       cluster.disconnect();
-      disconnect([server], done);
+      done();
     });
   });
 
@@ -145,7 +144,7 @@ describe('cluster:pipeline', function () {
       [12182, 12183, ['127.0.0.1', 30002]],
       [12184, 16383, ['127.0.0.1', 30001]]
     ];
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -153,7 +152,7 @@ describe('cluster:pipeline', function () {
         return 'bar';
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -170,7 +169,7 @@ describe('cluster:pipeline', function () {
       expect(result[0][0].message).to.match(/MOVED/);
       expect(result[1]).to.eql([null, 'OK']);
       cluster.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 
@@ -180,7 +179,7 @@ describe('cluster:pipeline', function () {
       [12182, 12183, ['127.0.0.1', 30002]],
       [12184, 16383, ['127.0.0.1', 30001]]
     ];
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -208,7 +207,7 @@ describe('cluster:pipeline', function () {
       expect(result[0]).to.eql([null, 'bar']);
       expect(result[1]).to.eql([null, 'OK']);
       cluster.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 });
diff --git a/test/functional/cluster/pub_sub.js b/test/functional/cluster/pub_sub.js
index 7b69ba3..9514328 100644
--- a/test/functional/cluster/pub_sub.js
+++ b/test/functional/cluster/pub_sub.js
@@ -1,5 +1,3 @@
-var disconnect = require('./_helpers').disconnect;
-
 describe('cluster:pub/sub', function () {
   it('should receive messages', function (done) {
     var handler = function (argv) {
@@ -11,7 +9,7 @@ describe('cluster:pub/sub', function () {
       }
     };
     var node1 = new MockServer(30001, handler);
-    var node2 = new MockServer(30002, handler);
+    new MockServer(30002, handler);
 
     var options = [{ host: '127.0.0.1', port: '30001' }];
     var sub = new Redis.Cluster(options);
@@ -23,12 +21,12 @@ describe('cluster:pub/sub', function () {
       expect(channel).to.eql('test channel');
       expect(message).to.eql('hi');
       sub.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 
   it('should re-subscribe after reconnection', function (done) {
-    var server = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return [
           [0, 16383, ['127.0.0.1', 30001]]
@@ -44,7 +42,7 @@ describe('cluster:pub/sub', function () {
         expect(channels).to.eql(['test cluster']);
         Redis.prototype.subscribe.restore();
         client.disconnect();
-        disconnect([server], done);
+        done();
         return Redis.prototype.subscribe.apply(this, arguments);
       });
       client.once('end', function () {
@@ -55,7 +53,7 @@ describe('cluster:pub/sub', function () {
   });
 
   it('should re-psubscribe after reconnection', function (done) {
-    var server = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return [
           [0, 16383, ['127.0.0.1', 30001]]
@@ -71,7 +69,7 @@ describe('cluster:pub/sub', function () {
         expect(channels).to.eql(['test?']);
         Redis.prototype.psubscribe.restore();
         client.disconnect();
-        disconnect([server], done);
+        done();
         return Redis.prototype.psubscribe.apply(this, arguments);
       });
       client.once('end', function () {
diff --git a/test/functional/cluster/transaction.js b/test/functional/cluster/transaction.js
index bc4f89e..5864be0 100644
--- a/test/functional/cluster/transaction.js
+++ b/test/functional/cluster/transaction.js
@@ -1,5 +1,4 @@
 var calculateSlot = require('cluster-key-slot');
-var disconnect = require('./_helpers').disconnect;
 
 describe('cluster:transaction', function () {
   it('should auto redirect commands on MOVED', function (done) {
@@ -9,7 +8,7 @@ describe('cluster:transaction', function () {
       [12182, 12183, ['127.0.0.1', 30002]],
       [12184, 16383, ['127.0.0.1', 30001]]
     ];
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -21,7 +20,7 @@ describe('cluster:transaction', function () {
         return ['bar', 'OK'];
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -42,7 +41,7 @@ describe('cluster:transaction', function () {
       expect(result[0]).to.eql([null, 'bar']);
       expect(result[1]).to.eql([null, 'OK']);
       cluster.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 
@@ -53,7 +52,7 @@ describe('cluster:transaction', function () {
       [12182, 12183, ['127.0.0.1', 30002]],
       [12184, 16383, ['127.0.0.1', 30001]]
     ];
-    var node1 = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -75,7 +74,7 @@ describe('cluster:transaction', function () {
         asked = false;
       }
     });
-    var node2 = new MockServer(30002, function (argv) {
+    new MockServer(30002, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -95,7 +94,7 @@ describe('cluster:transaction', function () {
       expect(result[0]).to.eql([null, 'bar']);
       expect(result[1]).to.eql([null, 'OK']);
       cluster.disconnect();
-      disconnect([node1, node2], done);
+      done();
     });
   });
 });
diff --git a/test/functional/cluster/tryagain.js b/test/functional/cluster/tryagain.js
index 47945b9..f55169b 100644
--- a/test/functional/cluster/tryagain.js
+++ b/test/functional/cluster/tryagain.js
@@ -1,12 +1,11 @@
-var disconnect = require('./_helpers').disconnect;
-
 describe('cluster:TRYAGAIN', function () {
   it('should retry the command', function (done) {
+    var cluster;
     var times = 0;
     var slotTable = [
       [0, 16383, ['127.0.0.1', 30001]]
     ];
-    var server = new MockServer(30001, function (argv) {
+    new MockServer(30001, function (argv) {
       if (argv[0] === 'cluster' && argv[1] === 'slots') {
         return slotTable;
       }
@@ -14,7 +13,7 @@ describe('cluster:TRYAGAIN', function () {
         if (times++ === 1) {
           process.nextTick(function () {
             cluster.disconnect();
-            disconnect([server], done);
+            done();
           });
         } else {
           return new Error('TRYAGAIN Multiple keys request during rehashing of slot');
@@ -22,7 +21,7 @@ describe('cluster:TRYAGAIN', function () {
       }
     });
 
-    var cluster = new Redis.Cluster([
+    cluster = new Redis.Cluster([
       { host: '127.0.0.1', port: '30001' }
     ], { retryDelayOnTryAgain: 1 });
     cluster.get('foo');
diff --git a/test/helpers/global.js b/test/helpers/global.js
index 0725b24..3dffb6a 100644
--- a/test/helpers/global.js
+++ b/test/helpers/global.js
@@ -7,7 +7,7 @@ global.spy = sinon.spy.bind(sinon);
 global.stub = sinon.stub.bind(sinon);
 global.mock = sinon.mock.bind(sinon);
 
-global.Redis = require('../..');
+global.Redis = require('../../lib');
 global.MockServer = require('./mock_server');
 
 afterEach(function (done) {
diff --git a/test/helpers/mock_server.js b/test/helpers/mock_server.js
index 730783d..9913018 100644
--- a/test/helpers/mock_server.js
+++ b/test/helpers/mock_server.js
@@ -7,6 +7,28 @@ var EventEmitter = require('events').EventEmitter;
 var enableDestroy = require('server-destroy');
 var Parser = require('redis-parser');
 
+var createdMockServers = [];
+
+afterEach(function (done) {
+  if (createdMockServers.length === 0) {
+    done();
+    return;
+  }
+  var pending = 0;
+
+  for (var i = 0; i < createdMockServers.length; ++i) {
+    pending += 1;
+    createdMockServers[i].disconnect(check);
+  }
+
+  function check() {
+    if (!--pending) {
+      createdMockServers = [];
+      done();
+    }
+  }
+});
+
 function MockServer(port, handler) {
   EventEmitter.call(this);
 
@@ -15,6 +37,8 @@ function MockServer(port, handler) {
 
   this.clients = [];
 
+  createdMockServers.push(this);
+
   this.connect();
 }
 
diff --git a/test/unit/index.js b/test/unit/index.js
index f710adb..bd344fd 100644
--- a/test/unit/index.js
+++ b/test/unit/index.js
@@ -1,6 +1,6 @@
 'use strict';
 
-var ioredis = require('../..');
+var ioredis = require('../../lib');
 describe('index', function () {
   describe('print()', function () {
     it('prints logs', function () {