# -*- coding: utf-8 -*- """ Unit tests for salt.utils.yamlloader.SaltYamlSafeLoader """ # Import python libs from __future__ import absolute_import, print_function, unicode_literals import collections import textwrap import salt.utils.files # Import 3rd-party libs from salt.ext import six from salt.utils.yamlloader import SaltYamlSafeLoader from tests.support.mock import mock_open, patch # Import Salt Testing Libs from tests.support.unit import TestCase # Import Salt Libs from yaml.constructor import ConstructorError class YamlLoaderTestCase(TestCase): """ TestCase for salt.utils.yamlloader module """ @staticmethod def render_yaml(data): """ Takes a YAML string, puts it into a mock file, passes that to the YAML SaltYamlSafeLoader and then returns the rendered/parsed YAML data """ if six.PY2: # On Python 2, data read from a filehandle will not already be # unicode, so we need to encode it first to properly simulate # reading from a file. This is because unicode_literals is imported # and all of the data to be used in mock_open will be a unicode # type. Encoding will make it a str. data = salt.utils.data.encode(data) with patch("salt.utils.files.fopen", mock_open(read_data=data)) as mocked_file: with salt.utils.files.fopen(mocked_file) as mocked_stream: return SaltYamlSafeLoader(mocked_stream).get_data() @staticmethod def raise_error(value): raise TypeError("{0!r} is not a unicode string".format(value)) def assert_unicode(self, value): """ Make sure the entire data structure is unicode """ if six.PY3: return if isinstance(value, six.string_types): if not isinstance(value, six.text_type): self.raise_error(value) elif isinstance(value, collections.Mapping): for k, v in six.iteritems(value): self.assert_unicode(k) self.assert_unicode(v) elif isinstance(value, collections.Iterable): for item in value: self.assert_unicode(item) def assert_matches(self, ret, expected): self.assertEqual(ret, expected) self.assert_unicode(ret) def test_yaml_basics(self): """ Test parsing an ordinary path """ self.assert_matches( self.render_yaml( textwrap.dedent( """\ p1: - alpha - beta""" ) ), {"p1": ["alpha", "beta"]}, ) def test_yaml_merge(self): """ Test YAML anchors """ # Simple merge test self.assert_matches( self.render_yaml( textwrap.dedent( """\ p1: &p1 v1: alpha p2: <<: *p1 v2: beta""" ) ), {"p1": {"v1": "alpha"}, "p2": {"v1": "alpha", "v2": "beta"}}, ) # Test that keys/nodes are overwritten self.assert_matches( self.render_yaml( textwrap.dedent( """\ p1: &p1 v1: alpha p2: <<: *p1 v1: new_alpha""" ) ), {"p1": {"v1": "alpha"}, "p2": {"v1": "new_alpha"}}, ) # Test merging of lists self.assert_matches( self.render_yaml( textwrap.dedent( """\ p1: &p1 v1: &v1 - t1 - t2 p2: v2: *v1""" ) ), {"p2": {"v2": ["t1", "t2"]}, "p1": {"v1": ["t1", "t2"]}}, ) def test_yaml_duplicates(self): """ Test that duplicates still throw an error """ with self.assertRaises(ConstructorError): self.render_yaml( textwrap.dedent( """\ p1: alpha p1: beta""" ) ) with self.assertRaises(ConstructorError): self.render_yaml( textwrap.dedent( """\ p1: &p1 v1: alpha p2: <<: *p1 v2: beta v2: betabeta""" ) ) def test_yaml_with_plain_scalars(self): """ Test that plain (i.e. unqoted) string and non-string scalars are properly handled """ self.assert_matches( self.render_yaml( textwrap.dedent( """\ foo: b: {foo: bar, one: 1, list: [1, two, 3]}""" ) ), {"foo": {"b": {"foo": "bar", "one": 1, "list": [1, "two", 3]}}}, )