Marionette JS Client

API Docs for: 1.7.1
Show:

File: lib/marionette/drivers/tcp-sync.js

  1. var wire = require('json-wire-protocol');
  2. var debug = require('debug')('marionette:tcp-sync');
  3. var sockittome = require('sockit-to-me');
  4. var DEFAULT_HOST = 'localhost';
  5. var DEFAULT_PORT = 2828;
  6. var SOCKET_TIMEOUT_EXTRA = 500;

  7. function TcpSync(options) {
  8.   if (!options) {
  9.     options = {};
  10.   }

  11.   if ('port' in options) {
  12.     this.port = options.port;
  13.   }
  14.   if ('host' in options) {
  15.     this.host = options.host;
  16.   }
  17.   if ('connectionTimeout' in options) {
  18.     this.connectionTimeout = options.connectionTimeout;
  19.   }

  20.   this.sockit = new sockittome.Sockit();
  21.   this.sockit.setPollTimeout(this.connectionTimeout + SOCKET_TIMEOUT_EXTRA);
  22. };

  23. TcpSync.prototype.isSync = true;
  24. TcpSync.prototype.host = DEFAULT_HOST;
  25. TcpSync.prototype.port = DEFAULT_PORT;
  26. TcpSync.prototype.connectionTimeout = 2000;
  27. TcpSync.prototype.retryInterval = 300;

  28. TcpSync.prototype.setScriptTimeout = function(timeout) {
  29.   this.sockit.setPollTimeout(timeout + SOCKET_TIMEOUT_EXTRA);
  30. };

  31. /**
  32.  * Utility to wait for the marionette socket to be ready.
  33.  *
  34.  * @method waitForSocket
  35.  * @param {Object} [options] for timeout.
  36.  * @param {Number} [options.interval] time between running test.
  37.  * @param {Number} [options.timeout] maximum wallclock time before
  38.  *   failing test.
  39.  * @param {Function} [callback] callback.
  40.  */
  41. TcpSync.prototype.waitForSocket = function(options, callback) {
  42.   // the logic is lifted from http://dxr.mozilla.org/mozilla-central/source/testing/marionette/client/marionette/marionette.py#562

  43.   if (typeof(options) === 'function') {
  44.     callback = options;
  45.     options = null;
  46.   }

  47.   options = options || {};
  48.   var interval = options.interval || 0;
  49.   var timeout = options.timeout || 30000;
  50.   var socketTimeout = 1000;

  51.   var sockit = new sockittome.Sockit();
  52.   var start = Date.now();
  53.   var self = this;

  54.   // Use sockittome's built in polling timeout during calls to connect
  55.   // to avoid socket misuse.
  56.   sockit.setPollTimeout(socketTimeout);
  57.   var socketConfig = { host: this.host, port: this.port };

  58.   function probeSocket() {
  59.     try {
  60.       debug('probing socket');
  61.       sockit.connect(socketConfig);

  62.       var s = sockit.read(16).toString();
  63.       sockit.close();
  64.       if (s.indexOf(":") != -1) {
  65.         callback();
  66.         return;
  67.       }
  68.     }
  69.     catch(e) {
  70.       debug('exception when probing socket', e.message);
  71.       // Above read _may_ fail so it is important to close the socket...
  72.       sockit.close();
  73.     }
  74.     // timeout. Abort.
  75.     if ((Date.now() - start) > timeout) {
  76.       console.error('timeout connecting to b2g.');
  77.       return;
  78.     }
  79.     // interval delay for the next iteration.
  80.     setTimeout(probeSocket.bind(self), interval);
  81.   };

  82.   probeSocket();
  83. }

  84. TcpSync.prototype.connect = function(callback) {

  85.   this.waitForSocket(function _connect() {
  86.     try {
  87.       this.sockit.connect({ host: this.host, port: this.port });
  88.     } catch(err) {
  89.       if (Date.now() - this._beginConnect >= this.connectionTimeout) {
  90.         callback(err);
  91.       }
  92.       setTimeout(_connect.bind(this, callback), this.retryInterval);
  93.       return;
  94.     }

  95.     if (!this._beginConnect) {
  96.       this._beginConnect = Date.now();
  97.     }

  98.     // Ensure this method's resolution is asynchronous in all cases
  99.     process.nextTick(function() {
  100.       debug('socket connected');
  101.       delete this._beginConnect;

  102.       this._readResponse();

  103.       callback();
  104.     }.bind(this));
  105.   }.bind(this));
  106. };

  107. TcpSync.prototype.defaultCallback = function(err, result) {
  108.   if (err) {
  109.     throw err;
  110.   }
  111.   return result;
  112. };

  113. // Following the json-wire-protocol implementation, read one byte at a time
  114. // until the 'separator' character is received. The data preceding the
  115. // delimiter describes the length of the JSON string.
  116. TcpSync.prototype._readResponse = function() {
  117.   var stream = new wire.Stream();
  118.   var char, data, error;

  119.   stream.on('data', function(parsed) {
  120.     debug('read', parsed);
  121.     data = parsed;
  122.   });
  123.   stream.on('error', function(err) {
  124.     error = err;
  125.   });

  126.   while (!data && !error) {
  127.     stream.write(this.sockit.read(1));
  128.   }

  129.   if (error) {
  130.     throw error;
  131.   }

  132.   return data;
  133. };

  134. TcpSync.prototype.send = function(command, callback) {
  135.   debug('write', command);
  136.   this.sockit.write(wire.stringify(command));
  137.   return callback(this._readResponse());
  138. };

  139. TcpSync.prototype.close = function() {
  140.   this.sockit.close();
  141. };

  142. module.exports = TcpSync;

  143.