test_create.py 63 KB

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