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