<commit_before>// Wrapper around Apple's Instruments app
"use strict";

var spawn = require('child_process').spawn
  , logger = require('../logger').get('appium')
  , fs = require('fs')
  , _ = require('underscore')
  , net = require('net')
  , uuid = require('uuid-js')
  , codes = require('../app/uiauto/lib/status.js').codes;

var Instruments = function(app, udid, bootstrap, template, sock, cb, exitCb) {
  this.app = app;
  this.udid = udid;
  this.bootstrap = bootstrap;
  this.template = template;
  this.commandQueue = [];
  this.curCommand = null;
  this.resultHandler = this.defaultResultHandler;
  this.readyHandler = this.defaultReadyHandler;
  this.exitHandler = this.defaultExitHandler;
  this.hasConnected = false;
  this.traceDir = null;
  this.proc = null;
  this.debugMode = false;
  this.onReceiveCommand = null;
  this.guid = uuid.create();
  this.bufferedData = "";
  this.eventRouter = {
    'cmd': this.commandHandler
  };
  if (typeof sock === "undefined") {
    sock = '/tmp/instruments_sock';
  }
  if (typeof cb !== "undefined") {
    this.readyHandler = cb;
  }
  if (typeof exitCb !== "undefined") {
    this.exitHandler = exitCb;
  }

  this.startSocketServer(sock);

};


/* INITIALIZATION */

Instruments.prototype.startSocketServer = function(sock) {
  // remove socket if it currently exists
  try {
    fs.unlinkSync(sock);
  } catch (Exception) {}

  var onSocketNeverConnect = function() {
    logger.error("Instruments socket client never checked in; timing out".red);
    this.proc.kill('SIGTERM');
    this.exitHandler(1);
  };

  var socketConnectTimeout = setTimeout(_.bind(onSocketNeverConnect, this), 300000);

  var server = net.createServer({allowHalfOpen: true}, _.bind(function(conn) {
    if (!this.hasConnected) {
      this.hasConnected = true;
      this.debug("Instruments is ready to receive commands");
      clearTimeout(socketConnectTimeout);
      this.readyHandler(this);
      this.readyHandler = this.defaultReadyHandler;
    }
    conn.setEncoding('utf8'); // get strings from sockets rather than buffers

    conn.on('data', _.bind(function(data) {
      // when data comes in, route it according to the "event" property
      this.debug("Socket data received (" + data.length + " bytes)");
      this.bufferedData += data;
    }, this));

    this.currentSocket = conn;
    this.debug("Socket Connected");

    conn.on('close', _.bind(function() {
        this.debug("Socket Completely closed");
        this.currentSocket = null;
    }, this));

    conn.on('end', _.bind(function() {
      this.debug("Socket closed by other side");
      var data = this.bufferedData;
      this.bufferedData = "";
      try {
        data = JSON.parse(data);
      } catch (e) {
        logger.error("Couldn't parse JSON data from socket, maybe buffer issue?");
        logger.error(data);
        data = {
          event: 'cmd'
          , result: {
            status: codes.UnknownError
            , value: "Error parsing socket data from instruments"
          }
        };
      }
      if (!_.has(data, 'event')) {
        logger.error("Socket data came in witout event, it was:");
        logger.error(JSON.stringify(data));
      } else if (!_.has(this.eventRouter, data.event)) {
        logger.error("Socket is asking for event '" + data.event +
                    "' which doesn't exist");
      } else {
        this.debug("Socket data being routed for '" + data.event + "' event");
        _.bind(this.eventRouter[data.event], this)(data, conn);
      }
    }, this));

  }, this));

  server.listen(sock, _.bind(function() {
    this.debug("Instruments socket server started at " + sock);
    this.launch();
  }, this));
};

