test_serializers.py 13 KB

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