test_create.py 63 KB

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