2
0

test_load.py 42 KB

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