THRIFT-2976: add browserify support and tests
Client: nodejs
Patch: Andrew de Andrade
diff --git a/lib/nodejs/lib/thrift/browser.js b/lib/nodejs/lib/thrift/browser.js
new file mode 100644
index 0000000..4593a8f
--- /dev/null
+++ b/lib/nodejs/lib/thrift/browser.js
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+exports.Thrift = require('./thrift');
+
+var xhrConnection = require('./xhr_connection');
+exports.XHRConnection = xhrConnection.XHRConnection;
+exports.createXHRConnection = xhrConnection.createXHRConnection;
+exports.createXHRClient = xhrConnection.createXHRClient;
+
+exports.Multiplexer = require('./multiplexed_protocol').Multiplexer;
+
+exports.TWebSocketTransport = require('./ws_transport');
+exports.TBufferedTransport = require('./buffered_transport');
+exports.TFramedTransport = require('./framed_transport');
+
+exports.Protocol = exports.TJSONProtocol = require('./json_protocol');
+exports.TBinaryProtocol = require('./binary_protocol');
+exports.TCompactProtocol = require('./compact_protocol');
diff --git a/lib/nodejs/lib/thrift/index.js b/lib/nodejs/lib/thrift/index.js
index e313dbb..61e5cbd 100644
--- a/lib/nodejs/lib/thrift/index.js
+++ b/lib/nodejs/lib/thrift/index.js
@@ -36,6 +36,11 @@
 exports.createWSConnection = wsConnection.createWSConnection;
 exports.createWSClient = wsConnection.createWSClient;
 
+var xhrConnection = require('./xhr_connection');
+exports.XHRConnection = xhrConnection.XHRConnection;
+exports.createXHRConnection = xhrConnection.createXHRConnection;
+exports.createXHRClient = xhrConnection.createXHRClient;
+
 var server = require('./server');
 exports.createServer = server.createServer;
 exports.createMultiplexServer = server.createMultiplexServer;
diff --git a/lib/nodejs/lib/thrift/json_protocol.js b/lib/nodejs/lib/thrift/json_protocol.js
index f4678f5..77339f7 100644
--- a/lib/nodejs/lib/thrift/json_protocol.js
+++ b/lib/nodejs/lib/thrift/json_protocol.js
@@ -39,6 +39,8 @@
  *     var protocol  = new Thrift.Protocol(transport);
  */
 function TJSONProtocol(trans) {
+  this.tstack = [];
+  this.tpos = [];
   this.trans = trans;
 };
 
@@ -607,15 +609,7 @@
  *  False unless the next number in the protocol buffer
  *  is 1, in which case the value property is True */
 TJSONProtocol.prototype.readBool = function() {
-  var r = this.readI32();
-
-  if (r !== null && r.value == '1') {
-    r.value = true;
-  } else {
-    r.value = false;
-  }
-
-  return r;
+  return this.readI32() == '1';
 };
 
 /** Returns the an object with a value property set to the
@@ -685,8 +679,7 @@
 /** Returns the an object with a value property set to the
     next value found in the protocol buffer */
 TJSONProtocol.prototype.readString = function() {
-  var r = this.readI32();
-  return r;
+  return this.readI32();
 };
 
 /**
diff --git a/lib/nodejs/lib/thrift/multiplexed_protocol.js b/lib/nodejs/lib/thrift/multiplexed_protocol.js
index 0745a1b..9caf6ab 100644
--- a/lib/nodejs/lib/thrift/multiplexed_protocol.js
+++ b/lib/nodejs/lib/thrift/multiplexed_protocol.js
@@ -19,7 +19,6 @@
 var util = require('util');
 var Thrift = require('./thrift');
 
-exports.Wrapper = Wrapper;
 exports.Multiplexer = Multiplexer;
 
 function Wrapper(serviceName, protocol, connection) {
diff --git a/lib/nodejs/lib/thrift/web_server.js b/lib/nodejs/lib/thrift/web_server.js
index 68eb94d..37159ea 100644
--- a/lib/nodejs/lib/thrift/web_server.js
+++ b/lib/nodejs/lib/thrift/web_server.js
@@ -516,6 +516,8 @@
                    "Upgrade: websocket\r\n" +
                    "Connection: Upgrade\r\n" +
                    "Sec-WebSocket-Accept: " + hash.digest("base64") + "\r\n" +
+                   "Sec-WebSocket-Origin: " + request.headers.origin + "\r\n" +
+                   "Sec-WebSocket-Location: ws://" + request.headers.host + request.url + "\r\n" +
                    "\r\n");
     //Handle WebSocket traffic
     var data = null;
diff --git a/lib/nodejs/lib/thrift/ws_connection.js b/lib/nodejs/lib/thrift/ws_connection.js
index 0812934..052cbd4 100644
--- a/lib/nodejs/lib/thrift/ws_connection.js
+++ b/lib/nodejs/lib/thrift/ws_connection.js
@@ -29,6 +29,8 @@
 
 var createClient = require('./create_client');
 
+exports.WSConnection = WSConnection;
+
 /**
  * @class
  * @name WSConnectOptions
@@ -73,7 +75,7 @@
  *     semantics implemented using Websockets.
  * @see {@link createWSConnection}
  */
