test_load.py 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157
  1. import io
  2. import sys
  3. import pytest
  4. from flexmock import flexmock
  5. from borgmatic.config import load as module
  6. def test_load_configuration_parses_contents():
  7. builtins = flexmock(sys.modules['builtins'])
  8. config_file = io.StringIO('key: value')
  9. config_file.name = 'config.yaml'
  10. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  11. config_paths = {'other.yaml'}
  12. assert module.load_configuration('config.yaml', config_paths) == {'key': 'value'}
  13. assert config_paths == {'config.yaml', 'other.yaml'}
  14. def test_load_configuration_with_only_integer_value_does_not_raise():
  15. builtins = flexmock(sys.modules['builtins'])
  16. config_file = io.StringIO('33')
  17. config_file.name = 'config.yaml'
  18. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  19. config_paths = {'other.yaml'}
  20. assert module.load_configuration('config.yaml', config_paths) == 33
  21. assert config_paths == {'config.yaml', 'other.yaml'}
  22. def test_load_configuration_inlines_include_relative_to_current_directory():
  23. builtins = flexmock(sys.modules['builtins'])
  24. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  25. flexmock(module.os.path).should_receive('isabs').and_return(False)
  26. flexmock(module.os.path).should_receive('exists').and_return(True)
  27. include_file = io.StringIO('value')
  28. include_file.name = 'include.yaml'
  29. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  30. config_file = io.StringIO('key: !include include.yaml')
  31. config_file.name = 'config.yaml'
  32. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  33. config_paths = {'other.yaml'}
  34. assert module.load_configuration('config.yaml', config_paths) == {'key': 'value'}
  35. assert config_paths == {'config.yaml', '/tmp/include.yaml', 'other.yaml'}
  36. def test_load_configuration_inlines_include_relative_to_config_parent_directory():
  37. builtins = flexmock(sys.modules['builtins'])
  38. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  39. flexmock(module.os.path).should_receive('isabs').with_args('/etc').and_return(True)
  40. flexmock(module.os.path).should_receive('isabs').with_args('/etc/config.yaml').and_return(True)
  41. flexmock(module.os.path).should_receive('isabs').with_args('include.yaml').and_return(False)
  42. flexmock(module.os.path).should_receive('exists').with_args('/tmp/include.yaml').and_return(
  43. False
  44. )
  45. flexmock(module.os.path).should_receive('exists').with_args('/etc/include.yaml').and_return(
  46. True
  47. )
  48. include_file = io.StringIO('value')
  49. include_file.name = 'include.yaml'
  50. builtins.should_receive('open').with_args('/etc/include.yaml').and_return(include_file)
  51. config_file = io.StringIO('key: !include include.yaml')
  52. config_file.name = '/etc/config.yaml'
  53. builtins.should_receive('open').with_args('/etc/config.yaml').and_return(config_file)
  54. config_paths = {'other.yaml'}
  55. assert module.load_configuration('/etc/config.yaml', config_paths) == {'key': 'value'}
  56. assert config_paths == {'/etc/config.yaml', '/etc/include.yaml', 'other.yaml'}
  57. def test_load_configuration_raises_if_relative_include_does_not_exist():
  58. builtins = flexmock(sys.modules['builtins'])
  59. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  60. flexmock(module.os.path).should_receive('isabs').with_args('/etc').and_return(True)
  61. flexmock(module.os.path).should_receive('isabs').with_args('/etc/config.yaml').and_return(True)
  62. flexmock(module.os.path).should_receive('isabs').with_args('include.yaml').and_return(False)
  63. flexmock(module.os.path).should_receive('exists').and_return(False)
  64. config_file = io.StringIO('key: !include include.yaml')
  65. config_file.name = '/etc/config.yaml'
  66. builtins.should_receive('open').with_args('/etc/config.yaml').and_return(config_file)
  67. config_paths = set()
  68. with pytest.raises(FileNotFoundError):
  69. module.load_configuration('/etc/config.yaml', config_paths)
  70. def test_load_configuration_inlines_absolute_include():
  71. builtins = flexmock(sys.modules['builtins'])
  72. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  73. flexmock(module.os.path).should_receive('isabs').and_return(True)
  74. flexmock(module.os.path).should_receive('exists').never()
  75. include_file = io.StringIO('value')
  76. include_file.name = '/root/include.yaml'
  77. builtins.should_receive('open').with_args('/root/include.yaml').and_return(include_file)
  78. config_file = io.StringIO('key: !include /root/include.yaml')
  79. config_file.name = 'config.yaml'
  80. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  81. config_paths = {'other.yaml'}
  82. assert module.load_configuration('config.yaml', config_paths) == {'key': 'value'}
  83. assert config_paths == {'config.yaml', '/root/include.yaml', 'other.yaml'}
  84. def test_load_configuration_raises_if_absolute_include_does_not_exist():
  85. builtins = flexmock(sys.modules['builtins'])
  86. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  87. flexmock(module.os.path).should_receive('isabs').and_return(True)
  88. builtins.should_receive('open').with_args('/root/include.yaml').and_raise(FileNotFoundError)
  89. config_file = io.StringIO('key: !include /root/include.yaml')
  90. config_file.name = 'config.yaml'
  91. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  92. config_paths = set()
  93. with pytest.raises(FileNotFoundError):
  94. assert module.load_configuration('config.yaml', config_paths)
  95. def test_load_configuration_inlines_multiple_file_include_as_list():
  96. builtins = flexmock(sys.modules['builtins'])
  97. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  98. flexmock(module.os.path).should_receive('isabs').and_return(True)
  99. flexmock(module.os.path).should_receive('exists').never()
  100. include1_file = io.StringIO('value1')
  101. include1_file.name = '/root/include1.yaml'
  102. builtins.should_receive('open').with_args('/root/include1.yaml').and_return(include1_file)
  103. include2_file = io.StringIO('value2')
  104. include2_file.name = '/root/include2.yaml'
  105. builtins.should_receive('open').with_args('/root/include2.yaml').and_return(include2_file)
  106. config_file = io.StringIO('key: !include [/root/include1.yaml, /root/include2.yaml]')
  107. config_file.name = 'config.yaml'
  108. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  109. config_paths = {'other.yaml'}
  110. assert module.load_configuration('config.yaml', config_paths) == {'key': ['value2', 'value1']}
  111. assert config_paths == {
  112. 'config.yaml',
  113. '/root/include1.yaml',
  114. '/root/include2.yaml',
  115. 'other.yaml',
  116. }
  117. def test_load_configuration_include_with_unsupported_filename_type_raises():
  118. builtins = flexmock(sys.modules['builtins'])
  119. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  120. flexmock(module.os.path).should_receive('isabs').and_return(True)
  121. flexmock(module.os.path).should_receive('exists').never()
  122. config_file = io.StringIO('key: !include {path: /root/include.yaml}')
  123. config_file.name = 'config.yaml'
  124. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  125. config_paths = set()
  126. with pytest.raises(ValueError):
  127. module.load_configuration('config.yaml', config_paths)
  128. def test_load_configuration_merges_include():
  129. builtins = flexmock(sys.modules['builtins'])
  130. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  131. flexmock(module.os.path).should_receive('isabs').and_return(False)
  132. flexmock(module.os.path).should_receive('exists').and_return(True)
  133. include_file = io.StringIO(
  134. '''
  135. foo: bar
  136. baz: quux
  137. '''
  138. )
  139. include_file.name = 'include.yaml'
  140. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  141. config_file = io.StringIO(
  142. '''
  143. foo: override
  144. <<: !include include.yaml
  145. '''
  146. )
  147. config_file.name = 'config.yaml'
  148. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  149. config_paths = {'other.yaml'}
  150. assert module.load_configuration('config.yaml', config_paths) == {
  151. 'foo': 'override',
  152. 'baz': 'quux',
  153. }
  154. assert config_paths == {'config.yaml', '/tmp/include.yaml', 'other.yaml'}
  155. def test_load_configuration_merges_multiple_file_include():
  156. builtins = flexmock(sys.modules['builtins'])
  157. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  158. flexmock(module.os.path).should_receive('isabs').and_return(False)
  159. flexmock(module.os.path).should_receive('exists').and_return(True)
  160. include1_file = io.StringIO(
  161. '''
  162. foo: bar
  163. baz: quux
  164. original: yes
  165. '''
  166. )
  167. include1_file.name = 'include1.yaml'
  168. builtins.should_receive('open').with_args('/tmp/include1.yaml').and_return(include1_file)
  169. include2_file = io.StringIO(
  170. '''
  171. baz: second
  172. '''
  173. )
  174. include2_file.name = 'include2.yaml'
  175. builtins.should_receive('open').with_args('/tmp/include2.yaml').and_return(include2_file)
  176. config_file = io.StringIO(
  177. '''
  178. foo: override
  179. <<: !include [include1.yaml, include2.yaml]
  180. '''
  181. )
  182. config_file.name = 'config.yaml'
  183. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  184. config_paths = {'other.yaml'}
  185. assert module.load_configuration('config.yaml', config_paths) == {
  186. 'foo': 'override',
  187. 'baz': 'second',
  188. 'original': 'yes',
  189. }
  190. assert config_paths == {'config.yaml', '/tmp/include1.yaml', '/tmp/include2.yaml', 'other.yaml'}
  191. def test_load_configuration_passes_through_credential_tag():
  192. builtins = flexmock(sys.modules['builtins'])
  193. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  194. flexmock(module.os.path).should_receive('isabs').and_return(False)
  195. flexmock(module.os.path).should_receive('exists').and_return(True)
  196. config_file = io.StringIO('key: !credential foo bar')
  197. config_file.name = 'config.yaml'
  198. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  199. config_paths = {'other.yaml'}
  200. assert module.load_configuration('config.yaml', config_paths) == {'key': '!credential foo bar'}
  201. assert config_paths == {'config.yaml', 'other.yaml'}
  202. def test_load_configuration_with_retain_tag_merges_include_but_keeps_local_values():
  203. builtins = flexmock(sys.modules['builtins'])
  204. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  205. flexmock(module.os.path).should_receive('isabs').and_return(False)
  206. flexmock(module.os.path).should_receive('exists').and_return(True)
  207. include_file = io.StringIO(
  208. '''
  209. stuff:
  210. foo: bar
  211. baz: quux
  212. other:
  213. a: b
  214. c: d
  215. '''
  216. )
  217. include_file.name = 'include.yaml'
  218. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  219. config_file = io.StringIO(
  220. '''
  221. stuff: !retain
  222. foo: override
  223. other:
  224. a: override
  225. <<: !include include.yaml
  226. '''
  227. )
  228. config_file.name = 'config.yaml'
  229. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  230. config_paths = {'other.yaml'}
  231. assert module.load_configuration('config.yaml', config_paths) == {
  232. 'stuff': {'foo': 'override'},
  233. 'other': {'a': 'override', 'c': 'd'},
  234. }
  235. assert config_paths == {'config.yaml', '/tmp/include.yaml', 'other.yaml'}
  236. def test_load_configuration_with_retain_tag_but_without_merge_include_raises():
  237. builtins = flexmock(sys.modules['builtins'])
  238. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  239. flexmock(module.os.path).should_receive('isabs').and_return(False)
  240. flexmock(module.os.path).should_receive('exists').and_return(True)
  241. include_file = io.StringIO(
  242. '''
  243. stuff: !retain
  244. foo: bar
  245. baz: quux
  246. '''
  247. )
  248. include_file.name = 'include.yaml'
  249. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  250. config_file = io.StringIO(
  251. '''
  252. stuff:
  253. foo: override
  254. <<: !include include.yaml
  255. '''
  256. )
  257. config_file.name = 'config.yaml'
  258. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  259. config_paths = set()
  260. with pytest.raises(ValueError):
  261. module.load_configuration('config.yaml', config_paths)
  262. def test_load_configuration_with_retain_tag_on_scalar_raises():
  263. builtins = flexmock(sys.modules['builtins'])
  264. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  265. flexmock(module.os.path).should_receive('isabs').and_return(False)
  266. flexmock(module.os.path).should_receive('exists').and_return(True)
  267. include_file = io.StringIO(
  268. '''
  269. stuff:
  270. foo: bar
  271. baz: quux
  272. '''
  273. )
  274. include_file.name = 'include.yaml'
  275. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  276. config_file = io.StringIO(
  277. '''
  278. stuff:
  279. foo: !retain override
  280. <<: !include include.yaml
  281. '''
  282. )
  283. config_file.name = 'config.yaml'
  284. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  285. config_paths = set()
  286. with pytest.raises(ValueError):
  287. module.load_configuration('config.yaml', config_paths)
  288. def test_load_configuration_with_omit_tag_merges_include_and_omits_requested_values():
  289. builtins = flexmock(sys.modules['builtins'])
  290. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  291. flexmock(module.os.path).should_receive('isabs').and_return(False)
  292. flexmock(module.os.path).should_receive('exists').and_return(True)
  293. include_file = io.StringIO(
  294. '''
  295. stuff:
  296. - a
  297. - b
  298. - c
  299. '''
  300. )
  301. include_file.name = 'include.yaml'
  302. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  303. config_file = io.StringIO(
  304. '''
  305. stuff:
  306. - x
  307. - !omit b
  308. - y
  309. <<: !include include.yaml
  310. '''
  311. )
  312. config_file.name = 'config.yaml'
  313. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  314. config_paths = {'other.yaml'}
  315. assert module.load_configuration('config.yaml', config_paths) == {'stuff': ['a', 'c', 'x', 'y']}
  316. assert config_paths == {'config.yaml', '/tmp/include.yaml', 'other.yaml'}
  317. def test_load_configuration_with_omit_tag_on_unknown_value_merges_include_and_does_not_raise():
  318. builtins = flexmock(sys.modules['builtins'])
  319. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  320. flexmock(module.os.path).should_receive('isabs').and_return(False)
  321. flexmock(module.os.path).should_receive('exists').and_return(True)
  322. include_file = io.StringIO(
  323. '''
  324. stuff:
  325. - a
  326. - b
  327. - c
  328. '''
  329. )
  330. include_file.name = 'include.yaml'
  331. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  332. config_file = io.StringIO(
  333. '''
  334. stuff:
  335. - x
  336. - !omit q
  337. - y
  338. <<: !include include.yaml
  339. '''
  340. )
  341. config_file.name = 'config.yaml'
  342. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  343. config_paths = {'other.yaml'}
  344. assert module.load_configuration('config.yaml', config_paths) == {
  345. 'stuff': ['a', 'b', 'c', 'x', 'y']
  346. }
  347. assert config_paths == {'config.yaml', '/tmp/include.yaml', 'other.yaml'}
  348. def test_load_configuration_with_omit_tag_on_non_list_item_raises():
  349. builtins = flexmock(sys.modules['builtins'])
  350. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  351. flexmock(module.os.path).should_receive('isabs').and_return(False)
  352. flexmock(module.os.path).should_receive('exists').and_return(True)
  353. include_file = io.StringIO(
  354. '''
  355. stuff:
  356. - a
  357. - b
  358. - c
  359. '''
  360. )
  361. include_file.name = 'include.yaml'
  362. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  363. config_file = io.StringIO(
  364. '''
  365. stuff: !omit
  366. - x
  367. - y
  368. <<: !include include.yaml
  369. '''
  370. )
  371. config_file.name = 'config.yaml'
  372. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  373. config_paths = set()
  374. with pytest.raises(ValueError):
  375. module.load_configuration('config.yaml', config_paths)
  376. def test_load_configuration_with_omit_tag_on_non_scalar_list_item_raises():
  377. builtins = flexmock(sys.modules['builtins'])
  378. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  379. flexmock(module.os.path).should_receive('isabs').and_return(False)
  380. flexmock(module.os.path).should_receive('exists').and_return(True)
  381. include_file = io.StringIO(
  382. '''
  383. stuff:
  384. - foo: bar
  385. baz: quux
  386. '''
  387. )
  388. include_file.name = 'include.yaml'
  389. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  390. config_file = io.StringIO(
  391. '''
  392. stuff:
  393. - !omit foo: bar
  394. baz: quux
  395. <<: !include include.yaml
  396. '''
  397. )
  398. config_file.name = 'config.yaml'
  399. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  400. config_paths = set()
  401. with pytest.raises(ValueError):
  402. module.load_configuration('config.yaml', config_paths)
  403. def test_load_configuration_with_omit_tag_but_without_merge_raises():
  404. builtins = flexmock(sys.modules['builtins'])
  405. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  406. flexmock(module.os.path).should_receive('isabs').and_return(False)
  407. flexmock(module.os.path).should_receive('exists').and_return(True)
  408. include_file = io.StringIO(
  409. '''
  410. stuff:
  411. - a
  412. - !omit b
  413. - c
  414. '''
  415. )
  416. include_file.name = 'include.yaml'
  417. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  418. config_file = io.StringIO(
  419. '''
  420. stuff:
  421. - x
  422. - y
  423. <<: !include include.yaml
  424. '''
  425. )
  426. config_file.name = 'config.yaml'
  427. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  428. config_paths = set()
  429. with pytest.raises(ValueError):
  430. module.load_configuration('config.yaml', config_paths)
  431. def test_load_configuration_does_not_merge_include_list():
  432. builtins = flexmock(sys.modules['builtins'])
  433. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  434. flexmock(module.os.path).should_receive('isabs').and_return(False)
  435. flexmock(module.os.path).should_receive('exists').and_return(True)
  436. include_file = io.StringIO(
  437. '''
  438. - one
  439. - two
  440. '''
  441. )
  442. include_file.name = 'include.yaml'
  443. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  444. config_file = io.StringIO(
  445. '''
  446. foo: bar
  447. repositories:
  448. <<: !include include.yaml
  449. '''
  450. )
  451. config_file.name = 'config.yaml'
  452. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  453. config_paths = set()
  454. with pytest.raises(module.ruamel.yaml.error.YAMLError):
  455. assert module.load_configuration('config.yaml', config_paths)
  456. @pytest.mark.parametrize(
  457. 'node_class',
  458. (
  459. module.ruamel.yaml.nodes.MappingNode,
  460. module.ruamel.yaml.nodes.SequenceNode,
  461. module.ruamel.yaml.nodes.ScalarNode,
  462. ),
  463. )
  464. def test_raise_retain_node_error_raises(node_class):
  465. with pytest.raises(ValueError):
  466. module.raise_retain_node_error(
  467. loader=flexmock(), node=node_class(tag=flexmock(), value=flexmock())
  468. )
  469. def test_raise_omit_node_error_raises():
  470. with pytest.raises(ValueError):
  471. module.raise_omit_node_error(loader=flexmock(), node=flexmock())
  472. def test_filter_omitted_nodes_discards_values_with_omit_tag_and_also_equal_values():
  473. nodes = [flexmock(), flexmock()]
  474. values = [
  475. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='a'),
  476. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='b'),
  477. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='c'),
  478. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='a'),
  479. module.ruamel.yaml.nodes.ScalarNode(tag='!omit', value='b'),
  480. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='c'),
  481. ]
  482. result = module.filter_omitted_nodes(nodes, values)
  483. assert [item.value for item in result] == ['a', 'c', 'a', 'c']
  484. def test_filter_omitted_nodes_keeps_all_values_when_given_only_one_node():
  485. nodes = [flexmock()]
  486. values = [
  487. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='a'),
  488. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='b'),
  489. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='c'),
  490. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='a'),
  491. module.ruamel.yaml.nodes.ScalarNode(tag='!omit', value='b'),
  492. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='c'),
  493. ]
  494. result = module.filter_omitted_nodes(nodes, values)
  495. assert [item.value for item in result] == ['a', 'b', 'c', 'a', 'b', 'c']
  496. def test_merge_values_combines_mapping_values():
  497. nodes = [
  498. (
  499. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='option'),
  500. module.ruamel.yaml.nodes.MappingNode(
  501. tag='tag:yaml.org,2002:map',
  502. value=[
  503. (
  504. module.ruamel.yaml.nodes.ScalarNode(
  505. tag='tag:yaml.org,2002:str', value='keep_hourly'
  506. ),
  507. module.ruamel.yaml.nodes.ScalarNode(
  508. tag='tag:yaml.org,2002:int', value='24'
  509. ),
  510. ),
  511. (
  512. module.ruamel.yaml.nodes.ScalarNode(
  513. tag='tag:yaml.org,2002:str', value='keep_daily'
  514. ),
  515. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='7'),
  516. ),
  517. ],
  518. ),
  519. ),
  520. (
  521. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='option'),
  522. module.ruamel.yaml.nodes.MappingNode(
  523. tag='tag:yaml.org,2002:map',
  524. value=[
  525. (
  526. module.ruamel.yaml.nodes.ScalarNode(
  527. tag='tag:yaml.org,2002:str', value='keep_daily'
  528. ),
  529. module.ruamel.yaml.nodes.ScalarNode(
  530. tag='tag:yaml.org,2002:int', value='25'
  531. ),
  532. ),
  533. ],
  534. ),
  535. ),
  536. (
  537. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='option'),
  538. module.ruamel.yaml.nodes.MappingNode(
  539. tag='tag:yaml.org,2002:map',
  540. value=[
  541. (
  542. module.ruamel.yaml.nodes.ScalarNode(
  543. tag='tag:yaml.org,2002:str', value='keep_nanosecondly'
  544. ),
  545. module.ruamel.yaml.nodes.ScalarNode(
  546. tag='tag:yaml.org,2002:int', value='1000'
  547. ),
  548. ),
  549. ],
  550. ),
  551. ),
  552. ]
  553. values = module.merge_values(nodes)
  554. assert len(values) == 4
  555. assert values[0][0].value == 'keep_hourly'
  556. assert values[0][1].value == '24'
  557. assert values[1][0].value == 'keep_daily'
  558. assert values[1][1].value == '7'
  559. assert values[2][0].value == 'keep_daily'
  560. assert values[2][1].value == '25'
  561. assert values[3][0].value == 'keep_nanosecondly'
  562. assert values[3][1].value == '1000'
  563. def test_merge_values_combines_sequence_values():
  564. nodes = [
  565. (
  566. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='option'),
  567. module.ruamel.yaml.nodes.SequenceNode(
  568. tag='tag:yaml.org,2002:seq',
  569. value=[
  570. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='1'),
  571. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='2'),
  572. ],
  573. ),
  574. ),
  575. (
  576. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='option'),
  577. module.ruamel.yaml.nodes.SequenceNode(
  578. tag='tag:yaml.org,2002:seq',
  579. value=[
  580. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='3'),
  581. ],
  582. ),
  583. ),
  584. (
  585. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='option'),
  586. module.ruamel.yaml.nodes.SequenceNode(
  587. tag='tag:yaml.org,2002:seq',
  588. value=[
  589. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='4'),
  590. ],
  591. ),
  592. ),
  593. ]
  594. values = module.merge_values(nodes)
  595. assert len(values) == 4
  596. assert values[0].value == '1'
  597. assert values[1].value == '2'
  598. assert values[2].value == '3'
  599. assert values[3].value == '4'
  600. def test_deep_merge_nodes_replaces_colliding_scalar_values():
  601. node_values = [
  602. (
  603. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  604. module.ruamel.yaml.nodes.MappingNode(
  605. tag='tag:yaml.org,2002:map',
  606. value=[
  607. (
  608. module.ruamel.yaml.nodes.ScalarNode(
  609. tag='tag:yaml.org,2002:str', value='keep_hourly'
  610. ),
  611. module.ruamel.yaml.nodes.ScalarNode(
  612. tag='tag:yaml.org,2002:int', value='24'
  613. ),
  614. ),
  615. (
  616. module.ruamel.yaml.nodes.ScalarNode(
  617. tag='tag:yaml.org,2002:str', value='keep_daily'
  618. ),
  619. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='7'),
  620. ),
  621. ],
  622. ),
  623. ),
  624. (
  625. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  626. module.ruamel.yaml.nodes.MappingNode(
  627. tag='tag:yaml.org,2002:map',
  628. value=[
  629. (
  630. module.ruamel.yaml.nodes.ScalarNode(
  631. tag='tag:yaml.org,2002:str', value='keep_daily'
  632. ),
  633. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='5'),
  634. ),
  635. ],
  636. ),
  637. ),
  638. ]
  639. result = module.deep_merge_nodes(node_values)
  640. assert len(result) == 1
  641. (section_key, section_value) = result[0]
  642. assert section_key.value == 'retention'
  643. options = section_value.value
  644. assert len(options) == 2
  645. assert options[0][0].value == 'keep_daily'
  646. assert options[0][1].value == '5'
  647. assert options[1][0].value == 'keep_hourly'
  648. assert options[1][1].value == '24'
  649. def test_deep_merge_nodes_keeps_non_colliding_scalar_values():
  650. node_values = [
  651. (
  652. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  653. module.ruamel.yaml.nodes.MappingNode(
  654. tag='tag:yaml.org,2002:map',
  655. value=[
  656. (
  657. module.ruamel.yaml.nodes.ScalarNode(
  658. tag='tag:yaml.org,2002:str', value='keep_hourly'
  659. ),
  660. module.ruamel.yaml.nodes.ScalarNode(
  661. tag='tag:yaml.org,2002:int', value='24'
  662. ),
  663. ),
  664. (
  665. module.ruamel.yaml.nodes.ScalarNode(
  666. tag='tag:yaml.org,2002:str', value='keep_daily'
  667. ),
  668. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='7'),
  669. ),
  670. ],
  671. ),
  672. ),
  673. (
  674. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  675. module.ruamel.yaml.nodes.MappingNode(
  676. tag='tag:yaml.org,2002:map',
  677. value=[
  678. (
  679. module.ruamel.yaml.nodes.ScalarNode(
  680. tag='tag:yaml.org,2002:str', value='keep_minutely'
  681. ),
  682. module.ruamel.yaml.nodes.ScalarNode(
  683. tag='tag:yaml.org,2002:int', value='10'
  684. ),
  685. ),
  686. ],
  687. ),
  688. ),
  689. ]
  690. result = module.deep_merge_nodes(node_values)
  691. assert len(result) == 1
  692. (section_key, section_value) = result[0]
  693. assert section_key.value == 'retention'
  694. options = section_value.value
  695. assert len(options) == 3
  696. assert options[0][0].value == 'keep_daily'
  697. assert options[0][1].value == '7'
  698. assert options[1][0].value == 'keep_hourly'
  699. assert options[1][1].value == '24'
  700. assert options[2][0].value == 'keep_minutely'
  701. assert options[2][1].value == '10'
  702. def test_deep_merge_nodes_keeps_deeply_nested_values():
  703. node_values = [
  704. (
  705. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='storage'),
  706. module.ruamel.yaml.nodes.MappingNode(
  707. tag='tag:yaml.org,2002:map',
  708. value=[
  709. (
  710. module.ruamel.yaml.nodes.ScalarNode(
  711. tag='tag:yaml.org,2002:str', value='lock_wait'
  712. ),
  713. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='5'),
  714. ),
  715. (
  716. module.ruamel.yaml.nodes.ScalarNode(
  717. tag='tag:yaml.org,2002:str', value='extra_borg_options'
  718. ),
  719. module.ruamel.yaml.nodes.MappingNode(
  720. tag='tag:yaml.org,2002:map',
  721. value=[
  722. (
  723. module.ruamel.yaml.nodes.ScalarNode(
  724. tag='tag:yaml.org,2002:str', value='init'
  725. ),
  726. module.ruamel.yaml.nodes.ScalarNode(
  727. tag='tag:yaml.org,2002:str', value='--init-option'
  728. ),
  729. ),
  730. ],
  731. ),
  732. ),
  733. ],
  734. ),
  735. ),
  736. (
  737. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='storage'),
  738. module.ruamel.yaml.nodes.MappingNode(
  739. tag='tag:yaml.org,2002:map',
  740. value=[
  741. (
  742. module.ruamel.yaml.nodes.ScalarNode(
  743. tag='tag:yaml.org,2002:str', value='extra_borg_options'
  744. ),
  745. module.ruamel.yaml.nodes.MappingNode(
  746. tag='tag:yaml.org,2002:map',
  747. value=[
  748. (
  749. module.ruamel.yaml.nodes.ScalarNode(
  750. tag='tag:yaml.org,2002:str', value='prune'
  751. ),
  752. module.ruamel.yaml.nodes.ScalarNode(
  753. tag='tag:yaml.org,2002:str', value='--prune-option'
  754. ),
  755. ),
  756. ],
  757. ),
  758. ),
  759. ],
  760. ),
  761. ),
  762. ]
  763. result = module.deep_merge_nodes(node_values)
  764. assert len(result) == 1
  765. (section_key, section_value) = result[0]
  766. assert section_key.value == 'storage'
  767. options = section_value.value
  768. assert len(options) == 2
  769. assert options[0][0].value == 'extra_borg_options'
  770. assert options[1][0].value == 'lock_wait'
  771. assert options[1][1].value == '5'
  772. nested_options = options[0][1].value
  773. assert len(nested_options) == 2
  774. assert nested_options[0][0].value == 'init'
  775. assert nested_options[0][1].value == '--init-option'
  776. assert nested_options[1][0].value == 'prune'
  777. assert nested_options[1][1].value == '--prune-option'
  778. def test_deep_merge_nodes_appends_colliding_sequence_values():
  779. node_values = [
  780. (
  781. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  782. module.ruamel.yaml.nodes.MappingNode(
  783. tag='tag:yaml.org,2002:map',
  784. value=[
  785. (
  786. module.ruamel.yaml.nodes.ScalarNode(
  787. tag='tag:yaml.org,2002:str', value='before_backup'
  788. ),
  789. module.ruamel.yaml.nodes.SequenceNode(
  790. tag='tag:yaml.org,2002:seq',
  791. value=[
  792. module.ruamel.yaml.ScalarNode(
  793. tag='tag:yaml.org,2002:str', value='echo 1'
  794. ),
  795. module.ruamel.yaml.ScalarNode(
  796. tag='tag:yaml.org,2002:str', value='echo 2'
  797. ),
  798. ],
  799. ),
  800. ),
  801. ],
  802. ),
  803. ),
  804. (
  805. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  806. module.ruamel.yaml.nodes.MappingNode(
  807. tag='tag:yaml.org,2002:map',
  808. value=[
  809. (
  810. module.ruamel.yaml.nodes.ScalarNode(
  811. tag='tag:yaml.org,2002:str', value='before_backup'
  812. ),
  813. module.ruamel.yaml.nodes.SequenceNode(
  814. tag='tag:yaml.org,2002:seq',
  815. value=[
  816. module.ruamel.yaml.ScalarNode(
  817. tag='tag:yaml.org,2002:str', value='echo 3'
  818. ),
  819. module.ruamel.yaml.ScalarNode(
  820. tag='tag:yaml.org,2002:str', value='echo 4'
  821. ),
  822. ],
  823. ),
  824. ),
  825. ],
  826. ),
  827. ),
  828. ]
  829. result = module.deep_merge_nodes(node_values)
  830. assert len(result) == 1
  831. (section_key, section_value) = result[0]
  832. assert section_key.value == 'hooks'
  833. options = section_value.value
  834. assert len(options) == 1
  835. assert options[0][0].value == 'before_backup'
  836. assert [item.value for item in options[0][1].value] == ['echo 1', 'echo 2', 'echo 3', 'echo 4']
  837. def test_deep_merge_nodes_errors_on_colliding_values_of_different_types():
  838. node_values = [
  839. (
  840. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  841. module.ruamel.yaml.nodes.MappingNode(
  842. tag='tag:yaml.org,2002:map',
  843. value=[
  844. (
  845. module.ruamel.yaml.nodes.ScalarNode(
  846. tag='tag:yaml.org,2002:str', value='before_backup'
  847. ),
  848. module.ruamel.yaml.nodes.ScalarNode(
  849. tag='tag:yaml.org,2002:str', value='echo oopsie daisy'
  850. ),
  851. ),
  852. ],
  853. ),
  854. ),
  855. (
  856. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  857. module.ruamel.yaml.nodes.MappingNode(
  858. tag='tag:yaml.org,2002:map',
  859. value=[
  860. (
  861. module.ruamel.yaml.nodes.ScalarNode(
  862. tag='tag:yaml.org,2002:str', value='before_backup'
  863. ),
  864. module.ruamel.yaml.nodes.SequenceNode(
  865. tag='tag:yaml.org,2002:seq',
  866. value=[
  867. module.ruamel.yaml.ScalarNode(
  868. tag='tag:yaml.org,2002:str', value='echo 3'
  869. ),
  870. module.ruamel.yaml.ScalarNode(
  871. tag='tag:yaml.org,2002:str', value='echo 4'
  872. ),
  873. ],
  874. ),
  875. ),
  876. ],
  877. ),
  878. ),
  879. ]
  880. with pytest.raises(ValueError):
  881. module.deep_merge_nodes(node_values)
  882. def test_deep_merge_nodes_only_keeps_mapping_values_tagged_with_retain():
  883. node_values = [
  884. (
  885. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  886. module.ruamel.yaml.nodes.MappingNode(
  887. tag='tag:yaml.org,2002:map',
  888. value=[
  889. (
  890. module.ruamel.yaml.nodes.ScalarNode(
  891. tag='tag:yaml.org,2002:str', value='keep_hourly'
  892. ),
  893. module.ruamel.yaml.nodes.ScalarNode(
  894. tag='tag:yaml.org,2002:int', value='24'
  895. ),
  896. ),
  897. (
  898. module.ruamel.yaml.nodes.ScalarNode(
  899. tag='tag:yaml.org,2002:str', value='keep_daily'
  900. ),
  901. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='7'),
  902. ),
  903. ],
  904. ),
  905. ),
  906. (
  907. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  908. module.ruamel.yaml.nodes.MappingNode(
  909. tag='!retain',
  910. value=[
  911. (
  912. module.ruamel.yaml.nodes.ScalarNode(
  913. tag='tag:yaml.org,2002:str', value='keep_daily'
  914. ),
  915. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='5'),
  916. ),
  917. ],
  918. ),
  919. ),
  920. ]
  921. result = module.deep_merge_nodes(node_values)
  922. assert len(result) == 1
  923. (section_key, section_value) = result[0]
  924. assert section_key.value == 'retention'
  925. assert section_value.tag == 'tag:yaml.org,2002:map'
  926. options = section_value.value
  927. assert len(options) == 1
  928. assert options[0][0].value == 'keep_daily'
  929. assert options[0][1].value == '5'
  930. def test_deep_merge_nodes_only_keeps_sequence_values_tagged_with_retain():
  931. node_values = [
  932. (
  933. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  934. module.ruamel.yaml.nodes.MappingNode(
  935. tag='tag:yaml.org,2002:map',
  936. value=[
  937. (
  938. module.ruamel.yaml.nodes.ScalarNode(
  939. tag='tag:yaml.org,2002:str', value='before_backup'
  940. ),
  941. module.ruamel.yaml.nodes.SequenceNode(
  942. tag='tag:yaml.org,2002:seq',
  943. value=[
  944. module.ruamel.yaml.ScalarNode(
  945. tag='tag:yaml.org,2002:str', value='echo 1'
  946. ),
  947. module.ruamel.yaml.ScalarNode(
  948. tag='tag:yaml.org,2002:str', value='echo 2'
  949. ),
  950. ],
  951. ),
  952. ),
  953. ],
  954. ),
  955. ),
  956. (
  957. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  958. module.ruamel.yaml.nodes.MappingNode(
  959. tag='tag:yaml.org,2002:map',
  960. value=[
  961. (
  962. module.ruamel.yaml.nodes.ScalarNode(
  963. tag='tag:yaml.org,2002:str', value='before_backup'
  964. ),
  965. module.ruamel.yaml.nodes.SequenceNode(
  966. tag='!retain',
  967. value=[
  968. module.ruamel.yaml.ScalarNode(
  969. tag='tag:yaml.org,2002:str', value='echo 3'
  970. ),
  971. module.ruamel.yaml.ScalarNode(
  972. tag='tag:yaml.org,2002:str', value='echo 4'
  973. ),
  974. ],
  975. ),
  976. ),
  977. ],
  978. ),
  979. ),
  980. ]
  981. result = module.deep_merge_nodes(node_values)
  982. assert len(result) == 1
  983. (section_key, section_value) = result[0]
  984. assert section_key.value == 'hooks'
  985. options = section_value.value
  986. assert len(options) == 1
  987. assert options[0][0].value == 'before_backup'
  988. assert options[0][1].tag == 'tag:yaml.org,2002:seq'
  989. assert [item.value for item in options[0][1].value] == ['echo 3', 'echo 4']
  990. def test_deep_merge_nodes_skips_sequence_values_tagged_with_omit():
  991. node_values = [
  992. (
  993. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  994. module.ruamel.yaml.nodes.MappingNode(
  995. tag='tag:yaml.org,2002:map',
  996. value=[
  997. (
  998. module.ruamel.yaml.nodes.ScalarNode(
  999. tag='tag:yaml.org,2002:str', value='before_backup'
  1000. ),
  1001. module.ruamel.yaml.nodes.SequenceNode(
  1002. tag='tag:yaml.org,2002:seq',
  1003. value=[
  1004. module.ruamel.yaml.ScalarNode(
  1005. tag='tag:yaml.org,2002:str', value='echo 1'
  1006. ),
  1007. module.ruamel.yaml.ScalarNode(
  1008. tag='tag:yaml.org,2002:str', value='echo 2'
  1009. ),
  1010. ],
  1011. ),
  1012. ),
  1013. ],
  1014. ),
  1015. ),
  1016. (
  1017. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  1018. module.ruamel.yaml.nodes.MappingNode(
  1019. tag='tag:yaml.org,2002:map',
  1020. value=[
  1021. (
  1022. module.ruamel.yaml.nodes.ScalarNode(
  1023. tag='tag:yaml.org,2002:str', value='before_backup'
  1024. ),
  1025. module.ruamel.yaml.nodes.SequenceNode(
  1026. tag='tag:yaml.org,2002:seq',
  1027. value=[
  1028. module.ruamel.yaml.ScalarNode(tag='!omit', value='echo 2'),
  1029. module.ruamel.yaml.ScalarNode(
  1030. tag='tag:yaml.org,2002:str', value='echo 3'
  1031. ),
  1032. ],
  1033. ),
  1034. ),
  1035. ],
  1036. ),
  1037. ),
  1038. ]
  1039. result = module.deep_merge_nodes(node_values)
  1040. assert len(result) == 1
  1041. (section_key, section_value) = result[0]
  1042. assert section_key.value == 'hooks'
  1043. options = section_value.value
  1044. assert len(options) == 1
  1045. assert options[0][0].value == 'before_backup'
  1046. assert [item.value for item in options[0][1].value] == ['echo 1', 'echo 3']