2
0

test_load.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. import io
  2. import sys
  3. import pytest
  4. import ruamel.yaml
  5. from flexmock import flexmock
  6. from borgmatic.config import load as module
  7. def test_load_configuration_parses_contents():
  8. builtins = flexmock(sys.modules['builtins'])
  9. builtins.should_receive('open').with_args('config.yaml').and_return('key: value')
  10. assert module.load_configuration('config.yaml') == {'key': 'value'}
  11. def test_load_configuration_inlines_include_relative_to_current_directory():
  12. builtins = flexmock(sys.modules['builtins'])
  13. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  14. flexmock(module.os.path).should_receive('isabs').and_return(False)
  15. flexmock(module.os.path).should_receive('exists').and_return(True)
  16. include_file = io.StringIO('value')
  17. include_file.name = 'include.yaml'
  18. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  19. config_file = io.StringIO('key: !include include.yaml')
  20. config_file.name = 'config.yaml'
  21. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  22. assert module.load_configuration('config.yaml') == {'key': 'value'}
  23. def test_load_configuration_inlines_include_relative_to_config_parent_directory():
  24. builtins = flexmock(sys.modules['builtins'])
  25. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  26. flexmock(module.os.path).should_receive('isabs').with_args('/etc').and_return(True)
  27. flexmock(module.os.path).should_receive('isabs').with_args('/etc/config.yaml').and_return(True)
  28. flexmock(module.os.path).should_receive('isabs').with_args('include.yaml').and_return(False)
  29. flexmock(module.os.path).should_receive('exists').with_args('/tmp/include.yaml').and_return(
  30. False
  31. )
  32. flexmock(module.os.path).should_receive('exists').with_args('/etc/include.yaml').and_return(
  33. True
  34. )
  35. include_file = io.StringIO('value')
  36. include_file.name = 'include.yaml'
  37. builtins.should_receive('open').with_args('/etc/include.yaml').and_return(include_file)
  38. config_file = io.StringIO('key: !include include.yaml')
  39. config_file.name = '/etc/config.yaml'
  40. builtins.should_receive('open').with_args('/etc/config.yaml').and_return(config_file)
  41. assert module.load_configuration('/etc/config.yaml') == {'key': 'value'}
  42. def test_load_configuration_raises_if_relative_include_does_not_exist():
  43. builtins = flexmock(sys.modules['builtins'])
  44. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  45. flexmock(module.os.path).should_receive('isabs').with_args('/etc').and_return(True)
  46. flexmock(module.os.path).should_receive('isabs').with_args('/etc/config.yaml').and_return(True)
  47. flexmock(module.os.path).should_receive('isabs').with_args('include.yaml').and_return(False)
  48. flexmock(module.os.path).should_receive('exists').and_return(False)
  49. config_file = io.StringIO('key: !include include.yaml')
  50. config_file.name = '/etc/config.yaml'
  51. builtins.should_receive('open').with_args('/etc/config.yaml').and_return(config_file)
  52. with pytest.raises(FileNotFoundError):
  53. module.load_configuration('/etc/config.yaml')
  54. def test_load_configuration_inlines_absolute_include():
  55. builtins = flexmock(sys.modules['builtins'])
  56. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  57. flexmock(module.os.path).should_receive('isabs').and_return(True)
  58. flexmock(module.os.path).should_receive('exists').never()
  59. include_file = io.StringIO('value')
  60. include_file.name = '/root/include.yaml'
  61. builtins.should_receive('open').with_args('/root/include.yaml').and_return(include_file)
  62. config_file = io.StringIO('key: !include /root/include.yaml')
  63. config_file.name = 'config.yaml'
  64. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  65. assert module.load_configuration('config.yaml') == {'key': 'value'}
  66. def test_load_configuration_raises_if_absolute_include_does_not_exist():
  67. builtins = flexmock(sys.modules['builtins'])
  68. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  69. flexmock(module.os.path).should_receive('isabs').and_return(True)
  70. builtins.should_receive('open').with_args('/root/include.yaml').and_raise(FileNotFoundError)
  71. config_file = io.StringIO('key: !include /root/include.yaml')
  72. config_file.name = 'config.yaml'
  73. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  74. with pytest.raises(FileNotFoundError):
  75. assert module.load_configuration('config.yaml')
  76. def test_load_configuration_merges_include():
  77. builtins = flexmock(sys.modules['builtins'])
  78. flexmock(module.os).should_receive('getcwd').and_return('/tmp')
  79. flexmock(module.os.path).should_receive('isabs').and_return(False)
  80. flexmock(module.os.path).should_receive('exists').and_return(True)
  81. include_file = io.StringIO(
  82. '''
  83. foo: bar
  84. baz: quux
  85. '''
  86. )
  87. include_file.name = 'include.yaml'
  88. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  89. config_file = io.StringIO(
  90. '''
  91. foo: override
  92. <<: !include include.yaml
  93. '''
  94. )
  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') == {'foo': 'override', 'baz': 'quux'}
  98. def test_load_configuration_does_not_merge_include_list():
  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(False)
  102. flexmock(module.os.path).should_receive('exists').and_return(True)
  103. include_file = io.StringIO(
  104. '''
  105. - one
  106. - two
  107. '''
  108. )
  109. include_file.name = 'include.yaml'
  110. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  111. config_file = io.StringIO(
  112. '''
  113. foo: bar
  114. repositories:
  115. <<: !include include.yaml
  116. '''
  117. )
  118. config_file.name = 'config.yaml'
  119. builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
  120. with pytest.raises(ruamel.yaml.error.YAMLError):
  121. assert module.load_configuration('config.yaml')
  122. def test_deep_merge_nodes_replaces_colliding_scalar_values():
  123. node_values = [
  124. (
  125. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  126. ruamel.yaml.nodes.MappingNode(
  127. tag='tag:yaml.org,2002:map',
  128. value=[
  129. (
  130. ruamel.yaml.nodes.ScalarNode(
  131. tag='tag:yaml.org,2002:str', value='keep_hourly'
  132. ),
  133. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='24'),
  134. ),
  135. (
  136. ruamel.yaml.nodes.ScalarNode(
  137. tag='tag:yaml.org,2002:str', value='keep_daily'
  138. ),
  139. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='7'),
  140. ),
  141. ],
  142. ),
  143. ),
  144. (
  145. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  146. ruamel.yaml.nodes.MappingNode(
  147. tag='tag:yaml.org,2002:map',
  148. value=[
  149. (
  150. ruamel.yaml.nodes.ScalarNode(
  151. tag='tag:yaml.org,2002:str', value='keep_daily'
  152. ),
  153. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='5'),
  154. ),
  155. ],
  156. ),
  157. ),
  158. ]
  159. result = module.deep_merge_nodes(node_values)
  160. assert len(result) == 1
  161. (section_key, section_value) = result[0]
  162. assert section_key.value == 'retention'
  163. options = section_value.value
  164. assert len(options) == 2
  165. assert options[0][0].value == 'keep_hourly'
  166. assert options[0][1].value == '24'
  167. assert options[1][0].value == 'keep_daily'
  168. assert options[1][1].value == '5'
  169. def test_deep_merge_nodes_keeps_non_colliding_scalar_values():
  170. node_values = [
  171. (
  172. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  173. ruamel.yaml.nodes.MappingNode(
  174. tag='tag:yaml.org,2002:map',
  175. value=[
  176. (
  177. ruamel.yaml.nodes.ScalarNode(
  178. tag='tag:yaml.org,2002:str', value='keep_hourly'
  179. ),
  180. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='24'),
  181. ),
  182. (
  183. ruamel.yaml.nodes.ScalarNode(
  184. tag='tag:yaml.org,2002:str', value='keep_daily'
  185. ),
  186. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='7'),
  187. ),
  188. ],
  189. ),
  190. ),
  191. (
  192. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='retention'),
  193. ruamel.yaml.nodes.MappingNode(
  194. tag='tag:yaml.org,2002:map',
  195. value=[
  196. (
  197. ruamel.yaml.nodes.ScalarNode(
  198. tag='tag:yaml.org,2002:str', value='keep_minutely'
  199. ),
  200. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='10'),
  201. ),
  202. ],
  203. ),
  204. ),
  205. ]
  206. result = module.deep_merge_nodes(node_values)
  207. assert len(result) == 1
  208. (section_key, section_value) = result[0]
  209. assert section_key.value == 'retention'
  210. options = section_value.value
  211. assert len(options) == 3
  212. assert options[0][0].value == 'keep_hourly'
  213. assert options[0][1].value == '24'
  214. assert options[1][0].value == 'keep_daily'
  215. assert options[1][1].value == '7'
  216. assert options[2][0].value == 'keep_minutely'
  217. assert options[2][1].value == '10'
  218. def test_deep_merge_nodes_keeps_deeply_nested_values():
  219. node_values = [
  220. (
  221. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='storage'),
  222. ruamel.yaml.nodes.MappingNode(
  223. tag='tag:yaml.org,2002:map',
  224. value=[
  225. (
  226. ruamel.yaml.nodes.ScalarNode(
  227. tag='tag:yaml.org,2002:str', value='lock_wait'
  228. ),
  229. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:int', value='5'),
  230. ),
  231. (
  232. ruamel.yaml.nodes.ScalarNode(
  233. tag='tag:yaml.org,2002:str', value='extra_borg_options'
  234. ),
  235. ruamel.yaml.nodes.MappingNode(
  236. tag='tag:yaml.org,2002:map',
  237. value=[
  238. (
  239. ruamel.yaml.nodes.ScalarNode(
  240. tag='tag:yaml.org,2002:str', value='init'
  241. ),
  242. ruamel.yaml.nodes.ScalarNode(
  243. tag='tag:yaml.org,2002:str', value='--init-option'
  244. ),
  245. ),
  246. ],
  247. ),
  248. ),
  249. ],
  250. ),
  251. ),
  252. (
  253. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='storage'),
  254. ruamel.yaml.nodes.MappingNode(
  255. tag='tag:yaml.org,2002:map',
  256. value=[
  257. (
  258. ruamel.yaml.nodes.ScalarNode(
  259. tag='tag:yaml.org,2002:str', value='extra_borg_options'
  260. ),
  261. ruamel.yaml.nodes.MappingNode(
  262. tag='tag:yaml.org,2002:map',
  263. value=[
  264. (
  265. ruamel.yaml.nodes.ScalarNode(
  266. tag='tag:yaml.org,2002:str', value='prune'
  267. ),
  268. ruamel.yaml.nodes.ScalarNode(
  269. tag='tag:yaml.org,2002:str', value='--prune-option'
  270. ),
  271. ),
  272. ],
  273. ),
  274. ),
  275. ],
  276. ),
  277. ),
  278. ]
  279. result = module.deep_merge_nodes(node_values)
  280. assert len(result) == 1
  281. (section_key, section_value) = result[0]
  282. assert section_key.value == 'storage'
  283. options = section_value.value
  284. assert len(options) == 2
  285. assert options[0][0].value == 'lock_wait'
  286. assert options[0][1].value == '5'
  287. assert options[1][0].value == 'extra_borg_options'
  288. nested_options = options[1][1].value
  289. assert len(nested_options) == 2
  290. assert nested_options[0][0].value == 'init'
  291. assert nested_options[0][1].value == '--init-option'
  292. assert nested_options[1][0].value == 'prune'
  293. assert nested_options[1][1].value == '--prune-option'
  294. def test_deep_merge_nodes_appends_colliding_sequence_values():
  295. node_values = [
  296. (
  297. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  298. ruamel.yaml.nodes.MappingNode(
  299. tag='tag:yaml.org,2002:map',
  300. value=[
  301. (
  302. ruamel.yaml.nodes.ScalarNode(
  303. tag='tag:yaml.org,2002:str', value='before_backup'
  304. ),
  305. ruamel.yaml.nodes.SequenceNode(
  306. tag='tag:yaml.org,2002:int', value=['echo 1', 'echo 2']
  307. ),
  308. ),
  309. ],
  310. ),
  311. ),
  312. (
  313. ruamel.yaml.nodes.ScalarNode(tag='tag:yaml.org,2002:str', value='hooks'),
  314. ruamel.yaml.nodes.MappingNode(
  315. tag='tag:yaml.org,2002:map',
  316. value=[
  317. (
  318. ruamel.yaml.nodes.ScalarNode(
  319. tag='tag:yaml.org,2002:str', value='before_backup'
  320. ),
  321. ruamel.yaml.nodes.SequenceNode(
  322. tag='tag:yaml.org,2002:int', value=['echo 3', 'echo 4']
  323. ),
  324. ),
  325. ],
  326. ),
  327. ),
  328. ]
  329. result = module.deep_merge_nodes(node_values)
  330. assert len(result) == 1
  331. (section_key, section_value) = result[0]
  332. assert section_key.value == 'hooks'
  333. options = section_value.value
  334. assert len(options) == 1
  335. assert options[0][0].value == 'before_backup'
  336. assert options[0][1].value == ['echo 1', 'echo 2', 'echo 3', 'echo 4']