From a0f98c0e0042d5dfcd449befefd832147ca83048 Mon Sep 17 00:00:00 2001 From: Yaroslav Admin Date: Fri, 1 May 2020 11:27:02 +0200 Subject: [PATCH] fix(cli): restore command line help contents These were lost after migration to yargs as CLI parser. Fixes #3474 --- lib/cli.js | 115 ++++++---------- test/e2e/cli.feature | 171 ++++++++++++++++++++++++ test/e2e/step_definitions/core_steps.js | 29 ++++ 3 files changed, 239 insertions(+), 76 deletions(-) create mode 100644 test/e2e/cli.feature diff --git a/lib/cli.js b/lib/cli.js index baf36ac4c..448f56eb6 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -10,16 +10,6 @@ const helper = require('./helper') const constant = require('./constants') function processArgs (argv, options, fs, path) { - if (argv.help) { - console.log(yargs.help()) - process.exit(0) - } - - if (argv.version) { - console.log(`Karma version: ${constant.VERSION}`) - process.exit(0) - } - // TODO(vojta): warn/throw when unknown argument (probably mispelled) Object.getOwnPropertyNames(argv).forEach(function (name) { let argumentValue = argv[name] @@ -130,6 +120,10 @@ function processArgs (argv, options, fs, path) { options.configFile = configFile ? path.resolve(configFile) : null + if (options.cmd === 'run') { + options.clientArgs = parseClientArgs(process.argv) + } + return options } @@ -151,39 +145,44 @@ function argsBeforeDoubleDash (argv) { return idx === -1 ? argv : argv.slice(0, idx) } -function describeShared () { - yargs +function describeRoot () { + return yargs .usage('Karma - Spectacular Test Runner for JavaScript.\n\n' + + 'Run --help with particular command to see its description and available options.\n\n' + 'Usage:\n' + - ' $0 \n\n' + - 'Commands:\n' + - ' start [] [] Start the server / do single run.\n' + - ' init [] Initialize a config file.\n' + - ' run [] [ -- ] Trigger a test run.\n' + - ' completion Shell completion for karma.\n\n' + - 'Run --help with particular command to see its description and available options.') + ' $0 ') + .command('init', 'Initialize a config file.', describeInit) + .command('start', 'Start the server / do a single run.', describeStart) + .command('run', 'Trigger a test run.', describeRun) + .command('stop', 'Stop the server.', describeStop) + .command('completion', 'Shell completion for karma.', describeCompletion) + .demandCommand(1, 'Command not specified.') + .strictCommands() .describe('help', 'Print usage and options.') .describe('version', 'Print current version.') } -function describeInit () { +function describeInit (yargs) { yargs .usage('Karma - Spectacular Test Runner for JavaScript.\n\n' + 'INIT - Initialize a config file.\n\n' + 'Usage:\n' + - ' $0 init []') + ' $0 init [configFile]') + .strictCommands(false) + .version(false) .describe('log-level', ' Level of logging.') .describe('colors', 'Use colors when reporting and printing logs.') .describe('no-colors', 'Do not use colors when reporting or printing logs.') - .describe('help', 'Print usage and options.') } -function describeStart () { +function describeStart (yargs) { yargs .usage('Karma - Spectacular Test Runner for JavaScript.\n\n' + 'START - Start the server / do a single run.\n\n' + 'Usage:\n' + - ' $0 start [] []') + ' $0 start [configFile]') + .strictCommands(false) + .version(false) .describe('port', ' Port where the server is running.') .describe('auto-watch', 'Auto watch source files and run on change.') .describe('detached', 'Detach the server.') @@ -201,93 +200,57 @@ function describeStart () { .describe('no-fail-on-empty-test-suite', 'Do not fail on empty test suite.') .describe('fail-on-failing-test-suite', 'Fail on failing test suite.') .describe('no-fail-on-failing-test-suite', 'Do not fail on failing test suite.') - .describe('help', 'Print usage and options.') } -function describeRun () { +function describeRun (yargs) { yargs .usage('Karma - Spectacular Test Runner for JavaScript.\n\n' + 'RUN - Run the tests (requires running server).\n\n' + 'Usage:\n' + - ' $0 run [] [] [ -- ]') + ' $0 run [configFile] [-- ]') + .strictCommands(false) + .version(false) .describe('port', ' Port where the server is listening.') .describe('no-refresh', 'Do not re-glob all the patterns.') .describe('fail-on-empty-test-suite', 'Fail on empty test suite.') .describe('no-fail-on-empty-test-suite', 'Do not fail on empty test suite.') - .describe('help', 'Print usage.') .describe('log-level', ' Level of logging.') .describe('colors', 'Use colors when reporting and printing logs.') .describe('no-colors', 'Do not use colors when reporting or printing logs.') } -function describeStop () { +function describeStop (yargs) { yargs .usage('Karma - Spectacular Test Runner for JavaScript.\n\n' + 'STOP - Stop the server (requires running server).\n\n' + 'Usage:\n' + - ' $0 run [] []') + ' $0 stop [configFile]') + .strictCommands(false) + .version(false) .describe('port', ' Port where the server is listening.') .describe('log-level', ' Level of logging.') - .describe('help', 'Print usage.') } -function describeCompletion () { +function describeCompletion (yargs) { yargs .usage('Karma - Spectacular Test Runner for JavaScript.\n\n' + 'COMPLETION - Bash/ZSH completion for karma.\n\n' + 'Installation:\n' + - ' $0 completion >> ~/.bashrc\n') - .describe('help', 'Print usage.') + ' $0 completion >> ~/.bashrc') + .strictCommands(false) + .version(false) } function printRunnerProgress (data) { process.stdout.write(data) } -exports.process = function () { - const argv = yargs.parse(argsBeforeDoubleDash(process.argv.slice(2))) - const options = { - cmd: argv._.shift() - } - - switch (options.cmd) { - case 'start': - describeStart() - break - - case 'run': - describeRun() - options.clientArgs = parseClientArgs(process.argv) - break - - case 'stop': - describeStop() - break - - case 'init': - describeInit() - break - - case 'completion': - describeCompletion() - break - - default: - describeShared() - if (!options.cmd) { - processArgs(argv, options, fs, path) - console.error('Command not specified.') - } else { - console.error('Unknown command "' + options.cmd + '".') - } - yargs.showHelp() - process.exit(1) - } - - return processArgs(argv, options, fs, path) +exports.process = () => { + const argv = describeRoot().parse(argsBeforeDoubleDash(process.argv.slice(2))) + return processArgs(argv, { cmd: argv._.shift() }, fs, path) } -exports.run = function () { +exports.run = () => { const config = exports.process() switch (config.cmd) { diff --git a/test/e2e/cli.feature b/test/e2e/cli.feature new file mode 100644 index 000000000..2913f23f2 --- /dev/null +++ b/test/e2e/cli.feature @@ -0,0 +1,171 @@ +Feature: CLI + In order to use Karma + As a person who wants to write great tests + I want command line interface to provide help and useful errors. + + Scenario: Top-level CLI help + When I execute Karma with arguments: "--help" + Then the stdout is exactly: + """ + Karma - Spectacular Test Runner for JavaScript. + + Run --help with particular command to see its description and available options. + + Usage: + karma + + Commands: + karma init Initialize a config file. + karma start Start the server / do a single run. + karma run Trigger a test run. + karma stop Stop the server. + karma completion Shell completion for karma. + + Options: + --help Print usage and options. [boolean] + --version Print current version. [boolean] + """ + + Scenario: Current version + When I execute Karma with arguments: "--version" + Then the stdout matches RegExp: + """ + ^\d\.\d\.\d$ + """ + + Scenario: Error when command is unknown + When I execute Karma with arguments: "strat" + Then the stderr is exactly: + """ + Karma - Spectacular Test Runner for JavaScript. + + Run --help with particular command to see its description and available options. + + Usage: + karma + + Commands: + karma init Initialize a config file. + karma start Start the server / do a single run. + karma run Trigger a test run. + karma stop Stop the server. + karma completion Shell completion for karma. + + Options: + --help Print usage and options. [boolean] + --version Print current version. [boolean] + + Unknown command: strat + """ + + Scenario: Init command help + When I execute Karma with arguments: "init --help" + Then the stdout is exactly: + """ + Karma - Spectacular Test Runner for JavaScript. + + INIT - Initialize a config file. + + Usage: + karma init [configFile] + + Options: + --help Print usage and options. [boolean] + --log-level Level of logging. + --colors Use colors when reporting and printing logs. + --no-colors Do not use colors when reporting or printing logs. + """ + + Scenario: Start command help + When I execute Karma with arguments: "start --help" + Then the stdout is exactly: + """ + Karma - Spectacular Test Runner for JavaScript. + + START - Start the server / do a single run. + + Usage: + karma start [configFile] + + Options: + --help Print usage and options. [boolean] + --port Port where the server is running. + --auto-watch Auto watch source files and run on change. + --detached Detach the server. + --no-auto-watch Do not watch source files. + --log-level Level + of logging. + --colors Use colors when reporting and printing logs. + --no-colors Do not use colors when reporting or printing + logs. + --reporters List of reporters (available: dots, progress, + junit, growl, coverage). + --browsers List of browsers to start (eg. --browsers + Chrome,ChromeCanary,Firefox). + --capture-timeout Kill browser if does not capture in + given time [ms]. + --single-run Run the test when browsers captured and exit. + --no-single-run Disable single-run. + --report-slower-than Report tests that are slower than + given time [ms]. + --fail-on-empty-test-suite Fail on empty test suite. + --no-fail-on-empty-test-suite Do not fail on empty test suite. + --fail-on-failing-test-suite Fail on failing test suite. + --no-fail-on-failing-test-suite Do not fail on failing test suite. + """ + + Scenario: Run command help + When I execute Karma with arguments: "run --help" + Then the stdout is exactly: + """ + Karma - Spectacular Test Runner for JavaScript. + + RUN - Run the tests (requires running server). + + Usage: + karma run [configFile] [-- ] + + Options: + --help Print usage and options. [boolean] + --port Port where the server is listening. + --no-refresh Do not re-glob all the patterns. + --fail-on-empty-test-suite Fail on empty test suite. + --no-fail-on-empty-test-suite Do not fail on empty test suite. + --log-level Level + of logging. + --colors Use colors when reporting and printing logs. + --no-colors Do not use colors when reporting or printing + logs. + """ + + Scenario: Stop command help + When I execute Karma with arguments: "stop --help" + Then the stdout is exactly: + """ + Karma - Spectacular Test Runner for JavaScript. + + STOP - Stop the server (requires running server). + + Usage: + karma stop [configFile] + + Options: + --help Print usage and options. [boolean] + --port Port where the server is listening. + --log-level Level of logging. + """ + + Scenario: Completion command help + When I execute Karma with arguments: "completion --help" + Then the stdout is exactly: + """ + Karma - Spectacular Test Runner for JavaScript. + + COMPLETION - Bash/ZSH completion for karma. + + Installation: + karma completion >> ~/.bashrc + + Options: + --help Print usage and options. [boolean] + """ diff --git a/test/e2e/step_definitions/core_steps.js b/test/e2e/step_definitions/core_steps.js index 996e97f2d..05f37072f 100644 --- a/test/e2e/step_definitions/core_steps.js +++ b/test/e2e/step_definitions/core_steps.js @@ -47,6 +47,10 @@ When('I {command} Karma with additional arguments: {string}', async function (co await this.runForegroundProcess(`${command} ${this.configFile} ${args}`) }) +When('I execute Karma with arguments: {string}', async function (args) { + await this.runForegroundProcess(args) +}) + Then(/^it passes with(:? (no\sdebug|like|regexp))?:$/, { timeout: 10 * 1000 }, function (mode, expectedOutput, callback) { const noDebug = mode === 'no debug' const like = mode === 'like' @@ -105,6 +109,31 @@ Then('it fails with like:', function (expectedOutput, callback) { } }) +Then(/^the (stdout|stderr) (is exactly|contains|matches RegExp):$/, function (outputType, comparison, expectedOutput) { + const actualOutput = (outputType === 'stdout' ? this.lastRun.stdout : this.lastRun.stderr).trim() + expectedOutput = expectedOutput.trim() + + switch (comparison) { + case 'is exactly': + if (actualOutput !== expectedOutput) { + throw new Error(`Expected output to be exactly as above, but got:\n\n${actualOutput}`) + } + break + case 'contains': + if (!actualOutput.includes(expectedOutput)) { + throw new Error(`Expected output to contain the above text, but got:\n\n${actualOutput}`) + } + break + case 'matches RegExp': + if (!(new RegExp(expectedOutput).test(actualOutput))) { + throw new Error(`Expected output to match the above RegExp, but got:\n\n${actualOutput}`) + } + break + default: + throw new Error(`Unknown comparison type: ${comparison}`) + } +}) + Then(/^The (server|stopper) is dead(:? with exit code (\d+))?$/, function (stopperOrServer, code, callback) { const server = stopperOrServer === 'server' setTimeout(() => {