test_check.py 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512
  1. import pytest
  2. from flexmock import flexmock
  3. from borgmatic.actions import check as module
  4. def test_parse_checks_returns_them_as_tuple():
  5. checks = module.parse_checks({'checks': [{'name': 'foo'}, {'name': 'bar'}]})
  6. assert checks == ('foo', 'bar')
  7. def test_parse_checks_with_missing_value_returns_defaults():
  8. checks = module.parse_checks({})
  9. assert checks == ('repository', 'archives')
  10. def test_parse_checks_with_empty_list_returns_defaults():
  11. checks = module.parse_checks({'checks': []})
  12. assert checks == ('repository', 'archives')
  13. def test_parse_checks_with_none_value_returns_defaults():
  14. checks = module.parse_checks({'checks': None})
  15. assert checks == ('repository', 'archives')
  16. def test_parse_checks_with_disabled_returns_no_checks():
  17. checks = module.parse_checks({'checks': [{'name': 'foo'}, {'name': 'disabled'}]})
  18. assert checks == ()
  19. def test_parse_checks_prefers_override_checks_to_configured_checks():
  20. checks = module.parse_checks(
  21. {'checks': [{'name': 'archives'}]}, only_checks=['repository', 'extract']
  22. )
  23. assert checks == ('repository', 'extract')
  24. @pytest.mark.parametrize(
  25. 'frequency,expected_result',
  26. (
  27. (None, None),
  28. ('always', None),
  29. ('1 hour', module.datetime.timedelta(hours=1)),
  30. ('2 hours', module.datetime.timedelta(hours=2)),
  31. ('1 day', module.datetime.timedelta(days=1)),
  32. ('2 days', module.datetime.timedelta(days=2)),
  33. ('1 week', module.datetime.timedelta(weeks=1)),
  34. ('2 weeks', module.datetime.timedelta(weeks=2)),
  35. ('1 month', module.datetime.timedelta(days=30)),
  36. ('2 months', module.datetime.timedelta(days=60)),
  37. ('1 year', module.datetime.timedelta(days=365)),
  38. ('2 years', module.datetime.timedelta(days=365 * 2)),
  39. ),
  40. )
  41. def test_parse_frequency_parses_into_timedeltas(frequency, expected_result):
  42. assert module.parse_frequency(frequency) == expected_result
  43. @pytest.mark.parametrize(
  44. 'frequency',
  45. (
  46. 'sometime',
  47. 'x days',
  48. '3 decades',
  49. ),
  50. )
  51. def test_parse_frequency_raises_on_parse_error(frequency):
  52. with pytest.raises(ValueError):
  53. module.parse_frequency(frequency)
  54. def test_filter_checks_on_frequency_without_config_uses_default_checks():
  55. flexmock(module).should_receive('parse_frequency').and_return(
  56. module.datetime.timedelta(weeks=4)
  57. )
  58. flexmock(module).should_receive('make_check_time_path')
  59. flexmock(module).should_receive('probe_for_check_time').and_return(None)
  60. assert module.filter_checks_on_frequency(
  61. config={},
  62. borg_repository_id='repo',
  63. checks=('repository', 'archives'),
  64. force=False,
  65. archives_check_id='1234',
  66. ) == ('repository', 'archives')
  67. def test_filter_checks_on_frequency_retains_unconfigured_check():
  68. assert module.filter_checks_on_frequency(
  69. config={},
  70. borg_repository_id='repo',
  71. checks=('data',),
  72. force=False,
  73. ) == ('data',)
  74. def test_filter_checks_on_frequency_retains_check_without_frequency():
  75. flexmock(module).should_receive('parse_frequency').and_return(None)
  76. assert module.filter_checks_on_frequency(
  77. config={'checks': [{'name': 'archives'}]},
  78. borg_repository_id='repo',
  79. checks=('archives',),
  80. force=False,
  81. archives_check_id='1234',
  82. ) == ('archives',)
  83. def test_filter_checks_on_frequency_retains_check_with_empty_only_run_on():
  84. flexmock(module).should_receive('parse_frequency').and_return(None)
  85. assert module.filter_checks_on_frequency(
  86. config={'checks': [{'name': 'archives', 'only_run_on': []}]},
  87. borg_repository_id='repo',
  88. checks=('archives',),
  89. force=False,
  90. archives_check_id='1234',
  91. datetime_now=flexmock(weekday=lambda: 0),
  92. ) == ('archives',)
  93. def test_filter_checks_on_frequency_retains_check_with_only_run_on_matching_today():
  94. flexmock(module).should_receive('parse_frequency').and_return(None)
  95. assert module.filter_checks_on_frequency(
  96. config={'checks': [{'name': 'archives', 'only_run_on': [module.calendar.day_name[0]]}]},
  97. borg_repository_id='repo',
  98. checks=('archives',),
  99. force=False,
  100. archives_check_id='1234',
  101. datetime_now=flexmock(weekday=lambda: 0),
  102. ) == ('archives',)
  103. def test_filter_checks_on_frequency_retains_check_with_only_run_on_matching_today_via_weekday_value():
  104. flexmock(module).should_receive('parse_frequency').and_return(None)
  105. assert module.filter_checks_on_frequency(
  106. config={'checks': [{'name': 'archives', 'only_run_on': ['weekday']}]},
  107. borg_repository_id='repo',
  108. checks=('archives',),
  109. force=False,
  110. archives_check_id='1234',
  111. datetime_now=flexmock(weekday=lambda: 0),
  112. ) == ('archives',)
  113. def test_filter_checks_on_frequency_retains_check_with_only_run_on_matching_today_via_weekend_value():
  114. flexmock(module).should_receive('parse_frequency').and_return(None)
  115. assert module.filter_checks_on_frequency(
  116. config={'checks': [{'name': 'archives', 'only_run_on': ['weekend']}]},
  117. borg_repository_id='repo',
  118. checks=('archives',),
  119. force=False,
  120. archives_check_id='1234',
  121. datetime_now=flexmock(weekday=lambda: 6),
  122. ) == ('archives',)
  123. def test_filter_checks_on_frequency_skips_check_with_only_run_on_not_matching_today():
  124. flexmock(module).should_receive('parse_frequency').and_return(None)
  125. assert (
  126. module.filter_checks_on_frequency(
  127. config={'checks': [{'name': 'archives', 'only_run_on': [module.calendar.day_name[5]]}]},
  128. borg_repository_id='repo',
  129. checks=('archives',),
  130. force=False,
  131. archives_check_id='1234',
  132. datetime_now=flexmock(weekday=lambda: 0),
  133. )
  134. == ()
  135. )
  136. def test_filter_checks_on_frequency_retains_check_with_elapsed_frequency():
  137. flexmock(module).should_receive('parse_frequency').and_return(
  138. module.datetime.timedelta(hours=1)
  139. )
  140. flexmock(module).should_receive('make_check_time_path')
  141. flexmock(module).should_receive('probe_for_check_time').and_return(
  142. module.datetime.datetime(year=module.datetime.MINYEAR, month=1, day=1)
  143. )
  144. assert module.filter_checks_on_frequency(
  145. config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
  146. borg_repository_id='repo',
  147. checks=('archives',),
  148. force=False,
  149. archives_check_id='1234',
  150. ) == ('archives',)
  151. def test_filter_checks_on_frequency_retains_check_with_missing_check_time_file():
  152. flexmock(module).should_receive('parse_frequency').and_return(
  153. module.datetime.timedelta(hours=1)
  154. )
  155. flexmock(module).should_receive('make_check_time_path')
  156. flexmock(module).should_receive('probe_for_check_time').and_return(None)
  157. assert module.filter_checks_on_frequency(
  158. config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
  159. borg_repository_id='repo',
  160. checks=('archives',),
  161. force=False,
  162. archives_check_id='1234',
  163. ) == ('archives',)
  164. def test_filter_checks_on_frequency_skips_check_with_unelapsed_frequency():
  165. flexmock(module).should_receive('parse_frequency').and_return(
  166. module.datetime.timedelta(hours=1)
  167. )
  168. flexmock(module).should_receive('make_check_time_path')
  169. flexmock(module).should_receive('probe_for_check_time').and_return(
  170. module.datetime.datetime.now()
  171. )
  172. assert (
  173. module.filter_checks_on_frequency(
  174. config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
  175. borg_repository_id='repo',
  176. checks=('archives',),
  177. force=False,
  178. archives_check_id='1234',
  179. )
  180. == ()
  181. )
  182. def test_filter_checks_on_frequency_retains_check_with_unelapsed_frequency_and_force():
  183. assert module.filter_checks_on_frequency(
  184. config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
  185. borg_repository_id='repo',
  186. checks=('archives',),
  187. force=True,
  188. archives_check_id='1234',
  189. ) == ('archives',)
  190. def test_filter_checks_on_frequency_passes_through_empty_checks():
  191. assert (
  192. module.filter_checks_on_frequency(
  193. config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
  194. borg_repository_id='repo',
  195. checks=(),
  196. force=False,
  197. archives_check_id='1234',
  198. )
  199. == ()
  200. )
  201. def test_make_archives_check_id_with_flags_returns_a_value_and_does_not_raise():
  202. assert module.make_archives_check_id(('--match-archives', 'sh:foo-*'))
  203. def test_make_archives_check_id_with_empty_flags_returns_none():
  204. assert module.make_archives_check_id(()) is None
  205. def test_make_check_time_path_with_borgmatic_source_directory_includes_it():
  206. flexmock(module.borgmatic.config.paths).should_receive(
  207. 'get_borgmatic_state_directory'
  208. ).and_return(
  209. '/home/user/.local/state/borgmatic',
  210. )
  211. assert (
  212. module.make_check_time_path({}, '1234', 'archives', '5678')
  213. == '/home/user/.local/state/borgmatic/checks/1234/archives/5678'
  214. )
  215. def test_make_check_time_path_with_archives_check_and_no_archives_check_id_defaults_to_all():
  216. flexmock(module.borgmatic.config.paths).should_receive(
  217. 'get_borgmatic_state_directory'
  218. ).and_return(
  219. '/home/user/.local/state/borgmatic',
  220. )
  221. assert (
  222. module.make_check_time_path(
  223. {},
  224. '1234',
  225. 'archives',
  226. )
  227. == '/home/user/.local/state/borgmatic/checks/1234/archives/all'
  228. )
  229. def test_make_check_time_path_with_repositories_check_ignores_archives_check_id():
  230. flexmock(module.borgmatic.config.paths).should_receive(
  231. 'get_borgmatic_state_directory'
  232. ).and_return(
  233. '/home/user/.local/state/borgmatic',
  234. )
  235. assert (
  236. module.make_check_time_path({}, '1234', 'repository', '5678')
  237. == '/home/user/.local/state/borgmatic/checks/1234/repository'
  238. )
  239. def test_read_check_time_does_not_raise():
  240. flexmock(module.os).should_receive('stat').and_return(flexmock(st_mtime=123))
  241. assert module.read_check_time('/path')
  242. def test_read_check_time_on_missing_file_does_not_raise():
  243. flexmock(module.os).should_receive('stat').and_raise(FileNotFoundError)
  244. assert module.read_check_time('/path') is None
  245. def test_probe_for_check_time_uses_maximum_of_multiple_check_times():
  246. flexmock(module).should_receive('make_check_time_path').and_return(
  247. '~/.borgmatic/checks/1234/archives/5678'
  248. ).and_return('~/.borgmatic/checks/1234/archives/all')
  249. flexmock(module).should_receive('read_check_time').and_return(1).and_return(2)
  250. assert module.probe_for_check_time(flexmock(), flexmock(), flexmock(), flexmock()) == 2
  251. def test_probe_for_check_time_deduplicates_identical_check_time_paths():
  252. flexmock(module).should_receive('make_check_time_path').and_return(
  253. '~/.borgmatic/checks/1234/archives/5678'
  254. ).and_return('~/.borgmatic/checks/1234/archives/5678')
  255. flexmock(module).should_receive('read_check_time').and_return(1).once()
  256. assert module.probe_for_check_time(flexmock(), flexmock(), flexmock(), flexmock()) == 1
  257. def test_probe_for_check_time_skips_none_check_time():
  258. flexmock(module).should_receive('make_check_time_path').and_return(
  259. '~/.borgmatic/checks/1234/archives/5678'
  260. ).and_return('~/.borgmatic/checks/1234/archives/all')
  261. flexmock(module).should_receive('read_check_time').and_return(None).and_return(2)
  262. assert module.probe_for_check_time(flexmock(), flexmock(), flexmock(), flexmock()) == 2
  263. def test_probe_for_check_time_uses_single_check_time():
  264. flexmock(module).should_receive('make_check_time_path').and_return(
  265. '~/.borgmatic/checks/1234/archives/5678'
  266. ).and_return('~/.borgmatic/checks/1234/archives/all')
  267. flexmock(module).should_receive('read_check_time').and_return(1).and_return(None)
  268. assert module.probe_for_check_time(flexmock(), flexmock(), flexmock(), flexmock()) == 1
  269. def test_probe_for_check_time_returns_none_when_no_check_time_found():
  270. flexmock(module).should_receive('make_check_time_path').and_return(
  271. '~/.borgmatic/checks/1234/archives/5678'
  272. ).and_return('~/.borgmatic/checks/1234/archives/all')
  273. flexmock(module).should_receive('read_check_time').and_return(None).and_return(None)
  274. assert module.probe_for_check_time(flexmock(), flexmock(), flexmock(), flexmock()) is None
  275. def test_upgrade_check_times_moves_checks_from_borgmatic_source_directory_to_state_directory():
  276. flexmock(module.borgmatic.config.paths).should_receive(
  277. 'get_borgmatic_source_directory'
  278. ).and_return('/home/user/.borgmatic')
  279. flexmock(module.borgmatic.config.paths).should_receive(
  280. 'get_borgmatic_state_directory'
  281. ).and_return('/home/user/.local/state/borgmatic')
  282. flexmock(module.os.path).should_receive('exists').with_args(
  283. '/home/user/.borgmatic/checks'
  284. ).and_return(True)
  285. flexmock(module.os.path).should_receive('exists').with_args(
  286. '/home/user/.local/state/borgmatic/checks'
  287. ).and_return(False)
  288. flexmock(module.os).should_receive('makedirs')
  289. flexmock(module.shutil).should_receive('move').with_args(
  290. '/home/user/.borgmatic/checks', '/home/user/.local/state/borgmatic/checks'
  291. ).once()
  292. flexmock(module).should_receive('make_check_time_path').and_return(
  293. '/home/user/.local/state/borgmatic/checks/1234/archives/all'
  294. )
  295. flexmock(module.os.path).should_receive('isfile').and_return(False)
  296. flexmock(module.os).should_receive('mkdir').never()
  297. module.upgrade_check_times(flexmock(), flexmock())
  298. def test_upgrade_check_times_with_checks_already_in_borgmatic_state_directory_does_not_move_anything():
  299. flexmock(module.borgmatic.config.paths).should_receive(
  300. 'get_borgmatic_source_directory'
  301. ).and_return('/home/user/.borgmatic')
  302. flexmock(module.borgmatic.config.paths).should_receive(
  303. 'get_borgmatic_state_directory'
  304. ).and_return('/home/user/.local/state/borgmatic')
  305. flexmock(module.os.path).should_receive('exists').with_args(
  306. '/home/user/.borgmatic/checks'
  307. ).and_return(True)
  308. flexmock(module.os.path).should_receive('exists').with_args(
  309. '/home/user/.local/state/borgmatic/checks'
  310. ).and_return(True)
  311. flexmock(module.os).should_receive('makedirs').never()
  312. flexmock(module.shutil).should_receive('move').never()
  313. flexmock(module).should_receive('make_check_time_path').and_return(
  314. '/home/user/.local/state/borgmatic/checks/1234/archives/all'
  315. )
  316. flexmock(module.os.path).should_receive('isfile').and_return(False)
  317. flexmock(module.shutil).should_receive('move').never()
  318. flexmock(module.os).should_receive('mkdir').never()
  319. module.upgrade_check_times(flexmock(), flexmock())
  320. def test_upgrade_check_times_renames_old_check_paths_to_all():
  321. flexmock(module.borgmatic.config.paths).should_receive(
  322. 'get_borgmatic_source_directory'
  323. ).and_return('/home/user/.borgmatic')
  324. flexmock(module.borgmatic.config.paths).should_receive(
  325. 'get_borgmatic_state_directory'
  326. ).and_return('/home/user/.local/state/borgmatic')
  327. flexmock(module.os.path).should_receive('exists').and_return(False)
  328. base_path = '/home/user/.local/state/borgmatic/checks/1234'
  329. flexmock(module).should_receive('make_check_time_path').with_args(
  330. object, object, 'archives', 'all'
  331. ).and_return(f'{base_path}/archives/all')
  332. flexmock(module).should_receive('make_check_time_path').with_args(
  333. object, object, 'data', 'all'
  334. ).and_return(f'{base_path}/data/all')
  335. flexmock(module.os.path).should_receive('isfile').with_args(f'{base_path}/archives').and_return(
  336. True
  337. )
  338. flexmock(module.os.path).should_receive('isfile').with_args(
  339. f'{base_path}/archives.temp'
  340. ).and_return(False)
  341. flexmock(module.os.path).should_receive('isfile').with_args(f'{base_path}/data').and_return(
  342. False
  343. )
  344. flexmock(module.os.path).should_receive('isfile').with_args(
  345. f'{base_path}/data.temp'
  346. ).and_return(False)
  347. flexmock(module.shutil).should_receive('move').with_args(
  348. f'{base_path}/archives', f'{base_path}/archives.temp'
  349. ).once()
  350. flexmock(module.os).should_receive('mkdir').with_args(f'{base_path}/archives').once()
  351. flexmock(module.shutil).should_receive('move').with_args(
  352. f'{base_path}/archives.temp', f'{base_path}/archives/all'
  353. ).once()
  354. module.upgrade_check_times(flexmock(), flexmock())
  355. def test_upgrade_check_times_renames_data_check_paths_when_archives_paths_are_already_upgraded():
  356. flexmock(module.borgmatic.config.paths).should_receive(
  357. 'get_borgmatic_source_directory'
  358. ).and_return('/home/user/.borgmatic')
  359. flexmock(module.borgmatic.config.paths).should_receive(
  360. 'get_borgmatic_state_directory'
  361. ).and_return('/home/user/.local/state/borgmatic')
  362. flexmock(module.os.path).should_receive('exists').and_return(False)
  363. base_path = '/home/user/.local/state/borgmatic/checks/1234'
  364. flexmock(module).should_receive('make_check_time_path').with_args(
  365. object, object, 'archives', 'all'
  366. ).and_return(f'{base_path}/archives/all')
  367. flexmock(module).should_receive('make_check_time_path').with_args(
  368. object, object, 'data', 'all'
  369. ).and_return(f'{base_path}/data/all')
  370. flexmock(module.os.path).should_receive('isfile').with_args(f'{base_path}/archives').and_return(
  371. False
  372. )
  373. flexmock(module.os.path).should_receive('isfile').with_args(
  374. f'{base_path}/archives.temp'
  375. ).and_return(False)
  376. flexmock(module.os.path).should_receive('isfile').with_args(f'{base_path}/data').and_return(
  377. True
  378. )
  379. flexmock(module.shutil).should_receive('move').with_args(
  380. f'{base_path}/data', f'{base_path}/data.temp'
  381. ).once()
  382. flexmock(module.os).should_receive('mkdir').with_args(f'{base_path}/data').once()
  383. flexmock(module.shutil).should_receive('move').with_args(
  384. f'{base_path}/data.temp', f'{base_path}/data/all'
  385. ).once()
  386. module.upgrade_check_times(flexmock(), flexmock())
  387. def test_upgrade_check_times_skips_already_upgraded_check_paths():
  388. flexmock(module.borgmatic.config.paths).should_receive(
  389. 'get_borgmatic_source_directory'
  390. ).and_return('/home/user/.borgmatic')
  391. flexmock(module.borgmatic.config.paths).should_receive(
  392. 'get_borgmatic_state_directory'
  393. ).and_return('/home/user/.local/state/borgmatic')
  394. flexmock(module.os.path).should_receive('exists').and_return(False)
  395. flexmock(module).should_receive('make_check_time_path').and_return(
  396. '/home/user/.local/state/borgmatic/checks/1234/archives/all'
  397. )
  398. flexmock(module.os.path).should_receive('isfile').and_return(False)
  399. flexmock(module.shutil).should_receive('move').never()
  400. flexmock(module.os).should_receive('mkdir').never()
  401. module.upgrade_check_times(flexmock(), flexmock())
  402. def test_upgrade_check_times_renames_stale_temporary_check_path():
  403. flexmock(module.borgmatic.config.paths).should_receive(
  404. 'get_borgmatic_source_directory'
  405. ).and_return('/home/user/.borgmatic')
  406. flexmock(module.borgmatic.config.paths).should_receive(
  407. 'get_borgmatic_state_directory'
  408. ).and_return('/home/user/.local/state/borgmatic')
  409. flexmock(module.os.path).should_receive('exists').and_return(False)
  410. base_path = '/home/borgmatic/.local/state/checks/1234'
  411. flexmock(module).should_receive('make_check_time_path').with_args(
  412. object, object, 'archives', 'all'
  413. ).and_return(f'{base_path}/archives/all')
  414. flexmock(module).should_receive('make_check_time_path').with_args(
  415. object, object, 'data', 'all'
  416. ).and_return(f'{base_path}/data/all')
  417. flexmock(module.os.path).should_receive('isfile').with_args(f'{base_path}/archives').and_return(
  418. False
  419. )
  420. flexmock(module.os.path).should_receive('isfile').with_args(
  421. f'{base_path}/archives.temp'
  422. ).and_return(True)
  423. flexmock(module.os.path).should_receive('isfile').with_args(f'{base_path}/data').and_return(
  424. False
  425. )
  426. flexmock(module.os.path).should_receive('isfile').with_args(
  427. f'{base_path}/data.temp'
  428. ).and_return(False)
  429. flexmock(module.shutil).should_receive('move').with_args(
  430. f'{base_path}/archives', f'{base_path}/archives.temp'
  431. ).and_raise(FileNotFoundError)
  432. flexmock(module.os).should_receive('mkdir').with_args(f'{base_path}/archives').once()
  433. flexmock(module.shutil).should_receive('move').with_args(
  434. f'{base_path}/archives.temp', f'{base_path}/archives/all'
  435. ).once()
  436. module.upgrade_check_times(flexmock(), flexmock())
  437. def test_collect_spot_check_source_paths_parses_borg_output():
  438. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
  439. {'hook1': False, 'hook2': True}
  440. )
  441. flexmock(module.borgmatic.actions.create).should_receive(
  442. 'process_source_directories'
  443. ).and_return(['foo', 'bar'])
  444. flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
  445. dry_run=True,
  446. repository_path='repo',
  447. config=object,
  448. source_directories=['foo', 'bar'],
  449. local_borg_version=object,
  450. global_arguments=object,
  451. borgmatic_runtime_directory='/run/borgmatic',
  452. local_path=object,
  453. remote_path=object,
  454. list_files=True,
  455. stream_processes=True,
  456. ).and_return((('borg', 'create'), ('repo::archive',), flexmock(), flexmock()))
  457. flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
  458. flexmock()
  459. )
  460. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  461. flexmock(module.borgmatic.execute).should_receive(
  462. 'execute_command_and_capture_output'
  463. ).and_return(
  464. 'warning: stuff\n- /etc/path\n+ /etc/other\n? /nope',
  465. )
  466. flexmock(module.os.path).should_receive('isfile').and_return(True)
  467. assert module.collect_spot_check_source_paths(
  468. repository={'path': 'repo'},
  469. config={'working_directory': '/'},
  470. local_borg_version=flexmock(),
  471. global_arguments=flexmock(),
  472. local_path=flexmock(),
  473. remote_path=flexmock(),
  474. borgmatic_runtime_directory='/run/borgmatic',
  475. ) == ('/etc/path', '/etc/other')
  476. def test_collect_spot_check_source_paths_passes_through_stream_processes_false():
  477. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
  478. {'hook1': False, 'hook2': False}
  479. )
  480. flexmock(module.borgmatic.actions.create).should_receive(
  481. 'process_source_directories'
  482. ).and_return(['foo', 'bar'])
  483. flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
  484. dry_run=True,
  485. repository_path='repo',
  486. config=object,
  487. source_directories=['foo', 'bar'],
  488. local_borg_version=object,
  489. global_arguments=object,
  490. borgmatic_runtime_directory='/run/borgmatic',
  491. local_path=object,
  492. remote_path=object,
  493. list_files=True,
  494. stream_processes=False,
  495. ).and_return((('borg', 'create'), ('repo::archive',), flexmock(), flexmock()))
  496. flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
  497. flexmock()
  498. )
  499. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  500. flexmock(module.borgmatic.execute).should_receive(
  501. 'execute_command_and_capture_output'
  502. ).and_return(
  503. 'warning: stuff\n- /etc/path\n+ /etc/other\n? /nope',
  504. )
  505. flexmock(module.os.path).should_receive('isfile').and_return(True)
  506. assert module.collect_spot_check_source_paths(
  507. repository={'path': 'repo'},
  508. config={'working_directory': '/'},
  509. local_borg_version=flexmock(),
  510. global_arguments=flexmock(),
  511. local_path=flexmock(),
  512. remote_path=flexmock(),
  513. borgmatic_runtime_directory='/run/borgmatic',
  514. ) == ('/etc/path', '/etc/other')
  515. def test_collect_spot_check_source_paths_without_working_directory_parses_borg_output():
  516. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
  517. {'hook1': False, 'hook2': True}
  518. )
  519. flexmock(module.borgmatic.actions.create).should_receive(
  520. 'process_source_directories'
  521. ).and_return(['foo', 'bar'])
  522. flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
  523. dry_run=True,
  524. repository_path='repo',
  525. config=object,
  526. source_directories=['foo', 'bar'],
  527. local_borg_version=object,
  528. global_arguments=object,
  529. borgmatic_runtime_directory='/run/borgmatic',
  530. local_path=object,
  531. remote_path=object,
  532. list_files=True,
  533. stream_processes=True,
  534. ).and_return((('borg', 'create'), ('repo::archive',), flexmock(), flexmock()))
  535. flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
  536. flexmock()
  537. )
  538. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  539. flexmock(module.borgmatic.execute).should_receive(
  540. 'execute_command_and_capture_output'
  541. ).and_return(
  542. 'warning: stuff\n- /etc/path\n+ /etc/other\n? /nope',
  543. )
  544. flexmock(module.os.path).should_receive('isfile').and_return(True)
  545. assert module.collect_spot_check_source_paths(
  546. repository={'path': 'repo'},
  547. config={},
  548. local_borg_version=flexmock(),
  549. global_arguments=flexmock(),
  550. local_path=flexmock(),
  551. remote_path=flexmock(),
  552. borgmatic_runtime_directory='/run/borgmatic',
  553. ) == ('/etc/path', '/etc/other')
  554. def test_collect_spot_check_source_paths_skips_directories():
  555. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
  556. {'hook1': False, 'hook2': True}
  557. )
  558. flexmock(module.borgmatic.actions.create).should_receive(
  559. 'process_source_directories'
  560. ).and_return(['foo', 'bar'])
  561. flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
  562. dry_run=True,
  563. repository_path='repo',
  564. config=object,
  565. source_directories=['foo', 'bar'],
  566. local_borg_version=object,
  567. global_arguments=object,
  568. borgmatic_runtime_directory='/run/borgmatic',
  569. local_path=object,
  570. remote_path=object,
  571. list_files=True,
  572. stream_processes=True,
  573. ).and_return((('borg', 'create'), ('repo::archive',), flexmock(), flexmock()))
  574. flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
  575. flexmock()
  576. )
  577. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  578. flexmock(module.borgmatic.execute).should_receive(
  579. 'execute_command_and_capture_output'
  580. ).and_return(
  581. 'warning: stuff\n- /etc/path\n+ /etc/dir\n? /nope',
  582. )
  583. flexmock(module.os.path).should_receive('isfile').with_args('/etc/path').and_return(False)
  584. flexmock(module.os.path).should_receive('isfile').with_args('/etc/dir').and_return(False)
  585. assert (
  586. module.collect_spot_check_source_paths(
  587. repository={'path': 'repo'},
  588. config={'working_directory': '/'},
  589. local_borg_version=flexmock(),
  590. global_arguments=flexmock(),
  591. local_path=flexmock(),
  592. remote_path=flexmock(),
  593. borgmatic_runtime_directory='/run/borgmatic',
  594. )
  595. == ()
  596. )
  597. def test_collect_spot_check_archive_paths_excludes_directories():
  598. flexmock(module.borgmatic.config.paths).should_receive(
  599. 'get_borgmatic_source_directory'
  600. ).and_return('/home/user/.borgmatic')
  601. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  602. (
  603. 'f /etc/path',
  604. 'f /etc/other',
  605. 'd /etc/dir',
  606. )
  607. )
  608. assert module.collect_spot_check_archive_paths(
  609. repository={'path': 'repo'},
  610. archive='archive',
  611. config={},
  612. local_borg_version=flexmock(),
  613. global_arguments=flexmock(),
  614. local_path=flexmock(),
  615. remote_path=flexmock(),
  616. borgmatic_runtime_directory='/run/user/1001/borgmatic',
  617. ) == ('/etc/path', '/etc/other')
  618. def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_runtime_directory_as_stored_with_prefix_truncation():
  619. flexmock(module.borgmatic.config.paths).should_receive(
  620. 'get_borgmatic_source_directory'
  621. ).and_return('/root/.borgmatic')
  622. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  623. (
  624. 'f /etc/path',
  625. 'f /borgmatic/some/thing',
  626. )
  627. )
  628. assert module.collect_spot_check_archive_paths(
  629. repository={'path': 'repo'},
  630. archive='archive',
  631. config={},
  632. local_borg_version=flexmock(),
  633. global_arguments=flexmock(),
  634. local_path=flexmock(),
  635. remote_path=flexmock(),
  636. borgmatic_runtime_directory='/run/user/0/borgmatic',
  637. ) == ('/etc/path',)
  638. def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_source_directory():
  639. flexmock(module.borgmatic.config.paths).should_receive(
  640. 'get_borgmatic_source_directory'
  641. ).and_return('/root/.borgmatic')
  642. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  643. (
  644. 'f /etc/path',
  645. 'f /root/.borgmatic/some/thing',
  646. )
  647. )
  648. assert module.collect_spot_check_archive_paths(
  649. repository={'path': 'repo'},
  650. archive='archive',
  651. config={},
  652. local_borg_version=flexmock(),
  653. global_arguments=flexmock(),
  654. local_path=flexmock(),
  655. remote_path=flexmock(),
  656. borgmatic_runtime_directory='/run/user/0/borgmatic',
  657. ) == ('/etc/path',)
  658. def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_runtime_directory():
  659. flexmock(module.borgmatic.config.paths).should_receive(
  660. 'get_borgmatic_source_directory'
  661. ).and_return('/root.borgmatic')
  662. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  663. (
  664. 'f /etc/path',
  665. 'f /run/user/0/borgmatic/some/thing',
  666. )
  667. )
  668. assert module.collect_spot_check_archive_paths(
  669. repository={'path': 'repo'},
  670. archive='archive',
  671. config={},
  672. local_borg_version=flexmock(),
  673. global_arguments=flexmock(),
  674. local_path=flexmock(),
  675. remote_path=flexmock(),
  676. borgmatic_runtime_directory='/run/user/0/borgmatic',
  677. ) == ('/etc/path',)
  678. def test_collect_spot_check_source_paths_uses_working_directory():
  679. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
  680. {'hook1': False, 'hook2': True}
  681. )
  682. flexmock(module.borgmatic.actions.create).should_receive(
  683. 'process_source_directories'
  684. ).and_return(['foo', 'bar'])
  685. flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
  686. dry_run=True,
  687. repository_path='repo',
  688. config=object,
  689. source_directories=['foo', 'bar'],
  690. local_borg_version=object,
  691. global_arguments=object,
  692. borgmatic_runtime_directory='/run/borgmatic',
  693. local_path=object,
  694. remote_path=object,
  695. list_files=True,
  696. stream_processes=True,
  697. ).and_return((('borg', 'create'), ('repo::archive',), flexmock(), flexmock()))
  698. flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
  699. flexmock()
  700. )
  701. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  702. '/working/dir'
  703. )
  704. flexmock(module.borgmatic.execute).should_receive(
  705. 'execute_command_and_capture_output'
  706. ).and_return(
  707. 'warning: stuff\n- foo\n+ bar\n? /nope',
  708. )
  709. flexmock(module.os.path).should_receive('isfile').with_args('/working/dir/foo').and_return(True)
  710. flexmock(module.os.path).should_receive('isfile').with_args('/working/dir/bar').and_return(True)
  711. assert module.collect_spot_check_source_paths(
  712. repository={'path': 'repo'},
  713. config={'working_directory': '/working/dir'},
  714. local_borg_version=flexmock(),
  715. global_arguments=flexmock(),
  716. local_path=flexmock(),
  717. remote_path=flexmock(),
  718. borgmatic_runtime_directory='/run/borgmatic',
  719. ) == ('foo', 'bar')
  720. def test_compare_spot_check_hashes_returns_paths_having_failing_hashes():
  721. flexmock(module.random).should_receive('sample').replace_with(
  722. lambda population, count: population[:count]
  723. )
  724. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  725. None,
  726. )
  727. flexmock(module.os.path).should_receive('exists').and_return(True)
  728. flexmock(module.borgmatic.execute).should_receive(
  729. 'execute_command_and_capture_output'
  730. ).with_args(('xxh64sum', '/foo', '/bar'), working_directory=None).and_return(
  731. 'hash1 /foo\nhash2 /bar'
  732. )
  733. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  734. ['hash1 /foo', 'nothash2 /bar']
  735. )
  736. assert module.compare_spot_check_hashes(
  737. repository={'path': 'repo'},
  738. archive='archive',
  739. config={
  740. 'checks': [
  741. {
  742. 'name': 'archives',
  743. 'frequency': '2 weeks',
  744. },
  745. {
  746. 'name': 'spot',
  747. 'data_sample_percentage': 50,
  748. },
  749. ]
  750. },
  751. local_borg_version=flexmock(),
  752. global_arguments=flexmock(),
  753. local_path=flexmock(),
  754. remote_path=flexmock(),
  755. log_prefix='repo',
  756. source_paths=('/foo', '/bar', '/baz', '/quux'),
  757. ) == ('/bar',)
  758. def test_compare_spot_check_hashes_handles_data_sample_percentage_above_100():
  759. flexmock(module.random).should_receive('sample').replace_with(
  760. lambda population, count: population[:count]
  761. )
  762. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  763. None,
  764. )
  765. flexmock(module.os.path).should_receive('exists').and_return(True)
  766. flexmock(module.borgmatic.execute).should_receive(
  767. 'execute_command_and_capture_output'
  768. ).with_args(('xxh64sum', '/foo', '/bar'), working_directory=None).and_return(
  769. 'hash1 /foo\nhash2 /bar'
  770. )
  771. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  772. ['nothash1 /foo', 'nothash2 /bar']
  773. )
  774. assert module.compare_spot_check_hashes(
  775. repository={'path': 'repo'},
  776. archive='archive',
  777. config={
  778. 'checks': [
  779. {
  780. 'name': 'archives',
  781. 'frequency': '2 weeks',
  782. },
  783. {
  784. 'name': 'spot',
  785. 'data_sample_percentage': 1000,
  786. },
  787. ]
  788. },
  789. local_borg_version=flexmock(),
  790. global_arguments=flexmock(),
  791. local_path=flexmock(),
  792. remote_path=flexmock(),
  793. log_prefix='repo',
  794. source_paths=('/foo', '/bar'),
  795. ) == ('/foo', '/bar')
  796. def test_compare_spot_check_hashes_uses_xxh64sum_command_option():
  797. flexmock(module.random).should_receive('sample').replace_with(
  798. lambda population, count: population[:count]
  799. )
  800. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  801. None,
  802. )
  803. flexmock(module.os.path).should_receive('exists').and_return(True)
  804. flexmock(module.borgmatic.execute).should_receive(
  805. 'execute_command_and_capture_output'
  806. ).with_args(('/usr/local/bin/xxh64sum', '/foo', '/bar'), working_directory=None).and_return(
  807. 'hash1 /foo\nhash2 /bar'
  808. )
  809. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  810. ['hash1 /foo', 'nothash2 /bar']
  811. )
  812. assert module.compare_spot_check_hashes(
  813. repository={'path': 'repo'},
  814. archive='archive',
  815. config={
  816. 'checks': [
  817. {
  818. 'name': 'spot',
  819. 'data_sample_percentage': 50,
  820. 'xxh64sum_command': '/usr/local/bin/xxh64sum',
  821. },
  822. ]
  823. },
  824. local_borg_version=flexmock(),
  825. global_arguments=flexmock(),
  826. local_path=flexmock(),
  827. remote_path=flexmock(),
  828. log_prefix='repo',
  829. source_paths=('/foo', '/bar', '/baz', '/quux'),
  830. ) == ('/bar',)
  831. def test_compare_spot_check_hashes_considers_path_missing_from_archive_as_not_matching():
  832. flexmock(module.random).should_receive('sample').replace_with(
  833. lambda population, count: population[:count]
  834. )
  835. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  836. None,
  837. )
  838. flexmock(module.os.path).should_receive('exists').and_return(True)
  839. flexmock(module.borgmatic.execute).should_receive(
  840. 'execute_command_and_capture_output'
  841. ).with_args(('xxh64sum', '/foo', '/bar'), working_directory=None).and_return(
  842. 'hash1 /foo\nhash2 /bar'
  843. )
  844. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  845. ['hash1 /foo']
  846. )
  847. assert module.compare_spot_check_hashes(
  848. repository={'path': 'repo'},
  849. archive='archive',
  850. config={
  851. 'checks': [
  852. {
  853. 'name': 'spot',
  854. 'data_sample_percentage': 50,
  855. },
  856. ]
  857. },
  858. local_borg_version=flexmock(),
  859. global_arguments=flexmock(),
  860. local_path=flexmock(),
  861. remote_path=flexmock(),
  862. log_prefix='repo',
  863. source_paths=('/foo', '/bar', '/baz', '/quux'),
  864. ) == ('/bar',)
  865. def test_compare_spot_check_hashes_considers_non_existent_path_as_not_matching():
  866. flexmock(module.random).should_receive('sample').replace_with(
  867. lambda population, count: population[:count]
  868. )
  869. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  870. None,
  871. )
  872. flexmock(module.os.path).should_receive('exists').with_args('/foo').and_return(True)
  873. flexmock(module.os.path).should_receive('exists').with_args('/bar').and_return(False)
  874. flexmock(module.borgmatic.execute).should_receive(
  875. 'execute_command_and_capture_output'
  876. ).with_args(('xxh64sum', '/foo'), working_directory=None).and_return('hash1 /foo')
  877. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  878. ['hash1 /foo', 'hash2 /bar']
  879. )
  880. assert module.compare_spot_check_hashes(
  881. repository={'path': 'repo'},
  882. archive='archive',
  883. config={
  884. 'checks': [
  885. {
  886. 'name': 'spot',
  887. 'data_sample_percentage': 50,
  888. },
  889. ]
  890. },
  891. local_borg_version=flexmock(),
  892. global_arguments=flexmock(),
  893. local_path=flexmock(),
  894. remote_path=flexmock(),
  895. log_prefix='repo',
  896. source_paths=('/foo', '/bar', '/baz', '/quux'),
  897. ) == ('/bar',)
  898. def test_compare_spot_check_hashes_with_too_many_paths_feeds_them_to_commands_in_chunks():
  899. flexmock(module).SAMPLE_PATHS_SUBSET_COUNT = 2
  900. flexmock(module.random).should_receive('sample').replace_with(
  901. lambda population, count: population[:count]
  902. )
  903. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  904. None,
  905. )
  906. flexmock(module.os.path).should_receive('exists').and_return(True)
  907. flexmock(module.borgmatic.execute).should_receive(
  908. 'execute_command_and_capture_output'
  909. ).with_args(('xxh64sum', '/foo', '/bar'), working_directory=None).and_return(
  910. 'hash1 /foo\nhash2 /bar'
  911. )
  912. flexmock(module.borgmatic.execute).should_receive(
  913. 'execute_command_and_capture_output'
  914. ).with_args(('xxh64sum', '/baz', '/quux'), working_directory=None).and_return(
  915. 'hash3 /baz\nhash4 /quux'
  916. )
  917. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  918. ['hash1 /foo', 'hash2 /bar']
  919. ).and_return(['hash3 /baz', 'nothash4 /quux'])
  920. assert module.compare_spot_check_hashes(
  921. repository={'path': 'repo'},
  922. archive='archive',
  923. config={
  924. 'checks': [
  925. {
  926. 'name': 'archives',
  927. 'frequency': '2 weeks',
  928. },
  929. {
  930. 'name': 'spot',
  931. 'data_sample_percentage': 100,
  932. },
  933. ]
  934. },
  935. local_borg_version=flexmock(),
  936. global_arguments=flexmock(),
  937. local_path=flexmock(),
  938. remote_path=flexmock(),
  939. log_prefix='repo',
  940. source_paths=('/foo', '/bar', '/baz', '/quux'),
  941. ) == ('/quux',)
  942. def test_compare_spot_check_hashes_uses_working_directory_to_access_source_paths():
  943. flexmock(module.random).should_receive('sample').replace_with(
  944. lambda population, count: population[:count]
  945. )
  946. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  947. '/working/dir',
  948. )
  949. flexmock(module.os.path).should_receive('exists').with_args('/working/dir/foo').and_return(True)
  950. flexmock(module.os.path).should_receive('exists').with_args('/working/dir/bar').and_return(True)
  951. flexmock(module.borgmatic.execute).should_receive(
  952. 'execute_command_and_capture_output'
  953. ).with_args(('xxh64sum', 'foo', 'bar'), working_directory='/working/dir').and_return(
  954. 'hash1 foo\nhash2 bar'
  955. )
  956. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  957. ['hash1 foo', 'nothash2 bar']
  958. )
  959. assert module.compare_spot_check_hashes(
  960. repository={'path': 'repo'},
  961. archive='archive',
  962. config={
  963. 'checks': [
  964. {
  965. 'name': 'archives',
  966. 'frequency': '2 weeks',
  967. },
  968. {
  969. 'name': 'spot',
  970. 'data_sample_percentage': 50,
  971. },
  972. ],
  973. 'working_directory': '/working/dir',
  974. },
  975. local_borg_version=flexmock(),
  976. global_arguments=flexmock(),
  977. local_path=flexmock(),
  978. remote_path=flexmock(),
  979. log_prefix='repo',
  980. source_paths=('foo', 'bar', 'baz', 'quux'),
  981. ) == ('bar',)
  982. def test_spot_check_without_spot_configuration_errors():
  983. with pytest.raises(ValueError):
  984. module.spot_check(
  985. repository={'path': 'repo'},
  986. config={
  987. 'checks': [
  988. {
  989. 'name': 'archives',
  990. },
  991. ]
  992. },
  993. local_borg_version=flexmock(),
  994. global_arguments=flexmock(),
  995. local_path=flexmock(),
  996. remote_path=flexmock(),
  997. borgmatic_runtime_directory='/run/borgmatic',
  998. )
  999. def test_spot_check_without_any_configuration_errors():
  1000. with pytest.raises(ValueError):
  1001. module.spot_check(
  1002. repository={'path': 'repo'},
  1003. config={},
  1004. local_borg_version=flexmock(),
  1005. global_arguments=flexmock(),
  1006. local_path=flexmock(),
  1007. remote_path=flexmock(),
  1008. borgmatic_runtime_directory='/run/borgmatic',
  1009. )
  1010. def test_spot_check_data_tolerance_percenatge_greater_than_data_sample_percentage_errors():
  1011. with pytest.raises(ValueError):
  1012. module.spot_check(
  1013. repository={'path': 'repo'},
  1014. config={
  1015. 'checks': [
  1016. {
  1017. 'name': 'spot',
  1018. 'data_tolerance_percentage': 7,
  1019. 'data_sample_percentage': 5,
  1020. },
  1021. ]
  1022. },
  1023. local_borg_version=flexmock(),
  1024. global_arguments=flexmock(),
  1025. local_path=flexmock(),
  1026. remote_path=flexmock(),
  1027. borgmatic_runtime_directory='/run/borgmatic',
  1028. )
  1029. def test_spot_check_with_count_delta_greater_than_count_tolerance_percentage_errors():
  1030. flexmock(module).should_receive('collect_spot_check_source_paths').and_return(
  1031. ('/foo', '/bar', '/baz', '/quux')
  1032. )
  1033. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  1034. 'archive'
  1035. )
  1036. flexmock(module).should_receive('collect_spot_check_archive_paths').and_return(
  1037. ('/foo', '/bar')
  1038. ).once()
  1039. with pytest.raises(ValueError):
  1040. module.spot_check(
  1041. repository={'path': 'repo'},
  1042. config={
  1043. 'checks': [
  1044. {
  1045. 'name': 'spot',
  1046. 'count_tolerance_percentage': 1,
  1047. 'data_tolerance_percentage': 4,
  1048. 'data_sample_percentage': 5,
  1049. },
  1050. ]
  1051. },
  1052. local_borg_version=flexmock(),
  1053. global_arguments=flexmock(),
  1054. local_path=flexmock(),
  1055. remote_path=flexmock(),
  1056. borgmatic_runtime_directory='/run/borgmatic',
  1057. )
  1058. def test_spot_check_with_failing_percentage_greater_than_data_tolerance_percentage_errors():
  1059. flexmock(module).should_receive('collect_spot_check_source_paths').and_return(
  1060. ('/foo', '/bar', '/baz', '/quux')
  1061. )
  1062. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  1063. 'archive'
  1064. )
  1065. flexmock(module).should_receive('collect_spot_check_archive_paths').and_return(('/foo', '/bar'))
  1066. flexmock(module).should_receive('compare_spot_check_hashes').and_return(
  1067. ('/bar', '/baz', '/quux')
  1068. ).once()
  1069. with pytest.raises(ValueError):
  1070. module.spot_check(
  1071. repository={'path': 'repo'},
  1072. config={
  1073. 'checks': [
  1074. {
  1075. 'name': 'spot',
  1076. 'count_tolerance_percentage': 55,
  1077. 'data_tolerance_percentage': 4,
  1078. 'data_sample_percentage': 5,
  1079. },
  1080. ]
  1081. },
  1082. local_borg_version=flexmock(),
  1083. global_arguments=flexmock(),
  1084. local_path=flexmock(),
  1085. remote_path=flexmock(),
  1086. borgmatic_runtime_directory='/run/borgmatic',
  1087. )
  1088. def test_spot_check_with_high_enough_tolerances_does_not_raise():
  1089. flexmock(module).should_receive('collect_spot_check_source_paths').and_return(
  1090. ('/foo', '/bar', '/baz', '/quux')
  1091. )
  1092. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  1093. 'archive'
  1094. )
  1095. flexmock(module).should_receive('collect_spot_check_archive_paths').and_return(('/foo', '/bar'))
  1096. flexmock(module).should_receive('compare_spot_check_hashes').and_return(
  1097. ('/bar', '/baz', '/quux')
  1098. ).once()
  1099. module.spot_check(
  1100. repository={'path': 'repo'},
  1101. config={
  1102. 'checks': [
  1103. {
  1104. 'name': 'spot',
  1105. 'count_tolerance_percentage': 55,
  1106. 'data_tolerance_percentage': 80,
  1107. 'data_sample_percentage': 80,
  1108. },
  1109. ]
  1110. },
  1111. local_borg_version=flexmock(),
  1112. global_arguments=flexmock(),
  1113. local_path=flexmock(),
  1114. remote_path=flexmock(),
  1115. borgmatic_runtime_directory='/run/borgmatic',
  1116. )
  1117. def test_run_check_checks_archives_for_configured_repository():
  1118. flexmock(module.logger).answer = lambda message: None
  1119. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
  1120. flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
  1121. flexmock(module).should_receive('upgrade_check_times')
  1122. flexmock(module).should_receive('parse_checks')
  1123. flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
  1124. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  1125. flexmock(module).should_receive('filter_checks_on_frequency').and_return(
  1126. {'repository', 'archives'}
  1127. )
  1128. flexmock(module.borgmatic.borg.check).should_receive('check_archives').once()
  1129. flexmock(module).should_receive('make_check_time_path')
  1130. flexmock(module).should_receive('write_check_time')
  1131. flexmock(module.borgmatic.borg.extract).should_receive('extract_last_archive_dry_run').never()
  1132. flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
  1133. check_arguments = flexmock(
  1134. repository=None,
  1135. progress=flexmock(),
  1136. repair=flexmock(),
  1137. only_checks=flexmock(),
  1138. force=flexmock(),
  1139. )
  1140. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  1141. module.run_check(
  1142. config_filename='test.yaml',
  1143. repository={'path': 'repo'},
  1144. config={'repositories': ['repo']},
  1145. hook_context={},
  1146. local_borg_version=None,
  1147. check_arguments=check_arguments,
  1148. global_arguments=global_arguments,
  1149. local_path=None,
  1150. remote_path=None,
  1151. )
  1152. def test_run_check_runs_configured_extract_check():
  1153. flexmock(module.logger).answer = lambda message: None
  1154. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
  1155. flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
  1156. flexmock(module).should_receive('upgrade_check_times')
  1157. flexmock(module).should_receive('parse_checks')
  1158. flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
  1159. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  1160. flexmock(module).should_receive('filter_checks_on_frequency').and_return({'extract'})
  1161. flexmock(module.borgmatic.borg.check).should_receive('check_archives').never()
  1162. flexmock(module.borgmatic.borg.extract).should_receive('extract_last_archive_dry_run').once()
  1163. flexmock(module).should_receive('make_check_time_path')
  1164. flexmock(module).should_receive('write_check_time')
  1165. flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
  1166. check_arguments = flexmock(
  1167. repository=None,
  1168. progress=flexmock(),
  1169. repair=flexmock(),
  1170. only_checks=flexmock(),
  1171. force=flexmock(),
  1172. )
  1173. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  1174. module.run_check(
  1175. config_filename='test.yaml',
  1176. repository={'path': 'repo'},
  1177. config={'repositories': ['repo']},
  1178. hook_context={},
  1179. local_borg_version=None,
  1180. check_arguments=check_arguments,
  1181. global_arguments=global_arguments,
  1182. local_path=None,
  1183. remote_path=None,
  1184. )
  1185. def test_run_check_runs_configured_spot_check():
  1186. flexmock(module.logger).answer = lambda message: None
  1187. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
  1188. flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
  1189. flexmock(module).should_receive('upgrade_check_times')
  1190. flexmock(module).should_receive('parse_checks')
  1191. flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
  1192. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  1193. flexmock(module).should_receive('filter_checks_on_frequency').and_return({'spot'})
  1194. flexmock(module.borgmatic.borg.check).should_receive('check_archives').never()
  1195. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  1196. flexmock()
  1197. )
  1198. flexmock(module.borgmatic.actions.check).should_receive('spot_check').once()
  1199. flexmock(module).should_receive('make_check_time_path')
  1200. flexmock(module).should_receive('write_check_time')
  1201. flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
  1202. check_arguments = flexmock(
  1203. repository=None,
  1204. progress=flexmock(),
  1205. repair=flexmock(),
  1206. only_checks=flexmock(),
  1207. force=flexmock(),
  1208. )
  1209. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  1210. module.run_check(
  1211. config_filename='test.yaml',
  1212. repository={'path': 'repo'},
  1213. config={'repositories': ['repo']},
  1214. hook_context={},
  1215. local_borg_version=None,
  1216. check_arguments=check_arguments,
  1217. global_arguments=global_arguments,
  1218. local_path=None,
  1219. remote_path=None,
  1220. )
  1221. def test_run_check_without_checks_runs_nothing_except_hooks():
  1222. flexmock(module.logger).answer = lambda message: None
  1223. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
  1224. flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
  1225. flexmock(module).should_receive('upgrade_check_times')
  1226. flexmock(module).should_receive('parse_checks')
  1227. flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
  1228. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  1229. flexmock(module).should_receive('filter_checks_on_frequency').and_return({})
  1230. flexmock(module.borgmatic.borg.check).should_receive('check_archives').never()
  1231. flexmock(module).should_receive('make_check_time_path')
  1232. flexmock(module).should_receive('write_check_time').never()
  1233. flexmock(module.borgmatic.borg.extract).should_receive('extract_last_archive_dry_run').never()
  1234. flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
  1235. check_arguments = flexmock(
  1236. repository=None,
  1237. progress=flexmock(),
  1238. repair=flexmock(),
  1239. only_checks=flexmock(),
  1240. force=flexmock(),
  1241. )
  1242. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  1243. module.run_check(
  1244. config_filename='test.yaml',
  1245. repository={'path': 'repo'},
  1246. config={'repositories': ['repo']},
  1247. hook_context={},
  1248. local_borg_version=None,
  1249. check_arguments=check_arguments,
  1250. global_arguments=global_arguments,
  1251. local_path=None,
  1252. remote_path=None,
  1253. )
  1254. def test_run_check_checks_archives_in_selected_repository():
  1255. flexmock(module.logger).answer = lambda message: None
  1256. flexmock(module.borgmatic.config.validate).should_receive(
  1257. 'repositories_match'
  1258. ).once().and_return(True)
  1259. flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
  1260. flexmock(module).should_receive('upgrade_check_times')
  1261. flexmock(module).should_receive('parse_checks')
  1262. flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
  1263. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  1264. flexmock(module).should_receive('filter_checks_on_frequency').and_return(
  1265. {'repository', 'archives'}
  1266. )
  1267. flexmock(module.borgmatic.borg.check).should_receive('check_archives').once()
  1268. flexmock(module).should_receive('make_check_time_path')
  1269. flexmock(module).should_receive('write_check_time')
  1270. flexmock(module.borgmatic.borg.extract).should_receive('extract_last_archive_dry_run').never()
  1271. check_arguments = flexmock(
  1272. repository=flexmock(),
  1273. progress=flexmock(),
  1274. repair=flexmock(),
  1275. only_checks=flexmock(),
  1276. force=flexmock(),
  1277. )
  1278. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  1279. module.run_check(
  1280. config_filename='test.yaml',
  1281. repository={'path': 'repo'},
  1282. config={'repositories': ['repo']},
  1283. hook_context={},
  1284. local_borg_version=None,
  1285. check_arguments=check_arguments,
  1286. global_arguments=global_arguments,
  1287. local_path=None,
  1288. remote_path=None,
  1289. )
  1290. def test_run_check_bails_if_repository_does_not_match():
  1291. flexmock(module.logger).answer = lambda message: None
  1292. flexmock(module.borgmatic.config.validate).should_receive(
  1293. 'repositories_match'
  1294. ).once().and_return(False)
  1295. flexmock(module.borgmatic.borg.check).should_receive('check_archives').never()
  1296. check_arguments = flexmock(
  1297. repository=flexmock(),
  1298. progress=flexmock(),
  1299. repair=flexmock(),
  1300. only_checks=flexmock(),
  1301. force=flexmock(),
  1302. )
  1303. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  1304. module.run_check(
  1305. config_filename='test.yaml',
  1306. repository={'path': 'repo'},
  1307. config={'repositories': ['repo']},
  1308. hook_context={},
  1309. local_borg_version=None,
  1310. check_arguments=check_arguments,
  1311. global_arguments=global_arguments,
  1312. local_path=None,
  1313. remote_path=None,
  1314. )