| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:args/command_runner.dart'; |
| import 'package:test/test.dart'; |
| |
| import 'test_utils.dart'; |
| |
| const _defaultUsage = ''' |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage information. |
| |
| Available commands: |
| help Display help information for test. |
| |
| Run "test help <command>" for more information about a command.'''; |
| |
| void main() { |
| late CommandRunner runner; |
| setUp(() { |
| runner = CommandRunner('test', 'A test command runner.'); |
| }); |
| |
| test('.invocation has a sane default', () { |
| expect(runner.invocation, equals('test <command> [arguments]')); |
| }); |
| |
| group('.usage', () { |
| test('returns the usage string', () { |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| $_defaultUsage''')); |
| }); |
| |
| test('contains custom commands', () { |
| runner.addCommand(FooCommand()); |
| |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage information. |
| |
| Available commands: |
| foo Set a value. |
| |
| Run "test help <command>" for more information about a command.''')); |
| }); |
| |
| group('displays categories', () { |
| test('when some commands are categorized', () { |
| runner.addCommand(Category1Command()); |
| runner.addCommand(Category2Command()); |
| runner.addCommand(FooCommand()); |
| |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage information. |
| |
| Available commands: |
| foo Set a value. |
| |
| Displayers |
| baz Display a value. |
| |
| Printers |
| bar Print a value. |
| |
| Run "test help <command>" for more information about a command.''')); |
| }); |
| |
| test('except when all commands in a category are hidden', () { |
| runner.addCommand(Category1Command()); |
| runner.addCommand(HiddenCategorizedCommand()); |
| |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage information. |
| |
| Available commands: |
| |
| Printers |
| bar Print a value. |
| |
| Run "test help <command>" for more information about a command.''')); |
| }); |
| |
| test('when all commands are categorized', () { |
| runner.addCommand(Category1Command()); |
| runner.addCommand(Category2Command()); |
| |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage information. |
| |
| Available commands: |
| |
| Displayers |
| baz Display a value. |
| |
| Printers |
| bar Print a value. |
| |
| Run "test help <command>" for more information about a command.''')); |
| }); |
| |
| test('when multiple commands are in a category', () { |
| runner.addCommand(Category1Command()); |
| runner.addCommand(Category2Command()); |
| runner.addCommand(Category2Command2()); |
| |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage information. |
| |
| Available commands: |
| |
| Displayers |
| baz Display a value. |
| baz2 Display another value. |
| |
| Printers |
| bar Print a value. |
| |
| Run "test help <command>" for more information about a command.''')); |
| }); |
| }); |
| |
| test('truncates newlines in command descriptions by default', () { |
| runner.addCommand(MultilineCommand()); |
| |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage information. |
| |
| Available commands: |
| multiline Multi |
| |
| Run "test help <command>" for more information about a command.''')); |
| }); |
| |
| test('supports newlines in command summaries', () { |
| runner.addCommand(MultilineSummaryCommand()); |
| |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage information. |
| |
| Available commands: |
| multiline Multi |
| line. |
| |
| Run "test help <command>" for more information about a command.''')); |
| }); |
| |
| test('contains custom options', () { |
| runner.argParser.addFlag('foo', help: 'Do something.'); |
| |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage information. |
| --[no-]foo Do something. |
| |
| Available commands: |
| help Display help information for test. |
| |
| Run "test help <command>" for more information about a command.''')); |
| }); |
| |
| test("doesn't print hidden commands", () { |
| runner |
| ..addCommand(HiddenCommand()) |
| ..addCommand(HiddenCategorizedCommand()) |
| ..addCommand(FooCommand()); |
| |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage information. |
| |
| Available commands: |
| foo Set a value. |
| |
| Run "test help <command>" for more information about a command.''')); |
| }); |
| |
| test("doesn't print aliases", () { |
| runner.addCommand(AliasedCommand()); |
| |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage information. |
| |
| Available commands: |
| aliased Set a value. |
| |
| Run "test help <command>" for more information about a command.''')); |
| }); |
| |
| test('respects usageLineLength', () { |
| runner = CommandRunner('name', 'desc', usageLineLength: 35); |
| expect(runner.usage, equals(''' |
| desc |
| |
| Usage: name <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage |
| information. |
| |
| Available commands: |
| help Display help information |
| for name. |
| |
| Run "name help <command>" for more |
| information about a command.''')); |
| }); |
| }); |
| |
| test('usageException splits up the message and usage', () { |
| expect(() => runner.usageException('message'), |
| throwsUsageException('message', _defaultUsage)); |
| }); |
| |
| group('run()', () { |
| test('runs a command', () { |
| var command = FooCommand(); |
| runner.addCommand(command); |
| |
| expect( |
| runner.run(['foo']).then((_) { |
| expect(command.hasRun, isTrue); |
| }), |
| completes); |
| }); |
| |
| test('runs an asynchronous command', () { |
| var command = AsyncCommand(); |
| runner.addCommand(command); |
| |
| expect( |
| runner.run(['async']).then((_) { |
| expect(command.hasRun, isTrue); |
| }), |
| completes); |
| }); |
| |
| test('runs a command with a return value', () { |
| var runner = CommandRunner<int>('test', ''); |
| var command = ValueCommand(); |
| runner.addCommand(command); |
| |
| expect(runner.run(['foo']), completion(equals(12))); |
| }); |
| |
| test('runs a command with an asynchronous return value', () { |
| var runner = CommandRunner<String>('test', ''); |
| var command = AsyncValueCommand(); |
| runner.addCommand(command); |
| |
| expect(runner.run(['foo']), completion(equals('hi'))); |
| }); |
| |
| test('runs a hidden comand', () { |
| var command = HiddenCommand(); |
| runner.addCommand(command); |
| |
| expect( |
| runner.run(['hidden']).then((_) { |
| expect(command.hasRun, isTrue); |
| }), |
| completes); |
| }); |
| |
| test('runs an aliased comand', () { |
| var command = AliasedCommand(); |
| runner.addCommand(command); |
| |
| expect( |
| runner.run(['als']).then((_) { |
| expect(command.hasRun, isTrue); |
| }), |
| completes); |
| }); |
| |
| test('runs a subcommand', () { |
| var command = AsyncCommand(); |
| runner.addCommand(FooCommand()..addSubcommand(command)); |
| |
| expect( |
| runner.run(['foo', 'async']).then((_) { |
| expect(command.hasRun, isTrue); |
| }), |
| completes); |
| }); |
| |
| group('suggests similar commands', () { |
| test('deletions', () { |
| var command = FooCommand(); |
| runner.addCommand(command); |
| |
| for (var typo in ['afoo', 'foao', 'fooa']) { |
| expect(() => runner.run([typo]), throwsUsageException(''' |
| Could not find a command named "$typo". |
| |
| Did you mean one of these? |
| foo |
| ''', anything)); |
| } |
| }); |
| |
| test('additions', () { |
| var command = LongCommand(); |
| runner.addCommand(command); |
| |
| for (var typo in ['ong', 'lng', 'lon']) { |
| expect(() => runner.run([typo]), throwsUsageException(''' |
| Could not find a command named "$typo". |
| |
| Did you mean one of these? |
| long |
| ''', anything)); |
| } |
| }); |
| |
| test('substitutions', () { |
| var command = LongCommand(); |
| runner.addCommand(command); |
| |
| for (var typo in ['aong', 'lang', 'lona']) { |
| expect(() => runner.run([typo]), throwsUsageException(''' |
| Could not find a command named "$typo". |
| |
| Did you mean one of these? |
| long |
| ''', anything)); |
| } |
| }); |
| |
| test('swaps', () { |
| var command = LongCommand(); |
| runner.addCommand(command); |
| |
| for (var typo in ['olng', 'lnog', 'logn']) { |
| expect(() => runner.run([typo]), throwsUsageException(''' |
| Could not find a command named "$typo". |
| |
| Did you mean one of these? |
| long |
| ''', anything)); |
| } |
| }); |
| |
| test('combinations', () { |
| var command = LongCommand(); |
| runner.addCommand(command); |
| |
| for (var typo in ['oln', 'on', 'lgn', 'alogn']) { |
| expect(() => runner.run([typo]), throwsUsageException(''' |
| Could not find a command named "$typo". |
| |
| Did you mean one of these? |
| long |
| ''', anything)); |
| } |
| }); |
| |
| test('sorts by relevance', () { |
| var a = CustomNameCommand('abcd'); |
| runner.addCommand(a); |
| var b = CustomNameCommand('bcd'); |
| runner.addCommand(b); |
| |
| expect(() => runner.run(['abdc']), throwsUsageException(''' |
| Could not find a command named "abdc". |
| |
| Did you mean one of these? |
| abcd |
| bcd |
| ''', anything)); |
| |
| expect(() => runner.run(['bdc']), throwsUsageException(''' |
| Could not find a command named "bdc". |
| |
| Did you mean one of these? |
| bcd |
| abcd |
| ''', anything)); |
| }); |
| |
| test('omits commands with an edit distance over 2', () { |
| var command = LongCommand(); |
| runner.addCommand(command); |
| |
| for (var typo in ['llllong', 'aolgn', 'abcg', 'longggg']) { |
| expect( |
| () => runner.run([typo]), |
| throwsUsageException( |
| 'Could not find a command named "$typo".', anything)); |
| } |
| }); |
| |
| test('max edit distance is configurable', () { |
| runner = CommandRunner('test', 'A test command runner.', |
| suggestionDistanceLimit: 1) |
| ..addCommand(LongCommand()); |
| expect( |
| () => runner.run(['ng']), |
| throwsUsageException( |
| 'Could not find a command named "ng".', anything)); |
| |
| runner = CommandRunner('test', 'A test command runner.', |
| suggestionDistanceLimit: 3) |
| ..addCommand(LongCommand()); |
| expect(() => runner.run(['g']), throwsUsageException(''' |
| Could not find a command named "g". |
| |
| Did you mean one of these? |
| long |
| ''', anything)); |
| }); |
| |
| test('supports subcommands', () { |
| var command = FooCommand(); |
| command.addSubcommand(LongCommand()); |
| runner.addCommand(command); |
| expect(() => runner.run(['foo', 'ong']), throwsUsageException(''' |
| Could not find a subcommand named "ong" for "test foo". |
| |
| Did you mean one of these? |
| long |
| ''', anything)); |
| }); |
| |
| test('doesn\'t show hidden commands', () { |
| var command = HiddenCommand(); |
| runner.addCommand(command); |
| expect( |
| () => runner.run(['hidde']), |
| throwsUsageException( |
| 'Could not find a command named "hidde".', anything)); |
| }); |
| |
| test('Suggests based on aliases', () { |
| var command = AliasedCommand(); |
| runner.addCommand(command); |
| expect(() => runner.run(['rename']), throwsUsageException(''' |
| Could not find a command named "rename". |
| |
| Did you mean one of these? |
| aliased |
| ''', anything)); |
| }); |
| |
| test('Suggests based on suggestedAliases', () { |
| var command = SuggestionAliasedCommand(); |
| runner.addCommand(command); |
| expect(() => runner.run(['renamed']), throwsUsageException(''' |
| Could not find a command named "renamed". |
| |
| Did you mean one of these? |
| aliased |
| ''', anything)); |
| }); |
| }); |
| |
| group('with --help', () { |
| test('with no command prints the usage', () { |
| expect(() => runner.run(['--help']), prints(''' |
| A test command runner. |
| |
| $_defaultUsage |
| ''')); |
| }); |
| |
| test('with a preceding command prints the usage for that command', () { |
| var command = FooCommand(); |
| runner.addCommand(command); |
| |
| expect(() => runner.run(['foo', '--help']), prints(''' |
| Set a value. |
| |
| Usage: test foo [arguments] |
| -h, --help Print this usage information. |
| |
| Run "test help" to see global options. |
| ''')); |
| }); |
| |
| test('with a following command prints the usage for that command', () { |
| var command = FooCommand(); |
| runner.addCommand(command); |
| |
| expect(() => runner.run(['--help', 'foo']), prints(''' |
| Set a value. |
| |
| Usage: test foo [arguments] |
| -h, --help Print this usage information. |
| |
| Run "test help" to see global options. |
| ''')); |
| }); |
| }); |
| |
| group('with help command', () { |
| test('with no command prints the usage', () { |
| expect(() => runner.run(['help']), prints(''' |
| A test command runner. |
| |
| $_defaultUsage |
| ''')); |
| }); |
| |
| test('with a command prints the usage for that command', () { |
| var command = FooCommand(); |
| runner.addCommand(command); |
| |
| expect(() => runner.run(['help', 'foo']), prints(''' |
| Set a value. |
| |
| Usage: test foo [arguments] |
| -h, --help Print this usage information. |
| |
| Run "test help" to see global options. |
| ''')); |
| }); |
| |
| test('prints its own usage', () { |
| expect(() => runner.run(['help', 'help']), prints(''' |
| Display help information for test. |
| |
| Usage: test help [command] |
| -h, --help Print this usage information. |
| |
| Run "test help" to see global options. |
| ''')); |
| }); |
| }); |
| |
| group('with an invalid argument', () { |
| test('at the root throws the root usage', () { |
| expect( |
| runner.run(['--asdf']), |
| throwsUsageException( |
| 'Could not find an option named "asdf".', _defaultUsage)); |
| }); |
| |
| test('for a command throws the command usage', () { |
| var command = FooCommand(); |
| runner.addCommand(command); |
| |
| expect(runner.run(['foo', '--asdf']), |
| throwsUsageException('Could not find an option named "asdf".', ''' |
| Usage: test foo [arguments] |
| -h, --help Print this usage information. |
| |
| Run "test help" to see global options.''')); |
| }); |
| }); |
| }); |
| |
| group('with a footer', () { |
| setUp(() { |
| runner = CommandRunnerWithFooter('test', 'A test command runner.'); |
| }); |
| |
| test('includes the footer in the usage string', () { |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| $_defaultUsage |
| Also, footer!''')); |
| }); |
| |
| test('includes the footer in usage errors', () { |
| expect( |
| runner.run(['--bad']), |
| throwsUsageException('Could not find an option named "bad".', |
| '$_defaultUsage\nAlso, footer!')); |
| }); |
| }); |
| |
| group('with a footer and wrapping', () { |
| setUp(() { |
| runner = |
| CommandRunnerWithFooterAndWrapping('test', 'A test command runner.'); |
| }); |
| test('includes the footer in the usage string', () { |
| expect(runner.usage, equals(''' |
| A test command runner. |
| |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage |
| information. |
| |
| Available commands: |
| help Display help information for |
| test. |
| |
| Run "test help <command>" for more |
| information about a command. |
| LONG footer! This is a long footer, so |
| we can check wrapping on long footer |
| messages. |
| |
| And make sure that they preserve |
| newlines properly.''')); |
| }); |
| |
| test('includes the footer in usage errors', () { |
| expect(runner.run(['--bad']), |
| throwsUsageException('Could not find an option named "bad".', ''' |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage |
| information. |
| |
| Available commands: |
| help Display help information for |
| test. |
| |
| Run "test help <command>" for more |
| information about a command. |
| LONG footer! This is a long footer, so |
| we can check wrapping on long footer |
| messages. |
| |
| And make sure that they preserve |
| newlines properly.''')); |
| }); |
| }); |
| |
| group('throws a useful error when', () { |
| test('arg parsing fails', () { |
| expect( |
| runner.run(['--bad']), |
| throwsUsageException( |
| 'Could not find an option named "bad".', _defaultUsage)); |
| }); |
| |
| test("a top-level command doesn't exist", () { |
| expect( |
| runner.run(['bad']), |
| throwsUsageException( |
| 'Could not find a command named "bad".', _defaultUsage)); |
| }); |
| |
| test("a subcommand doesn't exist", () { |
| runner.addCommand(FooCommand()..addSubcommand(AsyncCommand())); |
| |
| expect(runner.run(['foo bad']), |
| throwsUsageException('Could not find a command named "foo bad".', ''' |
| Usage: test <command> [arguments] |
| |
| Global options: |
| -h, --help Print this usage information. |
| |
| Available commands: |
| foo Set a value. |
| |
| Run "test help <command>" for more information about a command.''')); |
| }); |
| |
| test("a subcommand wasn't passed", () { |
| runner.addCommand(FooCommand()..addSubcommand(AsyncCommand())); |
| |
| expect(runner.run(['foo']), |
| throwsUsageException('Missing subcommand for "test foo".', ''' |
| Usage: test foo <subcommand> [arguments] |
| -h, --help Print this usage information. |
| |
| Available subcommands: |
| async Set a value asynchronously. |
| |
| Run "test help" to see global options.''')); |
| }); |
| |
| test("a command that doesn't take arguments was given them", () { |
| runner.addCommand(FooCommand()); |
| |
| expect(runner.run(['foo', 'bar']), |
| throwsUsageException('Command "foo" does not take any arguments.', ''' |
| Usage: test foo [arguments] |
| -h, --help Print this usage information. |
| |
| Run "test help" to see global options.''')); |
| }); |
| }); |
| |
| test('mandatory options in commands', () async { |
| var subcommand = _MandatoryOptionCommand(); |
| runner.addCommand(subcommand); |
| expect( |
| () => runner.run([subcommand.name]), |
| throwsA(isA<ArgumentError>().having((e) => e.message, 'message', |
| contains('Option mandatory-option is mandatory')))); |
| expect(await runner.run([subcommand.name, '--mandatory-option', 'foo']), |
| 'foo'); |
| }); |
| } |
| |
| class _MandatoryOptionCommand extends Command { |
| _MandatoryOptionCommand() { |
| argParser.addOption('mandatory-option', mandatory: true); |
| } |
| |
| @override |
| String get description => 'A command with a mandatory option'; |
| |
| @override |
| String get name => 'mandatory-option-command'; |
| |
| @override |
| String run() => argResults!['mandatory-option'] as String; |
| } |