UNPKG

npm-run-batch

Version:

npm run-script helper that allows running a bunch of run-scripts - in series or parallel!

192 lines (177 loc) 5.69 kB
var spawn = require('cross-spawn'); var Promise = require('bluebird'); var fs = require('fs'); var path = require('path'); var tmp = require('tmp'); var expect = require('chai').expect; var diff = require('diff'); var chalk = require('chalk'); var pkg = require('../../package.json'); // tmp.setGracefulCleanup(); var samples = [{ task: "batch:series", expectedOutput: "test/fixtures/series.expected", expectError: null }, { task: "batch:ofbatches", expectedOutput: "test/fixtures/ofbatches.expected", expectError: null }, { task: "batch:dev", expectedOutput: "test/fixtures/dev.expected", expectError: null }, { task: "batch:prod", expectedOutput: "test/fixtures/prod.expected", expectError: null }, { task: "batch:error", expectedOutput: "test/fixtures/err.expected", expectError: [ 'ERROR:npm,run-script,sample:err exited with status code 1' ] }]; function keepLine(line) { // This gets hackier and hackier to test. We are removing lines // that either change by run/env or npm/node version. return !line.match(/(.*npm ERR!)|(.*at )|(Unhandled rejection Error.*)/); } function fwdSlashPaths(line) { // the fixture paths are recorded on a developer machine. // There are two reasons for variations - // 1. CI systems use different paths. // 2. Windows and *nix use different path conventions. // var fixturePath = '/Users/sramam/github/sramam/npm-run-batch'; var currentPath = path.resolve(__dirname, '..', '..').replace(/\\/g, '/'); return line.replace(/\\/g, '/').replace(currentPath, fixturePath); } function adjustPkgVersion(line) { // The recordings are made against a version, but the npm version keeps morphing. // To prevent having to keep modifying the recordings in the fixture, a manual // process, we keep the recordings at version 0.0.1 and automate the conversion // here. return line.replace('npm-run-batch@0.0.1', 'npm-run-batch@' + pkg.version); } function adjustFixture(fixture) { // fixes paths & versions in fixtures, to match current run return fixture .split('\n') .reduce(function (_, line) { if (keepLine(line)) { _.push(adjustPkgVersion(line)); } return _; }, []) .join('\n'); } function adjustOutputStackTrace(output) { return output .split('\n') .reduce(function (_, line) { if (keepLine(line)) { _.push(fwdSlashPaths(line)); } return _; }, []) .join('\n'); } // the error logs if created, seem to put a time-stamp in the filename, // making this very hard to compare. We'll normalize these to hack-fix this function remapErrorLogFile(str) { return str .split('\n') .map(function (line) { return line .trim() // this strips whitespace from each line .replace(/.npm\/_logs\/.*-debug.log/, '.npm/_logs/npm-debug.log'); }) .join('\n') } function compareFiles(actualOutput, expectedOutput) { var errorFile = /\/.npm\/_logs\/.*-debug.log/; var actual = remapErrorLogFile( adjustOutputStackTrace( fs.readFileSync(actualOutput, 'utf-8') ) ).trim(); var expected = remapErrorLogFile( adjustFixture( fs.readFileSync(expectedOutput, 'utf-8') ) ).trim(); var res = (actual === expected); if (res === false) { console.log('--------'); console.log(diff.diffChars(actual, expected)); console.log('--------'); console.log(`\n:::actual:::\n${actual}\n----`); console.log(`\n:::expected:::\n${expected}\n----`); // If the comparison fails, log diff to stderr. diff.diffChars(actual, expected) .forEach(function (part) { var color = part.added ? 'green' : part.removed ? 'red' : 'grey'; process.stderr.write(chalk[color](part.value)); }); process.stderr.write('\n'); // useful for a quick comparison when things fail // these files are .gitignored. fs.writeFileSync('./j1', actual, 'utf8'); fs.writeFileSync('./j2', expected, 'utf8'); } return res; } function spawnNpm(cmd) { var outFile = tmp.fileSync(); var outStream = fs.createWriteStream('', { fd: fs.openSync(outFile.name, 'w'), decodeStrings: true }); return new Promise(function (resolve, reject) { var options = { cwd: process.cwd(), env: process.env, stdio: ['inherit', outStream, outStream] }; var child = spawn('npm', ['run', cmd], options); child.once('close', function (code) { if (code === 0) { resolve(outFile.name); } else { var err = new Error('ERROR: `npm run ' + cmd + '` exited with errorCode ' + code); process.exitCode = 0; // coerce the code back to zero, so the test process can succeed reject(err, outFile.name); } }); child.once('error', function (err) { expect(process.exitCode, -1); process.exitCode = 0; reject(err, outFile.name); }); }) } describe('npm-run-batch', function () { samples.forEach(function (sample) { it(sample.task, function (done) { // fs.unlinkSync ('./.npm-run-batch.cache') spawnNpm(sample.task) .then(function (fname) { expect( compareFiles(fname, sample.expectedOutput) ).to.be.true; done(); }).catch(function (err) { if (sample.expectError) { const cache = JSON.parse(fs.readFileSync('./.npm-run-batch.cache', 'utf8')); const actual = cache.errors.map(e => e.message); expect(actual).to.deep.equal(sample.expectError); fs.unlinkSync ('./.npm-run-batch.cache'); done(); } else { done(err); } }); }); }); });