test_borg.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. from collections import OrderedDict
  2. from subprocess import STDOUT
  3. import os
  4. from flexmock import flexmock
  5. from borgmatic import borg as module
  6. from borgmatic.tests.builtins import builtins_mock
  7. from borgmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS
  8. def test_initialize_with_passphrase_should_set_environment():
  9. orig_environ = os.environ
  10. try:
  11. os.environ = {}
  12. module.initialize({'encryption_passphrase': 'pass'}, command='borg')
  13. assert os.environ.get('BORG_PASSPHRASE') == 'pass'
  14. finally:
  15. os.environ = orig_environ
  16. def test_initialize_without_passphrase_should_not_set_environment():
  17. orig_environ = os.environ
  18. try:
  19. os.environ = {}
  20. module.initialize({}, command='borg')
  21. assert os.environ.get('BORG_PASSPHRASE') == None
  22. finally:
  23. os.environ = orig_environ
  24. def insert_subprocess_mock(check_call_command, **kwargs):
  25. subprocess = flexmock(STDOUT=STDOUT)
  26. subprocess.should_receive('check_call').with_args(check_call_command, **kwargs).once()
  27. flexmock(module).subprocess = subprocess
  28. def insert_subprocess_never():
  29. subprocess = flexmock()
  30. subprocess.should_receive('check_call').never()
  31. flexmock(module).subprocess = subprocess
  32. def insert_platform_mock():
  33. flexmock(module.platform).should_receive('node').and_return('host')
  34. def insert_datetime_mock():
  35. flexmock(module).datetime = flexmock().should_receive('now').and_return(
  36. flexmock().should_receive('isoformat').and_return('now').mock
  37. ).mock
  38. CREATE_COMMAND_WITHOUT_EXCLUDES = ('borg', 'create', 'repo::host-now', 'foo', 'bar')
  39. CREATE_COMMAND = CREATE_COMMAND_WITHOUT_EXCLUDES + ('--exclude-from', 'excludes')
  40. def test_create_archive_should_call_borg_with_parameters():
  41. insert_subprocess_mock(CREATE_COMMAND)
  42. insert_platform_mock()
  43. insert_datetime_mock()
  44. module.create_archive(
  45. excludes_filename='excludes',
  46. verbosity=None,
  47. storage_config={},
  48. source_directories='foo bar',
  49. repository='repo',
  50. command='borg',
  51. )
  52. def test_create_archive_with_two_spaces_in_source_directories():
  53. insert_subprocess_mock(CREATE_COMMAND)
  54. insert_platform_mock()
  55. insert_datetime_mock()
  56. module.create_archive(
  57. excludes_filename='excludes',
  58. verbosity=None,
  59. storage_config={},
  60. source_directories='foo bar',
  61. repository='repo',
  62. command='borg',
  63. )
  64. def test_create_archive_with_none_excludes_filename_should_call_borg_without_excludes():
  65. insert_subprocess_mock(CREATE_COMMAND_WITHOUT_EXCLUDES)
  66. insert_platform_mock()
  67. insert_datetime_mock()
  68. module.create_archive(
  69. excludes_filename=None,
  70. verbosity=None,
  71. storage_config={},
  72. source_directories='foo bar',
  73. repository='repo',
  74. command='borg',
  75. )
  76. def test_create_archive_with_verbosity_some_should_call_borg_with_stats_parameter():
  77. insert_subprocess_mock(CREATE_COMMAND + ('--stats',))
  78. insert_platform_mock()
  79. insert_datetime_mock()
  80. module.create_archive(
  81. excludes_filename='excludes',
  82. verbosity=VERBOSITY_SOME,
  83. storage_config={},
  84. source_directories='foo bar',
  85. repository='repo',
  86. command='borg',
  87. )
  88. def test_create_archive_with_verbosity_lots_should_call_borg_with_verbose_parameter():
  89. insert_subprocess_mock(CREATE_COMMAND + ('--verbose', '--stats'))
  90. insert_platform_mock()
  91. insert_datetime_mock()
  92. module.create_archive(
  93. excludes_filename='excludes',
  94. verbosity=VERBOSITY_LOTS,
  95. storage_config={},
  96. source_directories='foo bar',
  97. repository='repo',
  98. command='borg',
  99. )
  100. def test_create_archive_with_compression_should_call_borg_with_compression_parameters():
  101. insert_subprocess_mock(CREATE_COMMAND + ('--compression', 'rle'))
  102. insert_platform_mock()
  103. insert_datetime_mock()
  104. module.create_archive(
  105. excludes_filename='excludes',
  106. verbosity=None,
  107. storage_config={'compression': 'rle'},
  108. source_directories='foo bar',
  109. repository='repo',
  110. command='borg',
  111. )
  112. def test_create_archive_with_one_file_system_should_call_borg_with_one_file_system_parameters():
  113. insert_subprocess_mock(CREATE_COMMAND + ('--one-file-system',))
  114. insert_platform_mock()
  115. insert_datetime_mock()
  116. module.create_archive(
  117. excludes_filename='excludes',
  118. verbosity=None,
  119. storage_config={},
  120. source_directories='foo bar',
  121. repository='repo',
  122. command='borg',
  123. one_file_system=True,
  124. )
  125. def test_create_archive_with_remote_path_should_call_borg_with_remote_path_parameters():
  126. insert_subprocess_mock(CREATE_COMMAND + ('--remote-path', 'borg1'))
  127. insert_platform_mock()
  128. insert_datetime_mock()
  129. module.create_archive(
  130. excludes_filename='excludes',
  131. verbosity=None,
  132. storage_config={},
  133. source_directories='foo bar',
  134. repository='repo',
  135. command='borg',
  136. remote_path='borg1',
  137. )
  138. def test_create_archive_with_umask_should_call_borg_with_umask_parameters():
  139. insert_subprocess_mock(CREATE_COMMAND + ('--umask', '740'))
  140. insert_platform_mock()
  141. insert_datetime_mock()
  142. module.create_archive(
  143. excludes_filename='excludes',
  144. verbosity=None,
  145. storage_config={'umask': 740},
  146. source_directories='foo bar',
  147. repository='repo',
  148. command='borg',
  149. )
  150. def test_create_archive_with_source_directories_glob_expands():
  151. insert_subprocess_mock(('borg', 'create', 'repo::host-now', 'foo', 'food'))
  152. insert_platform_mock()
  153. insert_datetime_mock()
  154. flexmock(module).should_receive('glob').with_args('foo*').and_return(['foo', 'food'])
  155. module.create_archive(
  156. excludes_filename=None,
  157. verbosity=None,
  158. storage_config={},
  159. source_directories='foo*',
  160. repository='repo',
  161. command='borg',
  162. )
  163. def test_create_archive_with_non_matching_source_directories_glob_passes_through():
  164. insert_subprocess_mock(('borg', 'create', 'repo::host-now', 'foo*'))
  165. insert_platform_mock()
  166. insert_datetime_mock()
  167. flexmock(module).should_receive('glob').with_args('foo*').and_return([])
  168. module.create_archive(
  169. excludes_filename=None,
  170. verbosity=None,
  171. storage_config={},
  172. source_directories='foo*',
  173. repository='repo',
  174. command='borg',
  175. )
  176. def test_create_archive_with_glob_should_call_borg_with_expanded_directories():
  177. insert_subprocess_mock(('borg', 'create', 'repo::host-now', 'foo', 'food'))
  178. insert_platform_mock()
  179. insert_datetime_mock()
  180. flexmock(module).should_receive('glob').with_args('foo*').and_return(['foo', 'food'])
  181. module.create_archive(
  182. excludes_filename=None,
  183. verbosity=None,
  184. storage_config={},
  185. source_directories='foo*',
  186. repository='repo',
  187. command='borg',
  188. )
  189. BASE_PRUNE_FLAGS = (
  190. ('--keep-daily', '1'),
  191. ('--keep-weekly', '2'),
  192. ('--keep-monthly', '3'),
  193. )
  194. def test_make_prune_flags_should_return_flags_from_config():
  195. retention_config = OrderedDict(
  196. (
  197. ('keep_daily', 1),
  198. ('keep_weekly', 2),
  199. ('keep_monthly', 3),
  200. )
  201. )
  202. result = module._make_prune_flags(retention_config)
  203. assert tuple(result) == BASE_PRUNE_FLAGS
  204. PRUNE_COMMAND = (
  205. 'borg', 'prune', 'repo', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3',
  206. )
  207. def test_prune_archives_should_call_borg_with_parameters():
  208. retention_config = flexmock()
  209. flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
  210. BASE_PRUNE_FLAGS,
  211. )
  212. insert_subprocess_mock(PRUNE_COMMAND)
  213. module.prune_archives(
  214. verbosity=None,
  215. repository='repo',
  216. retention_config=retention_config,
  217. command='borg',
  218. )
  219. def test_prune_archives_with_verbosity_some_should_call_borg_with_stats_parameter():
  220. retention_config = flexmock()
  221. flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
  222. BASE_PRUNE_FLAGS,
  223. )
  224. insert_subprocess_mock(PRUNE_COMMAND + ('--stats',))
  225. module.prune_archives(
  226. repository='repo',
  227. verbosity=VERBOSITY_SOME,
  228. retention_config=retention_config,
  229. command='borg',
  230. )
  231. def test_prune_archives_with_verbosity_lots_should_call_borg_with_verbose_parameter():
  232. retention_config = flexmock()
  233. flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
  234. BASE_PRUNE_FLAGS,
  235. )
  236. insert_subprocess_mock(PRUNE_COMMAND + ('--verbose', '--stats',))
  237. module.prune_archives(
  238. repository='repo',
  239. verbosity=VERBOSITY_LOTS,
  240. retention_config=retention_config,
  241. command='borg',
  242. )
  243. def test_prune_archive_with_remote_path_should_call_borg_with_remote_path_parameters():
  244. retention_config = flexmock()
  245. flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
  246. BASE_PRUNE_FLAGS,
  247. )
  248. insert_subprocess_mock(PRUNE_COMMAND + ('--remote-path', 'borg1'))
  249. module.prune_archives(
  250. verbosity=None,
  251. repository='repo',
  252. retention_config=retention_config,
  253. command='borg',
  254. remote_path='borg1',
  255. )
  256. def test_parse_checks_returns_them_as_tuple():
  257. checks = module._parse_checks({'checks': 'foo disabled bar'})
  258. assert checks == ('foo', 'bar')
  259. def test_parse_checks_with_missing_value_returns_defaults():
  260. checks = module._parse_checks({})
  261. assert checks == module.DEFAULT_CHECKS
  262. def test_parse_checks_with_blank_value_returns_defaults():
  263. checks = module._parse_checks({'checks': ''})
  264. assert checks == module.DEFAULT_CHECKS
  265. def test_parse_checks_with_disabled_returns_no_checks():
  266. checks = module._parse_checks({'checks': 'disabled'})
  267. assert checks == ()
  268. def test_make_check_flags_with_checks_returns_flags():
  269. flags = module._make_check_flags(('foo', 'bar'))
  270. assert flags == ('--foo-only', '--bar-only')
  271. def test_make_check_flags_with_default_checks_returns_no_flags():
  272. flags = module._make_check_flags(module.DEFAULT_CHECKS)
  273. assert flags == ()
  274. def test_make_check_flags_with_checks_and_last_returns_flags_including_last():
  275. flags = module._make_check_flags(('foo', 'bar'), check_last=3)
  276. assert flags == ('--foo-only', '--bar-only', '--last', 3)
  277. def test_make_check_flags_with_last_returns_last_flag():
  278. flags = module._make_check_flags(module.DEFAULT_CHECKS, check_last=3)
  279. assert flags == ('--last', 3)
  280. def test_check_archives_should_call_borg_with_parameters():
  281. checks = flexmock()
  282. check_last = flexmock()
  283. consistency_config = flexmock().should_receive('get').and_return(check_last).mock
  284. flexmock(module).should_receive('_parse_checks').and_return(checks)
  285. flexmock(module).should_receive('_make_check_flags').with_args(checks, check_last).and_return(())
  286. stdout = flexmock()
  287. insert_subprocess_mock(
  288. ('borg', 'check', 'repo'),
  289. stdout=stdout, stderr=STDOUT,
  290. )
  291. insert_platform_mock()
  292. insert_datetime_mock()
  293. builtins_mock().should_receive('open').and_return(stdout)
  294. flexmock(module.os).should_receive('devnull')
  295. module.check_archives(
  296. verbosity=None,
  297. repository='repo',
  298. consistency_config=consistency_config,
  299. command='borg',
  300. )
  301. def test_check_archives_with_verbosity_some_should_call_borg_with_verbose_parameter():
  302. consistency_config = flexmock().should_receive('get').and_return(None).mock
  303. flexmock(module).should_receive('_parse_checks').and_return(flexmock())
  304. flexmock(module).should_receive('_make_check_flags').and_return(())
  305. insert_subprocess_mock(
  306. ('borg', 'check', 'repo', '--verbose'),
  307. stdout=None, stderr=STDOUT,
  308. )
  309. insert_platform_mock()
  310. insert_datetime_mock()
  311. module.check_archives(
  312. verbosity=VERBOSITY_SOME,
  313. repository='repo',
  314. consistency_config=consistency_config,
  315. command='borg',
  316. )
  317. def test_check_archives_with_verbosity_lots_should_call_borg_with_verbose_parameter():
  318. consistency_config = flexmock().should_receive('get').and_return(None).mock
  319. flexmock(module).should_receive('_parse_checks').and_return(flexmock())
  320. flexmock(module).should_receive('_make_check_flags').and_return(())
  321. insert_subprocess_mock(
  322. ('borg', 'check', 'repo', '--verbose'),
  323. stdout=None, stderr=STDOUT,
  324. )
  325. insert_platform_mock()
  326. insert_datetime_mock()
  327. module.check_archives(
  328. verbosity=VERBOSITY_LOTS,
  329. repository='repo',
  330. consistency_config=consistency_config,
  331. command='borg',
  332. )
  333. def test_check_archives_without_any_checks_should_bail():
  334. consistency_config = flexmock().should_receive('get').and_return(None).mock
  335. flexmock(module).should_receive('_parse_checks').and_return(())
  336. insert_subprocess_never()
  337. module.check_archives(
  338. verbosity=None,
  339. repository='repo',
  340. consistency_config=consistency_config,
  341. command='borg',
  342. )
  343. def test_check_archives_with_remote_path_should_call_borg_with_remote_path_parameters():
  344. checks = flexmock()
  345. check_last = flexmock()
  346. consistency_config = flexmock().should_receive('get').and_return(check_last).mock
  347. flexmock(module).should_receive('_parse_checks').and_return(checks)
  348. flexmock(module).should_receive('_make_check_flags').with_args(checks, check_last).and_return(())
  349. stdout = flexmock()
  350. insert_subprocess_mock(
  351. ('borg', 'check', 'repo', '--remote-path', 'borg1'),
  352. stdout=stdout, stderr=STDOUT,
  353. )
  354. insert_platform_mock()
  355. insert_datetime_mock()
  356. builtins_mock().should_receive('open').and_return(stdout)
  357. flexmock(module.os).should_receive('devnull')
  358. module.check_archives(
  359. verbosity=None,
  360. repository='repo',
  361. consistency_config=consistency_config,
  362. command='borg',
  363. remote_path='borg1',
  364. )