<commit_before>require('../logger').toFile("../../console.json");
var fs = require('fs');
var path = require('path');
var exec = require('child_process').exec;
var sync = jxcore.utils.cmdSync;
var spawn = require('child_process').spawn;
var eopts = {
  encoding: 'utf8',
  timeout: 0,
  maxBuffer: 1e8,
  killSignal: 'SIGTERM'
};

if (!process.argv[2]) {
  logme("Needs argument!");
  process.exit(1);
  return;
}

var job = JSON.parse(new Buffer(process.argv[2], "base64") + "");
var nodeId = 0;

// out: [ [ '8a09fc3c', 'device' ] ]
var getAndroidDevices = function () {
  var res = sync("adb devices");

  if (res.exitCode != 0) {
    logme("Error: getAndroidDevices failed", res.out);
    process.exit(1);
    return;
  }

  var devs = [];
  res = res.out.split('\n');
  if (res[0].indexOf("List of devices") == 0) {
    for (var i = 1; i < res.length; i++) {
      if (res[i].trim().length == 0) continue;
      if (res[i].indexOf('offline') > 0 ||
          res[i].indexOf('unauthorized') > 0 ||
          res[i].indexOf('no permissions') > 0) {
        logme("Warning: Phone " + res[i] + " - CANNOT BE USED");
        continue; // phone offline/unauthorized/no debug permissions
      }
      var dev = res[i].split('\t');
      devs.push(dev);
    }
  }

  if (devs.length == 0) {
    logme("Error: No Android device found.", "");
    process.exit(1)
    return;
  }

  var arr = [];
  for (var i = 0; i < devs.length; i++) {
    var man = sync("adb -s " + devs[i][0] + " shell getprop ro.product.manufacturer");
    var pro = sync("adb -s " + devs[i][0] + " shell getprop ro.product.model")
    arr.push({
      deviceId: devs[i][0],
      deviceName: man.out.replace("\n", "").trim() + "-" + pro.out.replace("\n", "").trim()
    })
  }

  return arr;
};

var arrDevices = getAndroidDevices();
var builds = path.join(__dirname, "../builder/builds/" + job.uqID + "/build_android");
var appCounter = 0;
var testFailed = false;

var deployAndroid = function (apk_path, device_name, class_name) {
  var cmd = 'adb -s ' + device_name + ' install -r ' + apk_path + ';adb -s ' + device_name + ' shell pm list packages';
  var res = null;
  var failureReasonIndex = -1;
  var failureReason = "";
  exec(cmd, eopts, function (err, stdout, stderr) {
    if (err || stdout.indexOf(class_name) < 0) {
      res = ("Error: problem deploying Android apk(" + apk_path + ") to device " + device_name + (err ? ("\n" + err) : ""));
      failureReasonIndex = stdout.indexOf('Failure');
      if (failureReasonIndex > -1) {
        failureReason = stdout.substring(failureReasonIndex);
        failureReason = failureReason.substring(0, failureReason.indexOf('\n'));
        res += "\n" + failureReason;
      }
    }
    jxcore.utils.continue();
  });
  jxcore.utils.jump();

  return res;
};

var logArray = {};
var grabLogcat = function (class_name, deviceId, deviceName, cb) {
  this.deviceId = deviceId;
  this.class_name = class_name;
  this.deviceName = deviceName;
  this.cb = cb;
  var _this = this;

  this.run = function () {
    var child = spawn('adb', ['-s', _this.deviceId, 'logcat', '-v', 'threadtime'], eopts);
    for (var i = 0; i < arrDevices.length; i++) {
      if (arrDevices[i].deviceId == _this.deviceId) {
        arrDevices[i].child = child;
        break;
      }
    }

    _this.child = child;
    _this.child.deviceId = _this.deviceId;
    logArray[_this.deviceName] = [];

    var firstLog = true;
    child.stdout.on('data', function (data) {
      if (firstLog) {
        firstLog = false;
        // logcat in place run app
        _this.cb(null, _this);
      }
      data = data + "";

      if (data.indexOf("****TEST_LOGGER:[PROCESS_ON_EXIT") >= 0) {
        if (data.indexOf("****TEST_LOGGER:[PROCESS_ON_EXIT_FAILED]****") >= 0)
          _this.child.failed = true;

        logme("STOP log received from ", _this.deviceId, "Test has ", _this.child.failed ? "FAILED" : "SUCCEEDED");
        _this.killing = true;
        stopAndroidApp(_this.class_name, _this.deviceId, function () {
          _this.child.kill();
        });
      }

      logArray[_this.deviceName].push(data);
    });

    child.stderr.on('data', function (data) {
      if (firstLog) {
        firstLog = false;
        // logcat in place run app
        _this.cb(null, _this);
      }

      logArray[_this.deviceName].push(data + "");
    });

    child.on('exit', function (code) {
      if (_this.killing) {
        logme("Device test finished on", _this.deviceId, "");
      } else {
        _this.child.failed = true;
        _this.cb("Unexpected exit on device " + _this.deviceId + " app:" + _this.class_name + " code:" + code);
        logme('child process exited with code ' + code, "on device", _this.deviceId, "");
      }
      process.emit('mobile_ready', _this.deviceId, _this.child.failed);
    });
  }
};

var logcatIndex = 0;
var runAndroidApp = function (class_name, deviceId, deviceName, cb) {
  // clear logcat cache
  sync('adb -s "' + deviceId + '" logcat -c');
  // !! this may not work on some devices so don't check the failure
  // CI restarts the devices on each run

  //listen logcat on parallel
  var lg = new grabLogcat(class_name, deviceId, deviceName, function (err, _this) {
    logcatIndex++;
    if (!err) {
      var cmd = 'adb -s "' + deviceId + '" shell am start -n ' + class_name + "/" + class_name + ".MainActivity";
      var res = sync(cmd);
      if (res.exitCode != 0) {
        var str = "\n" + res.out;
        if (str.length > 512) str = str.substr(0, 512);
        logme("Error: problem running Android apk(" + class_name + ") on device " + deviceName, str, "");
        if (_this) _this.child.kill();
        cb(true, null);
        return false;
      }

      cb(null);
    } else {
      cb(err)
    }
  });

  lg.run();
};

var runAndroidInstrumentationTests = function (class_name, runner, deviceId, deviceName) {
  var cmd = 'adb -s "' + deviceId + '" shell am instrument -w ' + class_name + "/" + runner;
  var res = sync(cmd);
  if (res.exitCode != 0 || res.out.indexOf("FAILURES!!!") > -1) {
    testFailed = true;
    var str = "\n" + res.out;
    if (str.length > 512) {
      str = str.substr(0, 512);
    }
    logme("Error: problem running Android instrumentation tests (" + class_name + ") on device " + deviceName, str, "");
  }
  logArray[deviceName] = [res.out + ""];
};

var uninstallApp = function (class_name, device_name) {
  var cmd = 'sleep 1;adb -s "' + device_name + '" uninstall ' + class_name;
  var res = sync(cmd);
  if (res.exitCode != 0) {
    logme("Error: problem stopping Android apk(" + class_name + ") to device " + device_name, res.out, "");
    return false;
  }

  return true;
};

var stopAndroidApp = function (class_name, device_name, cb) {
  var cmd = 'adb -s "'+device_name+'" shell pm uninstall '+class_name
    + ';adb -s "' + device_name + '" reboot';

  if (cb) {
    exec(cmd, eopts, cb);
  } else {
    sync(cmd);

    return true;
  }
};

process.on('SIGTERM', function(){
  if(process.logsOnDisk) return;
  try {
    fs.writeFileSync(path.join(__dirname, "../../result_.json"), JSON.stringify(logArray));
  } catch(e) {
    logme("Could not write logs. Error:", e + "");
  }
  process.exit(1);
});

process.on('mobile_ready', function (deviceId, failed) {
  for (var i = 0; i < arrDevices.length; i++) {
    if (arrDevices[i].deviceId == deviceId) {
      arrDevices[i].finished = true;
      arrDevices[i].failed = failed;
      if (failed)
        testFailed = true;
      break;
    }
  }
  appCounter++;
  if (appCounter < arrDevices.length) return;
  appCounter = 0;

  process.logsOnDisk = true;
  try {
    fs.writeFileSync(path.join(__dirname, "../../result_.json"), JSON.stringify(logArray));
  } catch(e) {
    logme("Could not write logs. Error:", e + "");
  }
  logme("Android task is completed.", testFailed ? "[FAILED]" : "[SUCCESS]");
  for (var i = 0; i < arrDevices.length; i++) {
    stopAndroidApp(job.config.csname.android, arrDevices[i].deviceId);
  }
  process.exit(testFailed ? 1 : 0);
});

// remove apps
for (var i = 0; i < arrDevices.length; i++) {
  stopAndroidApp(job.config.csname.android, arrDevices[i].deviceId);
  uninstallApp(job.config.csname.android, arrDevices[i].deviceId);
}

var isDeviceBooted = function (device_name, timeout) {
  var result = false;
  setTimeout(function () {
    var cmd = 'adb -s ' + device_name + ' shell getprop sys.boot_completed';
    var res = sync(cmd);
    result = res.exitCode === 0 && res.out.indexOf('1') === 0;
    jxcore.utils.continue();
  }, timeout);
  jxcore.utils.pause();
  return result;
}

//ensure all devices are up and running
var devicesReady = true;
for (var i = 0; i < arrDevices.length; i++) {
  var bootCheckCount = 0;
  var bootCheckTimeout = 0;
  while (bootCheckCount < 3 && !isDeviceBooted(arrDevices[i].deviceId, bootCheckTimeout)) {
    bootCheckCount += 1;
    bootCheckTimeout = 15000;  // wait 15 seconds before next try
  }
  if (bootCheckCount === 3) {
    devicesReady = false;
    break;
  }
}
if (!devicesReady) {
  logme("\n\nDevices on this node are not ready.\n",
        "Cancelling the test result on this node.\n");
  if (job.config.serverScript && job.config.serverScript.length) {
    jxcore.utils.cmdSync("curl 192.168.1.150:8060/cancel=1");
  }
  process.exit(0);
}

var retry_count=0;
// deploy apps
for (var i = 0; i < arrDevices.length; i++) {
  var res = deployAndroid(builds + "/android_" + nodeId + "_" + job.uqID + ".apk", arrDevices[i].deviceId, job.config.csname.android);
  if (res && retry_count < 2) {
    retry_count++;
    i--;
    continue;
  }
  if (res) {
    logme("\n\nTest on this node has failed but the reason wasn't the test application itself.\n",
      "Cancelling the test result on this node.\n", res);

    if (job.config.serverScript && job.config.serverScript.length)
      jxcore.utils.cmdSync("curl 192.168.1.150:8060/cancel=1");

    process.exit(0);
  }
  retry_count = 0;
}

var callback = function (err) {
  if (err) {
    logme("Error!", err, "");
  }
};

if (job.config.serverScript && job.config.serverScript.length)
  jxcore.utils.cmdSync("curl 192.168.1.150:8060/droid=" + arrDevices.length);

if (job.config.instrumentationTestRunner) {
  //run junit tests
  for (var i = 0; i < arrDevices.length; i++) {
    runAndroidInstrumentationTests(job.config.csname.android, job.config.instrumentationTestRunner, arrDevices[i].deviceId, arrDevices[i].deviceName);
  }

  try {
    fs.writeFileSync(path.join(__dirname, "../../result_.json"), JSON.stringify(logArray));
  } catch(e) {
    logme("Could not write logs. Error:", e + "");
  }

  logme("Android instrumentation tests task is completed.", testFailed ? "[FAILED]" : "[SUCCESS]");
  for (var i = 0; i < arrDevices.length; i++) {
    stopAndroidApp(job.config.csname.android, arrDevices[i].deviceId);
  }
  process.exit(testFailed ? 1 : 0);

} else {
  for (var i = 0; i < arrDevices.length; i++) {
    runAndroidApp(job.config.csname.android, arrDevices[i].deviceId, arrDevices[i].deviceName, callback);
  }
}

function timeoutKill() {
  // shut down the test;
  logme("TIMEOUT REACHED. KILLING the APPS");
  for (var i = 0; i < arrDevices.length; i++) {
    var dev = arrDevices[i];
    if (dev.finished) continue;

    logArray[dev.deviceName].push("TIME-OUT KILL (timeout was " + timeout + "ms)");
    stopAndroidApp(job.config.csname.android, dev.deviceId);
    if (dev.child) {
      dev.child.kill();
    }
  }
  setTimeout(function(){
    process.emit("SIGTERM");
  }, 1000);
}

