test_borgmatic.py 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337
  1. import logging
  2. import subprocess
  3. import time
  4. from flexmock import flexmock
  5. import borgmatic.hooks.command
  6. from borgmatic.commands import borgmatic as module
  7. def test_run_configuration_runs_actions_for_each_repository():
  8. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  9. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  10. expected_results = [flexmock(), flexmock()]
  11. flexmock(module).should_receive('run_actions').and_return(expected_results[:1]).and_return(
  12. expected_results[1:]
  13. )
  14. config = {'repositories': [{'path': 'foo'}, {'path': 'bar'}]}
  15. arguments = {'global': flexmock(monitoring_verbosity=1)}
  16. results = list(module.run_configuration('test.yaml', config, arguments))
  17. assert results == expected_results
  18. def test_run_configuration_with_skip_actions_does_not_raise():
  19. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  20. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  21. flexmock(module).should_receive('run_actions').and_return(flexmock()).and_return(flexmock())
  22. config = {'repositories': [{'path': 'foo'}, {'path': 'bar'}], 'skip_actions': ['compact']}
  23. arguments = {'global': flexmock(monitoring_verbosity=1)}
  24. list(module.run_configuration('test.yaml', config, arguments))
  25. def test_run_configuration_with_invalid_borg_version_errors():
  26. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  27. flexmock(module.borg_version).should_receive('local_borg_version').and_raise(ValueError)
  28. flexmock(module.command).should_receive('execute_hook').never()
  29. flexmock(module.dispatch).should_receive('call_hooks').never()
  30. flexmock(module).should_receive('run_actions').never()
  31. config = {'repositories': ['foo']}
  32. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'prune': flexmock()}
  33. list(module.run_configuration('test.yaml', config, arguments))
  34. def test_run_configuration_logs_monitor_start_error():
  35. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  36. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  37. flexmock(module.dispatch).should_receive('call_hooks').and_raise(OSError).and_return(
  38. None
  39. ).and_return(None).and_return(None)
  40. expected_results = [flexmock()]
  41. flexmock(module).should_receive('log_error_records').and_return(expected_results)
  42. flexmock(module).should_receive('run_actions').never()
  43. config = {'repositories': ['foo']}
  44. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  45. results = list(module.run_configuration('test.yaml', config, arguments))
  46. assert results == expected_results
  47. def test_run_configuration_bails_for_monitor_start_soft_failure():
  48. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  49. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  50. error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
  51. flexmock(module.dispatch).should_receive('call_hooks').and_raise(error)
  52. flexmock(module).should_receive('log_error_records').never()
  53. flexmock(module).should_receive('run_actions').never()
  54. config = {'repositories': ['foo']}
  55. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  56. results = list(module.run_configuration('test.yaml', config, arguments))
  57. assert results == []
  58. def test_run_configuration_logs_actions_error():
  59. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  60. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  61. flexmock(module.command).should_receive('execute_hook')
  62. flexmock(module.dispatch).should_receive('call_hooks')
  63. expected_results = [flexmock()]
  64. flexmock(module).should_receive('log_error_records').and_return(expected_results)
  65. flexmock(module).should_receive('run_actions').and_raise(OSError)
  66. config = {'repositories': [{'path': 'foo'}]}
  67. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False)}
  68. results = list(module.run_configuration('test.yaml', config, arguments))
  69. assert results == expected_results
  70. def test_run_configuration_bails_for_actions_soft_failure():
  71. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  72. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  73. flexmock(module.dispatch).should_receive('call_hooks')
  74. error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
  75. flexmock(module).should_receive('run_actions').and_raise(error)
  76. flexmock(module).should_receive('log_error_records').never()
  77. flexmock(module.command).should_receive('considered_soft_failure').and_return(True)
  78. config = {'repositories': [{'path': 'foo'}]}
  79. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  80. results = list(module.run_configuration('test.yaml', config, arguments))
  81. assert results == []
  82. def test_run_configuration_logs_monitor_log_error():
  83. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  84. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  85. flexmock(module.dispatch).should_receive('call_hooks').and_return(None).and_return(
  86. None
  87. ).and_raise(OSError)
  88. expected_results = [flexmock()]
  89. flexmock(module).should_receive('log_error_records').and_return(expected_results)
  90. flexmock(module).should_receive('run_actions').and_return([])
  91. config = {'repositories': [{'path': 'foo'}]}
  92. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  93. results = list(module.run_configuration('test.yaml', config, arguments))
  94. assert results == expected_results
  95. def test_run_configuration_bails_for_monitor_log_soft_failure():
  96. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  97. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  98. error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
  99. flexmock(module.dispatch).should_receive('call_hooks').and_return(None).and_return(
  100. None
  101. ).and_raise(error)
  102. flexmock(module).should_receive('log_error_records').never()
  103. flexmock(module).should_receive('run_actions').and_return([])
  104. flexmock(module.command).should_receive('considered_soft_failure').and_return(True)
  105. config = {'repositories': [{'path': 'foo'}]}
  106. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  107. results = list(module.run_configuration('test.yaml', config, arguments))
  108. assert results == []
  109. def test_run_configuration_logs_monitor_finish_error():
  110. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  111. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  112. flexmock(module.dispatch).should_receive('call_hooks').and_return(None).and_return(
  113. None
  114. ).and_return(None).and_raise(OSError)
  115. expected_results = [flexmock()]
  116. flexmock(module).should_receive('log_error_records').and_return(expected_results)
  117. flexmock(module).should_receive('run_actions').and_return([])
  118. config = {'repositories': [{'path': 'foo'}]}
  119. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  120. results = list(module.run_configuration('test.yaml', config, arguments))
  121. assert results == expected_results
  122. def test_run_configuration_bails_for_monitor_finish_soft_failure():
  123. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  124. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  125. error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
  126. flexmock(module.dispatch).should_receive('call_hooks').and_return(None).and_return(
  127. None
  128. ).and_raise(None).and_raise(error)
  129. flexmock(module).should_receive('log_error_records').never()
  130. flexmock(module).should_receive('run_actions').and_return([])
  131. flexmock(module.command).should_receive('considered_soft_failure').and_return(True)
  132. config = {'repositories': [{'path': 'foo'}]}
  133. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  134. results = list(module.run_configuration('test.yaml', config, arguments))
  135. assert results == []
  136. def test_run_configuration_does_not_call_monitoring_hooks_if_monitoring_hooks_are_disabled():
  137. flexmock(module).should_receive('verbosity_to_log_level').and_return(module.DISABLED)
  138. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  139. flexmock(module.dispatch).should_receive('call_hooks').never()
  140. flexmock(module).should_receive('run_actions').and_return([])
  141. config = {'repositories': [{'path': 'foo'}]}
  142. arguments = {'global': flexmock(monitoring_verbosity=-2, dry_run=False), 'create': flexmock()}
  143. results = list(module.run_configuration('test.yaml', config, arguments))
  144. assert results == []
  145. def test_run_configuration_logs_on_error_hook_error():
  146. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  147. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  148. flexmock(module.command).should_receive('execute_hook').and_raise(OSError)
  149. expected_results = [flexmock(), flexmock()]
  150. flexmock(module).should_receive('log_error_records').and_return(
  151. expected_results[:1]
  152. ).and_return(expected_results[1:])
  153. flexmock(module).should_receive('run_actions').and_raise(OSError)
  154. config = {'repositories': [{'path': 'foo'}]}
  155. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  156. results = list(module.run_configuration('test.yaml', config, arguments))
  157. assert results == expected_results
  158. def test_run_configuration_bails_for_on_error_hook_soft_failure():
  159. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  160. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  161. error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
  162. flexmock(module.command).should_receive('execute_hook').and_raise(error)
  163. expected_results = [flexmock()]
  164. flexmock(module).should_receive('log_error_records').and_return(expected_results)
  165. flexmock(module).should_receive('run_actions').and_raise(OSError)
  166. config = {'repositories': [{'path': 'foo'}]}
  167. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  168. results = list(module.run_configuration('test.yaml', config, arguments))
  169. assert results == expected_results
  170. def test_run_configuration_retries_soft_error():
  171. # Run action first fails, second passes
  172. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  173. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  174. flexmock(module.command).should_receive('execute_hook')
  175. flexmock(module).should_receive('run_actions').and_raise(OSError).and_return([])
  176. flexmock(module).should_receive('log_error_records').and_return([flexmock()]).once()
  177. config = {'repositories': [{'path': 'foo'}], 'retries': 1}
  178. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  179. results = list(module.run_configuration('test.yaml', config, arguments))
  180. assert results == []
  181. def test_run_configuration_retries_hard_error():
  182. # Run action fails twice
  183. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  184. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  185. flexmock(module.command).should_receive('execute_hook')
  186. flexmock(module).should_receive('run_actions').and_raise(OSError).times(2)
  187. flexmock(module).should_receive('log_error_records').with_args(
  188. 'foo: Error running actions for repository',
  189. OSError,
  190. levelno=logging.WARNING,
  191. log_command_error_output=True,
  192. ).and_return([flexmock()])
  193. error_logs = [flexmock()]
  194. flexmock(module).should_receive('log_error_records').with_args(
  195. 'foo: Error running actions for repository',
  196. OSError,
  197. ).and_return(error_logs)
  198. config = {'repositories': [{'path': 'foo'}], 'retries': 1}
  199. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  200. results = list(module.run_configuration('test.yaml', config, arguments))
  201. assert results == error_logs
  202. def test_run_configuration_repos_ordered():
  203. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  204. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  205. flexmock(module.command).should_receive('execute_hook')
  206. flexmock(module).should_receive('run_actions').and_raise(OSError).times(2)
  207. expected_results = [flexmock(), flexmock()]
  208. flexmock(module).should_receive('log_error_records').with_args(
  209. 'foo: Error running actions for repository', OSError
  210. ).and_return(expected_results[:1]).ordered()
  211. flexmock(module).should_receive('log_error_records').with_args(
  212. 'bar: Error running actions for repository', OSError
  213. ).and_return(expected_results[1:]).ordered()
  214. config = {'repositories': [{'path': 'foo'}, {'path': 'bar'}]}
  215. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  216. results = list(module.run_configuration('test.yaml', config, arguments))
  217. assert results == expected_results
  218. def test_run_configuration_retries_round_robin():
  219. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  220. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  221. flexmock(module.command).should_receive('execute_hook')
  222. flexmock(module).should_receive('run_actions').and_raise(OSError).times(4)
  223. flexmock(module).should_receive('log_error_records').with_args(
  224. 'foo: Error running actions for repository',
  225. OSError,
  226. levelno=logging.WARNING,
  227. log_command_error_output=True,
  228. ).and_return([flexmock()]).ordered()
  229. flexmock(module).should_receive('log_error_records').with_args(
  230. 'bar: Error running actions for repository',
  231. OSError,
  232. levelno=logging.WARNING,
  233. log_command_error_output=True,
  234. ).and_return([flexmock()]).ordered()
  235. foo_error_logs = [flexmock()]
  236. flexmock(module).should_receive('log_error_records').with_args(
  237. 'foo: Error running actions for repository', OSError
  238. ).and_return(foo_error_logs).ordered()
  239. bar_error_logs = [flexmock()]
  240. flexmock(module).should_receive('log_error_records').with_args(
  241. 'bar: Error running actions for repository', OSError
  242. ).and_return(bar_error_logs).ordered()
  243. config = {
  244. 'repositories': [{'path': 'foo'}, {'path': 'bar'}],
  245. 'retries': 1,
  246. }
  247. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  248. results = list(module.run_configuration('test.yaml', config, arguments))
  249. assert results == foo_error_logs + bar_error_logs
  250. def test_run_configuration_retries_one_passes():
  251. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  252. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  253. flexmock(module.command).should_receive('execute_hook')
  254. flexmock(module).should_receive('run_actions').and_raise(OSError).and_raise(OSError).and_return(
  255. []
  256. ).and_raise(OSError).times(4)
  257. flexmock(module).should_receive('log_error_records').with_args(
  258. 'foo: Error running actions for repository',
  259. OSError,
  260. levelno=logging.WARNING,
  261. log_command_error_output=True,
  262. ).and_return([flexmock()]).ordered()
  263. flexmock(module).should_receive('log_error_records').with_args(
  264. 'bar: Error running actions for repository',
  265. OSError,
  266. levelno=logging.WARNING,
  267. log_command_error_output=True,
  268. ).and_return(flexmock()).ordered()
  269. error_logs = [flexmock()]
  270. flexmock(module).should_receive('log_error_records').with_args(
  271. 'bar: Error running actions for repository', OSError
  272. ).and_return(error_logs).ordered()
  273. config = {
  274. 'repositories': [{'path': 'foo'}, {'path': 'bar'}],
  275. 'retries': 1,
  276. }
  277. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  278. results = list(module.run_configuration('test.yaml', config, arguments))
  279. assert results == error_logs
  280. def test_run_configuration_retry_wait():
  281. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  282. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  283. flexmock(module.command).should_receive('execute_hook')
  284. flexmock(module).should_receive('run_actions').and_raise(OSError).times(4)
  285. flexmock(module).should_receive('log_error_records').with_args(
  286. 'foo: Error running actions for repository',
  287. OSError,
  288. levelno=logging.WARNING,
  289. log_command_error_output=True,
  290. ).and_return([flexmock()]).ordered()
  291. flexmock(time).should_receive('sleep').with_args(10).and_return().ordered()
  292. flexmock(module).should_receive('log_error_records').with_args(
  293. 'foo: Error running actions for repository',
  294. OSError,
  295. levelno=logging.WARNING,
  296. log_command_error_output=True,
  297. ).and_return([flexmock()]).ordered()
  298. flexmock(time).should_receive('sleep').with_args(20).and_return().ordered()
  299. flexmock(module).should_receive('log_error_records').with_args(
  300. 'foo: Error running actions for repository',
  301. OSError,
  302. levelno=logging.WARNING,
  303. log_command_error_output=True,
  304. ).and_return([flexmock()]).ordered()
  305. flexmock(time).should_receive('sleep').with_args(30).and_return().ordered()
  306. error_logs = [flexmock()]
  307. flexmock(module).should_receive('log_error_records').with_args(
  308. 'foo: Error running actions for repository', OSError
  309. ).and_return(error_logs).ordered()
  310. config = {
  311. 'repositories': [{'path': 'foo'}],
  312. 'retries': 3,
  313. 'retry_wait': 10,
  314. }
  315. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  316. results = list(module.run_configuration('test.yaml', config, arguments))
  317. assert results == error_logs
  318. def test_run_configuration_retries_timeout_multiple_repos():
  319. flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
  320. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  321. flexmock(module.command).should_receive('execute_hook')
  322. flexmock(module).should_receive('run_actions').and_raise(OSError).and_raise(OSError).and_return(
  323. []
  324. ).and_raise(OSError).times(4)
  325. flexmock(module).should_receive('log_error_records').with_args(
  326. 'foo: Error running actions for repository',
  327. OSError,
  328. levelno=logging.WARNING,
  329. log_command_error_output=True,
  330. ).and_return([flexmock()]).ordered()
  331. flexmock(module).should_receive('log_error_records').with_args(
  332. 'bar: Error running actions for repository',
  333. OSError,
  334. levelno=logging.WARNING,
  335. log_command_error_output=True,
  336. ).and_return([flexmock()]).ordered()
  337. # Sleep before retrying foo (and passing)
  338. flexmock(time).should_receive('sleep').with_args(10).and_return().ordered()
  339. # Sleep before retrying bar (and failing)
  340. flexmock(time).should_receive('sleep').with_args(10).and_return().ordered()
  341. error_logs = [flexmock()]
  342. flexmock(module).should_receive('log_error_records').with_args(
  343. 'bar: Error running actions for repository', OSError
  344. ).and_return(error_logs).ordered()
  345. config = {
  346. 'repositories': [{'path': 'foo'}, {'path': 'bar'}],
  347. 'retries': 1,
  348. 'retry_wait': 10,
  349. }
  350. arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
  351. results = list(module.run_configuration('test.yaml', config, arguments))
  352. assert results == error_logs
  353. def test_run_actions_runs_rcreate():
  354. flexmock(module).should_receive('add_custom_log_levels')
  355. flexmock(module.command).should_receive('execute_hook')
  356. flexmock(borgmatic.actions.rcreate).should_receive('run_rcreate').once()
  357. tuple(
  358. module.run_actions(
  359. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'rcreate': flexmock()},
  360. config_filename=flexmock(),
  361. config={'repositories': []},
  362. local_path=flexmock(),
  363. remote_path=flexmock(),
  364. local_borg_version=flexmock(),
  365. repository={'path': 'repo'},
  366. )
  367. )
  368. def test_run_actions_adds_log_file_to_hook_context():
  369. flexmock(module).should_receive('add_custom_log_levels')
  370. flexmock(module.command).should_receive('execute_hook')
  371. expected = flexmock()
  372. flexmock(borgmatic.actions.create).should_receive('run_create').with_args(
  373. config_filename=object,
  374. repository={'path': 'repo'},
  375. config={'repositories': []},
  376. hook_context={'repository': 'repo', 'repositories': '', 'log_file': 'foo'},
  377. local_borg_version=object,
  378. create_arguments=object,
  379. global_arguments=object,
  380. dry_run_label='',
  381. local_path=object,
  382. remote_path=object,
  383. ).once().and_return(expected)
  384. result = tuple(
  385. module.run_actions(
  386. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'create': flexmock()},
  387. config_filename=flexmock(),
  388. config={'repositories': []},
  389. local_path=flexmock(),
  390. remote_path=flexmock(),
  391. local_borg_version=flexmock(),
  392. repository={'path': 'repo'},
  393. )
  394. )
  395. assert result == (expected,)
  396. def test_run_actions_runs_transfer():
  397. flexmock(module).should_receive('add_custom_log_levels')
  398. flexmock(module.command).should_receive('execute_hook')
  399. flexmock(borgmatic.actions.transfer).should_receive('run_transfer').once()
  400. tuple(
  401. module.run_actions(
  402. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'transfer': flexmock()},
  403. config_filename=flexmock(),
  404. config={'repositories': []},
  405. local_path=flexmock(),
  406. remote_path=flexmock(),
  407. local_borg_version=flexmock(),
  408. repository={'path': 'repo'},
  409. )
  410. )
  411. def test_run_actions_runs_create():
  412. flexmock(module).should_receive('add_custom_log_levels')
  413. flexmock(module.command).should_receive('execute_hook')
  414. expected = flexmock()
  415. flexmock(borgmatic.actions.create).should_receive('run_create').and_yield(expected).once()
  416. result = tuple(
  417. module.run_actions(
  418. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'create': flexmock()},
  419. config_filename=flexmock(),
  420. config={'repositories': []},
  421. local_path=flexmock(),
  422. remote_path=flexmock(),
  423. local_borg_version=flexmock(),
  424. repository={'path': 'repo'},
  425. )
  426. )
  427. assert result == (expected,)
  428. def test_run_actions_with_skip_actions_skips_create():
  429. flexmock(module).should_receive('add_custom_log_levels')
  430. flexmock(module.command).should_receive('execute_hook')
  431. flexmock(borgmatic.actions.create).should_receive('run_create').never()
  432. tuple(
  433. module.run_actions(
  434. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'create': flexmock()},
  435. config_filename=flexmock(),
  436. config={'repositories': [], 'skip_actions': ['create']},
  437. local_path=flexmock(),
  438. remote_path=flexmock(),
  439. local_borg_version=flexmock(),
  440. repository={'path': 'repo'},
  441. )
  442. )
  443. def test_run_actions_runs_prune():
  444. flexmock(module).should_receive('add_custom_log_levels')
  445. flexmock(module.command).should_receive('execute_hook')
  446. flexmock(borgmatic.actions.prune).should_receive('run_prune').once()
  447. tuple(
  448. module.run_actions(
  449. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'prune': flexmock()},
  450. config_filename=flexmock(),
  451. config={'repositories': []},
  452. local_path=flexmock(),
  453. remote_path=flexmock(),
  454. local_borg_version=flexmock(),
  455. repository={'path': 'repo'},
  456. )
  457. )
  458. def test_run_actions_with_skip_actions_skips_prune():
  459. flexmock(module).should_receive('add_custom_log_levels')
  460. flexmock(module.command).should_receive('execute_hook')
  461. flexmock(borgmatic.actions.prune).should_receive('run_prune').never()
  462. tuple(
  463. module.run_actions(
  464. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'prune': flexmock()},
  465. config_filename=flexmock(),
  466. config={'repositories': [], 'skip_actions': ['prune']},
  467. local_path=flexmock(),
  468. remote_path=flexmock(),
  469. local_borg_version=flexmock(),
  470. repository={'path': 'repo'},
  471. )
  472. )
  473. def test_run_actions_runs_compact():
  474. flexmock(module).should_receive('add_custom_log_levels')
  475. flexmock(module.command).should_receive('execute_hook')
  476. flexmock(borgmatic.actions.compact).should_receive('run_compact').once()
  477. tuple(
  478. module.run_actions(
  479. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'compact': flexmock()},
  480. config_filename=flexmock(),
  481. config={'repositories': []},
  482. local_path=flexmock(),
  483. remote_path=flexmock(),
  484. local_borg_version=flexmock(),
  485. repository={'path': 'repo'},
  486. )
  487. )
  488. def test_run_actions_with_skip_actions_skips_compact():
  489. flexmock(module).should_receive('add_custom_log_levels')
  490. flexmock(module.command).should_receive('execute_hook')
  491. flexmock(borgmatic.actions.compact).should_receive('run_compact').never()
  492. tuple(
  493. module.run_actions(
  494. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'compact': flexmock()},
  495. config_filename=flexmock(),
  496. config={'repositories': [], 'skip_actions': ['compact']},
  497. local_path=flexmock(),
  498. remote_path=flexmock(),
  499. local_borg_version=flexmock(),
  500. repository={'path': 'repo'},
  501. )
  502. )
  503. def test_run_actions_runs_check_when_repository_enabled_for_checks():
  504. flexmock(module).should_receive('add_custom_log_levels')
  505. flexmock(module.command).should_receive('execute_hook')
  506. flexmock(module.checks).should_receive('repository_enabled_for_checks').and_return(True)
  507. flexmock(borgmatic.actions.check).should_receive('run_check').once()
  508. tuple(
  509. module.run_actions(
  510. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'check': flexmock()},
  511. config_filename=flexmock(),
  512. config={'repositories': []},
  513. local_path=flexmock(),
  514. remote_path=flexmock(),
  515. local_borg_version=flexmock(),
  516. repository={'path': 'repo'},
  517. )
  518. )
  519. def test_run_actions_skips_check_when_repository_not_enabled_for_checks():
  520. flexmock(module).should_receive('add_custom_log_levels')
  521. flexmock(module.command).should_receive('execute_hook')
  522. flexmock(module.checks).should_receive('repository_enabled_for_checks').and_return(False)
  523. flexmock(borgmatic.actions.check).should_receive('run_check').never()
  524. tuple(
  525. module.run_actions(
  526. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'check': flexmock()},
  527. config_filename=flexmock(),
  528. config={'repositories': []},
  529. local_path=flexmock(),
  530. remote_path=flexmock(),
  531. local_borg_version=flexmock(),
  532. repository={'path': 'repo'},
  533. )
  534. )
  535. def test_run_actions_with_skip_actions_skips_check():
  536. flexmock(module).should_receive('add_custom_log_levels')
  537. flexmock(module.command).should_receive('execute_hook')
  538. flexmock(module.checks).should_receive('repository_enabled_for_checks').and_return(True)
  539. flexmock(borgmatic.actions.check).should_receive('run_check').never()
  540. tuple(
  541. module.run_actions(
  542. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'check': flexmock()},
  543. config_filename=flexmock(),
  544. config={'repositories': [], 'skip_actions': ['check']},
  545. local_path=flexmock(),
  546. remote_path=flexmock(),
  547. local_borg_version=flexmock(),
  548. repository={'path': 'repo'},
  549. )
  550. )
  551. def test_run_actions_runs_extract():
  552. flexmock(module).should_receive('add_custom_log_levels')
  553. flexmock(module.command).should_receive('execute_hook')
  554. flexmock(borgmatic.actions.extract).should_receive('run_extract').once()
  555. tuple(
  556. module.run_actions(
  557. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'extract': flexmock()},
  558. config_filename=flexmock(),
  559. config={'repositories': []},
  560. local_path=flexmock(),
  561. remote_path=flexmock(),
  562. local_borg_version=flexmock(),
  563. repository={'path': 'repo'},
  564. )
  565. )
  566. def test_run_actions_runs_export_tar():
  567. flexmock(module).should_receive('add_custom_log_levels')
  568. flexmock(module.command).should_receive('execute_hook')
  569. flexmock(borgmatic.actions.export_tar).should_receive('run_export_tar').once()
  570. tuple(
  571. module.run_actions(
  572. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'export-tar': flexmock()},
  573. config_filename=flexmock(),
  574. config={'repositories': []},
  575. local_path=flexmock(),
  576. remote_path=flexmock(),
  577. local_borg_version=flexmock(),
  578. repository={'path': 'repo'},
  579. )
  580. )
  581. def test_run_actions_runs_mount():
  582. flexmock(module).should_receive('add_custom_log_levels')
  583. flexmock(module.command).should_receive('execute_hook')
  584. flexmock(borgmatic.actions.mount).should_receive('run_mount').once()
  585. tuple(
  586. module.run_actions(
  587. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'mount': flexmock()},
  588. config_filename=flexmock(),
  589. config={'repositories': []},
  590. local_path=flexmock(),
  591. remote_path=flexmock(),
  592. local_borg_version=flexmock(),
  593. repository={'path': 'repo'},
  594. )
  595. )
  596. def test_run_actions_runs_restore():
  597. flexmock(module).should_receive('add_custom_log_levels')
  598. flexmock(module.command).should_receive('execute_hook')
  599. flexmock(borgmatic.actions.restore).should_receive('run_restore').once()
  600. tuple(
  601. module.run_actions(
  602. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'restore': flexmock()},
  603. config_filename=flexmock(),
  604. config={'repositories': []},
  605. local_path=flexmock(),
  606. remote_path=flexmock(),
  607. local_borg_version=flexmock(),
  608. repository={'path': 'repo'},
  609. )
  610. )
  611. def test_run_actions_runs_rlist():
  612. flexmock(module).should_receive('add_custom_log_levels')
  613. flexmock(module.command).should_receive('execute_hook')
  614. expected = flexmock()
  615. flexmock(borgmatic.actions.rlist).should_receive('run_rlist').and_yield(expected).once()
  616. result = tuple(
  617. module.run_actions(
  618. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'rlist': flexmock()},
  619. config_filename=flexmock(),
  620. config={'repositories': []},
  621. local_path=flexmock(),
  622. remote_path=flexmock(),
  623. local_borg_version=flexmock(),
  624. repository={'path': 'repo'},
  625. )
  626. )
  627. assert result == (expected,)
  628. def test_run_actions_runs_list():
  629. flexmock(module).should_receive('add_custom_log_levels')
  630. flexmock(module.command).should_receive('execute_hook')
  631. expected = flexmock()
  632. flexmock(borgmatic.actions.list).should_receive('run_list').and_yield(expected).once()
  633. result = tuple(
  634. module.run_actions(
  635. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'list': flexmock()},
  636. config_filename=flexmock(),
  637. config={'repositories': []},
  638. local_path=flexmock(),
  639. remote_path=flexmock(),
  640. local_borg_version=flexmock(),
  641. repository={'path': 'repo'},
  642. )
  643. )
  644. assert result == (expected,)
  645. def test_run_actions_runs_rinfo():
  646. flexmock(module).should_receive('add_custom_log_levels')
  647. flexmock(module.command).should_receive('execute_hook')
  648. expected = flexmock()
  649. flexmock(borgmatic.actions.rinfo).should_receive('run_rinfo').and_yield(expected).once()
  650. result = tuple(
  651. module.run_actions(
  652. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'rinfo': flexmock()},
  653. config_filename=flexmock(),
  654. config={'repositories': []},
  655. local_path=flexmock(),
  656. remote_path=flexmock(),
  657. local_borg_version=flexmock(),
  658. repository={'path': 'repo'},
  659. )
  660. )
  661. assert result == (expected,)
  662. def test_run_actions_runs_info():
  663. flexmock(module).should_receive('add_custom_log_levels')
  664. flexmock(module.command).should_receive('execute_hook')
  665. expected = flexmock()
  666. flexmock(borgmatic.actions.info).should_receive('run_info').and_yield(expected).once()
  667. result = tuple(
  668. module.run_actions(
  669. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'info': flexmock()},
  670. config_filename=flexmock(),
  671. config={'repositories': []},
  672. local_path=flexmock(),
  673. remote_path=flexmock(),
  674. local_borg_version=flexmock(),
  675. repository={'path': 'repo'},
  676. )
  677. )
  678. assert result == (expected,)
  679. def test_run_actions_runs_break_lock():
  680. flexmock(module).should_receive('add_custom_log_levels')
  681. flexmock(module.command).should_receive('execute_hook')
  682. flexmock(borgmatic.actions.break_lock).should_receive('run_break_lock').once()
  683. tuple(
  684. module.run_actions(
  685. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'break-lock': flexmock()},
  686. config_filename=flexmock(),
  687. config={'repositories': []},
  688. local_path=flexmock(),
  689. remote_path=flexmock(),
  690. local_borg_version=flexmock(),
  691. repository={'path': 'repo'},
  692. )
  693. )
  694. def test_run_actions_runs_export_key():
  695. flexmock(module).should_receive('add_custom_log_levels')
  696. flexmock(module.command).should_receive('execute_hook')
  697. flexmock(borgmatic.actions.export_key).should_receive('run_export_key').once()
  698. tuple(
  699. module.run_actions(
  700. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'export': flexmock()},
  701. config_filename=flexmock(),
  702. config={'repositories': []},
  703. local_path=flexmock(),
  704. remote_path=flexmock(),
  705. local_borg_version=flexmock(),
  706. repository={'path': 'repo'},
  707. )
  708. )
  709. def test_run_actions_runs_borg():
  710. flexmock(module).should_receive('add_custom_log_levels')
  711. flexmock(module.command).should_receive('execute_hook')
  712. flexmock(borgmatic.actions.borg).should_receive('run_borg').once()
  713. tuple(
  714. module.run_actions(
  715. arguments={'global': flexmock(dry_run=False, log_file='foo'), 'borg': flexmock()},
  716. config_filename=flexmock(),
  717. config={'repositories': []},
  718. local_path=flexmock(),
  719. remote_path=flexmock(),
  720. local_borg_version=flexmock(),
  721. repository={'path': 'repo'},
  722. )
  723. )
  724. def test_run_actions_runs_multiple_actions_in_argument_order():
  725. flexmock(module).should_receive('add_custom_log_levels')
  726. flexmock(module.command).should_receive('execute_hook')
  727. flexmock(borgmatic.actions.borg).should_receive('run_borg').once().ordered()
  728. flexmock(borgmatic.actions.restore).should_receive('run_restore').once().ordered()
  729. tuple(
  730. module.run_actions(
  731. arguments={
  732. 'global': flexmock(dry_run=False, log_file='foo'),
  733. 'borg': flexmock(),
  734. 'restore': flexmock(),
  735. },
  736. config_filename=flexmock(),
  737. config={'repositories': []},
  738. local_path=flexmock(),
  739. remote_path=flexmock(),
  740. local_borg_version=flexmock(),
  741. repository={'path': 'repo'},
  742. )
  743. )
  744. def test_load_configurations_collects_parsed_configurations_and_logs():
  745. configuration = flexmock()
  746. other_configuration = flexmock()
  747. test_expected_logs = [flexmock(), flexmock()]
  748. other_expected_logs = [flexmock(), flexmock()]
  749. flexmock(module.validate).should_receive('parse_configuration').and_return(
  750. configuration, test_expected_logs
  751. ).and_return(other_configuration, other_expected_logs)
  752. configs, logs = tuple(module.load_configurations(('test.yaml', 'other.yaml')))
  753. assert configs == {'test.yaml': configuration, 'other.yaml': other_configuration}
  754. assert set(logs) >= set(test_expected_logs + other_expected_logs)
  755. def test_load_configurations_logs_warning_for_permission_error():
  756. flexmock(module.validate).should_receive('parse_configuration').and_raise(PermissionError)
  757. configs, logs = tuple(module.load_configurations(('test.yaml',)))
  758. assert configs == {}
  759. assert max(log.levelno for log in logs) == logging.WARNING
  760. def test_load_configurations_logs_critical_for_parse_error():
  761. flexmock(module.validate).should_receive('parse_configuration').and_raise(ValueError)
  762. configs, logs = tuple(module.load_configurations(('test.yaml',)))
  763. assert configs == {}
  764. assert max(log.levelno for log in logs) == logging.CRITICAL
  765. def test_log_record_does_not_raise():
  766. module.log_record(levelno=1, foo='bar', baz='quux')
  767. def test_log_record_with_suppress_does_not_raise():
  768. module.log_record(levelno=1, foo='bar', baz='quux', suppress_log=True)
  769. def test_log_error_records_generates_output_logs_for_message_only():
  770. flexmock(module).should_receive('log_record').replace_with(dict).once()
  771. logs = tuple(module.log_error_records('Error'))
  772. assert {log['levelno'] for log in logs} == {logging.CRITICAL}
  773. def test_log_error_records_generates_output_logs_for_called_process_error_with_bytes_ouput():
  774. flexmock(module).should_receive('log_record').replace_with(dict).times(3)
  775. flexmock(module.logger).should_receive('getEffectiveLevel').and_return(logging.WARNING)
  776. logs = tuple(
  777. module.log_error_records('Error', subprocess.CalledProcessError(1, 'ls', b'error output'))
  778. )
  779. assert {log['levelno'] for log in logs} == {logging.CRITICAL}
  780. assert any(log for log in logs if 'error output' in str(log))
  781. def test_log_error_records_generates_output_logs_for_called_process_error_with_string_ouput():
  782. flexmock(module).should_receive('log_record').replace_with(dict).times(3)
  783. flexmock(module.logger).should_receive('getEffectiveLevel').and_return(logging.WARNING)
  784. logs = tuple(
  785. module.log_error_records('Error', subprocess.CalledProcessError(1, 'ls', 'error output'))
  786. )
  787. assert {log['levelno'] for log in logs} == {logging.CRITICAL}
  788. assert any(log for log in logs if 'error output' in str(log))
  789. def test_log_error_records_splits_called_process_error_with_multiline_ouput_into_multiple_logs():
  790. flexmock(module).should_receive('log_record').replace_with(dict).times(4)
  791. flexmock(module.logger).should_receive('getEffectiveLevel').and_return(logging.WARNING)
  792. logs = tuple(
  793. module.log_error_records(
  794. 'Error', subprocess.CalledProcessError(1, 'ls', 'error output\nanother line')
  795. )
  796. )
  797. assert {log['levelno'] for log in logs} == {logging.CRITICAL}
  798. assert any(log for log in logs if 'error output' in str(log))
  799. def test_log_error_records_generates_logs_for_value_error():
  800. flexmock(module).should_receive('log_record').replace_with(dict).twice()
  801. logs = tuple(module.log_error_records('Error', ValueError()))
  802. assert {log['levelno'] for log in logs} == {logging.CRITICAL}
  803. def test_log_error_records_generates_logs_for_os_error():
  804. flexmock(module).should_receive('log_record').replace_with(dict).twice()
  805. logs = tuple(module.log_error_records('Error', OSError()))
  806. assert {log['levelno'] for log in logs} == {logging.CRITICAL}
  807. def test_log_error_records_generates_nothing_for_other_error():
  808. flexmock(module).should_receive('log_record').never()
  809. logs = tuple(module.log_error_records('Error', KeyError()))
  810. assert logs == ()
  811. def test_get_local_path_uses_configuration_value():
  812. assert module.get_local_path({'test.yaml': {'local_path': 'borg1'}}) == 'borg1'
  813. def test_get_local_path_without_local_path_defaults_to_borg():
  814. assert module.get_local_path({'test.yaml': {}}) == 'borg'
  815. def test_collect_highlander_action_summary_logs_info_for_success_with_bootstrap():
  816. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  817. flexmock(module.borgmatic.actions.config.bootstrap).should_receive('run_bootstrap')
  818. arguments = {
  819. 'bootstrap': flexmock(repository='repo'),
  820. 'global': flexmock(dry_run=False),
  821. }
  822. logs = tuple(
  823. module.collect_highlander_action_summary_logs(
  824. {'test.yaml': {}}, arguments=arguments, configuration_parse_errors=False
  825. )
  826. )
  827. assert {log.levelno for log in logs} == {logging.ANSWER}
  828. def test_collect_highlander_action_summary_logs_error_on_bootstrap_failure():
  829. flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
  830. flexmock(module.borgmatic.actions.config.bootstrap).should_receive('run_bootstrap').and_raise(
  831. ValueError
  832. )
  833. arguments = {
  834. 'bootstrap': flexmock(repository='repo'),
  835. 'global': flexmock(dry_run=False),
  836. }
  837. logs = tuple(
  838. module.collect_highlander_action_summary_logs(
  839. {'test.yaml': {}}, arguments=arguments, configuration_parse_errors=False
  840. )
  841. )
  842. assert {log.levelno for log in logs} == {logging.CRITICAL}
  843. def test_collect_highlander_action_summary_logs_error_on_bootstrap_local_borg_version_failure():
  844. flexmock(module.borg_version).should_receive('local_borg_version').and_raise(ValueError)
  845. flexmock(module.borgmatic.actions.config.bootstrap).should_receive('run_bootstrap').never()
  846. arguments = {
  847. 'bootstrap': flexmock(repository='repo'),
  848. 'global': flexmock(dry_run=False),
  849. }
  850. logs = tuple(
  851. module.collect_highlander_action_summary_logs(
  852. {'test.yaml': {}}, arguments=arguments, configuration_parse_errors=False
  853. )
  854. )
  855. assert {log.levelno for log in logs} == {logging.CRITICAL}
  856. def test_collect_highlander_action_summary_logs_info_for_success_with_generate():
  857. flexmock(module.borgmatic.actions.config.generate).should_receive('run_generate')
  858. arguments = {
  859. 'generate': flexmock(destination='test.yaml'),
  860. 'global': flexmock(dry_run=False),
  861. }
  862. logs = tuple(
  863. module.collect_highlander_action_summary_logs(
  864. {'test.yaml': {}}, arguments=arguments, configuration_parse_errors=False
  865. )
  866. )
  867. assert {log.levelno for log in logs} == {logging.ANSWER}
  868. def test_collect_highlander_action_summary_logs_error_on_generate_failure():
  869. flexmock(module.borgmatic.actions.config.generate).should_receive('run_generate').and_raise(
  870. ValueError
  871. )
  872. arguments = {
  873. 'generate': flexmock(destination='test.yaml'),
  874. 'global': flexmock(dry_run=False),
  875. }
  876. logs = tuple(
  877. module.collect_highlander_action_summary_logs(
  878. {'test.yaml': {}}, arguments=arguments, configuration_parse_errors=False
  879. )
  880. )
  881. assert {log.levelno for log in logs} == {logging.CRITICAL}
  882. def test_collect_highlander_action_summary_logs_info_for_success_with_validate():
  883. flexmock(module.borgmatic.actions.config.validate).should_receive('run_validate')
  884. arguments = {
  885. 'validate': flexmock(),
  886. 'global': flexmock(dry_run=False),
  887. }
  888. logs = tuple(
  889. module.collect_highlander_action_summary_logs(
  890. {'test.yaml': {}}, arguments=arguments, configuration_parse_errors=False
  891. )
  892. )
  893. assert {log.levelno for log in logs} == {logging.ANSWER}
  894. def test_collect_highlander_action_summary_logs_error_on_validate_parse_failure():
  895. flexmock(module.borgmatic.actions.config.validate).should_receive('run_validate')
  896. arguments = {
  897. 'validate': flexmock(),
  898. 'global': flexmock(dry_run=False),
  899. }
  900. logs = tuple(
  901. module.collect_highlander_action_summary_logs(
  902. {'test.yaml': {}}, arguments=arguments, configuration_parse_errors=True
  903. )
  904. )
  905. assert {log.levelno for log in logs} == {logging.CRITICAL}
  906. def test_collect_highlander_action_summary_logs_error_on_run_validate_failure():
  907. flexmock(module.borgmatic.actions.config.validate).should_receive('run_validate').and_raise(
  908. ValueError
  909. )
  910. arguments = {
  911. 'validate': flexmock(),
  912. 'global': flexmock(dry_run=False),
  913. }
  914. logs = tuple(
  915. module.collect_highlander_action_summary_logs(
  916. {'test.yaml': {}}, arguments=arguments, configuration_parse_errors=False
  917. )
  918. )
  919. assert {log.levelno for log in logs} == {logging.CRITICAL}
  920. def test_collect_configuration_run_summary_logs_info_for_success():
  921. flexmock(module.command).should_receive('execute_hook').never()
  922. flexmock(module.validate).should_receive('guard_configuration_contains_repository')
  923. flexmock(module).should_receive('run_configuration').and_return([])
  924. arguments = {}
  925. logs = tuple(
  926. module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
  927. )
  928. assert {log.levelno for log in logs} == {logging.INFO}
  929. def test_collect_configuration_run_summary_executes_hooks_for_create():
  930. flexmock(module.validate).should_receive('guard_configuration_contains_repository')
  931. flexmock(module).should_receive('run_configuration').and_return([])
  932. arguments = {'create': flexmock(), 'global': flexmock(monitoring_verbosity=1, dry_run=False)}
  933. logs = tuple(
  934. module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
  935. )
  936. assert {log.levelno for log in logs} == {logging.INFO}
  937. def test_collect_configuration_run_summary_logs_info_for_success_with_extract():
  938. flexmock(module.validate).should_receive('guard_single_repository_selected')
  939. flexmock(module.validate).should_receive('guard_configuration_contains_repository')
  940. flexmock(module).should_receive('run_configuration').and_return([])
  941. arguments = {'extract': flexmock(repository='repo')}
  942. logs = tuple(
  943. module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
  944. )
  945. assert {log.levelno for log in logs} == {logging.INFO}
  946. def test_collect_configuration_run_summary_logs_extract_with_repository_error():
  947. flexmock(module.validate).should_receive('guard_configuration_contains_repository').and_raise(
  948. ValueError
  949. )
  950. expected_logs = (flexmock(),)
  951. flexmock(module).should_receive('log_error_records').and_return(expected_logs)
  952. arguments = {'extract': flexmock(repository='repo')}
  953. logs = tuple(
  954. module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
  955. )
  956. assert logs == expected_logs
  957. def test_collect_configuration_run_summary_logs_info_for_success_with_mount():
  958. flexmock(module.validate).should_receive('guard_single_repository_selected')
  959. flexmock(module.validate).should_receive('guard_configuration_contains_repository')
  960. flexmock(module).should_receive('run_configuration').and_return([])
  961. arguments = {'mount': flexmock(repository='repo')}
  962. logs = tuple(
  963. module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
  964. )
  965. assert {log.levelno for log in logs} == {logging.INFO}
  966. def test_collect_configuration_run_summary_logs_mount_with_repository_error():
  967. flexmock(module.validate).should_receive('guard_configuration_contains_repository').and_raise(
  968. ValueError
  969. )
  970. expected_logs = (flexmock(),)
  971. flexmock(module).should_receive('log_error_records').and_return(expected_logs)
  972. arguments = {'mount': flexmock(repository='repo')}
  973. logs = tuple(
  974. module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
  975. )
  976. assert logs == expected_logs
  977. def test_collect_configuration_run_summary_logs_missing_configs_error():
  978. arguments = {'global': flexmock(config_paths=[])}
  979. expected_logs = (flexmock(),)
  980. flexmock(module).should_receive('log_error_records').and_return(expected_logs)
  981. logs = tuple(module.collect_configuration_run_summary_logs({}, arguments=arguments))
  982. assert logs == expected_logs
  983. def test_collect_configuration_run_summary_logs_pre_hook_error():
  984. flexmock(module.command).should_receive('execute_hook').and_raise(ValueError)
  985. expected_logs = (flexmock(),)
  986. flexmock(module).should_receive('log_error_records').and_return(expected_logs)
  987. arguments = {'create': flexmock(), 'global': flexmock(monitoring_verbosity=1, dry_run=False)}
  988. logs = tuple(
  989. module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
  990. )
  991. assert logs == expected_logs
  992. def test_collect_configuration_run_summary_logs_post_hook_error():
  993. flexmock(module.command).should_receive('execute_hook').and_return(None).and_raise(ValueError)
  994. flexmock(module.validate).should_receive('guard_configuration_contains_repository')
  995. flexmock(module).should_receive('run_configuration').and_return([])
  996. expected_logs = (flexmock(),)
  997. flexmock(module).should_receive('log_error_records').and_return(expected_logs)
  998. arguments = {'create': flexmock(), 'global': flexmock(monitoring_verbosity=1, dry_run=False)}
  999. logs = tuple(
  1000. module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
  1001. )
  1002. assert expected_logs[0] in logs
  1003. def test_collect_configuration_run_summary_logs_for_list_with_archive_and_repository_error():
  1004. flexmock(module.validate).should_receive('guard_configuration_contains_repository').and_raise(
  1005. ValueError
  1006. )
  1007. expected_logs = (flexmock(),)
  1008. flexmock(module).should_receive('log_error_records').and_return(expected_logs)
  1009. arguments = {'list': flexmock(repository='repo', archive='test')}
  1010. logs = tuple(
  1011. module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
  1012. )
  1013. assert logs == expected_logs
  1014. def test_collect_configuration_run_summary_logs_info_for_success_with_list():
  1015. flexmock(module.validate).should_receive('guard_configuration_contains_repository')
  1016. flexmock(module).should_receive('run_configuration').and_return([])
  1017. arguments = {'list': flexmock(repository='repo', archive=None)}
  1018. logs = tuple(
  1019. module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
  1020. )
  1021. assert {log.levelno for log in logs} == {logging.INFO}
  1022. def test_collect_configuration_run_summary_logs_run_configuration_error():
  1023. flexmock(module.validate).should_receive('guard_configuration_contains_repository')
  1024. flexmock(module).should_receive('run_configuration').and_return(
  1025. [logging.makeLogRecord(dict(levelno=logging.CRITICAL, levelname='CRITICAL', msg='Error'))]
  1026. )
  1027. flexmock(module).should_receive('log_error_records').and_return([])
  1028. arguments = {}
  1029. logs = tuple(
  1030. module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
  1031. )
  1032. assert {log.levelno for log in logs} == {logging.CRITICAL}
  1033. def test_collect_configuration_run_summary_logs_run_umount_error():
  1034. flexmock(module.validate).should_receive('guard_configuration_contains_repository')
  1035. flexmock(module).should_receive('run_configuration').and_return([])
  1036. flexmock(module.borg_umount).should_receive('unmount_archive').and_raise(OSError)
  1037. flexmock(module).should_receive('log_error_records').and_return(
  1038. [logging.makeLogRecord(dict(levelno=logging.CRITICAL, levelname='CRITICAL', msg='Error'))]
  1039. )
  1040. arguments = {'umount': flexmock(mount_point='/mnt')}
  1041. logs = tuple(
  1042. module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
  1043. )
  1044. assert {log.levelno for log in logs} == {logging.INFO, logging.CRITICAL}
  1045. def test_collect_configuration_run_summary_logs_outputs_merged_json_results():
  1046. flexmock(module.validate).should_receive('guard_configuration_contains_repository')
  1047. flexmock(module).should_receive('run_configuration').and_return(['foo', 'bar']).and_return(
  1048. ['baz']
  1049. )
  1050. stdout = flexmock()
  1051. stdout.should_receive('write').with_args('["foo", "bar", "baz"]').once()
  1052. flexmock(module.sys).stdout = stdout
  1053. arguments = {}
  1054. tuple(
  1055. module.collect_configuration_run_summary_logs(
  1056. {'test.yaml': {}, 'test2.yaml': {}}, arguments=arguments
  1057. )
  1058. )