test_check.py 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491
  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.borg.create).should_receive('make_base_create_command').with_args(
  442. dry_run=True,
  443. repository_path='repo',
  444. config=object,
  445. config_paths=(),
  446. local_borg_version=object,
  447. global_arguments=object,
  448. borgmatic_runtime_directories=(),
  449. local_path=object,
  450. remote_path=object,
  451. list_files=True,
  452. stream_processes=True,
  453. ).and_return((('borg', 'create'), ('repo::archive',), flexmock(), flexmock()))
  454. flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
  455. flexmock()
  456. )
  457. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  458. flexmock(module.borgmatic.execute).should_receive(
  459. 'execute_command_and_capture_output'
  460. ).and_return(
  461. 'warning: stuff\n- /etc/path\n+ /etc/other\n? /nope',
  462. )
  463. flexmock(module.os.path).should_receive('isfile').and_return(True)
  464. assert module.collect_spot_check_source_paths(
  465. repository={'path': 'repo'},
  466. config={'working_directory': '/'},
  467. local_borg_version=flexmock(),
  468. global_arguments=flexmock(),
  469. local_path=flexmock(),
  470. remote_path=flexmock(),
  471. ) == ('/etc/path', '/etc/other')
  472. def test_collect_spot_check_source_paths_passes_through_stream_processes_false():
  473. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
  474. {'hook1': False, 'hook2': False}
  475. )
  476. flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
  477. dry_run=True,
  478. repository_path='repo',
  479. config=object,
  480. config_paths=(),
  481. local_borg_version=object,
  482. global_arguments=object,
  483. borgmatic_runtime_directories=(),
  484. local_path=object,
  485. remote_path=object,
  486. list_files=True,
  487. stream_processes=False,
  488. ).and_return((('borg', 'create'), ('repo::archive',), flexmock(), flexmock()))
  489. flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
  490. flexmock()
  491. )
  492. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  493. flexmock(module.borgmatic.execute).should_receive(
  494. 'execute_command_and_capture_output'
  495. ).and_return(
  496. 'warning: stuff\n- /etc/path\n+ /etc/other\n? /nope',
  497. )
  498. flexmock(module.os.path).should_receive('isfile').and_return(True)
  499. assert module.collect_spot_check_source_paths(
  500. repository={'path': 'repo'},
  501. config={'working_directory': '/'},
  502. local_borg_version=flexmock(),
  503. global_arguments=flexmock(),
  504. local_path=flexmock(),
  505. remote_path=flexmock(),
  506. ) == ('/etc/path', '/etc/other')
  507. def test_collect_spot_check_source_paths_without_working_directory_parses_borg_output():
  508. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
  509. {'hook1': False, 'hook2': True}
  510. )
  511. flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
  512. dry_run=True,
  513. repository_path='repo',
  514. config=object,
  515. config_paths=(),
  516. local_borg_version=object,
  517. global_arguments=object,
  518. borgmatic_runtime_directories=(),
  519. local_path=object,
  520. remote_path=object,
  521. list_files=True,
  522. stream_processes=True,
  523. ).and_return((('borg', 'create'), ('repo::archive',), flexmock(), flexmock()))
  524. flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
  525. flexmock()
  526. )
  527. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  528. flexmock(module.borgmatic.execute).should_receive(
  529. 'execute_command_and_capture_output'
  530. ).and_return(
  531. 'warning: stuff\n- /etc/path\n+ /etc/other\n? /nope',
  532. )
  533. flexmock(module.os.path).should_receive('isfile').and_return(True)
  534. assert module.collect_spot_check_source_paths(
  535. repository={'path': 'repo'},
  536. config={},
  537. local_borg_version=flexmock(),
  538. global_arguments=flexmock(),
  539. local_path=flexmock(),
  540. remote_path=flexmock(),
  541. ) == ('/etc/path', '/etc/other')
  542. def test_collect_spot_check_source_paths_skips_directories():
  543. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
  544. {'hook1': False, 'hook2': True}
  545. )
  546. flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
  547. dry_run=True,
  548. repository_path='repo',
  549. config=object,
  550. config_paths=(),
  551. local_borg_version=object,
  552. global_arguments=object,
  553. borgmatic_runtime_directories=(),
  554. local_path=object,
  555. remote_path=object,
  556. list_files=True,
  557. stream_processes=True,
  558. ).and_return((('borg', 'create'), ('repo::archive',), flexmock(), flexmock()))
  559. flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
  560. flexmock()
  561. )
  562. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  563. flexmock(module.borgmatic.execute).should_receive(
  564. 'execute_command_and_capture_output'
  565. ).and_return(
  566. 'warning: stuff\n- /etc/path\n+ /etc/dir\n? /nope',
  567. )
  568. flexmock(module.os.path).should_receive('isfile').with_args('/etc/path').and_return(False)
  569. flexmock(module.os.path).should_receive('isfile').with_args('/etc/dir').and_return(False)
  570. assert (
  571. module.collect_spot_check_source_paths(
  572. repository={'path': 'repo'},
  573. config={'working_directory': '/'},
  574. local_borg_version=flexmock(),
  575. global_arguments=flexmock(),
  576. local_path=flexmock(),
  577. remote_path=flexmock(),
  578. )
  579. == ()
  580. )
  581. def test_collect_spot_check_archive_paths_excludes_directories():
  582. flexmock(module.borgmatic.config.paths).should_receive(
  583. 'get_borgmatic_source_directory'
  584. ).and_return('/home/user/.borgmatic')
  585. flexmock(module.borgmatic.config.paths).should_receive(
  586. 'get_borgmatic_runtime_directory'
  587. ).and_return('/run/user/1001/borgmatic')
  588. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  589. (
  590. 'f /etc/path',
  591. 'f /etc/other',
  592. 'd /etc/dir',
  593. )
  594. )
  595. assert module.collect_spot_check_archive_paths(
  596. repository={'path': 'repo'},
  597. archive='archive',
  598. config={},
  599. local_borg_version=flexmock(),
  600. global_arguments=flexmock(),
  601. local_path=flexmock(),
  602. remote_path=flexmock(),
  603. ) == ('/etc/path', '/etc/other')
  604. def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_runtime_directory_as_stored_with_prefix_truncation():
  605. flexmock(module.borgmatic.config.paths).should_receive(
  606. 'get_borgmatic_source_directory'
  607. ).and_return('/root/.borgmatic')
  608. flexmock(module.borgmatic.config.paths).should_receive(
  609. 'get_borgmatic_runtime_directory'
  610. ).and_return('/run/user/0/borgmatic')
  611. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  612. (
  613. 'f /etc/path',
  614. 'f /borgmatic/some/thing',
  615. )
  616. )
  617. assert module.collect_spot_check_archive_paths(
  618. repository={'path': 'repo'},
  619. archive='archive',
  620. config={},
  621. local_borg_version=flexmock(),
  622. global_arguments=flexmock(),
  623. local_path=flexmock(),
  624. remote_path=flexmock(),
  625. ) == ('/etc/path',)
  626. def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_source_directory():
  627. flexmock(module.borgmatic.config.paths).should_receive(
  628. 'get_borgmatic_source_directory'
  629. ).and_return('/root/.borgmatic')
  630. flexmock(module.borgmatic.config.paths).should_receive(
  631. 'get_borgmatic_runtime_directory'
  632. ).and_return('/run/user/0/borgmatic')
  633. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  634. (
  635. 'f /etc/path',
  636. 'f /root/.borgmatic/some/thing',
  637. )
  638. )
  639. assert module.collect_spot_check_archive_paths(
  640. repository={'path': 'repo'},
  641. archive='archive',
  642. config={},
  643. local_borg_version=flexmock(),
  644. global_arguments=flexmock(),
  645. local_path=flexmock(),
  646. remote_path=flexmock(),
  647. ) == ('/etc/path',)
  648. def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_runtime_directory():
  649. flexmock(module.borgmatic.config.paths).should_receive(
  650. 'get_borgmatic_source_directory'
  651. ).and_return('/root.borgmatic')
  652. flexmock(module.borgmatic.config.paths).should_receive(
  653. 'get_borgmatic_runtime_directory'
  654. ).and_return('/run/user/0/borgmatic')
  655. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  656. (
  657. 'f /etc/path',
  658. 'f /run/user/0/borgmatic/some/thing',
  659. )
  660. )
  661. assert module.collect_spot_check_archive_paths(
  662. repository={'path': 'repo'},
  663. archive='archive',
  664. config={},
  665. local_borg_version=flexmock(),
  666. global_arguments=flexmock(),
  667. local_path=flexmock(),
  668. remote_path=flexmock(),
  669. ) == ('/etc/path',)
  670. def test_collect_spot_check_source_paths_uses_working_directory():
  671. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
  672. {'hook1': False, 'hook2': True}
  673. )
  674. flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
  675. dry_run=True,
  676. repository_path='repo',
  677. config=object,
  678. config_paths=(),
  679. local_borg_version=object,
  680. global_arguments=object,
  681. borgmatic_runtime_directories=(),
  682. local_path=object,
  683. remote_path=object,
  684. list_files=True,
  685. stream_processes=True,
  686. ).and_return((('borg', 'create'), ('repo::archive',), flexmock(), flexmock()))
  687. flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
  688. flexmock()
  689. )
  690. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  691. '/working/dir'
  692. )
  693. flexmock(module.borgmatic.execute).should_receive(
  694. 'execute_command_and_capture_output'
  695. ).and_return(
  696. 'warning: stuff\n- foo\n+ bar\n? /nope',
  697. )
  698. flexmock(module.os.path).should_receive('isfile').with_args('/working/dir/foo').and_return(True)
  699. flexmock(module.os.path).should_receive('isfile').with_args('/working/dir/bar').and_return(True)
  700. assert module.collect_spot_check_source_paths(
  701. repository={'path': 'repo'},
  702. config={'working_directory': '/working/dir'},
  703. local_borg_version=flexmock(),
  704. global_arguments=flexmock(),
  705. local_path=flexmock(),
  706. remote_path=flexmock(),
  707. ) == ('foo', 'bar')
  708. def test_compare_spot_check_hashes_returns_paths_having_failing_hashes():
  709. flexmock(module.random).should_receive('sample').replace_with(
  710. lambda population, count: population[:count]
  711. )
  712. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  713. None,
  714. )
  715. flexmock(module.os.path).should_receive('exists').and_return(True)
  716. flexmock(module.borgmatic.execute).should_receive(
  717. 'execute_command_and_capture_output'
  718. ).with_args(('xxh64sum', '/foo', '/bar'), working_directory=None).and_return(
  719. 'hash1 /foo\nhash2 /bar'
  720. )
  721. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  722. ['hash1 /foo', 'nothash2 /bar']
  723. )
  724. assert module.compare_spot_check_hashes(
  725. repository={'path': 'repo'},
  726. archive='archive',
  727. config={
  728. 'checks': [
  729. {
  730. 'name': 'archives',
  731. 'frequency': '2 weeks',
  732. },
  733. {
  734. 'name': 'spot',
  735. 'data_sample_percentage': 50,
  736. },
  737. ]
  738. },
  739. local_borg_version=flexmock(),
  740. global_arguments=flexmock(),
  741. local_path=flexmock(),
  742. remote_path=flexmock(),
  743. log_label='repo',
  744. source_paths=('/foo', '/bar', '/baz', '/quux'),
  745. ) == ('/bar',)
  746. def test_compare_spot_check_hashes_handles_data_sample_percentage_above_100():
  747. flexmock(module.random).should_receive('sample').replace_with(
  748. lambda population, count: population[:count]
  749. )
  750. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  751. None,
  752. )
  753. flexmock(module.os.path).should_receive('exists').and_return(True)
  754. flexmock(module.borgmatic.execute).should_receive(
  755. 'execute_command_and_capture_output'
  756. ).with_args(('xxh64sum', '/foo', '/bar'), working_directory=None).and_return(
  757. 'hash1 /foo\nhash2 /bar'
  758. )
  759. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  760. ['nothash1 /foo', 'nothash2 /bar']
  761. )
  762. assert module.compare_spot_check_hashes(
  763. repository={'path': 'repo'},
  764. archive='archive',
  765. config={
  766. 'checks': [
  767. {
  768. 'name': 'archives',
  769. 'frequency': '2 weeks',
  770. },
  771. {
  772. 'name': 'spot',
  773. 'data_sample_percentage': 1000,
  774. },
  775. ]
  776. },
  777. local_borg_version=flexmock(),
  778. global_arguments=flexmock(),
  779. local_path=flexmock(),
  780. remote_path=flexmock(),
  781. log_label='repo',
  782. source_paths=('/foo', '/bar'),
  783. ) == ('/foo', '/bar')
  784. def test_compare_spot_check_hashes_uses_xxh64sum_command_option():
  785. flexmock(module.random).should_receive('sample').replace_with(
  786. lambda population, count: population[:count]
  787. )
  788. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  789. None,
  790. )
  791. flexmock(module.os.path).should_receive('exists').and_return(True)
  792. flexmock(module.borgmatic.execute).should_receive(
  793. 'execute_command_and_capture_output'
  794. ).with_args(('/usr/local/bin/xxh64sum', '/foo', '/bar'), working_directory=None).and_return(
  795. 'hash1 /foo\nhash2 /bar'
  796. )
  797. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  798. ['hash1 /foo', 'nothash2 /bar']
  799. )
  800. assert module.compare_spot_check_hashes(
  801. repository={'path': 'repo'},
  802. archive='archive',
  803. config={
  804. 'checks': [
  805. {
  806. 'name': 'spot',
  807. 'data_sample_percentage': 50,
  808. 'xxh64sum_command': '/usr/local/bin/xxh64sum',
  809. },
  810. ]
  811. },
  812. local_borg_version=flexmock(),
  813. global_arguments=flexmock(),
  814. local_path=flexmock(),
  815. remote_path=flexmock(),
  816. log_label='repo',
  817. source_paths=('/foo', '/bar', '/baz', '/quux'),
  818. ) == ('/bar',)
  819. def test_compare_spot_check_hashes_considers_path_missing_from_archive_as_not_matching():
  820. flexmock(module.random).should_receive('sample').replace_with(
  821. lambda population, count: population[:count]
  822. )
  823. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  824. None,
  825. )
  826. flexmock(module.os.path).should_receive('exists').and_return(True)
  827. flexmock(module.borgmatic.execute).should_receive(
  828. 'execute_command_and_capture_output'
  829. ).with_args(('xxh64sum', '/foo', '/bar'), working_directory=None).and_return(
  830. 'hash1 /foo\nhash2 /bar'
  831. )
  832. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  833. ['hash1 /foo']
  834. )
  835. assert module.compare_spot_check_hashes(
  836. repository={'path': 'repo'},
  837. archive='archive',
  838. config={
  839. 'checks': [
  840. {
  841. 'name': 'spot',
  842. 'data_sample_percentage': 50,
  843. },
  844. ]
  845. },
  846. local_borg_version=flexmock(),
  847. global_arguments=flexmock(),
  848. local_path=flexmock(),
  849. remote_path=flexmock(),
  850. log_label='repo',
  851. source_paths=('/foo', '/bar', '/baz', '/quux'),
  852. ) == ('/bar',)
  853. def test_compare_spot_check_hashes_considers_non_existent_path_as_not_matching():
  854. flexmock(module.random).should_receive('sample').replace_with(
  855. lambda population, count: population[:count]
  856. )
  857. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  858. None,
  859. )
  860. flexmock(module.os.path).should_receive('exists').with_args('/foo').and_return(True)
  861. flexmock(module.os.path).should_receive('exists').with_args('/bar').and_return(False)
  862. flexmock(module.borgmatic.execute).should_receive(
  863. 'execute_command_and_capture_output'
  864. ).with_args(('xxh64sum', '/foo'), working_directory=None).and_return('hash1 /foo')
  865. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  866. ['hash1 /foo', 'hash2 /bar']
  867. )
  868. assert module.compare_spot_check_hashes(
  869. repository={'path': 'repo'},
  870. archive='archive',
  871. config={
  872. 'checks': [
  873. {
  874. 'name': 'spot',
  875. 'data_sample_percentage': 50,
  876. },
  877. ]
  878. },
  879. local_borg_version=flexmock(),
  880. global_arguments=flexmock(),
  881. local_path=flexmock(),
  882. remote_path=flexmock(),
  883. log_label='repo',
  884. source_paths=('/foo', '/bar', '/baz', '/quux'),
  885. ) == ('/bar',)
  886. def test_compare_spot_check_hashes_with_too_many_paths_feeds_them_to_commands_in_chunks():
  887. flexmock(module).SAMPLE_PATHS_SUBSET_COUNT = 2
  888. flexmock(module.random).should_receive('sample').replace_with(
  889. lambda population, count: population[:count]
  890. )
  891. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  892. None,
  893. )
  894. flexmock(module.os.path).should_receive('exists').and_return(True)
  895. flexmock(module.borgmatic.execute).should_receive(
  896. 'execute_command_and_capture_output'
  897. ).with_args(('xxh64sum', '/foo', '/bar'), working_directory=None).and_return(
  898. 'hash1 /foo\nhash2 /bar'
  899. )
  900. flexmock(module.borgmatic.execute).should_receive(
  901. 'execute_command_and_capture_output'
  902. ).with_args(('xxh64sum', '/baz', '/quux'), working_directory=None).and_return(
  903. 'hash3 /baz\nhash4 /quux'
  904. )
  905. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  906. ['hash1 /foo', 'hash2 /bar']
  907. ).and_return(['hash3 /baz', 'nothash4 /quux'])
  908. assert module.compare_spot_check_hashes(
  909. repository={'path': 'repo'},
  910. archive='archive',
  911. config={
  912. 'checks': [
  913. {
  914. 'name': 'archives',
  915. 'frequency': '2 weeks',
  916. },
  917. {
  918. 'name': 'spot',
  919. 'data_sample_percentage': 100,
  920. },
  921. ]
  922. },
  923. local_borg_version=flexmock(),
  924. global_arguments=flexmock(),
  925. local_path=flexmock(),
  926. remote_path=flexmock(),
  927. log_label='repo',
  928. source_paths=('/foo', '/bar', '/baz', '/quux'),
  929. ) == ('/quux',)
  930. def test_compare_spot_check_hashes_uses_working_directory_to_access_source_paths():
  931. flexmock(module.random).should_receive('sample').replace_with(
  932. lambda population, count: population[:count]
  933. )
  934. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  935. '/working/dir',
  936. )
  937. flexmock(module.os.path).should_receive('exists').with_args('/working/dir/foo').and_return(True)
  938. flexmock(module.os.path).should_receive('exists').with_args('/working/dir/bar').and_return(True)
  939. flexmock(module.borgmatic.execute).should_receive(
  940. 'execute_command_and_capture_output'
  941. ).with_args(('xxh64sum', 'foo', 'bar'), working_directory='/working/dir').and_return(
  942. 'hash1 foo\nhash2 bar'
  943. )
  944. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  945. ['hash1 foo', 'nothash2 bar']
  946. )
  947. assert module.compare_spot_check_hashes(
  948. repository={'path': 'repo'},
  949. archive='archive',
  950. config={
  951. 'checks': [
  952. {
  953. 'name': 'archives',
  954. 'frequency': '2 weeks',
  955. },
  956. {
  957. 'name': 'spot',
  958. 'data_sample_percentage': 50,
  959. },
  960. ],
  961. 'working_directory': '/working/dir',
  962. },
  963. local_borg_version=flexmock(),
  964. global_arguments=flexmock(),
  965. local_path=flexmock(),
  966. remote_path=flexmock(),
  967. log_label='repo',
  968. source_paths=('foo', 'bar', 'baz', 'quux'),
  969. ) == ('bar',)
  970. def test_spot_check_without_spot_configuration_errors():
  971. with pytest.raises(ValueError):
  972. module.spot_check(
  973. repository={'path': 'repo'},
  974. config={
  975. 'checks': [
  976. {
  977. 'name': 'archives',
  978. },
  979. ]
  980. },
  981. local_borg_version=flexmock(),
  982. global_arguments=flexmock(),
  983. local_path=flexmock(),
  984. remote_path=flexmock(),
  985. )
  986. def test_spot_check_without_any_configuration_errors():
  987. with pytest.raises(ValueError):
  988. module.spot_check(
  989. repository={'path': 'repo'},
  990. config={},
  991. local_borg_version=flexmock(),
  992. global_arguments=flexmock(),
  993. local_path=flexmock(),
  994. remote_path=flexmock(),
  995. )
  996. def test_spot_check_data_tolerance_percenatge_greater_than_data_sample_percentage_errors():
  997. with pytest.raises(ValueError):
  998. module.spot_check(
  999. repository={'path': 'repo'},
  1000. config={
  1001. 'checks': [
  1002. {
  1003. 'name': 'spot',
  1004. 'data_tolerance_percentage': 7,
  1005. 'data_sample_percentage': 5,
  1006. },
  1007. ]
  1008. },
  1009. local_borg_version=flexmock(),
  1010. global_arguments=flexmock(),
  1011. local_path=flexmock(),
  1012. remote_path=flexmock(),
  1013. )
  1014. def test_spot_check_with_count_delta_greater_than_count_tolerance_percentage_errors():
  1015. flexmock(module).should_receive('collect_spot_check_source_paths').and_return(
  1016. ('/foo', '/bar', '/baz', '/quux')
  1017. )
  1018. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  1019. 'archive'
  1020. )
  1021. flexmock(module).should_receive('collect_spot_check_archive_paths').and_return(
  1022. ('/foo', '/bar')
  1023. ).once()
  1024. with pytest.raises(ValueError):
  1025. module.spot_check(
  1026. repository={'path': 'repo'},
  1027. config={
  1028. 'checks': [
  1029. {
  1030. 'name': 'spot',
  1031. 'count_tolerance_percentage': 1,
  1032. 'data_tolerance_percentage': 4,
  1033. 'data_sample_percentage': 5,
  1034. },
  1035. ]
  1036. },
  1037. local_borg_version=flexmock(),
  1038. global_arguments=flexmock(),
  1039. local_path=flexmock(),
  1040. remote_path=flexmock(),
  1041. )
  1042. def test_spot_check_with_failing_percentage_greater_than_data_tolerance_percentage_errors():
  1043. flexmock(module).should_receive('collect_spot_check_source_paths').and_return(
  1044. ('/foo', '/bar', '/baz', '/quux')
  1045. )
  1046. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  1047. 'archive'
  1048. )
  1049. flexmock(module).should_receive('collect_spot_check_archive_paths').and_return(('/foo', '/bar'))
  1050. flexmock(module).should_receive('compare_spot_check_hashes').and_return(
  1051. ('/bar', '/baz', '/quux')
  1052. ).once()
  1053. with pytest.raises(ValueError):
  1054. module.spot_check(
  1055. repository={'path': 'repo'},
  1056. config={
  1057. 'checks': [
  1058. {
  1059. 'name': 'spot',
  1060. 'count_tolerance_percentage': 55,
  1061. 'data_tolerance_percentage': 4,
  1062. 'data_sample_percentage': 5,
  1063. },
  1064. ]
  1065. },
  1066. local_borg_version=flexmock(),
  1067. global_arguments=flexmock(),
  1068. local_path=flexmock(),
  1069. remote_path=flexmock(),
  1070. )
  1071. def test_spot_check_with_high_enough_tolerances_does_not_raise():
  1072. flexmock(module).should_receive('collect_spot_check_source_paths').and_return(
  1073. ('/foo', '/bar', '/baz', '/quux')
  1074. )
  1075. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  1076. 'archive'
  1077. )
  1078. flexmock(module).should_receive('collect_spot_check_archive_paths').and_return(('/foo', '/bar'))
  1079. flexmock(module).should_receive('compare_spot_check_hashes').and_return(
  1080. ('/bar', '/baz', '/quux')
  1081. ).once()
  1082. module.spot_check(
  1083. repository={'path': 'repo'},
  1084. config={
  1085. 'checks': [
  1086. {
  1087. 'name': 'spot',
  1088. 'count_tolerance_percentage': 55,
  1089. 'data_tolerance_percentage': 80,
  1090. 'data_sample_percentage': 80,
  1091. },
  1092. ]
  1093. },
  1094. local_borg_version=flexmock(),
  1095. global_arguments=flexmock(),
  1096. local_path=flexmock(),
  1097. remote_path=flexmock(),
  1098. )
  1099. def test_run_check_checks_archives_for_configured_repository():
  1100. flexmock(module.logger).answer = lambda message: None
  1101. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
  1102. flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
  1103. flexmock(module).should_receive('upgrade_check_times')
  1104. flexmock(module).should_receive('parse_checks')
  1105. flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
  1106. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  1107. flexmock(module).should_receive('filter_checks_on_frequency').and_return(
  1108. {'repository', 'archives'}
  1109. )
  1110. flexmock(module.borgmatic.borg.check).should_receive('check_archives').once()
  1111. flexmock(module).should_receive('make_check_time_path')
  1112. flexmock(module).should_receive('write_check_time')
  1113. flexmock(module.borgmatic.borg.extract).should_receive('extract_last_archive_dry_run').never()
  1114. flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
  1115. check_arguments = flexmock(
  1116. repository=None,
  1117. progress=flexmock(),
  1118. repair=flexmock(),
  1119. only_checks=flexmock(),
  1120. force=flexmock(),
  1121. )
  1122. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  1123. module.run_check(
  1124. config_filename='test.yaml',
  1125. repository={'path': 'repo'},
  1126. config={'repositories': ['repo']},
  1127. hook_context={},
  1128. local_borg_version=None,
  1129. check_arguments=check_arguments,
  1130. global_arguments=global_arguments,
  1131. local_path=None,
  1132. remote_path=None,
  1133. )
  1134. def test_run_check_runs_configured_extract_check():
  1135. flexmock(module.logger).answer = lambda message: None
  1136. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
  1137. flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
  1138. flexmock(module).should_receive('upgrade_check_times')
  1139. flexmock(module).should_receive('parse_checks')
  1140. flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
  1141. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  1142. flexmock(module).should_receive('filter_checks_on_frequency').and_return({'extract'})
  1143. flexmock(module.borgmatic.borg.check).should_receive('check_archives').never()
  1144. flexmock(module.borgmatic.borg.extract).should_receive('extract_last_archive_dry_run').once()
  1145. flexmock(module).should_receive('make_check_time_path')
  1146. flexmock(module).should_receive('write_check_time')
  1147. flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
  1148. check_arguments = flexmock(
  1149. repository=None,
  1150. progress=flexmock(),
  1151. repair=flexmock(),
  1152. only_checks=flexmock(),
  1153. force=flexmock(),
  1154. )
  1155. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  1156. module.run_check(
  1157. config_filename='test.yaml',
  1158. repository={'path': 'repo'},
  1159. config={'repositories': ['repo']},
  1160. hook_context={},
  1161. local_borg_version=None,
  1162. check_arguments=check_arguments,
  1163. global_arguments=global_arguments,
  1164. local_path=None,
  1165. remote_path=None,
  1166. )
  1167. def test_run_check_runs_configured_spot_check():
  1168. flexmock(module.logger).answer = lambda message: None
  1169. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
  1170. flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
  1171. flexmock(module).should_receive('upgrade_check_times')
  1172. flexmock(module).should_receive('parse_checks')
  1173. flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
  1174. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  1175. flexmock(module).should_receive('filter_checks_on_frequency').and_return({'spot'})
  1176. flexmock(module.borgmatic.borg.check).should_receive('check_archives').never()
  1177. flexmock(module.borgmatic.actions.check).should_receive('spot_check').once()
  1178. flexmock(module).should_receive('make_check_time_path')
  1179. flexmock(module).should_receive('write_check_time')
  1180. flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
  1181. check_arguments = flexmock(
  1182. repository=None,
  1183. progress=flexmock(),
  1184. repair=flexmock(),
  1185. only_checks=flexmock(),
  1186. force=flexmock(),
  1187. )
  1188. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  1189. module.run_check(
  1190. config_filename='test.yaml',
  1191. repository={'path': 'repo'},
  1192. config={'repositories': ['repo']},
  1193. hook_context={},
  1194. local_borg_version=None,
  1195. check_arguments=check_arguments,
  1196. global_arguments=global_arguments,
  1197. local_path=None,
  1198. remote_path=None,
  1199. )
  1200. def test_run_check_without_checks_runs_nothing_except_hooks():
  1201. flexmock(module.logger).answer = lambda message: None
  1202. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
  1203. flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
  1204. flexmock(module).should_receive('upgrade_check_times')
  1205. flexmock(module).should_receive('parse_checks')
  1206. flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
  1207. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  1208. flexmock(module).should_receive('filter_checks_on_frequency').and_return({})
  1209. flexmock(module.borgmatic.borg.check).should_receive('check_archives').never()
  1210. flexmock(module).should_receive('make_check_time_path')
  1211. flexmock(module).should_receive('write_check_time').never()
  1212. flexmock(module.borgmatic.borg.extract).should_receive('extract_last_archive_dry_run').never()
  1213. flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
  1214. check_arguments = flexmock(
  1215. repository=None,
  1216. progress=flexmock(),
  1217. repair=flexmock(),
  1218. only_checks=flexmock(),
  1219. force=flexmock(),
  1220. )
  1221. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  1222. module.run_check(
  1223. config_filename='test.yaml',
  1224. repository={'path': 'repo'},
  1225. config={'repositories': ['repo']},
  1226. hook_context={},
  1227. local_borg_version=None,
  1228. check_arguments=check_arguments,
  1229. global_arguments=global_arguments,
  1230. local_path=None,
  1231. remote_path=None,
  1232. )
  1233. def test_run_check_checks_archives_in_selected_repository():
  1234. flexmock(module.logger).answer = lambda message: None
  1235. flexmock(module.borgmatic.config.validate).should_receive(
  1236. 'repositories_match'
  1237. ).once().and_return(True)
  1238. flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
  1239. flexmock(module).should_receive('upgrade_check_times')
  1240. flexmock(module).should_receive('parse_checks')
  1241. flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
  1242. flexmock(module).should_receive('make_archives_check_id').and_return(None)
  1243. flexmock(module).should_receive('filter_checks_on_frequency').and_return(
  1244. {'repository', 'archives'}
  1245. )
  1246. flexmock(module.borgmatic.borg.check).should_receive('check_archives').once()
  1247. flexmock(module).should_receive('make_check_time_path')
  1248. flexmock(module).should_receive('write_check_time')
  1249. flexmock(module.borgmatic.borg.extract).should_receive('extract_last_archive_dry_run').never()
  1250. check_arguments = flexmock(
  1251. repository=flexmock(),
  1252. progress=flexmock(),
  1253. repair=flexmock(),
  1254. only_checks=flexmock(),
  1255. force=flexmock(),
  1256. )
  1257. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  1258. module.run_check(
  1259. config_filename='test.yaml',
  1260. repository={'path': 'repo'},
  1261. config={'repositories': ['repo']},
  1262. hook_context={},
  1263. local_borg_version=None,
  1264. check_arguments=check_arguments,
  1265. global_arguments=global_arguments,
  1266. local_path=None,
  1267. remote_path=None,
  1268. )
  1269. def test_run_check_bails_if_repository_does_not_match():
  1270. flexmock(module.logger).answer = lambda message: None
  1271. flexmock(module.borgmatic.config.validate).should_receive(
  1272. 'repositories_match'
  1273. ).once().and_return(False)
  1274. flexmock(module.borgmatic.borg.check).should_receive('check_archives').never()
  1275. check_arguments = flexmock(
  1276. repository=flexmock(),
  1277. progress=flexmock(),
  1278. repair=flexmock(),
  1279. only_checks=flexmock(),
  1280. force=flexmock(),
  1281. )
  1282. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  1283. module.run_check(
  1284. config_filename='test.yaml',
  1285. repository={'path': 'repo'},
  1286. config={'repositories': ['repo']},
  1287. hook_context={},
  1288. local_borg_version=None,
  1289. check_arguments=check_arguments,
  1290. global_arguments=global_arguments,
  1291. local_path=None,
  1292. remote_path=None,
  1293. )