<commit_before>require('colors');
var fs = require('fs');
var path = require('path');
var async = require('async');
var wd = require('wd');
var finalhandler = require('finalhandler')
var http = require('http')
var serveStatic = require('serve-static')
var http = require('http');
var io = require('socket.io');
var events = require('events');
var gutil = require('gulp-util');
var SauceTunnel = require('sauce-tunnel');
var extend = require('extend');
var argv = require('yargs').argv;
var freeport = require('freeport');
var seleniumLauncher = require('selenium-standalone');

// Helper to get a fully-qualified string from a browser definition
function toBrowserString(browser) {
  return (browser.platform ? (browser.platform + ' ') : '') + 
    browser.browserName + 
    (browser.version ? (' ' + browser.version) : '');
}

// Browser abstraction, responsible for spinning up a browser instance via wd.js and
// executing runner.html test files passed in options.files
function BrowserRunner(reporter, local, def, options, doneCallback) {
  
  this.reporter = reporter;
  this.options = options;
  this.def = def;
  this.results = [];
  this.files = options.files.slice();
  
  this.doneCallback = function(err) {
    var data = {browser: this.def};
    if (err) {
      data.error = err;
    } else {
      data.results = this.results;
    }
    this.reporter.emit('browser-end', data);
    this.browser.quit();
    doneCallback(err, this);
  };

  // Create wd browser instance
  if (local) {
    this.browser = wd.remote('localhost', options.seleniumPort);
  } else {
    var user = options.sauceUser || process.env.SAUCE_USERNAME;
    var key = options.sauceKey || process.env.SAUCE_ACCESS_KEY;
    this.browser = wd.remote('ondemand.saucelabs.com', 80, user, key);
  }

  // Setup browser logging
  if (argv.verbose) {
    this.browser.on('status', function(info) {
      console.log(info.cyan);
    });
    this.browser.on('command', function(eventType, command, response) {
      console.log(' > ' + eventType.cyan, command, (response || '').grey);
    });
    this.browser.on('http', function(meth, path, data) {
      console.log(' > ' + meth.magenta, path, (data || '').grey);
    });
  }

  // Initialize the browser, then start tests
  this.browser.init(def, function(err, session, result) {
    if (err) {
      this.doneCallback(err);
    } else {
      this.reporter.emit('browser-start', {browser: this.def, session: session, result: result});
      this.testNextFile();
    }
  }.bind(this));
}
BrowserRunner.prototype.onEvent = function(data) {
  this.test.extendTimeout();
  if (data.event == 'end') {
    this.test.setResults(data.data);
  }
};
BrowserRunner.prototype.testNextFile = function() {
  var file = this.files.shift();
  if (!file) {
    this.doneCallback(null);
  } else {
    var qs = ['?stream=' + (this.options.port), 'browser=' + this.def.id].join('&');
    var url = ['http://localhost:' + this.options.port, this.options.component, 'tests', file + qs].join('/');
    this.test = new TestRunner(this.browser, url, false, function(err, res) {
      if (err) {
        this.doneCallback(err);
      } else {
        this.results.push({
          file: file,
          results: this.test.results
        });
        this.testNextFile();
      }
    }.bind(this));
  }
};

