329 lines
8.1 KiB
JavaScript
329 lines
8.1 KiB
JavaScript
/**
|
|
* Run the tests, and setup es if needed
|
|
*
|
|
* ENV VARS:
|
|
* RUN - a list of task names to run, specifying this turns of all other tasks
|
|
* ES_REF - the ES branch/tag we should use to generate the tests and download es
|
|
* ES_RELEASE - a specific ES release to download in use for testing
|
|
* ES_PORT - the port number we should run elasticsearch on
|
|
* ES_HOST - the hostname elasticsearch should bind to
|
|
* ES_V - a version identifier used by jenkins. don't use this
|
|
*
|
|
* Tasks:
|
|
* NODE_UNIT - run the unit tests in node (default: true)
|
|
* NODE_INTEGRATION - run the integration tests in node (default: true)
|
|
* SAUCE_LABS - run the browser tests (default: false)
|
|
* CHECK_COVERAGE - check for coverage and ship it to coveralls (default: false)
|
|
*
|
|
*******/
|
|
|
|
var Promise = require('bluebird');
|
|
var _ = require('lodash');
|
|
var through2 = require('through2');
|
|
var map = require('through2-map');
|
|
var split = require('split');
|
|
var join = require('path').join;
|
|
var cp = require('child_process');
|
|
var chalk = require('chalk');
|
|
var format = require('util').format;
|
|
|
|
var NL_RE = /(\r?\n)/g;
|
|
var ROOT = join(__dirname, '..');
|
|
var GRUNT = join(ROOT, 'node_modules', '.bin', 'grunt');
|
|
var ENV = _.clone(process.env);
|
|
var JENKINS = !!ENV.JENKINS_HOME;
|
|
var TASKS = [];
|
|
|
|
var output; // main output stream
|
|
var taskOut; // task output stream
|
|
|
|
task('NODE_UNIT', true, function () {
|
|
if (!JENKINS) {
|
|
return grunt('mochacov:ci_unit');
|
|
}
|
|
|
|
return grunt('mochacov:jenkins_unit');
|
|
});
|
|
|
|
task('NODE_INTEGRATION', true, function () {
|
|
var branch = ENV.ES_REF;
|
|
|
|
return node('scripts/generate', '--no-api', '--branch', branch)
|
|
.then(function () {
|
|
var target = (JENKINS ? 'jenkins_' : '') + 'integration:' + branch;
|
|
return grunt('esvm:ci_env', 'mocha_' + target, 'esvm_shutdown:ci_env');
|
|
});
|
|
});
|
|
|
|
task('SAUCE_LABS', false, function () {
|
|
return new Promise(function (resolve, reject) {
|
|
// build the clients and start the server, once the server is ready call trySaucelabs()
|
|
var serverTasks = ['browser_clients:build', 'run:browser_test_server:keepalive'];
|
|
spawn(GRUNT, serverTasks, function (proc) {
|
|
var toLines = split();
|
|
|
|
proc.stdout
|
|
.pipe(toLines)
|
|
.pipe(through2(function (line, enc, cb) {
|
|
cb();
|
|
|
|
if (String(line).indexOf('listening on port 8000') === -1) return;
|
|
|
|
|
|
trySaucelabs()
|
|
.finally(function () { if (proc) proc.kill(); })
|
|
.then(resolve, reject);
|
|
|
|
proc.on('exit', function () { proc = null; });
|
|
proc.stdout.unpipe(toLines);
|
|
toLines.end();
|
|
}));
|
|
})
|
|
// ignore server errors
|
|
.catch(_.noop);
|
|
|
|
// attempt to run tests on saucelabs and retry if it fails
|
|
var saucelabsAttempts = 0;
|
|
function trySaucelabs() {
|
|
saucelabsAttempts++;
|
|
return new Promise(function (resolve, reject) {
|
|
log(chalk.green('saucelabs attempt #', saucelabsAttempts));
|
|
spawn(GRUNT, ['saucelabs-mocha'], function (cp) {
|
|
|
|
var failedTests = 0;
|
|
cp.stdout
|
|
.pipe(split())
|
|
.pipe(map(function (line) {
|
|
failedTests += String(line).trim() === 'Passed: false' ? 1 : 0;
|
|
}));
|
|
|
|
cp.on('error', reject);
|
|
cp.on('exit', function (code) {
|
|
if (code > 0) {
|
|
if (failedTests > 0) {
|
|
return reject(new Error('Browser tests failed'));
|
|
}
|
|
|
|
if (saucelabsAttempts >= 3) {
|
|
return reject(new Error('Saucelabs is like really really down. Tried 3 times'));
|
|
}
|
|
|
|
log(chalk.blue('trying saucelabs again...'));
|
|
return trySaucelabs().then(resolve, reject);
|
|
}
|
|
|
|
return resolve();
|
|
});
|
|
})
|
|
// swallow spawn() errors, custom error handler in place
|
|
.catch(_.noop);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
task('CHECK_COVERAGE', false, function () {
|
|
return grunt('mochacov:ship_coverage')
|
|
.catch(function () {
|
|
log('FAILED TO SHIP COVERAGE! but that\'s okay');
|
|
});
|
|
});
|
|
|
|
execTask('SETUP', function () {
|
|
return Promise.try(function readVersion() {
|
|
if (!ENV.ES_V) {
|
|
if (ENV.ES_RELEASE) {
|
|
return ['v' + ENV.ES_RELEASE, ENV.ES_RELEASE];
|
|
}
|
|
|
|
if (ENV.ES_REF) {
|
|
return [ENV.ES_REF, null];
|
|
}
|
|
}
|
|
|
|
var match;
|
|
if (match = ENV.ES_V.match(/^(.*)_nightly$/)) {
|
|
return [match[1], null];
|
|
}
|
|
|
|
if (/^(?:1\.\d+|0\.90)\..*$/.test(ENV.ES_V)) {
|
|
return ['v' + ENV.ES_V, ENV.ES_V];
|
|
}
|
|
|
|
throw new Error('unable to parse ES_V ' + ENV.ES_V);
|
|
})
|
|
.then(function readOtherConf(ver) {
|
|
if (!ver) {
|
|
throw new Error('Unable to run the ci script without at least an ES_REF or ES_RELEASE environment var.');
|
|
}
|
|
|
|
log('ES_PORT:', ENV.ES_PORT = parseInt(ENV.ES_PORT || 9400, 10));
|
|
log('ES_HOST:', ENV.ES_HOST = ENV.ES_HOST || 'localhost');
|
|
|
|
if (ver[0]) log('ES_REF:', ENV.ES_REF = ver[0]);
|
|
else delete ENV.ES_REF;
|
|
|
|
if (ver[1]) log('ES_RELEASE:', ENV.ES_RELEASE = ver[1]);
|
|
else delete ENV.ES_RELEASE;
|
|
})
|
|
.then(function readTasks() {
|
|
if (!ENV.RUN) {
|
|
return _.filter(TASKS, { default: true });
|
|
}
|
|
|
|
return ENV.RUN
|
|
.split(',')
|
|
.map(function (name) {
|
|
return _.find(TASKS, { name: name.trim() });
|
|
})
|
|
.filter(Boolean);
|
|
});
|
|
})
|
|
.then(function (queue) {
|
|
if (!queue.length) {
|
|
throw new Error('no tasks to run');
|
|
}
|
|
|
|
// Recursively do tasks until the queue is empty
|
|
return (function next() {
|
|
if (!queue.length) return;
|
|
return execTask(queue.shift()).then(next);
|
|
}());
|
|
})
|
|
.then(function () {
|
|
logImportant(chalk.bold.green('✔︎ SUCCESS'));
|
|
})
|
|
.catch(function (e) {
|
|
logImportant(chalk.bold.red('✗ FAILURE\n\n' + e.stack));
|
|
|
|
// override process exit code once it is ready to close
|
|
process.once('exit', function () {
|
|
process.exit(1);
|
|
});
|
|
});
|
|
|
|
/** ****
|
|
* utils
|
|
******/
|
|
function log() {
|
|
var chunk = format.apply(null, arguments);
|
|
(taskOut || output || process.stdout).write(chunk + '\n');
|
|
}
|
|
|
|
function logImportant(text) {
|
|
log('\n------------');
|
|
log(text);
|
|
log('------------');
|
|
}
|
|
|
|
function push(m) {
|
|
return function () {
|
|
var args = _.toArray(arguments);
|
|
var cb = args.pop();
|
|
this.push(m.apply(this, args));
|
|
cb();
|
|
};
|
|
}
|
|
|
|
function indent() {
|
|
var str = through2(
|
|
push(function (chunk) { return String(chunk).replace(NL_RE, '$1 '); }),
|
|
push(function () { return '\n'; })
|
|
);
|
|
str.push(' ');
|
|
return str;
|
|
}
|
|
|
|
function task(name, def, fn) {
|
|
if (_.isFunction(def)) {
|
|
fn = def;
|
|
def = true;
|
|
}
|
|
|
|
TASKS.push({
|
|
name: name,
|
|
default: def,
|
|
fn: fn
|
|
});
|
|
}
|
|
|
|
function execTask(name, task) {
|
|
if (_.isObject(name)) {
|
|
task = name.fn;
|
|
name = name.name;
|
|
}
|
|
|
|
output = through2();
|
|
output
|
|
.pipe(process.stdout, { end: false });
|
|
|
|
log(chalk.white.underline(name));
|
|
|
|
taskOut = through2();
|
|
taskOut
|
|
.pipe(indent())
|
|
.pipe(output);
|
|
|
|
function flushTaskOut() {
|
|
return new Promise(function (resolve) {
|
|
// wait for the taskOut to finish writing before continuing
|
|
output.once('finish', function () {
|
|
log('');
|
|
resolve();
|
|
});
|
|
taskOut.end(); // will end output as well
|
|
taskOut = output = null;
|
|
});
|
|
}
|
|
|
|
return Promise.try(task).finally(flushTaskOut);
|
|
}
|
|
|
|
function spawn(file, args, block) {
|
|
return new Promise(function (resolve, reject) {
|
|
var proc = cp.spawn(file, args, {
|
|
cwd: ROOT,
|
|
env: ENV,
|
|
stdio: [0, 'pipe', 'pipe']
|
|
});
|
|
|
|
proc.stdout.pipe(taskOut, { end: false });
|
|
proc.stderr.pipe(taskOut, { end: false });
|
|
|
|
var stdout = '';
|
|
proc.stdout
|
|
.pipe(through2(function (chunk, enc, cb) {
|
|
stdout += chunk;
|
|
cb();
|
|
}));
|
|
|
|
if (block) block(proc);
|
|
|
|
proc.on('exit', function (code) {
|
|
if (code > 0) {
|
|
reject(new Error('non-zero exit code: ' + code));
|
|
} else {
|
|
resolve(stdout);
|
|
}
|
|
});
|
|
|
|
proc.on('error', function (origErr) {
|
|
reject(new Error('Unable to execute "' + file + ' ' + args.join(' ') + '": ' + origErr.message));
|
|
});
|
|
});
|
|
}
|
|
|
|
function node(/* args... */) {
|
|
return spawn(
|
|
process.execPath,
|
|
_.toArray(arguments)
|
|
);
|
|
}
|
|
|
|
function grunt(/* args... */) {
|
|
return spawn(
|
|
GRUNT,
|
|
_.toArray(arguments)
|
|
);
|
|
}
|