-var WSConnection = exports.WSConnection = function(host, port, options) {
+function WSConnection(host, port, options) {
   //Initialize the emitter base object
   EventEmitter.call(this);
 
diff --git a/lib/nodejs/lib/thrift/ws_transport.js b/lib/nodejs/lib/thrift/ws_transport.js
new file mode 100644
index 0000000..8e750e2
--- /dev/null
+++ b/lib/nodejs/lib/thrift/ws_transport.js
@@ -0,0 +1,204 @@
+/*
+ * 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.
+ */
+
+module.exports = TWebSocketTransport;
+
+/**
+ * Constructor Function for the WebSocket transport.
+ * @constructor
+ * @param {string} [url] - The URL to connect to.
+ * @classdesc The Apache Thrift Transport layer performs byte level I/O
+ * between RPC clients and servers. The JavaScript TWebSocketTransport object
+ * uses the WebSocket protocol. Target servers must implement WebSocket.
+ * (see: node.js example server_http.js).
+ * @example
+ *   var transport = new Thrift.TWebSocketTransport("http://localhost:8585");
+ */
+function TWebSocketTransport(url) {
+    this.__reset(url);
+};
+
+
+TWebSocketTransport.prototype.__reset = function(url) {
+  this.url = url;             //Where to connect
+  this.socket = null;         //The web socket
+  this.callbacks = [];        //Pending callbacks
+  this.send_pending = [];     //Buffers/Callback pairs waiting to be sent
+  this.send_buf = '';         //Outbound data, immutable until sent
+  this.recv_buf = '';         //Inbound data
+  this.rb_wpos = 0;           //Network write position in receive buffer
+  this.rb_rpos = 0;           //Client read position in receive buffer
+};
+
+/**
+ * Sends the current WS request and registers callback. The async
+ * parameter is ignored (WS flush is always async) and the callback
+ * function parameter is required.
+ * @param {object} async - Ignored.
+ * @param {object} callback - The client completion callback.
+ * @returns {undefined|string} Nothing (undefined)
+ */
+TWebSocketTransport.prototype.flush = function(async, callback) {
+  var self = this;
+  if (this.isOpen()) {
+    //Send data and register a callback to invoke the client callback
+    this.socket.send(this.send_buf);
+    this.callbacks.push((function() {
+      var clientCallback = callback;
+      return function(msg) {
+        self.setRecvBuffer(msg);
+        clientCallback();
+      };
+    }()));
+  } else {
+    //Queue the send to go out __onOpen
+    this.send_pending.push({
+      buf: this.send_buf,
+      cb:  callback
+    });
+  }
+};
+
+TWebSocketTransport.prototype.__onOpen = function() {
+   var self = this;
+   if (this.send_pending.length > 0) {
+      //If the user made calls before the connection was fully
+      //open, send them now
+      this.send_pending.forEach(function(elem) {
+         this.socket.send(elem.buf);
+         this.callbacks.push((function() {
+           var clientCallback = elem.cb;
+           return function(msg) {
+              self.setRecvBuffer(msg);
+              clientCallback();
+           };
+         }()));
+      });
+      this.send_pending = [];
+   }
+};
+
+TWebSocketTransport.prototype.__onClose = function(evt) {
+  this.__reset(this.url);
+};
+
+TWebSocketTransport.prototype.__onMessage = function(evt) {
+  if (this.callbacks.length) {
+    this.callbacks.shift()(evt.data);
+  }
+};
+
+TWebSocketTransport.prototype.__onError = function(evt) {
+  console.log("Thrift WebSocket Error: " + evt.toString());
+  this.socket.close();
+};
+
+/**
+ * Sets the buffer to use when receiving server responses.
+ * @param {string} buf - The buffer to receive server responses.
+ */
+TWebSocketTransport.prototype.setRecvBuffer = function(buf) {
+  this.recv_buf = buf;
+  this.recv_buf_sz = this.recv_buf.length;
+  this.wpos = this.recv_buf.length;
+  this.rpos = 0;
+};
+
+/**
+ * Returns true if the transport is open
+ * @readonly
+ * @returns {boolean}
+ */
+TWebSocketTransport.prototype.isOpen = function() {
+  return this.socket && this.socket.readyState == this.socket.OPEN;
+};
+
+/**
+ * Opens the transport connection
+ */
+TWebSocketTransport.prototype.open = function() {
+  //If OPEN/CONNECTING/CLOSING ignore additional opens
+  if (this.socket && this.socket.readyState != this.socket.CLOSED) {
+    return;
+  }
+  //If there is no socket or the socket is closed:
+  this.socket = new WebSocket(this.url);
+  this.socket.onopen = this.__onOpen.bind(this);
+  this.socket.onmessage = this.__onMessage.bind(this);
+  this.socket.onerror = this.__onError.bind(this);
+  this.socket.onclose = this.__onClose.bind(this);
+};
+
+/**
+ * Closes the transport connection
+ */
+TWebSocketTransport.prototype.close = function() {
+  this.socket.close();
+};
+
+/**
+ * Returns the specified number of characters from the response
+ * buffer.
+ * @param {number} len - The number of characters to return.
+ * @returns {string} Characters sent by the server.
+ */
+TWebSocketTransport.prototype.read = function(len) {
+  var avail = this.wpos - this.rpos;
+
+  if (avail === 0) {
+    return '';
+  }
+
+  var give = len;
+
+  if (avail < len) {
+    give = avail;
+  }
+
+  var ret = this.read_buf.substr(this.rpos, give);
+  this.rpos += give;
+
+  //clear buf when complete?
+  return ret;
+};
+
+/**
+ * Returns the entire response buffer.
+ * @returns {string} Characters sent by the server.
+ */
+TWebSocketTransport.prototype.readAll = function() {
+  return this.recv_buf;
+};
+
+/**
+ * Sets the send buffer to buf.
+ * @param {string} buf - The buffer to send.
+ */
+TWebSocketTransport.prototype.write = function(buf) {
+  this.send_buf = buf;
+};
+
+/**
+ * Returns the send buffer.
+ * @readonly
+ * @returns {string} The send buffer.
+ */
+TWebSocketTransport.prototype.getSendBuffer = function() {
+  return this.send_buf;
+};
diff --git a/lib/nodejs/lib/thrift/xhr_connection.js b/lib/nodejs/lib/thrift/xhr_connection.js
new file mode 100644
index 0000000..6459c90
--- /dev/null
+++ b/lib/nodejs/lib/thrift/xhr_connection.js
@@ -0,0 +1,280 @@
+/*
+ * 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.
+ */
+var util = require('util');
+var EventEmitter = require("events").EventEmitter;
+var thrift = require('./thrift');
+
+var TBufferedTransport = require('./buffered_transport');
+var TJSONProtocol = require('./json_protocol');
+var InputBufferUnderrunError = require('./input_buffer_underrun_error');
+
+var createClient = require('./create_client');
+
+exports.XHRConnection = XHRConnection;
+
+/**
+ * Constructor Function for the XHR Connection.
+ * If you do not specify a host and port then XHRConnection will default to the
+ * host and port of the page from which this javascript is served.
+ * @constructor
+ * @param {string} [url] - The URL to connect to.
+ * @classdesc TXHRConnection objects provide Thrift end point transport
+ *     semantics implemented using XHR.
+ * @example
+ *     var transport = new Thrift.TXHRConnection('localhost', 9099, {});
+ */
+function XHRConnection(host, port, options) {
+  this.options = options || {};
+  this.wpos = 0;
+  this.rpos = 0;
+  this.useCORS = (options && options.useCORS);
+  this.send_buf = '';
+  this.recv_buf = '';
+  this.transport = options.transport || TBufferedTransport;
+  this.protocol = options.protocol || TJSONProtocol;
+  this.headers = options.headers || {};
+
+  host = host || window.location.host;
+  port = port || window.location.port;
+  var prefix = options.https ? 'https://' : 'http://';
+  var path = options.path || '/';
+
+  if (port === '') {
+    port = undefined;
+  }
+
+  if (!port || port === 80 || port === '80') {
+    this.url = prefix + host + path;
+  } else {
+    this.url = prefix + host + ':' + port + path;
+  }
+
+  //The sequence map is used to map seqIDs back to the
+  //  calling client in multiplexed scenarios
+  this.seqId2Service = {};
+};
+
+util.inherits(XHRConnection, EventEmitter);
+
+/**
+* Gets the browser specific XmlHttpRequest Object.
+* @returns {object} the browser XHR interface object
+*/
+XHRConnection.prototype.getXmlHttpRequestObject = function() {
+  try { return new XMLHttpRequest(); } catch (e1) { }
+  try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch (e2) { }
+  try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e3) { }
+
+  throw "Your browser doesn't support XHR.";
+};
+
+/**
+ * Sends the current XRH request if the transport was created with a URL
+ * and the async parameter is false. If the transport was not created with
+ * a URL, or the async parameter is True and no callback is provided, or
+ * the URL is an empty string, the current send buffer is returned.
+ * @param {object} async - If true the current send buffer is returned.
+ * @param {object} callback - Optional async completion callback
+ * @returns {undefined|string} Nothing or the current send buffer.
+ * @throws {string} If XHR fails.
+ */
+XHRConnection.prototype.flush = function() {
+  var self = this;
+  if (this.url === undefined || this.url === '') {
+    return this.send_buf;
+  }
+
+  var xreq = this.getXmlHttpRequestObject();
+
+  if (xreq.overrideMimeType) {
+    xreq.overrideMimeType('application/json');
+  }
+
+  xreq.onreadystatechange = function() {
+    if (this.readyState == 4 && this.status == 200) {
+      self.setRecvBuffer(this.responseText);
+    }
+  };
+
+  xreq.open('POST', this.url, true);
+
+  Object.keys(this.headers).forEach(function(headerKey) {
+    xreq.setRequestHeader(headerKey, self.headers[headerKey]);
+  });
+
+  xreq.send(this.send_buf);
+};
+
+/**
+ * Sets the buffer to provide the protocol when deserializing.
+ * @param {string} buf - The buffer to supply the protocol.
+ */
+XHRConnection.prototype.setRecvBuffer = function(buf) {
+  this.recv_buf = buf;
+  this.recv_buf_sz = this.recv_buf.length;
+  this.wpos = this.recv_buf.length;
+  this.rpos = 0;
+
+  if (Object.prototype.toString.call(buf) == "[object ArrayBuffer]") {
+    var data = new Uint8Array(buf);
+  }
+  var thing = new Buffer(data || buf);
+
+  this.transport.receiver(this.__decodeCallback.bind(this))(thing);
+
+};
+
+XHRConnection.prototype.__decodeCallback = function(transport_with_data) {
+  var proto = new this.protocol(transport_with_data);
+  try {
+    while (true) {
+      var header = proto.readMessageBegin();
+      var dummy_seqid = header.rseqid * -1;
+      var client = this.client;
+      //The Multiplexed Protocol stores a hash of seqid to service names
+      //  in seqId2Service. If the SeqId is found in the hash we need to
+      //  lookup the appropriate client for this call.
+      //  The client var is a single client object when not multiplexing,
+      //  when using multiplexing it is a service name keyed hash of client
+      //  objects.
+      //NOTE: The 2 way interdependencies between protocols, transports,
+      //  connections and clients in the Node.js implementation are irregular
+      //  and make the implementation difficult to extend and maintain. We
+      //  should bring this stuff inline with typical thrift I/O stack
+      //  operation soon.
+      //  --ra
+      var service_name = this.seqId2Service[header.rseqid];
+      if (service_name) {
+        client = this.client[service_name];
+        delete this.seqId2Service[header.rseqid];
+      }
+      /*jshint -W083 */
+      client._reqs[dummy_seqid] = function(err, success) {
+        transport_with_data.commitPosition();
+        var clientCallback = client._reqs[header.rseqid];
+        delete client._reqs[header.rseqid];
+        if (clientCallback) {
+          clientCallback(err, success);
+        }
+      };
+      /*jshint +W083 */
+      if (client['recv_' + header.fname]) {
+        client['recv_' + header.fname](proto, header.mtype, dummy_seqid);
+      } else {
+        delete client._reqs[dummy_seqid];
+        this.emit("error",
+          new thrift.TApplicationException(
+            thrift.TApplicationExceptionType.WRONG_METHOD_NAME,
+            "Received a response to an unknown RPC function"));
+      }
+    }
+  } catch (e) {
+    if (e instanceof InputBufferUnderrunError) {
+      transport_with_data.rollbackPosition();
+    } else {
+      throw e;
+    }
+  }
+};
+
+/**
+ * Returns true if the transport is open, XHR always returns true.
+ * @readonly
+ * @returns {boolean} Always True.
+ */
+XHRConnection.prototype.isOpen = function() {
+  return true;
+};
+
+/**
+ * Opens the transport connection, with XHR this is a nop.
+ */
+XHRConnection.prototype.open = function() {};
+
+/**
+ * Closes the transport connection, with XHR this is a nop.
+ */
+XHRConnection.prototype.close = function() {};
+
+/**
+ * Returns the specified number of characters from the response
+ * buffer.
+ * @param {number} len - The number of characters to return.
+ * @returns {string} Characters sent by the server.
+ */
+XHRConnection.prototype.read = function(len) {
+  var avail = this.wpos - this.rpos;
+
+  if (avail === 0) {
+    return '';
+  }
+
+  var give = len;
+
+  if (avail < len) {
+    give = avail;
+  }
+
+  var ret = this.read_buf.substr(this.rpos, give);
+  this.rpos += give;
+
+  //clear buf when complete?
+  return ret;
+};
+
+/**
+ * Returns the entire response buffer.
+ * @returns {string} Characters sent by the server.
+ */
+XHRConnection.prototype.readAll = function() {
+  return this.recv_buf;
+};
+
+/**
+ * Sets the send buffer to buf.
+ * @param {string} buf - The buffer to send.
+ */
+XHRConnection.prototype.write = function(buf) {
+  this.send_buf = buf;
+  this.flush();
+};
+
+/**
+ * Returns the send buffer.
+ * @readonly
+ * @returns {string} The send buffer.
+ */
+XHRConnection.prototype.getSendBuffer = function() {
+  return this.send_buf;
+};
+
+/**
+ * Creates a new TXHRTransport object, used by Thrift clients to connect
+ *    to Thrift HTTP based servers.
+ * @param {string} host - The host name or IP to connect to.
+ * @param {number} port - The TCP port to connect to.
+ * @param {XHRConnectOptions} options - The configuration options to use.
+ * @returns {XHRConnection} The connection object.
+ * @see {@link XHRConnectOptions}
+ */
+exports.createXHRConnection = function(host, port, options) {
+  return new XHRConnection(host, port, options);
+};
+
+exports.createXHRClient = createClient;
diff --git a/lib/nodejs/test/binary.test.js b/lib/nodejs/test/binary.test.js
index dacadef..38ba634 100644
--- a/lib/nodejs/test/binary.test.js
+++ b/lib/nodejs/test/binary.test.js
@@ -17,117 +17,121 @@
  * under the License.
  */
 
-var testCase = require('nodeunit').testCase;
+var test = require('tape');
 var binary = require('thrift/binary');
 
-module.exports = testCase({
-  "Should read signed byte": function(test){
-    test.strictEqual(1, binary.readByte(0x01));
-    test.strictEqual(-1, binary.readByte(0xFF));
+var cases = {
+  "Should read signed byte": function(assert){
+    assert.equal(1, binary.readByte(0x01));
+    assert.equal(-1, binary.readByte(0xFF));
 
-    test.strictEqual(127, binary.readByte(0x7F));
-    test.strictEqual(-128, binary.readByte(0x80));
-    test.done();
+    assert.equal(127, binary.readByte(0x7F));
+    assert.equal(-128, binary.readByte(0x80));
+    assert.end();
   },
-  "Should write byte": function(test){
-  	//Protocol simply writes to the buffer. Nothing to test.. yet.
-  	test.ok(true);
-  	test.done();
+  "Should write byte": function(assert){
+    //Protocol simply writes to the buffer. Nothing to test.. yet.
+    assert.ok(true);
+    assert.end();
   },
-  "Should read I16": function(test) {
-    test.strictEqual(0, binary.readI16([0x00, 0x00]));
-    test.strictEqual(1, binary.readI16([0x00, 0x01]));
-    test.strictEqual(-1, binary.readI16([0xff, 0xff]));
+  "Should read I16": function(assert) {
+    assert.equal(0, binary.readI16([0x00, 0x00]));
+    assert.equal(1, binary.readI16([0x00, 0x01]));
+    assert.equal(-1, binary.readI16([0xff, 0xff]));
 
     // Min I16
-    test.strictEqual(-32768, binary.readI16([0x80, 0x00]));
+    assert.equal(-32768, binary.readI16([0x80, 0x00]));
     // Max I16
-    test.strictEqual(32767, binary.readI16([0x7f, 0xff]));
-    test.done();
+    assert.equal(32767, binary.readI16([0x7f, 0xff]));
+    assert.end();
   },
 
-  "Should write I16": function(test) {
-    test.deepEqual([0x00, 0x00], binary.writeI16([], 0));
-    test.deepEqual([0x00, 0x01], binary.writeI16([], 1));
-    test.deepEqual([0xff, 0xff], binary.writeI16([], -1));
+  "Should write I16": function(assert) {
+    assert.deepEqual([0x00, 0x00], binary.writeI16([], 0));
+    assert.deepEqual([0x00, 0x01], binary.writeI16([], 1));
+    assert.deepEqual([0xff, 0xff], binary.writeI16([], -1));
 
     // Min I16
-    test.deepEqual([0x80, 0x00], binary.writeI16([], -32768));
+    assert.deepEqual([0x80, 0x00], binary.writeI16([], -32768));
     // Max I16
-    test.deepEqual([0x7f, 0xff], binary.writeI16([], 32767));
-    test.done();
+    assert.deepEqual([0x7f, 0xff], binary.writeI16([], 32767));
+    assert.end();
   },
 
-  "Should read I32": function(test) {
-    test.strictEqual(0, binary.readI32([0x00, 0x00, 0x00, 0x00]));
-    test.strictEqual(1, binary.readI32([0x00, 0x00, 0x00, 0x01]));
-    test.strictEqual(-1, binary.readI32([0xff, 0xff, 0xff, 0xff]));
+  "Should read I32": function(assert) {
+    assert.equal(0, binary.readI32([0x00, 0x00, 0x00, 0x00]));
+    assert.equal(1, binary.readI32([0x00, 0x00, 0x00, 0x01]));
+    assert.equal(-1, binary.readI32([0xff, 0xff, 0xff, 0xff]));
 
     // Min I32
-    test.strictEqual(-2147483648, binary.readI32([0x80, 0x00, 0x00, 0x00]));
+    assert.equal(-2147483648, binary.readI32([0x80, 0x00, 0x00, 0x00]));
     // Max I32
-    test.strictEqual(2147483647, binary.readI32([0x7f, 0xff, 0xff, 0xff]));
-    test.done();
+    assert.equal(2147483647, binary.readI32([0x7f, 0xff, 0xff, 0xff]));
+    assert.end();
   },
 
-  "Should write I32": function(test) {
-    test.deepEqual([0x00, 0x00, 0x00, 0x00], binary.writeI32([], 0));
-    test.deepEqual([0x00, 0x00, 0x00, 0x01], binary.writeI32([], 1));
-    test.deepEqual([0xff, 0xff, 0xff, 0xff], binary.writeI32([], -1));
+  "Should write I32": function(assert) {
+    assert.deepEqual([0x00, 0x00, 0x00, 0x00], binary.writeI32([], 0));
+    assert.deepEqual([0x00, 0x00, 0x00, 0x01], binary.writeI32([], 1));
+    assert.deepEqual([0xff, 0xff, 0xff, 0xff], binary.writeI32([], -1));
 
     // Min I32
-    test.deepEqual([0x80, 0x00, 0x00, 0x00], binary.writeI32([], -2147483648));
+    assert.deepEqual([0x80, 0x00, 0x00, 0x00], binary.writeI32([], -2147483648));
     // Max I32
-    test.deepEqual([0x7f, 0xff, 0xff, 0xff], binary.writeI32([], 2147483647));
- 	test.done();
+    assert.deepEqual([0x7f, 0xff, 0xff, 0xff], binary.writeI32([], 2147483647));
+    assert.end();
   },
 
-  "Should read doubles": function(test) {
-    test.strictEqual(0, binary.readDouble([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
-    test.strictEqual(0, binary.readDouble([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
-    test.strictEqual(1, binary.readDouble([0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
-    test.strictEqual(2, binary.readDouble([0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
-    test.strictEqual(-2, binary.readDouble([0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
+  "Should read doubles": function(assert) {
+    assert.equal(0, binary.readDouble([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
+    assert.equal(0, binary.readDouble([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
+    assert.equal(1, binary.readDouble([0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
+    assert.equal(2, binary.readDouble([0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
+    assert.equal(-2, binary.readDouble([0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
 
-    test.strictEqual(Math.PI, binary.readDouble([0x40, 0x9, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18]))
+    assert.equal(Math.PI, binary.readDouble([0x40, 0x9, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18]))
 
-    test.strictEqual(Infinity, binary.readDouble([0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
-    test.strictEqual(-Infinity, binary.readDouble([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
+    assert.equal(Infinity, binary.readDouble([0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
+    assert.equal(-Infinity, binary.readDouble([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
 
-    test.ok(isNaN(binary.readDouble([0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])))
+    assert.ok(isNaN(binary.readDouble([0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])))
 
-    test.strictEqual(1/3, binary.readDouble([0x3f, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55]))
+    assert.equal(1/3, binary.readDouble([0x3f, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55]))
 
     // Min subnormal positive double
-    test.strictEqual(4.9406564584124654e-324, binary.readDouble([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]))
+    assert.equal(4.9406564584124654e-324, binary.readDouble([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]))
     // Min normal positive double
-    test.strictEqual(2.2250738585072014e-308, binary.readDouble([0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
+    assert.equal(2.2250738585072014e-308, binary.readDouble([0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
     // Max positive double
-    test.strictEqual(1.7976931348623157e308, binary.readDouble([0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]))
-  	test.done();
+    assert.equal(1.7976931348623157e308, binary.readDouble([0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]))
+    assert.end();
   },
 
-  "Should write doubles": function(test) {
-    test.deepEqual([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], 0));
-    test.deepEqual([0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], 1));
-    test.deepEqual([0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], 2));
-    test.deepEqual([0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], -2));
+  "Should write doubles": function(assert) {
+    assert.deepEqual([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], 0));
+    assert.deepEqual([0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], 1));
+    assert.deepEqual([0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], 2));
+    assert.deepEqual([0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], -2));
 
-    test.deepEqual([0x40, 0x9, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18], binary.writeDouble([], Math.PI));
+    assert.deepEqual([0x40, 0x9, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18], binary.writeDouble([], Math.PI));
 
-    test.deepEqual([0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], Infinity));
-    test.deepEqual([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], -Infinity));
+    assert.deepEqual([0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], Infinity));
+    assert.deepEqual([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], -Infinity));
 
-    test.deepEqual([0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], NaN));
+    assert.deepEqual([0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], NaN));
 
-    test.deepEqual([0x3f, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55], binary.writeDouble([], 1/3));
+    assert.deepEqual([0x3f, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55], binary.writeDouble([], 1/3));
 
     // Min subnormal positive double
-    test.deepEqual([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], binary.writeDouble([], 4.9406564584124654e-324));
+    assert.deepEqual([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], binary.writeDouble([], 4.9406564584124654e-324));
     // Min normal positive double
-    test.deepEqual([0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], 2.2250738585072014e-308));
+    assert.deepEqual([0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], binary.writeDouble([], 2.2250738585072014e-308));
     // Max positive double
-    test.deepEqual([0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], binary.writeDouble([], 1.7976931348623157e308));
-  	test.done();
+    assert.deepEqual([0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], binary.writeDouble([], 1.7976931348623157e308));
+    assert.end();
   }
+};
+
+Object.keys(cases).forEach(function(caseName) {
+  test(caseName, cases[caseName]);
 });
diff --git a/lib/nodejs/test/browser_client.js b/lib/nodejs/test/browser_client.js
new file mode 100644
index 0000000..27db543
--- /dev/null
+++ b/lib/nodejs/test/browser_client.js
@@ -0,0 +1,27 @@
+
+var assert = require('assert');
+var thrift = require('thrift');
+var helpers = require('./helpers');
+var ThriftTest = require('./gen-nodejs/ThriftTest');
+var ThriftTestDriver = require('./test_driver').ThriftTestDriver;
+
+// createXHRConnection createWSConnection
+var connection = thrift.createXHRConnection("localhost", 9090, {
+    transport: helpers.transports['buffered'],
+    protocol: helpers.protocols['json'],
+    path: '/test'
+});
+
+connection.on('error', function(err) {
+    assert(false, err);
+});
+
+// Uncomment the following line to start a websockets connection
+// connection.open();
+
+// createWSClient createXHRClient
+var client = thrift.createXHRClient(ThriftTest, connection);
+
+ThriftTestDriver(client, function (status) {
+    console.log('Browser:', status);
+});
diff --git a/lib/nodejs/test/server.js b/lib/nodejs/test/server.js
index 2d53e11..b33e91b 100644
--- a/lib/nodejs/test/server.js
+++ b/lib/nodejs/test/server.js
@@ -57,7 +57,10 @@
   options.processor = ThriftTest;
 
   options = {
-    services: { "/test": options }
+    services: { "/test": options },
+    cors: {
+      '*': true
+    }
   }
 }
 
diff --git a/lib/nodejs/test/test-cases.js b/lib/nodejs/test/test-cases.js
index 3386657..0d13cdd 100644
--- a/lib/nodejs/test/test-cases.js
+++ b/lib/nodejs/test/test-cases.js
@@ -1,6 +1,5 @@
 'use strict';
 
-var assert = require('assert');
 var ttypes = require('./gen-nodejs/ThriftTest_types');
 
 //all Languages in UTF-8
@@ -55,17 +54,19 @@
   ['testByte', -1],
   ['testByte', -127],
   ['testI32', -1],
+  ['testDouble', -5.2098523],
+  ['testDouble', 7.012052175215044],
+  ['testEnum', ttypes.Numberz.ONE]
+];
+
+var simpleLoose = [
   ['testI64', 5],
   ['testI64', -5],
   ['testI64', 734359738368],
   ['testI64', -34359738368],
   ['testI64', -734359738368],
-  ['testDouble', -5.2098523],
-  ['testDouble', 7.012052175215044],
-  ['testEnum', ttypes.Numberz.ONE],
   ['testTypedef', 69]
-];
-
+]
 
 var mapout = {};
 for (var i = 0; i < 5; ++i) {
@@ -118,6 +119,7 @@
 };
 
 module.exports.simple = simple;
+module.exports.simpleLoose = simpleLoose;
 module.exports.deep = deep;
 
 module.exports.out = out;
diff --git a/lib/nodejs/test/testAll.sh b/lib/nodejs/test/testAll.sh
index 0d10758..fd11425 100755
--- a/lib/nodejs/test/testAll.sh
+++ b/lib/nodejs/test/testAll.sh
@@ -24,7 +24,7 @@
 DIR="$( cd "$( dirname "$0" )" && pwd )"
 
 ISTANBUL="$DIR/../../../node_modules/istanbul/lib/cli.js"
-NODEUNIT="${DIR}/../../../node_modules/nodeunit/bin/nodeunit"
+RUNBROWSER="$DIR/../../../node_modules/run-browser/bin/cli.js"
 
 REPORT_PREFIX="${DIR}/../coverage/report"
 
@@ -54,6 +54,18 @@
   return $RET
 }
 
+testBrowser()
+{
+  echo "   Testing browser client with http server with json protocol and buffered transport";
+  RET=0
+  node ${DIR}/server.js --type http -p json -t buffered &
+  SERVERPID=$!
+  sleep 1
+  ${RUNBROWSER} ${DIR}/browser_client.js --phantom || RET=1
+  kill -2 $SERVERPID || RET=1
+  return $RET
+}
+
 TESTOK=0
 
 #generating thrift code
@@ -62,11 +74,11 @@
 
 #unit tests
 
-${NODEUNIT} ${DIR}/binary.test.js || TESTOK=1
+node ${DIR}/binary.test.js || TESTOK=1
 
 #integration tests
 
-for type in tcp multiplex http websocket
+for type in tcp multiplex websocket http
 do
 
   for protocol in compact binary json
@@ -81,6 +93,9 @@
   done
 done
 
+# XHR only until phantomjs 2 is released.
+testBrowser
+
 if [ -n "${COVER}" ]; then
   ${ISTANBUL} report --dir "${DIR}/../coverage" --include "${DIR}/../coverage/report*/coverage.json" lcov cobertura html
   rm -r ${DIR}/../coverage/report*/*
diff --git a/lib/nodejs/test/test_driver.js b/lib/nodejs/test/test_driver.js
index 9f2b894..f79baa6 100644
--- a/lib/nodejs/test/test_driver.js
+++ b/lib/nodejs/test/test_driver.js
@@ -26,177 +26,195 @@
  // supports an optional callback function which is called with
  // a status message when the test is complete.
 
-var assert = require('assert');
+var test = require('tape');
+//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);
-      })
-    };
-  }
+  test('NodeJS Style Callback Client Tests', function(assert) {
 
-  testCases.simple.forEach(makeAsserter(assert.equal));
-  testCases.deep.forEach(makeAsserter(assert.deepEqual));
+    var checkRecursively = makeRecursiveCheck(assert);
 
-  client.testStruct(testCases.out, function(err, response) {
-    assert(!err);
-    checkRecursively(testCases.out, response);
-  });
+    function makeAsserter(assertionFn) {
+      return function(c) {
+        var fnName = c[0];
+        var expected = c[1];
+        client[fnName](expected, function(err, actual) {
+          assert.error(err, fnName + ': no callback error');
+          assertionFn(actual, expected, fnName);
+        })
+      };
+    }
 
-  client.testNest(testCases.out2, function(err, response) {
-    assert(!err);
-    checkRecursively(testCases.out2, response);
-  });
+    testCases.simple.forEach(makeAsserter(assert.equal));
+    testCases.simpleLoose.forEach(makeAsserter(function(a, e, m){
+      assert.ok(a == e, m);
+    }));
+    testCases.deep.forEach(makeAsserter(assert.deepEqual));
 
-  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();
+    client.testStruct(testCases.out, function(err, response) {
+      assert.error(err, 'testStruct: no callback error');
+      checkRecursively(testCases.out, response, 'testStruct');
     });
-  }, callback);
 
+    client.testNest(testCases.out2, function(err, response) {
+      assert.error(err, 'testNest: no callback error');
+      checkRecursively(testCases.out2, response, 'testNest');
+    });
+
+    client.testInsanity(testCases.crazy, function(err, response) {
+      assert.error(err, 'testInsanity: no callback error');
+      checkRecursively(testCases.insanity, response, 'testInsanity');
+    });
+
+    client.testException('TException', function(err, response) {
+      assert.error(err, 'testException: no callback error');
+      assert.ok(!response, 'testException: no response');
+    });
+
+    client.testException('Xception', function(err, response) {
+      assert.ok(!response, 'testException: no response');
+      assert.equal(err.errorCode, 1001, 'testException: correct error code');
+      assert.equal('Xception', err.message, 'testException: correct error message');
+    });
+
+    client.testException('no Exception', function(err, response) {
+      assert.error(err, 'testException: no callback error');
+      assert.ok(!response, 'testException: no response');
+    });
+
+    client.testOneway(0, function(err, response) {
+      assert.fail('testOneway should not answer');
+    });
+
+    checkOffByOne(function(done) {
+      client.testI32(-1, function(err, response) {
+        assert.error(err, 'checkOffByOne: no callback error');
+        assert.equal(-1, response);
+        assert.end();
+        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);
-    };
-  }
+  test('Q Promise Client Tests', function(assert) {
 
-  testCases.simple.forEach(makeAsserter(assert.equal));
-  testCases.deep.forEach(makeAsserter(assert.deepEqual));
+    var checkRecursively = makeRecursiveCheck(assert);
 
-  client.testStruct(testCases.out)
-    .then(function(response) {
-      checkRecursivelyP(testCases.out, response);
-    })
-    .fail(failTest);
+    function fail(msg) {
+      return function() {
+        assert.fail(msg);
+      }
+    }
 
-  client.testNest(testCases.out2)
-    .then(function(response) {
-      checkRecursivelyP(testCases.out2, response);
-    })
-    .fail(failTest);
+    function makeAsserter(assertionFn) {
+      return function(c) {
+        var fnName = c[0];
+        var expected = c[1];
+        client[fnName](expected)
+          .then(function(actual) {
+            assertionFn(actual, expected, fnName);
+          })
+          .fail(fail('fnName'));
+      };
+    }
 
-  client.testInsanity(testCases.crazy)
-    .then(function(response) {
-      checkRecursivelyP(testCases.insanity, response);
-    })
-    .fail(failTest);
+    testCases.simple.forEach(makeAsserter(assert.equal));
+    testCases.simpleLoose.forEach(makeAsserter(function(a, e, m){
+      assert.ok(a == e, m);
+    }));
+    testCases.deep.forEach(makeAsserter(assert.deepEqual));
 
-  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)
+    client.testStruct(testCases.out)
       .then(function(response) {
-          assert.equal(-1, response);
-          done();
+        checkRecursively(testCases.out, response, 'testStruct');
       })
-      .fail(function() {
-        assert(false);
-      });
-  }, callback);
+      .fail(fail('testStruct'));
 
+    client.testNest(testCases.out2)
+      .then(function(response) {
+        checkRecursively(testCases.out2, response, 'testNest');
+      })
+      .fail(fail('testNest'));
+
+    client.testInsanity(testCases.crazy)
+      .then(function(response) {
+        checkRecursively(testCases.insanity, response, 'testInsanity');
+      })
+      .fail(fail('testInsanity'));
+
+    client.testException('TException')
+      .then(function(response) {
+        assert.ok(!response, 'testException: TException');
+      })
+      .fail(fail('testException: TException'));
+
+    client.testException('Xception')
+      .then(function(response) {
+        assert.ok(!response);
+      })
+      .fail(function(err) {
+        assert.equal(err.errorCode, 1001);
+        assert.equal('Xception', err.message);
+      });
+
+    client.testException('no Exception')
+      .then(function(response) {
+        assert.equal(undefined, response); //void
+      })
+      .fail(fail('testException'));
+
+    client.testOneway(0, fail('testOneway: should not answer'));
+
+    checkOffByOne(function(done) {
+      client.testI32(-1)
+        .then(function(response) {
+            assert.equal(-1, response);
+            assert.end();
+            done();
+        })
+        .fail(fail('checkOffByOne'));
+    }, callback);
+  });
 };
 
 
 // Helper Functions
 // =========================================================
 
-function failTest() {
-  assert(false);
-}
+function makeRecursiveCheck(assert) {
 
-// 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]);
-      }
-    }
-  }
-}
+  return function (map1, map2, msg) {
+    var equal = true;
 
-// 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]);
+    var equal = checkRecursively(map1, map2);
+
+    assert.ok(equal, msg);
+
+    // deepEqual doesn't work with fields using node-int64
+    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);
+            return map1 === n.toNumber();
+          } else {
+            return map1 == map2;
+          }
+        } else {
+          return Object.keys(map1).every(function(key) {
+            return checkRecursively(map1[key], map2[key]);
+          });
+        }
       }
     }
   }
diff --git a/lib/nodejs/test/test_handler.js b/lib/nodejs/test/test_handler.js
index 83b7d41..de6f503 100644
--- a/lib/nodejs/test/test_handler.js
+++ b/lib/nodejs/test/test_handler.js
@@ -25,7 +25,7 @@
 
 function makeSyncHandler(label) {
   return function(thing) {
-    console.log(label + '(\'' + thing + '\')');
+    //console.log(label + '(\'' + thing + '\')');
     return thing;
   }
 }
@@ -72,7 +72,7 @@
 ];
 
 function testVoid() {
-  console.log('testVoid()');
+  //console.log('testVoid()');
 }
 
 function testVoidAsync(result) {
@@ -80,7 +80,7 @@
 }
 
 function testMapMap(hello) {
-  console.log('testMapMap(' + hello + ')');
+  //console.log('testMapMap(' + hello + ')');
 
   var mapmap = [];
   var pos = [];
@@ -96,9 +96,9 @@
 }
 
 function testInsanity(argument) {
-  console.log('testInsanity(');
-  console.log(argument);
-  console.log(')');
+  //console.log('testInsanity(');
+  //console.log(argument);
+  //console.log(')');
 
   var hello = new ttypes.Xtruct();
   hello.string_thing = 'Hello2';
@@ -131,13 +131,13 @@
   insane[1] = first_map;
   insane[2] = second_map;
 
-  console.log('insane result:');
-  console.log(insane);
+  //console.log('insane result:');
+  //console.log(insane);
   return insane;
 }
 
 function testMulti(arg0, arg1, arg2, arg3, arg4, arg5) {
-  console.log('testMulti()');
+  //console.log('testMulti()');
 
   var hello = new ttypes.Xtruct();
   hello.string_thing = 'Hello2';
@@ -153,7 +153,7 @@
 }
 
 function testException(arg) {
-  console.log('testException('+arg+')');
+  //console.log('testException('+arg+')');
   if (arg === 'Xception') {
     var x = new ttypes.Xception();
     x.errorCode = 1001;
@@ -167,7 +167,7 @@
 }
 
 function testExceptionAsync(arg, result) {
-  console.log('testException('+arg+')');
+  //console.log('testException('+arg+')');
   if (arg === 'Xception') {
     var x = new ttypes.Xception();
     x.errorCode = 1001;
@@ -181,7 +181,7 @@
 }
 
 function testMultiException(arg0, arg1) {
-  console.log('testMultiException(' + arg0 + ', ' + arg1 + ')');
+  //console.log('testMultiException(' + arg0 + ', ' + arg1 + ')');
   if (arg0 === ('Xception')) {
     var x = new ttypes.Xception();
     x.errorCode = 1001;
@@ -201,7 +201,7 @@
 }
 
 function testMultiExceptionAsync(arg0, arg1, result) {
-  console.log('testMultiException(' + arg0 + ', ' + arg1 + ')');
+  //console.log('testMultiException(' + arg0 + ', ' + arg1 + ')');
   if (arg0 === ('Xception')) {
     var x = new ttypes.Xception();
     x.errorCode = 1001;
@@ -221,7 +221,7 @@
 }
 
 function testOneway(sleepFor) {
-  console.log('testOneway(' + sleepFor + ') => JavaScript (like Rust) never sleeps!');
+  //console.log('testOneway(' + sleepFor + ') => JavaScript (like Rust) never sleeps!');
 }
 
 function testOnewayAsync(sleepFor, result) {