test_arguments.py 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183
  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('help 2')
  742. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(int).and_return(str)
  743. arguments_group.should_receive('add_argument').with_args(
  744. '--foo.bar',
  745. type=int,
  746. metavar='BAR',
  747. help='help 1',
  748. ).once()
  749. arguments_group.should_receive('add_argument').with_args(
  750. '--foo.baz',
  751. type=str,
  752. metavar='BAZ',
  753. help='help 2',
  754. ).once()
  755. flexmock(module).should_receive('add_array_element_arguments')
  756. module.add_arguments_from_schema(
  757. arguments_group=arguments_group,
  758. schema={
  759. 'type': 'object',
  760. 'properties': {
  761. 'foo': {
  762. 'type': 'object',
  763. 'properties': {
  764. 'bar': {'type': 'integer'},
  765. 'baz': {'type': 'str'},
  766. }
  767. }
  768. }
  769. },
  770. unparsed_arguments=(),
  771. )
  772. def test_add_arguments_from_schema_uses_first_non_null_type_from_multi_type_object():
  773. arguments_group = flexmock()
  774. flexmock(module).should_receive('make_argument_description').and_return('help 1')
  775. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(int)
  776. arguments_group.should_receive('add_argument').with_args(
  777. '--foo.bar',
  778. type=int,
  779. metavar='BAR',
  780. help='help 1',
  781. ).once()
  782. flexmock(module).should_receive('add_array_element_arguments')
  783. module.add_arguments_from_schema(
  784. arguments_group=arguments_group,
  785. schema={
  786. 'type': 'object',
  787. 'properties': {
  788. 'foo': {
  789. 'type': ['null', 'object', 'boolean'],
  790. 'properties': {
  791. 'bar': {'type': 'integer'},
  792. }
  793. }
  794. }
  795. },
  796. unparsed_arguments=(),
  797. )
  798. def test_add_arguments_from_schema_with_empty_multi_type_raises():
  799. arguments_group = flexmock()
  800. flexmock(module).should_receive('make_argument_description').and_return('help 1')
  801. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(int)
  802. arguments_group.should_receive('add_argument').never()
  803. flexmock(module).should_receive('add_array_element_arguments').never()
  804. with pytest.raises(ValueError):
  805. module.add_arguments_from_schema(
  806. arguments_group=arguments_group,
  807. schema={
  808. 'type': 'object',
  809. 'properties': {
  810. 'foo': {
  811. 'type': [],
  812. 'properties': {
  813. 'bar': {'type': 'integer'},
  814. }
  815. }
  816. }
  817. },
  818. unparsed_arguments=(),
  819. )
  820. def test_add_arguments_from_schema_with_propertyless_option_does_not_add_flag():
  821. arguments_group = flexmock()
  822. flexmock(module).should_receive('make_argument_description').never()
  823. flexmock(module.borgmatic.config.schema).should_receive('parse_type').never()
  824. arguments_group.should_receive('add_argument').never()
  825. flexmock(module).should_receive('add_array_element_arguments').never()
  826. module.add_arguments_from_schema(
  827. arguments_group=arguments_group,
  828. schema={
  829. 'type': 'object',
  830. 'properties': {
  831. 'foo': {
  832. 'type': 'object',
  833. }
  834. }
  835. },
  836. unparsed_arguments=(),
  837. )
  838. def test_add_arguments_from_schema_with_array_adds_flag():
  839. arguments_group = flexmock()
  840. flexmock(module).should_receive('make_argument_description').and_return('help')
  841. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str)
  842. arguments_group.should_receive('add_argument').with_args(
  843. '--foo',
  844. type=str,
  845. metavar='FOO',
  846. help='help',
  847. ).once()
  848. flexmock(module).should_receive('add_array_element_arguments')
  849. module.add_arguments_from_schema(
  850. arguments_group=arguments_group,
  851. schema={
  852. 'type': 'object',
  853. 'properties': {
  854. 'foo': {
  855. 'type': 'array',
  856. 'items': {
  857. 'type': 'integer',
  858. }
  859. }
  860. }
  861. },
  862. unparsed_arguments=(),
  863. )
  864. def test_add_arguments_from_schema_with_array_and_nested_object_adds_multiple_flags():
  865. arguments_group = flexmock()
  866. flexmock(module).should_receive('make_argument_description').and_return('help 1').and_return('help 2')
  867. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(int).and_return(str)
  868. arguments_group.should_receive('add_argument').with_args(
  869. '--foo[0].bar',
  870. type=int,
  871. metavar='BAR',
  872. help='help 1',
  873. ).once()
  874. arguments_group.should_receive('add_argument').with_args(
  875. '--foo',
  876. type=str,
  877. metavar='FOO',
  878. help='help 2',
  879. ).once()
  880. flexmock(module).should_receive('add_array_element_arguments')
  881. flexmock(module).should_receive('add_array_element_arguments').with_args(
  882. arguments_group=arguments_group,
  883. unparsed_arguments=(),
  884. flag_name='foo[0].bar',
  885. ).once()
  886. module.add_arguments_from_schema(
  887. arguments_group=arguments_group,
  888. schema={
  889. 'type': 'object',
  890. 'properties': {
  891. 'foo': {
  892. 'type': 'array',
  893. 'items': {
  894. 'type': 'object',
  895. 'properties': {
  896. 'bar': {
  897. 'type': 'integer',
  898. }
  899. }
  900. }
  901. }
  902. }
  903. },
  904. unparsed_arguments=(),
  905. )
  906. def test_add_arguments_from_schema_with_default_false_boolean_adds_valueless_flag():
  907. arguments_group = flexmock()
  908. flexmock(module).should_receive('make_argument_description').and_return('help')
  909. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(bool)
  910. arguments_group.should_receive('add_argument').with_args(
  911. '--foo',
  912. action='store_true',
  913. default=None,
  914. help='help',
  915. ).once()
  916. flexmock(module).should_receive('add_array_element_arguments')
  917. module.add_arguments_from_schema(
  918. arguments_group=arguments_group,
  919. schema={
  920. 'type': 'object',
  921. 'properties': {
  922. 'foo': {
  923. 'type': 'boolean',
  924. 'default': False,
  925. }
  926. }
  927. },
  928. unparsed_arguments=(),
  929. )
  930. def test_add_arguments_from_schema_with_default_true_boolean_adds_value_flag():
  931. arguments_group = flexmock()
  932. flexmock(module).should_receive('make_argument_description').and_return('help')
  933. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(bool)
  934. arguments_group.should_receive('add_argument').with_args(
  935. '--foo',
  936. type=bool,
  937. metavar='FOO',
  938. help='help',
  939. ).once()
  940. flexmock(module).should_receive('add_array_element_arguments')
  941. module.add_arguments_from_schema(
  942. arguments_group=arguments_group,
  943. schema={
  944. 'type': 'object',
  945. 'properties': {
  946. 'foo': {
  947. 'type': 'boolean',
  948. 'default': True,
  949. }
  950. }
  951. },
  952. unparsed_arguments=(),
  953. )
  954. def test_add_arguments_from_schema_with_defaultless_boolean_adds_value_flag():
  955. arguments_group = flexmock()
  956. flexmock(module).should_receive('make_argument_description').and_return('help')
  957. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(bool)
  958. arguments_group.should_receive('add_argument').with_args(
  959. '--foo',
  960. type=bool,
  961. metavar='FOO',
  962. help='help',
  963. ).once()
  964. flexmock(module).should_receive('add_array_element_arguments')
  965. module.add_arguments_from_schema(
  966. arguments_group=arguments_group,
  967. schema={
  968. 'type': 'object',
  969. 'properties': {
  970. 'foo': {
  971. 'type': 'boolean',
  972. }
  973. }
  974. },
  975. unparsed_arguments=(),
  976. )
  977. def test_add_arguments_from_schema_skips_omitted_flag_name():
  978. arguments_group = flexmock()
  979. flexmock(module).should_receive('make_argument_description').and_return('help')
  980. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str)
  981. arguments_group.should_receive('add_argument').with_args(
  982. '--match-archives',
  983. type=object,
  984. metavar=object,
  985. help=object,
  986. ).never()
  987. arguments_group.should_receive('add_argument').with_args(
  988. '--foo',
  989. type=str,
  990. metavar='FOO',
  991. help='help',
  992. ).once()
  993. flexmock(module).should_receive('add_array_element_arguments')
  994. module.add_arguments_from_schema(
  995. arguments_group=arguments_group,
  996. schema={
  997. 'type': 'object',
  998. 'properties': {
  999. 'match_archives': {
  1000. 'type': 'string',
  1001. },
  1002. 'foo': {
  1003. 'type': 'string',
  1004. },
  1005. }
  1006. },
  1007. unparsed_arguments=(),
  1008. )
  1009. def test_add_arguments_from_schema_rewrites_option_name_to_flag_name():
  1010. arguments_group = flexmock()
  1011. flexmock(module).should_receive('make_argument_description').and_return('help')
  1012. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str)
  1013. arguments_group.should_receive('add_argument').with_args(
  1014. '--foo-and-stuff',
  1015. type=str,
  1016. metavar='FOO_AND_STUFF',
  1017. help='help',
  1018. ).once()
  1019. flexmock(module).should_receive('add_array_element_arguments')
  1020. module.add_arguments_from_schema(
  1021. arguments_group=arguments_group,
  1022. schema={
  1023. 'type': 'object',
  1024. 'properties': {
  1025. 'foo_and_stuff': {
  1026. 'type': 'string',
  1027. },
  1028. }
  1029. },
  1030. unparsed_arguments=(),
  1031. )