1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
|
# coding=utf-8
# flake8: noqa E302
"""
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 shlex
import sys
from typing import List
from cmd2.argparse_completer import ACArgumentParser, AutoCompleter
try:
from cmd2.argcomplete_bridge import CompletionFinder, tokens_for_completion
skip_no_argcomplete = False
skip_reason = ''
except ImportError:
# Don't test if argcomplete isn't present (likely on Windows)
skip_no_argcomplete = True
skip_reason = "argcomplete isn't installed\n"
skip_travis = "TRAVIS" in os.environ and os.environ["TRAVIS"] == "true"
if skip_travis:
skip_reason += 'These tests cannot run on TRAVIS\n'
skip_windows = sys.platform.startswith('win')
if skip_windows:
skip_reason = 'argcomplete doesn\'t support Windows'
skip = skip_no_argcomplete or skip_travis or skip_windows
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_no_argcomplete or skip_windows, 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_no_argcomplete or skip_windows, 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 list -a "J', '"John Boyega"\013"Jake Lloyd"', ''),
('media movies list ', '', ''),
('media movies add ', '\013\013 ', '''
Hint:
TITLE Movie Title'''),
])
def test_commands(parser1, capfd, mock, comp_line, exp_out, exp_err):
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):
completer = CompletionFinder()
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_no_argcomplete or skip_windows, 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
@pytest.mark.skipif(skip_no_argcomplete, reason=skip_reason)
def test_argcomplete_tokens_for_completion_simple():
line = 'this is "a test"'
endidx = len(line)
tokens, raw_tokens, begin_idx, end_idx = tokens_for_completion(line, endidx)
assert tokens == shlex.split(line)
assert raw_tokens == ['this', 'is', '"a test"']
assert begin_idx == line.rfind("is ") + len("is ")
assert end_idx == end_idx
@pytest.mark.skipif(skip_no_argcomplete, reason=skip_reason)
def test_argcomplete_tokens_for_completion_unclosed_quotee_exception():
line = 'this is "a test'
endidx = len(line)
tokens, raw_tokens, begin_idx, end_idx = tokens_for_completion(line, endidx)
assert tokens == ['this', 'is', 'a test']
assert raw_tokens == ['this', 'is', '"a test']
assert begin_idx == line.rfind("is ") + len("is ") + 1
assert end_idx == end_idx
|