test_prune.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  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. PRUNE_COMMAND = ('borg', 'prune', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')
  89. def test_prune_archives_calls_borg_with_flags():
  90. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  91. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  92. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  93. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  94. insert_execute_command_mock(PRUNE_COMMAND + ('repo',), logging.INFO)
  95. prune_arguments = flexmock(stats=False, list_archives=False)
  96. module.prune_archives(
  97. dry_run=False,
  98. repository_path='repo',
  99. config={},
  100. local_borg_version='1.2.3',
  101. global_arguments=flexmock(log_json=False),
  102. prune_arguments=prune_arguments,
  103. )
  104. def test_prune_archives_with_log_info_calls_borg_with_info_flag():
  105. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  106. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  107. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  108. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  109. insert_execute_command_mock(PRUNE_COMMAND + ('--info', 'repo'), logging.INFO)
  110. insert_logging_mock(logging.INFO)
  111. prune_arguments = flexmock(stats=False, list_archives=False)
  112. module.prune_archives(
  113. repository_path='repo',
  114. config={},
  115. dry_run=False,
  116. local_borg_version='1.2.3',
  117. global_arguments=flexmock(log_json=False),
  118. prune_arguments=prune_arguments,
  119. )
  120. def test_prune_archives_with_log_debug_calls_borg_with_debug_flag():
  121. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  122. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  123. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  124. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  125. insert_execute_command_mock(PRUNE_COMMAND + ('--debug', '--show-rc', 'repo'), logging.INFO)
  126. insert_logging_mock(logging.DEBUG)
  127. prune_arguments = flexmock(stats=False, list_archives=False)
  128. module.prune_archives(
  129. repository_path='repo',
  130. config={},
  131. dry_run=False,
  132. local_borg_version='1.2.3',
  133. global_arguments=flexmock(log_json=False),
  134. prune_arguments=prune_arguments,
  135. )
  136. def test_prune_archives_with_dry_run_calls_borg_with_dry_run_flag():
  137. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  138. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  139. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  140. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  141. insert_execute_command_mock(PRUNE_COMMAND + ('--dry-run', 'repo'), logging.INFO)
  142. prune_arguments = flexmock(stats=False, list_archives=False)
  143. module.prune_archives(
  144. repository_path='repo',
  145. config={},
  146. dry_run=True,
  147. local_borg_version='1.2.3',
  148. global_arguments=flexmock(log_json=False),
  149. prune_arguments=prune_arguments,
  150. )
  151. def test_prune_archives_with_local_path_calls_borg_via_local_path():
  152. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  153. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  154. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  155. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  156. insert_execute_command_mock(('borg1',) + PRUNE_COMMAND[1:] + ('repo',), logging.INFO)
  157. prune_arguments = flexmock(stats=False, list_archives=False)
  158. module.prune_archives(
  159. dry_run=False,
  160. repository_path='repo',
  161. config={},
  162. local_borg_version='1.2.3',
  163. global_arguments=flexmock(log_json=False),
  164. local_path='borg1',
  165. prune_arguments=prune_arguments,
  166. )
  167. def test_prune_archives_with_remote_path_calls_borg_with_remote_path_flags():
  168. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  169. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  170. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  171. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  172. insert_execute_command_mock(PRUNE_COMMAND + ('--remote-path', 'borg1', 'repo'), logging.INFO)
  173. prune_arguments = flexmock(stats=False, list_archives=False)
  174. module.prune_archives(
  175. dry_run=False,
  176. repository_path='repo',
  177. config={},
  178. local_borg_version='1.2.3',
  179. global_arguments=flexmock(log_json=False),
  180. remote_path='borg1',
  181. prune_arguments=prune_arguments,
  182. )
  183. def test_prune_archives_with_stats_calls_borg_with_stats_flag_and_answer_output_log_level():
  184. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  185. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  186. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  187. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  188. insert_execute_command_mock(PRUNE_COMMAND + ('--stats', 'repo'), module.borgmatic.logger.ANSWER)
  189. prune_arguments = flexmock(stats=True, list_archives=False)
  190. module.prune_archives(
  191. dry_run=False,
  192. repository_path='repo',
  193. config={},
  194. local_borg_version='1.2.3',
  195. global_arguments=flexmock(log_json=False),
  196. prune_arguments=prune_arguments,
  197. )
  198. def test_prune_archives_with_files_calls_borg_with_list_flag_and_answer_output_log_level():
  199. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  200. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  201. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  202. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  203. insert_execute_command_mock(PRUNE_COMMAND + ('--list', 'repo'), module.borgmatic.logger.ANSWER)
  204. prune_arguments = flexmock(stats=False, list_archives=True)
  205. module.prune_archives(
  206. dry_run=False,
  207. repository_path='repo',
  208. config={},
  209. local_borg_version='1.2.3',
  210. global_arguments=flexmock(log_json=False),
  211. prune_arguments=prune_arguments,
  212. )
  213. def test_prune_archives_with_umask_calls_borg_with_umask_flags():
  214. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  215. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  216. config = {'umask': '077'}
  217. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  218. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  219. insert_execute_command_mock(PRUNE_COMMAND + ('--umask', '077', 'repo'), logging.INFO)
  220. prune_arguments = flexmock(stats=False, list_archives=False)
  221. module.prune_archives(
  222. dry_run=False,
  223. repository_path='repo',
  224. config=config,
  225. local_borg_version='1.2.3',
  226. global_arguments=flexmock(log_json=False),
  227. prune_arguments=prune_arguments,
  228. )
  229. def test_prune_archives_with_log_json_calls_borg_with_log_json_flag():
  230. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  231. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  232. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  233. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  234. insert_execute_command_mock(PRUNE_COMMAND + ('--log-json', 'repo'), logging.INFO)
  235. prune_arguments = flexmock(stats=False, list_archives=False)
  236. module.prune_archives(
  237. dry_run=False,
  238. repository_path='repo',
  239. config={},
  240. local_borg_version='1.2.3',
  241. global_arguments=flexmock(log_json=True),
  242. prune_arguments=prune_arguments,
  243. )
  244. def test_prune_archives_with_lock_wait_calls_borg_with_lock_wait_flags():
  245. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  246. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  247. config = {'lock_wait': 5}
  248. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  249. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  250. insert_execute_command_mock(PRUNE_COMMAND + ('--lock-wait', '5', 'repo'), logging.INFO)
  251. prune_arguments = flexmock(stats=False, list_archives=False)
  252. module.prune_archives(
  253. dry_run=False,
  254. repository_path='repo',
  255. config=config,
  256. local_borg_version='1.2.3',
  257. global_arguments=flexmock(log_json=False),
  258. prune_arguments=prune_arguments,
  259. )
  260. def test_prune_archives_with_extra_borg_options_calls_borg_with_extra_options():
  261. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  262. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  263. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  264. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  265. insert_execute_command_mock(PRUNE_COMMAND + ('--extra', '--options', 'repo'), logging.INFO)
  266. prune_arguments = flexmock(stats=False, list_archives=False)
  267. module.prune_archives(
  268. dry_run=False,
  269. repository_path='repo',
  270. config={'extra_borg_options': {'prune': '--extra --options'}},
  271. local_borg_version='1.2.3',
  272. global_arguments=flexmock(log_json=False),
  273. prune_arguments=prune_arguments,
  274. )
  275. def test_prune_archives_with_date_based_matching_calls_borg_with_date_based_flags():
  276. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  277. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  278. flexmock(module.flags).should_receive('make_flags').and_return(())
  279. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  280. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  281. flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
  282. (
  283. '--newer',
  284. '1d',
  285. '--newest',
  286. '1y',
  287. '--older',
  288. '1m',
  289. '--oldest',
  290. '1w',
  291. '--match-archives',
  292. None,
  293. )
  294. )
  295. flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
  296. flexmock(module.environment).should_receive('make_environment')
  297. flexmock(module).should_receive('execute_command').with_args(
  298. (
  299. 'borg',
  300. 'prune',
  301. '--keep-daily',
  302. '1',
  303. '--keep-weekly',
  304. '2',
  305. '--keep-monthly',
  306. '3',
  307. '--newer',
  308. '1d',
  309. '--newest',
  310. '1y',
  311. '--older',
  312. '1m',
  313. '--oldest',
  314. '1w',
  315. '--match-archives',
  316. None,
  317. '--repo',
  318. 'repo',
  319. ),
  320. output_log_level=logging.INFO,
  321. borg_local_path='borg',
  322. extra_environment=None,
  323. )
  324. prune_arguments = flexmock(
  325. stats=False, list_archives=False, newer='1d', newest='1y', older='1m', oldest='1w'
  326. )
  327. module.prune_archives(
  328. dry_run=False,
  329. repository_path='repo',
  330. config={},
  331. local_borg_version='1.2.3',
  332. global_arguments=flexmock(log_json=False),
  333. prune_arguments=prune_arguments,
  334. )