test_check.py 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130
  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):
  7. flexmock(module.environment).should_receive('make_environment')
  8. flexmock(module).should_receive('execute_command').with_args(
  9. command, extra_environment=None
  10. ).once()
  11. def insert_execute_command_never():
  12. flexmock(module).should_receive('execute_command').never()
  13. def test_parse_checks_returns_them_as_tuple():
  14. checks = module.parse_checks({'checks': [{'name': 'foo'}, {'name': 'bar'}]})
  15. assert checks == ('foo', 'bar')
  16. def test_parse_checks_with_missing_value_returns_defaults():
  17. checks = module.parse_checks({})
  18. assert checks == ('repository', 'archives')
  19. def test_parse_checks_with_empty_list_returns_defaults():
  20. checks = module.parse_checks({'checks': []})
  21. assert checks == ('repository', 'archives')
  22. def test_parse_checks_with_none_value_returns_defaults():
  23. checks = module.parse_checks({'checks': None})
  24. assert checks == ('repository', 'archives')
  25. def test_parse_checks_with_disabled_returns_no_checks():
  26. checks = module.parse_checks({'checks': [{'name': 'foo'}, {'name': 'disabled'}]})
  27. assert checks == ()
  28. def test_parse_checks_prefers_override_checks_to_configured_checks():
  29. checks = module.parse_checks(
  30. {'checks': [{'name': 'archives'}]}, only_checks=['repository', 'extract']
  31. )
  32. assert checks == ('repository', 'extract')
  33. @pytest.mark.parametrize(
  34. 'frequency,expected_result',
  35. (
  36. (None, None),
  37. ('always', None),
  38. ('1 hour', module.datetime.timedelta(hours=1)),
  39. ('2 hours', module.datetime.timedelta(hours=2)),
  40. ('1 day', module.datetime.timedelta(days=1)),
  41. ('2 days', module.datetime.timedelta(days=2)),
  42. ('1 week', module.datetime.timedelta(weeks=1)),
  43. ('2 weeks', module.datetime.timedelta(weeks=2)),
  44. ('1 month', module.datetime.timedelta(days=30)),
  45. ('2 months', module.datetime.timedelta(days=60)),
  46. ('1 year', module.datetime.timedelta(days=365)),
  47. ('2 years', module.datetime.timedelta(days=365 * 2)),
  48. ),
  49. )
  50. def test_parse_frequency_parses_into_timedeltas(frequency, expected_result):
  51. assert module.parse_frequency(frequency) == expected_result
  52. @pytest.mark.parametrize(
  53. 'frequency',
  54. (
  55. 'sometime',
  56. 'x days',
  57. '3 decades',
  58. ),
  59. )
  60. def test_parse_frequency_raises_on_parse_error(frequency):
  61. with pytest.raises(ValueError):
  62. module.parse_frequency(frequency)
  63. def test_filter_checks_on_frequency_without_config_uses_default_checks():
  64. flexmock(module).should_receive('parse_frequency').and_return(
  65. module.datetime.timedelta(weeks=4)
  66. )
  67. flexmock(module).should_receive('make_check_time_path')
  68. flexmock(module).should_receive('probe_for_check_time').and_return(None)
  69. assert module.filter_checks_on_frequency(
  70. config={},
  71. borg_repository_id='repo',
  72. checks=('repository', 'archives'),
  73. force=False,
  74. archives_check_id='1234',
  75. ) == ('repository', 'archives')
  76. def test_filter_checks_on_frequency_retains_unconfigured_check():
  77. assert module.filter_checks_on_frequency(
  78. config={},
  79. borg_repository_id='repo',
  80. checks=('data',),
  81. force=False,
  82. ) == ('data',)
  83. def test_filter_checks_on_frequency_retains_check_without_frequency():
  84. flexmock(module).should_receive('parse_frequency').and_return(None)
  85. assert module.filter_checks_on_frequency(
  86. config={'checks': [{'name': 'archives'}]},
  87. borg_repository_id='repo',
  88. checks=('archives',),
  89. force=False,
  90. archives_check_id='1234',
  91. ) == ('archives',)
  92. def test_filter_checks_on_frequency_retains_check_with_elapsed_frequency():
  93. flexmock(module).should_receive('parse_frequency').and_return(
  94. module.datetime.timedelta(hours=1)
  95. )
  96. flexmock(module).should_receive('make_check_time_path')
  97. flexmock(module).should_receive('probe_for_check_time').and_return(
  98. module.datetime.datetime(year=module.datetime.MINYEAR, month=1, day=1)
  99. )
  100. assert module.filter_checks_on_frequency(
  101. config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
  102. borg_repository_id='repo',
  103. checks=('archives',),
  104. force=False,
  105. archives_check_id='1234',
  106. ) == ('archives',)
  107. def test_filter_checks_on_frequency_retains_check_with_missing_check_time_file():
  108. flexmock(module).should_receive('parse_frequency').and_return(
  109. module.datetime.timedelta(hours=1)
  110. )
  111. flexmock(module).should_receive('make_check_time_path')
  112. flexmock(module).should_receive('probe_for_check_time').and_return(None)
  113. assert module.filter_checks_on_frequency(
  114. config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
  115. borg_repository_id='repo',
  116. checks=('archives',),
  117. force=False,
  118. archives_check_id='1234',
  119. ) == ('archives',)
  120. def test_filter_checks_on_frequency_skips_check_with_unelapsed_frequency():
  121. flexmock(module).should_receive('parse_frequency').and_return(
  122. module.datetime.timedelta(hours=1)
  123. )
  124. flexmock(module).should_receive('make_check_time_path')
  125. flexmock(module).should_receive('probe_for_check_time').and_return(
  126. module.datetime.datetime.now()
  127. )
  128. assert (
  129. module.filter_checks_on_frequency(
  130. config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
  131. borg_repository_id='repo',
  132. checks=('archives',),
  133. force=False,
  134. archives_check_id='1234',
  135. )
  136. == ()
  137. )
  138. def test_filter_checks_on_frequency_restains_check_with_unelapsed_frequency_and_force():
  139. assert module.filter_checks_on_frequency(
  140. config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
  141. borg_repository_id='repo',
  142. checks=('archives',),
  143. force=True,
  144. archives_check_id='1234',
  145. ) == ('archives',)
  146. def test_make_archive_filter_flags_with_default_checks_and_prefix_returns_default_flags():
  147. flexmock(module.feature).should_receive('available').and_return(True)
  148. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  149. flags = module.make_archive_filter_flags(
  150. '1.2.3',
  151. {},
  152. ('repository', 'archives'),
  153. check_arguments=flexmock(match_archives=None),
  154. prefix='foo',
  155. )
  156. assert flags == ('--match-archives', 'sh:foo*')
  157. def test_make_archive_filter_flags_with_all_checks_and_prefix_returns_default_flags():
  158. flexmock(module.feature).should_receive('available').and_return(True)
  159. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  160. flags = module.make_archive_filter_flags(
  161. '1.2.3',
  162. {},
  163. ('repository', 'archives', 'extract'),
  164. check_arguments=flexmock(match_archives=None),
  165. prefix='foo',
  166. )
  167. assert flags == ('--match-archives', 'sh:foo*')
  168. def test_make_archive_filter_flags_with_all_checks_and_prefix_without_borg_features_returns_glob_archives_flags():
  169. flexmock(module.feature).should_receive('available').and_return(False)
  170. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  171. flags = module.make_archive_filter_flags(
  172. '1.2.3',
  173. {},
  174. ('repository', 'archives', 'extract'),
  175. check_arguments=flexmock(match_archives=None),
  176. prefix='foo',
  177. )
  178. assert flags == ('--glob-archives', 'foo*')
  179. def test_make_archive_filter_flags_with_archives_check_and_last_includes_last_flag():
  180. flexmock(module.feature).should_receive('available').and_return(True)
  181. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  182. flags = module.make_archive_filter_flags(
  183. '1.2.3', {}, ('archives',), check_arguments=flexmock(match_archives=None), check_last=3
  184. )
  185. assert flags == ('--last', '3')
  186. def test_make_archive_filter_flags_with_data_check_and_last_includes_last_flag():
  187. flexmock(module.feature).should_receive('available').and_return(True)
  188. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  189. flags = module.make_archive_filter_flags(
  190. '1.2.3', {}, ('data',), check_arguments=flexmock(match_archives=None), check_last=3
  191. )
  192. assert flags == ('--last', '3')
  193. def test_make_archive_filter_flags_with_repository_check_and_last_omits_last_flag():
  194. flexmock(module.feature).should_receive('available').and_return(True)
  195. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  196. flags = module.make_archive_filter_flags(
  197. '1.2.3', {}, ('repository',), check_arguments=flexmock(match_archives=None), check_last=3
  198. )
  199. assert flags == ()
  200. def test_make_archive_filter_flags_with_default_checks_and_last_includes_last_flag():
  201. flexmock(module.feature).should_receive('available').and_return(True)
  202. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  203. flags = module.make_archive_filter_flags(
  204. '1.2.3',
  205. {},
  206. ('repository', 'archives'),
  207. check_arguments=flexmock(match_archives=None),
  208. check_last=3,
  209. )
  210. assert flags == ('--last', '3')
  211. def test_make_archive_filter_flags_with_archives_check_and_prefix_includes_match_archives_flag():
  212. flexmock(module.feature).should_receive('available').and_return(True)
  213. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  214. flags = module.make_archive_filter_flags(
  215. '1.2.3', {}, ('archives',), check_arguments=flexmock(match_archives=None), prefix='foo-'
  216. )
  217. assert flags == ('--match-archives', 'sh:foo-*')
  218. def test_make_archive_filter_flags_with_data_check_and_prefix_includes_match_archives_flag():
  219. flexmock(module.feature).should_receive('available').and_return(True)
  220. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  221. flags = module.make_archive_filter_flags(
  222. '1.2.3', {}, ('data',), check_arguments=flexmock(match_archives=None), prefix='foo-'
  223. )
  224. assert flags == ('--match-archives', 'sh:foo-*')
  225. def test_make_archive_filter_flags_prefers_check_arguments_match_archives_to_config_match_archives():
  226. flexmock(module.feature).should_receive('available').and_return(True)
  227. flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
  228. 'baz-*', None, '1.2.3'
  229. ).and_return(('--match-archives', 'sh:baz-*'))
  230. flags = module.make_archive_filter_flags(
  231. '1.2.3',
  232. {'match_archives': 'bar-{now}'}, # noqa: FS003
  233. ('archives',),
  234. check_arguments=flexmock(match_archives='baz-*'),
  235. prefix='',
  236. )
  237. assert flags == ('--match-archives', 'sh:baz-*')
  238. def test_make_archive_filter_flags_with_archives_check_and_empty_prefix_uses_archive_name_format_instead():
  239. flexmock(module.feature).should_receive('available').and_return(True)
  240. flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
  241. None, 'bar-{now}', '1.2.3' # noqa: FS003
  242. ).and_return(('--match-archives', 'sh:bar-*'))
  243. flags = module.make_archive_filter_flags(
  244. '1.2.3',
  245. {'archive_name_format': 'bar-{now}'}, # noqa: FS003
  246. ('archives',),
  247. check_arguments=flexmock(match_archives=None),
  248. prefix='',
  249. )
  250. assert flags == ('--match-archives', 'sh:bar-*')
  251. def test_make_archive_filter_flags_with_archives_check_and_none_prefix_omits_match_archives_flag():
  252. flexmock(module.feature).should_receive('available').and_return(True)
  253. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  254. flags = module.make_archive_filter_flags(
  255. '1.2.3', {}, ('archives',), check_arguments=flexmock(match_archives=None), prefix=None
  256. )
  257. assert flags == ()
  258. def test_make_archive_filter_flags_with_repository_check_and_prefix_omits_match_archives_flag():
  259. flexmock(module.feature).should_receive('available').and_return(True)
  260. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  261. flags = module.make_archive_filter_flags(
  262. '1.2.3', {}, ('repository',), check_arguments=flexmock(match_archives=None), prefix='foo-'
  263. )
  264. assert flags == ()
  265. def test_make_archive_filter_flags_with_default_checks_and_prefix_includes_match_archives_flag():
  266. flexmock(module.feature).should_receive('available').and_return(True)
  267. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  268. flags = module.make_archive_filter_flags(
  269. '1.2.3',
  270. {},
  271. ('repository', 'archives'),
  272. check_arguments=flexmock(match_archives=None),
  273. prefix='foo-',
  274. )
  275. assert flags == ('--match-archives', 'sh:foo-*')
  276. def test_make_archives_check_id_with_flags_returns_a_value_and_does_not_raise():
  277. assert module.make_archives_check_id(('--match-archives', 'sh:foo-*'))
  278. def test_make_archives_check_id_with_empty_flags_returns_none():
  279. assert module.make_archives_check_id(()) is None
  280. def test_make_check_flags_with_repository_check_returns_flag():
  281. flags = module.make_check_flags(('repository',), ())
  282. assert flags == ('--repository-only',)
  283. def test_make_check_flags_with_archives_check_returns_flag():
  284. flags = module.make_check_flags(('archives',), ())
  285. assert flags == ('--archives-only',)
  286. def test_make_check_flags_with_archives_check_and_archive_filter_flags_includes_those_flags():
  287. flags = module.make_check_flags(('archives',), ('--match-archives', 'sh:foo-*'))
  288. assert flags == ('--archives-only', '--match-archives', 'sh:foo-*')
  289. def test_make_check_flags_without_archives_check_and_with_archive_filter_flags_includes_those_flags():
  290. flags = module.make_check_flags(('repository',), ('--match-archives', 'sh:foo-*'))
  291. assert flags == ('--repository-only',)
  292. def test_make_check_flags_with_data_check_returns_flag_and_implies_archives():
  293. flexmock(module.feature).should_receive('available').and_return(True)
  294. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  295. flags = module.make_check_flags(('data',), ())
  296. assert flags == (
  297. '--archives-only',
  298. '--verify-data',
  299. )
  300. def test_make_check_flags_with_extract_omits_extract_flag():
  301. flexmock(module.feature).should_receive('available').and_return(True)
  302. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  303. flags = module.make_check_flags(('extract',), ())
  304. assert flags == ()
  305. def test_make_check_flags_with_repository_and_data_checks_does_not_return_repository_only():
  306. flexmock(module.feature).should_receive('available').and_return(True)
  307. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  308. flags = module.make_check_flags(
  309. (
  310. 'repository',
  311. 'data',
  312. ),
  313. (),
  314. )
  315. assert flags == ('--verify-data',)
  316. def test_make_check_time_path_with_borgmatic_source_directory_includes_it():
  317. flexmock(module.os.path).should_receive('expanduser').with_args('~/.borgmatic').and_return(
  318. '/home/user/.borgmatic'
  319. )
  320. assert (
  321. module.make_check_time_path(
  322. {'borgmatic_source_directory': '~/.borgmatic'}, '1234', 'archives', '5678'
  323. )
  324. == '/home/user/.borgmatic/checks/1234/archives/5678'
  325. )
  326. def test_make_check_time_path_without_borgmatic_source_directory_uses_default():
  327. flexmock(module.os.path).should_receive('expanduser').with_args(
  328. module.state.DEFAULT_BORGMATIC_SOURCE_DIRECTORY
  329. ).and_return('/home/user/.borgmatic')
  330. assert (
  331. module.make_check_time_path({}, '1234', 'archives', '5678')
  332. == '/home/user/.borgmatic/checks/1234/archives/5678'
  333. )
  334. def test_make_check_time_path_with_archives_check_and_no_archives_check_id_defaults_to_all():
  335. flexmock(module.os.path).should_receive('expanduser').with_args('~/.borgmatic').and_return(
  336. '/home/user/.borgmatic'
  337. )
  338. assert (
  339. module.make_check_time_path(
  340. {'borgmatic_source_directory': '~/.borgmatic'},
  341. '1234',
  342. 'archives',
  343. )
  344. == '/home/user/.borgmatic/checks/1234/archives/all'
  345. )
  346. def test_make_check_time_path_with_repositories_check_ignores_archives_check_id():
  347. flexmock(module.os.path).should_receive('expanduser').with_args('~/.borgmatic').and_return(
  348. '/home/user/.borgmatic'
  349. )
  350. assert (
  351. module.make_check_time_path(
  352. {'borgmatic_source_directory': '~/.borgmatic'}, '1234', 'repository', '5678'
  353. )
  354. == '/home/user/.borgmatic/checks/1234/repository'
  355. )
  356. def test_read_check_time_does_not_raise():
  357. flexmock(module.os).should_receive('stat').and_return(flexmock(st_mtime=123))
  358. assert module.read_check_time('/path')
  359. def test_read_check_time_on_missing_file_does_not_raise():
  360. flexmock(module.os).should_receive('stat').and_raise(FileNotFoundError)
  361. assert module.read_check_time('/path') is None
  362. def test_probe_for_check_time_uses_maximum_of_multiple_check_times():
  363. flexmock(module).should_receive('make_check_time_path').and_return(
  364. '~/.borgmatic/checks/1234/archives/5678'
  365. ).and_return('~/.borgmatic/checks/1234/archives/all')
  366. flexmock(module).should_receive('read_check_time').and_return(1).and_return(2)
  367. assert module.probe_for_check_time(flexmock(), flexmock(), flexmock(), flexmock()) == 2
  368. def test_probe_for_check_time_deduplicates_identical_check_time_paths():
  369. flexmock(module).should_receive('make_check_time_path').and_return(
  370. '~/.borgmatic/checks/1234/archives/5678'
  371. ).and_return('~/.borgmatic/checks/1234/archives/5678')
  372. flexmock(module).should_receive('read_check_time').and_return(1).once()
  373. assert module.probe_for_check_time(flexmock(), flexmock(), flexmock(), flexmock()) == 1
  374. def test_probe_for_check_time_skips_none_check_time():
  375. flexmock(module).should_receive('make_check_time_path').and_return(
  376. '~/.borgmatic/checks/1234/archives/5678'
  377. ).and_return('~/.borgmatic/checks/1234/archives/all')
  378. flexmock(module).should_receive('read_check_time').and_return(None).and_return(2)
  379. assert module.probe_for_check_time(flexmock(), flexmock(), flexmock(), flexmock()) == 2
  380. def test_probe_for_check_time_uses_single_check_time():
  381. flexmock(module).should_receive('make_check_time_path').and_return(
  382. '~/.borgmatic/checks/1234/archives/5678'
  383. ).and_return('~/.borgmatic/checks/1234/archives/all')
  384. flexmock(module).should_receive('read_check_time').and_return(1).and_return(None)
  385. assert module.probe_for_check_time(flexmock(), flexmock(), flexmock(), flexmock()) == 1
  386. def test_probe_for_check_time_returns_none_when_no_check_time_found():
  387. flexmock(module).should_receive('make_check_time_path').and_return(
  388. '~/.borgmatic/checks/1234/archives/5678'
  389. ).and_return('~/.borgmatic/checks/1234/archives/all')
  390. flexmock(module).should_receive('read_check_time').and_return(None).and_return(None)
  391. assert module.probe_for_check_time(flexmock(), flexmock(), flexmock(), flexmock()) is None
  392. def test_upgrade_check_times_renames_old_check_paths_to_all():
  393. base_path = '~/.borgmatic/checks/1234'
  394. flexmock(module).should_receive('make_check_time_path').with_args(
  395. object, object, 'archives', 'all'
  396. ).and_return(f'{base_path}/archives/all')
  397. flexmock(module).should_receive('make_check_time_path').with_args(
  398. object, object, 'data', 'all'
  399. ).and_return(f'{base_path}/data/all')
  400. flexmock(module.os.path).should_receive('isfile').with_args(f'{base_path}/archives').and_return(
  401. True
  402. )
  403. flexmock(module.os.path).should_receive('isfile').with_args(
  404. f'{base_path}/archives.temp'
  405. ).and_return(False)
  406. flexmock(module.os.path).should_receive('isfile').with_args(f'{base_path}/data').and_return(
  407. False
  408. )
  409. flexmock(module.os.path).should_receive('isfile').with_args(
  410. f'{base_path}/data.temp'
  411. ).and_return(False)
  412. flexmock(module.os).should_receive('rename').with_args(
  413. f'{base_path}/archives', f'{base_path}/archives.temp'
  414. ).once()
  415. flexmock(module.os).should_receive('mkdir').with_args(f'{base_path}/archives').once()
  416. flexmock(module.os).should_receive('rename').with_args(
  417. f'{base_path}/archives.temp', f'{base_path}/archives/all'
  418. ).once()
  419. module.upgrade_check_times(flexmock(), flexmock())
  420. def test_upgrade_check_times_renames_data_check_paths_when_archives_paths_are_already_upgraded():
  421. base_path = '~/.borgmatic/checks/1234'
  422. flexmock(module).should_receive('make_check_time_path').with_args(
  423. object, object, 'archives', 'all'
  424. ).and_return(f'{base_path}/archives/all')
  425. flexmock(module).should_receive('make_check_time_path').with_args(
  426. object, object, 'data', 'all'
  427. ).and_return(f'{base_path}/data/all')
  428. flexmock(module.os.path).should_receive('isfile').with_args(f'{base_path}/archives').and_return(
  429. False
  430. )
  431. flexmock(module.os.path).should_receive('isfile').with_args(
  432. f'{base_path}/archives.temp'
  433. ).and_return(False)
  434. flexmock(module.os.path).should_receive('isfile').with_args(f'{base_path}/data').and_return(
  435. True
  436. )
  437. flexmock(module.os).should_receive('rename').with_args(
  438. f'{base_path}/data', f'{base_path}/data.temp'
  439. ).once()
  440. flexmock(module.os).should_receive('mkdir').with_args(f'{base_path}/data').once()
  441. flexmock(module.os).should_receive('rename').with_args(
  442. f'{base_path}/data.temp', f'{base_path}/data/all'
  443. ).once()
  444. module.upgrade_check_times(flexmock(), flexmock())
  445. def test_upgrade_check_times_skips_missing_check_paths():
  446. flexmock(module).should_receive('make_check_time_path').and_return(
  447. '~/.borgmatic/checks/1234/archives/all'
  448. )
  449. flexmock(module.os.path).should_receive('isfile').and_return(False)
  450. flexmock(module.os).should_receive('rename').never()
  451. flexmock(module.os).should_receive('mkdir').never()
  452. module.upgrade_check_times(flexmock(), flexmock())
  453. def test_upgrade_check_times_renames_stale_temporary_check_path():
  454. base_path = '~/.borgmatic/checks/1234'
  455. flexmock(module).should_receive('make_check_time_path').with_args(
  456. object, object, 'archives', 'all'
  457. ).and_return(f'{base_path}/archives/all')
  458. flexmock(module).should_receive('make_check_time_path').with_args(
  459. object, object, 'data', 'all'
  460. ).and_return(f'{base_path}/data/all')
  461. flexmock(module.os.path).should_receive('isfile').with_args(f'{base_path}/archives').and_return(
  462. False
  463. )
  464. flexmock(module.os.path).should_receive('isfile').with_args(
  465. f'{base_path}/archives.temp'
  466. ).and_return(True)
  467. flexmock(module.os.path).should_receive('isfile').with_args(f'{base_path}/data').and_return(
  468. False
  469. )
  470. flexmock(module.os.path).should_receive('isfile').with_args(
  471. f'{base_path}/data.temp'
  472. ).and_return(False)
  473. flexmock(module.os).should_receive('rename').with_args(
  474. f'{base_path}/archives', f'{base_path}/archives.temp'
  475. ).and_raise(FileNotFoundError)
  476. flexmock(module.os).should_receive('mkdir').with_args(f'{base_path}/archives').once()
  477. flexmock(module.os).should_receive('rename').with_args(
  478. f'{base_path}/archives.temp', f'{base_path}/archives/all'
  479. ).once()
  480. module.upgrade_check_times(flexmock(), flexmock())
  481. def test_check_archives_with_progress_passes_through_to_borg():
  482. checks = ('repository',)
  483. config = {'check_last': None}
  484. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  485. '{"repository": {"id": "repo"}}'
  486. )
  487. flexmock(module).should_receive('upgrade_check_times')
  488. flexmock(module).should_receive('parse_checks')
  489. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  490. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  491. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  492. flexmock(module).should_receive('make_check_flags').and_return(())
  493. flexmock(module).should_receive('execute_command').never()
  494. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  495. flexmock(module.environment).should_receive('make_environment')
  496. flexmock(module).should_receive('execute_command').with_args(
  497. ('borg', 'check', '--progress', 'repo'),
  498. output_file=module.DO_NOT_CAPTURE,
  499. extra_environment=None,
  500. ).once()
  501. flexmock(module).should_receive('make_check_time_path')
  502. flexmock(module).should_receive('write_check_time')
  503. module.check_archives(
  504. repository_path='repo',
  505. config=config,
  506. local_borg_version='1.2.3',
  507. check_arguments=flexmock(
  508. progress=True, repair=None, only_checks=None, force=None, match_archives=None
  509. ),
  510. global_arguments=flexmock(log_json=False),
  511. )
  512. def test_check_archives_with_repair_passes_through_to_borg():
  513. checks = ('repository',)
  514. config = {'check_last': None}
  515. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  516. '{"repository": {"id": "repo"}}'
  517. )
  518. flexmock(module).should_receive('upgrade_check_times')
  519. flexmock(module).should_receive('parse_checks')
  520. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  521. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  522. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  523. flexmock(module).should_receive('make_check_flags').and_return(())
  524. flexmock(module).should_receive('execute_command').never()
  525. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  526. flexmock(module.environment).should_receive('make_environment')
  527. flexmock(module).should_receive('execute_command').with_args(
  528. ('borg', 'check', '--repair', 'repo'),
  529. output_file=module.DO_NOT_CAPTURE,
  530. extra_environment=None,
  531. ).once()
  532. flexmock(module).should_receive('make_check_time_path')
  533. flexmock(module).should_receive('write_check_time')
  534. module.check_archives(
  535. repository_path='repo',
  536. config=config,
  537. local_borg_version='1.2.3',
  538. check_arguments=flexmock(
  539. progress=None, repair=True, only_checks=None, force=None, match_archives=None
  540. ),
  541. global_arguments=flexmock(log_json=False),
  542. )
  543. @pytest.mark.parametrize(
  544. 'checks',
  545. (
  546. ('repository',),
  547. ('archives',),
  548. ('repository', 'archives'),
  549. ('repository', 'archives', 'other'),
  550. ),
  551. )
  552. def test_check_archives_calls_borg_with_parameters(checks):
  553. check_last = flexmock()
  554. config = {'check_last': check_last}
  555. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  556. '{"repository": {"id": "repo"}}'
  557. )
  558. flexmock(module).should_receive('upgrade_check_times')
  559. flexmock(module).should_receive('parse_checks')
  560. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  561. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  562. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  563. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  564. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  565. insert_execute_command_mock(('borg', 'check', 'repo'))
  566. flexmock(module).should_receive('make_check_time_path')
  567. flexmock(module).should_receive('write_check_time')
  568. module.check_archives(
  569. repository_path='repo',
  570. config=config,
  571. local_borg_version='1.2.3',
  572. check_arguments=flexmock(
  573. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  574. ),
  575. global_arguments=flexmock(log_json=False),
  576. )
  577. def test_check_archives_with_json_error_raises():
  578. checks = ('archives',)
  579. check_last = flexmock()
  580. config = {'check_last': check_last}
  581. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  582. '{"unexpected": {"id": "repo"}}'
  583. )
  584. flexmock(module).should_receive('upgrade_check_times')
  585. flexmock(module).should_receive('parse_checks')
  586. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  587. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  588. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  589. with pytest.raises(ValueError):
  590. module.check_archives(
  591. repository_path='repo',
  592. config=config,
  593. local_borg_version='1.2.3',
  594. check_arguments=flexmock(
  595. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  596. ),
  597. global_arguments=flexmock(log_json=False),
  598. )
  599. def test_check_archives_with_missing_json_keys_raises():
  600. checks = ('archives',)
  601. check_last = flexmock()
  602. config = {'check_last': check_last}
  603. flexmock(module.rinfo).should_receive('display_repository_info').and_return('{invalid JSON')
  604. flexmock(module).should_receive('upgrade_check_times')
  605. flexmock(module).should_receive('parse_checks')
  606. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  607. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  608. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  609. with pytest.raises(ValueError):
  610. module.check_archives(
  611. repository_path='repo',
  612. config=config,
  613. local_borg_version='1.2.3',
  614. check_arguments=flexmock(
  615. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  616. ),
  617. global_arguments=flexmock(log_json=False),
  618. )
  619. def test_check_archives_with_extract_check_calls_extract_only():
  620. checks = ('extract',)
  621. check_last = flexmock()
  622. config = {'check_last': check_last}
  623. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  624. '{"repository": {"id": "repo"}}'
  625. )
  626. flexmock(module).should_receive('upgrade_check_times')
  627. flexmock(module).should_receive('parse_checks')
  628. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  629. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  630. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  631. flexmock(module).should_receive('make_check_flags').never()
  632. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  633. flexmock(module.extract).should_receive('extract_last_archive_dry_run').once()
  634. flexmock(module).should_receive('write_check_time')
  635. insert_execute_command_never()
  636. module.check_archives(
  637. repository_path='repo',
  638. config=config,
  639. local_borg_version='1.2.3',
  640. check_arguments=flexmock(
  641. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  642. ),
  643. global_arguments=flexmock(log_json=False),
  644. )
  645. def test_check_archives_with_log_info_passes_through_to_borg():
  646. checks = ('repository',)
  647. config = {'check_last': None}
  648. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  649. '{"repository": {"id": "repo"}}'
  650. )
  651. flexmock(module).should_receive('upgrade_check_times')
  652. flexmock(module).should_receive('parse_checks')
  653. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  654. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  655. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  656. flexmock(module).should_receive('make_check_flags').and_return(())
  657. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  658. insert_logging_mock(logging.INFO)
  659. insert_execute_command_mock(('borg', 'check', '--info', 'repo'))
  660. flexmock(module).should_receive('make_check_time_path')
  661. flexmock(module).should_receive('write_check_time')
  662. module.check_archives(
  663. repository_path='repo',
  664. config=config,
  665. local_borg_version='1.2.3',
  666. check_arguments=flexmock(
  667. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  668. ),
  669. global_arguments=flexmock(log_json=False),
  670. )
  671. def test_check_archives_with_log_debug_passes_through_to_borg():
  672. checks = ('repository',)
  673. config = {'check_last': None}
  674. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  675. '{"repository": {"id": "repo"}}'
  676. )
  677. flexmock(module).should_receive('upgrade_check_times')
  678. flexmock(module).should_receive('parse_checks')
  679. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  680. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  681. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  682. flexmock(module).should_receive('make_check_flags').and_return(())
  683. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  684. insert_logging_mock(logging.DEBUG)
  685. insert_execute_command_mock(('borg', 'check', '--debug', '--show-rc', 'repo'))
  686. flexmock(module).should_receive('make_check_time_path')
  687. flexmock(module).should_receive('write_check_time')
  688. module.check_archives(
  689. repository_path='repo',
  690. config=config,
  691. local_borg_version='1.2.3',
  692. check_arguments=flexmock(
  693. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  694. ),
  695. global_arguments=flexmock(log_json=False),
  696. )
  697. def test_check_archives_without_any_checks_bails():
  698. config = {'check_last': None}
  699. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  700. '{"repository": {"id": "repo"}}'
  701. )
  702. flexmock(module).should_receive('upgrade_check_times')
  703. flexmock(module).should_receive('parse_checks')
  704. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  705. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  706. flexmock(module).should_receive('filter_checks_on_frequency').and_return(())
  707. insert_execute_command_never()
  708. module.check_archives(
  709. repository_path='repo',
  710. config=config,
  711. local_borg_version='1.2.3',
  712. check_arguments=flexmock(
  713. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  714. ),
  715. global_arguments=flexmock(log_json=False),
  716. )
  717. def test_check_archives_with_local_path_calls_borg_via_local_path():
  718. checks = ('repository',)
  719. check_last = flexmock()
  720. config = {'check_last': check_last}
  721. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  722. '{"repository": {"id": "repo"}}'
  723. )
  724. flexmock(module).should_receive('upgrade_check_times')
  725. flexmock(module).should_receive('parse_checks')
  726. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  727. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  728. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  729. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  730. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  731. insert_execute_command_mock(('borg1', 'check', 'repo'))
  732. flexmock(module).should_receive('make_check_time_path')
  733. flexmock(module).should_receive('write_check_time')
  734. module.check_archives(
  735. repository_path='repo',
  736. config=config,
  737. local_borg_version='1.2.3',
  738. check_arguments=flexmock(
  739. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  740. ),
  741. global_arguments=flexmock(log_json=False),
  742. local_path='borg1',
  743. )
  744. def test_check_archives_with_remote_path_passes_through_to_borg():
  745. checks = ('repository',)
  746. check_last = flexmock()
  747. config = {'check_last': check_last}
  748. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  749. '{"repository": {"id": "repo"}}'
  750. )
  751. flexmock(module).should_receive('upgrade_check_times')
  752. flexmock(module).should_receive('parse_checks')
  753. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  754. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  755. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  756. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  757. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  758. insert_execute_command_mock(('borg', 'check', '--remote-path', 'borg1', 'repo'))
  759. flexmock(module).should_receive('make_check_time_path')
  760. flexmock(module).should_receive('write_check_time')
  761. module.check_archives(
  762. repository_path='repo',
  763. config=config,
  764. local_borg_version='1.2.3',
  765. check_arguments=flexmock(
  766. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  767. ),
  768. global_arguments=flexmock(log_json=False),
  769. remote_path='borg1',
  770. )
  771. def test_check_archives_with_log_json_passes_through_to_borg():
  772. checks = ('repository',)
  773. check_last = flexmock()
  774. config = {'check_last': check_last}
  775. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  776. '{"repository": {"id": "repo"}}'
  777. )
  778. flexmock(module).should_receive('upgrade_check_times')
  779. flexmock(module).should_receive('parse_checks')
  780. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  781. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  782. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  783. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  784. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  785. insert_execute_command_mock(('borg', 'check', '--log-json', 'repo'))
  786. flexmock(module).should_receive('make_check_time_path')
  787. flexmock(module).should_receive('write_check_time')
  788. module.check_archives(
  789. repository_path='repo',
  790. config=config,
  791. local_borg_version='1.2.3',
  792. check_arguments=flexmock(
  793. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  794. ),
  795. global_arguments=flexmock(log_json=True),
  796. )
  797. def test_check_archives_with_lock_wait_passes_through_to_borg():
  798. checks = ('repository',)
  799. check_last = flexmock()
  800. config = {'lock_wait': 5, 'check_last': check_last}
  801. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  802. '{"repository": {"id": "repo"}}'
  803. )
  804. flexmock(module).should_receive('upgrade_check_times')
  805. flexmock(module).should_receive('parse_checks')
  806. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  807. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  808. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  809. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  810. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  811. insert_execute_command_mock(('borg', 'check', '--lock-wait', '5', 'repo'))
  812. flexmock(module).should_receive('make_check_time_path')
  813. flexmock(module).should_receive('write_check_time')
  814. module.check_archives(
  815. repository_path='repo',
  816. config=config,
  817. local_borg_version='1.2.3',
  818. check_arguments=flexmock(
  819. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  820. ),
  821. global_arguments=flexmock(log_json=False),
  822. )
  823. def test_check_archives_with_retention_prefix():
  824. checks = ('repository',)
  825. check_last = flexmock()
  826. prefix = 'foo-'
  827. config = {'check_last': check_last, 'prefix': prefix}
  828. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  829. '{"repository": {"id": "repo"}}'
  830. )
  831. flexmock(module).should_receive('upgrade_check_times')
  832. flexmock(module).should_receive('parse_checks')
  833. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  834. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  835. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  836. flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
  837. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  838. insert_execute_command_mock(('borg', 'check', 'repo'))
  839. flexmock(module).should_receive('make_check_time_path')
  840. flexmock(module).should_receive('write_check_time')
  841. module.check_archives(
  842. repository_path='repo',
  843. config=config,
  844. local_borg_version='1.2.3',
  845. check_arguments=flexmock(
  846. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  847. ),
  848. global_arguments=flexmock(log_json=False),
  849. )
  850. def test_check_archives_with_extra_borg_options_passes_through_to_borg():
  851. checks = ('repository',)
  852. config = {'check_last': None, 'extra_borg_options': {'check': '--extra --options'}}
  853. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  854. '{"repository": {"id": "repo"}}'
  855. )
  856. flexmock(module).should_receive('upgrade_check_times')
  857. flexmock(module).should_receive('parse_checks')
  858. flexmock(module).should_receive('make_archive_filter_flags').and_return(())
  859. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  860. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  861. flexmock(module).should_receive('make_check_flags').and_return(())
  862. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  863. insert_execute_command_mock(('borg', 'check', '--extra', '--options', 'repo'))
  864. flexmock(module).should_receive('make_check_time_path')
  865. flexmock(module).should_receive('write_check_time')
  866. module.check_archives(
  867. repository_path='repo',
  868. config=config,
  869. local_borg_version='1.2.3',
  870. check_arguments=flexmock(
  871. progress=None, repair=None, only_checks=None, force=None, match_archives=None
  872. ),
  873. global_arguments=flexmock(log_json=False),
  874. )
  875. def test_check_archives_with_match_archives_passes_through_to_borg():
  876. checks = ('archives',)
  877. config = {'check_last': None}
  878. flexmock(module.rinfo).should_receive('display_repository_info').and_return(
  879. '{"repository": {"id": "repo"}}'
  880. )
  881. flexmock(module).should_receive('upgrade_check_times')
  882. flexmock(module).should_receive('parse_checks')
  883. flexmock(module).should_receive('make_archive_filter_flags').and_return(
  884. ('--match-archives', 'foo-*')
  885. )
  886. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  887. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  888. flexmock(module).should_receive('make_check_flags').and_return(('--match-archives', 'foo-*'))
  889. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  890. flexmock(module.environment).should_receive('make_environment')
  891. flexmock(module).should_receive('execute_command').with_args(
  892. ('borg', 'check', '--match-archives', 'foo-*', 'repo'),
  893. extra_environment=None,
  894. ).once()
  895. flexmock(module).should_receive('make_check_time_path')
  896. flexmock(module).should_receive('write_check_time')
  897. module.check_archives(
  898. repository_path='repo',
  899. config=config,
  900. local_borg_version='1.2.3',
  901. check_arguments=flexmock(
  902. progress=None, repair=None, only_checks=None, force=None, match_archives='foo-*'
  903. ),
  904. global_arguments=flexmock(log_json=False),
  905. )