-
Notifications
You must be signed in to change notification settings - Fork 127
Bash completion #386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Bash completion #386
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
94156f8
Figured out how to detect the second tab press. Writing parameter hin…
anselor 6efe721
Adds some semblance of testing for bash completion. Tests the complet…
anselor efc6ab8
Added argcomplete to unit test environment. Added exclusion for Windows
anselor 7d07826
Maybe this will do the trick.
anselor ea5eb8e
Another attempt at getting it working on travis.
anselor 0f1283e
stupid typo. One more try.
anselor 3e0e3b1
OK, giving up. Disabling bash completion test on travis.
anselor 7486bae
Skip a couple tests on macOS which were problematic on my computer
tleonhardt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| # coding=utf-8 | ||
| """ | ||
| Unit/functional testing for argparse completer in cmd2 | ||
|
|
||
| Copyright 2018 Eric Lin <anselor@gmail.com> | ||
| Released under MIT license, see LICENSE file | ||
| """ | ||
| import os | ||
| import pytest | ||
| import sys | ||
| from typing import List | ||
|
|
||
| from cmd2.argparse_completer import ACArgumentParser, AutoCompleter | ||
|
|
||
|
|
||
| try: | ||
| from cmd2.argcomplete_bridge import CompletionFinder | ||
| skip_reason1 = False | ||
| skip_reason = '' | ||
| except ImportError: | ||
| # Don't test if argcomplete isn't present (likely on Windows) | ||
| skip_reason1 = True | ||
| skip_reason = "argcomplete isn't installed\n" | ||
|
|
||
| skip_reason2 = "TRAVIS" in os.environ and os.environ["TRAVIS"] == "true" | ||
| if skip_reason2: | ||
| skip_reason += 'These tests cannot run on TRAVIS\n' | ||
|
|
||
| skip_reason3 = sys.platform.startswith('win') | ||
| if skip_reason3: | ||
| skip_reason = 'argcomplete doesn\'t support Windows' | ||
|
|
||
| skip = skip_reason1 or skip_reason2 or skip_reason3 | ||
|
|
||
| skip_mac = sys.platform.startswith('dar') | ||
|
|
||
|
|
||
| actors = ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher', 'Alec Guinness', 'Peter Mayhew', | ||
| 'Anthony Daniels', 'Adam Driver', 'Daisy Ridley', 'John Boyega', 'Oscar Isaac', | ||
| 'Lupita Nyong\'o', 'Andy Serkis', 'Liam Neeson', 'Ewan McGregor', 'Natalie Portman', | ||
| 'Jake Lloyd', 'Hayden Christensen', 'Christopher Lee'] | ||
|
|
||
|
|
||
| def query_actors() -> List[str]: | ||
| """Simulating a function that queries and returns a completion values""" | ||
| return actors | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def parser1(): | ||
| """creates a argparse object to test completion against""" | ||
| ratings_types = ['G', 'PG', 'PG-13', 'R', 'NC-17'] | ||
|
|
||
| def _do_media_movies(self, args) -> None: | ||
| if not args.command: | ||
| self.do_help('media movies') | ||
| else: | ||
| print('media movies ' + str(args.__dict__)) | ||
|
|
||
| def _do_media_shows(self, args) -> None: | ||
| if not args.command: | ||
| self.do_help('media shows') | ||
|
|
||
| if not args.command: | ||
| self.do_help('media shows') | ||
| else: | ||
| print('media shows ' + str(args.__dict__)) | ||
|
|
||
| media_parser = ACArgumentParser(prog='media') | ||
|
|
||
| media_types_subparsers = media_parser.add_subparsers(title='Media Types', dest='type') | ||
|
|
||
| movies_parser = media_types_subparsers.add_parser('movies') | ||
| movies_parser.set_defaults(func=_do_media_movies) | ||
|
|
||
| movies_commands_subparsers = movies_parser.add_subparsers(title='Commands', dest='command') | ||
|
|
||
| movies_list_parser = movies_commands_subparsers.add_parser('list') | ||
|
|
||
| movies_list_parser.add_argument('-t', '--title', help='Title Filter') | ||
| movies_list_parser.add_argument('-r', '--rating', help='Rating Filter', nargs='+', | ||
| choices=ratings_types) | ||
| movies_list_parser.add_argument('-d', '--director', help='Director Filter') | ||
| movies_list_parser.add_argument('-a', '--actor', help='Actor Filter', action='append') | ||
|
|
||
| movies_add_parser = movies_commands_subparsers.add_parser('add') | ||
| movies_add_parser.add_argument('title', help='Movie Title') | ||
| movies_add_parser.add_argument('rating', help='Movie Rating', choices=ratings_types) | ||
| movies_add_parser.add_argument('-d', '--director', help='Director', nargs=(1, 2), required=True) | ||
| movies_add_parser.add_argument('actor', help='Actors', nargs='*') | ||
|
|
||
| movies_commands_subparsers.add_parser('delete') | ||
|
|
||
| shows_parser = media_types_subparsers.add_parser('shows') | ||
| shows_parser.set_defaults(func=_do_media_shows) | ||
|
|
||
| shows_commands_subparsers = shows_parser.add_subparsers(title='Commands', dest='command') | ||
|
|
||
| shows_commands_subparsers.add_parser('list') | ||
|
|
||
| return media_parser | ||
|
|
||
|
|
||
| # noinspection PyShadowingNames | ||
| @pytest.mark.skipif(skip, reason=skip_reason) | ||
| def test_bash_nocomplete(parser1): | ||
| completer = CompletionFinder() | ||
| result = completer(parser1, AutoCompleter(parser1)) | ||
| assert result is None | ||
|
|
||
|
|
||
| # save the real os.fdopen | ||
| os_fdopen = os.fdopen | ||
|
|
||
|
|
||
| def my_fdopen(fd, mode, *args): | ||
| """mock fdopen that redirects 8 and 9 from argcomplete to stdin/stdout for testing""" | ||
| if fd > 7: | ||
| return os_fdopen(fd - 7, mode, *args) | ||
| return os_fdopen(fd, mode) | ||
|
|
||
|
|
||
| # noinspection PyShadowingNames | ||
| @pytest.mark.skipif(skip, reason=skip_reason) | ||
| def test_invalid_ifs(parser1, mock): | ||
| completer = CompletionFinder() | ||
|
|
||
| mock.patch.dict(os.environ, {'_ARGCOMPLETE': '1', | ||
| '_ARGCOMPLETE_IFS': '\013\013'}) | ||
|
|
||
| mock.patch.object(os, 'fdopen', my_fdopen) | ||
|
|
||
| with pytest.raises(SystemExit): | ||
| completer(parser1, AutoCompleter(parser1), exit_method=sys.exit) | ||
|
|
||
|
|
||
| # noinspection PyShadowingNames | ||
| @pytest.mark.skipif(skip or skip_mac, reason=skip_reason) | ||
| @pytest.mark.parametrize('comp_line, exp_out, exp_err', [ | ||
| ('media ', 'movies\013shows', ''), | ||
| ('media mo', 'movies', ''), | ||
| ('media movies add ', '\013\013 ', ''' | ||
| Hint: | ||
| TITLE Movie Title'''), | ||
| ('media movies list -a "J', '"John Boyega"\013"Jake Lloyd"', ''), | ||
| ('media movies list ', '', '') | ||
| ]) | ||
| def test_commands(parser1, capfd, mock, comp_line, exp_out, exp_err): | ||
| completer = CompletionFinder() | ||
|
|
||
| mock.patch.dict(os.environ, {'_ARGCOMPLETE': '1', | ||
| '_ARGCOMPLETE_IFS': '\013', | ||
| 'COMP_TYPE': '63', | ||
| 'COMP_LINE': comp_line, | ||
| 'COMP_POINT': str(len(comp_line))}) | ||
|
|
||
| mock.patch.object(os, 'fdopen', my_fdopen) | ||
|
|
||
| with pytest.raises(SystemExit): | ||
| choices = {'actor': query_actors, # function | ||
| } | ||
| autocompleter = AutoCompleter(parser1, arg_choices=choices) | ||
| completer(parser1, autocompleter, exit_method=sys.exit) | ||
|
|
||
| out, err = capfd.readouterr() | ||
| assert out == exp_out | ||
| assert err == exp_err | ||
|
|
||
|
|
||
| def fdopen_fail_8(fd, mode, *args): | ||
| """mock fdopen that forces failure if fd == 8""" | ||
| if fd == 8: | ||
| raise IOError() | ||
| return my_fdopen(fd, mode, *args) | ||
|
|
||
|
|
||
| # noinspection PyShadowingNames | ||
| @pytest.mark.skipif(skip, reason=skip_reason) | ||
| def test_fail_alt_stdout(parser1, mock): | ||
| completer = CompletionFinder() | ||
|
|
||
| comp_line = 'media movies list ' | ||
| mock.patch.dict(os.environ, {'_ARGCOMPLETE': '1', | ||
| '_ARGCOMPLETE_IFS': '\013', | ||
| 'COMP_TYPE': '63', | ||
| 'COMP_LINE': comp_line, | ||
| 'COMP_POINT': str(len(comp_line))}) | ||
| mock.patch.object(os, 'fdopen', fdopen_fail_8) | ||
|
|
||
| try: | ||
| choices = {'actor': query_actors, # function | ||
| } | ||
| autocompleter = AutoCompleter(parser1, arg_choices=choices) | ||
| completer(parser1, autocompleter, exit_method=sys.exit) | ||
| except SystemExit as err: | ||
| assert err.code == 1 | ||
|
|
||
|
|
||
| def fdopen_fail_9(fd, mode, *args): | ||
| """mock fdopen that forces failure if fd == 9""" | ||
| if fd == 9: | ||
| raise IOError() | ||
| return my_fdopen(fd, mode, *args) | ||
|
|
||
|
|
||
| # noinspection PyShadowingNames | ||
| @pytest.mark.skipif(skip or skip_mac, reason=skip_reason) | ||
| def test_fail_alt_stderr(parser1, capfd, mock): | ||
| completer = CompletionFinder() | ||
|
|
||
| comp_line = 'media movies add ' | ||
| exp_out = '\013\013 ' | ||
| exp_err = ''' | ||
| Hint: | ||
| TITLE Movie Title''' | ||
|
|
||
| mock.patch.dict(os.environ, {'_ARGCOMPLETE': '1', | ||
| '_ARGCOMPLETE_IFS': '\013', | ||
| 'COMP_TYPE': '63', | ||
| 'COMP_LINE': comp_line, | ||
| 'COMP_POINT': str(len(comp_line))}) | ||
| mock.patch.object(os, 'fdopen', fdopen_fail_9) | ||
|
|
||
| with pytest.raises(SystemExit): | ||
| choices = {'actor': query_actors, # function | ||
| } | ||
| autocompleter = AutoCompleter(parser1, arg_choices=choices) | ||
| completer(parser1, autocompleter, exit_method=sys.exit) | ||
|
|
||
| out, err = capfd.readouterr() | ||
| assert out == exp_out | ||
| assert err == exp_err |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strange. But if it works, cool.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. The first tab press comes in as 9. Makes total sense.