test_create.py 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585
  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_style, Pattern_type
  6. from ..test_verbosity import insert_logging_mock
  7. def test_write_patterns_file_writes_pattern_lines():
  8. temporary_file = flexmock(name='filename', flush=lambda: None)
  9. temporary_file.should_receive('write').with_args('R /foo\n+ sh:/foo/bar')
  10. flexmock(module.tempfile).should_receive('NamedTemporaryFile').and_return(temporary_file)
  11. module.write_patterns_file(
  12. [Pattern('/foo'), Pattern('/foo/bar', Pattern_type.INCLUDE, Pattern_style.SHELL)],
  13. borgmatic_runtime_directory='/run/user/0',
  14. )
  15. def test_write_patterns_file_with_empty_exclude_patterns_does_not_raise():
  16. module.write_patterns_file([], borgmatic_runtime_directory='/run/user/0')
  17. def test_write_patterns_file_appends_to_existing():
  18. patterns_file = flexmock(name='filename', flush=lambda: None)
  19. patterns_file.should_receive('write').with_args('\n')
  20. patterns_file.should_receive('write').with_args('R /foo\n+ /foo/bar')
  21. flexmock(module.tempfile).should_receive('NamedTemporaryFile').never()
  22. module.write_patterns_file(
  23. [Pattern('/foo'), Pattern('/foo/bar', Pattern_type.INCLUDE)],
  24. borgmatic_runtime_directory='/run/user/0',
  25. patterns_file=patterns_file,
  26. )
  27. def test_make_exclude_flags_includes_exclude_caches_when_true_in_config():
  28. exclude_flags = module.make_exclude_flags(config={'exclude_caches': True})
  29. assert exclude_flags == ('--exclude-caches',)
  30. def test_make_exclude_flags_does_not_include_exclude_caches_when_false_in_config():
  31. exclude_flags = module.make_exclude_flags(config={'exclude_caches': False})
  32. assert exclude_flags == ()
  33. def test_make_exclude_flags_includes_exclude_if_present_when_in_config():
  34. exclude_flags = module.make_exclude_flags(
  35. config={'exclude_if_present': ['exclude_me', 'also_me']}
  36. )
  37. assert exclude_flags == (
  38. '--exclude-if-present',
  39. 'exclude_me',
  40. '--exclude-if-present',
  41. 'also_me',
  42. )
  43. def test_make_exclude_flags_includes_keep_exclude_tags_when_true_in_config():
  44. exclude_flags = module.make_exclude_flags(config={'keep_exclude_tags': True})
  45. assert exclude_flags == ('--keep-exclude-tags',)
  46. def test_make_exclude_flags_does_not_include_keep_exclude_tags_when_false_in_config():
  47. exclude_flags = module.make_exclude_flags(config={'keep_exclude_tags': False})
  48. assert exclude_flags == ()
  49. def test_make_exclude_flags_includes_exclude_nodump_when_true_in_config():
  50. exclude_flags = module.make_exclude_flags(config={'exclude_nodump': True})
  51. assert exclude_flags == ('--exclude-nodump',)
  52. def test_make_exclude_flags_does_not_include_exclude_nodump_when_false_in_config():
  53. exclude_flags = module.make_exclude_flags(config={'exclude_nodump': False})
  54. assert exclude_flags == ()
  55. def test_make_exclude_flags_is_empty_when_config_has_no_excludes():
  56. exclude_flags = module.make_exclude_flags(config={})
  57. assert exclude_flags == ()
  58. def test_make_list_filter_flags_with_debug_and_feature_available_includes_plus_and_minus():
  59. flexmock(module.logger).should_receive('isEnabledFor').and_return(True)
  60. flexmock(module.feature).should_receive('available').and_return(True)
  61. assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=False) == 'AME+-'
  62. def test_make_list_filter_flags_with_info_and_feature_available_omits_plus_and_minus():
  63. flexmock(module.logger).should_receive('isEnabledFor').and_return(False)
  64. flexmock(module.feature).should_receive('available').and_return(True)
  65. assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=False) == 'AME'
  66. def test_make_list_filter_flags_with_debug_and_feature_available_and_dry_run_includes_plus_and_minus():
  67. flexmock(module.logger).should_receive('isEnabledFor').and_return(True)
  68. flexmock(module.feature).should_receive('available').and_return(True)
  69. assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=True) == 'AME+-'
  70. def test_make_list_filter_flags_with_info_and_feature_available_and_dry_run_includes_plus_and_minus():
  71. flexmock(module.logger).should_receive('isEnabledFor').and_return(False)
  72. flexmock(module.feature).should_receive('available').and_return(True)
  73. assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=True) == 'AME+-'
  74. def test_make_list_filter_flags_with_debug_and_feature_not_available_includes_x():
  75. flexmock(module.logger).should_receive('isEnabledFor').and_return(True)
  76. flexmock(module.feature).should_receive('available').and_return(False)
  77. assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=False) == 'AMEx-'
  78. def test_make_list_filter_flags_with_info_and_feature_not_available_omits_x():
  79. flexmock(module.logger).should_receive('isEnabledFor').and_return(False)
  80. flexmock(module.feature).should_receive('available').and_return(False)
  81. assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=False) == 'AME-'
  82. @pytest.mark.parametrize(
  83. 'character_device,block_device,fifo,expected_result',
  84. (
  85. (False, False, False, False),
  86. (True, False, False, True),
  87. (False, True, False, True),
  88. (True, True, False, True),
  89. (False, False, True, True),
  90. (False, True, True, True),
  91. (True, False, True, True),
  92. ),
  93. )
  94. def test_special_file_looks_at_file_type(character_device, block_device, fifo, expected_result):
  95. flexmock(module.os).should_receive('stat').and_return(flexmock(st_mode=flexmock()))
  96. flexmock(module.stat).should_receive('S_ISCHR').and_return(character_device)
  97. flexmock(module.stat).should_receive('S_ISBLK').and_return(block_device)
  98. flexmock(module.stat).should_receive('S_ISFIFO').and_return(fifo)
  99. assert module.special_file('/dev/special') == expected_result
  100. def test_special_file_treats_broken_symlink_as_non_special():
  101. flexmock(module.os).should_receive('stat').and_raise(FileNotFoundError)
  102. assert module.special_file('/broken/symlink') is False
  103. def test_special_file_prepends_relative_path_with_working_directory():
  104. flexmock(module.os).should_receive('stat').with_args('/working/dir/relative').and_return(
  105. flexmock(st_mode=flexmock())
  106. )
  107. flexmock(module.stat).should_receive('S_ISCHR').and_return(False)
  108. flexmock(module.stat).should_receive('S_ISBLK').and_return(False)
  109. flexmock(module.stat).should_receive('S_ISFIFO').and_return(False)
  110. assert module.special_file('relative', '/working/dir') is False
  111. def test_any_parent_directories_treats_parents_as_match():
  112. module.any_parent_directories('/foo/bar.txt', ('/foo', '/etc'))
  113. def test_any_parent_directories_treats_grandparents_as_match():
  114. module.any_parent_directories('/foo/bar/baz.txt', ('/foo', '/etc'))
  115. def test_any_parent_directories_treats_unrelated_paths_as_non_match():
  116. module.any_parent_directories('/foo/bar.txt', ('/usr', '/etc'))
  117. def test_collect_special_file_paths_parses_special_files_from_borg_dry_run_file_list():
  118. flexmock(module.environment).should_receive('make_environment').and_return(None)
  119. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  120. 'Processing files ...\n- /foo\n+ /bar\n- /baz'
  121. )
  122. flexmock(module).should_receive('special_file').and_return(True)
  123. flexmock(module.os.path).should_receive('exists').and_return(False)
  124. flexmock(module).should_receive('any_parent_directories').never()
  125. assert module.collect_special_file_paths(
  126. dry_run=False,
  127. create_command=('borg', 'create'),
  128. config={},
  129. local_path=None,
  130. working_directory=None,
  131. borgmatic_runtime_directory='/run/borgmatic',
  132. ) == ('/foo', '/bar', '/baz')
  133. def test_collect_special_file_paths_skips_borgmatic_runtime_directory():
  134. flexmock(module.environment).should_receive('make_environment').and_return(None)
  135. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  136. '+ /foo\n- /run/borgmatic/bar\n- /baz'
  137. )
  138. flexmock(module).should_receive('special_file').and_return(True)
  139. flexmock(module.os.path).should_receive('exists').and_return(True)
  140. flexmock(module).should_receive('any_parent_directories').with_args(
  141. '/foo', ('/run/borgmatic',)
  142. ).and_return(False)
  143. flexmock(module).should_receive('any_parent_directories').with_args(
  144. '/run/borgmatic/bar', ('/run/borgmatic',)
  145. ).and_return(True)
  146. flexmock(module).should_receive('any_parent_directories').with_args(
  147. '/baz', ('/run/borgmatic',)
  148. ).and_return(False)
  149. assert module.collect_special_file_paths(
  150. dry_run=False,
  151. create_command=('borg', 'create'),
  152. config={},
  153. local_path=None,
  154. working_directory=None,
  155. borgmatic_runtime_directory='/run/borgmatic',
  156. ) == ('/foo', '/baz')
  157. def test_collect_special_file_paths_with_borgmatic_runtime_directory_missing_from_paths_output_errors():
  158. flexmock(module.environment).should_receive('make_environment').and_return(None)
  159. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  160. '+ /foo\n- /bar\n- /baz'
  161. )
  162. flexmock(module).should_receive('special_file').and_return(True)
  163. flexmock(module.os.path).should_receive('exists').and_return(True)
  164. flexmock(module).should_receive('any_parent_directories').and_return(False)
  165. with pytest.raises(ValueError):
  166. module.collect_special_file_paths(
  167. dry_run=False,
  168. create_command=('borg', 'create'),
  169. config={},
  170. local_path=None,
  171. working_directory=None,
  172. borgmatic_runtime_directory='/run/borgmatic',
  173. )
  174. def test_collect_special_file_paths_with_dry_run_and_borgmatic_runtime_directory_missing_from_paths_output_does_not_raise():
  175. flexmock(module.environment).should_receive('make_environment').and_return(None)
  176. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  177. '+ /foo\n- /bar\n- /baz'
  178. )
  179. flexmock(module).should_receive('special_file').and_return(True)
  180. flexmock(module.os.path).should_receive('exists').and_return(True)
  181. flexmock(module).should_receive('any_parent_directories').and_return(False)
  182. assert module.collect_special_file_paths(
  183. dry_run=True,
  184. create_command=('borg', 'create'),
  185. config={},
  186. local_path=None,
  187. working_directory=None,
  188. borgmatic_runtime_directory='/run/borgmatic',
  189. ) == ('/foo', '/bar', '/baz')
  190. def test_collect_special_file_paths_excludes_non_special_files():
  191. flexmock(module.environment).should_receive('make_environment').and_return(None)
  192. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  193. '+ /foo\n+ /bar\n+ /baz'
  194. )
  195. flexmock(module).should_receive('special_file').and_return(True).and_return(False).and_return(
  196. True
  197. )
  198. flexmock(module.os.path).should_receive('exists').and_return(False)
  199. flexmock(module).should_receive('any_parent_directories').never()
  200. assert module.collect_special_file_paths(
  201. dry_run=False,
  202. create_command=('borg', 'create'),
  203. config={},
  204. local_path=None,
  205. working_directory=None,
  206. borgmatic_runtime_directory='/run/borgmatic',
  207. ) == ('/foo', '/baz')
  208. def test_collect_special_file_paths_omits_exclude_no_dump_flag_from_command():
  209. flexmock(module.environment).should_receive('make_environment').and_return(None)
  210. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  211. ('borg', 'create', '--dry-run', '--list'),
  212. capture_stderr=True,
  213. working_directory=None,
  214. extra_environment=None,
  215. borg_local_path='borg',
  216. borg_exit_codes=None,
  217. ).and_return('Processing files ...\n- /foo\n+ /bar\n- /baz').once()
  218. flexmock(module).should_receive('special_file').and_return(True)
  219. flexmock(module.os.path).should_receive('exists').and_return(False)
  220. flexmock(module).should_receive('any_parent_directories').never()
  221. module.collect_special_file_paths(
  222. dry_run=False,
  223. create_command=('borg', 'create', '--exclude-nodump'),
  224. config={},
  225. local_path='borg',
  226. working_directory=None,
  227. borgmatic_runtime_directory='/run/borgmatic',
  228. )
  229. DEFAULT_ARCHIVE_NAME = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}' # noqa: FS003
  230. REPO_ARCHIVE = (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  231. def test_make_base_create_produces_borg_command():
  232. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  233. flexmock(module).should_receive('write_patterns_file').and_return(None)
  234. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  235. flexmock(module.feature).should_receive('available').and_return(True)
  236. flexmock(module).should_receive('make_exclude_flags').and_return(())
  237. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  238. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  239. )
  240. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  241. dry_run=False,
  242. repository_path='repo',
  243. config={
  244. 'source_directories': ['foo', 'bar'],
  245. 'repositories': ['repo'],
  246. },
  247. patterns=[Pattern('foo'), Pattern('bar')],
  248. local_borg_version='1.2.3',
  249. global_arguments=flexmock(log_json=False),
  250. borgmatic_runtime_directory='/run/borgmatic',
  251. )
  252. assert create_flags == ('borg', 'create')
  253. assert create_positional_arguments == REPO_ARCHIVE
  254. assert not pattern_file
  255. def test_make_base_create_command_includes_patterns_file_in_borg_command():
  256. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  257. mock_pattern_file = flexmock(name='/tmp/patterns')
  258. flexmock(module).should_receive('write_patterns_file').and_return(mock_pattern_file).and_return(
  259. None
  260. )
  261. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  262. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  263. '{hostname}'
  264. )
  265. flexmock(module.feature).should_receive('available').and_return(True)
  266. pattern_flags = ('--patterns-from', mock_pattern_file.name)
  267. flexmock(module).should_receive('make_exclude_flags').and_return(())
  268. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  269. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  270. )
  271. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  272. dry_run=False,
  273. repository_path='repo',
  274. config={
  275. 'source_directories': ['foo', 'bar'],
  276. 'repositories': ['repo'],
  277. 'patterns': ['pattern'],
  278. },
  279. patterns=[Pattern('foo'), Pattern('bar')],
  280. local_borg_version='1.2.3',
  281. global_arguments=flexmock(log_json=False),
  282. borgmatic_runtime_directory='/run/borgmatic',
  283. )
  284. assert create_flags == ('borg', 'create') + pattern_flags
  285. assert create_positional_arguments == (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  286. assert pattern_file == mock_pattern_file
  287. def test_make_base_create_command_with_store_config_false_omits_config_files():
  288. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  289. flexmock(module).should_receive('write_patterns_file').and_return(None)
  290. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  291. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  292. '{hostname}'
  293. )
  294. flexmock(module.feature).should_receive('available').and_return(True)
  295. flexmock(module).should_receive('make_exclude_flags').and_return(())
  296. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  297. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  298. )
  299. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  300. dry_run=False,
  301. repository_path='repo',
  302. config={
  303. 'source_directories': ['foo', 'bar'],
  304. 'repositories': ['repo'],
  305. 'store_config_files': False,
  306. },
  307. patterns=[Pattern('foo'), Pattern('bar')],
  308. local_borg_version='1.2.3',
  309. global_arguments=flexmock(log_json=False),
  310. borgmatic_runtime_directory='/run/borgmatic',
  311. )
  312. assert create_flags == ('borg', 'create')
  313. assert create_positional_arguments == REPO_ARCHIVE
  314. assert not pattern_file
  315. @pytest.mark.parametrize(
  316. 'option_name,option_value,feature_available,option_flags',
  317. (
  318. ('checkpoint_interval', 600, True, ('--checkpoint-interval', '600')),
  319. ('checkpoint_volume', 1024, True, ('--checkpoint-volume', '1024')),
  320. ('chunker_params', '1,2,3,4', True, ('--chunker-params', '1,2,3,4')),
  321. ('compression', 'rle', True, ('--compression', 'rle')),
  322. ('one_file_system', True, True, ('--one-file-system',)),
  323. ('upload_rate_limit', 100, True, ('--upload-ratelimit', '100')),
  324. ('upload_rate_limit', 100, False, ('--remote-ratelimit', '100')),
  325. ('upload_buffer_size', 160, True, ('--upload-buffer', '160')),
  326. ('numeric_ids', True, True, ('--numeric-ids',)),
  327. ('numeric_ids', True, False, ('--numeric-owner',)),
  328. ('read_special', True, True, ('--read-special',)),
  329. ('ctime', True, True, ()),
  330. ('ctime', False, True, ('--noctime',)),
  331. ('birthtime', True, True, ()),
  332. ('birthtime', False, True, ('--nobirthtime',)),
  333. ('atime', True, True, ('--atime',)),
  334. ('atime', True, False, ()),
  335. ('atime', False, True, ()),
  336. ('atime', False, False, ('--noatime',)),
  337. ('flags', True, True, ()),
  338. ('flags', True, False, ()),
  339. ('flags', False, True, ('--noflags',)),
  340. ('flags', False, False, ('--nobsdflags',)),
  341. ('files_cache', 'ctime,size', True, ('--files-cache', 'ctime,size')),
  342. ('umask', 740, True, ('--umask', '740')),
  343. ('lock_wait', 5, True, ('--lock-wait', '5')),
  344. ),
  345. )
  346. def test_make_base_create_command_includes_configuration_option_as_command_flag(
  347. option_name, option_value, feature_available, option_flags
  348. ):
  349. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  350. flexmock(module).should_receive('write_patterns_file').and_return(None)
  351. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  352. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  353. '{hostname}'
  354. )
  355. flexmock(module.feature).should_receive('available').and_return(feature_available)
  356. flexmock(module).should_receive('make_exclude_flags').and_return(())
  357. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  358. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  359. )
  360. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  361. dry_run=False,
  362. repository_path='repo',
  363. config={
  364. 'source_directories': ['foo', 'bar'],
  365. 'repositories': ['repo'],
  366. option_name: option_value,
  367. },
  368. patterns=[Pattern('foo'), Pattern('bar')],
  369. local_borg_version='1.2.3',
  370. global_arguments=flexmock(log_json=False),
  371. borgmatic_runtime_directory='/run/borgmatic',
  372. )
  373. assert create_flags == ('borg', 'create') + option_flags
  374. assert create_positional_arguments == REPO_ARCHIVE
  375. assert not pattern_file
  376. def test_make_base_create_command_includes_dry_run_in_borg_command():
  377. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  378. flexmock(module).should_receive('write_patterns_file').and_return(None)
  379. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  380. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  381. '{hostname}'
  382. )
  383. flexmock(module.feature).should_receive('available').and_return(True)
  384. flexmock(module).should_receive('make_exclude_flags').and_return(())
  385. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  386. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  387. )
  388. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  389. dry_run=True,
  390. repository_path='repo',
  391. config={
  392. 'source_directories': ['foo', 'bar'],
  393. 'repositories': ['repo'],
  394. 'exclude_patterns': ['exclude'],
  395. },
  396. patterns=[Pattern('foo'), Pattern('bar')],
  397. local_borg_version='1.2.3',
  398. global_arguments=flexmock(log_json=False),
  399. borgmatic_runtime_directory='/run/borgmatic',
  400. )
  401. assert create_flags == ('borg', 'create', '--dry-run')
  402. assert create_positional_arguments == REPO_ARCHIVE
  403. assert not pattern_file
  404. def test_make_base_create_command_includes_local_path_in_borg_command():
  405. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  406. flexmock(module).should_receive('write_patterns_file').and_return(None)
  407. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  408. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  409. '{hostname}'
  410. )
  411. flexmock(module.feature).should_receive('available').and_return(True)
  412. flexmock(module).should_receive('make_exclude_flags').and_return(())
  413. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  414. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  415. )
  416. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  417. dry_run=False,
  418. repository_path='repo',
  419. config={
  420. 'source_directories': ['foo', 'bar'],
  421. 'repositories': ['repo'],
  422. },
  423. patterns=[Pattern('foo'), Pattern('bar')],
  424. local_borg_version='1.2.3',
  425. global_arguments=flexmock(log_json=False),
  426. borgmatic_runtime_directory='/run/borgmatic',
  427. local_path='borg1',
  428. )
  429. assert create_flags == ('borg1', 'create')
  430. assert create_positional_arguments == REPO_ARCHIVE
  431. assert not pattern_file
  432. def test_make_base_create_command_includes_remote_path_in_borg_command():
  433. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  434. flexmock(module).should_receive('write_patterns_file').and_return(None)
  435. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  436. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  437. '{hostname}'
  438. )
  439. flexmock(module.feature).should_receive('available').and_return(True)
  440. flexmock(module).should_receive('make_exclude_flags').and_return(())
  441. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  442. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  443. )
  444. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  445. dry_run=False,
  446. repository_path='repo',
  447. config={
  448. 'source_directories': ['foo', 'bar'],
  449. 'repositories': ['repo'],
  450. },
  451. patterns=[Pattern('foo'), Pattern('bar')],
  452. local_borg_version='1.2.3',
  453. global_arguments=flexmock(log_json=False),
  454. borgmatic_runtime_directory='/run/borgmatic',
  455. remote_path='borg1',
  456. )
  457. assert create_flags == ('borg', 'create', '--remote-path', 'borg1')
  458. assert create_positional_arguments == REPO_ARCHIVE
  459. assert not pattern_file
  460. def test_make_base_create_command_includes_log_json_in_borg_command():
  461. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  462. flexmock(module).should_receive('write_patterns_file').and_return(None)
  463. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  464. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  465. '{hostname}'
  466. )
  467. flexmock(module.feature).should_receive('available').and_return(True)
  468. flexmock(module).should_receive('make_exclude_flags').and_return(())
  469. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  470. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  471. )
  472. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  473. dry_run=False,
  474. repository_path='repo',
  475. config={
  476. 'source_directories': ['foo', 'bar'],
  477. 'repositories': ['repo'],
  478. },
  479. patterns=[Pattern('foo'), Pattern('bar')],
  480. local_borg_version='1.2.3',
  481. global_arguments=flexmock(log_json=True),
  482. borgmatic_runtime_directory='/run/borgmatic',
  483. )
  484. assert create_flags == ('borg', 'create', '--log-json')
  485. assert create_positional_arguments == REPO_ARCHIVE
  486. assert not pattern_file
  487. def test_make_base_create_command_includes_list_flags_in_borg_command():
  488. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  489. flexmock(module).should_receive('write_patterns_file').and_return(None)
  490. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  491. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  492. '{hostname}'
  493. )
  494. flexmock(module.feature).should_receive('available').and_return(True)
  495. flexmock(module).should_receive('make_exclude_flags').and_return(())
  496. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  497. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  498. )
  499. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  500. dry_run=False,
  501. repository_path='repo',
  502. config={
  503. 'source_directories': ['foo', 'bar'],
  504. 'repositories': ['repo'],
  505. },
  506. patterns=[Pattern('foo'), Pattern('bar')],
  507. local_borg_version='1.2.3',
  508. global_arguments=flexmock(log_json=False),
  509. borgmatic_runtime_directory='/run/borgmatic',
  510. list_files=True,
  511. )
  512. assert create_flags == ('borg', 'create', '--list', '--filter', 'FOO')
  513. assert create_positional_arguments == REPO_ARCHIVE
  514. assert not pattern_file
  515. def test_make_base_create_command_with_stream_processes_ignores_read_special_false_and_excludes_special_files():
  516. patterns = [Pattern('foo'), Pattern('bar')]
  517. patterns_file = flexmock(name='patterns')
  518. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  519. flexmock(module).should_receive('write_patterns_file').with_args(
  520. patterns, '/run/borgmatic'
  521. ).and_return(patterns_file)
  522. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  523. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  524. '{hostname}'
  525. )
  526. flexmock(module.feature).should_receive('available').and_return(True)
  527. flexmock(module).should_receive('make_exclude_flags').and_return(())
  528. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  529. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  530. )
  531. flexmock(module.logger).should_receive('warning').twice()
  532. flexmock(module.environment).should_receive('make_environment')
  533. flexmock(module).should_receive('collect_special_file_paths').and_return(('/dev/null',)).once()
  534. flexmock(module).should_receive('write_patterns_file').with_args(
  535. (
  536. Pattern(
  537. '/dev/null',
  538. Pattern_type.NO_RECURSE,
  539. Pattern_style.FNMATCH,
  540. ),
  541. ),
  542. '/run/borgmatic',
  543. patterns_file=patterns_file,
  544. ).and_return(patterns_file).once()
  545. flexmock(module).should_receive('make_exclude_flags').and_return(())
  546. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  547. dry_run=False,
  548. repository_path='repo',
  549. config={
  550. 'source_directories': ['foo', 'bar'],
  551. 'repositories': ['repo'],
  552. 'read_special': False,
  553. },
  554. patterns=patterns,
  555. local_borg_version='1.2.3',
  556. global_arguments=flexmock(log_json=False),
  557. borgmatic_runtime_directory='/run/borgmatic',
  558. stream_processes=flexmock(),
  559. )
  560. assert create_flags == ('borg', 'create', '--patterns-from', 'patterns', '--read-special')
  561. assert create_positional_arguments == REPO_ARCHIVE
  562. assert pattern_file
  563. def test_make_base_create_command_without_patterns_and_with_stream_processes_ignores_read_special_false_and_excludes_special_files():
  564. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  565. flexmock(module).should_receive('write_patterns_file').with_args(
  566. [], '/run/borgmatic'
  567. ).and_return(None)
  568. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  569. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  570. '{hostname}'
  571. )
  572. flexmock(module.feature).should_receive('available').and_return(True)
  573. flexmock(module).should_receive('make_exclude_flags').and_return(())
  574. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  575. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  576. )
  577. flexmock(module.logger).should_receive('warning').twice()
  578. flexmock(module.environment).should_receive('make_environment')
  579. flexmock(module).should_receive('collect_special_file_paths').and_return(('/dev/null',)).once()
  580. flexmock(module).should_receive('write_patterns_file').with_args(
  581. (
  582. Pattern(
  583. '/dev/null',
  584. Pattern_type.NO_RECURSE,
  585. Pattern_style.FNMATCH,
  586. ),
  587. ),
  588. '/run/borgmatic',
  589. patterns_file=None,
  590. ).and_return(flexmock(name='patterns')).once()
  591. flexmock(module).should_receive('make_exclude_flags').and_return(())
  592. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  593. dry_run=False,
  594. repository_path='repo',
  595. config={
  596. 'source_directories': [],
  597. 'repositories': ['repo'],
  598. 'read_special': False,
  599. },
  600. patterns=[],
  601. local_borg_version='1.2.3',
  602. global_arguments=flexmock(log_json=False),
  603. borgmatic_runtime_directory='/run/borgmatic',
  604. stream_processes=flexmock(),
  605. )
  606. assert create_flags == ('borg', 'create', '--read-special', '--patterns-from', 'patterns')
  607. assert create_positional_arguments == REPO_ARCHIVE
  608. assert pattern_file
  609. def test_make_base_create_command_with_stream_processes_and_read_special_true_skips_special_files_excludes():
  610. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  611. flexmock(module).should_receive('write_patterns_file').and_return(None)
  612. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  613. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  614. '{hostname}'
  615. )
  616. flexmock(module.feature).should_receive('available').and_return(True)
  617. flexmock(module).should_receive('make_exclude_flags').and_return(())
  618. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  619. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  620. )
  621. flexmock(module.logger).should_receive('warning').never()
  622. flexmock(module).should_receive('collect_special_file_paths').never()
  623. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  624. dry_run=False,
  625. repository_path='repo',
  626. config={
  627. 'source_directories': ['foo', 'bar'],
  628. 'repositories': ['repo'],
  629. 'read_special': True,
  630. },
  631. patterns=[Pattern('foo'), Pattern('bar')],
  632. local_borg_version='1.2.3',
  633. global_arguments=flexmock(log_json=False),
  634. borgmatic_runtime_directory='/run/borgmatic',
  635. stream_processes=flexmock(),
  636. )
  637. assert create_flags == ('borg', 'create', '--read-special')
  638. assert create_positional_arguments == REPO_ARCHIVE
  639. assert not pattern_file
  640. def test_make_base_create_command_includes_archive_name_format_in_borg_command():
  641. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  642. flexmock(module).should_receive('write_patterns_file').and_return(None)
  643. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  644. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  645. '{hostname}'
  646. )
  647. flexmock(module.feature).should_receive('available').and_return(True)
  648. flexmock(module).should_receive('make_exclude_flags').and_return(())
  649. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  650. ('repo::ARCHIVE_NAME',)
  651. )
  652. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  653. dry_run=False,
  654. repository_path='repo',
  655. config={
  656. 'source_directories': ['foo', 'bar'],
  657. 'repositories': ['repo'],
  658. 'archive_name_format': 'ARCHIVE_NAME',
  659. },
  660. patterns=[Pattern('foo'), Pattern('bar')],
  661. local_borg_version='1.2.3',
  662. global_arguments=flexmock(log_json=False),
  663. borgmatic_runtime_directory='/run/borgmatic',
  664. )
  665. assert create_flags == ('borg', 'create')
  666. assert create_positional_arguments == ('repo::ARCHIVE_NAME',)
  667. assert not pattern_file
  668. def test_make_base_create_command_includes_default_archive_name_format_in_borg_command():
  669. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  670. flexmock(module).should_receive('write_patterns_file').and_return(None)
  671. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  672. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  673. '{hostname}'
  674. )
  675. flexmock(module.feature).should_receive('available').and_return(True)
  676. flexmock(module).should_receive('make_exclude_flags').and_return(())
  677. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  678. ('repo::{hostname}',)
  679. )
  680. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  681. dry_run=False,
  682. repository_path='repo',
  683. config={
  684. 'source_directories': ['foo', 'bar'],
  685. 'repositories': ['repo'],
  686. },
  687. patterns=[Pattern('foo'), Pattern('bar')],
  688. local_borg_version='1.2.3',
  689. global_arguments=flexmock(log_json=False),
  690. borgmatic_runtime_directory='/run/borgmatic',
  691. )
  692. assert create_flags == ('borg', 'create')
  693. assert create_positional_arguments == ('repo::{hostname}',)
  694. assert not pattern_file
  695. def test_make_base_create_command_includes_archive_name_format_with_placeholders_in_borg_command():
  696. repository_archive_pattern = 'repo::Documents_{hostname}-{now}' # noqa: FS003
  697. flexmock(module).should_receive('write_patterns_file').and_return(None)
  698. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  699. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  700. '{hostname}'
  701. )
  702. flexmock(module.feature).should_receive('available').and_return(True)
  703. flexmock(module).should_receive('make_exclude_flags').and_return(())
  704. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  705. (repository_archive_pattern,)
  706. )
  707. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  708. dry_run=False,
  709. repository_path='repo',
  710. config={
  711. 'source_directories': ['foo', 'bar'],
  712. 'repositories': ['repo'],
  713. 'archive_name_format': 'Documents_{hostname}-{now}', # noqa: FS003
  714. },
  715. patterns=[Pattern('foo'), Pattern('bar')],
  716. local_borg_version='1.2.3',
  717. global_arguments=flexmock(log_json=False),
  718. borgmatic_runtime_directory='/run/borgmatic',
  719. )
  720. assert create_flags == ('borg', 'create')
  721. assert create_positional_arguments == (repository_archive_pattern,)
  722. assert not pattern_file
  723. def test_make_base_create_command_includes_repository_and_archive_name_format_with_placeholders_in_borg_command():
  724. repository_archive_pattern = '{fqdn}::Documents_{hostname}-{now}' # noqa: FS003
  725. flexmock(module).should_receive('write_patterns_file').and_return(None)
  726. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  727. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  728. '{hostname}'
  729. )
  730. flexmock(module.feature).should_receive('available').and_return(True)
  731. flexmock(module).should_receive('make_exclude_flags').and_return(())
  732. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  733. (repository_archive_pattern,)
  734. )
  735. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  736. dry_run=False,
  737. repository_path='{fqdn}', # noqa: FS003
  738. config={
  739. 'source_directories': ['foo', 'bar'],
  740. 'repositories': ['{fqdn}'], # noqa: FS003
  741. 'archive_name_format': 'Documents_{hostname}-{now}', # noqa: FS003
  742. },
  743. patterns=[Pattern('foo'), Pattern('bar')],
  744. local_borg_version='1.2.3',
  745. global_arguments=flexmock(log_json=False),
  746. borgmatic_runtime_directory='/run/borgmatic',
  747. )
  748. assert create_flags == ('borg', 'create')
  749. assert create_positional_arguments == (repository_archive_pattern,)
  750. assert not pattern_file
  751. def test_make_base_create_command_includes_extra_borg_options_in_borg_command():
  752. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  753. flexmock(module).should_receive('write_patterns_file').and_return(None)
  754. flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
  755. flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
  756. '{hostname}'
  757. )
  758. flexmock(module.feature).should_receive('available').and_return(True)
  759. flexmock(module).should_receive('make_exclude_flags').and_return(())
  760. flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
  761. (f'repo::{DEFAULT_ARCHIVE_NAME}',)
  762. )
  763. (create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
  764. dry_run=False,
  765. repository_path='repo',
  766. config={
  767. 'source_directories': ['foo', 'bar'],
  768. 'repositories': ['repo'],
  769. 'extra_borg_options': {'create': '--extra --options'},
  770. },
  771. patterns=[Pattern('foo'), Pattern('bar')],
  772. local_borg_version='1.2.3',
  773. global_arguments=flexmock(log_json=False),
  774. borgmatic_runtime_directory='/run/borgmatic',
  775. )
  776. assert create_flags == ('borg', 'create', '--extra', '--options')
  777. assert create_positional_arguments == REPO_ARCHIVE
  778. assert not pattern_file
  779. def test_make_base_create_command_with_non_existent_directory_and_source_directories_must_exist_raises():
  780. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  781. flexmock(module).should_receive('check_all_root_patterns_exist').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(log_json=False),
  794. borgmatic_runtime_directory='/run/borgmatic',
  795. )
  796. def test_create_archive_calls_borg_with_parameters():
  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. extra_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(log_json=False),
  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. extra_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(log_json=False),
  855. borgmatic_runtime_directory='/borgmatic/run',
  856. )
  857. def test_create_archive_with_log_info_calls_borg_with_info_parameter():
  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. extra_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(log_json=False),
  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. extra_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(log_json=False),
  915. borgmatic_runtime_directory='/borgmatic/run',
  916. json=True,
  917. )
  918. def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
  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. extra_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(log_json=False),
  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. extra_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(log_json=False),
  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. extra_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(log_json=False),
  1010. borgmatic_runtime_directory='/borgmatic/run',
  1011. stats=True,
  1012. )
  1013. def test_create_archive_with_working_directory_calls_borg_with_working_directory():
  1014. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1015. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1016. flexmock(module).should_receive('make_base_create_command').and_return(
  1017. (('borg', 'create'), REPO_ARCHIVE, flexmock())
  1018. )
  1019. flexmock(module.environment).should_receive('make_environment')
  1020. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  1021. '/working/dir'
  1022. )
  1023. flexmock(module).should_receive('execute_command').with_args(
  1024. ('borg', 'create') + REPO_ARCHIVE,
  1025. output_log_level=logging.INFO,
  1026. output_file=None,
  1027. borg_local_path='borg',
  1028. borg_exit_codes=None,
  1029. working_directory='/working/dir',
  1030. extra_environment=None,
  1031. )
  1032. module.create_archive(
  1033. dry_run=False,
  1034. repository_path='repo',
  1035. config={
  1036. 'source_directories': ['foo', 'bar'],
  1037. 'repositories': ['repo'],
  1038. 'working_directory': '/working/dir',
  1039. 'exclude_patterns': None,
  1040. },
  1041. patterns=[Pattern('foo'), Pattern('bar')],
  1042. local_borg_version='1.2.3',
  1043. global_arguments=flexmock(log_json=False),
  1044. borgmatic_runtime_directory='/borgmatic/run',
  1045. )
  1046. def test_create_archive_with_exit_codes_calls_borg_using_them():
  1047. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1048. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1049. flexmock(module).should_receive('make_base_create_command').and_return(
  1050. (('borg', 'create'), REPO_ARCHIVE, flexmock())
  1051. )
  1052. flexmock(module.environment).should_receive('make_environment')
  1053. borg_exit_codes = flexmock()
  1054. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1055. flexmock(module).should_receive('execute_command').with_args(
  1056. ('borg', 'create') + REPO_ARCHIVE,
  1057. output_log_level=logging.INFO,
  1058. output_file=None,
  1059. borg_local_path='borg',
  1060. borg_exit_codes=borg_exit_codes,
  1061. working_directory=None,
  1062. extra_environment=None,
  1063. )
  1064. module.create_archive(
  1065. dry_run=False,
  1066. repository_path='repo',
  1067. config={
  1068. 'source_directories': ['foo', 'bar'],
  1069. 'repositories': ['repo'],
  1070. 'exclude_patterns': None,
  1071. 'borg_exit_codes': borg_exit_codes,
  1072. },
  1073. patterns=[Pattern('foo'), Pattern('bar')],
  1074. local_borg_version='1.2.3',
  1075. global_arguments=flexmock(log_json=False),
  1076. borgmatic_runtime_directory='/borgmatic/run',
  1077. )
  1078. def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_answer_output_log_level():
  1079. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1080. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1081. flexmock(module).should_receive('make_base_create_command').and_return(
  1082. (('borg', 'create'), REPO_ARCHIVE, flexmock())
  1083. )
  1084. flexmock(module.environment).should_receive('make_environment')
  1085. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1086. flexmock(module).should_receive('execute_command').with_args(
  1087. ('borg', 'create', '--stats') + REPO_ARCHIVE,
  1088. output_log_level=module.borgmatic.logger.ANSWER,
  1089. output_file=None,
  1090. borg_local_path='borg',
  1091. borg_exit_codes=None,
  1092. working_directory=None,
  1093. extra_environment=None,
  1094. )
  1095. module.create_archive(
  1096. dry_run=False,
  1097. repository_path='repo',
  1098. config={
  1099. 'source_directories': ['foo', 'bar'],
  1100. 'repositories': ['repo'],
  1101. 'exclude_patterns': None,
  1102. },
  1103. patterns=[Pattern('foo'), Pattern('bar')],
  1104. local_borg_version='1.2.3',
  1105. global_arguments=flexmock(log_json=False),
  1106. borgmatic_runtime_directory='/borgmatic/run',
  1107. stats=True,
  1108. )
  1109. def test_create_archive_with_files_calls_borg_with_answer_output_log_level():
  1110. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1111. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1112. flexmock(module).should_receive('make_base_create_command').and_return(
  1113. (
  1114. ('borg', 'create', '--list', '--filter', 'FOO'),
  1115. REPO_ARCHIVE,
  1116. flexmock(),
  1117. )
  1118. )
  1119. flexmock(module.environment).should_receive('make_environment')
  1120. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1121. flexmock(module).should_receive('execute_command').with_args(
  1122. ('borg', 'create', '--list', '--filter', 'FOO') + REPO_ARCHIVE,
  1123. output_log_level=module.borgmatic.logger.ANSWER,
  1124. output_file=None,
  1125. borg_local_path='borg',
  1126. borg_exit_codes=None,
  1127. working_directory=None,
  1128. extra_environment=None,
  1129. )
  1130. module.create_archive(
  1131. dry_run=False,
  1132. repository_path='repo',
  1133. config={
  1134. 'source_directories': ['foo', 'bar'],
  1135. 'repositories': ['repo'],
  1136. 'exclude_patterns': None,
  1137. },
  1138. patterns=[Pattern('foo'), Pattern('bar')],
  1139. local_borg_version='1.2.3',
  1140. global_arguments=flexmock(log_json=False),
  1141. borgmatic_runtime_directory='/borgmatic/run',
  1142. list_files=True,
  1143. )
  1144. def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_parameter_and_no_list():
  1145. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1146. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1147. flexmock(module).should_receive('make_base_create_command').and_return(
  1148. (('borg', 'create'), REPO_ARCHIVE, flexmock())
  1149. )
  1150. flexmock(module.environment).should_receive('make_environment')
  1151. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1152. flexmock(module).should_receive('execute_command').with_args(
  1153. ('borg', 'create', '--info', '--progress') + REPO_ARCHIVE,
  1154. output_log_level=logging.INFO,
  1155. output_file=module.DO_NOT_CAPTURE,
  1156. borg_local_path='borg',
  1157. borg_exit_codes=None,
  1158. working_directory=None,
  1159. extra_environment=None,
  1160. )
  1161. insert_logging_mock(logging.INFO)
  1162. module.create_archive(
  1163. dry_run=False,
  1164. repository_path='repo',
  1165. config={
  1166. 'source_directories': ['foo', 'bar'],
  1167. 'repositories': ['repo'],
  1168. 'exclude_patterns': None,
  1169. },
  1170. patterns=[Pattern('foo'), Pattern('bar')],
  1171. local_borg_version='1.2.3',
  1172. global_arguments=flexmock(log_json=False),
  1173. borgmatic_runtime_directory='/borgmatic/run',
  1174. progress=True,
  1175. )
  1176. def test_create_archive_with_progress_calls_borg_with_progress_parameter():
  1177. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1178. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1179. flexmock(module).should_receive('make_base_create_command').and_return(
  1180. (('borg', 'create'), REPO_ARCHIVE, flexmock())
  1181. )
  1182. flexmock(module.environment).should_receive('make_environment')
  1183. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1184. flexmock(module).should_receive('execute_command').with_args(
  1185. ('borg', 'create', '--progress') + REPO_ARCHIVE,
  1186. output_log_level=logging.INFO,
  1187. output_file=module.DO_NOT_CAPTURE,
  1188. borg_local_path='borg',
  1189. borg_exit_codes=None,
  1190. working_directory=None,
  1191. extra_environment=None,
  1192. )
  1193. module.create_archive(
  1194. dry_run=False,
  1195. repository_path='repo',
  1196. config={
  1197. 'source_directories': ['foo', 'bar'],
  1198. 'repositories': ['repo'],
  1199. 'exclude_patterns': None,
  1200. },
  1201. patterns=[Pattern('foo'), Pattern('bar')],
  1202. local_borg_version='1.2.3',
  1203. global_arguments=flexmock(log_json=False),
  1204. borgmatic_runtime_directory='/borgmatic/run',
  1205. progress=True,
  1206. )
  1207. def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progress_parameter():
  1208. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1209. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1210. processes = flexmock()
  1211. flexmock(module).should_receive('make_base_create_command').and_return(
  1212. (
  1213. ('borg', 'create', '--read-special'),
  1214. REPO_ARCHIVE,
  1215. flexmock(),
  1216. )
  1217. )
  1218. flexmock(module.environment).should_receive('make_environment')
  1219. create_command = (
  1220. 'borg',
  1221. 'create',
  1222. '--read-special',
  1223. '--progress',
  1224. ) + REPO_ARCHIVE
  1225. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1226. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1227. create_command + ('--dry-run', '--list'),
  1228. processes=processes,
  1229. output_log_level=logging.INFO,
  1230. output_file=module.DO_NOT_CAPTURE,
  1231. borg_local_path='borg',
  1232. borg_exit_codes=None,
  1233. working_directory=None,
  1234. extra_environment=None,
  1235. )
  1236. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1237. create_command,
  1238. processes=processes,
  1239. output_log_level=logging.INFO,
  1240. output_file=module.DO_NOT_CAPTURE,
  1241. borg_local_path='borg',
  1242. borg_exit_codes=None,
  1243. working_directory=None,
  1244. extra_environment=None,
  1245. )
  1246. module.create_archive(
  1247. dry_run=False,
  1248. repository_path='repo',
  1249. config={
  1250. 'source_directories': ['foo', 'bar'],
  1251. 'repositories': ['repo'],
  1252. 'exclude_patterns': None,
  1253. },
  1254. patterns=[Pattern('foo'), Pattern('bar')],
  1255. local_borg_version='1.2.3',
  1256. global_arguments=flexmock(log_json=False),
  1257. borgmatic_runtime_directory='/borgmatic/run',
  1258. progress=True,
  1259. stream_processes=processes,
  1260. )
  1261. def test_create_archive_with_json_calls_borg_with_json_flag():
  1262. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1263. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1264. flexmock(module).should_receive('make_base_create_command').and_return(
  1265. (('borg', 'create'), REPO_ARCHIVE, flexmock())
  1266. )
  1267. flexmock(module.environment).should_receive('make_environment')
  1268. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1269. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  1270. ('borg', 'create', '--json') + REPO_ARCHIVE,
  1271. working_directory=None,
  1272. extra_environment=None,
  1273. borg_local_path='borg',
  1274. borg_exit_codes=None,
  1275. ).and_return('[]')
  1276. json_output = module.create_archive(
  1277. dry_run=False,
  1278. repository_path='repo',
  1279. config={
  1280. 'source_directories': ['foo', 'bar'],
  1281. 'repositories': ['repo'],
  1282. 'exclude_patterns': None,
  1283. },
  1284. patterns=[Pattern('foo'), Pattern('bar')],
  1285. local_borg_version='1.2.3',
  1286. global_arguments=flexmock(log_json=False),
  1287. borgmatic_runtime_directory='/borgmatic/run',
  1288. json=True,
  1289. )
  1290. assert json_output == '[]'
  1291. def test_create_archive_with_stats_and_json_calls_borg_without_stats_flag():
  1292. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1293. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1294. flexmock(module).should_receive('make_base_create_command').and_return(
  1295. (('borg', 'create'), REPO_ARCHIVE, flexmock())
  1296. )
  1297. flexmock(module.environment).should_receive('make_environment')
  1298. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1299. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  1300. ('borg', 'create', '--json') + REPO_ARCHIVE,
  1301. working_directory=None,
  1302. extra_environment=None,
  1303. borg_local_path='borg',
  1304. borg_exit_codes=None,
  1305. ).and_return('[]')
  1306. json_output = module.create_archive(
  1307. dry_run=False,
  1308. repository_path='repo',
  1309. config={
  1310. 'source_directories': ['foo*'],
  1311. 'repositories': ['repo'],
  1312. 'exclude_patterns': None,
  1313. },
  1314. patterns=[Pattern('foo'), Pattern('bar')],
  1315. local_borg_version='1.2.3',
  1316. global_arguments=flexmock(log_json=False),
  1317. borgmatic_runtime_directory='/borgmatic/run',
  1318. json=True,
  1319. stats=True,
  1320. )
  1321. assert json_output == '[]'
  1322. def test_create_archive_calls_borg_with_working_directory():
  1323. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  1324. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  1325. flexmock(module).should_receive('make_base_create_command').and_return(
  1326. (('borg', 'create'), REPO_ARCHIVE, flexmock())
  1327. )
  1328. flexmock(module.environment).should_receive('make_environment')
  1329. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  1330. '/working/dir'
  1331. )
  1332. flexmock(module).should_receive('execute_command').with_args(
  1333. ('borg', 'create') + REPO_ARCHIVE,
  1334. output_log_level=logging.INFO,
  1335. output_file=None,
  1336. borg_local_path='borg',
  1337. borg_exit_codes=None,
  1338. working_directory='/working/dir',
  1339. extra_environment=None,
  1340. )
  1341. module.create_archive(
  1342. dry_run=False,
  1343. repository_path='repo',
  1344. config={
  1345. 'source_directories': ['foo', 'bar'],
  1346. 'repositories': ['repo'],
  1347. 'exclude_patterns': None,
  1348. 'working_directory': '/working/dir',
  1349. },
  1350. patterns=[Pattern('foo'), Pattern('bar')],
  1351. local_borg_version='1.2.3',
  1352. global_arguments=flexmock(log_json=False),
  1353. borgmatic_runtime_directory='/borgmatic/run',
  1354. )
  1355. def test_check_all_root_patterns_exist_with_existent_pattern_path_does_not_raise():
  1356. flexmock(module.os.path).should_receive('exists').and_return(True)
  1357. module.check_all_root_patterns_exist([Pattern('foo')])
  1358. def test_check_all_root_patterns_exist_with_non_root_pattern_skips_existence_check():
  1359. flexmock(module.os.path).should_receive('exists').never()
  1360. module.check_all_root_patterns_exist([Pattern('foo', Pattern_type.INCLUDE)])
  1361. def test_check_all_root_patterns_exist_with_non_existent_pattern_path_raises():
  1362. flexmock(module.os.path).should_receive('exists').and_return(False)
  1363. with pytest.raises(ValueError):
  1364. module.check_all_root_patterns_exist([Pattern('foo')])