test_check.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. import logging
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic.borg import check as module
  5. from ..test_verbosity import insert_logging_mock
  6. def insert_execute_command_mock(command, borg_exit_codes=None):
  7. flexmock(module.environment).should_receive('make_environment')
  8. flexmock(module).should_receive('execute_command').with_args(
  9. command,
  10. extra_environment=None,
  11. borg_local_path=command[0],
  12. borg_exit_codes=borg_exit_codes,
  13. ).once()
  14. def insert_execute_command_never():
  15. flexmock(module).should_receive('execute_command').never()
  16. def test_make_archive_filter_flags_with_default_checks_and_prefix_returns_default_flags():
  17. flexmock(module.feature).should_receive('available').and_return(True)
  18. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  19. flags = module.make_archive_filter_flags(
  20. '1.2.3',
  21. {'prefix': 'foo'},
  22. ('repository', 'archives'),
  23. check_arguments=flexmock(match_archives=None),
  24. )
  25. assert flags == ('--match-archives', 'sh:foo*')
  26. def test_make_archive_filter_flags_with_all_checks_and_prefix_returns_default_flags():
  27. flexmock(module.feature).should_receive('available').and_return(True)
  28. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  29. flags = module.make_archive_filter_flags(
  30. '1.2.3',
  31. {'prefix': 'foo'},
  32. ('repository', 'archives', 'extract'),
  33. check_arguments=flexmock(match_archives=None),
  34. )
  35. assert flags == ('--match-archives', 'sh:foo*')
  36. def test_make_archive_filter_flags_with_all_checks_and_prefix_without_borg_features_returns_glob_archives_flags():
  37. flexmock(module.feature).should_receive('available').and_return(False)
  38. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  39. flags = module.make_archive_filter_flags(
  40. '1.2.3',
  41. {'prefix': 'foo'},
  42. ('repository', 'archives', 'extract'),
  43. check_arguments=flexmock(match_archives=None),
  44. )
  45. assert flags == ('--glob-archives', 'foo*')
  46. def test_make_archive_filter_flags_with_archives_check_and_last_includes_last_flag():
  47. flexmock(module.feature).should_receive('available').and_return(True)
  48. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  49. flags = module.make_archive_filter_flags(
  50. '1.2.3',
  51. {'check_last': 3},
  52. ('archives',),
  53. check_arguments=flexmock(match_archives=None),
  54. )
  55. assert flags == ('--last', '3')
  56. def test_make_archive_filter_flags_with_data_check_and_last_includes_last_flag():
  57. flexmock(module.feature).should_receive('available').and_return(True)
  58. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  59. flags = module.make_archive_filter_flags(
  60. '1.2.3',
  61. {'check_last': 3},
  62. ('data',),
  63. check_arguments=flexmock(match_archives=None),
  64. )
  65. assert flags == ('--last', '3')
  66. def test_make_archive_filter_flags_with_repository_check_and_last_omits_last_flag():
  67. flexmock(module.feature).should_receive('available').and_return(True)
  68. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  69. flags = module.make_archive_filter_flags(
  70. '1.2.3',
  71. {'check_last': 3},
  72. ('repository',),
  73. check_arguments=flexmock(match_archives=None),
  74. )
  75. assert flags == ()
  76. def test_make_archive_filter_flags_with_default_checks_and_last_includes_last_flag():
  77. flexmock(module.feature).should_receive('available').and_return(True)
  78. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  79. flags = module.make_archive_filter_flags(
  80. '1.2.3',
  81. {'check_last': 3},
  82. ('repository', 'archives'),
  83. check_arguments=flexmock(match_archives=None),
  84. )
  85. assert flags == ('--last', '3')
  86. def test_make_archive_filter_flags_with_archives_check_and_prefix_includes_match_archives_flag():
  87. flexmock(module.feature).should_receive('available').and_return(True)
  88. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  89. flags = module.make_archive_filter_flags(
  90. '1.2.3',
  91. {'prefix': 'foo-'},
  92. ('archives',),
  93. check_arguments=flexmock(match_archives=None),
  94. )
  95. assert flags == ('--match-archives', 'sh:foo-*')
  96. def test_make_archive_filter_flags_with_data_check_and_prefix_includes_match_archives_flag():
  97. flexmock(module.feature).should_receive('available').and_return(True)
  98. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  99. flags = module.make_archive_filter_flags(
  100. '1.2.3',
  101. {'prefix': 'foo-'},
  102. ('data',),
  103. check_arguments=flexmock(match_archives=None),
  104. )
  105. assert flags == ('--match-archives', 'sh:foo-*')
  106. def test_make_archive_filter_flags_prefers_check_arguments_match_archives_to_config_match_archives():
  107. flexmock(module.feature).should_receive('available').and_return(True)
  108. flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
  109. 'baz-*', None, '1.2.3'
  110. ).and_return(('--match-archives', 'sh:baz-*'))
  111. flags = module.make_archive_filter_flags(
  112. '1.2.3',
  113. {'match_archives': 'bar-{now}', 'prefix': ''}, # noqa: FS003
  114. ('archives',),
  115. check_arguments=flexmock(match_archives='baz-*'),
  116. )
  117. assert flags == ('--match-archives', 'sh:baz-*')
  118. def test_make_archive_filter_flags_with_archives_check_and_empty_prefix_uses_archive_name_format_instead():
  119. flexmock(module.feature).should_receive('available').and_return(True)
  120. flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
  121. None, 'bar-{now}', '1.2.3' # noqa: FS003
  122. ).and_return(('--match-archives', 'sh:bar-*'))
  123. flags = module.make_archive_filter_flags(
  124. '1.2.3',
  125. {'archive_name_format': 'bar-{now}', 'prefix': ''}, # noqa: FS003
  126. ('archives',),
  127. check_arguments=flexmock(match_archives=None),
  128. )
  129. assert flags == ('--match-archives', 'sh:bar-*')
  130. def test_make_archive_filter_flags_with_archives_check_and_none_prefix_omits_match_archives_flag():
  131. flexmock(module.feature).should_receive('available').and_return(True)
  132. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  133. flags = module.make_archive_filter_flags(
  134. '1.2.3',
  135. {},
  136. ('archives',),
  137. check_arguments=flexmock(match_archives=None),
  138. )
  139. assert flags == ()
  140. def test_make_archive_filter_flags_with_repository_check_and_prefix_omits_match_archives_flag():
  141. flexmock(module.feature).should_receive('available').and_return(True)
  142. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  143. flags = module.make_archive_filter_flags(
  144. '1.2.3',
  145. {'prefix': 'foo-'},
  146. ('repository',),
  147. check_arguments=flexmock(match_archives=None),
  148. )
  149. assert flags == ()
  150. def test_make_archive_filter_flags_with_default_checks_and_prefix_includes_match_archives_flag():
  151. flexmock(module.feature).should_receive('available').and_return(True)
  152. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  153. flags = module.make_archive_filter_flags(
  154. '1.2.3',
  155. {'prefix': 'foo-'},
  156. ('repository', 'archives'),
  157. check_arguments=flexmock(match_archives=None),
  158. )
  159. assert flags == ('--match-archives', 'sh:foo-*')
  160. def test_make_check_flags_with_repository_check_returns_flag():
  161. flags = module.make_check_flags(('repository',), ())
  162. assert flags == ('--repository-only',)
  163. def test_make_check_flags_with_archives_check_returns_flag():
  164. flags = module.make_check_flags(('archives',), ())
  165. assert flags == ('--archives-only',)
  166. def test_make_check_flags_with_archives_check_and_archive_filter_flags_includes_those_flags():
  167. flags = module.make_check_flags(('archives',), ('--match-archives', 'sh:foo-*'))
  168. assert flags == ('--archives-only', '--match-archives', 'sh:foo-*')
  169. def test_make_check_flags_without_archives_check_and_with_archive_filter_flags_includes_those_flags():
  170. flags = module.make_check_flags(('repository',), ('--match-archives', 'sh:foo-*'))
  171. assert flags == ('--repository-only',)
  172. def test_make_check_flags_with_data_check_returns_flag_and_implies_archives():
  173. flexmock(module.feature).should_receive('available').and_return(True)
  174. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  175. flags = module.make_check_flags(('data',), ())
  176. assert flags == (
  177. '--archives-only',
  178. '--verify-data',
  179. )
  180. def test_make_check_flags_with_extract_omits_extract_flag():
  181. flexmock(module.feature).should_receive('available').and_return(True)
  182. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  183. flags = module.make_check_flags(('extract',), ())
  184. assert flags == ()
  185. def test_make_check_flags_with_repository_and_data_checks_does_not_return_repository_only():
  186. flexmock(module.feature).should_receive('available').and_return(True)
  187. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  188. flags = module.make_check_flags(
  189. (
  190. 'repository',
  191. 'data',
  192. ),
  193. (),
  194. )
  195. assert flags == ('--verify-data',)
  196. def test_get_repository_id_with_valid_json_does_not_raise():
  197. config = {}
  198. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  199. '{"repository": {"id": "repo"}}'
  200. )
  201. assert module.get_repository_id(
  202. repository_path='repo',
  203. config=config,
  204. local_borg_version='1.2.3',
  205. global_arguments=flexmock(log_json=False),
  206. local_path='borg',
  207. remote_path=None,
  208. )
  209. def test_get_repository_id_with_json_error_raises():
  210. config = {}
  211. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  212. '{"unexpected": {"id": "repo"}}'
  213. )
  214. with pytest.raises(ValueError):
  215. module.get_repository_id(
  216. repository_path='repo',
  217. config=config,
  218. local_borg_version='1.2.3',
  219. global_arguments=flexmock(log_json=False),
  220. local_path='borg',
  221. remote_path=None,
  222. )
  223. def test_get_repository_id_with_missing_json_keys_raises():
  224. config = {}
  225. flexmock(module.rinfo).should_receive('display_repository_info').and_return('{invalid JSON')
  226. with pytest.raises(ValueError):
  227. module.get_repository_id(
  228. repository_path='repo',
  229. config=config,
  230. local_borg_version='1.2.3',
  231. global_arguments=flexmock(log_json=False),
  232. local_path='borg',
  233. remote_path=None,
  234. )
  235. def test_check_archives_with_progress_passes_through_to_borg():
  236. config = {}
  237. flexmock(module).should_receive('make_check_flags').and_return(())
  238. flexmock(module).should_receive('execute_command').never()
  239. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  240. flexmock(module.environment).should_receive('make_environment')
  241. flexmock(module).should_receive('execute_command').with_args(
  242. ('borg', 'check', '--progress', 'repo'),
  243. output_file=module.DO_NOT_CAPTURE,
  244. extra_environment=None,
  245. borg_local_path='borg',
  246. borg_exit_codes=None,
  247. ).once()
  248. module.check_archives(
  249. repository_path='repo',
  250. config=config,
  251. local_borg_version='1.2.3',
  252. check_arguments=flexmock(
  253. progress=True, repair=None, only_checks=None, force=None, match_archives=None
  254. ),
  255. global_arguments=flexmock(log_json=False),
  256. checks={'repository'},
  257. archive_filter_flags=(),
  258. )
  259. def test_check_archives_with_repair_passes_through_to_borg():
  260. config = {}
  261. flexmock(module).should_receive('make_check_flags').and_return(())
  262. flexmock(module).should_receive('execute_command').never()
  263. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  264. flexmock(module.environment).should_receive('make_environment')
  265. flexmock(module).should_receive('execute_command').with_args(
  266. ('borg', 'check', '--repair', 'repo'),
  267. output_file=module.DO_NOT_CAPTURE,
  268. extra_environment=None,
  269. borg_local_path='borg',
  270. borg_exit_codes=None,
  271. ).once()
  272. module.check_archives(
  273. repository_path='repo',
  274. config=config,
  275. local_borg_version='1.2.3',
  276. check_arguments=flexmock(
  277. progress=None, repair=True, only_checks=None, force=None, match_archives=None
  278. ),
  279. global_arguments=flexmock(log_json=False),
  280. checks={'repository'},
  281. archive_filter_flags=(),
  282. )
  283. @pytest.mark.parametrize(
  284. 'checks',
  285. (
  286. ('repository',),
  287. ('archives',),
  288. ('repository', 'archives'),
  289. ('repository', 'archives', 'other'),
  290. ),
  291. )
  292. def test_check_archives_calls_borg_with_parameters(checks):
  293. config = {}
  294. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  295. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  296. insert_execute_command_mock(('borg', 'check', 'repo'))
  297. module.check_archives(
  298. repository_path='repo',
  299. config=config,
  300. local_borg_version='1.2.3',
  301. check_arguments=flexmock(
  302. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  303. ),
  304. global_arguments=flexmock(log_json=False),
  305. checks=checks,
  306. archive_filter_flags=(),
  307. )
  308. def test_check_archives_with_log_info_passes_through_to_borg():
  309. config = {}
  310. flexmock(module).should_receive('make_check_flags').and_return(())
  311. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  312. insert_logging_mock(logging.INFO)
  313. insert_execute_command_mock(('borg', 'check', '--info', 'repo'))
  314. module.check_archives(
  315. repository_path='repo',
  316. config=config,
  317. local_borg_version='1.2.3',
  318. check_arguments=flexmock(
  319. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  320. ),
  321. global_arguments=flexmock(log_json=False),
  322. checks={'repository'},
  323. archive_filter_flags=(),
  324. )
  325. def test_check_archives_with_log_debug_passes_through_to_borg():
  326. config = {}
  327. flexmock(module).should_receive('make_check_flags').and_return(())
  328. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  329. insert_logging_mock(logging.DEBUG)
  330. insert_execute_command_mock(('borg', 'check', '--debug', '--show-rc', 'repo'))
  331. module.check_archives(
  332. repository_path='repo',
  333. config=config,
  334. local_borg_version='1.2.3',
  335. check_arguments=flexmock(
  336. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  337. ),
  338. global_arguments=flexmock(log_json=False),
  339. checks={'repository'},
  340. archive_filter_flags=(),
  341. )
  342. def test_check_archives_with_local_path_calls_borg_via_local_path():
  343. checks = {'repository'}
  344. config = {}
  345. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  346. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  347. insert_execute_command_mock(('borg1', 'check', 'repo'))
  348. module.check_archives(
  349. repository_path='repo',
  350. config=config,
  351. local_borg_version='1.2.3',
  352. check_arguments=flexmock(
  353. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  354. ),
  355. global_arguments=flexmock(log_json=False),
  356. checks=checks,
  357. archive_filter_flags=(),
  358. local_path='borg1',
  359. )
  360. def test_check_archives_with_exit_codes_calls_borg_using_them():
  361. checks = {'repository'}
  362. borg_exit_codes = flexmock()
  363. config = {'borg_exit_codes': borg_exit_codes}
  364. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  365. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  366. insert_execute_command_mock(('borg', 'check', 'repo'), borg_exit_codes=borg_exit_codes)
  367. module.check_archives(
  368. repository_path='repo',
  369. config=config,
  370. local_borg_version='1.2.3',
  371. check_arguments=flexmock(
  372. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  373. ),
  374. global_arguments=flexmock(log_json=False),
  375. checks=checks,
  376. archive_filter_flags=(),
  377. )
  378. def test_check_archives_with_remote_path_passes_through_to_borg():
  379. checks = {'repository'}
  380. config = {}
  381. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  382. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  383. insert_execute_command_mock(('borg', 'check', '--remote-path', 'borg1', 'repo'))
  384. module.check_archives(
  385. repository_path='repo',
  386. config=config,
  387. local_borg_version='1.2.3',
  388. check_arguments=flexmock(
  389. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  390. ),
  391. global_arguments=flexmock(log_json=False),
  392. checks=checks,
  393. archive_filter_flags=(),
  394. remote_path='borg1',
  395. )
  396. def test_check_archives_with_log_json_passes_through_to_borg():
  397. checks = {'repository'}
  398. config = {}
  399. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  400. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  401. insert_execute_command_mock(('borg', 'check', '--log-json', 'repo'))
  402. module.check_archives(
  403. repository_path='repo',
  404. config=config,
  405. local_borg_version='1.2.3',
  406. check_arguments=flexmock(
  407. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  408. ),
  409. global_arguments=flexmock(log_json=True),
  410. checks=checks,
  411. archive_filter_flags=(),
  412. )
  413. def test_check_archives_with_lock_wait_passes_through_to_borg():
  414. checks = {'repository'}
  415. config = {'lock_wait': 5}
  416. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  417. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  418. insert_execute_command_mock(('borg', 'check', '--lock-wait', '5', 'repo'))
  419. module.check_archives(
  420. repository_path='repo',
  421. config=config,
  422. local_borg_version='1.2.3',
  423. check_arguments=flexmock(
  424. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  425. ),
  426. global_arguments=flexmock(log_json=False),
  427. checks=checks,
  428. archive_filter_flags=(),
  429. )
  430. def test_check_archives_with_retention_prefix():
  431. checks = {'repository'}
  432. prefix = 'foo-'
  433. config = {'prefix': prefix}
  434. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  435. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  436. insert_execute_command_mock(('borg', 'check', 'repo'))
  437. module.check_archives(
  438. repository_path='repo',
  439. config=config,
  440. local_borg_version='1.2.3',
  441. check_arguments=flexmock(
  442. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  443. ),
  444. global_arguments=flexmock(log_json=False),
  445. checks=checks,
  446. archive_filter_flags=(),
  447. )
  448. def test_check_archives_with_extra_borg_options_passes_through_to_borg():
  449. config = {'extra_borg_options': {'check': '--extra --options'}}
  450. flexmock(module).should_receive('make_check_flags').and_return(())
  451. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  452. insert_execute_command_mock(('borg', 'check', '--extra', '--options', 'repo'))
  453. module.check_archives(
  454. repository_path='repo',
  455. config=config,
  456. local_borg_version='1.2.3',
  457. check_arguments=flexmock(
  458. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  459. ),
  460. global_arguments=flexmock(log_json=False),
  461. checks={'repository'},
  462. archive_filter_flags=(),
  463. )
  464. def test_check_archives_with_match_archives_passes_through_to_borg():
  465. config = {}
  466. flexmock(module).should_receive('make_check_flags').and_return(('--match-archives', 'foo-*'))
  467. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  468. flexmock(module.environment).should_receive('make_environment')
  469. flexmock(module).should_receive('execute_command').with_args(
  470. ('borg', 'check', '--match-archives', 'foo-*', 'repo'),
  471. extra_environment=None,
  472. borg_local_path='borg',
  473. borg_exit_codes=None,
  474. ).once()
  475. module.check_archives(
  476. repository_path='repo',
  477. config=config,
  478. local_borg_version='1.2.3',
  479. check_arguments=flexmock(
  480. progress=None, repair=None, only_checks=None, force=None, match_archives='foo-*'
  481. ),
  482. global_arguments=flexmock(log_json=False),
  483. checks={'archives'},
  484. archive_filter_flags=('--match-archives', 'foo-*'),
  485. )