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