test_create.py 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549
  1. import logging
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic.borg import create as module
  5. from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Pattern_type
  6. from ..test_verbosity import insert_logging_mock
  7. @pytest.mark.parametrize(
  8. 'character_device,block_device,fifo,expected_result',
  9. (
  10. (False, False, False, False),
  11. (True, False, False, True),
  12. (False, True, False, True),
  13. (True, True, False, True),
  14. (False, False, True, True),
  15. (False, True, True, True),
  16. (True, False, True, True),
  17. ),
  18. )
  19. def test_special_file_looks_at_file_type(character_device, block_device, fifo, expected_result):
  20. flexmock(module.os).should_receive('stat').and_return(flexmock(st_mode=flexmock()))
  21. flexmock(module.stat).should_receive('S_ISCHR').and_return(character_device)
  22. flexmock(module.stat).should_receive('S_ISBLK').and_return(block_device)
  23. flexmock(module.stat).should_receive('S_ISFIFO').and_return(fifo)
  24. assert module.special_file('/dev/special') == expected_result
  25. def test_special_file_treats_broken_symlink_as_non_special():
  26. flexmock(module.os).should_receive('stat').and_raise(FileNotFoundError)
  27. assert module.special_file('/broken/symlink') is False
  28. def test_special_file_prepends_relative_path_with_working_directory():
  29. flexmock(module.os).should_receive('stat').with_args('/working/dir/relative').and_return(
  30. flexmock(st_mode=flexmock()),
  31. )
  32. flexmock(module.stat).should_receive('S_ISCHR').and_return(False)
  33. flexmock(module.stat).should_receive('S_ISBLK').and_return(False)
  34. flexmock(module.stat).should_receive('S_ISFIFO').and_return(False)
  35. assert module.special_file('relative', '/working/dir') is False
  36. def test_any_parent_directories_treats_parents_as_match():
  37. module.any_parent_directories('/foo/bar.txt', ('/foo', '/etc'))
  38. def test_any_parent_directories_treats_grandparents_as_match():
  39. module.any_parent_directories('/foo/bar/baz.txt', ('/foo', '/etc'))
  40. def test_any_parent_directories_treats_unrelated_paths_as_non_match():
  41. module.any_parent_directories('/foo/bar.txt', ('/usr', '/etc'))
  42. def test_collect_special_file_paths_parses_special_files_from_borg_dry_run_file_list():
  43. flexmock(module.flags).should_receive('omit_flag').replace_with(
  44. lambda arguments, flag: arguments,
  45. )
  46. flexmock(module.flags).should_receive('omit_flag_and_value').replace_with(
  47. lambda arguments, flag: arguments,
  48. )
  49. flexmock(module.environment).should_receive('make_environment').and_return(None)
  50. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  51. 'Processing files ...\n- /foo\n+ /bar\n- /baz',
  52. )
  53. flexmock(module).should_receive('special_file').and_return(True)
  54. flexmock(module.os.path).should_receive('exists').and_return(False)
  55. flexmock(module).should_receive('any_parent_directories').never()
  56. assert module.collect_special_file_paths(
  57. dry_run=False,
  58. create_command=('borg', 'create'),
  59. config={},
  60. local_path=None,
  61. working_directory=None,
  62. borgmatic_runtime_directory='/run/borgmatic',
  63. ) == ('/foo', '/bar', '/baz')
  64. def test_collect_special_file_paths_skips_borgmatic_runtime_directory():
  65. flexmock(module.flags).should_receive('omit_flag').replace_with(
  66. lambda arguments, flag: arguments,
  67. )
  68. flexmock(module.flags).should_receive('omit_flag_and_value').replace_with(
  69. lambda arguments, flag: arguments,
  70. )
  71. flexmock(module.environment).should_receive('make_environment').and_return(None)
  72. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  73. '+ /foo\n- /run/borgmatic/bar\n- /baz',
  74. )
  75. flexmock(module).should_receive('special_file').and_return(True)
  76. flexmock(module.os.path).should_receive('exists').and_return(True)
  77. flexmock(module).should_receive('any_parent_directories').with_args(
  78. '/foo',
  79. ('/run/borgmatic',),
  80. ).and_return(False)
  81. flexmock(module).should_receive('any_parent_directories').with_args(
  82. '/run/borgmatic/bar',
  83. ('/run/borgmatic',),
  84. ).and_return(True)
  85. flexmock(module).should_receive('any_parent_directories').with_args(
  86. '/baz',
  87. ('/run/borgmatic',),
  88. ).and_return(False)
  89. assert module.collect_special_file_paths(
  90. dry_run=False,
  91. create_command=('borg', 'create'),
  92. config={},
  93. local_path=None,
  94. working_directory=None,
  95. borgmatic_runtime_directory='/run/borgmatic',
  96. ) == ('/foo', '/baz')
  97. def test_collect_special_file_paths_with_borgmatic_runtime_directory_missing_from_paths_output_errors():
  98. flexmock(module.flags).should_receive('omit_flag').replace_with(
  99. lambda arguments, flag: arguments,
  100. )
  101. flexmock(module.flags).should_receive('omit_flag_and_value').replace_with(
  102. lambda arguments, flag: arguments,
  103. )
  104. flexmock(module.environment).should_receive('make_environment').and_return(None)
  105. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  106. '+ /foo\n- /bar\n- /baz',
  107. )
  108. flexmock(module).should_receive('special_file').and_return(True)
  109. flexmock(module.os.path).should_receive('exists').and_return(True)
  110. flexmock(module).should_receive('any_parent_directories').and_return(False)
  111. with pytest.raises(ValueError):
  112. module.collect_special_file_paths(
  113. dry_run=False,
  114. create_command=('borg', 'create'),
  115. config={},
  116. local_path=None,
  117. working_directory=None,
  118. borgmatic_runtime_directory='/run/borgmatic',
  119. )
  120. def test_collect_special_file_paths_with_dry_run_and_borgmatic_runtime_directory_missing_from_paths_output_does_not_raise():
  121. flexmock(module.flags).should_receive('omit_flag').replace_with(
  122. lambda arguments, flag: arguments,
  123. )
  124. flexmock(module.flags).should_receive('omit_flag_and_value').replace_with(
  125. lambda arguments, flag: arguments,
  126. )
  127. flexmock(module.environment).should_receive('make_environment').and_return(None)
  128. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  129. '+ /foo\n- /bar\n- /baz',
  130. )
  131. flexmock(module).should_receive('special_file').and_return(True)
  132. flexmock(module.os.path).should_receive('exists').and_return(True)
  133. flexmock(module).should_receive('any_parent_directories').and_return(False)
  134. assert module.collect_special_file_paths(
  135. dry_run=True,
  136. create_command=('borg', 'create'),
  137. config={},
  138. local_path=None,
  139. working_directory=None,
  140. borgmatic_runtime_directory='/run/borgmatic',
  141. ) == ('/foo', '/bar', '/baz')
  142. def test_collect_special_file_paths_excludes_non_special_files():
  143. flexmock(module.flags).should_receive('omit_flag').replace_with(
  144. lambda arguments, flag: arguments,
  145. )
  146. flexmock(module.flags).should_receive('omit_flag_and_value').replace_with(
  147. lambda arguments, flag: arguments,
  148. )
  149. flexmock(module.environment).should_receive('make_environment').and_return(None)
  150. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  151. '+ /foo\n+ /bar\n+ /baz',
  152. )
  153. flexmock(module).should_receive('special_file').and_return(True).and_return(False).and_return(
  154. True,
  155. )
  156. flexmock(module.os.path).should_receive('exists').and_return(False)
  157. flexmock(module).should_receive('any_parent_directories').never()
  158. assert module.collect_special_file_paths(
  159. dry_run=False,
  160. create_command=('borg', 'create'),
  161. config={},
  162. local_path=None,
  163. working_directory=None,
  164. borgmatic_runtime_directory='/run/borgmatic',
  165. ) == ('/foo', '/baz')
  166. DEFAULT_ARCHIVE_NAME = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}'
  167. REPO_ARCHIVE = (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  168. def test_make_base_create_produces_borg_command():
  169. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  170. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  171. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  172. flexmock(module.feature).should_receive('available').and_return(True)
  173. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  174. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  175. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  176. )
  177. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  178. dry_run=False,
  179. repository_path='repo',
  180. config={
  181. 'source_directories': ['foo', 'bar'],
  182. 'repositories': ['repo'],
  183. },
  184. patterns=[Pattern('foo'), Pattern('bar')],
  185. local_borg_version='1.2.3',
  186. global_arguments=flexmock(),
  187. borgmatic_runtime_directory='/run/borgmatic',
  188. )
  189. assert create_flags == ('borg', 'create')
  190. assert create_positional_arguments == REPO_ARCHIVE
  191. assert not pattern_file
  192. def test_make_base_create_command_includes_patterns_file_in_borg_command():
  193. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  194. mock_pattern_file = flexmock(name='/tmp/patterns')
  195. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(
  196. mock_pattern_file,
  197. ).and_return(None)
  198. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  199. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  200. '{hostname}',
  201. )
  202. flexmock(module.feature).should_receive('available').and_return(True)
  203. pattern_flags = ('--patterns-from', mock_pattern_file.name)
  204. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  205. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  206. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  207. )
  208. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  209. dry_run=False,
  210. repository_path='repo',
  211. config={
  212. 'source_directories': ['foo', 'bar'],
  213. 'repositories': ['repo'],
  214. 'patterns': ['pattern'],
  215. },
  216. patterns=[Pattern('foo'), Pattern('bar')],
  217. local_borg_version='1.2.3',
  218. global_arguments=flexmock(),
  219. borgmatic_runtime_directory='/run/borgmatic',
  220. )
  221. assert create_flags == ('borg', 'create', *pattern_flags)
  222. assert create_positional_arguments == (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  223. assert pattern_file == mock_pattern_file
  224. def test_make_base_create_command_with_store_config_false_omits_config_files():
  225. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  226. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  227. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  228. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  229. '{hostname}',
  230. )
  231. flexmock(module.feature).should_receive('available').and_return(True)
  232. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  233. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  234. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  235. )
  236. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  237. dry_run=False,
  238. repository_path='repo',
  239. config={
  240. 'source_directories': ['foo', 'bar'],
  241. 'repositories': ['repo'],
  242. 'store_config_files': False,
  243. },
  244. patterns=[Pattern('foo'), Pattern('bar')],
  245. local_borg_version='1.2.3',
  246. global_arguments=flexmock(),
  247. borgmatic_runtime_directory='/run/borgmatic',
  248. )
  249. assert create_flags == ('borg', 'create')
  250. assert create_positional_arguments == REPO_ARCHIVE
  251. assert not pattern_file
  252. @pytest.mark.parametrize(
  253. 'option_name,option_value,feature_available,option_flags',
  254. (
  255. ('checkpoint_interval', 600, True, ('--checkpoint-interval', '600')),
  256. ('checkpoint_volume', 1024, True, ('--checkpoint-volume', '1024')),
  257. ('chunker_params', '1,2,3,4', True, ('--chunker-params', '1,2,3,4')),
  258. ('compression', 'rle', True, ('--compression', 'rle')),
  259. ('one_file_system', True, True, ('--one-file-system',)),
  260. ('upload_rate_limit', 100, True, ('--upload-ratelimit', '100')),
  261. ('upload_rate_limit', 100, False, ('--remote-ratelimit', '100')),
  262. ('upload_buffer_size', 160, True, ('--upload-buffer', '160')),
  263. ('numeric_ids', True, True, ('--numeric-ids',)),
  264. ('numeric_ids', True, False, ('--numeric-owner',)),
  265. ('read_special', True, True, ('--read-special',)),
  266. ('ctime', True, True, ()),
  267. ('ctime', False, True, ('--noctime',)),
  268. ('birthtime', True, True, ()),
  269. ('birthtime', False, True, ('--nobirthtime',)),
  270. ('atime', True, True, ('--atime',)),
  271. ('atime', True, False, ()),
  272. ('atime', False, True, ()),
  273. ('atime', False, False, ('--noatime',)),
  274. ('flags', True, True, ()),
  275. ('flags', True, False, ()),
  276. ('flags', False, True, ('--noflags',)),
  277. ('flags', False, False, ('--nobsdflags',)),
  278. ('files_cache', 'ctime,size', True, ('--files-cache', 'ctime,size')),
  279. ('umask', 740, True, ('--umask', '740')),
  280. ('lock_wait', 5, True, ('--lock-wait', '5')),
  281. ),
  282. )
  283. def test_make_base_create_command_includes_configuration_option_as_command_flag(
  284. option_name,
  285. option_value,
  286. feature_available,
  287. option_flags,
  288. ):
  289. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  290. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  291. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  292. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  293. '{hostname}',
  294. )
  295. flexmock(module.feature).should_receive('available').and_return(feature_available)
  296. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  297. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  298. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  299. )
  300. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  301. dry_run=False,
  302. repository_path='repo',
  303. config={
  304. 'source_directories': ['foo', 'bar'],
  305. 'repositories': ['repo'],
  306. option_name: option_value,
  307. },
  308. patterns=[Pattern('foo'), Pattern('bar')],
  309. local_borg_version='1.2.3',
  310. global_arguments=flexmock(),
  311. borgmatic_runtime_directory='/run/borgmatic',
  312. )
  313. assert create_flags == ('borg', 'create', *option_flags)
  314. assert create_positional_arguments == REPO_ARCHIVE
  315. assert not pattern_file
  316. def test_make_base_create_command_includes_dry_run_in_borg_command():
  317. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  318. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  319. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  320. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  321. '{hostname}',
  322. )
  323. flexmock(module.feature).should_receive('available').and_return(True)
  324. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  325. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  326. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  327. )
  328. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  329. dry_run=True,
  330. repository_path='repo',
  331. config={
  332. 'source_directories': ['foo', 'bar'],
  333. 'repositories': ['repo'],
  334. 'exclude_patterns': ['exclude'],
  335. },
  336. patterns=[Pattern('foo'), Pattern('bar')],
  337. local_borg_version='1.2.3',
  338. global_arguments=flexmock(),
  339. borgmatic_runtime_directory='/run/borgmatic',
  340. )
  341. assert create_flags == ('borg', 'create', '--dry-run')
  342. assert create_positional_arguments == REPO_ARCHIVE
  343. assert not pattern_file
  344. def test_make_base_create_command_includes_comment_in_borg_command():
  345. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  346. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  347. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  348. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  349. '{hostname}',
  350. )
  351. flexmock(module.feature).should_receive('available').and_return(True)
  352. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  353. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  354. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  355. )
  356. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  357. dry_run=False,
  358. repository_path='repo',
  359. config={
  360. 'source_directories': ['foo', 'bar'],
  361. 'repositories': ['repo'],
  362. 'exclude_patterns': ['exclude'],
  363. },
  364. patterns=[Pattern('foo'), Pattern('bar')],
  365. local_borg_version='1.2.3',
  366. global_arguments=flexmock(),
  367. borgmatic_runtime_directory='/run/borgmatic',
  368. comment='a comment',
  369. )
  370. assert create_flags == ('borg', 'create', '--comment', 'a comment')
  371. assert create_positional_arguments == REPO_ARCHIVE
  372. assert not pattern_file
  373. def test_make_base_create_command_includes_local_path_in_borg_command():
  374. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  375. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  376. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  377. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  378. '{hostname}',
  379. )
  380. flexmock(module.feature).should_receive('available').and_return(True)
  381. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  382. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  383. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  384. )
  385. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  386. dry_run=False,
  387. repository_path='repo',
  388. config={
  389. 'source_directories': ['foo', 'bar'],
  390. 'repositories': ['repo'],
  391. },
  392. patterns=[Pattern('foo'), Pattern('bar')],
  393. local_borg_version='1.2.3',
  394. global_arguments=flexmock(),
  395. borgmatic_runtime_directory='/run/borgmatic',
  396. local_path='borg1',
  397. )
  398. assert create_flags == ('borg1', 'create')
  399. assert create_positional_arguments == REPO_ARCHIVE
  400. assert not pattern_file
  401. def test_make_base_create_command_includes_remote_path_in_borg_command():
  402. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  403. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  404. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  405. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  406. '{hostname}',
  407. )
  408. flexmock(module.feature).should_receive('available').and_return(True)
  409. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  410. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  411. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  412. )
  413. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  414. dry_run=False,
  415. repository_path='repo',
  416. config={
  417. 'source_directories': ['foo', 'bar'],
  418. 'repositories': ['repo'],
  419. },
  420. patterns=[Pattern('foo'), Pattern('bar')],
  421. local_borg_version='1.2.3',
  422. global_arguments=flexmock(),
  423. borgmatic_runtime_directory='/run/borgmatic',
  424. remote_path='borg1',
  425. )
  426. assert create_flags == ('borg', 'create', '--remote-path', 'borg1')
  427. assert create_positional_arguments == REPO_ARCHIVE
  428. assert not pattern_file
  429. def test_make_base_create_command_includes_log_json_in_borg_command():
  430. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  431. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  432. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  433. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  434. '{hostname}',
  435. )
  436. flexmock(module.feature).should_receive('available').and_return(True)
  437. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  438. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  439. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  440. )
  441. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  442. dry_run=False,
  443. repository_path='repo',
  444. config={
  445. 'source_directories': ['foo', 'bar'],
  446. 'repositories': ['repo'],
  447. 'log_json': True,
  448. },
  449. patterns=[Pattern('foo'), Pattern('bar')],
  450. local_borg_version='1.2.3',
  451. global_arguments=flexmock(),
  452. borgmatic_runtime_directory='/run/borgmatic',
  453. )
  454. assert create_flags == ('borg', 'create', '--log-json')
  455. assert create_positional_arguments == REPO_ARCHIVE
  456. assert not pattern_file
  457. def test_make_base_create_command_includes_list_flags_in_borg_command():
  458. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  459. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  460. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  461. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  462. '{hostname}',
  463. )
  464. flexmock(module.feature).should_receive('available').and_return(True)
  465. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  466. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  467. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  468. )
  469. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  470. dry_run=False,
  471. repository_path='repo',
  472. config={
  473. 'source_directories': ['foo', 'bar'],
  474. 'repositories': ['repo'],
  475. 'list_details': True,
  476. },
  477. patterns=[Pattern('foo'), Pattern('bar')],
  478. local_borg_version='1.2.3',
  479. global_arguments=flexmock(),
  480. borgmatic_runtime_directory='/run/borgmatic',
  481. )
  482. assert create_flags == ('borg', 'create', '--list', '--filter', 'FOO')
  483. assert create_positional_arguments == REPO_ARCHIVE
  484. assert not pattern_file
  485. def test_make_base_create_command_with_stream_processes_ignores_read_special_false_and_excludes_special_files():
  486. patterns = [Pattern('foo'), Pattern('bar')]
  487. patterns_file = flexmock(name='patterns')
  488. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  489. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').with_args(
  490. patterns,
  491. '/run/borgmatic',
  492. ).and_return(patterns_file)
  493. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  494. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  495. '{hostname}',
  496. )
  497. flexmock(module.feature).should_receive('available').and_return(True)
  498. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  499. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  500. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  501. )
  502. flexmock(module.logger).should_receive('warning').twice()
  503. flexmock(module.environment).should_receive('make_environment')
  504. flexmock(module).should_receive('collect_special_file_paths').and_return(('/dev/null',)).once()
  505. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').with_args(
  506. (
  507. Pattern(
  508. '/dev/null',
  509. Pattern_type.NO_RECURSE,
  510. Pattern_style.FNMATCH,
  511. source=Pattern_source.INTERNAL,
  512. ),
  513. ),
  514. '/run/borgmatic',
  515. patterns_file=patterns_file,
  516. ).and_return(patterns_file).once()
  517. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  518. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  519. dry_run=False,
  520. repository_path='repo',
  521. config={
  522. 'source_directories': ['foo', 'bar'],
  523. 'repositories': ['repo'],
  524. 'read_special': False,
  525. },
  526. patterns=patterns,
  527. local_borg_version='1.2.3',
  528. global_arguments=flexmock(),
  529. borgmatic_runtime_directory='/run/borgmatic',
  530. stream_processes=flexmock(),
  531. )
  532. assert create_flags == ('borg', 'create', '--patterns-from', 'patterns', '--read-special')
  533. assert create_positional_arguments == REPO_ARCHIVE
  534. assert pattern_file
  535. def test_make_base_create_command_without_patterns_and_with_stream_processes_ignores_read_special_false_and_excludes_special_files():
  536. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  537. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').with_args(
  538. [],
  539. '/run/borgmatic',
  540. ).and_return(None)
  541. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  542. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  543. '{hostname}',
  544. )
  545. flexmock(module.feature).should_receive('available').and_return(True)
  546. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  547. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  548. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  549. )
  550. flexmock(module.logger).should_receive('warning').twice()
  551. flexmock(module.environment).should_receive('make_environment')
  552. flexmock(module).should_receive('collect_special_file_paths').and_return(('/dev/null',)).once()
  553. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').with_args(
  554. (
  555. Pattern(
  556. '/dev/null',
  557. Pattern_type.NO_RECURSE,
  558. Pattern_style.FNMATCH,
  559. source=Pattern_source.INTERNAL,
  560. ),
  561. ),
  562. '/run/borgmatic',
  563. patterns_file=None,
  564. ).and_return(flexmock(name='patterns')).once()
  565. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  566. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  567. dry_run=False,
  568. repository_path='repo',
  569. config={
  570. 'source_directories': [],
  571. 'repositories': ['repo'],
  572. 'read_special': False,
  573. },
  574. patterns=[],
  575. local_borg_version='1.2.3',
  576. global_arguments=flexmock(),
  577. borgmatic_runtime_directory='/run/borgmatic',
  578. stream_processes=flexmock(),
  579. )
  580. assert create_flags == ('borg', 'create', '--read-special', '--patterns-from', 'patterns')
  581. assert create_positional_arguments == REPO_ARCHIVE
  582. assert pattern_file
  583. def test_make_base_create_command_with_stream_processes_and_read_special_true_skips_special_files_excludes():
  584. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  585. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  586. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  587. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  588. '{hostname}',
  589. )
  590. flexmock(module.feature).should_receive('available').and_return(True)
  591. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  592. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  593. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  594. )
  595. flexmock(module.logger).should_receive('warning').never()
  596. flexmock(module).should_receive('collect_special_file_paths').never()
  597. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  598. dry_run=False,
  599. repository_path='repo',
  600. config={
  601. 'source_directories': ['foo', 'bar'],
  602. 'repositories': ['repo'],
  603. 'read_special': True,
  604. },
  605. patterns=[Pattern('foo'), Pattern('bar')],
  606. local_borg_version='1.2.3',
  607. global_arguments=flexmock(),
  608. borgmatic_runtime_directory='/run/borgmatic',
  609. stream_processes=flexmock(),
  610. )
  611. assert create_flags == ('borg', 'create', '--read-special')
  612. assert create_positional_arguments == REPO_ARCHIVE
  613. assert not pattern_file
  614. def test_make_base_create_command_includes_archive_name_format_in_borg_command():
  615. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  616. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  617. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  618. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  619. '{hostname}',
  620. )
  621. flexmock(module.feature).should_receive('available').and_return(True)
  622. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  623. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  624. ('repo::ARCHIVE_NAME',),
  625. )
  626. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  627. dry_run=False,
  628. repository_path='repo',
  629. config={
  630. 'source_directories': ['foo', 'bar'],
  631. 'repositories': ['repo'],
  632. 'archive_name_format': 'ARCHIVE_NAME',
  633. },
  634. patterns=[Pattern('foo'), Pattern('bar')],
  635. local_borg_version='1.2.3',
  636. global_arguments=flexmock(),
  637. borgmatic_runtime_directory='/run/borgmatic',
  638. )
  639. assert create_flags == ('borg', 'create')
  640. assert create_positional_arguments == ('repo::ARCHIVE_NAME',)
  641. assert not pattern_file
  642. def test_make_base_create_command_includes_default_archive_name_format_in_borg_command():
  643. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  644. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  645. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  646. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  647. '{hostname}',
  648. )
  649. flexmock(module.feature).should_receive('available').and_return(True)
  650. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  651. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  652. ('repo::{hostname}',),
  653. )
  654. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  655. dry_run=False,
  656. repository_path='repo',
  657. config={
  658. 'source_directories': ['foo', 'bar'],
  659. 'repositories': ['repo'],
  660. },
  661. patterns=[Pattern('foo'), Pattern('bar')],
  662. local_borg_version='1.2.3',
  663. global_arguments=flexmock(),
  664. borgmatic_runtime_directory='/run/borgmatic',
  665. )
  666. assert create_flags == ('borg', 'create')
  667. assert create_positional_arguments == ('repo::{hostname}',)
  668. assert not pattern_file
  669. def test_make_base_create_command_includes_archive_name_format_with_placeholders_in_borg_command():
  670. repository_archive_pattern = 'repo::Documents_{hostname}-{now}'
  671. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  672. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  673. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  674. '{hostname}',
  675. )
  676. flexmock(module.feature).should_receive('available').and_return(True)
  677. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  678. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  679. (repository_archive_pattern,),
  680. )
  681. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  682. dry_run=False,
  683. repository_path='repo',
  684. config={
  685. 'source_directories': ['foo', 'bar'],
  686. 'repositories': ['repo'],
  687. 'archive_name_format': 'Documents_{hostname}-{now}',
  688. },
  689. patterns=[Pattern('foo'), Pattern('bar')],
  690. local_borg_version='1.2.3',
  691. global_arguments=flexmock(),
  692. borgmatic_runtime_directory='/run/borgmatic',
  693. )
  694. assert create_flags == ('borg', 'create')
  695. assert create_positional_arguments == (repository_archive_pattern,)
  696. assert not pattern_file
  697. def test_make_base_create_command_includes_repository_and_archive_name_format_with_placeholders_in_borg_command():
  698. repository_archive_pattern = '{fqdn}::Documents_{hostname}-{now}'
  699. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  700. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  701. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  702. '{hostname}',
  703. )
  704. flexmock(module.feature).should_receive('available').and_return(True)
  705. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  706. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  707. (repository_archive_pattern,),
  708. )
  709. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  710. dry_run=False,
  711. repository_path='{fqdn}',
  712. config={
  713. 'source_directories': ['foo', 'bar'],
  714. 'repositories': ['{fqdn}'],
  715. 'archive_name_format': 'Documents_{hostname}-{now}',
  716. },
  717. patterns=[Pattern('foo'), Pattern('bar')],
  718. local_borg_version='1.2.3',
  719. global_arguments=flexmock(),
  720. borgmatic_runtime_directory='/run/borgmatic',
  721. )
  722. assert create_flags == ('borg', 'create')
  723. assert create_positional_arguments == (repository_archive_pattern,)
  724. assert not pattern_file
  725. def test_make_base_create_command_includes_archive_suffix_in_borg_command():
  726. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  727. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  728. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  729. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  730. DEFAULT_ARCHIVE_NAME,
  731. )
  732. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  733. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  734. dry_run=False,
  735. repository_path='repo',
  736. config={
  737. 'source_directories': ['foo', 'bar'],
  738. 'repositories': ['repo'],
  739. },
  740. patterns=[Pattern('foo'), Pattern('bar')],
  741. local_borg_version='1.2.3',
  742. global_arguments=flexmock(),
  743. borgmatic_runtime_directory='/run/borgmatic',
  744. archive_suffix='.checkpoint',
  745. )
  746. assert create_flags == ('borg', 'create')
  747. assert create_positional_arguments == (f'repo::{DEFAULT_ARCHIVE_NAME}.checkpoint',)
  748. assert not pattern_file
  749. def test_make_base_create_command_includes_extra_borg_options_in_borg_command():
  750. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  751. flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
  752. flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
  753. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  754. '{hostname}',
  755. )
  756. flexmock(module.feature).should_receive('available').and_return(True)
  757. flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
  758. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  759. (f'repo::{DEFAULT_ARCHIVE_NAME}',),
  760. )
  761. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  762. dry_run=False,
  763. repository_path='repo',
  764. config={
  765. 'source_directories': ['foo', 'bar'],
  766. 'repositories': ['repo'],
  767. 'extra_borg_options': {'create': '--extra --options "value with space"'},
  768. },
  769. patterns=[Pattern('foo'), Pattern('bar')],
  770. local_borg_version='1.2.3',
  771. global_arguments=flexmock(),
  772. borgmatic_runtime_directory='/run/borgmatic',
  773. )
  774. assert create_flags == ('borg', 'create', '--extra', '--options', 'value with space')
  775. assert create_positional_arguments == REPO_ARCHIVE
  776. assert not pattern_file
  777. def test_make_base_create_command_with_non_existent_directory_and_source_directories_must_exist_raises():
  778. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  779. flexmock(module.borgmatic.borg.pattern).should_receive(
  780. 'check_all_root_patterns_exist',
  781. ).and_raise(ValueError)
  782. with pytest.raises(ValueError):
  783. module.make_base_create_command(
  784. dry_run=False,
  785. repository_path='repo',
  786. config={
  787. 'source_directories': ['foo', 'bar'],
  788. 'repositories': ['repo'],
  789. 'source_directories_must_exist': True,
  790. },
  791. patterns=[Pattern('foo'), Pattern('bar')],
  792. local_borg_version='1.2.3',
  793. global_arguments=flexmock(),
  794. borgmatic_runtime_directory='/run/borgmatic',
  795. )
  796. def test_create_archive_calls_borg_with_flags():
  797. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  798. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  799. flexmock(module).should_receive('make_base_create_command').and_return(
  800. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  801. )
  802. flexmock(module.environment).should_receive('make_environment')
  803. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  804. flexmock(module).should_receive('execute_command').with_args(
  805. ('borg', 'create', *REPO_ARCHIVE),
  806. output_log_level=logging.INFO,
  807. output_file=None,
  808. borg_local_path='borg',
  809. borg_exit_codes=None,
  810. working_directory=None,
  811. environment=None,
  812. )
  813. module.create_archive(
  814. dry_run=False,
  815. repository_path='repo',
  816. config={
  817. 'source_directories': ['foo', 'bar'],
  818. 'repositories': ['repo'],
  819. 'exclude_patterns': None,
  820. },
  821. patterns=[Pattern('foo'), Pattern('bar')],
  822. local_borg_version='1.2.3',
  823. global_arguments=flexmock(),
  824. borgmatic_runtime_directory='/borgmatic/run',
  825. )
  826. def test_create_archive_calls_borg_with_environment():
  827. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  828. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  829. flexmock(module).should_receive('make_base_create_command').and_return(
  830. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  831. )
  832. environment = {'BORG_THINGY': 'YUP'}
  833. flexmock(module.environment).should_receive('make_environment').and_return(environment)
  834. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  835. flexmock(module).should_receive('execute_command').with_args(
  836. ('borg', 'create', *REPO_ARCHIVE),
  837. output_log_level=logging.INFO,
  838. output_file=None,
  839. borg_local_path='borg',
  840. borg_exit_codes=None,
  841. working_directory=None,
  842. environment=environment,
  843. )
  844. module.create_archive(
  845. dry_run=False,
  846. repository_path='repo',
  847. config={
  848. 'source_directories': ['foo', 'bar'],
  849. 'repositories': ['repo'],
  850. 'exclude_patterns': None,
  851. },
  852. patterns=[Pattern('foo'), Pattern('bar')],
  853. local_borg_version='1.2.3',
  854. global_arguments=flexmock(),
  855. borgmatic_runtime_directory='/borgmatic/run',
  856. )
  857. def test_create_archive_with_log_info_calls_borg_with_info_flag():
  858. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  859. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  860. flexmock(module).should_receive('make_base_create_command').and_return(
  861. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  862. )
  863. flexmock(module.environment).should_receive('make_environment')
  864. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  865. flexmock(module).should_receive('execute_command').with_args(
  866. ('borg', 'create', '--info', *REPO_ARCHIVE),
  867. output_log_level=logging.INFO,
  868. output_file=None,
  869. borg_local_path='borg',
  870. borg_exit_codes=None,
  871. working_directory=None,
  872. environment=None,
  873. )
  874. insert_logging_mock(logging.INFO)
  875. module.create_archive(
  876. dry_run=False,
  877. repository_path='repo',
  878. config={
  879. 'source_directories': ['foo', 'bar'],
  880. 'repositories': ['repo'],
  881. 'exclude_patterns': None,
  882. },
  883. patterns=[Pattern('foo'), Pattern('bar')],
  884. local_borg_version='1.2.3',
  885. global_arguments=flexmock(),
  886. borgmatic_runtime_directory='/borgmatic/run',
  887. )
  888. def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
  889. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  890. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  891. flexmock(module).should_receive('make_base_create_command').and_return(
  892. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  893. )
  894. flexmock(module.environment).should_receive('make_environment')
  895. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  896. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  897. ('borg', 'create', '--json', *REPO_ARCHIVE),
  898. working_directory=None,
  899. environment=None,
  900. borg_local_path='borg',
  901. borg_exit_codes=None,
  902. )
  903. insert_logging_mock(logging.INFO)
  904. module.create_archive(
  905. dry_run=False,
  906. repository_path='repo',
  907. config={
  908. 'source_directories': ['foo', 'bar'],
  909. 'repositories': ['repo'],
  910. 'exclude_patterns': None,
  911. },
  912. patterns=[Pattern('foo'), Pattern('bar')],
  913. local_borg_version='1.2.3',
  914. global_arguments=flexmock(),
  915. borgmatic_runtime_directory='/borgmatic/run',
  916. json=True,
  917. )
  918. def test_create_archive_with_log_debug_calls_borg_with_debug_flag():
  919. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  920. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  921. flexmock(module).should_receive('make_base_create_command').and_return(
  922. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  923. )
  924. flexmock(module.environment).should_receive('make_environment')
  925. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  926. flexmock(module).should_receive('execute_command').with_args(
  927. ('borg', 'create', '--debug', '--show-rc', *REPO_ARCHIVE),
  928. output_log_level=logging.INFO,
  929. output_file=None,
  930. borg_local_path='borg',
  931. borg_exit_codes=None,
  932. working_directory=None,
  933. environment=None,
  934. )
  935. insert_logging_mock(logging.DEBUG)
  936. module.create_archive(
  937. dry_run=False,
  938. repository_path='repo',
  939. config={
  940. 'source_directories': ['foo', 'bar'],
  941. 'repositories': ['repo'],
  942. 'exclude_patterns': None,
  943. },
  944. patterns=[Pattern('foo'), Pattern('bar')],
  945. local_borg_version='1.2.3',
  946. global_arguments=flexmock(),
  947. borgmatic_runtime_directory='/borgmatic/run',
  948. )
  949. def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
  950. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  951. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  952. flexmock(module).should_receive('make_base_create_command').and_return(
  953. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  954. )
  955. flexmock(module.environment).should_receive('make_environment')
  956. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  957. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  958. ('borg', 'create', '--json', *REPO_ARCHIVE),
  959. working_directory=None,
  960. environment=None,
  961. borg_local_path='borg',
  962. borg_exit_codes=None,
  963. )
  964. insert_logging_mock(logging.DEBUG)
  965. module.create_archive(
  966. dry_run=False,
  967. repository_path='repo',
  968. config={
  969. 'source_directories': ['foo', 'bar'],
  970. 'repositories': ['repo'],
  971. 'exclude_patterns': None,
  972. },
  973. patterns=[Pattern('foo'), Pattern('bar')],
  974. local_borg_version='1.2.3',
  975. global_arguments=flexmock(),
  976. borgmatic_runtime_directory='/borgmatic/run',
  977. json=True,
  978. )
  979. def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats():
  980. # --dry-run and --stats are mutually exclusive, see:
  981. # https://borgbackup.readthedocs.io/en/stable/usage/create.html#description
  982. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  983. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  984. flexmock(module).should_receive('make_base_create_command').and_return(
  985. (('borg', 'create', '--dry-run'), REPO_ARCHIVE, flexmock()),
  986. )
  987. flexmock(module.environment).should_receive('make_environment')
  988. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  989. flexmock(module).should_receive('execute_command').with_args(
  990. ('borg', 'create', '--dry-run', '--info', *REPO_ARCHIVE),
  991. output_log_level=logging.INFO,
  992. output_file=None,
  993. borg_local_path='borg',
  994. borg_exit_codes=None,
  995. working_directory=None,
  996. environment=None,
  997. )
  998. insert_logging_mock(logging.INFO)
  999. module.create_archive(
  1000. dry_run=True,
  1001. repository_path='repo',
  1002. config={
  1003. 'source_directories': ['foo', 'bar'],
  1004. 'repositories': ['repo'],
  1005. 'exclude_patterns': None,
  1006. },
  1007. patterns=[Pattern('foo'), Pattern('bar')],
  1008. local_borg_version='1.2.3',
  1009. global_arguments=flexmock(),
  1010. borgmatic_runtime_directory='/borgmatic/run',
  1011. )
  1012. def test_create_archive_with_working_directory_calls_borg_with_working_directory():
  1013. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1014. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1015. flexmock(module).should_receive('make_base_create_command').and_return(
  1016. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  1017. )
  1018. flexmock(module.environment).should_receive('make_environment')
  1019. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  1020. '/working/dir',
  1021. )
  1022. flexmock(module).should_receive('execute_command').with_args(
  1023. ('borg', 'create', *REPO_ARCHIVE),
  1024. output_log_level=logging.INFO,
  1025. output_file=None,
  1026. borg_local_path='borg',
  1027. borg_exit_codes=None,
  1028. working_directory='/working/dir',
  1029. environment=None,
  1030. )
  1031. module.create_archive(
  1032. dry_run=False,
  1033. repository_path='repo',
  1034. config={
  1035. 'source_directories': ['foo', 'bar'],
  1036. 'repositories': ['repo'],
  1037. 'working_directory': '/working/dir',
  1038. 'exclude_patterns': None,
  1039. },
  1040. patterns=[Pattern('foo'), Pattern('bar')],
  1041. local_borg_version='1.2.3',
  1042. global_arguments=flexmock(),
  1043. borgmatic_runtime_directory='/borgmatic/run',
  1044. )
  1045. def test_create_archive_with_exit_codes_calls_borg_using_them():
  1046. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1047. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1048. flexmock(module).should_receive('make_base_create_command').and_return(
  1049. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  1050. )
  1051. flexmock(module.environment).should_receive('make_environment')
  1052. borg_exit_codes = flexmock()
  1053. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1054. flexmock(module).should_receive('execute_command').with_args(
  1055. ('borg', 'create', *REPO_ARCHIVE),
  1056. output_log_level=logging.INFO,
  1057. output_file=None,
  1058. borg_local_path='borg',
  1059. borg_exit_codes=borg_exit_codes,
  1060. working_directory=None,
  1061. environment=None,
  1062. )
  1063. module.create_archive(
  1064. dry_run=False,
  1065. repository_path='repo',
  1066. config={
  1067. 'source_directories': ['foo', 'bar'],
  1068. 'repositories': ['repo'],
  1069. 'exclude_patterns': None,
  1070. 'borg_exit_codes': borg_exit_codes,
  1071. },
  1072. patterns=[Pattern('foo'), Pattern('bar')],
  1073. local_borg_version='1.2.3',
  1074. global_arguments=flexmock(),
  1075. borgmatic_runtime_directory='/borgmatic/run',
  1076. )
  1077. def test_create_archive_with_stats_calls_borg_with_stats_flag_and_answer_output_log_level():
  1078. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1079. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1080. flexmock(module).should_receive('make_base_create_command').and_return(
  1081. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  1082. )
  1083. flexmock(module.environment).should_receive('make_environment')
  1084. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1085. flexmock(module).should_receive('execute_command').with_args(
  1086. ('borg', 'create', '--stats', *REPO_ARCHIVE),
  1087. output_log_level=module.borgmatic.logger.ANSWER,
  1088. output_file=None,
  1089. borg_local_path='borg',
  1090. borg_exit_codes=None,
  1091. working_directory=None,
  1092. environment=None,
  1093. )
  1094. module.create_archive(
  1095. dry_run=False,
  1096. repository_path='repo',
  1097. config={
  1098. 'source_directories': ['foo', 'bar'],
  1099. 'repositories': ['repo'],
  1100. 'exclude_patterns': None,
  1101. 'statistics': True,
  1102. },
  1103. patterns=[Pattern('foo'), Pattern('bar')],
  1104. local_borg_version='1.2.3',
  1105. global_arguments=flexmock(),
  1106. borgmatic_runtime_directory='/borgmatic/run',
  1107. )
  1108. def test_create_archive_with_files_calls_borg_with_answer_output_log_level():
  1109. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1110. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1111. flexmock(module).should_receive('make_base_create_command').and_return(
  1112. (
  1113. ('borg', 'create', '--list', '--filter', 'FOO'),
  1114. REPO_ARCHIVE,
  1115. flexmock(),
  1116. ),
  1117. )
  1118. flexmock(module.environment).should_receive('make_environment')
  1119. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1120. flexmock(module).should_receive('execute_command').with_args(
  1121. ('borg', 'create', '--list', '--filter', 'FOO', *REPO_ARCHIVE),
  1122. output_log_level=module.borgmatic.logger.ANSWER,
  1123. output_file=None,
  1124. borg_local_path='borg',
  1125. borg_exit_codes=None,
  1126. working_directory=None,
  1127. environment=None,
  1128. )
  1129. module.create_archive(
  1130. dry_run=False,
  1131. repository_path='repo',
  1132. config={
  1133. 'source_directories': ['foo', 'bar'],
  1134. 'repositories': ['repo'],
  1135. 'exclude_patterns': None,
  1136. 'list_details': True,
  1137. },
  1138. patterns=[Pattern('foo'), Pattern('bar')],
  1139. local_borg_version='1.2.3',
  1140. global_arguments=flexmock(),
  1141. borgmatic_runtime_directory='/borgmatic/run',
  1142. )
  1143. def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_flag_and_no_list():
  1144. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1145. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1146. flexmock(module).should_receive('make_base_create_command').and_return(
  1147. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  1148. )
  1149. flexmock(module.environment).should_receive('make_environment')
  1150. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1151. flexmock(module).should_receive('execute_command').with_args(
  1152. ('borg', 'create', '--info', '--progress', *REPO_ARCHIVE),
  1153. output_log_level=logging.INFO,
  1154. output_file=module.DO_NOT_CAPTURE,
  1155. borg_local_path='borg',
  1156. borg_exit_codes=None,
  1157. working_directory=None,
  1158. environment=None,
  1159. )
  1160. insert_logging_mock(logging.INFO)
  1161. module.create_archive(
  1162. dry_run=False,
  1163. repository_path='repo',
  1164. config={
  1165. 'source_directories': ['foo', 'bar'],
  1166. 'repositories': ['repo'],
  1167. 'exclude_patterns': None,
  1168. 'progress': True,
  1169. },
  1170. patterns=[Pattern('foo'), Pattern('bar')],
  1171. local_borg_version='1.2.3',
  1172. global_arguments=flexmock(),
  1173. borgmatic_runtime_directory='/borgmatic/run',
  1174. )
  1175. def test_create_archive_with_progress_calls_borg_with_progress_flag():
  1176. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1177. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1178. flexmock(module).should_receive('make_base_create_command').and_return(
  1179. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  1180. )
  1181. flexmock(module.environment).should_receive('make_environment')
  1182. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1183. flexmock(module).should_receive('execute_command').with_args(
  1184. ('borg', 'create', '--progress', *REPO_ARCHIVE),
  1185. output_log_level=logging.INFO,
  1186. output_file=module.DO_NOT_CAPTURE,
  1187. borg_local_path='borg',
  1188. borg_exit_codes=None,
  1189. working_directory=None,
  1190. environment=None,
  1191. )
  1192. module.create_archive(
  1193. dry_run=False,
  1194. repository_path='repo',
  1195. config={
  1196. 'source_directories': ['foo', 'bar'],
  1197. 'repositories': ['repo'],
  1198. 'exclude_patterns': None,
  1199. 'progress': True,
  1200. },
  1201. patterns=[Pattern('foo'), Pattern('bar')],
  1202. local_borg_version='1.2.3',
  1203. global_arguments=flexmock(),
  1204. borgmatic_runtime_directory='/borgmatic/run',
  1205. )
  1206. def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progress_flag():
  1207. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1208. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1209. processes = flexmock()
  1210. flexmock(module).should_receive('make_base_create_command').and_return(
  1211. (
  1212. ('borg', 'create', '--read-special'),
  1213. REPO_ARCHIVE,
  1214. flexmock(),
  1215. ),
  1216. )
  1217. flexmock(module.environment).should_receive('make_environment')
  1218. create_command = ('borg', 'create', '--read-special', '--progress', *REPO_ARCHIVE)
  1219. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1220. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1221. (*create_command, '--dry-run', '--list'),
  1222. processes=processes,
  1223. output_log_level=logging.INFO,
  1224. output_file=module.DO_NOT_CAPTURE,
  1225. borg_local_path='borg',
  1226. borg_exit_codes=None,
  1227. working_directory=None,
  1228. environment=None,
  1229. )
  1230. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1231. create_command,
  1232. processes=processes,
  1233. output_log_level=logging.INFO,
  1234. output_file=module.DO_NOT_CAPTURE,
  1235. borg_local_path='borg',
  1236. borg_exit_codes=None,
  1237. working_directory=None,
  1238. environment=None,
  1239. )
  1240. module.create_archive(
  1241. dry_run=False,
  1242. repository_path='repo',
  1243. config={
  1244. 'source_directories': ['foo', 'bar'],
  1245. 'repositories': ['repo'],
  1246. 'exclude_patterns': None,
  1247. 'progress': True,
  1248. },
  1249. patterns=[Pattern('foo'), Pattern('bar')],
  1250. local_borg_version='1.2.3',
  1251. global_arguments=flexmock(),
  1252. borgmatic_runtime_directory='/borgmatic/run',
  1253. stream_processes=processes,
  1254. )
  1255. def test_create_archive_with_json_calls_borg_with_json_flag():
  1256. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1257. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1258. flexmock(module).should_receive('make_base_create_command').and_return(
  1259. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  1260. )
  1261. flexmock(module.environment).should_receive('make_environment')
  1262. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1263. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  1264. ('borg', 'create', '--json', *REPO_ARCHIVE),
  1265. working_directory=None,
  1266. environment=None,
  1267. borg_local_path='borg',
  1268. borg_exit_codes=None,
  1269. ).and_return('[]')
  1270. json_output = module.create_archive(
  1271. dry_run=False,
  1272. repository_path='repo',
  1273. config={
  1274. 'source_directories': ['foo', 'bar'],
  1275. 'repositories': ['repo'],
  1276. 'exclude_patterns': None,
  1277. },
  1278. patterns=[Pattern('foo'), Pattern('bar')],
  1279. local_borg_version='1.2.3',
  1280. global_arguments=flexmock(),
  1281. borgmatic_runtime_directory='/borgmatic/run',
  1282. json=True,
  1283. )
  1284. assert json_output == '[]'
  1285. def test_create_archive_with_stats_and_json_calls_borg_without_stats_flag():
  1286. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1287. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1288. flexmock(module).should_receive('make_base_create_command').and_return(
  1289. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  1290. )
  1291. flexmock(module.environment).should_receive('make_environment')
  1292. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1293. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  1294. ('borg', 'create', '--json', *REPO_ARCHIVE),
  1295. working_directory=None,
  1296. environment=None,
  1297. borg_local_path='borg',
  1298. borg_exit_codes=None,
  1299. ).and_return('[]')
  1300. json_output = module.create_archive(
  1301. dry_run=False,
  1302. repository_path='repo',
  1303. config={
  1304. 'source_directories': ['foo*'],
  1305. 'repositories': ['repo'],
  1306. 'exclude_patterns': None,
  1307. },
  1308. patterns=[Pattern('foo'), Pattern('bar')],
  1309. local_borg_version='1.2.3',
  1310. global_arguments=flexmock(),
  1311. borgmatic_runtime_directory='/borgmatic/run',
  1312. json=True,
  1313. )
  1314. assert json_output == '[]'
  1315. def test_create_archive_with_comment_calls_borg_with_comment_flag():
  1316. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1317. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1318. flexmock(module).should_receive('make_base_create_command').and_return(
  1319. (('borg', 'create', '--comment', 'a comment'), REPO_ARCHIVE, flexmock()),
  1320. )
  1321. flexmock(module.environment).should_receive('make_environment')
  1322. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1323. flexmock(module).should_receive('execute_command').with_args(
  1324. ('borg', 'create', '--comment', 'a comment', *REPO_ARCHIVE),
  1325. output_log_level=logging.INFO,
  1326. output_file=None,
  1327. borg_local_path='borg',
  1328. borg_exit_codes=None,
  1329. working_directory=None,
  1330. environment=None,
  1331. )
  1332. module.create_archive(
  1333. dry_run=False,
  1334. repository_path='repo',
  1335. config={
  1336. 'source_directories': ['foo', 'bar'],
  1337. 'repositories': ['repo'],
  1338. 'exclude_patterns': None,
  1339. },
  1340. patterns=[Pattern('foo'), Pattern('bar')],
  1341. local_borg_version='1.2.3',
  1342. global_arguments=flexmock(),
  1343. borgmatic_runtime_directory='/borgmatic/run',
  1344. json=False,
  1345. comment='a comment',
  1346. )
  1347. def test_create_archive_calls_borg_with_working_directory():
  1348. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1349. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1350. flexmock(module).should_receive('make_base_create_command').and_return(
  1351. (('borg', 'create'), REPO_ARCHIVE, flexmock()),
  1352. )
  1353. flexmock(module.environment).should_receive('make_environment')
  1354. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  1355. '/working/dir',
  1356. )
  1357. flexmock(module).should_receive('execute_command').with_args(
  1358. ('borg', 'create', *REPO_ARCHIVE),
  1359. output_log_level=logging.INFO,
  1360. output_file=None,
  1361. borg_local_path='borg',
  1362. borg_exit_codes=None,
  1363. working_directory='/working/dir',
  1364. environment=None,
  1365. )
  1366. module.create_archive(
  1367. dry_run=False,
  1368. repository_path='repo',
  1369. config={
  1370. 'source_directories': ['foo', 'bar'],
  1371. 'repositories': ['repo'],
  1372. 'exclude_patterns': None,
  1373. 'working_directory': '/working/dir',
  1374. },
  1375. patterns=[Pattern('foo'), Pattern('bar')],
  1376. local_borg_version='1.2.3',
  1377. global_arguments=flexmock(),
  1378. borgmatic_runtime_directory='/borgmatic/run',
  1379. )