THRIFT-2969
Client: nodejs
Patch: Andrew de Andrade

General node test cleanup and consolidation.
diff --git a/lib/nodejs/test/test_driver.js b/lib/nodejs/test/test_driver.js
new file mode 100644
index 0000000..9f2b894
--- /dev/null
+++ b/lib/nodejs/test/test_driver.js
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * 'License'); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+ // This is the Node.js test driver for the standard Apache Thrift
+ // test service. The driver invokes every function defined in the
+ // Thrift Test service with a representative range of parameters.
+ //
+ // The ThriftTestDriver function requires a client object
+ // connected to a server hosting the Thrift Test service and
+ // supports an optional callback function which is called with
+ // a status message when the test is complete.
+
+var assert = require('assert');
+var ttypes = require('./gen-nodejs/ThriftTest_types');
+var Int64 = require('node-int64');
+var testCases = require('./test-cases');
+
+exports.ThriftTestDriver = function(client, callback) {
+
+  function makeAsserter(assertionFn) {
+    return function(c) {
+      var fnName = c[0];
+      var expected = c[1];
+      client[fnName](expected, function(err, actual) {
+        assert(!err);
+        assertionFn(actual, expected);
+      })
+    };
+  }
+
+  testCases.simple.forEach(makeAsserter(assert.equal));
+  testCases.deep.forEach(makeAsserter(assert.deepEqual));
+
+  client.testStruct(testCases.out, function(err, response) {
+    assert(!err);
+    checkRecursively(testCases.out, response);
+  });
+
+  client.testNest(testCases.out2, function(err, response) {
+    assert(!err);
+    checkRecursively(testCases.out2, response);
+  });
+
+  client.testInsanity(testCases.crazy, function(err, response) {
+    assert(!err);
+    checkRecursively(testCases.insanity, response);
+  });
+
+  client.testException('TException', function(err, response) {
+    assert(!err);
+    assert(!response);
+  });
+
+  client.testException('Xception', function(err, response) {
+    assert(!response);
+    assert.equal(err.errorCode, 1001);
+    assert.equal('Xception', err.message);
+  });
+
+  client.testException('no Exception', function(err, response) {
+    assert(!err);
+    assert.equal(undefined, response); //void
+  });
+
+  client.testOneway(0, function(err, response) {
+    assert(false); //should not answer
+  });
+
+  checkOffByOne(function(done) {
+    client.testI32(-1, function(err, response) {
+      assert(!err);
+      assert.equal(-1, response);
+      done();
+    });
+  }, callback);
+
+};
+
+exports.ThriftTestDriverPromise = function(client, callback) {
+
+  function makeAsserter(assertionFn) {
+    return function(c) {
+      var fnName = c[0];
+      var expected = c[1];
+      client[fnName](expected)
+        .then(function(actual) {
+          assert.equal(actual, expected);
+        })
+        .fail(failTest);
+    };
+  }
+
+  testCases.simple.forEach(makeAsserter(assert.equal));
+  testCases.deep.forEach(makeAsserter(assert.deepEqual));
+
+  client.testStruct(testCases.out)
+    .then(function(response) {
+      checkRecursivelyP(testCases.out, response);
+    })
+    .fail(failTest);
+
+  client.testNest(testCases.out2)
+    .then(function(response) {
+      checkRecursivelyP(testCases.out2, response);
+    })
+    .fail(failTest);
+
+  client.testInsanity(testCases.crazy)
+    .then(function(response) {
+      checkRecursivelyP(testCases.insanity, response);
+    })
+    .fail(failTest);
+
+  client.testException('TException')
+    .then(failTest);
+
+  client.testException('Xception')
+    .then(function(response) {
+      assert.equal(err.errorCode, 1001);
+      assert.equal('Xception', err.message);
+    })
+    .fail(failTest);
+
+  client.testException('no Exception')
+    .then(function(response) {
+      assert.equal(undefined, response); //void
+    })
+    .fail(failTest);
+
+  client.testOneway(0, failTest); //should not answer
+
+  checkOffByOne(function(done) {
+    client.testI32(-1)
+      .then(function(response) {
+          assert.equal(-1, response);
+          done();
+      })
+      .fail(function() {
+        assert(false);
+      });
+  }, callback);
+
+};
+
+
+// Helper Functions
+// =========================================================
+
+function failTest() {
+  assert(false);
+}
+
+// This is the version of checkRecursively that was in the vanilla callback
+// version of test_driver.
+function checkRecursively(map1, map2) {
+  if (typeof map1 !== 'function' && typeof map2 !== 'function') {
+    if (!map1 || typeof map1 !== 'object') {
+      //Handle int64 types (which use node-int64 in Node.js JavaScript)
+      if ((typeof map1 === "number") && (typeof map2 === "object") &&
+          (map2.buffer) && (map2.buffer instanceof Buffer) && (map2.buffer.length === 8)) {
+        var n = new Int64(map2.buffer);
+        assert.equal(map1, n.toNumber());
+      } else {
+        assert.equal(map1, map2);
+      }
+    } else {
+      for (var key in map1) {
+        checkRecursively(map1[key], map2[key]);
+      }
+    }
+  }
+}
+
+// This is the version of checkRecursively that was in the promise version of
+// test_driver.
+// deepEqual doesn't work with fields using node-int64
+function checkRecursivelyP(map1, map2) {
+  if (typeof map1 !== 'function' && typeof map2 !== 'function') {
+    if (!map1 || typeof map1 !== 'object') {
+        assert.equal(map1, map2);
+    } else {
+      for (var key in map1) {
+        checkRecursivelyP(map1[key], map2[key]);
+      }
+    }
+  }
+}
+
+function checkOffByOne(done, callback) {
+
+  var retry_limit = 30;
+  var retry_interval = 100;
+  var test_complete = false;
+  var retrys = 0;
+
+  /**
+   * redo a simple test after the oneway to make sure we aren't "off by one" --
+   * if the server treated oneway void like normal void, this next test will
+   * fail since it will get the void confirmation rather than the correct
+   * result. In this circumstance, the client will throw the exception:
+   *
+   * Because this is the last test against the server, when it completes
+   * the entire suite is complete by definition (the tests run serially).
+   */
+  done(function() {
+    test_complete = true;
+  });
+
+  //We wait up to retry_limit * retry_interval for the test suite to complete
+  function TestForCompletion() {
+    if(test_complete && callback) {
+      callback("Server successfully tested!");
+    } else {
+      if (++retrys < retry_limit) {
+        setTimeout(TestForCompletion, retry_interval);
+      } else if (callback) {
+        callback("Server test failed to complete after " +
+                 (retry_limit * retry_interval / 1000) + " seconds");
+      }
+    }
+  }
+
+  setTimeout(TestForCompletion, retry_interval);
+}