1import os
2import pickle
3
4import pytest
5
6from doit.cmdparse import DefaultUpdate, CmdParseError, CmdOption, CmdParse
7
8
9
10class TestDefaultUpdate(object):
11    def test(self):
12        du = DefaultUpdate()
13
14        du.set_default('a', 0)
15        du.set_default('b', 0)
16
17        assert 0 == du['a']
18        assert 0 == du['b']
19
20        # set b with non-default value
21        du['b'] = 1
22        # only a is update
23        du.update_defaults({'a':2, 'b':2})
24        assert 2 == du['a']
25        assert 1 == du['b']
26
27        # default for `a` can be updated again
28        du.update_defaults({'a':3})
29        assert 3 == du['a']
30
31
32    def test_add_defaults(self):
33        du = DefaultUpdate()
34        du.add_defaults({'a': 0, 'b':1})
35        du['c'] = 5
36        du.add_defaults({'a':2, 'c':2})
37        assert 0 == du['a']
38        assert 1 == du['b']
39        assert 5 == du['c']
40
41    # http://bugs.python.org/issue826897
42    def test_pickle(self):
43        du = DefaultUpdate()
44        du.set_default('x', 0)
45        dump = pickle.dumps(du,2)
46        pickle.loads(dump)
47
48
49class TestCmdOption(object):
50
51    def test_repr(self):
52        opt = CmdOption({'name':'opt1', 'default':'',
53                         'short':'o', 'long':'other'})
54        assert "CmdOption(" in repr(opt)
55        assert "'name':'opt1'" in repr(opt)
56        assert "'short':'o'" in repr(opt)
57        assert "'long':'other'" in repr(opt)
58
59    def test_non_required_fields(self):
60        opt1 = CmdOption({'name':'op1', 'default':''})
61        assert '' == opt1.long
62
63    def test_invalid_field(self):
64        opt_dict = {'name':'op1', 'default':'', 'non_existent':''}
65        pytest.raises(CmdParseError, CmdOption, opt_dict)
66
67    def test_missing_field(self):
68        opt_dict = {'name':'op1', 'long':'abc'}
69        pytest.raises(CmdParseError, CmdOption, opt_dict)
70
71
72class TestCmdOption_str2val(object):
73    def test_str2boolean(self):
74        opt = CmdOption({'name':'op1', 'default':'', 'type':bool,
75                         'short':'b', 'long': 'bobo'})
76        assert True == opt.str2boolean('1')
77        assert True == opt.str2boolean('yes')
78        assert True == opt.str2boolean('Yes')
79        assert True == opt.str2boolean('YES')
80        assert True == opt.str2boolean('true')
81        assert True == opt.str2boolean('on')
82        assert False == opt.str2boolean('0')
83        assert False == opt.str2boolean('false')
84        assert False == opt.str2boolean('no')
85        assert False == opt.str2boolean('off')
86        assert False == opt.str2boolean('OFF')
87        pytest.raises(ValueError, opt.str2boolean, '2')
88        pytest.raises(ValueError, opt.str2boolean, None)
89        pytest.raises(ValueError, opt.str2boolean, 'other')
90
91
92    def test_non_string_values_are_not_converted(self):
93        opt = CmdOption({'name':'op1', 'default':'', 'type':bool})
94        assert False == opt.str2type(False)
95        assert True == opt.str2type(True)
96        assert None == opt.str2type(None)
97
98    def test_str(self):
99        opt = CmdOption({'name':'op1', 'default':'', 'type':str})
100        assert 'foo' == opt.str2type('foo')
101        assert 'bar' == opt.str2type('bar')
102
103    def test_bool(self):
104        opt = CmdOption({'name':'op1', 'default':'', 'type':bool})
105        assert False == opt.str2type('off')
106        assert True == opt.str2type('on')
107
108    def test_int(self):
109        opt = CmdOption({'name':'op1', 'default':'', 'type':int})
110        assert 2 == opt.str2type('2')
111        assert -3 == opt.str2type('-3')
112
113    def test_list(self):
114        opt = CmdOption({'name':'op1', 'default':'', 'type':list})
115        assert ['foo'] == opt.str2type('foo')
116        assert [] == opt.str2type('')
117        assert ['foo', 'bar'] == opt.str2type('foo , bar ')
118
119    def test_invalid_value(self):
120        opt = CmdOption({'name':'op1', 'default':'', 'type':int})
121        pytest.raises(CmdParseError, opt.str2type, 'not a number')
122
123
124class TestCmdOption_help_param(object):
125    def test_bool_param(self):
126        opt1 = CmdOption({'name':'op1', 'default':'', 'type':bool,
127                          'short':'b', 'long': 'bobo'})
128        assert '-b, --bobo' == opt1.help_param()
129
130    def test_non_bool_param(self):
131        opt1 = CmdOption({'name':'op1', 'default':'', 'type':str,
132                          'short':'s', 'long': 'susu'})
133        assert '-s ARG, --susu=ARG' == opt1.help_param()
134
135
136    def test_no_long(self):
137        opt1 = CmdOption({'name':'op1', 'default':'', 'type':str,
138                          'short':'s'})
139        assert '-s ARG' == opt1.help_param()
140
141
142opt_bool = {'name': 'flag',
143            'short':'f',
144            'long': 'flag',
145            'inverse':'no-flag',
146            'type': bool,
147            'default': False,
148            'help': 'help for opt1'}
149
150opt_rare = {'name': 'rare_bool',
151            'long': 'rare-bool',
152            'env_var': 'RARE',
153            'type': bool,
154            'default': False,
155            'help': 'help for opt2',}
156
157opt_int = {'name': 'num',
158           'short':'n',
159           'long': 'number',
160           'type': int,
161           'default': 5,
162           'help': 'help for opt3'}
163
164opt_no = {'name': 'no',
165          'short':'',
166          'long': '',
167          'type': int,
168          'default': 5,
169          'help': 'user cant modify me'}
170
171opt_append = { 'name': 'list',
172             'short': 'l',
173             'long': 'list',
174             'type': list,
175             'default': [],
176             'help': 'use many -l to make a list'}
177
178opt_choices_desc = {'name': 'choices',
179                    'short':'c',
180                    'long': 'choice',
181                    'type': str,
182                    'choices': (("yes", "signify affirmative"),
183                                ("no","signify negative")),
184                    'default': "yes",
185                    'help': 'User chooses [default %(default)s]'}
186
187opt_choices_nodesc = {'name': 'choicesnodesc',
188                      'short':'C',
189                      'long': 'achoice',
190                      'type': str,
191                      'choices': (("yes", ""),
192                                  ("no", "")),
193                      'default': "no",
194                      'help': 'User chooses [default %(default)s]'}
195
196
197class TestCmdOption_help_doc(object):
198    def test_param(self):
199        opt1 = CmdOption(opt_bool)
200        got = opt1.help_doc()
201        assert '-f, --flag' in got[0]
202        assert 'help for opt1' in got[0]
203        assert '--no-flag' in got[1]
204        assert 2 == len(got)
205
206    def test_no_doc_param(self):
207        opt1 = CmdOption(opt_no)
208        assert 0 == len(opt1.help_doc())
209
210    def test_choices_desc_doc(self):
211        the_opt = CmdOption(opt_choices_desc)
212        doc = the_opt.help_doc()[0]
213        assert 'choices:\n' in doc
214        assert 'yes: signify affirmative' in doc
215        assert 'no: signify negative' in doc
216
217    def test_choices_nodesc_doc(self):
218        the_opt = CmdOption(opt_choices_nodesc)
219        doc = the_opt.help_doc()[0]
220        assert "choices: no, yes" in doc
221
222    def test_name_config_env(self):
223        opt1 = CmdOption(opt_rare)
224        got = opt1.help_doc()
225        assert 'config: rare_bool' in got[0]
226        assert 'environ: RARE' in got[0]
227
228
229
230class TestCommand(object):
231
232    @pytest.fixture
233    def cmd(self, request):
234        opt_list = (opt_bool, opt_rare, opt_int, opt_no,
235                    opt_append, opt_choices_desc, opt_choices_nodesc)
236        options = [CmdOption(o) for o in opt_list]
237        cmd = CmdParse(options)
238        return cmd
239
240    def test_contains(self, cmd):
241        assert 'flag' in cmd
242        assert 'num' in cmd
243        assert 'xxx' not in cmd
244
245    def test_getitem(self, cmd):
246        assert cmd['flag'].short == 'f'
247        assert cmd['num'].default == 5
248
249    def test_option_list(self, cmd):
250        opt_names = [o.name for o in cmd.options]
251        assert  ['flag', 'rare_bool', 'num', 'no', 'list', 'choices',
252                 'choicesnodesc']== opt_names
253
254    def test_short(self, cmd):
255        assert "fn:l:c:C:" == cmd.get_short(), cmd.get_short()
256
257    def test_long(self, cmd):
258        longs = ["flag", "no-flag", "rare-bool", "number=",
259                 "list=", "choice=", "achoice="]
260        assert longs == cmd.get_long()
261
262    def test_getOption(self, cmd):
263        # short
264        opt, is_inverse = cmd.get_option('-f')
265        assert (opt_bool['name'], False) == (opt.name, is_inverse)
266        # long
267        opt, is_inverse = cmd.get_option('--rare-bool')
268        assert (opt_rare['name'], False) == (opt.name, is_inverse)
269        # inverse
270        opt, is_inverse = cmd.get_option('--no-flag')
271        assert (opt_bool['name'], True) == (opt.name, is_inverse)
272        # not found
273        opt, is_inverse = cmd.get_option('not-there')
274        assert (None, None) == (opt, is_inverse)
275
276        opt, is_inverse = cmd.get_option('--list')
277        assert (opt_append['name'], False) == (opt.name, is_inverse)
278
279        opt, is_inverse = cmd.get_option('--choice')
280        assert (opt_choices_desc['name'], False) == (opt.name, is_inverse)
281
282        opt, is_inverse = cmd.get_option('--achoice')
283        assert (opt_choices_nodesc['name'], False) == (opt.name, is_inverse)
284
285
286    def test_parseDefaults(self, cmd):
287        params, args = cmd.parse([])
288        assert False == params['flag']
289        assert 5 == params['num']
290        assert [] == params['list']
291        assert "yes" == params['choices']
292        assert "no" == params['choicesnodesc']
293
294    def test_overwrite_defaults(self, cmd):
295        cmd.overwrite_defaults({'num': 9, 'i_dont_exist': 1})
296        params, args = cmd.parse([])
297        assert 9 == params['num']
298
299    def test_overwrite_defaults_convert_type(self, cmd):
300        cmd.overwrite_defaults({'num': '9', 'list': 'foo, bar', 'flag':'on'})
301        params, args = cmd.parse([])
302        assert 9 == params['num']
303        assert ['foo', 'bar'] == params['list']
304        assert True == params['flag']
305
306    def test_parseShortValues(self, cmd):
307        params, args = cmd.parse(['-n','89','-f', '-l', 'foo', '-l', 'bar',
308                                  '-c', 'no', '-C', 'yes'])
309        assert True == params['flag']
310        assert 89 == params['num']
311        assert ['foo', 'bar'] == params['list']
312        assert "no" == params['choices']
313        assert "yes" == params['choicesnodesc']
314
315    def test_parseLongValues(self, cmd):
316        params, args = cmd.parse(['--rare-bool','--num','89', '--no-flag',
317                                  '--list', 'flip', '--list', 'flop',
318                                  '--choice', 'no', '--achoice', 'yes'])
319        assert True == params['rare_bool']
320        assert False == params['flag']
321        assert 89 == params['num']
322        assert ['flip', 'flop'] == params['list']
323        assert "no" == params['choices']
324        assert "yes" == params['choicesnodesc']
325
326    def test_parsePositionalArgs(self, cmd):
327        params, args = cmd.parse(['-f','p1','p2', '--sub-arg'])
328        assert ['p1','p2', '--sub-arg'] == args
329
330    def test_parseError(self, cmd):
331        pytest.raises(CmdParseError, cmd.parse, ['--not-exist-param'])
332
333    def test_parseWrongType(self, cmd):
334        pytest.raises(CmdParseError, cmd.parse, ['--num','oi'])
335
336    def test_parseWrongChoice(self, cmd):
337        pytest.raises(CmdParseError, cmd.parse, ['--choice', 'maybe'])
338
339    def test_env_val(self):
340        opt_foo = {
341            'name': 'foo',
342            'long': 'foo',
343            'type': str,
344            'env_var': 'FOO',
345            'default': 'zero'
346        }
347        cmd = CmdParse([CmdOption(opt_foo)])
348
349        # get default
350        params, args = cmd.parse([])
351        assert params['foo'] == 'zero'
352
353        # get from env
354        os.environ['FOO'] = 'bar'
355        params2, args2 = cmd.parse([])
356        assert params2['foo'] == 'bar'
357
358        # command line has precedence
359        params2, args2 = cmd.parse(['--foo', 'XXX'])
360        assert params2['foo'] == 'XXX'
361
362
363    def test_env_val_bool(self):
364        opt_foo = {
365            'name': 'foo',
366            'long': 'foo',
367            'type': bool,
368            'env_var': 'FOO',
369            'default': False,
370        }
371        cmd = CmdParse([CmdOption(opt_foo)])
372
373        # get from env
374        os.environ['FOO'] = '1'
375        params, args = cmd.parse([])
376        assert params['foo'] == True
377
378        # get from env
379        os.environ['FOO'] = '0'
380        params, args = cmd.parse([])
381        assert params['foo'] == False
382