diff options
author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-07-15 22:49:36 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-07-15 22:49:36 -0400 |
commit | 94b424e9c41f99c6eb268c6c97f09e99a8342de8 (patch) | |
tree | bcbf724e20fed985f7d05515a10d28ba32112a68 /tests | |
parent | 8109e70b0442206103fa5fe1a3af79d1851d7ec1 (diff) | |
parent | 3ad59ceffb9810b774a93448328c7c590080cc98 (diff) | |
download | cmd2-git-94b424e9c41f99c6eb268c6c97f09e99a8342de8.tar.gz |
Merge pull request #718 from python-cmd2/auto_completer_refactor
Auto completer refactor
Diffstat (limited to 'tests')
-rw-r--r-- | tests/conftest.py | 2 | ||||
-rw-r--r-- | tests/test_acargparse.py | 66 | ||||
-rw-r--r-- | tests/test_argparse_completer.py | 739 | ||||
-rw-r--r-- | tests/test_argparse_custom.py | 219 | ||||
-rw-r--r-- | tests/test_autocompletion.py | 345 | ||||
-rw-r--r-- | tests/test_cmd2.py | 31 | ||||
-rw-r--r-- | tests/test_completion.py | 19 | ||||
-rw-r--r-- | tests/transcripts/regex_set.txt | 1 |
8 files changed, 991 insertions, 431 deletions
diff --git a/tests/conftest.py b/tests/conftest.py index 8040c21d..c0aea4a6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -95,6 +95,7 @@ echo: False editor: vim feedback_to_output: False locals_in_py: False +max_completion_items: 50 prompt: (Cmd) quiet: False timing: False @@ -108,6 +109,7 @@ echo: False # Echo command issued into output editor: vim # Program used by ``edit`` feedback_to_output: False # Include nonessentials in `|`, `>` results locals_in_py: False # Allow access to your application in py via self +max_completion_items: 50 # Maximum number of CompletionItems to display during tab completion prompt: (Cmd) # The prompt issued to solicit input quiet: False # Don't print nonessential feedback timing: False # Report execution times diff --git a/tests/test_acargparse.py b/tests/test_acargparse.py deleted file mode 100644 index 436158db..00000000 --- a/tests/test_acargparse.py +++ /dev/null @@ -1,66 +0,0 @@ -# flake8: noqa E302 -""" -Unit/functional testing for argparse customizations in cmd2 -""" -import pytest -from cmd2.argparse_completer import ACArgumentParser, is_potential_flag - - -def test_acarg_narg_empty_tuple(): - with pytest.raises(ValueError) as excinfo: - parser = ACArgumentParser(prog='test') - parser.add_argument('invalid_tuple', nargs=()) - assert 'Ranged values for nargs must be a tuple of 2 integers' in str(excinfo.value) - - -def test_acarg_narg_single_tuple(): - with pytest.raises(ValueError) as excinfo: - parser = ACArgumentParser(prog='test') - parser.add_argument('invalid_tuple', nargs=(1,)) - assert 'Ranged values for nargs must be a tuple of 2 integers' in str(excinfo.value) - - -def test_acarg_narg_tuple_triple(): - with pytest.raises(ValueError) as excinfo: - parser = ACArgumentParser(prog='test') - parser.add_argument('invalid_tuple', nargs=(1, 2, 3)) - assert 'Ranged values for nargs must be a tuple of 2 integers' in str(excinfo.value) - - -def test_acarg_narg_tuple_order(): - with pytest.raises(ValueError) as excinfo: - parser = ACArgumentParser(prog='test') - parser.add_argument('invalid_tuple', nargs=(2, 1)) - assert 'Invalid nargs range. The first value must be less than the second' in str(excinfo.value) - - -def test_acarg_narg_tuple_negative(): - with pytest.raises(ValueError) as excinfo: - parser = ACArgumentParser(prog='test') - parser.add_argument('invalid_tuple', nargs=(-1, 1)) - assert 'Negative numbers are invalid for nargs range' in str(excinfo.value) - - -def test_acarg_narg_tuple_zero_base(): - parser = ACArgumentParser(prog='test') - parser.add_argument('tuple', nargs=(0, 3)) - - -def test_acarg_narg_tuple_zero_to_one(): - parser = ACArgumentParser(prog='test') - parser.add_argument('tuple', nargs=(0, 1)) - - -def test_is_potential_flag(): - parser = ACArgumentParser() - - # Not valid flags - assert not is_potential_flag('', parser) - assert not is_potential_flag('non-flag', parser) - assert not is_potential_flag('-', parser) - assert not is_potential_flag('--has space', parser) - assert not is_potential_flag('-2', parser) - - # Valid flags - assert is_potential_flag('-flag', parser) - assert is_potential_flag('--flag', parser) diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py new file mode 100644 index 00000000..4ad4c560 --- /dev/null +++ b/tests/test_argparse_completer.py @@ -0,0 +1,739 @@ +# coding=utf-8 +# flake8: noqa E302 +""" +Unit/functional testing for argparse completer in cmd2 +""" +import argparse +from typing import List + +import pytest + +import cmd2 +from cmd2 import with_argparser, CompletionItem +from cmd2.utils import StdSim, basic_complete +from .conftest import run_cmd, complete_tester + +# Lists used in our tests +static_int_choices_list = [-12, -1, -2, 0, 1, 2] +static_choices_list = ['static', 'choices', 'stop', 'here'] +choices_from_function = ['choices', 'function', 'chatty', 'smith'] +choices_from_method = ['choices', 'method', 'most', 'improved'] + +set_value_choices = ['set', 'value', 'choices'] +one_or_more_choices = ['one', 'or', 'more', 'choices'] +optional_choices = ['a', 'few', 'optional', 'choices'] +range_choices = ['some', 'range', 'choices'] +remainder_choices = ['remainder', 'choices'] + +positional_choices = ['the', 'positional', 'choices'] + +completions_from_function = ['completions', 'function', 'fairly', 'complete'] +completions_from_method = ['completions', 'method', 'missed', 'spot'] + + +def choices_function() -> List[str]: + """Function that provides choices""" + return choices_from_function + + +def completer_function(text: str, line: str, begidx: int, endidx: int) -> List[str]: + """Tab completion function""" + return basic_complete(text, line, begidx, endidx, completions_from_function) + + +# noinspection PyMethodMayBeStatic,PyUnusedLocal +class AutoCompleteTester(cmd2.Cmd): + """Cmd2 app that exercises AutoCompleter class""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + ############################################################################################################ + # Begin code related to help and command name completion + ############################################################################################################ + def _music_create(self, args: argparse.Namespace) -> None: + """Implements the 'music create' command""" + self.poutput('music create') + + def _music_create_jazz(self, args: argparse.Namespace) -> None: + """Implements the 'music create jazz' command""" + self.poutput('music create jazz') + + def _music_create_rock(self, args: argparse.Namespace) -> None: + """Implements the 'music create rock' command""" + self.poutput('music create rock') + + # Top level parser for music command + music_parser = cmd2.ArgParser(description='Manage music', prog='music') + + # Add sub-commands to music + music_subparsers = music_parser.add_subparsers() + + # music -> create + music_create_parser = music_subparsers.add_parser('create', help='Create music') + music_create_parser.set_defaults(func=_music_create) + + # Add sub-commands to music -> create + music_create_subparsers = music_create_parser.add_subparsers() + + # music -> create -> jazz + music_create_jazz_parser = music_create_subparsers.add_parser('jazz', help='Create jazz') + music_create_jazz_parser.set_defaults(func=_music_create_jazz) + + # music -> create -> rock + music_create_rock_parser = music_create_subparsers.add_parser('rock', help='Create rocks') + music_create_rock_parser.set_defaults(func=_music_create_rock) + + @with_argparser(music_parser) + def do_music(self, args: argparse.Namespace) -> None: + """Music command""" + func = getattr(args, 'func', None) + if func is not None: + # Call whatever sub-command function was selected + func(self, args) + else: + # No sub-command was provided, so call help + # noinspection PyTypeChecker + self.do_help('music') + + ############################################################################################################ + # Begin code related to flag completion + ############################################################################################################ + + # Uses default flag prefix value (-) + flag_parser = cmd2.ArgParser() + flag_parser.add_argument('-n', '--normal_flag', help='A normal flag', action='store_true') + flag_parser.add_argument('-a', '--append_flag', help='Append flag', action='append') + flag_parser.add_argument('-o', '--append_const_flag', help='Append const flag', action='append_const', const=True) + flag_parser.add_argument('-c', '--count_flag', help='Count flag', action='count') + flag_parser.add_argument('-s', '--suppressed_flag', help=argparse.SUPPRESS, action='store_true') + flag_parser.add_argument('-r', '--remainder_flag', nargs=argparse.REMAINDER, help='a remainder flag') + + @with_argparser(flag_parser) + def do_flag(self, args: argparse.Namespace) -> None: + pass + + # Uses non-default flag prefix value (+) + plus_flag_parser = cmd2.ArgParser(prefix_chars='+') + plus_flag_parser.add_argument('+n', '++normal_flag', help='A normal flag', action='store_true') + + @with_argparser(plus_flag_parser) + def do_plus_flag(self, args: argparse.Namespace) -> None: + pass + + ############################################################################################################ + # Begin code related to testing choices, choices_function, and choices_method parameters + ############################################################################################################ + def choices_method(self) -> List[str]: + """Method that provides choices""" + return choices_from_method + + def completion_item_method(self) -> List[CompletionItem]: + """Choices method that returns CompletionItems""" + items = [] + for i in range(0, 10): + main_str = 'main_str{}'.format(i) + items.append(CompletionItem(main_str, desc='blah blah')) + return items + + choices_parser = cmd2.ArgParser() + + # Flag args for choices command. Include string and non-string arg types. + choices_parser.add_argument("-l", "--list", help="a flag populated with a choices list", + choices=static_choices_list) + choices_parser.add_argument("-f", "--function", help="a flag populated with a choices function", + choices_function=choices_function) + choices_parser.add_argument("-m", "--method", help="a flag populated with a choices method", + choices_method=choices_method) + choices_parser.add_argument('-n', "--no_header", help='this arg has a no descriptive header', + choices_method=completion_item_method) + choices_parser.add_argument('-i', '--int', type=int, help='a flag with an int type', + choices=static_int_choices_list) + + # Positional args for choices command + choices_parser.add_argument("list_pos", help="a positional populated with a choices list", + choices=static_choices_list) + choices_parser.add_argument("function_pos", help="a positional populated with a choices function", + choices_function=choices_function) + choices_parser.add_argument("method_pos", help="a positional populated with a choices method", + choices_method=choices_method) + + @with_argparser(choices_parser) + def do_choices(self, args: argparse.Namespace) -> None: + pass + + ############################################################################################################ + # Begin code related to testing completer_function and completer_method parameters + ############################################################################################################ + def completer_method(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + """Tab completion method""" + return basic_complete(text, line, begidx, endidx, completions_from_method) + + completer_parser = cmd2.ArgParser() + + # Flag args for completer command + completer_parser.add_argument("-f", "--function", help="a flag using a completer function", + completer_function=completer_function) + completer_parser.add_argument("-m", "--method", help="a flag using a completer method", + completer_method=completer_method) + + # Positional args for completer command + completer_parser.add_argument("function_pos", help="a positional using a completer function", + completer_function=completer_function) + completer_parser.add_argument("method_pos", help="a positional using a completer method", + completer_method=completer_method) + + @with_argparser(completer_parser) + def do_completer(self, args: argparse.Namespace) -> None: + pass + + ############################################################################################################ + # Begin code related to nargs + ############################################################################################################ + nargs_parser = cmd2.ArgParser() + + # Flag args for nargs command + nargs_parser.add_argument("--set_value", help="a flag with a set value for nargs", nargs=2, + choices=set_value_choices) + nargs_parser.add_argument("--one_or_more", help="a flag wanting one or more args", nargs=argparse.ONE_OR_MORE, + choices=one_or_more_choices) + nargs_parser.add_argument("--optional", help="a flag with an optional value", nargs=argparse.OPTIONAL, + choices=optional_choices) + nargs_parser.add_argument("--range", help="a flag with nargs range", nargs=(1, 2), + choices=range_choices) + nargs_parser.add_argument("--remainder", help="a flag wanting remaining", nargs=argparse.REMAINDER, + choices=remainder_choices) + + nargs_parser.add_argument("normal_pos", help="a remainder positional", nargs=2, + choices=positional_choices) + nargs_parser.add_argument("remainder_pos", help="a remainder positional", nargs=argparse.REMAINDER, + choices=remainder_choices) + + @with_argparser(nargs_parser) + def do_nargs(self, args: argparse.Namespace) -> None: + pass + + ############################################################################################################ + # Begin code related to testing tab hints + ############################################################################################################ + hint_parser = cmd2.ArgParser() + hint_parser.add_argument('-f', '--flag', help='a flag arg') + hint_parser.add_argument('-s', '--suppressed_help', help=argparse.SUPPRESS) + hint_parser.add_argument('-t', '--suppressed_hint', help='a flag arg', suppress_tab_hint=True) + + hint_parser.add_argument('hint_pos', help='here is a hint\nwith new lines') + hint_parser.add_argument('no_help_pos') + + @with_argparser(hint_parser) + def do_hint(self, args: argparse.Namespace) -> None: + pass + + +@pytest.fixture +def ac_app(): + app = AutoCompleteTester() + app.stdout = StdSim(app.stdout) + return app + + +@pytest.mark.parametrize('command', [ + 'music', + 'music create', + 'music create rock', + 'music create jazz' +]) +def test_help(ac_app, command): + out1, err1 = run_cmd(ac_app, '{} -h'.format(command)) + out2, err2 = run_cmd(ac_app, 'help {}'.format(command)) + assert out1 == out2 + + +@pytest.mark.parametrize('command, text, completions', [ + ('', 'mu', ['music ']), + ('music', 'cre', ['create ']), + ('music create', '', ['jazz', 'rock']) +]) +def test_complete_help(ac_app, command, text, completions): + line = 'help {} {}'.format(command, text) + endidx = len(line) + begidx = endidx - len(text) + + first_match = complete_tester(text, line, begidx, endidx, ac_app) + if completions: + assert first_match is not None + else: + assert first_match is None + + assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key) + + +@pytest.mark.parametrize('command_and_args, text, completions', [ + # Complete all flags (suppressed will not show) + ('flag', '-', ['--append_const_flag', '--append_flag', '--count_flag', '--help', '--normal_flag', + '--remainder_flag', '-a', '-c', '-h', '-n', '-o', '-r']), + ('flag', '--', ['--append_const_flag', '--append_flag', '--count_flag', '--help', + '--normal_flag', '--remainder_flag']), + + # Complete individual flag + ('flag', '-n', ['-n ']), + ('flag', '--n', ['--normal_flag ']), + + # No flags should complete until current flag has its args + ('flag --append_flag', '-', []), + + # Complete REMAINDER flag name + ('flag', '-r', ['-r ']), + ('flag', '--r', ['--remainder_flag ']), + + # No flags after a REMAINDER should complete + ('flag -r value', '-', []), + ('flag --remainder_flag value', '--', []), + + # Suppressed flag should not complete + ('flag', '-s', []), + ('flag', '--s', []), + + # A used flag should not show in completions + ('flag -n', '--', ['--append_const_flag', '--append_flag', '--count_flag', '--help', '--remainder_flag']), + + # Flags with actions set to append, append_const, and count will always show even if they've been used + ('flag --append_const_flag -c --append_flag value', '--', ['--append_const_flag', '--append_flag', '--count_flag', + '--help', '--normal_flag', '--remainder_flag']), + + # Non-default flag prefix character (+) + ('plus_flag', '+', ['++help', '++normal_flag', '+h', '+n']), + ('plus_flag', '++', ['++help', '++normal_flag']), + + # Flag completion should not occur after '--' since that tells argparse all remaining arguments are non-flags + ('flag --', '--', []), + ('flag --help --', '--', []), + ('plus_flag --', '++', []), + ('plus_flag ++help --', '++', []) +]) +def test_autcomp_flag_completion(ac_app, command_and_args, text, completions): + line = '{} {}'.format(command_and_args, text) + endidx = len(line) + begidx = endidx - len(text) + + first_match = complete_tester(text, line, begidx, endidx, ac_app) + if completions: + assert first_match is not None + else: + assert first_match is None + + assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key) + + +@pytest.mark.parametrize('flag, text, completions', [ + ('-l', '', static_choices_list), + ('--list', 's', ['static', 'stop']), + ('-f', '', choices_from_function), + ('--function', 'ch', ['choices', 'chatty']), + ('-m', '', choices_from_method), + ('--method', 'm', ['method', 'most']), + ('-i', '', [str(i) for i in static_int_choices_list]), + ('--int', '1', ['1 ']), + ('--int', '-', ['-12', '-1', '-2']), + ('--int', '-1', ['-12', '-1']) +]) +def test_autocomp_flag_choices_completion(ac_app, flag, text, completions): + line = 'choices {} {}'.format(flag, text) + endidx = len(line) + begidx = endidx - len(text) + + first_match = complete_tester(text, line, begidx, endidx, ac_app) + if completions: + assert first_match is not None + else: + assert first_match is None + + assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key) + + +@pytest.mark.parametrize('pos, text, completions', [ + (1, '', static_choices_list), + (1, 's', ['static', 'stop']), + (2, '', choices_from_function), + (2, 'ch', ['choices', 'chatty']), + (3, '', choices_from_method), + (3, 'm', ['method', 'most']) +]) +def test_autocomp_positional_choices_completion(ac_app, pos, text, completions): + # Generate line were preceding positionals are already filled + line = 'choices {} {}'.format('foo ' * (pos - 1), text) + endidx = len(line) + begidx = endidx - len(text) + + first_match = complete_tester(text, line, begidx, endidx, ac_app) + if completions: + assert first_match is not None + else: + assert first_match is None + + assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key) + + +@pytest.mark.parametrize('flag, text, completions', [ + ('-f', '', completions_from_function), + ('--function', 'f', ['function', 'fairly']), + ('-m', '', completions_from_method), + ('--method', 'm', ['method', 'missed']) +]) +def test_autocomp_flag_completers(ac_app, flag, text, completions): + line = 'completer {} {}'.format(flag, text) + endidx = len(line) + begidx = endidx - len(text) + + first_match = complete_tester(text, line, begidx, endidx, ac_app) + if completions: + assert first_match is not None + else: + assert first_match is None + + assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key) + + +@pytest.mark.parametrize('pos, text, completions', [ + (1, '', completions_from_function), + (1, 'c', ['completions', 'complete']), + (2, '', completions_from_method), + (2, 'm', ['method', 'missed']) +]) +def test_autocomp_positional_completers(ac_app, pos, text, completions): + # Generate line were preceding positionals are already filled + line = 'completer {} {}'.format('foo ' * (pos - 1), text) + endidx = len(line) + begidx = endidx - len(text) + + first_match = complete_tester(text, line, begidx, endidx, ac_app) + if completions: + assert first_match is not None + else: + assert first_match is None + + assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key) + + +def test_autocomp_blank_token(ac_app): + """Force a blank token to make sure AutoCompleter consumes them like argparse does""" + from cmd2.argparse_completer import AutoCompleter + + blank = '' + + # Blank flag arg + text = '' + line = 'completer -m {} {}'.format(blank, text) + endidx = len(line) + begidx = endidx - len(text) + + completer = AutoCompleter(ac_app.completer_parser, ac_app) + tokens = ['completer', '-f', blank, text] + completions = completer.complete_command(tokens, text, line, begidx, endidx) + assert completions == completions_from_function + + # Blank positional arg + text = '' + line = 'completer {} {}'.format(blank, text) + endidx = len(line) + begidx = endidx - len(text) + + completer = AutoCompleter(ac_app.completer_parser, ac_app) + tokens = ['completer', blank, text] + completions = completer.complete_command(tokens, text, line, begidx, endidx) + assert completions == completions_from_method + + +@pytest.mark.parametrize('num_aliases, show_description', [ + # The number of completion results determines if the description field of CompletionItems gets displayed + # in the tab completions. The count must be greater than 1 and less than ac_app.max_completion_items, + # which defaults to 50. + (1, False), + (5, True), + (100, False) +]) +def test_completion_items(ac_app, num_aliases, show_description): + # Create aliases + for i in range(0, num_aliases): + run_cmd(ac_app, 'alias create fake{} help'.format(i)) + + assert len(ac_app.aliases) == num_aliases + + text = 'fake' + line = 'alias list {}'.format(text) + endidx = len(line) + begidx = endidx - len(text) + + first_match = complete_tester(text, line, begidx, endidx, ac_app) + assert first_match is not None + assert len(ac_app.completion_matches) == num_aliases + assert len(ac_app.display_matches) == num_aliases + + # If show_description is True, the alias's value will be in the display text + assert ('help' in ac_app.display_matches[0]) == show_description + + +@pytest.mark.parametrize('args, completions', [ + # Flag with nargs = 2 + ('--set_value', set_value_choices), + ('--set_value set', ['value', 'choices']), + + # Both args are filled. At positional arg now. + ('--set_value set value', positional_choices), + + # Using the flag again will reset the choices available + ('--set_value set value --set_value', set_value_choices), + + # Flag with nargs = ONE_OR_MORE + ('--one_or_more', one_or_more_choices), + ('--one_or_more one', ['or', 'more', 'choices']), + + # Flag with nargs = OPTIONAL + ('--optional', optional_choices), + + # Only one arg allowed for an OPTIONAL. At positional now. + ('--optional optional', positional_choices), + + # Flag with nargs range (1, 2) + ('--range', range_choices), + ('--range some', ['range', 'choices']), + + # Already used 2 args so at positional + ('--range some range', positional_choices), + + # Flag with nargs = REMAINDER + ('--remainder', remainder_choices), + ('--remainder remainder ', ['choices ']), + + # No more flags can appear after a REMAINDER flag) + ('--remainder choices --set_value', ['remainder ']), + + # Double dash ends the current flag + ('--range choice --', positional_choices), + + # Double dash ends a REMAINDER flag + ('--remainder remainder --', positional_choices), + + # No more flags after a double dash + ('-- --one_or_more ', positional_choices), + + # Consume positional + ('', positional_choices), + ('positional', ['the', 'choices']), + + # Intermixed flag and positional + ('positional --set_value', set_value_choices), + ('positional --set_value set', ['choices', 'value']), + + # Intermixed flag and positional with flag finishing + ('positional --set_value set value', ['the', 'choices']), + ('positional --range choice --', ['the', 'choices']), + + # REMAINDER positional + ('the positional', remainder_choices), + ('the positional remainder', ['choices ']), + ('the positional remainder choices', []), + + # REMAINDER positional. Flags don't work in REMAINDER + ('the positional --set_value', remainder_choices), + ('the positional remainder --set_value', ['choices ']) +]) +def test_autcomp_nargs(ac_app, args, completions): + text = '' + line = 'nargs {} {}'.format(args, text) + endidx = len(line) + begidx = endidx - len(text) + + first_match = complete_tester(text, line, begidx, endidx, ac_app) + if completions: + assert first_match is not None + else: + assert first_match is None + + assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key) + + +@pytest.mark.parametrize('command_and_args, text, is_error', [ + # Flag is finished before moving on + ('hint --flag foo --', '', False), + ('hint --flag foo --help', '', False), + ('hint --flag foo', '--', False), + + ('nargs --one_or_more one --', '', False), + ('nargs --one_or_more one or --set_value', '', False), + ('nargs --one_or_more one or more', '--', False), + + ('nargs --set_value set value --', '', False), + ('nargs --set_value set value --one_or_more', '', False), + ('nargs --set_value set value', '--', False), + ('nargs --set_val set value', '--', False), # This exercises our abbreviated flag detection + + ('nargs --range choices --', '', False), + ('nargs --range choices range --set_value', '', False), + ('nargs --range range', '--', False), + + # Flag is not finished before moving on + ('hint --flag --', '', True), + ('hint --flag --help', '', True), + ('hint --flag', '--', True), + + ('nargs --one_or_more --', '', True), + ('nargs --one_or_more --set_value', '', True), + ('nargs --one_or_more', '--', True), + + ('nargs --set_value set --', '', True), + ('nargs --set_value set --one_or_more', '', True), + ('nargs --set_value set', '--', True), + ('nargs --set_val set', '--', True), # This exercises our abbreviated flag detection + + ('nargs --range --', '', True), + ('nargs --range --set_value', '', True), + ('nargs --range', '--', True), +]) +def test_unfinished_flag_error(ac_app, command_and_args, text, is_error, capsys): + line = '{} {}'.format(command_and_args, text) + endidx = len(line) + begidx = endidx - len(text) + + complete_tester(text, line, begidx, endidx, ac_app) + + out, err = capsys.readouterr() + assert is_error == all(x in out for x in ["Error:\n", "expected"]) + + +def test_completion_items_default_header(ac_app): + from cmd2.argparse_completer import DEFAULT_DESCRIPTIVE_HEADER + + text = '' + line = 'choices -n {}'.format(text) + endidx = len(line) + begidx = endidx - len(text) + + # This positional argument did not provide a descriptive header, so it should be DEFAULT_DESCRIPTIVE_HEADER + complete_tester(text, line, begidx, endidx, ac_app) + assert DEFAULT_DESCRIPTIVE_HEADER in ac_app.completion_header + + +@pytest.mark.parametrize('command_and_args, text, has_hint', [ + # Normal cases + ('hint', '', True), + ('hint --flag', '', True), + ('hint --suppressed_help', '', False), + ('hint --suppressed_hint', '--', False), + + # Hint because flag does not have enough values to be considered finished + ('nargs --one_or_more', '-', True), + + # This flag has reached its minimum value count and therefore a new flag could start. + # However the flag can still consume values and the text is not a single prefix character. + # Therefor a hint will be shown. + ('nargs --one_or_more choices', 'bad_completion', True), + + # Like the previous case, but this time text is a single prefix character which will cause flag + # name completion to occur instead of a hint for the current flag. + ('nargs --one_or_more choices', '-', False), + + # Hint because this is a REMAINDER flag and therefore no more flag name completions occur. + ('nargs --remainder', '-', True), + + # No hint for the positional because text is a single prefix character which results in flag name completion + ('hint', '-', False), + + # Hint because this is a REMAINDER positional and therefore no more flag name completions occur. + ('nargs the choices', '-', True), + ('nargs the choices remainder', '-', True), +]) +def test_autocomp_hint(ac_app, command_and_args, text, has_hint, capsys): + line = '{} {}'.format(command_and_args, text) + endidx = len(line) + begidx = endidx - len(text) + + complete_tester(text, line, begidx, endidx, ac_app) + out, err = capsys.readouterr() + assert has_hint == ("Hint:\n" in out) + + +def test_autocomp_hint_multiple_lines(ac_app, capsys): + text = '' + line = 'hint {}'.format(text) + endidx = len(line) + begidx = endidx - len(text) + + first_match = complete_tester(text, line, begidx, endidx, ac_app) + out, err = capsys.readouterr() + + assert first_match is None + assert out == ''' +Hint: + HINT_POS here is a hint + with new lines + +''' + + +def test_autocomp_hint_no_help_text(ac_app, capsys): + text = '' + line = 'hint foo {}'.format(text) + endidx = len(line) + begidx = endidx - len(text) + + first_match = complete_tester(text, line, begidx, endidx, ac_app) + out, err = capsys.readouterr() + + assert first_match is None + assert not out == ''' +Hint: + NO_HELP_POS + +''' + + +def test_single_prefix_char(): + from cmd2.argparse_completer import _single_prefix_char + parser = cmd2.ArgParser(prefix_chars='-+') + + # Invalid + assert not _single_prefix_char('', parser) + assert not _single_prefix_char('--', parser) + assert not _single_prefix_char('-+', parser) + assert not _single_prefix_char('++has space', parser) + assert not _single_prefix_char('foo', parser) + + # Valid + assert _single_prefix_char('-', parser) + assert _single_prefix_char('+', parser) + + +def test_looks_like_flag(): + from cmd2.argparse_completer import _looks_like_flag + parser = cmd2.ArgParser() + + # Does not start like a flag + assert not _looks_like_flag('', parser) + assert not _looks_like_flag('non-flag', parser) + assert not _looks_like_flag('-', parser) + assert not _looks_like_flag('--has space', parser) + assert not _looks_like_flag('-2', parser) + + # Does start like a flag + assert _looks_like_flag('--', parser) + assert _looks_like_flag('-flag', parser) + assert _looks_like_flag('--flag', parser) + + +def test_complete_command_no_tokens(ac_app): + from cmd2.argparse_completer import AutoCompleter + + parser = cmd2.ArgParser() + ac = AutoCompleter(parser, ac_app) + + completions = ac.complete_command(tokens=[], text='', line='', begidx=0, endidx=0) + assert not completions + + +def test_complete_command_help_no_tokens(ac_app): + from cmd2.argparse_completer import AutoCompleter + + parser = cmd2.ArgParser() + ac = AutoCompleter(parser, ac_app) + + completions = ac.complete_command_help(tokens=[], text='', line='', begidx=0, endidx=0) + assert not completions diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py new file mode 100644 index 00000000..caf30080 --- /dev/null +++ b/tests/test_argparse_custom.py @@ -0,0 +1,219 @@ +# flake8: noqa E302 +""" +Unit/functional testing for argparse customizations in cmd2 +""" +import argparse + +import pytest + +import cmd2 +from cmd2.argparse_custom import generate_range_error, INFINITY +from .conftest import run_cmd + + +class ApCustomTestApp(cmd2.Cmd): + """Test app for cmd2's argparse customization""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + range_parser = cmd2.ArgParser() + range_parser.add_argument('--arg0', nargs=1) + range_parser.add_argument('--arg1', nargs=2) + range_parser.add_argument('--arg2', nargs=(3,)) + range_parser.add_argument('--arg3', nargs=(2, 3)) + range_parser.add_argument('--arg4', nargs=argparse.ZERO_OR_MORE) + range_parser.add_argument('--arg5', nargs=argparse.ONE_OR_MORE) + + @cmd2.with_argparser(range_parser) + def do_range(self, _): + pass + + +@pytest.fixture +def cust_app(): + return ApCustomTestApp() + + +def fake_func(): + pass + + +@pytest.mark.parametrize('args, is_valid', [ + ({'choices': []}, True), + ({'choices_function': fake_func}, True), + ({'choices_method': fake_func}, True), + ({'completer_function': fake_func}, True), + ({'completer_method': fake_func}, True), + ({'choices': [], 'choices_function': fake_func}, False), + ({'choices': [], 'choices_method': fake_func}, False), + ({'choices_method': fake_func, 'completer_function': fake_func}, False), + ({'choices_method': fake_func, 'completer_method': fake_func}, False), +]) +def test_apcustom_invalid_args(args, is_valid): + parser = cmd2.ArgParser(prog='test') + try: + parser.add_argument('name', **args) + assert is_valid + except ValueError as ex: + assert not is_valid + assert 'Only one of the following may be used' in str(ex) + + +def test_apcustom_usage(): + usage = "A custom usage statement" + parser = cmd2.ArgParser(usage=usage) + assert usage in parser.format_help() + + +def test_apcustom_nargs_help_format(cust_app): + out, err = run_cmd(cust_app, 'help range') + assert 'Usage: range [-h] [--arg0 ARG0] [--arg1 ARG1{2}] [--arg2 ARG2{3+}]' in out[0] + assert ' [--arg3 ARG3{2..3}] [--arg4 [ARG4 [...]]] [--arg5 ARG5 [...]]' in out[1] + + +def test_apcustom_nargs_range_validation(cust_app): + # nargs = (3,) + out, err = run_cmd(cust_app, 'range --arg2 one two') + assert 'Error: argument --arg2: expected at least 3 arguments' in err[2] + + out, err = run_cmd(cust_app, 'range --arg2 one two three') + assert not err + + out, err = run_cmd(cust_app, 'range --arg2 one two three four') + assert not err + + # nargs = (2,3) + out, err = run_cmd(cust_app, 'range --arg3 one') + assert 'Error: argument --arg3: expected 2 to 3 arguments' in err[2] + + out, err = run_cmd(cust_app, 'range --arg3 one two') + assert not err + + out, err = run_cmd(cust_app, 'range --arg2 one two three') + assert not err + + +@pytest.mark.parametrize('nargs_tuple', [ + (), + ('f', 5), + (5, 'f'), + (1, 2, 3), +]) +def test_apcustom_narg_invalid_tuples(nargs_tuple): + with pytest.raises(ValueError) as excinfo: + parser = cmd2.ArgParser(prog='test') + parser.add_argument('invalid_tuple', nargs=nargs_tuple) + assert 'Ranged values for nargs must be a tuple of 1 or 2 integers' in str(excinfo.value) + + +def test_apcustom_narg_tuple_order(): + with pytest.raises(ValueError) as excinfo: + parser = cmd2.ArgParser(prog='test') + parser.add_argument('invalid_tuple', nargs=(2, 1)) + assert 'Invalid nargs range. The first value must be less than the second' in str(excinfo.value) + + +def test_apcustom_narg_tuple_negative(): + with pytest.raises(ValueError) as excinfo: + parser = cmd2.ArgParser(prog='test') + parser.add_argument('invalid_tuple', nargs=(-1, 1)) + assert 'Negative numbers are invalid for nargs range' in str(excinfo.value) + + +# noinspection PyUnresolvedReferences +def test_apcustom_narg_tuple_zero_base(): + parser = cmd2.ArgParser(prog='test') + arg = parser.add_argument('arg', nargs=(0,)) + assert arg.nargs == argparse.ZERO_OR_MORE + assert arg.nargs_range is None + assert "[arg [...]]" in parser.format_help() + + parser = cmd2.ArgParser(prog='test') + arg = parser.add_argument('arg', nargs=(0, 1)) + assert arg.nargs == argparse.OPTIONAL + assert arg.nargs_range is None + assert "[arg]" in parser.format_help() + + parser = cmd2.ArgParser(prog='test') + arg = parser.add_argument('arg', nargs=(0, 3)) + assert arg.nargs == argparse.ZERO_OR_MORE + assert arg.nargs_range == (0, 3) + assert "arg{0..3}" in parser.format_help() + + +# noinspection PyUnresolvedReferences +def test_apcustom_narg_tuple_one_base(): + parser = cmd2.ArgParser(prog='test') + arg = parser.add_argument('arg', nargs=(1,)) + assert arg.nargs == argparse.ONE_OR_MORE + assert arg.nargs_range is None + assert "arg [...]" in parser.format_help() + + parser = cmd2.ArgParser(prog='test') + arg = parser.add_argument('arg', nargs=(1, 5)) + assert arg.nargs == argparse.ONE_OR_MORE + assert arg.nargs_range == (1, 5) + assert "arg{1..5}" in parser.format_help() + + +# noinspection PyUnresolvedReferences +def test_apcustom_narg_tuple_other_ranges(): + + # Test range with no upper bound on max + parser = cmd2.ArgParser(prog='test') + arg = parser.add_argument('arg', nargs=(2,)) + assert arg.nargs == argparse.ONE_OR_MORE + assert arg.nargs_range == (2, INFINITY) + + # Test finite range + parser = cmd2.ArgParser(prog='test') + arg = parser.add_argument('arg', nargs=(2, 5)) + assert arg.nargs == argparse.ONE_OR_MORE + assert arg.nargs_range == (2, 5) + + +def test_apcustom_print_message(capsys): + import sys + test_message = 'The test message' + + # Specify the file + parser = cmd2.ArgParser(prog='test') + parser._print_message(test_message, file=sys.stdout) + out, err = capsys.readouterr() + assert test_message in out + + # Make sure file defaults to sys.stderr + parser = cmd2.ArgParser(prog='test') + parser._print_message(test_message) + out, err = capsys.readouterr() + assert test_message in err + + +def test_generate_range_error(): + # max is INFINITY + err_str = generate_range_error(1, INFINITY) + assert err_str == "expected at least 1 argument" + + err_str = generate_range_error(2, INFINITY) + assert err_str == "expected at least 2 arguments" + + # min and max are equal + err_str = generate_range_error(1, 1) + assert err_str == "expected 1 argument" + + err_str = generate_range_error(2, 2) + assert err_str == "expected 2 arguments" + + # min and max are not equal + err_str = generate_range_error(0, 1) + assert err_str == "expected 0 to 1 argument" + + err_str = generate_range_error(0, 2) + assert err_str == "expected 0 to 2 arguments" + + +def test_apcustom_required_options(): + # Make sure a 'required arguments' section shows when a flag is marked required + parser = cmd2.ArgParser(prog='test') + parser.add_argument('--required_flag', required=True) + assert 'required arguments' in parser.format_help() diff --git a/tests/test_autocompletion.py b/tests/test_autocompletion.py deleted file mode 100644 index 4e1ceff0..00000000 --- a/tests/test_autocompletion.py +++ /dev/null @@ -1,345 +0,0 @@ -# coding=utf-8 -# flake8: noqa E302 -""" -Unit/functional testing for argparse completer in cmd2 -""" -import pytest - -from cmd2.utils import StdSim -from .conftest import run_cmd, normalize, complete_tester - -from examples.tab_autocompletion import TabCompleteExample - -@pytest.fixture -def cmd2_app(): - app = TabCompleteExample() - app.stdout = StdSim(app.stdout) - return app - - -SUGGEST_HELP = '''Usage: suggest -t {movie, show} [-h] [-d DURATION{1..2}] - -Suggest command demonstrates argparse customizations. -See hybrid_suggest and orig_suggest to compare the help output. - -required arguments: - -t, --type {movie, show} - -optional arguments: - -h, --help show this help message and exit - -d, --duration DURATION{1..2} - Duration constraint in minutes. - single value - maximum duration - [a, b] - duration range''' - -MEDIA_MOVIES_ADD_HELP = '''Usage: media movies add -d DIRECTOR{1..2} - [-h] - title {G, PG, PG-13, R, NC-17} ... - -positional arguments: - title Movie Title - {G, PG, PG-13, R, NC-17} - Movie Rating - actor Actors - -required arguments: - -d, --director DIRECTOR{1..2} - Director - -optional arguments: - -h, --help show this help message and exit''' - -def test_help_required_group(cmd2_app): - out1, err1 = run_cmd(cmd2_app, 'suggest -h') - out2, err2 = run_cmd(cmd2_app, 'help suggest') - - assert out1 == out2 - assert out1[0].startswith('Usage: suggest') - assert out1[1] == '' - assert out1[2].startswith('Suggest command demonstrates argparse customizations.') - assert out1 == normalize(SUGGEST_HELP) - - -def test_help_required_group_long(cmd2_app): - out1, err1 = run_cmd(cmd2_app, 'media movies add -h') - out2, err2 = run_cmd(cmd2_app, 'help media movies add') - - assert out1 == out2 - assert out1[0].startswith('Usage: media movies add') - assert out1 == normalize(MEDIA_MOVIES_ADD_HELP) - - -def test_autocomp_flags(cmd2_app): - text = '-' - line = 'suggest {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and \ - cmd2_app.completion_matches == ['--duration', '--help', '--type', '-d', '-h', '-t'] - -def test_autcomp_hint(cmd2_app, capsys): - text = '' - line = 'suggest -d {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - out, err = capsys.readouterr() - - assert out == ''' -Hint: - -d, --duration DURATION Duration constraint in minutes. - single value - maximum duration - [a, b] - duration range - -''' - -def test_autcomp_flag_comp(cmd2_app, capsys): - text = '--d' - line = 'suggest {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - out, err = capsys.readouterr() - - assert first_match is not None and \ - cmd2_app.completion_matches == ['--duration '] - - -def test_autocomp_flags_choices(cmd2_app): - text = '' - line = 'suggest -t {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and \ - cmd2_app.completion_matches == ['movie', 'show'] - - -def test_autcomp_hint_in_narg_range(cmd2_app, capsys): - text = '' - line = 'suggest -d 2 {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - out, err = capsys.readouterr() - - assert out == ''' -Hint: - -d, --duration DURATION Duration constraint in minutes. - single value - maximum duration - [a, b] - duration range - -''' - -def test_autocomp_flags_narg_max(cmd2_app): - text = '' - line = 'suggest d 2 3 {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is None - - -def test_autcomp_narg_beyond_max(cmd2_app): - out, err = run_cmd(cmd2_app, 'suggest -t movie -d 3 4 5') - assert 'Error: unrecognized arguments: 5' in err[1] - - -def test_autocomp_subcmd_nested(cmd2_app): - text = '' - line = 'media movies {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and \ - cmd2_app.completion_matches == ['add', 'delete', 'list', 'load'] - - -def test_autocomp_subcmd_flag_choices_append(cmd2_app): - text = '' - line = 'media movies list -r {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and \ - cmd2_app.completion_matches == ['G', 'NC-17', 'PG', 'PG-13', 'R'] - -def test_autocomp_subcmd_flag_choices_append_exclude(cmd2_app): - text = '' - line = 'media movies list -r PG PG-13 {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and \ - cmd2_app.completion_matches == ['G', 'NC-17', 'R'] - - -def test_autocomp_subcmd_flag_comp_func(cmd2_app): - text = 'A' - line = 'media movies list -a "{}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and \ - cmd2_app.completion_matches == ['Adam Driver', 'Alec Guinness', 'Andy Serkis', 'Anthony Daniels'] - - -def test_autocomp_subcmd_flag_comp_list(cmd2_app): - text = 'G' - line = 'media movies list -d {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and first_match == '"Gareth Edwards' - - -def test_autocomp_subcmd_flag_comp_func_attr(cmd2_app): - text = 'A' - line = 'video movies list -a "{}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and \ - cmd2_app.completion_matches == ['Adam Driver', 'Alec Guinness', 'Andy Serkis', 'Anthony Daniels'] - - -def test_autocomp_subcmd_flag_comp_list_attr(cmd2_app): - text = 'G' - line = 'video movies list -d {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and first_match == '"Gareth Edwards' - - -def test_autocomp_pos_consumed(cmd2_app): - text = '' - line = 'library movie add SW_EP01 {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is None - - -def test_autocomp_pos_after_flag(cmd2_app): - text = 'Joh' - line = 'video movies add -d "George Lucas" -- "Han Solo" PG "Emilia Clarke" "{}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and \ - cmd2_app.completion_matches == ['John Boyega" '] - - -def test_autocomp_custom_func_list_arg(cmd2_app): - text = 'SW_' - line = 'library show add {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and \ - cmd2_app.completion_matches == ['SW_CW', 'SW_REB', 'SW_TCW'] - - -def test_autocomp_custom_func_list_and_dict_arg(cmd2_app): - text = '' - line = 'library show add SW_REB {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and \ - cmd2_app.completion_matches == ['S01E02', 'S01E03', 'S02E01', 'S02E03'] - - -def test_autocomp_custom_func_dict_arg(cmd2_app): - text = '/home/user/' - line = 'video movies load {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and \ - cmd2_app.completion_matches == ['/home/user/another.db', '/home/user/file space.db', '/home/user/file.db'] - - -def test_argparse_remainder_flag_completion(cmd2_app): - import cmd2 - import argparse - - # Test flag completion as first arg of positional with nargs=argparse.REMAINDER - text = '--h' - line = 'help command {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - # --h should not complete into --help because we are in the argparse.REMAINDER section - assert complete_tester(text, line, begidx, endidx, cmd2_app) is None - - # Test flag completion within an already started positional with nargs=argparse.REMAINDER - text = '--h' - line = 'help command subcommand {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - # --h should not complete into --help because we are in the argparse.REMAINDER section - assert complete_tester(text, line, begidx, endidx, cmd2_app) is None - - # Test a flag with nargs=argparse.REMAINDER - parser = argparse.ArgumentParser() - parser.add_argument('-f', nargs=argparse.REMAINDER) - - # Overwrite eof's parser for this test - cmd2.Cmd.do_eof.argparser = parser - - text = '--h' - line = 'eof -f {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - # --h should not complete into --help because we are in the argparse.REMAINDER section - assert complete_tester(text, line, begidx, endidx, cmd2_app) is None - - -def test_completion_after_double_dash(cmd2_app): - """ - Test completion after --, which argparse says (all args after -- are non-options) - All of these tests occur outside of an argparse.REMAINDER section since those tests - are handled in test_argparse_remainder_flag_completion - """ - - # Test -- as the last token - text = '--' - line = 'help {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - # Since -- is the last token, then it should show flag choices - first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and '--help' in cmd2_app.completion_matches - - # Test -- to end all flag completion - text = '--' - line = 'help -- {}'.format(text) - endidx = len(line) - begidx = endidx - len(text) - - # Since -- appeared before the -- being completed, nothing should be completed - assert complete_tester(text, line, begidx, endidx, cmd2_app) is None diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 9ffe547a..1bdbea5f 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1504,22 +1504,33 @@ invalid_command_name = [ 'noembedded"quotes', ] -def test_get_alias_names(base_app): - assert len(base_app.aliases) == 0 +def test_get_alias_completion_items(base_app): run_cmd(base_app, 'alias create fake run_pyscript') run_cmd(base_app, 'alias create ls !ls -hal') - assert len(base_app.aliases) == 2 - assert sorted(base_app._get_alias_names()) == ['fake', 'ls'] -def test_get_macro_names(base_app): - assert len(base_app.macros) == 0 + results = base_app._get_alias_completion_items() + assert len(results) == len(base_app.aliases) + + for cur_res in results: + assert cur_res in base_app.aliases + assert cur_res.description == base_app.aliases[cur_res] + +def test_get_macro_completion_items(base_app): run_cmd(base_app, 'macro create foo !echo foo') run_cmd(base_app, 'macro create bar !echo bar') - assert len(base_app.macros) == 2 - assert sorted(base_app._get_macro_names()) == ['bar', 'foo'] -def test_get_settable_names(base_app): - assert sorted(base_app._get_settable_names()) == sorted(base_app.settable.keys()) + results = base_app._get_macro_completion_items() + assert len(results) == len(base_app.macros) + + for cur_res in results: + assert cur_res in base_app.macros + assert cur_res.description == base_app.macros[cur_res].value + +def test_get_settable_completion_items(base_app): + results = base_app._get_settable_completion_items() + for cur_res in results: + assert cur_res in base_app.settable + assert cur_res.description == base_app.settable[cur_res] def test_alias_no_subcommand(base_app): out, err = run_cmd(base_app, 'alias') diff --git a/tests/test_completion.py b/tests/test_completion.py index 5cfc741c..1411cc49 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -67,7 +67,7 @@ class CompletionsExample(cmd2.Cmd): pass def complete_test_basic(self, text, line, begidx, endidx): - return self.basic_complete(text, line, begidx, endidx, food_item_strs) + return utils.basic_complete(text, line, begidx, endidx, food_item_strs) def do_test_delimited(self, args): pass @@ -80,7 +80,7 @@ class CompletionsExample(cmd2.Cmd): def complete_test_sort_key(self, text, line, begidx, endidx): num_strs = ['2', '11', '1'] - return self.basic_complete(text, line, begidx, endidx, num_strs) + return utils.basic_complete(text, line, begidx, endidx, num_strs) def do_test_raise_exception(self, args): pass @@ -516,7 +516,7 @@ def test_path_completion_directories_only(cmd2_app, request): expected = [text + 'cripts' + os.path.sep] - assert cmd2_app.path_complete(text, line, begidx, endidx, os.path.isdir) == expected + assert cmd2_app.path_complete(text, line, begidx, endidx, path_filter=os.path.isdir) == expected def test_basic_completion_single(cmd2_app): text = 'Pi' @@ -524,7 +524,7 @@ def test_basic_completion_single(cmd2_app): endidx = len(line) begidx = endidx - len(text) - assert cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs) == ['Pizza'] + assert utils.basic_complete(text, line, begidx, endidx, food_item_strs) == ['Pizza'] def test_basic_completion_multiple(cmd2_app): text = '' @@ -532,7 +532,7 @@ def test_basic_completion_multiple(cmd2_app): endidx = len(line) begidx = endidx - len(text) - matches = sorted(cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs)) + matches = sorted(utils.basic_complete(text, line, begidx, endidx, food_item_strs)) assert matches == sorted(food_item_strs) def test_basic_completion_nomatch(cmd2_app): @@ -541,7 +541,7 @@ def test_basic_completion_nomatch(cmd2_app): endidx = len(line) begidx = endidx - len(text) - assert cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs) == [] + assert utils.basic_complete(text, line, begidx, endidx, food_item_strs) == [] def test_delimiter_completion(cmd2_app): text = '/home/' @@ -592,7 +592,7 @@ def test_flag_based_default_completer(cmd2_app, request): begidx = endidx - len(text) assert cmd2_app.flag_based_complete(text, line, begidx, endidx, - flag_dict, cmd2_app.path_complete) == [text + 'onftest.py'] + flag_dict, all_else=cmd2_app.path_complete) == [text + 'onftest.py'] def test_flag_based_callable_completer(cmd2_app, request): test_dir = os.path.dirname(request.module.__file__) @@ -642,7 +642,7 @@ def test_index_based_default_completer(cmd2_app, request): begidx = endidx - len(text) assert cmd2_app.index_based_complete(text, line, begidx, endidx, - index_dict, cmd2_app.path_complete) == [text + 'onftest.py'] + index_dict, all_else=cmd2_app.path_complete) == [text + 'onftest.py'] def test_index_based_callable_completer(cmd2_app, request): test_dir = os.path.dirname(request.module.__file__) @@ -1072,8 +1072,7 @@ class SubcommandsWithUnknownExample(cmd2.Cmd): # create the parser for the "sport" sub-command parser_sport = base_subparsers.add_parser('sport', help='sport help') - sport_arg = parser_sport.add_argument('sport', help='Enter name of a sport') - setattr(sport_arg, 'arg_choices', sport_item_strs) + sport_arg = parser_sport.add_argument('sport', help='Enter name of a sport', choices=sport_item_strs) @cmd2.with_argparser_and_unknown_args(base_parser) def do_base(self, args): diff --git a/tests/transcripts/regex_set.txt b/tests/transcripts/regex_set.txt index 02bc9875..fdcca3a8 100644 --- a/tests/transcripts/regex_set.txt +++ b/tests/transcripts/regex_set.txt @@ -11,6 +11,7 @@ echo: False editor: /.*/ feedback_to_output: False locals_in_py: False +max_completion_items: 50 maxrepeats: 3 prompt: (Cmd)/ / quiet: False |