// set timeout
var timeout = job.config.timeout ? job.config.timeout * 1000 : 300000;
var logcatCounter = 0;
var interTimer = setInterval(function(){
  if (logcatIndex == arrDevices.length) {
    clearInterval(interTimer);
    setTimeout(function () {
      timeoutKill();
    }, timeout + 10000);
  } else {
    if (logcatCounter > 90) {
      timeoutKill();
    }
  }
}, 1000);<commit_msg>Changed test run to asynchronous<commit_after>require('../logger').toFile("../../console.json");
var fs = require('fs');
var path = require('path');
var exec = require('child_process').exec;
var sync = jxcore.utils.cmdSync;
var spawn = require('child_process').spawn;
var eopts = {
  encoding: 'utf8',
  timeout: 0,
  maxBuffer: 1e8,
  killSignal: 'SIGTERM'
};

if (!process.argv[2]) {
  logme("Needs argument!");
  process.exit(1);
  return;
}

var job = JSON.parse(new Buffer(process.argv[2], "base64") + "");
var nodeId = 0;

// out: [ [ '8a09fc3c', 'device' ] ]
var getAndroidDevices = function () {
  var res = sync("adb devices");

  if (res.exitCode != 0) {
    logme("Error: getAndroidDevices failed", res.out);
    process.exit(1);
    return;
  }

  var devs = [];
  res = res.out.split('\n');
  if (res[0].indexOf("List of devices") == 0) {
    for (var i = 1; i < res.length; i++) {
      if (res[i].trim().length == 0) continue;
      if (res[i].indexOf('offline') > 0 ||
          res[i].indexOf('unauthorized') > 0 ||
          res[i].indexOf('no permissions') > 0) {
        logme("Warning: Phone " + res[i] + " - CANNOT BE USED");
        continue; // phone offline/unauthorized/no debug permissions
      }
      var dev = res[i].split('\t');
      devs.push(dev);
    }
  }

  if (devs.length == 0) {
    logme("Error: No Android device found.", "");
    process.exit(1)
    return;
  }

  var arr = [];
  for (var i = 0; i < devs.length; i++) {
    var man = sync("adb -s " + devs[i][0] + " shell getprop ro.product.manufacturer");
    var pro = sync("adb -s " + devs[i][0] + " shell getprop ro.product.model")
    arr.push({
      deviceId: devs[i][0],
      deviceName: man.out.replace("\n", "").trim() + "-" + pro.out.replace("\n", "").trim()
    })
  }

  return arr;
};

var arrDevices = getAndroidDevices();
var builds = path.join(__dirname, "../builder/builds/" + job.uqID + "/build_android");
var appCounter = 0;
var testFailed = false;

var deployAndroid = function (apk_path, device_name, class_name) {
  var cmd = 'adb -s ' + device_name + ' install -r ' + apk_path + ';adb -s ' + device_name + ' shell pm list packages';
  var res = null;
  var failureReasonIndex = -1;
  var failureReason = "";
  exec(cmd, eopts, function (err, stdout, stderr) {
    if (err || stdout.indexOf(class_name) < 0) {
      res = ("Error: problem deploying Android apk(" + apk_path + ") to device " + device_name + (err ? ("\n" + err) : ""));
      failureReasonIndex = stdout.indexOf('Failure');
      if (failureReasonIndex > -1) {
        failureReason = stdout.substring(failureReasonIndex);
        failureReason = failureReason.substring(0, failureReason.indexOf('\n'));
        res += "\n" + failureReason;
      }
    }
    jxcore.utils.continue();
  });
  jxcore.utils.jump();

  return res;
};

var logArray = {};
var grabLogcat = function (class_name, deviceId, deviceName, cb) {
  this.deviceId = deviceId;
  this.class_name = class_name;
  this.deviceName = deviceName;
  this.cb = cb;
  var _this = this;

  this.run = function () {
    var child = spawn('adb', ['-s', _this.deviceId, 'logcat', '-v', 'threadtime'], eopts);
    for (var i = 0; i < arrDevices.length; i++) {
      if (arrDevices[i].deviceId == _this.deviceId) {
        arrDevices[i].child = child;
        break;
      }
    }

    _this.child = child;
    _this.child.deviceId = _this.deviceId;
    logArray[_this.deviceName] = [];

    var firstLog = true;
    child.stdout.on('data', function (data) {
      if (firstLog) {
        firstLog = false;
        // logcat in place run app
        _this.cb(null, _this);
      }
      data = data + "";

      if (data.indexOf("****TEST_LOGGER:[PROCESS_ON_EXIT") >= 0) {
        if (data.indexOf("****TEST_LOGGER:[PROCESS_ON_EXIT_FAILED]****") >= 0)
          _this.child.failed = true;

        logme("STOP log received from ", _this.deviceId, "Test has ", _this.child.failed ? "FAILED" : "SUCCEEDED");
        _this.killing = true;
        stopAndroidApp(_this.class_name, _this.deviceId, function () {
          _this.child.kill();
        });
      }

      logArray[_this.deviceName].push(data);
    });

    child.stderr.on('data', function (data) {
      if (firstLog) {
        firstLog = false;
        // logcat in place run app
        _this.cb(null, _this);
      }

      logArray[_this.deviceName].push(data + "");
    });

    child.on('exit', function (code) {
      if (_this.killing) {
        logme("Device test finished on", _this.deviceId, "");
      } else {
        _this.child.failed = true;
        _this.cb("Unexpected exit on device " + _this.deviceId + " app:" + _this.class_name + " code:" + code);
        logme('child process exited with code ' + code, "on device", _this.deviceId, "");
      }
      process.emit('mobile_ready', _this.deviceId, _this.child.failed);
    });
  }
};

var logcatIndex = 0;
var runAndroidApp = function (class_name, deviceId, deviceName, cb) {
  // clear logcat cache
  sync('adb -s "' + deviceId + '" logcat -c');
  // !! this may not work on some devices so don't check the failure
  // CI restarts the devices on each run

  //listen logcat on parallel
  var lg = new grabLogcat(class_name, deviceId, deviceName, function (err, _this) {
    logcatIndex++;
    if (!err) {
      var cmd = 'adb -s "' + deviceId + '" shell am start -n ' + class_name + "/" + class_name + ".MainActivity";
      var res = sync(cmd);
      if (res.exitCode != 0) {
        var str = "\n" + res.out;
        if (str.length > 512) str = str.substr(0, 512);
        logme("Error: problem running Android apk(" + class_name + ") on device " + deviceName, str, "");
        if (_this) _this.child.kill();
        cb(true, null);
        return false;
      }

      cb(null);
    } else {
      cb(err)
    }
  });

  lg.run();
};

var runAndroidInstrumentationTests = function (class_name, runner, deviceId, deviceName) {
  var cmd = 'adb -s "' + deviceId + '" shell am instrument -w ' + class_name + "/" + runner;
  exec(cmd, eopts, function (err, stdout, stderr) {
    if (err || stdout.indexOf("FAILURES!!!") > -1) {
      testFailed = true;
      arrDevices[i].failed = failed;
      logme("Error: problem running Android instrumentation tests (" + class_name + ") on device " + deviceName, "");
    }
    logArray[deviceName] = [stdout, stderr];
    arrDevices[i].finished = true;
    appCounter++;

    if (appCounter === arrDevices.length) {
      process.logsOnDisk = true;
      try {
        fs.writeFileSync(path.join(__dirname, "../../result_.json"), JSON.stringify(logArray));
      } catch(e) {
        logme("Could not write logs. Error:", e + "");
      }

      logme("Android instrumentation tests task is completed.", testFailed ? "[FAILED]" : "[SUCCESS]");
      for (var i = 0; i < arrDevices.length; i++) {
        stopAndroidApp(job.config.csname.android, arrDevices[i].deviceId);
      }
      process.exit(testFailed ? 1 : 0);
    }
  });
  logcatIndex++;
};

var uninstallApp = function (class_name, device_name) {
  var cmd = 'sleep 1;adb -s "' + device_name + '" uninstall ' + class_name;
  var res = sync(cmd);
  if (res.exitCode != 0) {
    logme("Error: problem stopping Android apk(" + class_name + ") to device " + device_name, res.out, "");
    return false;
  }

  return true;
};

var stopAndroidApp = function (class_name, device_name, cb) {
  var cmd = 'adb -s "'+device_name+'" shell pm uninstall '+class_name
    + ';adb -s "' + device_name + '" reboot';

  if (cb) {
    exec(cmd, eopts, cb);
  } else {
    sync(cmd);

    return true;
  }
};

process.on('SIGTERM', function(){
  if(process.logsOnDisk) return;
  try {
    fs.writeFileSync(path.join(__dirname, "../../result_.json"), JSON.stringify(logArray));
  } catch(e) {
    logme("Could not write logs. Error:", e + "");
  }
  process.exit(1);
});

process.on('mobile_ready', function (deviceId, failed) {
  for (var i = 0; i < arrDevices.length; i++) {
    if (arrDevices[i].deviceId == deviceId) {
      arrDevices[i].finished = true;
      arrDevices[i].failed = failed;
      if (failed)
        testFailed = true;
      break;
    }
  }
  appCounter++;
  if (appCounter < arrDevices.length) return;
  appCounter = 0;

  process.logsOnDisk = true;
  try {
    fs.writeFileSync(path.join(__dirname, "../../result_.json"), JSON.stringify(logArray));
  } catch(e) {
    logme("Could not write logs. Error:", e + "");
  }
  logme("Android task is completed.", testFailed ? "[FAILED]" : "[SUCCESS]");
  for (var i = 0; i < arrDevices.length; i++) {
    stopAndroidApp(job.config.csname.android, arrDevices[i].deviceId);
  }
  process.exit(testFailed ? 1 : 0);
});

// remove apps
for (var i = 0; i < arrDevices.length; i++) {
  stopAndroidApp(job.config.csname.android, arrDevices[i].deviceId);
  uninstallApp(job.config.csname.android, arrDevices[i].deviceId);
}

var isDeviceBooted = function (device_name, timeout) {
  var result = false;
  setTimeout(function () {
    var cmd = 'adb -s ' + device_name + ' shell getprop sys.boot_completed';
    var res = sync(cmd);
    result = res.exitCode === 0 && res.out.indexOf('1') === 0;
    jxcore.utils.continue();
  }, timeout);
  jxcore.utils.pause();
  return result;
}

//ensure all devices are up and running
var devicesReady = true;
for (var i = 0; i < arrDevices.length; i++) {
  var bootCheckCount = 0;
  var bootCheckTimeout = 0;
  while (bootCheckCount < 3 && !isDeviceBooted(arrDevices[i].deviceId, bootCheckTimeout)) {
    bootCheckCount += 1;
    bootCheckTimeout = 15000;  // wait 15 seconds before next try
  }
  if (bootCheckCount === 3) {
    devicesReady = false;
    break;
  }
}
if (!devicesReady) {
  logme("\n\nDevices on this node are not ready.\n",
        "Cancelling the test result on this node.\n");
  if (job.config.serverScript && job.config.serverScript.length) {
    jxcore.utils.cmdSync("curl 192.168.1.150:8060/cancel=1");
  }
  process.exit(0);
}

var retry_count=0;
// deploy apps
for (var i = 0; i < arrDevices.length; i++) {
  var res = deployAndroid(builds + "/android_" + nodeId + "_" + job.uqID + ".apk", arrDevices[i].deviceId, job.config.csname.android);
  if (res && retry_count < 2) {
    retry_count++;
    i--;
    continue;
  }
  if (res) {
    logme("\n\nTest on this node has failed but the reason wasn't the test application itself.\n",
      "Cancelling the test result on this node.\n", res);

    if (job.config.serverScript && job.config.serverScript.length)
      jxcore.utils.cmdSync("curl 192.168.1.150:8060/cancel=1");

    process.exit(0);
  }
  retry_count = 0;
}

var callback = function (err) {
  if (err) {
    logme("Error!", err, "");
  }
};

if (job.config.serverScript && job.config.serverScript.length)
  jxcore.utils.cmdSync("curl 192.168.1.150:8060/droid=" + arrDevices.length);

