test_validate.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import io
  2. import string
  3. import sys
  4. import pytest
  5. from flexmock import flexmock
  6. from borgmatic.config import validate as module
  7. def test_schema_filename_returns_plausable_path():
  8. schema_path = module.schema_filename()
  9. assert schema_path.endswith('/schema.yaml')
  10. def mock_config_and_schema(config_yaml, schema_yaml=None):
  11. '''
  12. Set up mocks for the given config config YAML string and the schema YAML string, or the default
  13. schema if no schema is provided. The idea is that that the code under test consumes these mocks
  14. when parsing the configuration.
  15. '''
  16. config_stream = io.StringIO(config_yaml)
  17. config_stream.name = 'config.yaml'
  18. if schema_yaml is None:
  19. schema_stream = open(module.schema_filename())
  20. else:
  21. schema_stream = io.StringIO(schema_yaml)
  22. schema_stream.name = 'schema.yaml'
  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. builtins.should_receive('open').with_args('/tmp/config.yaml').and_return(config_stream)
  28. builtins.should_receive('open').with_args('/tmp/schema.yaml').and_return(schema_stream)
  29. def test_parse_configuration_transforms_file_into_mapping():
  30. mock_config_and_schema(
  31. '''
  32. location:
  33. source_directories:
  34. - /home
  35. - /etc
  36. repositories:
  37. - hostname.borg
  38. retention:
  39. keep_minutely: 60
  40. keep_hourly: 24
  41. keep_daily: 7
  42. consistency:
  43. checks:
  44. - repository
  45. - archives
  46. '''
  47. )
  48. result = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
  49. assert result == {
  50. 'location': {'source_directories': ['/home', '/etc'], 'repositories': ['hostname.borg']},
  51. 'retention': {'keep_daily': 7, 'keep_hourly': 24, 'keep_minutely': 60},
  52. 'consistency': {'checks': ['repository', 'archives']},
  53. }
  54. def test_parse_configuration_passes_through_quoted_punctuation():
  55. escaped_punctuation = string.punctuation.replace('\\', r'\\').replace('"', r'\"')
  56. mock_config_and_schema(
  57. '''
  58. location:
  59. source_directories:
  60. - /home
  61. repositories:
  62. - "{}.borg"
  63. '''.format(
  64. escaped_punctuation
  65. )
  66. )
  67. result = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
  68. assert result == {
  69. 'location': {
  70. 'source_directories': ['/home'],
  71. 'repositories': ['{}.borg'.format(string.punctuation)],
  72. }
  73. }
  74. def test_parse_configuration_with_schema_lacking_examples_does_not_raise():
  75. mock_config_and_schema(
  76. '''
  77. location:
  78. source_directories:
  79. - /home
  80. repositories:
  81. - hostname.borg
  82. ''',
  83. '''
  84. map:
  85. location:
  86. required: true
  87. map:
  88. source_directories:
  89. required: true
  90. seq:
  91. - type: scalar
  92. repositories:
  93. required: true
  94. seq:
  95. - type: scalar
  96. ''',
  97. )
  98. module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
  99. def test_parse_configuration_inlines_include():
  100. mock_config_and_schema(
  101. '''
  102. location:
  103. source_directories:
  104. - /home
  105. repositories:
  106. - hostname.borg
  107. retention:
  108. !include include.yaml
  109. '''
  110. )
  111. builtins = flexmock(sys.modules['builtins'])
  112. include_file = io.StringIO(
  113. '''
  114. keep_daily: 7
  115. keep_hourly: 24
  116. '''
  117. )
  118. include_file.name = 'include.yaml'
  119. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  120. result = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
  121. assert result == {
  122. 'location': {'source_directories': ['/home'], 'repositories': ['hostname.borg']},
  123. 'retention': {'keep_daily': 7, 'keep_hourly': 24},
  124. }
  125. def test_parse_configuration_merges_include():
  126. mock_config_and_schema(
  127. '''
  128. location:
  129. source_directories:
  130. - /home
  131. repositories:
  132. - hostname.borg
  133. retention:
  134. keep_daily: 1
  135. <<: !include include.yaml
  136. '''
  137. )
  138. builtins = flexmock(sys.modules['builtins'])
  139. include_file = io.StringIO(
  140. '''
  141. keep_daily: 7
  142. keep_hourly: 24
  143. '''
  144. )
  145. include_file.name = 'include.yaml'
  146. builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
  147. result = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
  148. assert result == {
  149. 'location': {'source_directories': ['/home'], 'repositories': ['hostname.borg']},
  150. 'retention': {'keep_daily': 1, 'keep_hourly': 24},
  151. }
  152. def test_parse_configuration_raises_for_missing_config_file():
  153. with pytest.raises(FileNotFoundError):
  154. module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
  155. def test_parse_configuration_raises_for_missing_schema_file():
  156. mock_config_and_schema('')
  157. builtins = flexmock(sys.modules['builtins'])
  158. builtins.should_receive('open').with_args('/tmp/schema.yaml').and_raise(FileNotFoundError)
  159. with pytest.raises(FileNotFoundError):
  160. module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
  161. def test_parse_configuration_raises_for_syntax_error():
  162. mock_config_and_schema('foo:\nbar')
  163. with pytest.raises(ValueError):
  164. module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
  165. def test_parse_configuration_raises_for_validation_error():
  166. mock_config_and_schema(
  167. '''
  168. location:
  169. source_directories: yes
  170. repositories:
  171. - hostname.borg
  172. '''
  173. )
  174. with pytest.raises(module.Validation_error):
  175. module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
  176. def test_parse_configuration_applies_overrides():
  177. mock_config_and_schema(
  178. '''
  179. location:
  180. source_directories:
  181. - /home
  182. repositories:
  183. - hostname.borg
  184. local_path: borg1
  185. '''
  186. )
  187. result = module.parse_configuration(
  188. '/tmp/config.yaml', '/tmp/schema.yaml', overrides=['location.local_path=borg2']
  189. )
  190. assert result == {
  191. 'location': {
  192. 'source_directories': ['/home'],
  193. 'repositories': ['hostname.borg'],
  194. 'local_path': 'borg2',
  195. }
  196. }
  197. def test_parse_configuration_applies_normalization():
  198. mock_config_and_schema(
  199. '''
  200. location:
  201. source_directories:
  202. - /home
  203. repositories:
  204. - hostname.borg
  205. exclude_if_present: .nobackup
  206. '''
  207. )
  208. result = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
  209. assert result == {
  210. 'location': {
  211. 'source_directories': ['/home'],
  212. 'repositories': ['hostname.borg'],
  213. 'exclude_if_present': ['.nobackup'],
  214. }
  215. }