test_create.py 60 KB

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