/** * Run the tests, and setup es if needed * * ENV VARS: * ES_V - a version identifier used by jenkins. don't use this * ES_BRANCH - the ES branch we should use to generate the tests and download es * ES_RELEASE - a specific ES release to download in use for testing * NODE_UNIT=1 - 0/1 run the unit tests in node * NODE_INTEGRATION=1 - 0/1 run the integration tests in node * BROWSER_UNIT - the browser to test in using, sauce labs. One of 'ie', 'firefox', 'chrome' * COVERAGE - 0/1 check for coverage and ship it to coveralls *******/ var Promise = require('bluebird'); var _ = require('lodash-node'); var through2 = require('through2'); var map = require('through2-map'); var split = require('split'); var join = require('path').join; var fs = require('fs'); var child_process = require('child_process'); var chalk = require('chalk'); var format = require('util').format; var ROOT = join(__dirname, '..'); var GRUNT = join(ROOT, './node_modules/.bin/grunt'); var MOCHA = join(ROOT, './node_modules/.bin/mocha'); var MOCHA_REPORTER = 'test/utils/jenkins-reporter.js'; var ENV = _.clone(process.env); var JENKINS = !!ENV.JENKINS_HOME; /****** * SETUP ******/ var taskChain = Promise.resolve(); var output; // main output stream var taskOut; // task output stream /****** * GET VERSION ******/ task( 'read version from environment', true, function () { function read() { if (ENV.ES_V) { var match; if (match = ENV.ES_V.match(/^(.*)_nightly$/)) { return [match[1], null]; } if (match = ENV.ES_V.match(/^(1\.\d+|0\.90)\..*$/)) { return [match[1], ENV.ES_V]; } throw new Error('unable to parse ES_V ' + ENV.ES_V); } if (ENV.ES_BRANCH) { return [ENV.ES_BRANCH, ENV.ES_RELEASE || null]; } } var ver = read(); if (!ver) { throw new Error('Unable to run the ci script without at least an ES_BRANCH environment var.'); } if (ver[0]) { taskOut.write('branch: ' + (ENV.ES_BRANCH = ver[0]) + '\n'); } else { delete ENV.ES_BRANCH; } if (ver[1]) { taskOut.write('release: ' + (ENV.ES_RELEASE = ver[1]) + '\n'); } else { delete ENV.ES_RELEASE; } } ); task( 'node unit tests', ENV.NODE_UNIT !== '0', function () { if (!JENKINS) { return grunt('jshint', 'mochacov:unit'); } var report = join(ROOT, 'test/junit-node-unit.xml'); var tests = join(ROOT, 'test/unit/index.js'); return mocha(report, tests, '--reporter', join(ROOT, MOCHA_REPORTER)); } ); task( 'node integration tests', ENV.NODE_INTEGRATION !== '0', function () { var branch = ENV.ES_BRANCH; return node('scripts/generate', '--no-api', '--branch', branch) .then(function () { if (JENKINS) return; return grunt('esvm:ci_env', 'mochacov:integration_' + branch, 'esvm_shutdown:ci_env'); }) .then(function () { if (!JENKINS) return; var branchSuffix = '_' + branch.replace(/\./g, '_'); var tests = 'test/integration/yaml_suite/index' + branchSuffix + '.js'; var esPort = ENV.es_port || 9200; var report = 'test/junit-node-integration.xml'; return mocha(report, tests, '--host', 'localhost', '--port', esPort, '--reporter', MOCHA_REPORTER); }); } ); task( 'browser unit tests', ENV.BROWSER_UNIT === '1', 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 (cp) { var stdout = cp.stdout; var lines = split(); var findReady = through2(function (line, enc, cb) { cb(); line = String(line); if (line.indexOf('run:browser_test_server') === -1) return; trySaucelabs() .finally(function () { cp.kill(); }) .then(resolve, reject); stdout.unpipe(lines); lines.end(); }); stdout.pipe(lines).pipe(findReady); }); // attempt to run tests on saucelabs and retry if it fails var saucelabsAttempts = 0; function trySaucelabs() { saucelabsAttempts++; return new Promise(function (resolve, reject) { spawn(GRUNT, ['saucelabs-mocha'], function (cp) { var failedTests = 0; cp.stdout .pipe(split()) .pipe(map(function (line) { line = String(line); if (line.trim() === 'Passed: false') { failedTests ++; } })); 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')); } taskOut.write(chalk.blue('trying saucelabs again...')); return trySaucelabs().then(resolve, reject); } return resolve(); }); }) // swallow spawn() errors .then(_.noop, _.noop); }); } }); } ); task( 'code coverage', ENV.COVERAGE === '1', function () { return grunt('mochacov:ship_coverage') .catch(function () { taskOut.write('FAILED TO SHIP COVERAGE! but that\'s okay\n'); }); } ); /****** * FINISH */ taskChain .finally(function () { // output directly to stdout output = process.stdout; }) .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); output.write(chunk + '\n'); } function logImportant(text) { log('\n------------'); log(text); log('------------\n'); } function indent(line) { line = String(line).trim(); return line ? ' ' + line + '\n' : ''; } function task(name, condition, block) { if (!condition) return; taskChain = taskChain.then(function () { taskOut = through2(); output = through2(); taskOut .pipe(split()) .pipe(map(indent)) .pipe(output); output .pipe(process.stdout, { end: false }); log(chalk.white.underline(name)); function flushTaskOut() { return new Promise(function (resolve) { // wait for the taskOut to finish writing before continuing output.once('finish', function () { process.stdout.write('\n'); resolve(); }); taskOut.end(); // will end output as well taskOut = output = null; }); } return Promise.try(block).finally(flushTaskOut); }); } function spawn(file, args, block) { return new Promise(function (resolve, reject) { var cp = child_process.spawn(file, args, { cwd: ROOT, env: ENV, stdio: [0, 'pipe', 'pipe'] }); cp.stdout.pipe(taskOut, { end: false }); cp.stderr.pipe(taskOut, { end: false }); var stdout = ''; cp.stdout .pipe(through2(function (chunk, enc, cb) { stdout += chunk; cb(); })); block && block(cp); cp.on('exit', function (code) { if (code > 0) { reject(new Error('non-zero exit code: ' + code)); } else { resolve(stdout); } }); cp.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)); } function mocha(report/*, args... */) { return spawn(MOCHA, _.rest(arguments, 1), function (cp) { cp.stderr.unpipe(); cp.stderr.pipe(fs.createWriteStream(report)); }); }