for (var i = 0; i < arrDevices.length; i++) {
  if (job.config.instrumentationTestRunner) {
    runAndroidInstrumentationTests(job.config.csname.android, job.config.instrumentationTestRunner, arrDevices[i].deviceId, arrDevices[i].deviceName);
  } else {
    runAndroidApp(job.config.csname.android, arrDevices[i].deviceId, arrDevices[i].deviceName, callback);
  }
}

function timeoutKill() {
  // shut down the test;
  logme("TIMEOUT REACHED. KILLING the APPS");
  for (var i = 0; i < arrDevices.length; i++) {
    var dev = arrDevices[i];
    if (dev.finished) continue;

    logArray[dev.deviceName].push("TIME-OUT KILL (timeout was " + timeout + "ms)");
    stopAndroidApp(job.config.csname.android, dev.deviceId);
    if (dev.child) {
      dev.child.kill();
    }
  }
  setTimeout(function(){
    process.emit("SIGTERM");
  }, 1000);
}

// set timeout
var timeout = job.config.timeout ? job.config.timeout * 1000 : 300000;
var logcatCounter = 0;
var interTimer = setInterval(function(){
  if (logcatIndex == arrDevices.length) {
    clearInterval(interTimer);
    setTimeout(function () {
      timeoutKill();
    }, timeout + 10000);
  } else {
    if (logcatCounter > 90) {
      timeoutKill();
    }
  }
  logcatCounter++;
}, 1000);<|endoftext|>


<commit_before>"use strict";

var _ = require('lodash');

class Relay {
  constructor(incomingObject, outgoingObject) {
    this.incoming = incomingObject;
    this.outgoing = outgoingObject;
  }

  static looseEqual(obj1, obj2) {
    if (obj2 === undefined || obj1 === undefined) return false;
    var keys = Object.keys(obj1);

    for (let key of keys) {
      if (_.isObject(obj1[key])){
        if (!Relay.looseEqual(obj1[key], obj2[key])) return false;
        continue;
      }

      if (obj1[key] === 'RAW') continue;
      if (obj2[key] !== obj1[key]) return false;
    }

    return true;
  }
}

/**
 * Sequence
 *
 * the main datastructure for a seqence is the chain.
 * The chain is an array of arrays that contain the expected payload when
 * triggered. When the expected payload is received, it will emit the proper
 * event then move the chains cursor to the next set of expected events.
 * If the proper payload is not recieved, the chain will store it, and not move
 * the cursor to the next event step.
 */
class Sequence {
  /**
   * validPayload
   *
   * @param {object} payload - is the payload that was just recieved by the system
   * @param {object} incoming - is the payload that was modeled in the system.
   * @return {array}
   */
  static validPayload(payload, incoming) {
    var keys = Object.keys(incoming);

    for (let key of keys) {
      if (payload[key] === undefined) {
        return false;
      }
    }

    return true;
  }

  /**
   * reset the sequence head t
   *
   */
  reset() {
    this.head = 0;
  }

  constructor() {
    this.chain = [];
    this.head = 0; // head must be within the bounds of the array;

    // turns true when the previous relay call made the head variable goto 0
    this.looped = false;
  }

  _nextHead() {
    if (this.head + 1 >= this.chain.length) {
      this.looped = true;
      this.head = 0;
      return this.head;
    } else {
      this.looped = false;
      this.head++;
      return this.head;
    }
  }

  /**
   * Adds the relay list to the end of the chain
   *
   * @param {array} relays - an array of relay objects
   * @return {Sequence}
   */
  addRelay(relays) {
    this.chain.push(relays);
    return this;
  }

  /**
   * Returns an array of the payloads that need to be emitted next in the sequence
   *
   * @param {object} payload - The incoming object that will continue the sequence
   * @return {array} - If given the correct payload, will return a list of objects for the outgoing seqence.
   */
  relay(payload) {
    var parent = this;
    var activeRelays = _.filter(this.chain[this.head], function (rel) {
      return Relay.looseEqual(rel.incoming, payload);
    });

    if (activeRelays.length !== 0) {
      this._nextHead();

      return _.map(activeRelays, function (relay) {
        return relay.outgoing;
      });
    }
    return [];
  }
}

// need to figureout how to run the sequences in parallel
class DeviceOrchestrator {
  constructor(options) {
    this.io = options.io;
    this.events = {};
    this.sequences = {};

    // The home of the hooks
    this.brh = [];
    this.arh = [];
    this.bbh = [];
    this.abh = [];

    // contains the
    this.activeSequences = {};
  }

  defineSequence(name) {
    this.sequences[name] = new Sequence();
    this.as = name;
    return this.sequences[name];
  }

  trip(seqName) {
    console.log(`Tripping the next sequence ${seqName}`);
    var self = this;
    self.activeSequences[seqName] = self.sequences[seqName];
  }

  /**
   * Attach a custom listener to the Event System.
   */
  on(event, fn) {
    this.events[event] = fn;
  }

  /**
   * Broadcast a message to all sockets attached to the socket io evt system
   */
  emit() {
    var args = [].splice.call(arguments, 0);
    this.io.emit.apply(this.io, args);
  }

  /**
   * Takes a list of seqences that must be joined
   */
  listen(seqOrder, head) {
    var self = this;
    self.seqenceOrder = seqOrder;
    self.headName = headName;

    self.activeSequences[seqOrder[0]] = self.sequences[seqOrder[0]];

    self.io.on('connection', function (socket) {

      // attaching the events
      _.each(self.events, function (fn, eventName) {
        socket.on(eventName, fn.bind(socket));
      });

      socket.on('dev:trigger', function (ipl) {

        var cont = self._beforeRelayHooks(ipl);
        if (!cont) return;

        // TODO: make this return outgoing paloads for all active sequences
        var outgoingPayloads =  _.map(self._activeSequences(), (seq) => {
          console.log(seq);
          return seq.relay(ipl);
        });

        outgoingPayloads = _.flatten(outgoingPayloads);
        console.log("outgoing payloads", outgoingPayloads);

        cont = self._afterRelayHooks(ipl);
        if (!cont) return;

        _.each(outgoingPayloads, function (opl) {

          var cont = self._beforeBroadcastHooks(opl);
          if (!cont) return;

          socket.broadcast.emit('dev:command', opl);

          cont = self._afterBroadcastHooks(opl);
          if (!cont) return;
        });
      });
    });
  }

  // Where other programs can define extra functionality to the
  // RelayNet
  beforeRelayHook     (fn) { this._addHook('brh', fn); }
  afterRelayHook      (fn) { this._addHook('arh', fn); }
  beforeBroadcastHook (fn) { this._addHook('bbh', fn); }
  afterBroadcastHook  (fn) { this._addHook('abh', fn); }

  /*********** PRIVATE METHODS ************/
  /**
   * Returns a list of the sequences that have been tripped
   */
  _activeSequences() {
    return _.map(this.activeSequences, (seq, name) => {
      console.log(name, seq);
      return seq;
    });
  }

  _addHook(lst, fn) {
    this[lst].push(fn);
  }

  _runHooks(map, payload) {
    var ogSize = this[map].length;
    var outcome = _.filter(this[map], function (fn) {
      return fn(payload);
    });

    if (ogSize != outcome.length) return false;
    return true;
  }

  _beforeRelayHooks     (payload) { return this._runHooks("brh", payload); }
  _afterRelayHooks      (payload) { return this._runHooks("arh", payload); }
  _beforeBroadcastHooks (payload) { return this._runHooks("bbh", payload); }
  _afterBroadcastHooks  (payload) { return this._runHooks("abh", payload); }
}

module.exports = {
  DeviceOrchestrator,
  Sequence,
  Relay
};
<commit_msg>fixed some typo  :tada:<commit_after>"use strict";

var _ = require('lodash');

class Relay {
  constructor(incomingObject, outgoingObject) {
    this.incoming = incomingObject;
    this.outgoing = outgoingObject;
  }

  static looseEqual(obj1, obj2) {
    if (obj2 === undefined || obj1 === undefined) return false;
    var keys = Object.keys(obj1);

    for (let key of keys) {
      if (_.isObject(obj1[key])){
        if (!Relay.looseEqual(obj1[key], obj2[key])) return false;
        continue;
      }

      if (obj1[key] === 'RAW') continue;
      if (obj2[key] !== obj1[key]) return false;
    }

    return true;
  }
}

/**
 * Sequence
 *
 * the main datastructure for a seqence is the chain.
 * The chain is an array of arrays that contain the expected payload when
 * triggered. When the expected payload is received, it will emit the proper
 * event then move the chains cursor to the next set of expected events.
 * If the proper payload is not recieved, the chain will store it, and not move
 * the cursor to the next event step.
 */
class Sequence {
  /**
   * validPayload
   *
   * @param {object} payload - is the payload that was just recieved by the system
   * @param {object} incoming - is the payload that was modeled in the system.
   * @return {array}
   */
  static validPayload(payload, incoming) {
    var keys = Object.keys(incoming);

    for (let key of keys) {
      if (payload[key] === undefined) {
        return false;
      }
    }

    return true;
  }

  /**
   * reset the sequence head t
   *
   */
  reset() {
    this.head = 0;
  }

  constructor() {
    this.chain = [];
    this.head = 0; // head must be within the bounds of the array;

    // turns true when the previous relay call made the head variable goto 0
    this.looped = false;
  }

  _nextHead() {
    if (this.head + 1 >= this.chain.length) {
      this.looped = true;
      this.head = 0;
      return this.head;
    } else {
      this.looped = false;
      this.head++;
      return this.head;
    }
  }

  /**
   * Adds the relay list to the end of the chain
   *
   * @param {array} relays - an array of relay objects
   * @return {Sequence}
   */
  addRelay(relays) {
    this.chain.push(relays);
    return this;
  }

  /**
   * Returns an array of the payloads that need to be emitted next in the sequence
   *
   * @param {object} payload - The incoming object that will continue the sequence
   * @return {array} - If given the correct payload, will return a list of objects for the outgoing seqence.
   */
  relay(payload) {
    var parent = this;
    var activeRelays = _.filter(this.chain[this.head], function (rel) {
      return Relay.looseEqual(rel.incoming, payload);
    });

    if (activeRelays.length !== 0) {
      this._nextHead();

      return _.map(activeRelays, function (relay) {
        return relay.outgoing;
      });
    }
    return [];
  }
}

// need to figureout how to run the sequences in parallel
class DeviceOrchestrator {
  constructor(options) {
    this.io = options.io;
    this.events = {};
    this.sequences = {};

    // The home of the hooks
    this.brh = [];
    this.arh = [];
    this.bbh = [];
    this.abh = [];

    // contains the
    this.activeSequences = {};
  }

  defineSequence(name) {
    this.sequences[name] = new Sequence();
    this.as = name;
    return this.sequences[name];
  }

  trip(seqName) {
    console.log(`Tripping the next sequence ${seqName}`);
    var self = this;
    self.activeSequences[seqName] = self.sequences[seqName];
  }

  /**
   * Attach a custom listener to the Event System.
   */
  on(event, fn) {
    this.events[event] = fn;
  }

  /**
   * Broadcast a message to all sockets attached to the socket io evt system
   */
  emit() {
    var args = [].splice.call(arguments, 0);
    this.io.emit.apply(this.io, args);
  }

  /**
   * Takes a list of seqences that must be joined
   */
  listen(seqOrder, head) {
    var self = this;
    self.seqenceOrder = seqOrder;
    self.headName = head;

    self.activeSequences[seqOrder[0]] = self.sequences[seqOrder[0]];

    self.io.on('connection', function (socket) {

      // attaching the events
      _.each(self.events, function (fn, eventName) {
        socket.on(eventName, fn.bind(socket));
      });

      socket.on('dev:trigger', function (ipl) {

        var cont = self._beforeRelayHooks(ipl);
        if (!cont) return;

        // TODO: make this return outgoing paloads for all active sequences
        var outgoingPayloads =  _.map(self._activeSequences(), (seq) => {
          console.log(seq);
          return seq.relay(ipl);
        });

        outgoingPayloads = _.flatten(outgoingPayloads);
        console.log("outgoing payloads", outgoingPayloads);

        cont = self._afterRelayHooks(ipl);
        if (!cont) return;

        _.each(outgoingPayloads, function (opl) {

          var cont = self._beforeBroadcastHooks(opl);
          if (!cont) return;

          socket.broadcast.emit('dev:command', opl);

          cont = self._afterBroadcastHooks(opl);
          if (!cont) return;
        });
      });
    });
  }