Instruments.prototype.launch = function() {
  var self = this;
  // prepare temp dir
  var tmpDir = '/tmp/' + this.guid;
  fs.mkdir(tmpDir, function(e) {
    if (!e || (e && e.code === 'EEXIST')) {
      self.proc = self.spawnInstruments(tmpDir);
      self.proc.stdout.on('data', function(data) {
        self.outputStreamHandler(data);
      });
      self.proc.stderr.on('data', function(data) {
        self.errorStreamHandler(data);
      });

      self.proc.on('exit', function(code) {
        self.debug("Instruments exited with code " + code);
        self.exitCode = code;
        self.exitHandler(self.exitCode, self.traceDir);
        self.proc.stdin.end();
        self.proc = null;
        self.exitHandler = self.defaultExitHandler;
        self.resultHandler = self.defaultResultHandler;
        self.onReceiveCommand = null;
        if (self.currentSocket) {
			    self.debug("Socket closed forcibly due to exit");
			    self.currentSocket.end();
			    self.currentSocket.destroy();	// close this
		    }
      });
    } else {
      throw e;
    }
  });
};

Instruments.prototype.spawnInstruments = function(tmpDir) {
  var args = ["-t", this.template];
  if (this.udid) {
    args = args.concat(["-w", this.udid]);
  }
  args = args.concat([this.app]);
  args = args.concat(["-e", "UIASCRIPT", this.bootstrap]);
  args = args.concat(["-e", "UIARESULTSPATH", tmpDir]);
  return spawn("/usr/bin/instruments", args);
};


/* COMMAND LIFECYCLE */

