test_create.py 63 KB

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