2
0

test_arguments.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  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)