Instruments.prototype.commandHandler = function(data, c) {
  var hasResult = typeof data.result !== "undefined";
  if (hasResult && !this.curCommand) {
    logger.info("Got a result when we weren't expecting one! Ignoring it");
  } else if (!hasResult && this.curCommand) {<commit_msg>Fix #148 leaving domain sockets open<commit_after>// Wrapper around Apple's Instruments app
"use strict";

var spawn = require('child_process').spawn
  , logger = require('../logger').get('appium')
  , fs = require('fs')
  , _ = require('underscore')
  , net = require('net')
  , uuid = require('uuid-js')
  , codes = require('../app/uiauto/lib/status.js').codes;

var Instruments = function(app, udid, bootstrap, template, sock, cb, exitCb) {
  this.app = app;
  this.udid = udid;
  this.bootstrap = bootstrap;
  this.template = template;
  this.commandQueue = [];
  this.curCommand = null;
  this.resultHandler = this.defaultResultHandler;
  this.readyHandler = this.defaultReadyHandler;
  this.exitHandler = this.defaultExitHandler;
  this.hasConnected = false;
  this.traceDir = null;
  this.proc = null;
  this.debugMode = false;
  this.onReceiveCommand = null;
  this.guid = uuid.create();
  this.bufferedData = "";
  this.eventRouter = {
    'cmd': this.commandHandler
  };
  if (typeof sock === "undefined") {
    sock = '/tmp/instruments_sock';
  }
  if (typeof cb !== "undefined") {
    this.readyHandler = cb;
  }
  if (typeof exitCb !== "undefined") {
    this.exitHandler = exitCb;
  }

  this.startSocketServer(sock);

};


/* INITIALIZATION */

Instruments.prototype.startSocketServer = function(sock) {
  // remove socket if it currently exists
  try {
    fs.unlinkSync(sock);
  } catch (Exception) {}

  var onSocketNeverConnect = function() {
    logger.error("Instruments socket client never checked in; timing out".red);
    this.proc.kill('SIGTERM');
    this.exitHandler(1);
  };

  var socketConnectTimeout = setTimeout(_.bind(onSocketNeverConnect, this), 300000);

  this.socketServer = net.createServer({allowHalfOpen: true}, _.bind(function(conn) {
    if (!this.hasConnected) {
      this.hasConnected = true;
      this.debug("Instruments is ready to receive commands");
      clearTimeout(socketConnectTimeout);
      this.readyHandler(this);
      this.readyHandler = this.defaultReadyHandler;
    }
    conn.setEncoding('utf8'); // get strings from sockets rather than buffers

    conn.on('data', _.bind(function(data) {
      // when data comes in, route it according to the "event" property
      this.debug("Socket data received (" + data.length + " bytes)");
      this.bufferedData += data;
    }, this));

    this.currentSocket = conn;
    this.debug("Socket Connected");

    conn.on('close', _.bind(function() {
        this.debug("Socket Completely closed");
        this.currentSocket = null;
    }, this));

    conn.on('end', _.bind(function() {
      this.debug("Socket closed by other side");
      var data = this.bufferedData;
      this.bufferedData = "";
      try {
        data = JSON.parse(data);
      } catch (e) {
        logger.error("Couldn't parse JSON data from socket, maybe buffer issue?");
        logger.error(data);
        data = {
          event: 'cmd'
          , result: {
            status: codes.UnknownError
            , value: "Error parsing socket data from instruments"
          }
        };
      }
      if (!_.has(data, 'event')) {
        logger.error("Socket data came in witout event, it was:");
        logger.error(JSON.stringify(data));
      } else if (!_.has(this.eventRouter, data.event)) {
        logger.error("Socket is asking for event '" + data.event +
                    "' which doesn't exist");
      } else {
        this.debug("Socket data being routed for '" + data.event + "' event");
        _.bind(this.eventRouter[data.event], this)(data, conn);
      }
    }, this));

  }, this));
 
  this.socketServer.on('close', _.bind(function() {
    this.debug("Instruments socket server closed");
  }, this));
  
  this.socketServer.listen(sock, _.bind(function() {
    this.debug("Instruments socket server started at " + sock);
    this.launch();
  }, this));
};

Instruments.prototype.launch = function() {
  var self = this;
  // prepare temp dir
  var tmpDir = '/tmp/' + this.guid;
  fs.mkdir(tmpDir, function(e) {
    if (!e || (e && e.code === 'EEXIST')) {
      self.proc = self.spawnInstruments(tmpDir);
      self.proc.stdout.on('data', function(data) {
        self.outputStreamHandler(data);
      });
      self.proc.stderr.on('data', function(data) {
        self.errorStreamHandler(data);
      });

      self.proc.on('exit', function(code) {
        self.debug("Instruments exited with code " + code);
        self.exitCode = code;
        self.exitHandler(self.exitCode, self.traceDir);
        self.proc.stdin.end();
        self.proc = null;
        self.exitHandler = self.defaultExitHandler;
        self.resultHandler = self.defaultResultHandler;
        self.onReceiveCommand = null;
        if (self.currentSocket) {
          self.debug("Socket closed forcibly due to exit");
          self.currentSocket.end();
          self.currentSocket.destroy();	// close this
          self.socketServer.close();
        }
      });
    } else {
      throw e;
    }
  });
};

Instruments.prototype.spawnInstruments = function(tmpDir) {
  var args = ["-t", this.template];
  if (this.udid) {
    args = args.concat(["-w", this.udid]);
  }
  args = args.concat([this.app]);
  args = args.concat(["-e", "UIASCRIPT", this.bootstrap]);
  args = args.concat(["-e", "UIARESULTSPATH", tmpDir]);
  return spawn("/usr/bin/instruments", args);
};


/* COMMAND LIFECYCLE */

Instruments.prototype.commandHandler = function(data, c) {
  var hasResult = typeof data.result !== "undefined";
  if (hasResult && !this.curCommand) {
    logger.info("Got a result when we weren't expecting one! Ignoring it");
  } else if (!hasResult && this.curCommand) {
{'fraction_non_alphanumeric': 0.15137862677959993, 'fraction_numerical': 0.001712020183816904, 'mean_word_length': 2.886204481792717, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3070748', 'n_tokens_mistral': 3489, 'n_tokens_neox': 3348, 'n_words': 760}