2
0

test_arguments.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191
  1. import collections
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic.commands import arguments as module
  5. def test_get_subaction_parsers_with_no_subactions_returns_empty_result():
  6. assert module.get_subaction_parsers(flexmock(_subparsers=None)) == {}
  7. def test_get_subaction_parsers_with_subactions_returns_one_entry_per_subaction():
  8. foo_parser = flexmock()
  9. bar_parser = flexmock()
  10. baz_parser = flexmock()
  11. assert module.get_subaction_parsers(
  12. flexmock(
  13. _subparsers=flexmock(
  14. _group_actions=(
  15. flexmock(choices={'foo': foo_parser, 'bar': bar_parser}),
  16. flexmock(choices={'baz': baz_parser}),
  17. )
  18. )
  19. )
  20. ) == {'foo': foo_parser, 'bar': bar_parser, 'baz': baz_parser}
  21. def test_get_subactions_for_actions_with_no_subactions_returns_empty_result():
  22. assert module.get_subactions_for_actions({'action': flexmock(_subparsers=None)}) == {}
  23. def test_get_subactions_for_actions_with_subactions_returns_one_entry_per_action():
  24. assert module.get_subactions_for_actions(
  25. {
  26. 'action': flexmock(
  27. _subparsers=flexmock(
  28. _group_actions=(
  29. flexmock(choices={'foo': flexmock(), 'bar': flexmock()}),
  30. flexmock(choices={'baz': flexmock()}),
  31. )
  32. )
  33. ),
  34. 'other': flexmock(
  35. _subparsers=flexmock(_group_actions=(flexmock(choices={'quux': flexmock()}),))
  36. ),
  37. }
  38. ) == {'action': ('foo', 'bar', 'baz'), 'other': ('quux',)}
  39. def test_omit_values_colliding_with_action_names_drops_action_names_that_have_been_parsed_as_values():
  40. assert module.omit_values_colliding_with_action_names(
  41. ('check', '--only', 'extract', '--some-list', 'borg'),
  42. {'check': flexmock(only='extract', some_list=['borg'])},
  43. ) == ('check', '--only', '--some-list')
  44. def test_omit_values_colliding_twice_with_action_names_drops_action_names_that_have_been_parsed_as_values():
  45. assert module.omit_values_colliding_with_action_names(
  46. ('config', 'bootstrap', '--local-path', '--remote-path', 'borg'),
  47. {'bootstrap': flexmock(local_path='borg', remote_path='borg')},
  48. ) == ('config', 'bootstrap', '--local-path', '--remote-path')
  49. def test_parse_and_record_action_arguments_without_action_name_leaves_arguments_untouched():
  50. unparsed_arguments = ('--foo', '--bar')
  51. flexmock(module).should_receive('omit_values_colliding_with_action_names').and_return(
  52. unparsed_arguments
  53. )
  54. assert (
  55. module.parse_and_record_action_arguments(
  56. unparsed_arguments, flexmock(), flexmock(), 'action'
  57. )
  58. == unparsed_arguments
  59. )
  60. def test_parse_and_record_action_arguments_updates_parsed_arguments_and_returns_remaining():
  61. unparsed_arguments = ('action', '--foo', '--bar', '--verbosity', '1')
  62. other_parsed_arguments = flexmock()
  63. parsed_arguments = {'other': other_parsed_arguments}
  64. action_parsed_arguments = flexmock()
  65. flexmock(module).should_receive('omit_values_colliding_with_action_names').and_return(
  66. unparsed_arguments
  67. )
  68. action_parser = flexmock()
  69. flexmock(action_parser).should_receive('parse_known_args').and_return(
  70. action_parsed_arguments, ('action', '--verbosity', '1')
  71. )
  72. assert module.parse_and_record_action_arguments(
  73. unparsed_arguments, parsed_arguments, action_parser, 'action'
  74. ) == ('--verbosity', '1')
  75. assert parsed_arguments == {'other': other_parsed_arguments, 'action': action_parsed_arguments}
  76. def test_parse_and_record_action_arguments_with_alias_updates_canonical_parsed_arguments():
  77. unparsed_arguments = ('action', '--foo', '--bar', '--verbosity', '1')
  78. other_parsed_arguments = flexmock()
  79. parsed_arguments = {'other': other_parsed_arguments}
  80. action_parsed_arguments = flexmock()
  81. flexmock(module).should_receive('omit_values_colliding_with_action_names').and_return(
  82. unparsed_arguments
  83. )
  84. action_parser = flexmock()
  85. flexmock(action_parser).should_receive('parse_known_args').and_return(
  86. action_parsed_arguments, ('action', '--verbosity', '1')
  87. )
  88. assert module.parse_and_record_action_arguments(
  89. unparsed_arguments, parsed_arguments, action_parser, 'action', canonical_name='doit'
  90. ) == ('--verbosity', '1')
  91. assert parsed_arguments == {'other': other_parsed_arguments, 'doit': action_parsed_arguments}
  92. def test_parse_and_record_action_arguments_with_borg_action_consumes_arguments_after_action_name():
  93. unparsed_arguments = ('--verbosity', '1', 'borg', 'list')
  94. parsed_arguments = {}
  95. borg_parsed_arguments = flexmock(options=flexmock())
  96. flexmock(module).should_receive('omit_values_colliding_with_action_names').and_return(
  97. unparsed_arguments
  98. )
  99. borg_parser = flexmock()
  100. flexmock(borg_parser).should_receive('parse_known_args').and_return(
  101. borg_parsed_arguments, ('--verbosity', '1', 'borg', 'list')
  102. )
  103. assert module.parse_and_record_action_arguments(
  104. unparsed_arguments,
  105. parsed_arguments,
  106. borg_parser,
  107. 'borg',
  108. ) == ('--verbosity', '1')
  109. assert parsed_arguments == {'borg': borg_parsed_arguments}
  110. assert borg_parsed_arguments.options == ('list',)
  111. @pytest.mark.parametrize(
  112. 'argument, expected',
  113. [
  114. ('--foo', True),
  115. ('foo', False),
  116. (33, False),
  117. ],
  118. )
  119. def test_argument_is_flag_only_for_string_starting_with_double_dash(argument, expected):
  120. assert module.argument_is_flag(argument) == expected
  121. @pytest.mark.parametrize(
  122. 'arguments, expected',
  123. [
  124. # Ending with a valueless flag.
  125. (
  126. ('--foo', '--bar', 33, '--baz'),
  127. (
  128. ('--foo',),
  129. ('--bar', 33),
  130. ('--baz',),
  131. ),
  132. ),
  133. # Ending with a flag and its corresponding value.
  134. (
  135. ('--foo', '--bar', 33, '--baz', '--quux', 'thing'),
  136. (('--foo',), ('--bar', 33), ('--baz',), ('--quux', 'thing')),
  137. ),
  138. # Starting with an action name.
  139. (
  140. ('check', '--foo', '--bar', 33, '--baz'),
  141. (
  142. ('check',),
  143. ('--foo',),
  144. ('--bar', 33),
  145. ('--baz',),
  146. ),
  147. ),
  148. # Action name that one could mistake for a flag value.
  149. (('--progress', 'list'), (('--progress',), ('list',))),
  150. # No arguments.
  151. ((), ()),
  152. ],
  153. )
  154. def test_group_arguments_with_values_returns_flags_with_corresponding_values(arguments, expected):
  155. flexmock(module).should_receive('argument_is_flag').with_args('--foo').and_return(True)
  156. flexmock(module).should_receive('argument_is_flag').with_args('--bar').and_return(True)
  157. flexmock(module).should_receive('argument_is_flag').with_args('--baz').and_return(True)
  158. flexmock(module).should_receive('argument_is_flag').with_args('--quux').and_return(True)
  159. flexmock(module).should_receive('argument_is_flag').with_args('--progress').and_return(True)
  160. flexmock(module).should_receive('argument_is_flag').with_args(33).and_return(False)
  161. flexmock(module).should_receive('argument_is_flag').with_args('thing').and_return(False)
  162. flexmock(module).should_receive('argument_is_flag').with_args('check').and_return(False)
  163. flexmock(module).should_receive('argument_is_flag').with_args('list').and_return(False)
  164. assert module.group_arguments_with_values(arguments) == expected
  165. @pytest.mark.parametrize(
  166. 'arguments, grouped_arguments, expected',
  167. [
  168. # An unparsable flag remaining from each parsed action.
  169. (
  170. (
  171. ('--latest', 'archive', 'prune', 'extract', 'list', '--flag'),
  172. ('--latest', 'archive', 'check', 'extract', 'list', '--flag'),
  173. ('prune', 'check', 'list', '--flag'),
  174. ('prune', 'check', 'extract', '--flag'),
  175. ),
  176. (
  177. (
  178. ('--latest',),
  179. ('archive',),
  180. ('prune',),
  181. ('extract',),
  182. ('list',),
  183. ('--flag',),
  184. ),
  185. (
  186. ('--latest',),
  187. ('archive',),
  188. ('check',),
  189. ('extract',),
  190. ('list',),
  191. ('--flag',),
  192. ),
  193. (('prune',), ('check',), ('list',), ('--flag',)),
  194. (('prune',), ('check',), ('extract',), ('--flag',)),
  195. ),
  196. ('--flag',),
  197. ),
  198. # No unparsable flags remaining.
  199. (
  200. (
  201. ('--archive', 'archive', 'prune', 'extract', 'list'),
  202. ('--archive', 'archive', 'check', 'extract', 'list'),
  203. ('prune', 'check', 'list'),
  204. ('prune', 'check', 'extract'),
  205. ),
  206. (
  207. (
  208. (
  209. '--archive',
  210. 'archive',
  211. ),
  212. ('prune',),
  213. ('extract',),
  214. ('list',),
  215. ),
  216. (
  217. (
  218. '--archive',
  219. 'archive',
  220. ),
  221. ('check',),
  222. ('extract',),
  223. ('list',),
  224. ),
  225. (('prune',), ('check',), ('list',)),
  226. (('prune',), ('check',), ('extract',)),
  227. ),
  228. (),
  229. ),
  230. # No unparsable flags remaining, but some values in common.
  231. (
  232. (
  233. ('--verbosity', '5', 'archive', 'prune', 'extract', 'list'),
  234. ('--last', '5', 'archive', 'check', 'extract', 'list'),
  235. ('prune', 'check', 'list', '--last', '5'),
  236. ('prune', 'check', '--verbosity', '5', 'extract'),
  237. ),
  238. (
  239. (('--verbosity', '5'), ('archive',), ('prune',), ('extract',), ('list',)),
  240. (
  241. (
  242. '--last',
  243. '5',
  244. ),
  245. ('archive',),
  246. ('check',),
  247. ('extract',),
  248. ('list',),
  249. ),
  250. (('prune',), ('check',), ('list',), ('--last', '5')),
  251. (
  252. ('prune',),
  253. ('check',),
  254. (
  255. '--verbosity',
  256. '5',
  257. ),
  258. ('extract',),
  259. ),
  260. ),
  261. (),
  262. ),
  263. # No flags.
  264. ((), (), ()),
  265. ],
  266. )
  267. def test_get_unparsable_arguments_returns_remaining_arguments_that_no_action_can_parse(
  268. arguments, grouped_arguments, expected
  269. ):
  270. for action_arguments, grouped_action_arguments in zip(arguments, grouped_arguments):
  271. flexmock(module).should_receive('group_arguments_with_values').with_args(
  272. action_arguments
  273. ).and_return(grouped_action_arguments)
  274. assert module.get_unparsable_arguments(arguments) == expected
  275. def test_parse_arguments_for_actions_consumes_action_arguments_after_action_name():
  276. action_namespace = flexmock(foo=True)
  277. remaining = flexmock()
  278. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  279. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  280. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  281. {action: action_namespace}
  282. )
  283. or remaining
  284. )
  285. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  286. action_parsers = {'action': flexmock(), 'other': flexmock()}
  287. global_namespace = flexmock(config_paths=[])
  288. global_parser = flexmock()
  289. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  290. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  291. ('action', '--foo', 'true'), action_parsers, global_parser
  292. )
  293. assert arguments == {'global': global_namespace, 'action': action_namespace}
  294. assert remaining_action_arguments == (remaining, ())
  295. def test_parse_arguments_for_actions_consumes_action_arguments_with_alias():
  296. action_namespace = flexmock(foo=True)
  297. remaining = flexmock()
  298. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  299. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  300. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  301. {canonical or action: action_namespace}
  302. )
  303. or remaining
  304. )
  305. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  306. action_parsers = {
  307. 'action': flexmock(),
  308. '-a': flexmock(),
  309. 'other': flexmock(),
  310. '-o': flexmock(),
  311. }
  312. global_namespace = flexmock(config_paths=[])
  313. global_parser = flexmock()
  314. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  315. flexmock(module).ACTION_ALIASES = {'action': ['-a'], 'other': ['-o']}
  316. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  317. ('-a', '--foo', 'true'), action_parsers, global_parser
  318. )
  319. assert arguments == {'global': global_namespace, 'action': action_namespace}
  320. assert remaining_action_arguments == (remaining, ())
  321. def test_parse_arguments_for_actions_consumes_multiple_action_arguments():
  322. action_namespace = flexmock(foo=True)
  323. other_namespace = flexmock(bar=3)
  324. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  325. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  326. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  327. {action: action_namespace if action == 'action' else other_namespace}
  328. )
  329. or ()
  330. ).and_return(('other', '--bar', '3')).and_return('action', '--foo', 'true')
  331. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  332. action_parsers = {
  333. 'action': flexmock(),
  334. 'other': flexmock(),
  335. }
  336. global_namespace = flexmock(config_paths=[])
  337. global_parser = flexmock()
  338. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  339. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  340. ('action', '--foo', 'true', 'other', '--bar', '3'), action_parsers, global_parser
  341. )
  342. assert arguments == {
  343. 'global': global_namespace,
  344. 'action': action_namespace,
  345. 'other': other_namespace,
  346. }
  347. assert remaining_action_arguments == ((), (), ())
  348. def test_parse_arguments_for_actions_respects_command_line_action_ordering():
  349. other_namespace = flexmock()
  350. action_namespace = flexmock(foo=True)
  351. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  352. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  353. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  354. {action: other_namespace if action == 'other' else action_namespace}
  355. )
  356. or ()
  357. ).and_return(('action',)).and_return(('other', '--foo', 'true'))
  358. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  359. action_parsers = {
  360. 'action': flexmock(),
  361. 'other': flexmock(),
  362. }
  363. global_namespace = flexmock(config_paths=[])
  364. global_parser = flexmock()
  365. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  366. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  367. ('other', '--foo', 'true', 'action'), action_parsers, global_parser
  368. )
  369. assert arguments == collections.OrderedDict(
  370. [('other', other_namespace), ('action', action_namespace), ('global', global_namespace)]
  371. )
  372. assert remaining_action_arguments == ((), (), ())
  373. def test_parse_arguments_for_actions_applies_default_action_parsers():
  374. global_namespace = flexmock(config_paths=[])
  375. namespaces = {
  376. 'global': global_namespace,
  377. 'prune': flexmock(),
  378. 'compact': flexmock(),
  379. 'create': flexmock(progress=True),
  380. 'check': flexmock(),
  381. }
  382. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  383. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  384. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  385. {action: namespaces.get(action)}
  386. )
  387. or ()
  388. ).and_return(())
  389. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  390. action_parsers = {
  391. 'prune': flexmock(),
  392. 'compact': flexmock(),
  393. 'create': flexmock(),
  394. 'check': flexmock(),
  395. 'other': flexmock(),
  396. }
  397. global_parser = flexmock()
  398. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  399. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  400. ('--progress'), action_parsers, global_parser
  401. )
  402. assert arguments == namespaces
  403. assert remaining_action_arguments == ((), (), (), (), ())
  404. def test_parse_arguments_for_actions_consumes_global_arguments():
  405. action_namespace = flexmock()
  406. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  407. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  408. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  409. {action: action_namespace}
  410. )
  411. or ('--verbosity', 'lots')
  412. )
  413. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  414. action_parsers = {
  415. 'action': flexmock(),
  416. 'other': flexmock(),
  417. }
  418. global_namespace = flexmock(config_paths=[])
  419. global_parser = flexmock()
  420. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  421. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  422. ('action', '--verbosity', 'lots'), action_parsers, global_parser
  423. )
  424. assert arguments == {'global': global_namespace, 'action': action_namespace}
  425. assert remaining_action_arguments == (('--verbosity', 'lots'), ())
  426. def test_parse_arguments_for_actions_passes_through_unknown_arguments_before_action_name():
  427. action_namespace = flexmock()
  428. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  429. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  430. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  431. {action: action_namespace}
  432. )
  433. or ('--wtf', 'yes')
  434. )
  435. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  436. action_parsers = {
  437. 'action': flexmock(),
  438. 'other': flexmock(),
  439. }
  440. global_namespace = flexmock(config_paths=[])
  441. global_parser = flexmock()
  442. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  443. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  444. ('--wtf', 'yes', 'action'), action_parsers, global_parser
  445. )
  446. assert arguments == {'global': global_namespace, 'action': action_namespace}
  447. assert remaining_action_arguments == (('--wtf', 'yes'), ())
  448. def test_parse_arguments_for_actions_passes_through_unknown_arguments_after_action_name():
  449. action_namespace = flexmock()
  450. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  451. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  452. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  453. {action: action_namespace}
  454. )
  455. or ('--wtf', 'yes')
  456. )
  457. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  458. action_parsers = {
  459. 'action': flexmock(),
  460. 'other': flexmock(),
  461. }
  462. global_namespace = flexmock(config_paths=[])
  463. global_parser = flexmock()
  464. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  465. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  466. ('action', '--wtf', 'yes'), action_parsers, global_parser
  467. )
  468. assert arguments == {'global': global_namespace, 'action': action_namespace}
  469. assert remaining_action_arguments == (('--wtf', 'yes'), ())
  470. def test_parse_arguments_for_actions_with_borg_action_skips_other_action_parsers():
  471. action_namespace = flexmock(options=[])
  472. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  473. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  474. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  475. {action: action_namespace}
  476. )
  477. or ()
  478. ).and_return(())
  479. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  480. action_parsers = {
  481. 'borg': flexmock(),
  482. 'list': flexmock(),
  483. }
  484. global_namespace = flexmock(config_paths=[])
  485. global_parser = flexmock()
  486. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  487. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  488. ('borg', 'list'), action_parsers, global_parser
  489. )
  490. assert arguments == {'global': global_namespace, 'borg': action_namespace}
  491. assert remaining_action_arguments == ((), ())
  492. def test_parse_arguments_for_actions_raises_error_when_no_action_is_specified():
  493. flexmock(module).should_receive('get_subaction_parsers').and_return({'bootstrap': [flexmock()]})
  494. flexmock(module).should_receive('parse_and_record_action_arguments').and_return(flexmock())
  495. flexmock(module).should_receive('get_subactions_for_actions').and_return(
  496. {'config': ['bootstrap']}
  497. )
  498. action_parsers = {'config': flexmock()}
  499. global_parser = flexmock()
  500. global_parser.should_receive('parse_known_args').and_return((flexmock(), ()))
  501. with pytest.raises(ValueError):
  502. module.parse_arguments_for_actions(('config',), action_parsers, global_parser)
  503. def test_make_argument_description_without_description_bails():
  504. assert (
  505. module.make_argument_description(
  506. schema={
  507. 'description': None,
  508. 'type': 'not yours',
  509. },
  510. flag_name='flag',
  511. )
  512. is None
  513. )
  514. def test_make_argument_description_with_array_adds_example():
  515. buffer = flexmock()
  516. buffer.should_receive('getvalue').and_return('[example]')
  517. flexmock(module.io).should_receive('StringIO').and_return(buffer)
  518. yaml = flexmock()
  519. yaml.should_receive('dump')
  520. flexmock(module.ruamel.yaml).should_receive('YAML').and_return(yaml)
  521. assert (
  522. module.make_argument_description(
  523. schema={
  524. 'description': 'Thing.',
  525. 'type': 'array',
  526. 'example': ['example'],
  527. },
  528. flag_name='flag',
  529. )
  530. == 'Thing. Example value: "[example]"'
  531. )
  532. def test_make_argument_description_with_array_skips_missing_example():
  533. yaml = flexmock()
  534. yaml.should_receive('dump').and_return('[example]')
  535. flexmock(module.ruamel.yaml).should_receive('YAML').and_return(yaml)
  536. assert (
  537. module.make_argument_description(
  538. schema={
  539. 'description': 'Thing.',
  540. 'type': 'array',
  541. },
  542. flag_name='flag',
  543. )
  544. == 'Thing.'
  545. )
  546. def test_make_argument_description_with_array_index_in_flag_name_adds_to_description():
  547. assert 'list element' in module.make_argument_description(
  548. schema={
  549. 'description': 'Thing.',
  550. 'type': 'something',
  551. },
  552. flag_name='flag[0]',
  553. )
  554. def test_make_argument_description_escapes_percent_character():
  555. assert (
  556. module.make_argument_description(
  557. schema={
  558. 'description': '% Thing.',
  559. 'type': 'something',
  560. },
  561. flag_name='flag',
  562. )
  563. == '%% Thing.'
  564. )
  565. def test_add_array_element_arguments_without_array_index_bails():
  566. arguments_group = flexmock()
  567. arguments_group.should_receive('add_argument').never()
  568. module.add_array_element_arguments(
  569. arguments_group=arguments_group,
  570. unparsed_arguments=(),
  571. flag_name='foo',
  572. )
  573. def test_add_array_element_arguments_with_help_flag_bails():
  574. arguments_group = flexmock()
  575. arguments_group.should_receive('add_argument').never()
  576. module.add_array_element_arguments(
  577. arguments_group=arguments_group,
  578. unparsed_arguments=('--foo', '--help', '--bar'),
  579. flag_name='foo[0]',
  580. )
  581. def test_add_array_element_arguments_without_any_flags_bails():
  582. arguments_group = flexmock()
  583. arguments_group.should_receive('add_argument').never()
  584. module.add_array_element_arguments(
  585. arguments_group=arguments_group,
  586. unparsed_arguments=(),
  587. flag_name='foo[0]',
  588. )
  589. # Use this instead of a flexmock because it's not easy to check the type() of a flexmock instance.
  590. Group_action = collections.namedtuple(
  591. 'Group_action',
  592. (
  593. 'option_strings',
  594. 'choices',
  595. 'default',
  596. 'nargs',
  597. 'required',
  598. 'type',
  599. ),
  600. defaults=(
  601. flexmock(),
  602. flexmock(),
  603. flexmock(),
  604. flexmock(),
  605. flexmock(),
  606. ),
  607. )
  608. def test_add_array_element_arguments_without_array_index_flags_bails():
  609. arguments_group = flexmock(
  610. _group_actions=(
  611. Group_action(
  612. option_strings=('--foo[0].val',),
  613. ),
  614. ),
  615. _registries={'action': {'store_stuff': Group_action}},
  616. )
  617. arguments_group.should_receive('add_argument').never()
  618. module.add_array_element_arguments(
  619. arguments_group=arguments_group,
  620. unparsed_arguments=('--foo', '--bar'),
  621. flag_name='foo[0].val',
  622. )
  623. def test_add_array_element_arguments_with_non_matching_array_index_flags_bails():
  624. arguments_group = flexmock(
  625. _group_actions=(
  626. Group_action(
  627. option_strings=('--foo[0].val',),
  628. ),
  629. ),
  630. _registries={'action': {'store_stuff': Group_action}},
  631. )
  632. arguments_group.should_receive('add_argument').never()
  633. module.add_array_element_arguments(
  634. arguments_group=arguments_group,
  635. unparsed_arguments=('--foo', '--bar[25].val', 'barval'),
  636. flag_name='foo[0].val',
  637. )
  638. def test_add_array_element_arguments_with_identical_array_index_flag_bails():
  639. arguments_group = flexmock(
  640. _group_actions=(
  641. Group_action(
  642. option_strings=('--foo[0].val',),
  643. ),
  644. ),
  645. _registries={'action': {'store_stuff': Group_action}},
  646. )
  647. arguments_group.should_receive('add_argument').never()
  648. module.add_array_element_arguments(
  649. arguments_group=arguments_group,
  650. unparsed_arguments=('--foo[0].val', 'fooval', '--bar'),
  651. flag_name='foo[0].val',
  652. )
  653. def test_add_array_element_arguments_without_action_type_in_registry_bails():
  654. arguments_group = flexmock(
  655. _group_actions=(
  656. Group_action(
  657. option_strings=('--foo[0].val',),
  658. choices=flexmock(),
  659. default=flexmock(),
  660. nargs=flexmock(),
  661. required=flexmock(),
  662. type=flexmock(),
  663. ),
  664. ),
  665. _registries={'action': {'store_stuff': bool}},
  666. )
  667. arguments_group.should_receive('add_argument').never()
  668. module.add_array_element_arguments(
  669. arguments_group=arguments_group,
  670. unparsed_arguments=('--foo[25].val', 'fooval', '--bar[1].val', 'barval'),
  671. flag_name='foo[0].val',
  672. )
  673. def test_add_array_element_arguments_adds_arguments_for_array_index_flags():
  674. arguments_group = flexmock(
  675. _group_actions=(
  676. Group_action(
  677. option_strings=('--foo[0].val',),
  678. choices=flexmock(),
  679. default=flexmock(),
  680. nargs=flexmock(),
  681. required=flexmock(),
  682. type=flexmock(),
  683. ),
  684. ),
  685. _registries={'action': {'store_stuff': Group_action}},
  686. )
  687. arguments_group.should_receive('add_argument').with_args(
  688. '--foo[25].val',
  689. action='store_stuff',
  690. choices=object,
  691. default=object,
  692. dest='foo[25].val',
  693. nargs=object,
  694. required=object,
  695. type=object,
  696. ).once()
  697. module.add_array_element_arguments(
  698. arguments_group=arguments_group,
  699. unparsed_arguments=('--foo[25].val', 'fooval', '--bar[1].val', 'barval'),
  700. flag_name='foo[0].val',
  701. )
  702. def test_add_array_element_arguments_adds_arguments_for_array_index_flags_with_equals_sign():
  703. arguments_group = flexmock(
  704. _group_actions=(
  705. Group_action(
  706. option_strings=('--foo[0].val',),
  707. choices=flexmock(),
  708. default=flexmock(),
  709. nargs=flexmock(),
  710. required=flexmock(),
  711. type=flexmock(),
  712. ),
  713. ),
  714. _registries={'action': {'store_stuff': Group_action}},
  715. )
  716. arguments_group.should_receive('add_argument').with_args(
  717. '--foo[25].val',
  718. action='store_stuff',
  719. choices=object,
  720. default=object,
  721. dest='foo[25].val',
  722. nargs=object,
  723. required=object,
  724. type=object,
  725. ).once()
  726. module.add_array_element_arguments(
  727. arguments_group=arguments_group,
  728. unparsed_arguments=('--foo[25].val=fooval', '--bar[1].val=barval'),
  729. flag_name='foo[0].val',
  730. )
  731. def test_add_arguments_from_schema_with_non_dict_schema_bails():
  732. arguments_group = flexmock()
  733. flexmock(module).should_receive('make_argument_description').never()
  734. flexmock(module.borgmatic.config.schema).should_receive('parse_type').never()
  735. arguments_group.should_receive('add_argument').never()
  736. module.add_arguments_from_schema(
  737. arguments_group=arguments_group, schema='foo', unparsed_arguments=()
  738. )
  739. def test_add_arguments_from_schema_with_nested_object_adds_flag_for_each_option():
  740. arguments_group = flexmock()
  741. flexmock(module).should_receive('make_argument_description').and_return('help 1').and_return(
  742. 'help 2'
  743. )
  744. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(
  745. int
  746. ).and_return(str)
  747. arguments_group.should_receive('add_argument').with_args(
  748. '--foo.bar',
  749. type=int,
  750. metavar='BAR',
  751. help='help 1',
  752. ).once()
  753. arguments_group.should_receive('add_argument').with_args(
  754. '--foo.baz',
  755. type=str,
  756. metavar='BAZ',
  757. help='help 2',
  758. ).once()
  759. flexmock(module).should_receive('add_array_element_arguments')
  760. module.add_arguments_from_schema(
  761. arguments_group=arguments_group,
  762. schema={
  763. 'type': 'object',
  764. 'properties': {
  765. 'foo': {
  766. 'type': 'object',
  767. 'properties': {
  768. 'bar': {'type': 'integer'},
  769. 'baz': {'type': 'str'},
  770. },
  771. }
  772. },
  773. },
  774. unparsed_arguments=(),
  775. )
  776. def test_add_arguments_from_schema_uses_first_non_null_type_from_multi_type_object():
  777. arguments_group = flexmock()
  778. flexmock(module).should_receive('make_argument_description').and_return('help 1')
  779. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(int)
  780. arguments_group.should_receive('add_argument').with_args(
  781. '--foo.bar',
  782. type=int,
  783. metavar='BAR',
  784. help='help 1',
  785. ).once()
  786. flexmock(module).should_receive('add_array_element_arguments')
  787. module.add_arguments_from_schema(
  788. arguments_group=arguments_group,
  789. schema={
  790. 'type': 'object',
  791. 'properties': {
  792. 'foo': {
  793. 'type': ['null', 'object', 'boolean'],
  794. 'properties': {
  795. 'bar': {'type': 'integer'},
  796. },
  797. }
  798. },
  799. },
  800. unparsed_arguments=(),
  801. )
  802. def test_add_arguments_from_schema_with_empty_multi_type_raises():
  803. arguments_group = flexmock()
  804. flexmock(module).should_receive('make_argument_description').and_return('help 1')
  805. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(int)
  806. arguments_group.should_receive('add_argument').never()
  807. flexmock(module).should_receive('add_array_element_arguments').never()
  808. with pytest.raises(ValueError):
  809. module.add_arguments_from_schema(
  810. arguments_group=arguments_group,
  811. schema={
  812. 'type': 'object',
  813. 'properties': {
  814. 'foo': {
  815. 'type': [],
  816. 'properties': {
  817. 'bar': {'type': 'integer'},
  818. },
  819. }
  820. },
  821. },
  822. unparsed_arguments=(),
  823. )
  824. def test_add_arguments_from_schema_with_propertyless_option_does_not_add_flag():
  825. arguments_group = flexmock()
  826. flexmock(module).should_receive('make_argument_description').never()
  827. flexmock(module.borgmatic.config.schema).should_receive('parse_type').never()
  828. arguments_group.should_receive('add_argument').never()
  829. flexmock(module).should_receive('add_array_element_arguments').never()
  830. module.add_arguments_from_schema(
  831. arguments_group=arguments_group,
  832. schema={
  833. 'type': 'object',
  834. 'properties': {
  835. 'foo': {
  836. 'type': 'object',
  837. }
  838. },
  839. },
  840. unparsed_arguments=(),
  841. )
  842. def test_add_arguments_from_schema_with_array_adds_flag():
  843. arguments_group = flexmock()
  844. flexmock(module).should_receive('make_argument_description').and_return('help')
  845. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str)
  846. arguments_group.should_receive('add_argument').with_args(
  847. '--foo',
  848. type=str,
  849. metavar='FOO',
  850. help='help',
  851. ).once()
  852. flexmock(module).should_receive('add_array_element_arguments')
  853. module.add_arguments_from_schema(
  854. arguments_group=arguments_group,
  855. schema={
  856. 'type': 'object',
  857. 'properties': {
  858. 'foo': {
  859. 'type': 'array',
  860. 'items': {
  861. 'type': 'integer',
  862. },
  863. }
  864. },
  865. },
  866. unparsed_arguments=(),
  867. )
  868. def test_add_arguments_from_schema_with_array_and_nested_object_adds_multiple_flags():
  869. arguments_group = flexmock()
  870. flexmock(module).should_receive('make_argument_description').and_return('help 1').and_return(
  871. 'help 2'
  872. )
  873. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(
  874. int
  875. ).and_return(str)
  876. arguments_group.should_receive('add_argument').with_args(
  877. '--foo[0].bar',
  878. type=int,
  879. metavar='BAR',
  880. help='help 1',
  881. ).once()
  882. arguments_group.should_receive('add_argument').with_args(
  883. '--foo',
  884. type=str,
  885. metavar='FOO',
  886. help='help 2',
  887. ).once()
  888. flexmock(module).should_receive('add_array_element_arguments')
  889. flexmock(module).should_receive('add_array_element_arguments').with_args(
  890. arguments_group=arguments_group,
  891. unparsed_arguments=(),
  892. flag_name='foo[0].bar',
  893. ).once()
  894. module.add_arguments_from_schema(
  895. arguments_group=arguments_group,
  896. schema={
  897. 'type': 'object',
  898. 'properties': {
  899. 'foo': {
  900. 'type': 'array',
  901. 'items': {
  902. 'type': 'object',
  903. 'properties': {
  904. 'bar': {
  905. 'type': 'integer',
  906. }
  907. },
  908. },
  909. }
  910. },
  911. },
  912. unparsed_arguments=(),
  913. )
  914. def test_add_arguments_from_schema_with_default_false_boolean_adds_valueless_flag():
  915. arguments_group = flexmock()
  916. flexmock(module).should_receive('make_argument_description').and_return('help')
  917. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(bool)
  918. arguments_group.should_receive('add_argument').with_args(
  919. '--foo',
  920. action='store_true',
  921. default=None,
  922. help='help',
  923. ).once()
  924. flexmock(module).should_receive('add_array_element_arguments')
  925. module.add_arguments_from_schema(
  926. arguments_group=arguments_group,
  927. schema={
  928. 'type': 'object',
  929. 'properties': {
  930. 'foo': {
  931. 'type': 'boolean',
  932. 'default': False,
  933. }
  934. },
  935. },
  936. unparsed_arguments=(),
  937. )
  938. def test_add_arguments_from_schema_with_default_true_boolean_adds_value_flag():
  939. arguments_group = flexmock()
  940. flexmock(module).should_receive('make_argument_description').and_return('help')
  941. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(bool)
  942. arguments_group.should_receive('add_argument').with_args(
  943. '--foo',
  944. type=bool,
  945. metavar='FOO',
  946. help='help',
  947. ).once()
  948. flexmock(module).should_receive('add_array_element_arguments')
  949. module.add_arguments_from_schema(
  950. arguments_group=arguments_group,
  951. schema={
  952. 'type': 'object',
  953. 'properties': {
  954. 'foo': {
  955. 'type': 'boolean',
  956. 'default': True,
  957. }
  958. },
  959. },
  960. unparsed_arguments=(),
  961. )
  962. def test_add_arguments_from_schema_with_defaultless_boolean_adds_value_flag():
  963. arguments_group = flexmock()
  964. flexmock(module).should_receive('make_argument_description').and_return('help')
  965. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(bool)
  966. arguments_group.should_receive('add_argument').with_args(
  967. '--foo',
  968. type=bool,
  969. metavar='FOO',
  970. help='help',
  971. ).once()
  972. flexmock(module).should_receive('add_array_element_arguments')
  973. module.add_arguments_from_schema(
  974. arguments_group=arguments_group,
  975. schema={
  976. 'type': 'object',
  977. 'properties': {
  978. 'foo': {
  979. 'type': 'boolean',
  980. }
  981. },
  982. },
  983. unparsed_arguments=(),
  984. )
  985. def test_add_arguments_from_schema_skips_omitted_flag_name():
  986. arguments_group = flexmock()
  987. flexmock(module).should_receive('make_argument_description').and_return('help')
  988. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str)
  989. arguments_group.should_receive('add_argument').with_args(
  990. '--match-archives',
  991. type=object,
  992. metavar=object,
  993. help=object,
  994. ).never()
  995. arguments_group.should_receive('add_argument').with_args(
  996. '--foo',
  997. type=str,
  998. metavar='FOO',
  999. help='help',
  1000. ).once()
  1001. flexmock(module).should_receive('add_array_element_arguments')
  1002. module.add_arguments_from_schema(
  1003. arguments_group=arguments_group,
  1004. schema={
  1005. 'type': 'object',
  1006. 'properties': {
  1007. 'match_archives': {
  1008. 'type': 'string',
  1009. },
  1010. 'foo': {
  1011. 'type': 'string',
  1012. },
  1013. },
  1014. },
  1015. unparsed_arguments=(),
  1016. )
  1017. def test_add_arguments_from_schema_rewrites_option_name_to_flag_name():
  1018. arguments_group = flexmock()
  1019. flexmock(module).should_receive('make_argument_description').and_return('help')
  1020. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str)
  1021. arguments_group.should_receive('add_argument').with_args(
  1022. '--foo-and-stuff',
  1023. type=str,
  1024. metavar='FOO_AND_STUFF',
  1025. help='help',
  1026. ).once()
  1027. flexmock(module).should_receive('add_array_element_arguments')
  1028. module.add_arguments_from_schema(
  1029. arguments_group=arguments_group,
  1030. schema={
  1031. 'type': 'object',
  1032. 'properties': {
  1033. 'foo_and_stuff': {
  1034. 'type': 'string',
  1035. },
  1036. },
  1037. },
  1038. unparsed_arguments=(),
  1039. )