// Abstraction around a single test runner.html to be run on a given browser
function TestRunner(browser, url, shouldPoll, doneCallback) {
  this.timeout = 1000 * 30;
  this.doneCallback = doneCallback;
  this.browser = browser;
  async.series([
    function(next) {
      browser.get(url, next);
    }, function(next) {
      // Legacy support for polled results -- currently unused 
      if (shouldPoll) {
        this.waitForResults();
      } else {
        this.extendTimeout();
      }
    }.bind(this)
  ], doneCallback);
}
// Polled results
TestRunner.prototype.waitForResults = function() {
  this.resultsTimeout = Date.now() + this.timeout;
  this.pollResult();
};
TestRunner.prototype.pollResult = function() {
  this.browser.eval("window.mochaResults", function(err, res) {
    if (res) {
      this.results = res;
      this.doneCallback(null, this);
    } else {
      if (Date.now() < this.resultsTimeout) {
        setTimeout(this.pollResult.bind(this), 1000);
      } else {
        this.doneCallback('Timed out waiting for mochaResults');
      }
    }
  }.bind(this));
};
// Event-based results
TestRunner.prototype.setResults = function(results) {
  this.results = results;
  this.resetTimeout();
  this.doneCallback();
};
TestRunner.prototype.resetTimeout = function() {
  if (this.timeoutId) {
    clearTimeout(this.timeoutId);
  }
};
TestRunner.prototype.extendTimeout = function() {
  this.resetTimeout();
  this.timeoutId = setTimeout(function() {
    this.doneCallback('Timed out waiting for mochaResults');
  }.bind(this), this.timeout);
};