  // Where other programs can define extra functionality to the
  // RelayNet
  beforeRelayHook     (fn) { this._addHook('brh', fn); }
  afterRelayHook      (fn) { this._addHook('arh', fn); }
  beforeBroadcastHook (fn) { this._addHook('bbh', fn); }
  afterBroadcastHook  (fn) { this._addHook('abh', fn); }

  /*********** PRIVATE METHODS ************/
  /**
   * Returns a list of the sequences that have been tripped
   */
  _activeSequences() {
    return _.map(this.activeSequences, (seq, name) => {
      console.log(name, seq);
      return seq;
    });
  }

  _addHook(lst, fn) {
    this[lst].push(fn);
  }

  _runHooks(map, payload) {
    var ogSize = this[map].length;
    var outcome = _.filter(this[map], function (fn) {
      return fn(payload);
    });

    if (ogSize != outcome.length) return false;
    return true;
  }

  _beforeRelayHooks     (payload) { return this._runHooks("brh", payload); }
  _afterRelayHooks      (payload) { return this._runHooks("arh", payload); }
  _beforeBroadcastHooks (payload) { return this._runHooks("bbh", payload); }
  _afterBroadcastHooks  (payload) { return this._runHooks("abh", payload); }
}

module.exports = {
  DeviceOrchestrator,
  Sequence,
  Relay
};
<|endoftext|>


<commit_before>var fs = require('fs');

var read_all_tests = function(directory) {
  return fs.readdirSync(directory).filter(function(element, index, array) {
    return element.indexOf(".js") != -1;
  }).map(function(element) {
    return directory + "/" + element;
  })
}

var run_test = function(url, file, number_of_times, warm_up_iterations, concurrent, concurrent_batch_size, test_name, callback) {
  var final_results = {};
  var number_of_test_to_run = 0;
  // If we have not test_name set up
  if(typeof test_name == 'function') {
    callback = test_name;
    test_name = null;
  }
  // Ensure the batch size is correct
  if(concurrent && (number_of_times % concurrent_batch_size > 0)) return callback("Number of iterations must be divisible by batch size");

  // Load the file
  var _module = require(file);
  // No test set run all the available tests and store the results
  if(test_name == null) {
    // Execute the setup
    var keys = Object.keys(_module);
    var number_of_tests = keys.length;
    // Go over all the tests
    for(var i = 0; i < keys.length; i++) {
      // Wrap the scope so we can execute it by itself
      new function(_key, _module_func) {
        run_single_test(url, _key, _module_func, number_of_times, warm_up_iterations, concurrent, concurrent_batch_size, function(err, results) {
          // Final results
          final_results[_key] = { results: results};
          if(err) { final_results[_key].err = err; }
          // Adjust number of tests left to run
          number_of_tests = number_of_tests - 1;
          if(number_of_tests == 0) callback(null, final_results)
        });
      }(keys[i], _module[keys[i]])
    }
  } else {

  }
}

var run_single_test = function(url, func_name, func, number_of_times, warm_up_iterations, concurrent, concurrent_batch_size, callback) {
  var results = [];
  var test = func(url)();

  // Run the setup part first
  test.setup(function(err, setup_result) {
    if(err) return callback(err, null);

    if(!concurrent) {
      // Ensure we run warm up
      number_of_times = number_of_times + warm_up_iterations;
      // Execute the test
      exceute_test_serially(func_name, test, number_of_times, results, function(err, test_results) {
        test.teardown(function(_t_err, _t_result) {
          if(_t_err) return callback(_t_err, null);
          callback(err, test_results);
        })
      });
    } else {
      // Number of batches to run
      var number_of_batches = number_of_times / concurrent_batch_size;
      // Number of items in each batch
      var number_left_to_run = number_of_batches;

      // Warm up the JIT
      exceute_test_serially(func_name, test, warm_up_iterations, [], function(err, test_results) {
        if(err) return callback(err, null);

        // Iterate over the number of batches
        execute_test_batches(func_name, test, concurrent_batch_size, number_of_batches, results, function(err, test_results) {
          test.teardown(function(_t_err, _t_result) {
            if(_t_err) return callback(_t_err, null);
            callback(err, test_results);
          });
        });
      });
    }
  })
}

var execute_test_batches = function(func_name, test, number_left_to_run_batch, number_of_batches, results, callback) {
  var number_left_to_run = number_left_to_run_batch;

  if(number_of_batches == -1) return callback(null, results);

  for(var i = 0; i < number_left_to_run_batch; i++) {
    new function() {
      // Set start function
      var start = new Date();

      // Execute function
      test.test(function(err, result) {
        var end = new Date();
        var time = end.getTime() - start.getTime();
        results.push({start: start, end: end, time: time});

        // Adjust the number of tests to run
        number_left_to_run = number_left_to_run - 1;
        // Callback
        if(number_left_to_run == 0) {
          process.nextTick(function() {
            execute_test_batches(func_name, test, number_left_to_run_batch, number_of_batches - 1, results, callback);
          });
        }
      });
    }();
  }
}

var exceute_test_serially = function(func_name, test, number_of_times, results, callback) {
  // console.log("========================== exceute_test_serially :: " + number_of_times)
  if(number_of_times == 0) return callback(null, results);
  number_of_times = number_of_times - 1;
  // Set start function
  var start = new Date();
  // Execute function
  test.test(function(err, result) {
    var end = new Date();
    var time = end.getTime() - start.getTime();
    results.push({start: start, end: end, time: time});
    // Execute the next tick
    process.nextTick(function() {
      exceute_test_serially(func_name, test, number_of_times - 1, results, callback);
    })
  });
}

/**
 * Internal statistics object used for calculating average and standard devitation on
 * running queries
 * @ignore
 */
var RunningStats = function() {
  var self = this;
  this.m_n = 0;
  this.m_oldM = 0.0;
  this.m_oldS = 0.0;
  this.m_newM = 0.0;
  this.m_newS = 0.0;

  // Define getters
  Object.defineProperty(this, "numDataValues", { enumerable: true
    , get: function () { return this.m_n; }
  });

  Object.defineProperty(this, "mean", { enumerable: true
    , get: function () { return (this.m_n > 0) ? this.m_newM : 0.0; }
  });

  Object.defineProperty(this, "variance", { enumerable: true
    , get: function () { return ((this.m_n > 1) ? this.m_newS/(this.m_n - 1) : 0.0); }
  });

  Object.defineProperty(this, "standardDeviation", { enumerable: true
    , get: function () { return Math.sqrt(this.variance); }
  });

  Object.defineProperty(this, "sScore", { enumerable: true
    , get: function () {
      var bottom = this.mean + this.standardDeviation;
      if(bottom == 0) return 0;
      return ((2 * this.mean * this.standardDeviation)/(bottom));
    }
  });
}

/**
 * @ignore
 */
RunningStats.prototype.push = function(x) {
  // Update the number of samples
  this.m_n = this.m_n + 1;
  // See Knuth TAOCP vol 2, 3rd edition, page 232
  if(this.m_n == 1) {
    this.m_oldM = this.m_newM = x;
    this.m_oldS = 0.0;
  } else {
    this.m_newM = this.m_oldM + (x - this.m_oldM) / this.m_n;
    this.m_newS = this.m_oldS + (x - this.m_oldM) * (x - this.m_newM);

    // set up for next iteration
    this.m_oldM = this.m_newM;
    this.m_oldS = this.m_newS;
  }
}

exports.read_all_tests = read_all_tests;
exports.run_test = run_test;
exports.RunningStats = RunningStats;<commit_msg>Fixes for execution<commit_after>var fs = require('fs');

var read_all_tests = function(directory) {
  return fs.readdirSync(directory).filter(function(element, index, array) {
    return element.indexOf(".js") != -1;
  }).map(function(element) {
    return directory + "/" + element;
  })
}

var run_test = function(url, file, number_of_times, warm_up_iterations, concurrent, concurrent_batch_size, test_name, callback) {
  var final_results = {};
  var number_of_test_to_run = 0;
  // If we have not test_name set up
  if(typeof test_name == 'function') {
    callback = test_name;
    test_name = null;
  }
  // Ensure the batch size is correct
  if(concurrent && (number_of_times % concurrent_batch_size > 0)) return callback("Number of iterations must be divisible by batch size");

  // Load the file
  var _module = require(file);
  // No test set run all the available tests and store the results
  if(test_name == null) {
    // Execute the setup
    var keys = Object.keys(_module);
    var number_of_tests = keys.length;
    // Go over all the tests
    for(var i = 0; i < keys.length; i++) {
      // Wrap the scope so we can execute it by itself
      new function(_key, _module_func) {
        run_single_test(url, _key, _module_func, number_of_times, warm_up_iterations, concurrent, concurrent_batch_size, function(err, results) {
          // Final results
          final_results[_key] = { results: results};
          if(err) { final_results[_key].err = err; }
          // Adjust number of tests left to run
          number_of_tests = number_of_tests - 1;
          if(number_of_tests == 0) callback(null, final_results)
        });
      }(keys[i], _module[keys[i]])
    }
  } else {

  }
}

var run_single_test = function(url, func_name, func, number_of_times, warm_up_iterations, concurrent, concurrent_batch_size, callback) {
  var results = [];
  var test = func(url)();

  // Run the setup part first
  test.setup(function(err, setup_result) {
    if(err) return callback(err, null);

    if(!concurrent) {
      // Ensure we run warm up
      number_of_times = number_of_times + warm_up_iterations;
      // Execute the test
      exceute_test_serially(func_name, test, number_of_times, results, function(err, test_results) {
        test.teardown(function(_t_err, _t_result) {
          if(_t_err) return callback(_t_err, null);
          callback(err, test_results);
        })
      });
    } else {
      // Number of batches to run
      var number_of_batches = number_of_times / concurrent_batch_size;
      // Number of items in each batch
      var number_left_to_run = number_of_batches;

      // Warm up the JIT
      exceute_test_serially(func_name, test, warm_up_iterations, [], function(err, test_results) {
        if(err) return callback(err, null);

        // Iterate over the number of batches
        execute_test_batches(func_name, test, concurrent_batch_size, number_of_batches, results, function(err, test_results) {
          test.teardown(function(_t_err, _t_result) {
            if(_t_err) return callback(_t_err, null);
            callback(err, test_results);
          });
        });
      });
    }
  })
}

var execute_test_batches = function(func_name, test, number_left_to_run_batch, number_of_batches, results, callback) {
  var number_left_to_run = number_left_to_run_batch;

  if(number_of_batches == -1) return callback(null, results);

  for(var i = 0; i < number_left_to_run_batch; i++) {
    new function() {
      // Set start function
      var start = new Date();

      // Execute function
      test.test(function(err, result) {
        var end = new Date();
        var time = end.getTime() - start.getTime();
        results.push({start: start, end: end, time: time});

        // Adjust the number of tests to run
        number_left_to_run = number_left_to_run - 1;
        // Callback
        if(number_left_to_run == 0) {
          process.nextTick(function() {
            execute_test_batches(func_name, test, number_left_to_run_batch, number_of_batches - 1, results, callback);
          });
        }
      });
    }();
  }
}

var exceute_test_serially = function(func_name, test, number_of_times, results, callback) {
  if(number_of_times == 0) return callback(null, results);
  // Set start function
  var start = new Date();
  // Execute function
  test.test(function(err, result) {
    var end = new Date();
    var time = end.getTime() - start.getTime();
    results.push({start: start, end: end, time: time});
    // Execute the next tick
    process.nextTick(function() {
      exceute_test_serially(func_name, test, number_of_times - 1, results, callback);
    })
  });
}

/**
 * Internal statistics object used for calculating average and standard devitation on
 * running queries
 * @ignore
 */
var RunningStats = function() {
  var self = this;
  this.m_n = 0;
  this.m_oldM = 0.0;
  this.m_oldS = 0.0;
  this.m_newM = 0.0;
  this.m_newS = 0.0;

  // Define getters
  Object.defineProperty(this, "numDataValues", { enumerable: true
    , get: function () { return this.m_n; }
  });

  Object.defineProperty(this, "mean", { enumerable: true
    , get: function () { return (this.m_n > 0) ? this.m_newM : 0.0; }
  });

  Object.defineProperty(this, "variance", { enumerable: true
    , get: function () { return ((this.m_n > 1) ? this.m_newS/(this.m_n - 1) : 0.0); }
  });

  Object.defineProperty(this, "standardDeviation", { enumerable: true
    , get: function () { return Math.sqrt(this.variance); }
  });

  Object.defineProperty(this, "sScore", { enumerable: true
    , get: function () {
      var bottom = this.mean + this.standardDeviation;
      if(bottom == 0) return 0;
      return ((2 * this.mean * this.standardDeviation)/(bottom));
    }
  });
}

