diff --git a/.gitignore b/.gitignore index 0be52c37f..2f9914051 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ test/.* coverage/ .nyc_output/ .eslintcache +.idea diff --git a/README.md b/README.md index d2ac00b63..2c9ef1b51 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ citgm --help ``` ``` -Usage: citgm [options] +Usage: citgm [options] Options: diff --git a/lib/bin/citgm.js b/lib/bin/citgm.js index 973ec0e29..5abab80a6 100755 --- a/lib/bin/citgm.js +++ b/lib/bin/citgm.js @@ -7,18 +7,22 @@ import { windows, Tester } from '../citgm.js'; import { commonArgs } from '../common-args.js'; import { logger } from '../out.js'; import * as reporter from '../reporter/index.js'; +import async from 'async'; -let mod; +let mods; -const yargs = commonArgs().usage('citgm [options] ').option('sha', { - alias: 'c', - type: 'string', - description: 'Install module from commit-sha, branch or tag' -}); +const yargs = commonArgs() + .usage('citgm [options] ') + .option('sha', { + alias: 'c', + type: 'string', + description: 'Install module from commit-sha, branch or tag' + }); const app = yargs.argv; -mod = app._[0]; +mods = app._; +let modules = []; const log = logger({ level: app.verbose, @@ -32,7 +36,7 @@ if (!app.su) { log.warn('root', 'Running as root! Use caution!'); } -if (!mod) { +if (!mods.length) { yargs.showHelp(); process.exit(0); } @@ -56,18 +60,23 @@ if (!windows) { uidnumber(uid, gid, (err, uid, gid) => { options.uid = uid; options.gid = gid; - launch(mod, options); + launch(); }); } else { - launch(mod, options); + launch(); } -const start = new Date(); -function launch(mod, options) { +function runCitgm(mod, next) { + const start = new Date(); const runner = new Tester(mod, options); + let bailed = false; function cleanup() { + bailed = true; runner.cleanup(); + process.removeListener('SIGINT', cleanup); + process.removeListener('SIGHUP', cleanup); + process.removeListener('SIGBREAK', cleanup); } process.on('SIGINT', cleanup); @@ -84,28 +93,78 @@ function launch(mod, options) { .on('data', (type, key, message) => { log[type](key, message); }) - .on('end', (module) => { - module.duration = new Date() - start; - reporter.logger(log, module); - - log.info('duration', `test duration: ${module.duration}ms`); - if (app.markdown) { - reporter.markdown(log.bypass, module); - } - if (app.tap) { - const tap = typeof app.tap === 'string' ? app.tap : log.bypass; - reporter.tap(tap, module, app.append); + .on('end', (result) => { + result.duration = new Date() - start; + log.info('duration', `test duration: ${result.duration}ms`); + if (result.error) { + log.error( + `${result.name} done`, + `done - the test suite for ${result.name} version ${result.version} failed` + ); + } else { + log.info( + `${result.name} done`, + `done - the test suite for ${result.name} version ${result.version} passed.` + ); } - - if (app.junit) { - const junit = typeof app.junit === 'string' ? app.junit : log.bypass; - reporter.junit(junit, module, app.append); + modules.push(result); + if (!bailed) { + process.removeListener('SIGINT', cleanup); + process.removeListener('SIGHUP', cleanup); + process.removeListener('SIGBREAK', cleanup); } - - process.removeListener('SIGINT', cleanup); - process.removeListener('SIGHUP', cleanup); - process.removeListener('SIGBREAK', cleanup); - process.exit(module.error ? 1 : 0); + return next(bailed); }) .run(); } + +function runTask(mod, next) { + runCitgm(mod, next); +} + +function launch() { + const collection = mods; + + const q = async.queue(runTask, app.parallel || 1); + q.push(collection); + function done(exitCode = 0) { + q.kill(); + reporter.logger(log, modules); + + if (app.markdown) { + reporter.markdown(log.bypass, modules); + } + + if (app.tap) { + // If tap is a string it should be a path to write output to + // If not use `log.bypass` which is currently process.stdout.write + // TODO check that we can write to that path, perhaps require a flag to + // Overwrite + const tap = typeof app.tap === 'string' ? app.tap : log.bypass; + reporter.tap(tap, modules, app.append); + } + + if (app.junit) { + const junit = typeof app.junit === 'string' ? app.junit : log.bypass; + reporter.junit(junit, modules, app.append); + } + + const hasFailures = reporter.util.hasFailures(modules) ? 1 : 0; + process.exit(hasFailures || exitCode); + } + + function abort() { + q.pause(); + q.kill(); + process.removeListener('SIGINT', abort); + process.removeListener('SIGHUP', abort); + process.removeListener('SIGBREAK', abort); + done(1); + } + + q.drain(done); + + process.on('SIGINT', abort); + process.on('SIGHUP', abort); + process.on('SIGBREAK', abort); +} diff --git a/test/bin/test-citgm.js b/test/bin/test-citgm.js index bff8fa29d..f3e62803a 100644 --- a/test/bin/test-citgm.js +++ b/test/bin/test-citgm.js @@ -61,6 +61,29 @@ test('bin: omg-i-pass /w local module', (t) => { }); }); +test('bin: multiple packages', (t) => { + t.plan(3); + const proc = spawn(citgmPath, ['omg-i-pass', 'omg-i-fail']); + let stdout = ''; + + proc.on('close', (code) => { + t.ok(code !== 0, 'omg-i-fail should fail with a non-zero exit code'); + t.match( + stdout, + /info:\s*starting\s*\|\s*omg-i-pass\s*/, + 'stdout should contain omg-i-pass' + ); + t.match( + stdout, + /info:\s*starting\s*\|\s*omg-i-fail\s*/, + 'stdout should contain omg-i-fail' + ); + }); + proc.stdout.on('data', (data) => { + stdout += data; + }); +}); + test('bin: no module /w root check', (t) => { t.plan(1); const proc = spawn(citgmPath, ['-s']);