blob: cc80c6b48f498b324439da66a389e9475fecee19 [file] [log] [blame]
// 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;
}