test_load.py 46 KB

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