// Main test running sequence
function runTests(reporter, server, local, browsers, options, doneCallback) {
  var results = [];
  var runners = [];
  browsers = browsers.slice();

  // Initialize default options
  options = extend({}, options);
  options.component = options.component || path.basename(process.cwd());
  options.files = options.files || ['runner.html'];

  // Setup socket.io for mocha streaming
  var ioserver = io(server);
  ioserver.on('connection', function(socket) {
    socket.on('mocha event', function(data) {
      // Notify browser runner of test start/completion
      var browserRunner = runners[data.browser];
      browserRunner.onEvent(data);
      data.browser = browsers[data.browser];
      reporter.emit('test-status', data);
    });
  });

  // Performs end-of-run cleanup
  var done = function() {
    reporter.emit('run-end', {results: results});
    doneCallback(null, results);
  };

  // Start browsers and wait for results<commit_msg>Deal with JSON vs object split<commit_after>require('colors');
var fs = require('fs');
var path = require('path');
var async = require('async');
var wd = require('wd');
var finalhandler = require('finalhandler')
var http = require('http')
var serveStatic = require('serve-static')
var http = require('http');
var io = require('socket.io');
var events = require('events');
var gutil = require('gulp-util');
var SauceTunnel = require('sauce-tunnel');
var extend = require('extend');
var argv = require('yargs').argv;
var freeport = require('freeport');
var seleniumLauncher = require('selenium-standalone');

// Helper to get a fully-qualified string from a browser definition
function toBrowserString(browser) {
  return (browser.platform ? (browser.platform + ' ') : '') + 
    browser.browserName + 
    (browser.version ? (' ' + browser.version) : '');
}

// Browser abstraction, responsible for spinning up a browser instance via wd.js and
// executing runner.html test files passed in options.files
function BrowserRunner(reporter, local, def, options, doneCallback) {
  
  this.reporter = reporter;
  this.options = options;
  this.def = def;
  this.results = [];
  this.files = options.files.slice();
  
  this.doneCallback = function(err) {
    var data = {browser: this.def};
    if (err) {
      data.error = err;
    } else {
      data.results = this.results;
    }
    this.reporter.emit('browser-end', data);
    this.browser.quit();
    doneCallback(err, this);
  };

  // Create wd browser instance
  if (local) {
    this.browser = wd.remote('localhost', options.seleniumPort);
  } else {
    var user = options.sauceUser || process.env.SAUCE_USERNAME;
    var key = options.sauceKey || process.env.SAUCE_ACCESS_KEY;
    this.browser = wd.remote('ondemand.saucelabs.com', 80, user, key);
  }

  // Setup browser logging
  if (argv.verbose) {
    this.browser.on('status', function(info) {
      console.log(info.cyan);
    });
    this.browser.on('command', function(eventType, command, response) {
      console.log(' > ' + eventType.cyan, command, (response || '').grey);
    });
    this.browser.on('http', function(meth, path, data) {
      console.log(' > ' + meth.magenta, path, (data || '').grey);
    });
  }

  // Initialize the browser, then start tests
  this.browser.init(def, function(err, session, result) {
    if (err) {
      this.doneCallback(err);
    } else {
      this.reporter.emit('browser-start', {browser: this.def, session: session, result: result});
      this.testNextFile();
    }
  }.bind(this));
}
BrowserRunner.prototype.onEvent = function(data) {
  this.test.extendTimeout();
  if (data.event == 'end') {
    this.test.setResults(data.data);
  }
};
BrowserRunner.prototype.testNextFile = function() {
  var file = this.files.shift();
  if (!file) {
    this.doneCallback(null);
  } else {
    var qs = ['?stream=' + (this.options.port), 'browser=' + this.def.id].join('&');
    var url = ['http://localhost:' + this.options.port, this.options.component, 'tests', file + qs].join('/');
    this.test = new TestRunner(this.browser, url, false, function(err, res) {
      if (err) {
        this.doneCallback(err);
      } else {
        this.results.push({
          file: file,
          results: this.test.results
        });
        this.testNextFile();
      }
    }.bind(this));
  }
};

// Abstraction around a single test runner.html to be run on a given browser
function TestRunner(browser, url, shouldPoll, doneCallback) {
  this.timeout = 1000 * 30;
  this.doneCallback = doneCallback;
  this.browser = browser;
  async.series([
    function(next) {
      browser.get(url, next);
    }, function(next) {
      // Legacy support for polled results -- currently unused 
      if (shouldPoll) {
        this.waitForResults();
      } else {
        this.extendTimeout();
      }
    }.bind(this)
  ], doneCallback);
}
// Polled results
TestRunner.prototype.waitForResults = function() {
  this.resultsTimeout = Date.now() + this.timeout;
  this.pollResult();
};
TestRunner.prototype.pollResult = function() {
  this.browser.eval("window.mochaResults", function(err, res) {
    if (res) {
      this.results = res;
      this.doneCallback(null, this);
    } else {
      if (Date.now() < this.resultsTimeout) {
        setTimeout(this.pollResult.bind(this), 1000);
      } else {
        this.doneCallback('Timed out waiting for mochaResults');
      }
    }
  }.bind(this));
};
// Event-based results
TestRunner.prototype.setResults = function(results) {
  this.results = results;
  this.resetTimeout();
  this.doneCallback();
};
TestRunner.prototype.resetTimeout = function() {
  if (this.timeoutId) {
    clearTimeout(this.timeoutId);
  }
};
TestRunner.prototype.extendTimeout = function() {
  this.resetTimeout();
  this.timeoutId = setTimeout(function() {
    this.doneCallback('Timed out waiting for mochaResults');
  }.bind(this), this.timeout);
};

// Main test running sequence
function runTests(reporter, server, local, browsers, options, doneCallback) {
  var results = [];
  var runners = [];
  browsers = browsers.slice();

  // Initialize default options
  options = extend({}, options);
  options.component = options.component || path.basename(process.cwd());
  options.files = options.files || ['runner.html'];

  // Setup socket.io for mocha streaming
  var ioserver = io(server);
  ioserver.on('connection', function(socket) {
    socket.on('mocha event', function(data) {
      // Notify browser runner of test start/completion
      var browserRunner = runners[data.browser];
      browserRunner.onEvent(data);

      // Sometimes we get objects, sometimes JSON. Unsure where the behavior is
      // diverging.
      if (typeof data.data === 'string') {
        data.data = JSON.parse(data.data);
      }

      data.browser = browsers[data.browser];
      reporter.emit('test-status', data);
    });
  });

  // Performs end-of-run cleanup
  var done = function() {
    reporter.emit('run-end', {results: results});
    doneCallback(null, results);
  };

  // Start browsers and wait for results
{'fraction_non_alphanumeric': 0.1549425672843129, 'fraction_numerical': 0.0020122411335625054, 'mean_word_length': 3.318609703113686, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4053043', 'n_tokens_mistral': 3726, 'n_tokens_neox': 3633, 'n_words': 848}