test_prune.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. import logging
  2. from flexmock import flexmock
  3. from borgmatic.borg import prune as module
  4. from ..test_verbosity import insert_logging_mock
  5. def insert_execute_command_mock(prune_command, output_log_level):
  6. flexmock(module.environment).should_receive('make_environment')
  7. flexmock(module).should_receive('execute_command').with_args(
  8. prune_command,
  9. output_log_level=output_log_level,
  10. borg_local_path=prune_command[0],
  11. extra_environment=None,
  12. ).once()
  13. BASE_PRUNE_FLAGS = ('--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')
  14. def test_make_prune_flags_returns_flags_from_config():
  15. config = {
  16. 'keep_daily': 1,
  17. 'keep_weekly': 2,
  18. 'keep_monthly': 3,
  19. }
  20. flexmock(module.feature).should_receive('available').and_return(True)
  21. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  22. result = module.make_prune_flags(config, local_borg_version='1.2.3')
  23. assert result == BASE_PRUNE_FLAGS
  24. def test_make_prune_flags_accepts_prefix_with_placeholders():
  25. config = {
  26. 'keep_daily': 1,
  27. 'prefix': 'Documents_{hostname}-{now}', # noqa: FS003
  28. }
  29. flexmock(module.feature).should_receive('available').and_return(True)
  30. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  31. result = module.make_prune_flags(config, local_borg_version='1.2.3')
  32. expected = (
  33. '--keep-daily',
  34. '1',
  35. '--match-archives',
  36. 'sh:Documents_{hostname}-{now}*', # noqa: FS003
  37. )
  38. assert result == expected
  39. def test_make_prune_flags_with_prefix_without_borg_features_uses_glob_archives():
  40. config = {
  41. 'keep_daily': 1,
  42. 'prefix': 'Documents_{hostname}-{now}', # noqa: FS003
  43. }
  44. flexmock(module.feature).should_receive('available').and_return(False)
  45. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  46. result = module.make_prune_flags(config, local_borg_version='1.2.3')
  47. expected = (
  48. '--keep-daily',
  49. '1',
  50. '--glob-archives',
  51. 'Documents_{hostname}-{now}*', # noqa: FS003
  52. )
  53. assert result == expected
  54. def test_make_prune_flags_prefers_prefix_to_archive_name_format():
  55. config = {
  56. 'archive_name_format': 'bar-{now}', # noqa: FS003
  57. 'keep_daily': 1,
  58. 'prefix': 'bar-',
  59. }
  60. flexmock(module.feature).should_receive('available').and_return(True)
  61. flexmock(module.flags).should_receive('make_match_archives_flags').never()
  62. result = module.make_prune_flags(config, local_borg_version='1.2.3')
  63. expected = (
  64. '--keep-daily',
  65. '1',
  66. '--match-archives',
  67. 'sh:bar-*', # noqa: FS003
  68. )
  69. assert result == expected
  70. def test_make_prune_flags_without_prefix_uses_archive_name_format_instead():
  71. config = {
  72. 'archive_name_format': 'bar-{now}', # noqa: FS003
  73. 'keep_daily': 1,
  74. 'prefix': None,
  75. }
  76. flexmock(module.feature).should_receive('available').and_return(True)
  77. flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
  78. None, 'bar-{now}', '1.2.3' # noqa: FS003
  79. ).and_return(('--match-archives', 'sh:bar-*'))
  80. result = module.make_prune_flags(config, local_borg_version='1.2.3')
  81. expected = (
  82. '--keep-daily',
  83. '1',
  84. '--match-archives',
  85. 'sh:bar-*', # noqa: FS003
  86. )
  87. assert result == expected
  88. def test_make_prune_flags_ignores_keep_exclude_tags_in_config():
  89. config = {
  90. 'keep_daily': 1,
  91. 'keep_exclude_tags': True,
  92. }
  93. flexmock(module.feature).should_receive('available').and_return(True)
  94. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  95. result = module.make_prune_flags(config, local_borg_version='1.2.3')
  96. assert result == ('--keep-daily', '1')
  97. PRUNE_COMMAND = ('borg', 'prune', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')
  98. def test_prune_archives_calls_borg_with_flags():
  99. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  100. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  101. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  102. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  103. insert_execute_command_mock(PRUNE_COMMAND + ('repo',), logging.INFO)
  104. prune_arguments = flexmock(stats=False, list_archives=False)
  105. module.prune_archives(
  106. dry_run=False,
  107. repository_path='repo',
  108. config={},
  109. local_borg_version='1.2.3',
  110. global_arguments=flexmock(log_json=False),
  111. prune_arguments=prune_arguments,
  112. )
  113. def test_prune_archives_with_log_info_calls_borg_with_info_flag():
  114. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  115. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  116. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  117. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  118. insert_execute_command_mock(PRUNE_COMMAND + ('--info', 'repo'), logging.INFO)
  119. insert_logging_mock(logging.INFO)
  120. prune_arguments = flexmock(stats=False, list_archives=False)
  121. module.prune_archives(
  122. repository_path='repo',
  123. config={},
  124. dry_run=False,
  125. local_borg_version='1.2.3',
  126. global_arguments=flexmock(log_json=False),
  127. prune_arguments=prune_arguments,
  128. )
  129. def test_prune_archives_with_log_debug_calls_borg_with_debug_flag():
  130. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  131. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  132. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  133. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  134. insert_execute_command_mock(PRUNE_COMMAND + ('--debug', '--show-rc', 'repo'), logging.INFO)
  135. insert_logging_mock(logging.DEBUG)
  136. prune_arguments = flexmock(stats=False, list_archives=False)
  137. module.prune_archives(
  138. repository_path='repo',
  139. config={},
  140. dry_run=False,
  141. local_borg_version='1.2.3',
  142. global_arguments=flexmock(log_json=False),
  143. prune_arguments=prune_arguments,
  144. )
  145. def test_prune_archives_with_dry_run_calls_borg_with_dry_run_flag():
  146. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  147. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  148. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  149. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  150. insert_execute_command_mock(PRUNE_COMMAND + ('--dry-run', 'repo'), logging.INFO)
  151. prune_arguments = flexmock(stats=False, list_archives=False)
  152. module.prune_archives(
  153. repository_path='repo',
  154. config={},
  155. dry_run=True,
  156. local_borg_version='1.2.3',
  157. global_arguments=flexmock(log_json=False),
  158. prune_arguments=prune_arguments,
  159. )
  160. def test_prune_archives_with_local_path_calls_borg_via_local_path():
  161. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  162. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  163. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  164. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  165. insert_execute_command_mock(('borg1',) + PRUNE_COMMAND[1:] + ('repo',), logging.INFO)
  166. prune_arguments = flexmock(stats=False, list_archives=False)
  167. module.prune_archives(
  168. dry_run=False,
  169. repository_path='repo',
  170. config={},
  171. local_borg_version='1.2.3',
  172. global_arguments=flexmock(log_json=False),
  173. local_path='borg1',
  174. prune_arguments=prune_arguments,
  175. )
  176. def test_prune_archives_with_remote_path_calls_borg_with_remote_path_flags():
  177. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  178. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  179. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  180. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  181. insert_execute_command_mock(PRUNE_COMMAND + ('--remote-path', 'borg1', 'repo'), logging.INFO)
  182. prune_arguments = flexmock(stats=False, list_archives=False)
  183. module.prune_archives(
  184. dry_run=False,
  185. repository_path='repo',
  186. config={},
  187. local_borg_version='1.2.3',
  188. global_arguments=flexmock(log_json=False),
  189. remote_path='borg1',
  190. prune_arguments=prune_arguments,
  191. )
  192. def test_prune_archives_with_stats_calls_borg_with_stats_flag_and_answer_output_log_level():
  193. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  194. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  195. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  196. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  197. insert_execute_command_mock(PRUNE_COMMAND + ('--stats', 'repo'), module.borgmatic.logger.ANSWER)
  198. prune_arguments = flexmock(stats=True, list_archives=False)
  199. module.prune_archives(
  200. dry_run=False,
  201. repository_path='repo',
  202. config={},
  203. local_borg_version='1.2.3',
  204. global_arguments=flexmock(log_json=False),
  205. prune_arguments=prune_arguments,
  206. )
  207. def test_prune_archives_with_files_calls_borg_with_list_flag_and_answer_output_log_level():
  208. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  209. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  210. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  211. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  212. insert_execute_command_mock(PRUNE_COMMAND + ('--list', 'repo'), module.borgmatic.logger.ANSWER)
  213. prune_arguments = flexmock(stats=False, list_archives=True)
  214. module.prune_archives(
  215. dry_run=False,
  216. repository_path='repo',
  217. config={},
  218. local_borg_version='1.2.3',
  219. global_arguments=flexmock(log_json=False),
  220. prune_arguments=prune_arguments,
  221. )
  222. def test_prune_archives_with_umask_calls_borg_with_umask_flags():
  223. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  224. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  225. config = {'umask': '077'}
  226. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  227. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  228. insert_execute_command_mock(PRUNE_COMMAND + ('--umask', '077', 'repo'), logging.INFO)
  229. prune_arguments = flexmock(stats=False, list_archives=False)
  230. module.prune_archives(
  231. dry_run=False,
  232. repository_path='repo',
  233. config=config,
  234. local_borg_version='1.2.3',
  235. global_arguments=flexmock(log_json=False),
  236. prune_arguments=prune_arguments,
  237. )
  238. def test_prune_archives_with_log_json_calls_borg_with_log_json_flag():
  239. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  240. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  241. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  242. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  243. insert_execute_command_mock(PRUNE_COMMAND + ('--log-json', 'repo'), logging.INFO)
  244. prune_arguments = flexmock(stats=False, list_archives=False)
  245. module.prune_archives(
  246. dry_run=False,
  247. repository_path='repo',
  248. config={},
  249. local_borg_version='1.2.3',
  250. global_arguments=flexmock(log_json=True),
  251. prune_arguments=prune_arguments,
  252. )
  253. def test_prune_archives_with_lock_wait_calls_borg_with_lock_wait_flags():
  254. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  255. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  256. config = {'lock_wait': 5}
  257. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  258. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  259. insert_execute_command_mock(PRUNE_COMMAND + ('--lock-wait', '5', 'repo'), logging.INFO)
  260. prune_arguments = flexmock(stats=False, list_archives=False)
  261. module.prune_archives(
  262. dry_run=False,
  263. repository_path='repo',
  264. config=config,
  265. local_borg_version='1.2.3',
  266. global_arguments=flexmock(log_json=False),
  267. prune_arguments=prune_arguments,
  268. )
  269. def test_prune_archives_with_extra_borg_options_calls_borg_with_extra_options():
  270. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  271. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  272. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  273. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  274. insert_execute_command_mock(PRUNE_COMMAND + ('--extra', '--options', 'repo'), logging.INFO)
  275. prune_arguments = flexmock(stats=False, list_archives=False)
  276. module.prune_archives(
  277. dry_run=False,
  278. repository_path='repo',
  279. config={'extra_borg_options': {'prune': '--extra --options'}},
  280. local_borg_version='1.2.3',
  281. global_arguments=flexmock(log_json=False),
  282. prune_arguments=prune_arguments,
  283. )
  284. def test_prune_archives_with_date_based_matching_calls_borg_with_date_based_flags():
  285. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  286. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  287. flexmock(module.flags).should_receive('make_flags').and_return(())
  288. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  289. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  290. flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
  291. (
  292. '--newer',
  293. '1d',
  294. '--newest',
  295. '1y',
  296. '--older',
  297. '1m',
  298. '--oldest',
  299. '1w',
  300. '--match-archives',
  301. None,
  302. )
  303. )
  304. flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
  305. flexmock(module.environment).should_receive('make_environment')
  306. flexmock(module).should_receive('execute_command').with_args(
  307. (
  308. 'borg',
  309. 'prune',
  310. '--keep-daily',
  311. '1',
  312. '--keep-weekly',
  313. '2',
  314. '--keep-monthly',
  315. '3',
  316. '--newer',
  317. '1d',
  318. '--newest',
  319. '1y',
  320. '--older',
  321. '1m',
  322. '--oldest',
  323. '1w',
  324. '--match-archives',
  325. None,
  326. '--repo',
  327. 'repo',
  328. ),
  329. output_log_level=logging.INFO,
  330. borg_local_path='borg',
  331. extra_environment=None,
  332. )
  333. prune_arguments = flexmock(
  334. stats=False, list_archives=False, newer='1d', newest='1y', older='1m', oldest='1w'
  335. )
  336. module.prune_archives(
  337. dry_run=False,
  338. repository_path='repo',
  339. config={},
  340. local_borg_version='1.2.3',
  341. global_arguments=flexmock(log_json=False),
  342. prune_arguments=prune_arguments,
  343. )