test_create.py 71 KB

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