test_serializers.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. # -*- coding: utf-8 -*-
  2. # Import python libs
  3. from __future__ import absolute_import, print_function, unicode_literals
  4. from textwrap import dedent
  5. # Import Salt Testing libs
  6. from tests.support.unit import skipIf, TestCase
  7. # Import 3rd party libs
  8. import jinja2
  9. import yaml as _yaml # future lint: disable=blacklisted-import
  10. from salt.ext import six
  11. # Import salt libs
  12. import salt.serializers.configparser as configparser
  13. import salt.serializers.json as json
  14. import salt.serializers.yaml as yaml
  15. import salt.serializers.yamlex as yamlex
  16. import salt.serializers.msgpack as msgpack
  17. import salt.serializers.python as python
  18. import salt.serializers.toml as toml
  19. from salt.serializers.yaml import EncryptedString
  20. from salt.serializers import SerializationError
  21. from salt.utils.odict import OrderedDict
  22. # Import test support libs
  23. from tests.support.helpers import flaky
  24. SKIP_MESSAGE = '%s is unavailable, have prerequisites been met?'
  25. @flaky(condition=six.PY3)
  26. class TestSerializers(TestCase):
  27. @skipIf(not json.available, SKIP_MESSAGE % 'json')
  28. def test_serialize_json(self):
  29. data = {
  30. "foo": "bar"
  31. }
  32. serialized = json.serialize(data)
  33. assert serialized == '{"foo": "bar"}', serialized
  34. deserialized = json.deserialize(serialized)
  35. assert deserialized == data, deserialized
  36. @skipIf(not yaml.available, SKIP_MESSAGE % 'yaml')
  37. def test_serialize_yaml(self):
  38. data = {
  39. "foo": "bar",
  40. "encrypted_data": EncryptedString("foo")
  41. }
  42. # The C dumper produces unquoted strings when serializing an
  43. # EncryptedString, while the non-C dumper produces quoted strings.
  44. expected = '{encrypted_data: !encrypted foo, foo: bar}' \
  45. if hasattr(_yaml, 'CSafeDumper') \
  46. else "{encrypted_data: !encrypted 'foo', foo: bar}"
  47. serialized = yaml.serialize(data)
  48. assert serialized == expected, serialized
  49. deserialized = yaml.deserialize(serialized)
  50. assert deserialized == data, deserialized
  51. @skipIf(not yaml.available, SKIP_MESSAGE % 'sls')
  52. def test_serialize_sls(self):
  53. data = {
  54. "foo": "bar"
  55. }
  56. serialized = yamlex.serialize(data)
  57. assert serialized == '{foo: bar}', serialized
  58. serialized = yamlex.serialize(data, default_flow_style=False)
  59. assert serialized == 'foo: bar', serialized
  60. deserialized = yamlex.deserialize(serialized)
  61. assert deserialized == data, deserialized
  62. serialized = yaml.serialize(data)
  63. assert serialized == '{foo: bar}', serialized
  64. deserialized = yaml.deserialize(serialized)
  65. assert deserialized == data, deserialized
  66. serialized = yaml.serialize(data, default_flow_style=False)
  67. assert serialized == 'foo: bar', serialized
  68. deserialized = yaml.deserialize(serialized)
  69. assert deserialized == data, deserialized
  70. @skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
  71. def test_serialize_complex_sls(self):
  72. data = OrderedDict([
  73. ("foo", 1),
  74. ("bar", 2),
  75. ("baz", True),
  76. ])
  77. serialized = yamlex.serialize(data)
  78. assert serialized == '{foo: 1, bar: 2, baz: true}', serialized
  79. deserialized = yamlex.deserialize(serialized)
  80. assert deserialized == data, deserialized
  81. serialized = yaml.serialize(data)
  82. assert serialized == '{bar: 2, baz: true, foo: 1}', serialized
  83. deserialized = yaml.deserialize(serialized)
  84. assert deserialized == data, deserialized
  85. @skipIf(not yaml.available, SKIP_MESSAGE % 'yaml')
  86. @skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
  87. def test_compare_sls_vs_yaml(self):
  88. src = '{foo: 1, bar: 2, baz: {qux: true}}'
  89. sls_data = yamlex.deserialize(src)
  90. yml_data = yaml.deserialize(src)
  91. # ensure that sls & yaml have the same base
  92. assert isinstance(sls_data, dict)
  93. assert isinstance(yml_data, dict)
  94. assert sls_data == yml_data
  95. # ensure that sls is ordered, while yaml not
  96. assert isinstance(sls_data, OrderedDict)
  97. assert not isinstance(yml_data, OrderedDict)
  98. @skipIf(not yaml.available, SKIP_MESSAGE % 'yaml')
  99. @skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
  100. @skipIf(six.PY3, 'Flaky on Python 3.')
  101. def test_compare_sls_vs_yaml_with_jinja(self):
  102. tpl = '{{ data }}'
  103. env = jinja2.Environment()
  104. src = '{foo: 1, bar: 2, baz: {qux: true}}'
  105. sls_src = env.from_string(tpl).render(data=yamlex.deserialize(src))
  106. yml_src = env.from_string(tpl).render(data=yaml.deserialize(src))
  107. sls_data = yamlex.deserialize(sls_src)
  108. yml_data = yaml.deserialize(yml_src)
  109. # ensure that sls & yaml have the same base
  110. assert isinstance(sls_data, dict)
  111. assert isinstance(yml_data, dict)
  112. # The below has been commented out because something the loader test
  113. # is modifying the yaml renderer to render things to unicode. Without
  114. # running the loader test, the below passes. Even reloading the module
  115. # from disk does not reset its internal state (per the Python docs).
  116. ##
  117. #assert sls_data == yml_data
  118. # ensure that sls is ordered, while yaml not
  119. assert isinstance(sls_data, OrderedDict)
  120. assert not isinstance(yml_data, OrderedDict)
  121. # prove that yaml does not handle well with OrderedDict
  122. # while sls is jinja friendly.
  123. obj = OrderedDict([
  124. ('foo', 1),
  125. ('bar', 2),
  126. ('baz', {'qux': True})
  127. ])
  128. sls_obj = yamlex.deserialize(yamlex.serialize(obj))
  129. try:
  130. yml_obj = yaml.deserialize(yaml.serialize(obj))
  131. except SerializationError:
  132. # BLAAM! yaml was unable to serialize OrderedDict,
  133. # but it's not the purpose of the current test.
  134. yml_obj = obj.copy()
  135. sls_src = env.from_string(tpl).render(data=sls_obj)
  136. yml_src = env.from_string(tpl).render(data=yml_obj)
  137. final_obj = yaml.deserialize(sls_src)
  138. assert obj == final_obj
  139. # BLAAM! yml_src is not valid !
  140. final_obj = OrderedDict(yaml.deserialize(yml_src))
  141. assert obj != final_obj, 'Objects matched! {} == {}'.format(obj, final_obj)
  142. @skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
  143. def test_sls_aggregate(self):
  144. src = dedent("""
  145. a: lol
  146. foo: !aggregate hello
  147. bar: !aggregate [1, 2, 3]
  148. baz: !aggregate
  149. a: 42
  150. b: 666
  151. c: the beast
  152. """).strip()
  153. # test that !aggregate is correctly parsed
  154. sls_obj = yamlex.deserialize(src)
  155. assert sls_obj == {
  156. 'a': 'lol',
  157. 'foo': ['hello'],
  158. 'bar': [1, 2, 3],
  159. 'baz': {
  160. 'a': 42,
  161. 'b': 666,
  162. 'c': 'the beast'
  163. }
  164. }, sls_obj
  165. assert dedent("""
  166. a: lol
  167. foo: [hello]
  168. bar: [1, 2, 3]
  169. baz: {a: 42, b: 666, c: the beast}
  170. """).strip() == yamlex.serialize(sls_obj), sls_obj
  171. # test that !aggregate aggregates scalars
  172. src = dedent("""
  173. placeholder: !aggregate foo
  174. placeholder: !aggregate bar
  175. placeholder: !aggregate baz
  176. """).strip()
  177. sls_obj = yamlex.deserialize(src)
  178. assert sls_obj == {'placeholder': ['foo', 'bar', 'baz']}, sls_obj
  179. # test that !aggregate aggregates lists
  180. src = dedent("""
  181. placeholder: !aggregate foo
  182. placeholder: !aggregate [bar, baz]
  183. placeholder: !aggregate []
  184. placeholder: !aggregate ~
  185. """).strip()
  186. sls_obj = yamlex.deserialize(src)
  187. assert sls_obj == {'placeholder': ['foo', 'bar', 'baz']}, sls_obj
  188. # test that !aggregate aggregates dicts
  189. src = dedent("""
  190. placeholder: !aggregate {foo: 42}
  191. placeholder: !aggregate {bar: null}
  192. placeholder: !aggregate {baz: inga}
  193. """).strip()
  194. sls_obj = yamlex.deserialize(src)
  195. assert sls_obj == {
  196. 'placeholder': {
  197. 'foo': 42,
  198. 'bar': None,
  199. 'baz': 'inga'
  200. }
  201. }, sls_obj
  202. # test that !aggregate aggregates deep dicts
  203. src = dedent("""
  204. placeholder: {foo: !aggregate {foo: 42}}
  205. placeholder: {foo: !aggregate {bar: null}}
  206. placeholder: {foo: !aggregate {baz: inga}}
  207. """).strip()
  208. sls_obj = yamlex.deserialize(src)
  209. assert sls_obj == {
  210. 'placeholder': {
  211. 'foo': {
  212. 'foo': 42,
  213. 'bar': None,
  214. 'baz': 'inga'
  215. }
  216. }
  217. }, sls_obj
  218. # test that {foo: !aggregate bar} and {!aggregate foo: bar}
  219. # are roughly equivalent.
  220. src = dedent("""
  221. placeholder: {!aggregate foo: {foo: 42}}
  222. placeholder: {!aggregate foo: {bar: null}}
  223. placeholder: {!aggregate foo: {baz: inga}}
  224. """).strip()
  225. sls_obj = yamlex.deserialize(src)
  226. assert sls_obj == {
  227. 'placeholder': {
  228. 'foo': {
  229. 'foo': 42,
  230. 'bar': None,
  231. 'baz': 'inga'
  232. }
  233. }
  234. }, sls_obj
  235. @skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
  236. def test_sls_reset(self):
  237. src = dedent("""
  238. placeholder: {!aggregate foo: {foo: 42}}
  239. placeholder: {!aggregate foo: {bar: null}}
  240. !reset placeholder: {!aggregate foo: {baz: inga}}
  241. """).strip()
  242. sls_obj = yamlex.deserialize(src)
  243. assert sls_obj == {
  244. 'placeholder': {
  245. 'foo': {
  246. 'baz': 'inga'
  247. }
  248. }
  249. }, sls_obj
  250. @skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
  251. def test_sls_repr(self):
  252. """
  253. Ensure that obj __repr__ and __str__ methods are yaml friendly.
  254. """
  255. def convert(obj):
  256. return yamlex.deserialize(yamlex.serialize(obj))
  257. sls_obj = convert(OrderedDict([('foo', 'bar'), ('baz', 'qux')]))
  258. # ensure that repr and str are yaml friendly
  259. assert sls_obj.__str__() == '{foo: bar, baz: qux}'
  260. assert sls_obj.__repr__() == '{foo: bar, baz: qux}'
  261. # ensure that repr and str are already quoted
  262. assert sls_obj['foo'].__str__() == '"bar"'
  263. assert sls_obj['foo'].__repr__() == '"bar"'
  264. @skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
  265. def test_sls_micking_file_merging(self):
  266. def convert(obj):
  267. return yamlex.deserialize(yamlex.serialize(obj))
  268. # let say that we have 2 pillar files
  269. src1 = dedent("""
  270. a: first
  271. b: !aggregate first
  272. c:
  273. subkey1: first
  274. subkey2: !aggregate first
  275. """).strip()
  276. src2 = dedent("""
  277. a: second
  278. b: !aggregate second
  279. c:
  280. subkey2: !aggregate second
  281. subkey3: second
  282. """).strip()
  283. sls_obj1 = yamlex.deserialize(src1)
  284. sls_obj2 = yamlex.deserialize(src2)
  285. sls_obj3 = yamlex.merge_recursive(sls_obj1, sls_obj2)
  286. assert sls_obj3 == {
  287. 'a': 'second',
  288. 'b': ['first', 'second'],
  289. 'c': {
  290. 'subkey2': ['first', 'second'],
  291. 'subkey3': 'second'
  292. }
  293. }, sls_obj3
  294. @skipIf(not msgpack.available, SKIP_MESSAGE % 'msgpack')
  295. def test_msgpack(self):
  296. data = OrderedDict([
  297. ("foo", 1),
  298. ("bar", 2),
  299. ("baz", True),
  300. ])
  301. serialized = msgpack.serialize(data)
  302. deserialized = msgpack.deserialize(serialized)
  303. assert deserialized == data, deserialized
  304. @skipIf(not python.available, SKIP_MESSAGE % 'python')
  305. def test_serialize_python(self):
  306. data = {'foo': 'bar'}
  307. serialized = python.serialize(data)
  308. expected = repr({'foo': 'bar'})
  309. assert serialized == expected, serialized
  310. @skipIf(not configparser.available, SKIP_MESSAGE % 'configparser')
  311. def test_configparser(self):
  312. data = {'foo': {'bar': 'baz'}}
  313. # configparser appends empty lines
  314. serialized = configparser.serialize(data).strip()
  315. assert serialized == "[foo]\nbar = baz", serialized
  316. deserialized = configparser.deserialize(serialized)
  317. assert deserialized == data, deserialized
  318. @skipIf(not toml.available, SKIP_MESSAGE % 'toml')
  319. def test_serialize_toml(self):
  320. data = {
  321. "foo": "bar"
  322. }
  323. serialized = toml.serialize(data)
  324. assert serialized == 'foo = "bar"\n', serialized
  325. deserialized = toml.deserialize(serialized)
  326. assert deserialized == data, deserialized