test_create.py 63 KB

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