/**
 * @ignore
 */
RunningStats.prototype.push = function(x) {
  // Update the number of samples
  this.m_n = this.m_n + 1;
  // See Knuth TAOCP vol 2, 3rd edition, page 232
  if(this.m_n == 1) {
    this.m_oldM = this.m_newM = x;
    this.m_oldS = 0.0;
  } else {
    this.m_newM = this.m_oldM + (x - this.m_oldM) / this.m_n;
    this.m_newS = this.m_oldS + (x - this.m_oldM) * (x - this.m_newM);

    // set up for next iteration
    this.m_oldM = this.m_newM;
    this.m_oldS = this.m_newS;
  }
}

exports.read_all_tests = read_all_tests;
exports.run_test = run_test;
exports.RunningStats = RunningStats;<|endoftext|>


<commit_before>var sqlite3 = require("sqlite3");

exports.Driver = Driver;

function Driver(config, connection, opts) {
	this.config = config || {};
	this.opts   = opts || {};
	if (connection) {
		this.db = connection;
	} else {
		// on Windows, paths have a drive letter which is parsed by
		// url.parse() has the hostname. If host is defined, assume
		// it's the drive letter and add ":"
		this.db = new sqlite3.Database(((config.host ? config.host + ":" : "") + (config.pathname || "")) || ':memory:');
	}

	var escapes = {
		escape   : this.escape.bind(this),
		escapeId : this.escapeId.bind(this)
	};

	this.QuerySelect = new (require("../../sql/Select"))(escapes);
	this.QueryInsert = new (require("../../sql/Insert"))(escapes);
	this.QueryUpdate = new (require("../../sql/Update"))(escapes);
	this.QueryRemove = new (require("../../sql/Remove"))(escapes);
}

Driver.prototype.sync = function (opts, cb) {
	return require("../DDL/sqlite").sync(this, opts, cb);
};

Driver.prototype.drop = function (opts, cb) {
	return require("../DDL/sqlite").drop(this, opts, cb);
};

Driver.prototype.on = function (ev, cb) {
	if (ev == "error") {
		this.db.on("error", cb);
	}
	return this;
};

Driver.prototype.connect = function (cb) {
	process.nextTick(cb);
};

Driver.prototype.close = function (cb) {
	this.db.close();
	if (typeof cb == "function") process.nextTick(cb);
};

Driver.prototype.find = function (fields, table, conditions, opts, cb) {
	this.QuerySelect
		.clear()
		.fields(fields)
		.table(table);
	if (opts.fields) {
		this.QuerySelect.fields(opts.fields);
	}
	if (opts.merge) {
		this.QuerySelect.merge(opts.merge.from, opts.merge.to);
	}
	if (opts.offset) {
		this.QuerySelect.offset(opts.offset);
	}
	if (typeof opts.limit == "number") {
		this.QuerySelect.limit(opts.limit);
	} else if (opts.offset) {
		// OFFSET cannot be used without LIMIT so we use the biggest INTEGER number possible
		this.QuerySelect.limit('9223372036854775807');
	}
	if (opts.order) {
		this.QuerySelect.order(opts.order[0], opts.order[1]);
	}
	for (var k in conditions) {
		this.QuerySelect.where(k, conditions[k]);
	}

	if (this.opts.debug) {
		require("../../Debug").sql('sqlite', this.QuerySelect.build());
	}
	this.db.all(this.QuerySelect.build(), cb);
};

Driver.prototype.count = function (table, conditions, cb) {
	this.QuerySelect
		.clear()
		.count()
		.table(table);
	for (var k in conditions) {
		this.QuerySelect.where(k, conditions[k]);
	}

	if (this.opts.debug) {
		require("../../Debug").sql('sqlite', this.QuerySelect.build());
	}
	this.db.all(this.QuerySelect.build(), cb);
};

Driver.prototype.insert = function (table, data, cb) {
	this.QueryInsert
		.clear()
		.table(table);
	for (var k in data) {
		this.QueryInsert.set(k, data[k]);
	}

	if (this.opts.debug) {
		require("../../Debug").sql('sqlite', this.QueryInsert.build());
	}
	this.db.all(this.QueryInsert.build(), function (err, info) {
		if (err) {
			return cb(err);
		}
		this.db.get("SELECT last_insert_rowid() AS last_row_id", function (err, row) {
			if (err) {
				return cb(err);
			}
			return cb(null, {
				id: row.last_row_id
			});
		});
	}.bind(this));
};

Driver.prototype.update = function (table, changes, conditions, cb) {
	this.QueryUpdate
		.clear()
		.table(table);
	for (var k in conditions) {
		this.QueryUpdate.where(k, conditions[k]);
	}
	for (k in changes) {
		this.QueryUpdate.set(k, changes[k]);
	}

	if (this.opts.debug) {
		require("../../Debug").sql('sqlite', this.QueryUpdate.build());
	}
	this.db.all(this.QueryUpdate.build(), cb);
};

Driver.prototype.remove = function (table, conditions, cb) {
	this.QueryRemove
		.clear()
		.table(table);
	for (var k in conditions) {
		this.QueryRemove.where(k, conditions[k]);
	}

	if (this.opts.debug) {
		require("../../Debug").sql('sqlite', this.QueryRemove.build());
	}
	this.db.all(this.QueryRemove.build(), cb);
};

Driver.prototype.clear = function (table, cb) {
	var query = "DELETE FROM " + this.escapeId(table);

	if (this.opts.debug) {
		require("../../Debug").sql('sqlite', query);
	}
	this.db.all(query, cb);
};

