test_serializers.py 13 KB

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