test_create.py 63 KB

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