""" Unit tests for salt.utils.templates.py """ import logging import os import sys from pathlib import PurePath, PurePosixPath import salt.utils.files import salt.utils.templates from tests.support import mock from tests.support.helpers import with_tempdir from tests.support.unit import TestCase, skipIf try: import Cheetah as _ HAS_CHEETAH = True except ImportError: HAS_CHEETAH = False log = logging.getLogger(__name__) class RenderTestCase(TestCase): def setUp(self): # Default context for salt.utils.templates.render_*_tmpl to work self.context = { "opts": {"cachedir": "/D", "__cli": "salt"}, "saltenv": None, } ### Tests for Jinja (whitespace-friendly) def test_render_jinja_sanity(self): tmpl = """OK""" res = salt.utils.templates.render_jinja_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") def test_render_jinja_evaluate(self): tmpl = """{{ "OK" }}""" res = salt.utils.templates.render_jinja_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") def test_render_jinja_evaluate_multi(self): tmpl = """{% if 1 -%}OK{%- endif %}""" res = salt.utils.templates.render_jinja_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") def test_render_jinja_variable(self): tmpl = """{{ var }}""" ctx = dict(self.context) ctx["var"] = "OK" res = salt.utils.templates.render_jinja_tmpl(tmpl, ctx) self.assertEqual(res, "OK") ### Tests for mako template def test_render_mako_sanity(self): tmpl = """OK""" res = salt.utils.templates.render_mako_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") def test_render_mako_evaluate(self): tmpl = """${ "OK" }""" res = salt.utils.templates.render_mako_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") def test_render_mako_evaluate_multi(self): tmpl = """ % if 1: OK % endif """ res = salt.utils.templates.render_mako_tmpl(tmpl, dict(self.context)) stripped = res.strip() self.assertEqual(stripped, "OK") def test_render_mako_variable(self): tmpl = """${ var }""" ctx = dict(self.context) ctx["var"] = "OK" res = salt.utils.templates.render_mako_tmpl(tmpl, ctx) self.assertEqual(res, "OK") ### Tests for wempy template @skipIf( sys.version_info > (3,), "The wempy module is currently unsupported under Python3", ) def test_render_wempy_sanity(self): tmpl = """OK""" res = salt.utils.templates.render_wempy_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") @skipIf( sys.version_info > (3,), "The wempy module is currently unsupported under Python3", ) def test_render_wempy_evaluate(self): tmpl = """{{="OK"}}""" res = salt.utils.templates.render_wempy_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") @skipIf( sys.version_info > (3,), "The wempy module is currently unsupported under Python3", ) def test_render_wempy_evaluate_multi(self): tmpl = """{{if 1:}}OK{{pass}}""" res = salt.utils.templates.render_wempy_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") @skipIf( sys.version_info > (3,), "The wempy module is currently unsupported under Python3", ) def test_render_wempy_variable(self): tmpl = """{{=var}}""" ctx = dict(self.context) ctx["var"] = "OK" res = salt.utils.templates.render_wempy_tmpl(tmpl, ctx) self.assertEqual(res, "OK") ### Tests for genshi template (xml-based) def test_render_genshi_sanity(self): tmpl = """OK""" res = salt.utils.templates.render_genshi_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") def test_render_genshi_evaluate(self): tmpl = """${ "OK" }""" res = salt.utils.templates.render_genshi_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") def test_render_genshi_evaluate_condition(self): tmpl = """OK""" res = salt.utils.templates.render_genshi_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") def test_render_genshi_variable(self): tmpl = """$var""" ctx = dict(self.context) ctx["var"] = "OK" res = salt.utils.templates.render_genshi_tmpl(tmpl, ctx) self.assertEqual(res, "OK") def test_render_genshi_variable_replace(self): tmpl = """not ok""" ctx = dict(self.context) ctx["var"] = "OK" res = salt.utils.templates.render_genshi_tmpl(tmpl, ctx) self.assertEqual(res, "OK") ### Tests for cheetah template (line-oriented and xml-friendly) @skipIf(not HAS_CHEETAH, "The Cheetah Python module is missing.") def test_render_cheetah_sanity(self): tmpl = """OK""" res = salt.utils.templates.render_cheetah_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") @skipIf(not HAS_CHEETAH, "The Cheetah Python module is missing.") def test_render_cheetah_evaluate(self): tmpl = """<%="OK"%>""" res = salt.utils.templates.render_cheetah_tmpl(tmpl, dict(self.context)) self.assertEqual(res, "OK") @skipIf(not HAS_CHEETAH, "The Cheetah Python module is missing.") def test_render_cheetah_evaluate_xml(self): tmpl = """ <% if 1: %> OK <% pass %> """ res = salt.utils.templates.render_cheetah_tmpl(tmpl, dict(self.context)) stripped = res.strip() self.assertEqual(stripped, "OK") @skipIf(not HAS_CHEETAH, "The Cheetah Python module is missing.") def test_render_cheetah_evaluate_text(self): tmpl = """ #if 1 OK #end if """ res = salt.utils.templates.render_cheetah_tmpl(tmpl, dict(self.context)) stripped = res.strip() self.assertEqual(stripped, "OK") @skipIf(not HAS_CHEETAH, "The Cheetah Python module is missing.") def test_render_cheetah_variable(self): tmpl = """$var""" ctx = dict(self.context) ctx["var"] = "OK" res = salt.utils.templates.render_cheetah_tmpl(tmpl, ctx) self.assertEqual(res.strip(), "OK") class MockRender: def __call__(self, tplstr, context, tmplpath=None): self.tplstr = tplstr self.context = context self.tmplpath = tmplpath return tplstr class WrapRenderTestCase(TestCase): def assertDictContainsAll(self, actual, **expected): """ Make sure dictionary contains at least all expected values""" actual = {key: actual[key] for key in expected if key in actual} self.assertEqual(expected, actual) def _test_generated_sls_context(self, tmplpath, sls, **expected): """ Generic SLS Context Test""" # DeNormalize tmplpath tmplpath = str(PurePath(PurePosixPath(tmplpath))) if tmplpath.startswith("\\"): tmplpath = "C:{}".format(tmplpath) expected["tplpath"] = tmplpath actual = salt.utils.templates._generate_sls_context(tmplpath, sls) self.assertDictContainsAll(actual, **expected) @mock.patch("salt.utils.templates.generate_sls_context") @with_tempdir() def test_sls_context_call(self, tempdir, generate_sls_context): """ Check that generate_sls_context is called with proper parameters""" sls = "foo.bar" tmplpath = "/tmp/foo/bar.sls" slsfile = os.path.join(tempdir, "foo") with salt.utils.files.fopen(slsfile, "w") as fp: fp.write("{{ slspath }}") context = {"opts": {}, "saltenv": "base", "sls": sls} render = MockRender() wrapped = salt.utils.templates.wrap_tmpl_func(render) res = wrapped(slsfile, context=context, tmplpath=tmplpath) generate_sls_context.assert_called_with(tmplpath, sls) @mock.patch("salt.utils.templates.generate_sls_context") @with_tempdir() def test_sls_context_no_call(self, tempdir, generate_sls_context): """ Check that generate_sls_context is not called if sls is not set""" sls = "foo.bar" tmplpath = "/tmp/foo/bar.sls" slsfile = os.path.join(tempdir, "foo") with salt.utils.files.fopen(slsfile, "w") as fp: fp.write("{{ slspath }}") context = {"opts": {}, "saltenv": "base"} render = MockRender() wrapped = salt.utils.templates.wrap_tmpl_func(render) res = wrapped(slsfile, context=context, tmplpath=tmplpath) generate_sls_context.assert_not_called() def test_generate_sls_context__top_level(self): """ generate_sls_context - top_level Use case""" self._test_generated_sls_context( "/tmp/boo.sls", "boo", tplfile="boo.sls", tpldir=".", tpldot="", slsdotpath="", slscolonpath="", sls_path="", slspath="", ) def test_generate_sls_context__one_level_init_implicit(self): """ generate_sls_context - Basic one level with implicit init.sls """ self._test_generated_sls_context( "/tmp/foo/init.sls", "foo", tplfile="foo/init.sls", tpldir="foo", tpldot="foo", slsdotpath="foo", slscolonpath="foo", sls_path="foo", slspath="foo", ) def test_generate_sls_context__one_level_init_explicit(self): """ generate_sls_context - Basic one level with explicit init.sls """ self._test_generated_sls_context( "/tmp/foo/init.sls", "foo.init", tplfile="foo/init.sls", tpldir="foo", tpldot="foo", slsdotpath="foo", slscolonpath="foo", sls_path="foo", slspath="foo", ) def test_generate_sls_context__one_level(self): """ generate_sls_context - Basic one level with name""" self._test_generated_sls_context( "/tmp/foo/boo.sls", "foo.boo", tplfile="foo/boo.sls", tpldir="foo", tpldot="foo", slsdotpath="foo", slscolonpath="foo", sls_path="foo", slspath="foo", ) def test_generate_sls_context__one_level_repeating(self): """ generate_sls_context - Basic one level with name same as dir (Issue #56410) """ self._test_generated_sls_context( "/tmp/foo/foo.sls", "foo.foo", tplfile="foo/foo.sls", tpldir="foo", tpldot="foo", slsdotpath="foo", slscolonpath="foo", sls_path="foo", slspath="foo", ) def test_generate_sls_context__two_level_init_implicit(self): """ generate_sls_context - Basic two level with implicit init.sls """ self._test_generated_sls_context( "/tmp/foo/bar/init.sls", "foo.bar", tplfile="foo/bar/init.sls", tpldir="foo/bar", tpldot="foo.bar", slsdotpath="foo.bar", slscolonpath="foo:bar", sls_path="foo_bar", slspath="foo/bar", ) def test_generate_sls_context__two_level_init_explicit(self): """ generate_sls_context - Basic two level with explicit init.sls """ self._test_generated_sls_context( "/tmp/foo/bar/init.sls", "foo.bar.init", tplfile="foo/bar/init.sls", tpldir="foo/bar", tpldot="foo.bar", slsdotpath="foo.bar", slscolonpath="foo:bar", sls_path="foo_bar", slspath="foo/bar", ) def test_generate_sls_context__two_level(self): """ generate_sls_context - Basic two level with name""" self._test_generated_sls_context( "/tmp/foo/bar/boo.sls", "foo.bar.boo", tplfile="foo/bar/boo.sls", tpldir="foo/bar", tpldot="foo.bar", slsdotpath="foo.bar", slscolonpath="foo:bar", sls_path="foo_bar", slspath="foo/bar", ) def test_generate_sls_context__two_level_repeating(self): """ generate_sls_context - Basic two level with name same as dir (Issue #56410) """ self._test_generated_sls_context( "/tmp/foo/foo/foo.sls", "foo.foo.foo", tplfile="foo/foo/foo.sls", tpldir="foo/foo", tpldot="foo.foo", slsdotpath="foo.foo", slscolonpath="foo:foo", sls_path="foo_foo", slspath="foo/foo", ) @mock.patch( "salt.utils.templates._generate_sls_context_legacy", return_value="legacy" ) @mock.patch("salt.utils.templates._generate_sls_context", return_value="new") @mock.patch("salt.utils.templates.features.get", return_value=True) def test_feature_flag_on(self, feature_get, new_impl, legacy_impl): """ Test feature flag selection with FF on""" tplpath = "tplpath" sls = "sls" self.assertEqual("new", salt.utils.templates.generate_sls_context(tplpath, sls)) new_impl.assert_called_with(tplpath, sls) legacy_impl.assert_not_called() @mock.patch( "salt.utils.templates._generate_sls_context_legacy", return_value="legacy" ) @mock.patch("salt.utils.templates._generate_sls_context", return_value="new") @mock.patch("salt.utils.templates.features.get", return_value=False) def test_feature_flag_off(self, feature_get, new_impl, legacy_impl): """ Test feature flag selection with FF on""" tplpath = "tplpath" sls = "sls" self.assertEqual( "legacy", salt.utils.templates.generate_sls_context(tplpath, sls) ) new_impl.assert_not_called() legacy_impl.assert_called_with(tplpath, sls) @skipIf(sys.platform == "win32", "Backslash not possible under windows") def test_generate_sls_context__backslash_in_path(self): """ generate_sls_context - Handle backslash in path on non-windows """ self._test_generated_sls_context( "/tmp/foo/foo\\foo.sls", "foo.foo\\foo", tplfile="foo/foo\\foo.sls", tpldir="foo", tpldot="foo", slsdotpath="foo", slscolonpath="foo", sls_path="foo", slspath="foo", )