Driver.prototype.escapeId = function (id) {
	var m = id.match(/^(.+)\.\*$/);
	if (m) {
		return '`' + m[1].replace(/\`/g, '``') + '`.*';
	}
	return '`' + id.replace(/\`/g, '``').replace(/\./g, '`.`') + '`';
};

Driver.prototype.escape = function (value) {
	if (Array.isArray(value)) {
		if (value.length === 1 && Array.isArray(value[0])) {
			return "(" + value[0].map(this.escape) + ")";
		}
		return "(" + value.map(this.escape) + ")";
	}
	switch (typeof value) {
		case "number":
			return value;
	}

	return "'" + value.replace(/\'/g, "''") + "'";
};
<commit_msg>Added boolean support for sqlite.<commit_after>var sqlite3 = require("sqlite3");

exports.Driver = Driver;

function Driver(config, connection, opts) {
	this.config = config || {};
	this.opts   = opts || {};
	if (connection) {
		this.db = connection;
	} else {
		// on Windows, paths have a drive letter which is parsed by
		// url.parse() has the hostname. If host is defined, assume
		// it's the drive letter and add ":"
		this.db = new sqlite3.Database(((config.host ? config.host + ":" : "") + (config.pathname || "")) || ':memory:');
	}

	var escapes = {
		escape   : this.escape.bind(this),
		escapeId : this.escapeId.bind(this)
	};

	this.QuerySelect = new (require("../../sql/Select"))(escapes);
	this.QueryInsert = new (require("../../sql/Insert"))(escapes);
	this.QueryUpdate = new (require("../../sql/Update"))(escapes);
	this.QueryRemove = new (require("../../sql/Remove"))(escapes);
}

Driver.prototype.sync = function (opts, cb) {
	return require("../DDL/sqlite").sync(this, opts, cb);
};

Driver.prototype.drop = function (opts, cb) {
	return require("../DDL/sqlite").drop(this, opts, cb);
};

Driver.prototype.on = function (ev, cb) {
	if (ev == "error") {
		this.db.on("error", cb);
	}
	return this;
};

Driver.prototype.connect = function (cb) {
	process.nextTick(cb);
};

Driver.prototype.close = function (cb) {
	this.db.close();
	if (typeof cb == "function") process.nextTick(cb);
};

Driver.prototype.find = function (fields, table, conditions, opts, cb) {
	this.QuerySelect
		.clear()
		.fields(fields)
		.table(table);
	if (opts.fields) {
		this.QuerySelect.fields(opts.fields);
	}
	if (opts.merge) {
		this.QuerySelect.merge(opts.merge.from, opts.merge.to);
	}
	if (opts.offset) {
		this.QuerySelect.offset(opts.offset);
	}
	if (typeof opts.limit == "number") {
		this.QuerySelect.limit(opts.limit);
	} else if (opts.offset) {
		// OFFSET cannot be used without LIMIT so we use the biggest INTEGER number possible
		this.QuerySelect.limit('9223372036854775807');
	}
	if (opts.order) {
		this.QuerySelect.order(opts.order[0], opts.order[1]);
	}
	for (var k in conditions) {
		this.QuerySelect.where(k, conditions[k]);
	}

	if (this.opts.debug) {
		require("../../Debug").sql('sqlite', this.QuerySelect.build());
	}
	this.db.all(this.QuerySelect.build(), cb);
};

Driver.prototype.count = function (table, conditions, cb) {
	this.QuerySelect
		.clear()
		.count()
		.table(table);
	for (var k in conditions) {
		this.QuerySelect.where(k, conditions[k]);
	}

	if (this.opts.debug) {
		require("../../Debug").sql('sqlite', this.QuerySelect.build());
	}
	this.db.all(this.QuerySelect.build(), cb);
};

Driver.prototype.insert = function (table, data, cb) {
	this.QueryInsert
		.clear()
		.table(table);
	for (var k in data) {
		this.QueryInsert.set(k, data[k]);
	}

	if (this.opts.debug) {
		require("../../Debug").sql('sqlite', this.QueryInsert.build());
	}
	this.db.all(this.QueryInsert.build(), function (err, info) {
		if (err) {
			return cb(err);
		}
		this.db.get("SELECT last_insert_rowid() AS last_row_id", function (err, row) {
			if (err) {
				return cb(err);
			}
			return cb(null, {
				id: row.last_row_id
			});
		});
	}.bind(this));
};

Driver.prototype.update = function (table, changes, conditions, cb) {
	this.QueryUpdate
		.clear()
		.table(table);
	for (var k in conditions) {
		this.QueryUpdate.where(k, conditions[k]);
	}
	for (k in changes) {
		this.QueryUpdate.set(k, changes[k]);
	}

	if (this.opts.debug) {
		require("../../Debug").sql('sqlite', this.QueryUpdate.build());
	}
	this.db.all(this.QueryUpdate.build(), cb);
};

Driver.prototype.remove = function (table, conditions, cb) {
	this.QueryRemove
		.clear()
		.table(table);
	for (var k in conditions) {
		this.QueryRemove.where(k, conditions[k]);
	}

	if (this.opts.debug) {
		require("../../Debug").sql('sqlite', this.QueryRemove.build());
	}
	this.db.all(this.QueryRemove.build(), cb);
};

Driver.prototype.clear = function (table, cb) {
	var query = "DELETE FROM " + this.escapeId(table);

	if (this.opts.debug) {
		require("../../Debug").sql('sqlite', query);
	}
	this.db.all(query, cb);
};

Driver.prototype.escapeId = function (id) {
	var m = id.match(/^(.+)\.\*$/);
	if (m) {
		return '`' + m[1].replace(/\`/g, '``') + '`.*';
	}
	return '`' + id.replace(/\`/g, '``').replace(/\./g, '`.`') + '`';
};

Driver.prototype.escape = function (value) {
	if (Array.isArray(value)) {
		if (value.length === 1 && Array.isArray(value[0])) {
			return "(" + value[0].map(this.escape) + ")";
		}
		return "(" + value.map(this.escape) + ")";
	}
	switch (typeof value) {
		case "number":
			return value;
		case "boolean":
			return value ? 1 : 0;
	}

	return "'" + value.replace(/\'/g, "''") + "'";
};
<|endoftext|>


<commit_before>var system = require('system');
var args = system.args;

var page = require('webpage').create();

page.open(args[1] + args[2] + '#fight=' + args[3] + '&type=damage-done', function(status) {

		page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js', function() {
			setTimeout(function () {
				console.log("Start_Response")
				console.log(page.evaluate(eva));
				console.log("End_Response");
				phantom.exit();
			}, 10000);
		});

});

var eva = function() {

	var result = {
		player: {},
		total: {}
	};
	// 各々の値を取得
	for (var i = 0, size = $(".table-icon").length; i < size; i++) {

		var name = $(".main-table-name")[i].innerText.replace(/\t|(\r?\n)/g,"");

		result.player[name] = {
			name: name
		}

		if($(".main-table-performance")[i]) {
			result.player[name]['perf'] = $(".main-table-performance")[i].innerText.replace(/\t|(\r?\n)/g,"");
		}

		if($(".table-icon")[i]) {
			result.player[name]['job'] = $(".table-icon")[i].src.match(/\/(\w+).png/)[1];
		}

		if($(".main-table-number")[i]) {
			result.player[name]['dps'] = $(".main-table-number")[i].innerText.replace(/\t|(\r?\n)/g,"");
		}

	}

	// totalの値を取得
	{

		result.total = {
			perf: null,
			dps: null
		};

		if($(".main-table-performance")[$(".main-table-performance").length-1]) {
			result.total.perf = $(".main-table-performance")[$(".main-table-performance").length-1].innerText.replace(/\t|(\r?\n)/g,"");
		}
		if($(".main-table-number")[$(".main-table-number").length-1]) {
			// dps
			result.total.dps = $(".main-table-number")[$(".main-table-number").length-1].innerText;
		}

	}

	return JSON.stringify(result);
}
<commit_msg>Change: phantomjsで使用するjqueryのバージョンを3.2に上げた<commit_after>var system = require('system');
var args = system.args;

var page = require('webpage').create();

page.open(args[1] + args[2] + '#fight=' + args[3] + '&type=damage-done', function(status) {

		page.includeJs('https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js', function() {
			setTimeout(function () {
				console.log("Start_Response")
				console.log(page.evaluate(eva));
				console.log("End_Response");
				phantom.exit();
			}, 10000);
		});

});

var eva = function() {

	var result = {
		player: {},
		total: {}
	};
	// 各々の値を取得
	for (var i = 0, size = $(".table-icon").length; i < size; i++) {

		var name = $(".main-table-name")[i].innerText.replace(/\t|(\r?\n)/g,"");

		result.player[name] = {
			name: name
		}

		if($(".main-table-performance")[i]) {
			result.player[name]['perf'] = $(".main-table-performance")[i].innerText.replace(/\t|(\r?\n)/g,"");
		}

		if($(".table-icon")[i]) {
			result.player[name]['job'] = $(".table-icon")[i].src.match(/\/(\w+).png/)[1];
		}

		if($(".main-table-number")[i]) {
			result.player[name]['dps'] = $(".main-table-number")[i].innerText.replace(/\t|(\r?\n)/g,"");
		}

	}

	// totalの値を取得
	{

		result.total = {
			perf: null,
			dps: null
		};

		if($(".main-table-performance")[$(".main-table-performance").length-1]) {
			result.total.perf = $(".main-table-performance")[$(".main-table-performance").length-1].innerText.replace(/\t|(\r?\n)/g,"");
		}
		if($(".main-table-number")[$(".main-table-number").length-1]) {
			// dps
			result.total.dps = $(".main-table-number")[$(".main-table-number").length-1].innerText;
		}

	}

	return JSON.stringify(result);
}
<|endoftext|>


<commit_before>/**
 * This is the root module of our FreeDOM backend.
 * It runs in an isolated thread with its own namespace.
 * The root module has a special object 'freedom', which
 * is used as a message-passing channel to its parent (the outer webpage)
 **/
var window;
if (!window) {
  window = {};
}
// FreeDOM APIs
var core = freedom.core();
var social = freedom.socialprovider();
var storage = freedom.storageprovider();

// Internal State
var networks = {};
var userList = {};
var clientList = {};
var files = {};       // Files served from this node
var fetchQueue = [];  // Files on queue to be downloaded

// PC
var connections = {};
var signallingChannels = {};

console.log('File Drop root module');

function getMyClientIds() {
  var result = [];
  for (var k in networks) {
    if (networks.hasOwnProperty(k) && networks[k].clientId &&
        networks[k].status && networks[k].status == social.STATUS_NETWORK['ONLINE']) {
      result.push(networks[k].clientId);
    }
  }
  return result;
}

freedom.on('serve-data', function(data) {
  if (!data.key || !data.value) {
    console.log('serve-data: malformed request ' + JSON.stringify(data));
    return;
  }

  console.log('serve-data: now serving ' + data.key);
  files[data.key] = data.value;
  var clientIds = getMyClientIds();
  if (clientIds.length > 0) {
    freedom.emit('serve-descriptor', {
      targetId: clientIds,
      key: data.key,
      name: data.name
    });
    // DEBUG - remove later
    freedom.emit('stats', {
      key: data.key,
      inprogress: 1,
      done: 10
    });
    //
  } else {
    freedom.emit('serve-error', "Error connecting to server.");
  }
});

function setupConnection(name, targetId) {
  connections[targetId] = freedom.transport();
  connections[targetId].on('onData', function(message) {
    console.log("Receiving data with tag: " + message.tag);
    freedom.emit('download-data', message.data);
  });
  return core.createChannel().then(function (chan) {
    // Set up the signalling channel first, it may be needed in the
    // peerconnection setup.
    chan.channel.on('message', function(msg) {
      social.sendMessage(targetId, JSON.stringify({
        cmd: 'signal',
        data: msg
      }));
    });
    signallingChannels[targetId] = chan.channel;
    return connections[targetId].setup(name, chan.identifier);
  });
}

// Am I online some Social network right now?
function isOnline() {
  for (var k in networks) {
    if (networks.hasOwnProperty(k) && networks[k].status && networks[k].status == social.STATUS_NETWORK['ONLINE']) {
      return true;
    }
  }
  return false;
}

function fetch(data) {
  //@todo smarter way to choose a target in the future
  var serverId = data.targetId[0];
  var key = data.key;

  console.log("fetch: downloading " + key + " from " + serverId);
  //Tell 'em I'm comin' for them
  social.sendMessage(serverId, JSON.stringify({
    cmd: 'fetch',
    data: key
  }));
  setupConnection("fetcher", serverId);
}

freedom.on('download', function(data) {
  if (isOnline()) {
    fetch(data);
  } else {
    fetchQueue.push(data);
  }
});

social.on('onStatus', function(msg) {
  if (!networks.hasOwnProperty(msg.network)) {
    console.log('Logging into ' + msg.network);
    social.login({
      network: msg.network,
      agent: 'filedrop',
      version: '0.1',
      url: '',
      interactive: true
    });
  }
  networks[msg.network] = msg;
  if (msg.status && msg.status == social.STATUS_NETWORK['ONLINE']) {
    console.log('social.onStatus: ONLINE!');
    while (fetchQueue.length > 0) {
      fetch(fetchQueue.shift());
    }
  }
});

social.on('onUserProfile', function(data) {
  userList[data.userId] = data;
});

social.on('onClientList', function(data) {
  clientList[data.clientId] = data;
});

social.on('onMessage', function(data) {
  var msg;
  var targetId;
  var key;

  // Try parsing message
  try {
    msg = JSON.parse(data.message);
  } catch (e) {
    console.log("Error parsing message: " + data);
    return;
  }

  if (data.fromClientId && msg.cmd && msg.data && msg.cmd == 'fetch') {
    key = msg.data;
    targetId = data.fromClientId;

    console.log("social.onMessage: Received request for " + key + " from " + targetId);
    setupConnection("server-"+targetId, targetId).then(function(){ //SEND IT
      if (files[key]) {
        console.log("social.onMessage: Sending " + key + " to " + targetId);
        connections[targetId].send('filedrop', files[key]);
      } else {
        console.log("social.onMessage: I don't have key: " + key);
        social.sendMessage(targetId, JSON.stringify({
          cmd: 'error',
          data: 'File missing!'
        }));
      }
    });
  } else if (data.fromClientId && msg.cmd && msg.data && msg.cmd == 'error') {
    console.log('social.onMessage: ' + msg.data);
    freedom.emit('download-error', msg.data);
  } else if (data.fromClientId && msg.cmd && msg.data && msg.cmd == 'signal') {
    console.log('social.onMessage: signalling message');
    targetId = data.fromClientId;
    if (signallingChannels[targetId]) {
      signallingChannels[targetId].emit('message', msg.data);
    } else {
      //DEBUG
      console.error("Signalling channel missing!!");
    }
  } else {
    console.log("social.onMessage: Unrecognized message: " + JSON.stringify(data));
  }

});
<commit_msg>Updating filedrop demo to new social API<commit_after>/**
 * This is the root module of our FreeDOM backend.
 * It runs in an isolated thread with its own namespace.
 * The root module has a special object 'freedom', which
 * is used as a message-passing channel to its parent (the outer webpage)
 **/
var window;
if (!window) {
  window = {};
}
// FreeDOM APIs
var core = freedom.core();
var social = freedom.socialprovider();
var storage = freedom.storageprovider();

// Internal State
var myClientState = null;
var userList = {};
var clientList = {};
var files = {};       // Files served from this node
var fetchQueue = [];  // Files on queue to be downloaded

// PC
var connections = {};
var signallingChannels = {};

console.log('File Drop root module');

freedom.on('serve-data', function(data) {
  if (!data.key || !data.value) {
    console.log('serve-data: malformed request ' + JSON.stringify(data));
    return;
  }
  console.log('serve-data: now serving ' + data.key);
  files[data.key] = data.value;
  if (myClientState.status == social.STATUS["ONLINE"]) {
    freedom.emit('serve-descriptor', {
      targetId: myClientState.clientId,
      key: data.key,
      name: data.name
    });
    // DEBUG - remove later
    freedom.emit('stats', {
      key: data.key,
      inprogress: 1,
      done: 10
    });
    //
  } else {
    freedom.emit('serve-error', "Error connecting to server.");
  }
});

function setupConnection(name, targetId) {
  connections[targetId] = freedom.transport();
  connections[targetId].on('onData', function(message) {
    console.log("Receiving data with tag: " + message.tag);
    freedom.emit('download-data', message.data);
  });
  return core.createChannel().then(function (chan) {
    // Set up the signalling channel first, it may be needed in the
    // peerconnection setup.
    chan.channel.on('message', function(msg) {
      social.sendMessage(targetId, JSON.stringify({
        cmd: 'signal',
        data: msg
      }));
    });
    signallingChannels[targetId] = chan.channel;
    return connections[targetId].setup(name, chan.identifier);
  });
}

function fetch(data) {
  //@todo smarter way to choose a target in the future
  var serverId = data.targetId;
  var key = data.key;

  console.log("fetch: downloading " + key + " from " + serverId);
  //Tell 'em I'm comin' for them
  social.sendMessage(serverId, JSON.stringify({
    cmd: 'fetch',
    data: key
  }));
  setupConnection("fetcher", serverId);
}

freedom.on('download', function(data) {
  if (myClientState !== null &&
      myClientState.status == social.STATUS["ONLINE"]) {
    fetch(data);
  } else {
    fetchQueue.push(data);
  }
});

social.on('onUserProfile', function(data) {
  userList[data.userId] = data;
});

social.on('onClientState', function(data) {
  clientList[data.clientId] = data;
  if (myClientState !== null &&
      data.clientId == myClientState.clientId) {
    myClientState = data;
  }
});

social.on('onMessage', function(data) {
  var msg;
  var targetId;
  var key;

  // Try parsing message
  try {
    msg = JSON.parse(data.message);
  } catch (e) {
    console.log("Error parsing message: " + data);
    return;
  }

  if (data.from.clientId && msg.cmd && msg.data && msg.cmd == 'fetch') {
    key = msg.data;
    targetId = data.from.clientId;

    console.log("social.onMessage: Received request for " + key + " from " + targetId);
    setupConnection("server-"+targetId, targetId).then(function(){ //SEND IT
      if (files[key]) {
        console.log("social.onMessage: Sending " + key + " to " + targetId);
        connections[targetId].send('filedrop', files[key]);
      } else {
        console.log("social.onMessage: I don't have key: " + key);
        social.sendMessage(targetId, JSON.stringify({
          cmd: 'error',
          data: 'File missing!'
        }));
      }
    });
  } else if (data.from.clientId && msg.cmd && msg.data && msg.cmd == 'error') {
    console.log('social.onMessage: ' + msg.data);
    freedom.emit('download-error', msg.data);
  } else if (data.from.clientId && msg.cmd && msg.data && msg.cmd == 'signal') {
    console.log('social.onMessage: signalling message');
    targetId = data.from.clientId;
    if (signallingChannels[targetId]) {
      signallingChannels[targetId].emit('message', msg.data);
    } else {
      //DEBUG
      console.error("Signalling channel missing!!");
    }
  } else {
    console.log("social.onMessage: Unrecognized message: " + JSON.stringify(data));
  }

});

/** LOGIN AT START **/
console.log('Logging in to social API');
social.login({
  agent: 'filedrop',
  version: '0.1',
  url: '',
  interactive: true,
  rememberLogin: false
}).then(function(ret) {
  myClientState = ret;
  if (ret.status == social.STATUS["ONLINE"]) {
    console.log('social.onStatus: ONLINE!');
    while (fetchQueue.length > 0) {
      fetch(fetchQueue.shift());
    }
  } else {
    freedom.emit("serve-error", "Failed logging in. Status: "+ret.status);
  }
}, function(err) {
  freedom.emit("serve-error", err.message);
});

<|endoftext|>


<commit_before>/*
* grunt-cucumberjs
* https://github.com/mavdi/grunt-cucumberjs
*
* Copyright (c) 2013 Mehdi Avdi
* Licensed under the MIT license.
*/

'use strict';

module.exports = function(grunt) {

  var version = grunt.file.readJSON('./package.json').version;
  var projectPkg = grunt.file.readJSON('package.json');
  var spawn = require('child_process').spawn;
  var _ = require('underscore');

  var WIN32_BIN_PATH = '.\\node_modules\\.bin\\cucumber-js.cmd';
  var UNIX_BIN_PATH = './node_modules/cucumber/bin/cucumber.js';

  grunt.registerMultiTask('cucumberjs', 'Run cucumber.js features', function() {
    var done = this.async();

    var options = this.options({
      output: 'features_report.html',
      format: 'html',
      saveJson: false,
      theme: 'foundation',
      templateDir: 'features/templates',
      tags: ''
    });

    // resolve options set via cli
    for (var key in options) {
      if (grunt.option(key)) {
        options[key] = grunt.option(key);
      }
    }

    var commands = [];

    if (options.steps) {
      commands.push('-r', options.steps);
    }

    if (options.tags) {
      commands.push('-t', options.tags);
    }

    if (options.format === 'html') {
      commands.push('-f', 'json');
    } else {
      commands.push('-f', options.format);
    }

    if (grunt.option('features')) {
      commands.push(grunt.option('features'));
    } else {
      this.files.forEach(function(f) {
        f.src.forEach(function(filepath) {
          if (!grunt.file.exists(filepath)) {
            grunt.log.warn('Source file "' + filepath + '" not found.');
            return;
          }

          commands.push(filepath);
        });
      });
    }

    var buffer  = [];
    var cucumber;
    var binPath = '';

    if (process.platform === 'win32') {
      binPath = WIN32_BIN_PATH;
    } else {
      binPath = UNIX_BIN_PATH;
    }

    if(!grunt.file.exists(binPath)) {
      grunt.log.error('cucumberjs binary not found at path ' + binPath + '\n NOTE: You cannot install grunt-cucumberjs without bin links on windows');
      return done(false);
    }

    cucumber = spawn(binPath, commands);

    cucumber.stdout.on('data', function(data) {
      if (options.format === 'html') {
        buffer.push(data);
      } else {
        grunt.log.write(data);
      }
    });

    cucumber.stderr.on('data', function (data) {
      var stderr = new Buffer(data);
      grunt.log.error(stderr.toString());
    });

    cucumber.on('close', function (code) {
      if (options.format === 'html') {
        var featureJsonOutput;

        var output = Buffer.concat(buffer).toString();

        var featureStartIndex = output.substring(0, output.indexOf('"keyword": "Feature"')).lastIndexOf('[');

        var logOutput = output.substring(0, featureStartIndex - 1);

        var featureOutput = output.substring(featureStartIndex);

        try {
          featureJsonOutput = JSON.parse(featureOutput);
        } catch (e) {
          grunt.log.error('Unable to parse cucumberjs output into json.');

          return done(false);
        }

        generateReport(featureJsonOutput, logOutput);
      }

      if (code !== 0) {
        grunt.log.error('failed tests, please see the output');

        return done(false);
      } else {
        return done();
      }
    });

    /**
    * Adds passed/failed properties on features/scenarios
    *
    * @param {object} suite The test suite object
    */
    var setStats = function(suite) {
      var features = suite.features;

      features.forEach(function(feature) {
        feature.passed = 0;
        feature.failed = 0;

        if(!feature.elements) {
          return;
        }

        feature.elements.forEach(function(element) {
          element.passed = 0;
          element.failed = 0;
          element.notdefined = 0;
          element.skipped = 0;

          element.steps.forEach(function(step) {
            if(!step.result) {
              return 0;
            }
            if(step.result.status === 'passed') {
              return element.passed++;
            }
            if(step.result.status === 'failed') {
              return element.failed++;
            }
            if(step.result.status === 'undefined') {
              return element.notdefined++;
            }

            element.skipped ++;
          });

          if(element.failed > 0) {
            return feature.failed++;
          }
          if (element.passed > 0) {
            return feature.passed++;
          }
        });

        if(feature.failed > 0) {
          return suite.failed++;
        }
        if(feature.passed > 0) {
          return suite.passed++;
        }
      });

      suite.features = features;

      return suite;
    };

    /**
    * Returns the path of a template
    *
    * @param {string} name The template name
    */
    var getPath = function(name) {
      var path = 'node_modules/grunt-cucumberjs/templates/' + options.theme + '/' + name;
      // return the users custom template if it has been defined
      if (grunt.file.exists(options.templateDir + '/' + name)) {
        path = options.templateDir + '/' + name;
      }

      return path;
    };

    /**
    * Generate html report
    *
    * @param {object} featureOutput Features result object
    * @param {string} logOutput Contains any console statements captured during the test run
    */
    var generateReport = function(featureOutput, logOutput) {
      var suite = {
        name: projectPkg.name,
        features: featureOutput,
        passed: 0,
        failed: 0,
        logOutput: logOutput
      };

      suite = setStats(suite);

      if (options.saveJson) {
        grunt.file.write(options.output+'.json', JSON.stringify(featureOutput, null, '\t'));
      }
      grunt.file.write(
        options.output,
        _.template(grunt.file.read(getPath('index.tmpl')))({
          suite: suite,
          version: version,
          time: new Date(),
          features: _.template(grunt.file.read(getPath('features.tmpl')))({suite : suite, _ : _ }),
          styles: grunt.file.read(getPath('style.css')),
          script: grunt.file.read(getPath('script.js'))
        })
      );

      grunt.log.writeln('Generated ' + options.output + ' successfully.');
    };
  });
};
<commit_msg>Added  changes so that grunt cucumber.js can output live <commit_after>/*
* grunt-cucumberjs
* https://github.com/mavdi/grunt-cucumberjs
*
* Copyright (c) 2013 Mehdi Avdi
* Licensed under the MIT license.
*/

'use strict';

module.exports = function(grunt) {

  var version = grunt.file.readJSON('./package.json').version;
  var projectPkg = grunt.file.readJSON('package.json');
  var spawn = require('child_process').spawn;
  var _ = require('underscore');

  var WIN32_BIN_PATH = '.\\node_modules\\.bin\\cucumber-js.cmd';
  var UNIX_BIN_PATH = './node_modules/cucumber/bin/cucumber.js';

  grunt.registerMultiTask('cucumberjs', 'Run cucumber.js features', function() {
    var done = this.async();

    var options = this.options({
      output: 'features_report.html',
      format: 'html',
      saveJson: false,
      theme: 'foundation',
      templateDir: 'features/templates',
      tags: ''
    });

    // resolve options set via cli
    for (var key in options) {
      if (grunt.option(key)) {
        options[key] = grunt.option(key);
      }
    }

    var commands = [];

    if (options.steps) {
      commands.push('-r', options.steps);
    }

    if (options.tags) {
      commands.push('-t', options.tags);
    }

    if (options.format === 'html') {
      commands.push('-f', 'json');
    } else {
      commands.push('-f', options.format);
    }

    if (grunt.option('features')) {
      commands.push(grunt.option('features'));
    } else {
      this.files.forEach(function(f) {
        f.src.forEach(function(filepath) {
          if (!grunt.file.exists(filepath)) {
            grunt.log.warn('Source file "' + filepath + '" not found.');
            return;
          }

          commands.push(filepath);
        });
      });
    }

    var buffer  = [];
    var cucumber;
    var binPath = '';

    if (process.platform === 'win32') {
      binPath = WIN32_BIN_PATH;
    } else {
      binPath = UNIX_BIN_PATH;
    }

    if(!grunt.file.exists(binPath)) {
      grunt.log.error('cucumberjs binary not found at path ' + binPath + '\n NOTE: You cannot install grunt-cucumberjs without bin links on windows');
      return done(false);
    }

    cucumber = spawn(binPath, commands);

    cucumber.stdout.on('data', function(data) {
      process.stdout.write(data);
      if (options.format === 'html') {
        buffer.push(data);
      } else {
        grunt.log.write(data);
      }
    });

    cucumber.stderr.on('data', function (data) {
      process.stdout.write(data);
      var stderr = new Buffer(data);
      grunt.log.error(stderr.toString());
    });

    cucumber.on('close', function (code) {
      if (options.format === 'html') {
        var featureJsonOutput;

        var output = Buffer.concat(buffer).toString();

        var featureStartIndex = output.substring(0, output.indexOf('"keyword": "Feature"')).lastIndexOf('[');

        var logOutput = output.substring(0, featureStartIndex - 1);

        var featureOutput = output.substring(featureStartIndex);

        try {
          featureJsonOutput = JSON.parse(featureOutput);
        } catch (e) {
          grunt.log.error('Unable to parse cucumberjs output into json.');

          return done(false);
        }

        generateReport(featureJsonOutput, logOutput);
      }

      if (code !== 0) {
        grunt.log.error('failed tests, please see the output');

        return done(false);
      } else {
        return done();
      }
    });

    /**
    * Adds passed/failed properties on features/scenarios
    *
    * @param {object} suite The test suite object
    */
    var setStats = function(suite) {
      var features = suite.features;

      features.forEach(function(feature) {
        feature.passed = 0;
        feature.failed = 0;

        if(!feature.elements) {
          return;
        }

        feature.elements.forEach(function(element) {
          element.passed = 0;
          element.failed = 0;
          element.notdefined = 0;
          element.skipped = 0;

          element.steps.forEach(function(step) {
            if(!step.result) {
              return 0;
            }
            if(step.result.status === 'passed') {
              return element.passed++;
            }
            if(step.result.status === 'failed') {
              return element.failed++;
            }
            if(step.result.status === 'undefined') {
              return element.notdefined++;
            }

            element.skipped ++;
          });

          if(element.failed > 0) {
            return feature.failed++;
          }
          if (element.passed > 0) {
            return feature.passed++;
          }
        });

        if(feature.failed > 0) {
          return suite.failed++;
        }
        if(feature.passed > 0) {
          return suite.passed++;
        }
      });

      suite.features = features;

      return suite;
    };

    /**
    * Returns the path of a template
    *
    * @param {string} name The template name
    */
    var getPath = function(name) {
      var path = 'node_modules/grunt-cucumberjs/templates/' + options.theme + '/' + name;
      // return the users custom template if it has been defined
      if (grunt.file.exists(options.templateDir + '/' + name)) {
        path = options.templateDir + '/' + name;
      }

      return path;
    };

    /**
    * Generate html report
    *
    * @param {object} featureOutput Features result object
    * @param {string} logOutput Contains any console statements captured during the test run
    */
    var generateReport = function(featureOutput, logOutput) {
      var suite = {
        name: projectPkg.name,
        features: featureOutput,
        passed: 0,
        failed: 0,
        logOutput: logOutput
      };

      suite = setStats(suite);

      if (options.saveJson) {
        grunt.file.write(options.output+'.json', JSON.stringify(featureOutput, null, '\t'));
      }
      grunt.file.write(
        options.output,
        _.template(grunt.file.read(getPath('index.tmpl')))({
          suite: suite,
          version: version,
          time: new Date(),
          features: _.template(grunt.file.read(getPath('features.tmpl')))({suite : suite, _ : _ }),
          styles: grunt.file.read(getPath('style.css')),
          script: grunt.file.read(getPath('script.js'))
        })
      );

      grunt.log.writeln('Generated ' + options.output + ' successfully.');
    };
  });
};
<|endoftext|>


<commit_before>var thinkHttp = thinkRequire('Http');
var url = require('url');
var websocket = require('websocket').server;
var querystring = require('querystring');

var WebSocket = module.exports = Class(function(){
  'use strict';
  /**
   * socket初始化id
   * @type {Number}
   */
  var socketId = 1000;
  return {
    init: function(httpServer, app){
      this.httpServer = httpServer;
      this.app = app;
    },
    /**
     * 检测origin是否合法
     * @param  {[type]} origin [description]
     * @return {[type]}        [description]
     */
    originIsAllowed: function(origin){
      var allowOrigins = C('websocket_allow_origin');
      if (!allowOrigins) {
        return true;
      }
      var info = url.parse(origin);
      var hostname = info.hostname;
      if (isString(allowOrigins) && allowOrigins === hostname) {
        return true;
      }else if (isArray(allowOrigins) && allowOrigins.indexOf(hostname) > -1) {
        return true;
      }else if (isFunction(allowOrigins)) {
        return allowOrigins(hostname, info);
      }
      return false;
    },
    /**
     * 选择子协议
     * @param  {[type]} protocolFullCaseMap [description]
     * @return {[type]}                     [description]
     */
    getSubProtocal: function(protocolFullCaseMap){
      var selectedProtocal = C('websocket_sub_protocal');
      if (isFunction(selectedProtocal)) {
        var subProtocals = Object.values(protocolFullCaseMap);
        selectedProtocal = selectedProtocal(subProtocals);
      }
      return selectedProtocal;
    },
    /**
     * 建立连接处理
     * @param  {[type]} request [description]
     * @return {[type]}         [description]
     */
    openHandle: function(request, protocal){
      var req = request.httpRequest;
      if (req.url === '/') {
        return getPromise([]);
      }
      var deferred = getDefer();
      var fn = function(){};
      var res = {setHeader: fn, end: fn, write: fn};
      var self = this;
      thinkHttp(req, res).run().then(function(http){
        http.websocket = request.socket;
        //子协议
        http.websocket_sub_protocal = protocal;
        self.app.listener(http).then(function(){
          deferred.resolve({
            cookie: Object.values(http._cookie),
            http: http
          });
        }).catch(function(err){
          deferred.reject(err);
        })
      });
      return deferred.promise;
    },
    /**
     * 消息处理
     * @return {[type]} [description]
     */
    messageHandle: function(message, connection, app, type){
      if (type !== 'utf8') {
        connection.socket.send(WebSocket.ERROR_MESSAGE.TYPE_ERROR, message + ' is not valid json');
        return;
      }
      //解析数据
      try{
        message = JSON.parse(message);
      }catch(e){
        connection.socket.send(WebSocket.ERROR_MESSAGE.INVALID_JSON, message + ' is not valid json');
        return;
      }
      if (message.jsonrpc !== '2.0') {
        connection.socket.send(WebSocket.ERROR_MESSAGE.INVALID_JSONRPC, 'data.jsonrpc must be 2.0');
        return;
      }
      var method = message.method + '';
      if (!method) {
        connection.socket.send(WebSocket.ERROR_MESSAGE.INVALID_METHOD, 'data.method is not valid');
        return;
      }
      var pars = message.params;
      var headers = {};
      if (isObject(message.params.headers)) {
        headers = message.params.headers;
        pars = message.params.data;
      }
      if (isObject(pars)) {
        method += (method.indexOf('?') > -1 ? '&' : '?') + querystring.stringify(pars)
      }
      var self = this;
      var data = {
        host: '',
        url: method,
        headers: headers,
        write: function(data, encoding, errMsg){
          var pars = self.getRPCData(JSON.parse(data), errMsg);
          pars.id = message.id;
          connection.send(JSON.stringify(pars));
        },
        end: function(data){
          if (data) {
            this.write(data);
          }
          connection.close();
        }
      }
      var defaultHttp = thinkHttp.getDefaultHttp(data);
      var httpInstance = thinkHttp(defaultHttp.req, defaultHttp.res);
      //将websocket实例添加到http对象上
      httpInstance.http.websocket = connection.socket;
      httpInstance.run().then(app.listener);
    },
    /**
     * 获取rpc数据对象
     * @param  {[type]} data   [description]
     * @param  {[type]} errMsg [description]
     * @return {[type]}        [description]
     */
    getRPCData: function(data, errMsg){
      var pars = {jsonrpc: '2.0'};
      if (errMsg) {
        pars.error = {code: data, message: errMsg};
      }else{
        pars.result = data;
      }
      return pars;
    },
    run: function(){
      var instance = new websocket({
        httpServer: this.httpServer,
        autoAcceptConnections: false
      });
      var self = this;
      instance.on('request', function(request){
        //检测origin
        if (!self.originIsAllowed(request.origin)) {
          return request.reject();
        }
        var socket = request.socket;
        socket.id = socketId++;
        socket.activeTime = Date.now();
        //选择子协议
        var protocal = self.getSubProtocal(request.protocolFullCaseMap);
        return self.openHandle(request, protocal).then(function(data){
          var connection = socket.connection = request.accept(protocal, request.origin, data.cookie);
          socket.close = function(){
            connection.close();
          }
          if (!socket.send) {
            socket.send = function(data, errMsg){
              var pars = self.getRPCData(data, errMsg);
              connection.send(JSON.stringify(pars));
            }
          }
          var messageHandle = C('websocket_message_handle');
          connection.on('message', function(message) {
            socket.activeTime = Date.now();
            var data = message.type === 'utf8' ? message.utf8Data : message.binaryData;
            if (isFunction(messageHandle)) {
              messageHandle(data, connection, self.app, message.type);
            }else{
              self.messageHandle(data, connection, self.app, message.type);
            }
          });
          connection.on('close', function() {
            data.http && data.http.emit('websocket.close');
          });
        }).catch(function(err){
          request.reject(err);
        })
      })
    }
  }
});
/**
 * 错误信息
 * @type {Object}
 */
WebSocket.ERROR_MESSAGE = {
  TYPE_ERROR: -100001, //数据类型错误
  INVALID_JSON: -100002, //不是合法的json
  INVALID_JSONRPC: -100003, //不是jsonrpc数据格式
  INVALID_METHOD: -100004 //请求方法不合法
}<commit_msg>触发websocket.close事件时，需要判断http对象是否存在<commit_after>var thinkHttp = thinkRequire('Http');
var url = require('url');
var websocket = require('websocket').server;
var querystring = require('querystring');

var WebSocket = module.exports = Class(function(){
  'use strict';
  /**
   * socket初始化id
   * @type {Number}
   */
  var socketId = 1000;
  return {
    init: function(httpServer, app){
      this.httpServer = httpServer;
      this.app = app;
    },
    /**
     * 检测origin是否合法
     * @param  {[type]} origin [description]
     * @return {[type]}        [description]
     */
    originIsAllowed: function(origin){
      var allowOrigins = C('websocket_allow_origin');
      if (!allowOrigins) {
        return true;
      }
      var info = url.parse(origin);
      var hostname = info.hostname;
      if (isString(allowOrigins) && allowOrigins === hostname) {
        return true;
      }else if (isArray(allowOrigins) && allowOrigins.indexOf(hostname) > -1) {
        return true;
      }else if (isFunction(allowOrigins)) {
        return allowOrigins(hostname, info);
      }
      return false;
    },
    /**
     * 选择子协议
     * @param  {[type]} protocolFullCaseMap [description]
     * @return {[type]}                     [description]
     */
    getSubProtocal: function(protocolFullCaseMap){
      var selectedProtocal = C('websocket_sub_protocal');
      if (isFunction(selectedProtocal)) {
        var subProtocals = Object.values(protocolFullCaseMap);
        selectedProtocal = selectedProtocal(subProtocals);
      }
      return selectedProtocal;
    },
    /**
     * 建立连接处理
     * @param  {[type]} request [description]
     * @return {[type]}         [description]
     */
    openHandle: function(request, protocal){
      var req = request.httpRequest;
      if (req.url === '/') {
        return getPromise([]);
      }
      var deferred = getDefer();
      var fn = function(){};
      var res = {setHeader: fn, end: fn, write: fn};
      var self = this;
      thinkHttp(req, res).run().then(function(http){
        http.websocket = request.socket;
        //子协议
        http.websocket_sub_protocal = protocal;
        self.app.listener(http).then(function(){
          deferred.resolve({
            cookie: Object.values(http._cookie),
            http: http
          });
        }).catch(function(err){
          deferred.reject(err);
        })
      });
      return deferred.promise;
    },
    /**
     * 消息处理
     * @return {[type]} [description]
     */
    messageHandle: function(message, connection, app, type){
      if (type !== 'utf8') {
        connection.socket.send(WebSocket.ERROR_MESSAGE.TYPE_ERROR, message + ' is not valid json');
        return;
      }
      //解析数据
      try{
        message = JSON.parse(message);
      }catch(e){
        connection.socket.send(WebSocket.ERROR_MESSAGE.INVALID_JSON, message + ' is not valid json');
        return;
      }
      if (message.jsonrpc !== '2.0') {
        connection.socket.send(WebSocket.ERROR_MESSAGE.INVALID_JSONRPC, 'data.jsonrpc must be 2.0');
        return;
      }
      var method = message.method + '';
      if (!method) {
        connection.socket.send(WebSocket.ERROR_MESSAGE.INVALID_METHOD, 'data.method is not valid');
        return;
      }
      var pars = message.params;
      var headers = {};
      if (isObject(message.params.headers)) {
        headers = message.params.headers;
        pars = message.params.data;
      }
      if (isObject(pars)) {
        method += (method.indexOf('?') > -1 ? '&' : '?') + querystring.stringify(pars)
      }
      var self = this;
      var data = {
        host: '',
        url: method,
        headers: headers,
        write: function(data, encoding, errMsg){
          var pars = self.getRPCData(JSON.parse(data), errMsg);
          pars.id = message.id;
          connection.send(JSON.stringify(pars));
        },
        end: function(data){
          if (data) {
            this.write(data);
          }
          connection.close();
        }
      }
      var defaultHttp = thinkHttp.getDefaultHttp(data);
      var httpInstance = thinkHttp(defaultHttp.req, defaultHttp.res);
      //将websocket实例添加到http对象上
      httpInstance.http.websocket = connection.socket;
      httpInstance.run().then(app.listener);
    },
    /**
     * 获取rpc数据对象
     * @param  {[type]} data   [description]
     * @param  {[type]} errMsg [description]
     * @return {[type]}        [description]
     */
    getRPCData: function(data, errMsg){
      var pars = {jsonrpc: '2.0'};
      if (errMsg) {
        pars.error = {code: data, message: errMsg};
      }else{
        pars.result = data;
      }
      return pars;
    },
    run: function(){
      var instance = new websocket({
        httpServer: this.httpServer,
        autoAcceptConnections: false
      });
      var self = this;
      instance.on('request', function(request){
        //检测origin
        if (!self.originIsAllowed(request.origin)) {
          return request.reject();
        }
        var socket = request.socket;
        socket.id = socketId++;
        socket.activeTime = Date.now();
        //选择子协议
        var protocal = self.getSubProtocal(request.protocolFullCaseMap);
        return self.openHandle(request, protocal).then(function(data){
          var connection = socket.connection = request.accept(protocal, request.origin, data.cookie);
          socket.close = function(){
            connection.close();
          }
          if (!socket.send) {
            socket.send = function(data, errMsg){
              var pars = self.getRPCData(data, errMsg);
              connection.send(JSON.stringify(pars));
            }
          }
          var messageHandle = C('websocket_message_handle');
          connection.on('message', function(message) {
            socket.activeTime = Date.now();
            var data = message.type === 'utf8' ? message.utf8Data : message.binaryData;
            if (isFunction(messageHandle)) {
              messageHandle(data, connection, self.app, message.type);
            }else{
              self.messageHandle(data, connection, self.app, message.type);
            }
          });
          connection.on('close', function() {
            return data.http && data.http.emit('websocket.close');
          });
        }).catch(function(err){
          request.reject(err);
        })
      })
    }
  }
});
/**
 * 错误信息
 * @type {Object}
 */
WebSocket.ERROR_MESSAGE = {
  TYPE_ERROR: -100001, //数据类型错误
  INVALID_JSON: -100002, //不是合法的json
  INVALID_JSONRPC: -100003, //不是jsonrpc数据格式
  INVALID_METHOD: -100004 //请求方法不合法
}<|endoftext|>