test_load.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143
  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_with_retain_tag_merges_include_but_keeps_local_values():
  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. include_file = io.StringIO(
  197. '''
  198. stuff:
  199. foo: bar
  200. baz: quux
  201. other:
  202. a: b
  203. c: d
  204. '''
  205. )
  206. include_file.name = 'include.yaml'
  207. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  208. config_file = io.StringIO(
  209. '''
  210. stuff: !retain
  211. foo: override
  212. other:
  213. a: override
  214. <<: !include include.yaml
  215. '''
  216. )
  217. config_file.name = 'config.yaml'
  218. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  219. config_paths = {'other.yaml'}
  220. assert module.load_configuration('config.yaml', config_paths) == {
  221. 'stuff': {'foo': 'override'},
  222. 'other': {'a': 'override', 'c': 'd'},
  223. }
  224. assert config_paths == {'config.yaml', '/tmp/include.yaml', 'other.yaml'}
  225. def test_load_configuration_with_retain_tag_but_without_merge_include_raises():
  226. builtins = flexmock(sys.modules['builtins'])
  227. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  228. flexmock(module.os.path).should_receive('isabs').and_return(False)
  229. flexmock(module.os.path).should_receive('exists').and_return(True)
  230. include_file = io.StringIO(
  231. '''
  232. stuff: !retain
  233. foo: bar
  234. baz: quux
  235. '''
  236. )
  237. include_file.name = 'include.yaml'
  238. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  239. config_file = io.StringIO(
  240. '''
  241. stuff:
  242. foo: override
  243. <<: !include include.yaml
  244. '''
  245. )
  246. config_file.name = 'config.yaml'
  247. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  248. config_paths = set()
  249. with pytest.raises(ValueError):
  250. module.load_configuration('config.yaml', config_paths)
  251. def test_load_configuration_with_retain_tag_on_scalar_raises():
  252. builtins = flexmock(sys.modules['builtins'])
  253. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  254. flexmock(module.os.path).should_receive('isabs').and_return(False)
  255. flexmock(module.os.path).should_receive('exists').and_return(True)
  256. include_file = io.StringIO(
  257. '''
  258. stuff:
  259. foo: bar
  260. baz: quux
  261. '''
  262. )
  263. include_file.name = 'include.yaml'
  264. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  265. config_file = io.StringIO(
  266. '''
  267. stuff:
  268. foo: !retain override
  269. <<: !include include.yaml
  270. '''
  271. )
  272. config_file.name = 'config.yaml'
  273. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  274. config_paths = set()
  275. with pytest.raises(ValueError):
  276. module.load_configuration('config.yaml', config_paths)
  277. def test_load_configuration_with_omit_tag_merges_include_and_omits_requested_values():
  278. builtins = flexmock(sys.modules['builtins'])
  279. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  280. flexmock(module.os.path).should_receive('isabs').and_return(False)
  281. flexmock(module.os.path).should_receive('exists').and_return(True)
  282. include_file = io.StringIO(
  283. '''
  284. stuff:
  285. - a
  286. - b
  287. - c
  288. '''
  289. )
  290. include_file.name = 'include.yaml'
  291. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  292. config_file = io.StringIO(
  293. '''
  294. stuff:
  295. - x
  296. - !omit b
  297. - y
  298. <<: !include include.yaml
  299. '''
  300. )
  301. config_file.name = 'config.yaml'
  302. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  303. config_paths = {'other.yaml'}
  304. assert module.load_configuration('config.yaml', config_paths) == {'stuff': ['a', 'c', 'x', 'y']}
  305. assert config_paths == {'config.yaml', '/tmp/include.yaml', 'other.yaml'}
  306. def test_load_configuration_with_omit_tag_on_unknown_value_merges_include_and_does_not_raise():
  307. builtins = flexmock(sys.modules['builtins'])
  308. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  309. flexmock(module.os.path).should_receive('isabs').and_return(False)
  310. flexmock(module.os.path).should_receive('exists').and_return(True)
  311. include_file = io.StringIO(
  312. '''
  313. stuff:
  314. - a
  315. - b
  316. - c
  317. '''
  318. )
  319. include_file.name = 'include.yaml'
  320. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  321. config_file = io.StringIO(
  322. '''
  323. stuff:
  324. - x
  325. - !omit q
  326. - y
  327. <<: !include include.yaml
  328. '''
  329. )
  330. config_file.name = 'config.yaml'
  331. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  332. config_paths = {'other.yaml'}
  333. assert module.load_configuration('config.yaml', config_paths) == {
  334. 'stuff': ['a', 'b', 'c', 'x', 'y']
  335. }
  336. assert config_paths == {'config.yaml', '/tmp/include.yaml', 'other.yaml'}
  337. def test_load_configuration_with_omit_tag_on_non_list_item_raises():
  338. builtins = flexmock(sys.modules['builtins'])
  339. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  340. flexmock(module.os.path).should_receive('isabs').and_return(False)
  341. flexmock(module.os.path).should_receive('exists').and_return(True)
  342. include_file = io.StringIO(
  343. '''
  344. stuff:
  345. - a
  346. - b
  347. - c
  348. '''
  349. )
  350. include_file.name = 'include.yaml'
  351. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  352. config_file = io.StringIO(
  353. '''
  354. stuff: !omit
  355. - x
  356. - y
  357. <<: !include include.yaml
  358. '''
  359. )
  360. config_file.name = 'config.yaml'
  361. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  362. config_paths = set()
  363. with pytest.raises(ValueError):
  364. module.load_configuration('config.yaml', config_paths)
  365. def test_load_configuration_with_omit_tag_on_non_scalar_list_item_raises():
  366. builtins = flexmock(sys.modules['builtins'])
  367. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  368. flexmock(module.os.path).should_receive('isabs').and_return(False)
  369. flexmock(module.os.path).should_receive('exists').and_return(True)
  370. include_file = io.StringIO(
  371. '''
  372. stuff:
  373. - foo: bar
  374. baz: quux
  375. '''
  376. )
  377. include_file.name = 'include.yaml'
  378. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  379. config_file = io.StringIO(
  380. '''
  381. stuff:
  382. - !omit foo: bar
  383. baz: quux
  384. <<: !include include.yaml
  385. '''
  386. )
  387. config_file.name = 'config.yaml'
  388. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  389. config_paths = set()
  390. with pytest.raises(ValueError):
  391. module.load_configuration('config.yaml', config_paths)
  392. def test_load_configuration_with_omit_tag_but_without_merge_raises():
  393. builtins = flexmock(sys.modules['builtins'])
  394. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  395. flexmock(module.os.path).should_receive('isabs').and_return(False)
  396. flexmock(module.os.path).should_receive('exists').and_return(True)
  397. include_file = io.StringIO(
  398. '''
  399. stuff:
  400. - a
  401. - !omit b
  402. - c
  403. '''
  404. )
  405. include_file.name = 'include.yaml'
  406. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  407. config_file = io.StringIO(
  408. '''
  409. stuff:
  410. - x
  411. - y
  412. <<: !include include.yaml
  413. '''
  414. )
  415. config_file.name = 'config.yaml'
  416. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  417. config_paths = set()
  418. with pytest.raises(ValueError):
  419. module.load_configuration('config.yaml', config_paths)
  420. def test_load_configuration_does_not_merge_include_list():
  421. builtins = flexmock(sys.modules['builtins'])
  422. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  423. flexmock(module.os.path).should_receive('isabs').and_return(False)
  424. flexmock(module.os.path).should_receive('exists').and_return(True)
  425. include_file = io.StringIO(
  426. '''
  427. - one
  428. - two
  429. '''
  430. )
  431. include_file.name = 'include.yaml'
  432. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  433. config_file = io.StringIO(
  434. '''
  435. foo: bar
  436. repositories:
  437. <<: !include include.yaml
  438. '''
  439. )
  440. config_file.name = 'config.yaml'
  441. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  442. config_paths = set()
  443. with pytest.raises(module.ruamel.yaml.error.YAMLError):
  444. assert module.load_configuration('config.yaml', config_paths)
  445. @pytest.mark.parametrize(
  446. 'node_class',
  447. (
  448. module.ruamel.yaml.nodes.MappingNode,
  449. module.ruamel.yaml.nodes.SequenceNode,
  450. module.ruamel.yaml.nodes.ScalarNode,
  451. ),
  452. )
  453. def test_raise_retain_node_error_raises(node_class):
  454. with pytest.raises(ValueError):
  455. module.raise_retain_node_error(
  456. loader=flexmock(), node=node_class(tag=flexmock(), value=flexmock())
  457. )
  458. def test_raise_omit_node_error_raises():
  459. with pytest.raises(ValueError):
  460. module.raise_omit_node_error(loader=flexmock(), node=flexmock())
  461. def test_filter_omitted_nodes_discards_values_with_omit_tag_and_also_equal_values():
  462. nodes = [flexmock(), flexmock()]
  463. values = [
  464. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='a'),
  465. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='b'),
  466. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='c'),
  467. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='a'),
  468. module.ruamel.yaml.nodes.ScalarNode(tag='!omit', value='b'),
  469. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='c'),
  470. ]
  471. result = module.filter_omitted_nodes(nodes, values)
  472. assert [item.value for item in result] == ['a', 'c', 'a', 'c']
  473. def test_filter_omitted_nodes_keeps_all_values_when_given_only_one_node():
  474. nodes = [flexmock()]
  475. values = [
  476. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='a'),
  477. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='b'),
  478. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='c'),
  479. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='a'),
  480. module.ruamel.yaml.nodes.ScalarNode(tag='!omit', value='b'),
  481. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='c'),
  482. ]
  483. result = module.filter_omitted_nodes(nodes, values)
  484. assert [item.value for item in result] == ['a', 'b', 'c', 'a', 'b', 'c']
  485. def test_merge_values_combines_mapping_values():
  486. nodes = [
  487. (
  488. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='option'),
  489. module.ruamel.yaml.nodes.MappingNode(
  490. tag='tag:yaml.org,2002:map',
  491. value=[
  492. (
  493. module.ruamel.yaml.nodes.ScalarNode(
  494. tag='tag:yaml.org,2002:str', value='keep_hourly'
  495. ),
  496. module.ruamel.yaml.nodes.ScalarNode(
  497. tag='tag:yaml.org,2002:int', value='24'
  498. ),
  499. ),
  500. (
  501. module.ruamel.yaml.nodes.ScalarNode(
  502. tag='tag:yaml.org,2002:str', value='keep_daily'
  503. ),
  504. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='7'),
  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_daily'
  517. ),
  518. module.ruamel.yaml.nodes.ScalarNode(
  519. tag='tag:yaml.org,2002:int', value='25'
  520. ),
  521. ),
  522. ],
  523. ),
  524. ),
  525. (
  526. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='option'),
  527. module.ruamel.yaml.nodes.MappingNode(
  528. tag='tag:yaml.org,2002:map',
  529. value=[
  530. (
  531. module.ruamel.yaml.nodes.ScalarNode(
  532. tag='tag:yaml.org,2002:str', value='keep_nanosecondly'
  533. ),
  534. module.ruamel.yaml.nodes.ScalarNode(
  535. tag='tag:yaml.org,2002:int', value='1000'
  536. ),
  537. ),
  538. ],
  539. ),
  540. ),
  541. ]
  542. values = module.merge_values(nodes)
  543. assert len(values) == 4
  544. assert values[0][0].value == 'keep_hourly'
  545. assert values[0][1].value == '24'
  546. assert values[1][0].value == 'keep_daily'
  547. assert values[1][1].value == '7'
  548. assert values[2][0].value == 'keep_daily'
  549. assert values[2][1].value == '25'
  550. assert values[3][0].value == 'keep_nanosecondly'
  551. assert values[3][1].value == '1000'
  552. def test_merge_values_combines_sequence_values():
  553. nodes = [
  554. (
  555. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='option'),
  556. module.ruamel.yaml.nodes.SequenceNode(
  557. tag='tag:yaml.org,2002:seq',
  558. value=[
  559. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='1'),
  560. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='2'),
  561. ],
  562. ),
  563. ),
  564. (
  565. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='option'),
  566. module.ruamel.yaml.nodes.SequenceNode(
  567. tag='tag:yaml.org,2002:seq',
  568. value=[
  569. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='3'),
  570. ],
  571. ),
  572. ),
  573. (
  574. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='option'),
  575. module.ruamel.yaml.nodes.SequenceNode(
  576. tag='tag:yaml.org,2002:seq',
  577. value=[
  578. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='4'),
  579. ],
  580. ),
  581. ),
  582. ]
  583. values = module.merge_values(nodes)
  584. assert len(values) == 4
  585. assert values[0].value == '1'
  586. assert values[1].value == '2'
  587. assert values[2].value == '3'
  588. assert values[3].value == '4'
  589. def test_deep_merge_nodes_replaces_colliding_scalar_values():
  590. node_values = [
  591. (
  592. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  593. module.ruamel.yaml.nodes.MappingNode(
  594. tag='tag:yaml.org,2002:map',
  595. value=[
  596. (
  597. module.ruamel.yaml.nodes.ScalarNode(
  598. tag='tag:yaml.org,2002:str', value='keep_hourly'
  599. ),
  600. module.ruamel.yaml.nodes.ScalarNode(
  601. tag='tag:yaml.org,2002:int', value='24'
  602. ),
  603. ),
  604. (
  605. module.ruamel.yaml.nodes.ScalarNode(
  606. tag='tag:yaml.org,2002:str', value='keep_daily'
  607. ),
  608. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='7'),
  609. ),
  610. ],
  611. ),
  612. ),
  613. (
  614. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  615. module.ruamel.yaml.nodes.MappingNode(
  616. tag='tag:yaml.org,2002:map',
  617. value=[
  618. (
  619. module.ruamel.yaml.nodes.ScalarNode(
  620. tag='tag:yaml.org,2002:str', value='keep_daily'
  621. ),
  622. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='5'),
  623. ),
  624. ],
  625. ),
  626. ),
  627. ]
  628. result = module.deep_merge_nodes(node_values)
  629. assert len(result) == 1
  630. (section_key, section_value) = result[0]
  631. assert section_key.value == 'retention'
  632. options = section_value.value
  633. assert len(options) == 2
  634. assert options[0][0].value == 'keep_daily'
  635. assert options[0][1].value == '5'
  636. assert options[1][0].value == 'keep_hourly'
  637. assert options[1][1].value == '24'
  638. def test_deep_merge_nodes_keeps_non_colliding_scalar_values():
  639. node_values = [
  640. (
  641. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  642. module.ruamel.yaml.nodes.MappingNode(
  643. tag='tag:yaml.org,2002:map',
  644. value=[
  645. (
  646. module.ruamel.yaml.nodes.ScalarNode(
  647. tag='tag:yaml.org,2002:str', value='keep_hourly'
  648. ),
  649. module.ruamel.yaml.nodes.ScalarNode(
  650. tag='tag:yaml.org,2002:int', value='24'
  651. ),
  652. ),
  653. (
  654. module.ruamel.yaml.nodes.ScalarNode(
  655. tag='tag:yaml.org,2002:str', value='keep_daily'
  656. ),
  657. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='7'),
  658. ),
  659. ],
  660. ),
  661. ),
  662. (
  663. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  664. module.ruamel.yaml.nodes.MappingNode(
  665. tag='tag:yaml.org,2002:map',
  666. value=[
  667. (
  668. module.ruamel.yaml.nodes.ScalarNode(
  669. tag='tag:yaml.org,2002:str', value='keep_minutely'
  670. ),
  671. module.ruamel.yaml.nodes.ScalarNode(
  672. tag='tag:yaml.org,2002:int', value='10'
  673. ),
  674. ),
  675. ],
  676. ),
  677. ),
  678. ]
  679. result = module.deep_merge_nodes(node_values)
  680. assert len(result) == 1
  681. (section_key, section_value) = result[0]
  682. assert section_key.value == 'retention'
  683. options = section_value.value
  684. assert len(options) == 3
  685. assert options[0][0].value == 'keep_daily'
  686. assert options[0][1].value == '7'
  687. assert options[1][0].value == 'keep_hourly'
  688. assert options[1][1].value == '24'
  689. assert options[2][0].value == 'keep_minutely'
  690. assert options[2][1].value == '10'
  691. def test_deep_merge_nodes_keeps_deeply_nested_values():
  692. node_values = [
  693. (
  694. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='storage'),
  695. module.ruamel.yaml.nodes.MappingNode(
  696. tag='tag:yaml.org,2002:map',
  697. value=[
  698. (
  699. module.ruamel.yaml.nodes.ScalarNode(
  700. tag='tag:yaml.org,2002:str', value='lock_wait'
  701. ),
  702. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='5'),
  703. ),
  704. (
  705. module.ruamel.yaml.nodes.ScalarNode(
  706. tag='tag:yaml.org,2002:str', value='extra_borg_options'
  707. ),
  708. module.ruamel.yaml.nodes.MappingNode(
  709. tag='tag:yaml.org,2002:map',
  710. value=[
  711. (
  712. module.ruamel.yaml.nodes.ScalarNode(
  713. tag='tag:yaml.org,2002:str', value='init'
  714. ),
  715. module.ruamel.yaml.nodes.ScalarNode(
  716. tag='tag:yaml.org,2002:str', value='--init-option'
  717. ),
  718. ),
  719. ],
  720. ),
  721. ),
  722. ],
  723. ),
  724. ),
  725. (
  726. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='storage'),
  727. module.ruamel.yaml.nodes.MappingNode(
  728. tag='tag:yaml.org,2002:map',
  729. value=[
  730. (
  731. module.ruamel.yaml.nodes.ScalarNode(
  732. tag='tag:yaml.org,2002:str', value='extra_borg_options'
  733. ),
  734. module.ruamel.yaml.nodes.MappingNode(
  735. tag='tag:yaml.org,2002:map',
  736. value=[
  737. (
  738. module.ruamel.yaml.nodes.ScalarNode(
  739. tag='tag:yaml.org,2002:str', value='prune'
  740. ),
  741. module.ruamel.yaml.nodes.ScalarNode(
  742. tag='tag:yaml.org,2002:str', value='--prune-option'
  743. ),
  744. ),
  745. ],
  746. ),
  747. ),
  748. ],
  749. ),
  750. ),
  751. ]
  752. result = module.deep_merge_nodes(node_values)
  753. assert len(result) == 1
  754. (section_key, section_value) = result[0]
  755. assert section_key.value == 'storage'
  756. options = section_value.value
  757. assert len(options) == 2
  758. assert options[0][0].value == 'extra_borg_options'
  759. assert options[1][0].value == 'lock_wait'
  760. assert options[1][1].value == '5'
  761. nested_options = options[0][1].value
  762. assert len(nested_options) == 2
  763. assert nested_options[0][0].value == 'init'
  764. assert nested_options[0][1].value == '--init-option'
  765. assert nested_options[1][0].value == 'prune'
  766. assert nested_options[1][1].value == '--prune-option'
  767. def test_deep_merge_nodes_appends_colliding_sequence_values():
  768. node_values = [
  769. (
  770. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  771. module.ruamel.yaml.nodes.MappingNode(
  772. tag='tag:yaml.org,2002:map',
  773. value=[
  774. (
  775. module.ruamel.yaml.nodes.ScalarNode(
  776. tag='tag:yaml.org,2002:str', value='before_backup'
  777. ),
  778. module.ruamel.yaml.nodes.SequenceNode(
  779. tag='tag:yaml.org,2002:seq',
  780. value=[
  781. module.ruamel.yaml.ScalarNode(
  782. tag='tag:yaml.org,2002:str', value='echo 1'
  783. ),
  784. module.ruamel.yaml.ScalarNode(
  785. tag='tag:yaml.org,2002:str', value='echo 2'
  786. ),
  787. ],
  788. ),
  789. ),
  790. ],
  791. ),
  792. ),
  793. (
  794. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  795. module.ruamel.yaml.nodes.MappingNode(
  796. tag='tag:yaml.org,2002:map',
  797. value=[
  798. (
  799. module.ruamel.yaml.nodes.ScalarNode(
  800. tag='tag:yaml.org,2002:str', value='before_backup'
  801. ),
  802. module.ruamel.yaml.nodes.SequenceNode(
  803. tag='tag:yaml.org,2002:seq',
  804. value=[
  805. module.ruamel.yaml.ScalarNode(
  806. tag='tag:yaml.org,2002:str', value='echo 3'
  807. ),
  808. module.ruamel.yaml.ScalarNode(
  809. tag='tag:yaml.org,2002:str', value='echo 4'
  810. ),
  811. ],
  812. ),
  813. ),
  814. ],
  815. ),
  816. ),
  817. ]
  818. result = module.deep_merge_nodes(node_values)
  819. assert len(result) == 1
  820. (section_key, section_value) = result[0]
  821. assert section_key.value == 'hooks'
  822. options = section_value.value
  823. assert len(options) == 1
  824. assert options[0][0].value == 'before_backup'
  825. assert [item.value for item in options[0][1].value] == ['echo 1', 'echo 2', 'echo 3', 'echo 4']
  826. def test_deep_merge_nodes_errors_on_colliding_values_of_different_types():
  827. node_values = [
  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.ScalarNode(
  838. tag='tag:yaml.org,2002:str', value='echo oopsie daisy'
  839. ),
  840. ),
  841. ],
  842. ),
  843. ),
  844. (
  845. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  846. module.ruamel.yaml.nodes.MappingNode(
  847. tag='tag:yaml.org,2002:map',
  848. value=[
  849. (
  850. module.ruamel.yaml.nodes.ScalarNode(
  851. tag='tag:yaml.org,2002:str', value='before_backup'
  852. ),
  853. module.ruamel.yaml.nodes.SequenceNode(
  854. tag='tag:yaml.org,2002:seq',
  855. value=[
  856. module.ruamel.yaml.ScalarNode(
  857. tag='tag:yaml.org,2002:str', value='echo 3'
  858. ),
  859. module.ruamel.yaml.ScalarNode(
  860. tag='tag:yaml.org,2002:str', value='echo 4'
  861. ),
  862. ],
  863. ),
  864. ),
  865. ],
  866. ),
  867. ),
  868. ]
  869. with pytest.raises(ValueError):
  870. module.deep_merge_nodes(node_values)
  871. def test_deep_merge_nodes_only_keeps_mapping_values_tagged_with_retain():
  872. node_values = [
  873. (
  874. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  875. module.ruamel.yaml.nodes.MappingNode(
  876. tag='tag:yaml.org,2002:map',
  877. value=[
  878. (
  879. module.ruamel.yaml.nodes.ScalarNode(
  880. tag='tag:yaml.org,2002:str', value='keep_hourly'
  881. ),
  882. module.ruamel.yaml.nodes.ScalarNode(
  883. tag='tag:yaml.org,2002:int', value='24'
  884. ),
  885. ),
  886. (
  887. module.ruamel.yaml.nodes.ScalarNode(
  888. tag='tag:yaml.org,2002:str', value='keep_daily'
  889. ),
  890. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='7'),
  891. ),
  892. ],
  893. ),
  894. ),
  895. (
  896. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  897. module.ruamel.yaml.nodes.MappingNode(
  898. tag='!retain',
  899. value=[
  900. (
  901. module.ruamel.yaml.nodes.ScalarNode(
  902. tag='tag:yaml.org,2002:str', value='keep_daily'
  903. ),
  904. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='5'),
  905. ),
  906. ],
  907. ),
  908. ),
  909. ]
  910. result = module.deep_merge_nodes(node_values)
  911. assert len(result) == 1
  912. (section_key, section_value) = result[0]
  913. assert section_key.value == 'retention'
  914. assert section_value.tag == 'tag:yaml.org,2002:map'
  915. options = section_value.value
  916. assert len(options) == 1
  917. assert options[0][0].value == 'keep_daily'
  918. assert options[0][1].value == '5'
  919. def test_deep_merge_nodes_only_keeps_sequence_values_tagged_with_retain():
  920. node_values = [
  921. (
  922. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  923. module.ruamel.yaml.nodes.MappingNode(
  924. tag='tag:yaml.org,2002:map',
  925. value=[
  926. (
  927. module.ruamel.yaml.nodes.ScalarNode(
  928. tag='tag:yaml.org,2002:str', value='before_backup'
  929. ),
  930. module.ruamel.yaml.nodes.SequenceNode(
  931. tag='tag:yaml.org,2002:seq',
  932. value=[
  933. module.ruamel.yaml.ScalarNode(
  934. tag='tag:yaml.org,2002:str', value='echo 1'
  935. ),
  936. module.ruamel.yaml.ScalarNode(
  937. tag='tag:yaml.org,2002:str', value='echo 2'
  938. ),
  939. ],
  940. ),
  941. ),
  942. ],
  943. ),
  944. ),
  945. (
  946. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  947. module.ruamel.yaml.nodes.MappingNode(
  948. tag='tag:yaml.org,2002:map',
  949. value=[
  950. (
  951. module.ruamel.yaml.nodes.ScalarNode(
  952. tag='tag:yaml.org,2002:str', value='before_backup'
  953. ),
  954. module.ruamel.yaml.nodes.SequenceNode(
  955. tag='!retain',
  956. value=[
  957. module.ruamel.yaml.ScalarNode(
  958. tag='tag:yaml.org,2002:str', value='echo 3'
  959. ),
  960. module.ruamel.yaml.ScalarNode(
  961. tag='tag:yaml.org,2002:str', value='echo 4'
  962. ),
  963. ],
  964. ),
  965. ),
  966. ],
  967. ),
  968. ),
  969. ]
  970. result = module.deep_merge_nodes(node_values)
  971. assert len(result) == 1
  972. (section_key, section_value) = result[0]
  973. assert section_key.value == 'hooks'
  974. options = section_value.value
  975. assert len(options) == 1
  976. assert options[0][0].value == 'before_backup'
  977. assert options[0][1].tag == 'tag:yaml.org,2002:seq'
  978. assert [item.value for item in options[0][1].value] == ['echo 3', 'echo 4']
  979. def test_deep_merge_nodes_skips_sequence_values_tagged_with_omit():
  980. node_values = [
  981. (
  982. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  983. module.ruamel.yaml.nodes.MappingNode(
  984. tag='tag:yaml.org,2002:map',
  985. value=[
  986. (
  987. module.ruamel.yaml.nodes.ScalarNode(
  988. tag='tag:yaml.org,2002:str', value='before_backup'
  989. ),
  990. module.ruamel.yaml.nodes.SequenceNode(
  991. tag='tag:yaml.org,2002:seq',
  992. value=[
  993. module.ruamel.yaml.ScalarNode(
  994. tag='tag:yaml.org,2002:str', value='echo 1'
  995. ),
  996. module.ruamel.yaml.ScalarNode(
  997. tag='tag:yaml.org,2002:str', value='echo 2'
  998. ),
  999. ],
  1000. ),
  1001. ),
  1002. ],
  1003. ),
  1004. ),
  1005. (
  1006. module.ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  1007. module.ruamel.yaml.nodes.MappingNode(
  1008. tag='tag:yaml.org,2002:map',
  1009. value=[
  1010. (
  1011. module.ruamel.yaml.nodes.ScalarNode(
  1012. tag='tag:yaml.org,2002:str', value='before_backup'
  1013. ),
  1014. module.ruamel.yaml.nodes.SequenceNode(
  1015. tag='tag:yaml.org,2002:seq',
  1016. value=[
  1017. module.ruamel.yaml.ScalarNode(tag='!omit', value='echo 2'),
  1018. module.ruamel.yaml.ScalarNode(
  1019. tag='tag:yaml.org,2002:str', value='echo 3'
  1020. ),
  1021. ],
  1022. ),
  1023. ),
  1024. ],
  1025. ),
  1026. ),
  1027. ]
  1028. result = module.deep_merge_nodes(node_values)
  1029. assert len(result) == 1
  1030. (section_key, section_value) = result[0]
  1031. assert section_key.value == 'hooks'
  1032. options = section_value.value
  1033. assert len(options) == 1
  1034. assert options[0][0].value == 'before_backup'
  1035. assert [item.value for item in options[0][1].value] == ['echo 1', 'echo 3']