test_jinja.py 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750
  1. """
  2. Tests for salt.utils.jinja
  3. """
  4. import ast
  5. import builtins
  6. import copy
  7. import datetime
  8. import os
  9. import pprint
  10. import random
  11. import re
  12. import tempfile
  13. import salt.config
  14. import salt.loader
  15. # dateutils is needed so that the strftime jinja filter is loaded
  16. import salt.utils.dateutils # pylint: disable=unused-import
  17. import salt.utils.files
  18. import salt.utils.json
  19. import salt.utils.stringutils
  20. import salt.utils.yaml
  21. from jinja2 import DictLoader, Environment, Markup, exceptions
  22. from salt.exceptions import SaltRenderError
  23. from salt.utils.decorators.jinja import JinjaFilter
  24. from salt.utils.jinja import (
  25. SaltCacheLoader,
  26. SerializerExtension,
  27. ensure_sequence_filter,
  28. indent,
  29. tojson,
  30. )
  31. from salt.utils.odict import OrderedDict
  32. from salt.utils.templates import JINJA, render_jinja_tmpl
  33. from tests.support.case import ModuleCase
  34. from tests.support.helpers import requires_network
  35. from tests.support.mock import MagicMock, Mock, patch
  36. from tests.support.runtests import RUNTIME_VARS
  37. from tests.support.unit import TestCase, skipIf
  38. try:
  39. import timelib # pylint: disable=W0611
  40. HAS_TIMELIB = True
  41. except ImportError:
  42. HAS_TIMELIB = False
  43. BLINESEP = salt.utils.stringutils.to_bytes(os.linesep)
  44. class JinjaTestCase(TestCase):
  45. def test_tojson(self):
  46. """
  47. Test the ported tojson filter. Non-ascii unicode content should be
  48. dumped with ensure_ascii=True.
  49. """
  50. data = {"Non-ascii words": ["süß", "спам", "яйца"]}
  51. result = tojson(data)
  52. expected = (
  53. '{"Non-ascii words": ["s\\u00fc\\u00df", '
  54. '"\\u0441\\u043f\\u0430\\u043c", '
  55. '"\\u044f\\u0439\\u0446\\u0430"]}'
  56. )
  57. assert result == expected, result
  58. def test_indent(self):
  59. """
  60. Test the indent filter with Markup object as input. Double-quotes
  61. should not be URL-encoded.
  62. """
  63. data = Markup('foo:\n "bar"')
  64. result = indent(data)
  65. expected = Markup('foo:\n "bar"')
  66. assert result == expected, result
  67. class MockFileClient:
  68. """
  69. Does not download files but records any file request for testing
  70. """
  71. def __init__(self, loader=None):
  72. if loader:
  73. loader._file_client = self
  74. self.requests = []
  75. def get_file(self, template, dest="", makedirs=False, saltenv="base"):
  76. self.requests.append(
  77. {"path": template, "dest": dest, "makedirs": makedirs, "saltenv": saltenv}
  78. )
  79. def _setup_test_dir(src_dir, test_dir):
  80. os.makedirs(test_dir)
  81. salt.utils.files.recursive_copy(src_dir, test_dir)
  82. filename = os.path.join(test_dir, "non_ascii")
  83. with salt.utils.files.fopen(filename, "wb") as fp:
  84. fp.write(b"Assun\xc3\xa7\xc3\xa3o" + BLINESEP)
  85. filename = os.path.join(test_dir, "hello_simple")
  86. with salt.utils.files.fopen(filename, "wb") as fp:
  87. fp.write(b"world" + BLINESEP)
  88. filename = os.path.join(test_dir, "hello_import")
  89. lines = [
  90. r"{% from 'macro' import mymacro -%}",
  91. r"{% from 'macro' import mymacro -%}",
  92. r"{{ mymacro('Hey') ~ mymacro(a|default('a'), b|default('b')) }}",
  93. ]
  94. with salt.utils.files.fopen(filename, "wb") as fp:
  95. for line in lines:
  96. fp.write(line.encode("utf-8") + BLINESEP)
  97. class TestSaltCacheLoader(TestCase):
  98. def setUp(self):
  99. self.tempdir = tempfile.mkdtemp()
  100. self.template_dir = os.path.join(self.tempdir, "files", "test")
  101. _setup_test_dir(
  102. os.path.join(RUNTIME_VARS.BASE_FILES, "templates"), self.template_dir
  103. )
  104. self.opts = {
  105. "file_buffer_size": 1048576,
  106. "cachedir": self.tempdir,
  107. "file_roots": {"test": [self.template_dir]},
  108. "pillar_roots": {"test": [self.template_dir]},
  109. "extension_modules": os.path.join(
  110. os.path.dirname(os.path.abspath(__file__)), "extmods"
  111. ),
  112. }
  113. super().setUp()
  114. def tearDown(self):
  115. salt.utils.files.rm_rf(self.tempdir)
  116. self.tempdir = self.template_dir = self.opts
  117. def test_searchpath(self):
  118. """
  119. The searchpath is based on the cachedir option and the saltenv parameter
  120. """
  121. tmp = tempfile.gettempdir()
  122. opts = copy.deepcopy(self.opts)
  123. opts.update({"cachedir": tmp})
  124. loader = self.get_loader(opts=opts, saltenv="test")
  125. assert loader.searchpath == [os.path.join(tmp, "files", "test")]
  126. def test_mockclient(self):
  127. """
  128. A MockFileClient is used that records all file requests normally sent
  129. to the master.
  130. """
  131. loader = self.get_loader(opts=self.opts, saltenv="test")
  132. res = loader.get_source(None, "hello_simple")
  133. assert len(res) == 3
  134. # res[0] on Windows is unicode and use os.linesep so it works cross OS
  135. self.assertEqual(str(res[0]), "world" + os.linesep)
  136. tmpl_dir = os.path.join(self.template_dir, "hello_simple")
  137. self.assertEqual(res[1], tmpl_dir)
  138. assert res[2](), "Template up to date?"
  139. assert loader._file_client.requests
  140. self.assertEqual(loader._file_client.requests[0]["path"], "salt://hello_simple")
  141. def get_loader(self, opts=None, saltenv="base"):
  142. """
  143. Now that we instantiate the client in the __init__, we need to mock it
  144. """
  145. if opts is None:
  146. opts = self.opts
  147. with patch.object(SaltCacheLoader, "file_client", Mock()):
  148. loader = SaltCacheLoader(opts, saltenv)
  149. self.addCleanup(setattr, SaltCacheLoader, "_cached_client", None)
  150. # Create a mock file client and attach it to the loader
  151. MockFileClient(loader)
  152. return loader
  153. def get_test_saltenv(self):
  154. """
  155. Setup a simple jinja test environment
  156. """
  157. loader = self.get_loader(saltenv="test")
  158. jinja = Environment(loader=loader)
  159. return loader._file_client, jinja
  160. def test_import(self):
  161. """
  162. You can import and use macros from other files
  163. """
  164. fc, jinja = self.get_test_saltenv()
  165. result = jinja.get_template("hello_import").render()
  166. self.assertEqual(result, "Hey world !a b !")
  167. assert len(fc.requests) == 2
  168. self.assertEqual(fc.requests[0]["path"], "salt://hello_import")
  169. self.assertEqual(fc.requests[1]["path"], "salt://macro")
  170. def test_relative_import(self):
  171. """
  172. You can import using relative paths
  173. issue-13889
  174. """
  175. fc, jinja = self.get_test_saltenv()
  176. tmpl = jinja.get_template(os.path.join("relative", "rhello"))
  177. result = tmpl.render()
  178. self.assertEqual(result, "Hey world !a b !")
  179. assert len(fc.requests) == 3
  180. self.assertEqual(
  181. fc.requests[0]["path"], os.path.join("salt://relative", "rhello")
  182. )
  183. self.assertEqual(
  184. fc.requests[1]["path"], os.path.join("salt://relative", "rmacro")
  185. )
  186. self.assertEqual(fc.requests[2]["path"], "salt://macro")
  187. # This must fail when rendered: attempts to import from outside file root
  188. template = jinja.get_template("relative/rescape")
  189. self.assertRaises(exceptions.TemplateNotFound, template.render)
  190. def test_include(self):
  191. """
  192. You can also include a template that imports and uses macros
  193. """
  194. fc, jinja = self.get_test_saltenv()
  195. result = jinja.get_template("hello_include").render()
  196. self.assertEqual(result, "Hey world !a b !")
  197. assert len(fc.requests) == 3
  198. self.assertEqual(fc.requests[0]["path"], "salt://hello_include")
  199. self.assertEqual(fc.requests[1]["path"], "salt://hello_import")
  200. self.assertEqual(fc.requests[2]["path"], "salt://macro")
  201. def test_include_context(self):
  202. """
  203. Context variables are passes to the included template by default.
  204. """
  205. _, jinja = self.get_test_saltenv()
  206. result = jinja.get_template("hello_include").render(a="Hi", b="Salt")
  207. self.assertEqual(result, "Hey world !Hi Salt !")
  208. def test_cached_file_client(self):
  209. """
  210. Multiple instantiations of SaltCacheLoader use the cached file client
  211. """
  212. with patch("salt.transport.client.ReqChannel.factory", Mock()):
  213. loader_a = SaltCacheLoader(self.opts)
  214. loader_b = SaltCacheLoader(self.opts)
  215. assert loader_a._file_client is loader_b._file_client
  216. def test_file_client_kwarg(self):
  217. """
  218. A file client can be passed to SaltCacheLoader overriding the any
  219. cached file client
  220. """
  221. mfc = MockFileClient()
  222. loader = SaltCacheLoader(self.opts, _file_client=mfc)
  223. assert loader._file_client is mfc
  224. def test_cache_loader_shutdown(self):
  225. """
  226. The shudown method can be called without raising an exception when the
  227. file_client does not have a destroy method
  228. """
  229. mfc = MockFileClient()
  230. assert not hasattr(mfc, "destroy")
  231. loader = SaltCacheLoader(self.opts, _file_client=mfc)
  232. assert loader._file_client is mfc
  233. # Shutdown method should not raise any exceptions
  234. loader.shutdown()
  235. class TestGetTemplate(TestCase):
  236. def setUp(self):
  237. self.tempdir = tempfile.mkdtemp()
  238. self.template_dir = os.path.join(self.tempdir, "files", "test")
  239. _setup_test_dir(
  240. os.path.join(RUNTIME_VARS.BASE_FILES, "templates"), self.template_dir
  241. )
  242. self.local_opts = {
  243. "file_buffer_size": 1048576,
  244. "cachedir": self.tempdir,
  245. "file_client": "local",
  246. "file_ignore_regex": None,
  247. "file_ignore_glob": None,
  248. "file_roots": {"test": [self.template_dir]},
  249. "pillar_roots": {"test": [self.template_dir]},
  250. "fileserver_backend": ["roots"],
  251. "hash_type": "md5",
  252. "extension_modules": os.path.join(
  253. os.path.dirname(os.path.abspath(__file__)), "extmods"
  254. ),
  255. }
  256. self.local_salt = {}
  257. super().setUp()
  258. def tearDown(self):
  259. salt.utils.files.rm_rf(self.tempdir)
  260. self.tempdir = self.template_dir = self.local_opts = self.local_salt = None
  261. def test_fallback(self):
  262. """
  263. A Template with a filesystem loader is returned as fallback
  264. if the file is not contained in the searchpath
  265. """
  266. fn_ = os.path.join(self.template_dir, "hello_simple")
  267. with salt.utils.files.fopen(fn_) as fp_:
  268. out = render_jinja_tmpl(
  269. salt.utils.stringutils.to_unicode(fp_.read()),
  270. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  271. )
  272. self.assertEqual(out, "world" + os.linesep)
  273. def test_fallback_noloader(self):
  274. """
  275. A Template with a filesystem loader is returned as fallback
  276. if the file is not contained in the searchpath
  277. """
  278. filename = os.path.join(self.template_dir, "hello_import")
  279. with salt.utils.files.fopen(filename) as fp_:
  280. out = render_jinja_tmpl(
  281. salt.utils.stringutils.to_unicode(fp_.read()),
  282. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  283. )
  284. self.assertEqual(out, "Hey world !a b !" + os.linesep)
  285. def test_saltenv(self):
  286. """
  287. If the template is within the searchpath it can
  288. import, include and extend other templates.
  289. The initial template is expected to be already cached
  290. get_template does not request it from the master again.
  291. """
  292. fc = MockFileClient()
  293. with patch.object(SaltCacheLoader, "file_client", MagicMock(return_value=fc)):
  294. filename = os.path.join(self.template_dir, "hello_import")
  295. with salt.utils.files.fopen(filename) as fp_:
  296. out = render_jinja_tmpl(
  297. salt.utils.stringutils.to_unicode(fp_.read()),
  298. dict(
  299. opts={
  300. "cachedir": self.tempdir,
  301. "file_client": "remote",
  302. "file_roots": self.local_opts["file_roots"],
  303. "pillar_roots": self.local_opts["pillar_roots"],
  304. },
  305. a="Hi",
  306. b="Salt",
  307. saltenv="test",
  308. salt=self.local_salt,
  309. ),
  310. )
  311. self.assertEqual(out, "Hey world !Hi Salt !" + os.linesep)
  312. self.assertEqual(fc.requests[0]["path"], "salt://macro")
  313. def test_macro_additional_log_for_generalexc(self):
  314. """
  315. If we failed in a macro because of e.g. a TypeError, get
  316. more output from trace.
  317. """
  318. expected = r"""Jinja error:.*division.*
  319. .*macrogeneral\(2\):
  320. ---
  321. \{% macro mymacro\(\) -%\}
  322. \{\{ 1/0 \}\} <======================
  323. \{%- endmacro %\}
  324. ---.*"""
  325. filename = os.path.join(self.template_dir, "hello_import_generalerror")
  326. fc = MockFileClient()
  327. with patch.object(SaltCacheLoader, "file_client", MagicMock(return_value=fc)):
  328. with salt.utils.files.fopen(filename) as fp_:
  329. self.assertRaisesRegex(
  330. SaltRenderError,
  331. expected,
  332. render_jinja_tmpl,
  333. salt.utils.stringutils.to_unicode(fp_.read()),
  334. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  335. )
  336. def test_macro_additional_log_for_undefined(self):
  337. """
  338. If we failed in a macro because of undefined variables, get
  339. more output from trace.
  340. """
  341. expected = r"""Jinja variable 'b' is undefined
  342. .*macroundefined\(2\):
  343. ---
  344. \{% macro mymacro\(\) -%\}
  345. \{\{b.greetee\}\} <-- error is here <======================
  346. \{%- endmacro %\}
  347. ---"""
  348. filename = os.path.join(self.template_dir, "hello_import_undefined")
  349. fc = MockFileClient()
  350. with patch.object(SaltCacheLoader, "file_client", MagicMock(return_value=fc)):
  351. with salt.utils.files.fopen(filename) as fp_:
  352. self.assertRaisesRegex(
  353. SaltRenderError,
  354. expected,
  355. render_jinja_tmpl,
  356. salt.utils.stringutils.to_unicode(fp_.read()),
  357. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  358. )
  359. def test_macro_additional_log_syntaxerror(self):
  360. """
  361. If we failed in a macro, get more output from trace.
  362. """
  363. expected = r"""Jinja syntax error: expected token .*end.*got '-'.*
  364. .*macroerror\(2\):
  365. ---
  366. # macro
  367. \{% macro mymacro\(greeting, greetee='world'\) -\} <-- error is here <======================
  368. \{\{ greeting ~ ' ' ~ greetee \}\} !
  369. \{%- endmacro %\}
  370. ---.*"""
  371. filename = os.path.join(self.template_dir, "hello_import_error")
  372. fc = MockFileClient()
  373. with patch.object(SaltCacheLoader, "file_client", MagicMock(return_value=fc)):
  374. with salt.utils.files.fopen(filename) as fp_:
  375. self.assertRaisesRegex(
  376. SaltRenderError,
  377. expected,
  378. render_jinja_tmpl,
  379. salt.utils.stringutils.to_unicode(fp_.read()),
  380. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  381. )
  382. def test_non_ascii_encoding(self):
  383. fc = MockFileClient()
  384. with patch.object(SaltCacheLoader, "file_client", MagicMock(return_value=fc)):
  385. filename = os.path.join(self.template_dir, "hello_import")
  386. with salt.utils.files.fopen(filename) as fp_:
  387. out = render_jinja_tmpl(
  388. salt.utils.stringutils.to_unicode(fp_.read()),
  389. dict(
  390. opts={
  391. "cachedir": self.tempdir,
  392. "file_client": "remote",
  393. "file_roots": self.local_opts["file_roots"],
  394. "pillar_roots": self.local_opts["pillar_roots"],
  395. },
  396. a="Hi",
  397. b="Sàlt",
  398. saltenv="test",
  399. salt=self.local_salt,
  400. ),
  401. )
  402. self.assertEqual(
  403. out,
  404. salt.utils.stringutils.to_unicode("Hey world !Hi Sàlt !" + os.linesep),
  405. )
  406. self.assertEqual(fc.requests[0]["path"], "salt://macro")
  407. filename = os.path.join(self.template_dir, "non_ascii")
  408. with salt.utils.files.fopen(filename, "rb") as fp_:
  409. out = render_jinja_tmpl(
  410. salt.utils.stringutils.to_unicode(fp_.read(), "utf-8"),
  411. dict(
  412. opts={
  413. "cachedir": self.tempdir,
  414. "file_client": "remote",
  415. "file_roots": self.local_opts["file_roots"],
  416. "pillar_roots": self.local_opts["pillar_roots"],
  417. },
  418. a="Hi",
  419. b="Sàlt",
  420. saltenv="test",
  421. salt=self.local_salt,
  422. ),
  423. )
  424. self.assertEqual("Assunção" + os.linesep, out)
  425. self.assertEqual(fc.requests[0]["path"], "salt://macro")
  426. @skipIf(HAS_TIMELIB is False, "The `timelib` library is not installed.")
  427. def test_strftime(self):
  428. response = render_jinja_tmpl(
  429. '{{ "2002/12/25"|strftime }}',
  430. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  431. )
  432. self.assertEqual(response, "2002-12-25")
  433. objects = (
  434. datetime.datetime(2002, 12, 25, 12, 00, 00, 00),
  435. "2002/12/25",
  436. 1040814000,
  437. "1040814000",
  438. )
  439. for object in objects:
  440. response = render_jinja_tmpl(
  441. "{{ object|strftime }}",
  442. dict(
  443. object=object,
  444. opts=self.local_opts,
  445. saltenv="test",
  446. salt=self.local_salt,
  447. ),
  448. )
  449. self.assertEqual(response, "2002-12-25")
  450. response = render_jinja_tmpl(
  451. '{{ object|strftime("%b %d, %Y") }}',
  452. dict(
  453. object=object,
  454. opts=self.local_opts,
  455. saltenv="test",
  456. salt=self.local_salt,
  457. ),
  458. )
  459. self.assertEqual(response, "Dec 25, 2002")
  460. response = render_jinja_tmpl(
  461. '{{ object|strftime("%y") }}',
  462. dict(
  463. object=object,
  464. opts=self.local_opts,
  465. saltenv="test",
  466. salt=self.local_salt,
  467. ),
  468. )
  469. self.assertEqual(response, "02")
  470. def test_non_ascii(self):
  471. fn = os.path.join(self.template_dir, "non_ascii")
  472. out = JINJA(fn, opts=self.local_opts, saltenv="test", salt=self.local_salt)
  473. with salt.utils.files.fopen(out["data"], "rb") as fp:
  474. result = salt.utils.stringutils.to_unicode(fp.read(), "utf-8")
  475. self.assertEqual(
  476. salt.utils.stringutils.to_unicode("Assunção" + os.linesep), result
  477. )
  478. def test_get_context_has_enough_context(self):
  479. template = "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf"
  480. context = salt.utils.stringutils.get_context(template, 8)
  481. expected = "---\n[...]\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\n[...]\n---"
  482. self.assertEqual(expected, context)
  483. def test_get_context_at_top_of_file(self):
  484. template = "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf"
  485. context = salt.utils.stringutils.get_context(template, 1)
  486. expected = "---\n1\n2\n3\n4\n5\n6\n[...]\n---"
  487. self.assertEqual(expected, context)
  488. def test_get_context_at_bottom_of_file(self):
  489. template = "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf"
  490. context = salt.utils.stringutils.get_context(template, 15)
  491. expected = "---\n[...]\na\nb\nc\nd\ne\nf\n---"
  492. self.assertEqual(expected, context)
  493. def test_get_context_2_context_lines(self):
  494. template = "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf"
  495. context = salt.utils.stringutils.get_context(template, 8, num_lines=2)
  496. expected = "---\n[...]\n6\n7\n8\n9\na\n[...]\n---"
  497. self.assertEqual(expected, context)
  498. def test_get_context_with_marker(self):
  499. template = "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf"
  500. context = salt.utils.stringutils.get_context(
  501. template, 8, num_lines=2, marker=" <---"
  502. )
  503. expected = "---\n[...]\n6\n7\n8 <---\n9\na\n[...]\n---"
  504. self.assertEqual(expected, context)
  505. def test_render_with_syntax_error(self):
  506. template = "hello\n\n{{ bad\n\nfoo"
  507. expected = r".*---\nhello\n\n{{ bad\n\nfoo <======================\n---"
  508. self.assertRaisesRegex(
  509. SaltRenderError,
  510. expected,
  511. render_jinja_tmpl,
  512. template,
  513. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  514. )
  515. def test_render_with_utf8_syntax_error(self):
  516. with patch.object(builtins, "__salt_system_encoding__", "utf-8"):
  517. template = "hello\n\n{{ bad\n\nfoo한"
  518. expected = salt.utils.stringutils.to_str(
  519. r".*---\nhello\n\n{{ bad\n\nfoo한 <======================\n---"
  520. )
  521. self.assertRaisesRegex(
  522. SaltRenderError,
  523. expected,
  524. render_jinja_tmpl,
  525. template,
  526. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  527. )
  528. def test_render_with_undefined_variable(self):
  529. template = "hello\n\n{{ foo }}\n\nfoo"
  530. expected = r"Jinja variable \'foo\' is undefined"
  531. self.assertRaisesRegex(
  532. SaltRenderError,
  533. expected,
  534. render_jinja_tmpl,
  535. template,
  536. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  537. )
  538. def test_render_with_undefined_variable_utf8(self):
  539. template = "hello\xed\x95\x9c\n\n{{ foo }}\n\nfoo"
  540. expected = r"Jinja variable \'foo\' is undefined"
  541. self.assertRaisesRegex(
  542. SaltRenderError,
  543. expected,
  544. render_jinja_tmpl,
  545. template,
  546. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  547. )
  548. def test_render_with_undefined_variable_unicode(self):
  549. template = "hello한\n\n{{ foo }}\n\nfoo"
  550. expected = r"Jinja variable \'foo\' is undefined"
  551. self.assertRaisesRegex(
  552. SaltRenderError,
  553. expected,
  554. render_jinja_tmpl,
  555. template,
  556. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  557. )
  558. class TestJinjaDefaultOptions(TestCase):
  559. @classmethod
  560. def setUpClass(cls):
  561. cls.local_opts = {
  562. "cachedir": os.path.join(RUNTIME_VARS.TMP, "jinja-template-cache"),
  563. "file_buffer_size": 1048576,
  564. "file_client": "local",
  565. "file_ignore_regex": None,
  566. "file_ignore_glob": None,
  567. "file_roots": {
  568. "test": [os.path.join(RUNTIME_VARS.BASE_FILES, "templates")]
  569. },
  570. "pillar_roots": {
  571. "test": [os.path.join(RUNTIME_VARS.BASE_FILES, "templates")]
  572. },
  573. "fileserver_backend": ["roots"],
  574. "hash_type": "md5",
  575. "extension_modules": os.path.join(
  576. os.path.dirname(os.path.abspath(__file__)), "extmods"
  577. ),
  578. "jinja_env": {"line_comment_prefix": "##", "line_statement_prefix": "%"},
  579. }
  580. cls.local_salt = {
  581. "myvar": "zero",
  582. "mylist": [0, 1, 2, 3],
  583. }
  584. @classmethod
  585. def tearDownClass(cls):
  586. cls.local_opts = cls.local_salt = None
  587. def test_comment_prefix(self):
  588. template = """
  589. %- set myvar = 'one'
  590. ## ignored comment 1
  591. {{- myvar -}}
  592. {%- set myvar = 'two' %} ## ignored comment 2
  593. {{- myvar }} ## ignored comment 3
  594. %- if myvar == 'two':
  595. %- set myvar = 'three'
  596. %- endif
  597. {{- myvar -}}
  598. """
  599. rendered = render_jinja_tmpl(
  600. template, dict(opts=self.local_opts, saltenv="test", salt=self.local_salt)
  601. )
  602. self.assertEqual(rendered, "onetwothree")
  603. def test_statement_prefix(self):
  604. template = """
  605. {%- set mylist = ['1', '2', '3'] %}
  606. %- set mylist = ['one', 'two', 'three']
  607. %- for item in mylist:
  608. {{- item }}
  609. %- endfor
  610. """
  611. rendered = render_jinja_tmpl(
  612. template, dict(opts=self.local_opts, saltenv="test", salt=self.local_salt)
  613. )
  614. self.assertEqual(rendered, "onetwothree")
  615. class TestCustomExtensions(TestCase):
  616. @classmethod
  617. def setUpClass(cls):
  618. cls.local_opts = {
  619. "cachedir": os.path.join(RUNTIME_VARS.TMP, "jinja-template-cache"),
  620. "file_buffer_size": 1048576,
  621. "file_client": "local",
  622. "file_ignore_regex": None,
  623. "file_ignore_glob": None,
  624. "file_roots": {
  625. "test": [os.path.join(RUNTIME_VARS.BASE_FILES, "templates")]
  626. },
  627. "pillar_roots": {
  628. "test": [os.path.join(RUNTIME_VARS.BASE_FILES, "templates")]
  629. },
  630. "fileserver_backend": ["roots"],
  631. "hash_type": "md5",
  632. "extension_modules": os.path.join(
  633. os.path.dirname(os.path.abspath(__file__)), "extmods"
  634. ),
  635. }
  636. cls.local_salt = {
  637. # 'dns.A': dnsutil.A,
  638. # 'dns.AAAA': dnsutil.AAAA,
  639. # 'file.exists': filemod.file_exists,
  640. # 'file.basename': filemod.basename,
  641. # 'file.dirname': filemod.dirname
  642. }
  643. @classmethod
  644. def tearDownClass(cls):
  645. cls.local_opts = cls.local_salt = None
  646. def test_regex_escape(self):
  647. dataset = "foo?:.*/\\bar"
  648. env = Environment(extensions=[SerializerExtension])
  649. env.filters.update(JinjaFilter.salt_jinja_filters)
  650. rendered = env.from_string("{{ dataset|regex_escape }}").render(dataset=dataset)
  651. self.assertEqual(rendered, re.escape(dataset))
  652. def test_unique_string(self):
  653. dataset = "foo"
  654. unique = set(dataset)
  655. env = Environment(extensions=[SerializerExtension])
  656. env.filters.update(JinjaFilter.salt_jinja_filters)
  657. rendered = (
  658. env.from_string("{{ dataset|unique }}")
  659. .render(dataset=dataset)
  660. .strip("'{}")
  661. .split("', '")
  662. )
  663. self.assertEqual(sorted(rendered), sorted(list(unique)))
  664. def test_unique_tuple(self):
  665. dataset = ("foo", "foo", "bar")
  666. unique = set(dataset)
  667. env = Environment(extensions=[SerializerExtension])
  668. env.filters.update(JinjaFilter.salt_jinja_filters)
  669. rendered = (
  670. env.from_string("{{ dataset|unique }}")
  671. .render(dataset=dataset)
  672. .strip("'{}")
  673. .split("', '")
  674. )
  675. self.assertEqual(sorted(rendered), sorted(list(unique)))
  676. def test_unique_list(self):
  677. dataset = ["foo", "foo", "bar"]
  678. unique = ["foo", "bar"]
  679. env = Environment(extensions=[SerializerExtension])
  680. env.filters.update(JinjaFilter.salt_jinja_filters)
  681. rendered = (
  682. env.from_string("{{ dataset|unique }}")
  683. .render(dataset=dataset)
  684. .strip("'[]")
  685. .split("', '")
  686. )
  687. self.assertEqual(rendered, unique)
  688. def test_serialize_json(self):
  689. dataset = {"foo": True, "bar": 42, "baz": [1, 2, 3], "qux": 2.0}
  690. env = Environment(extensions=[SerializerExtension])
  691. rendered = env.from_string("{{ dataset|json }}").render(dataset=dataset)
  692. self.assertEqual(dataset, salt.utils.json.loads(rendered))
  693. def test_serialize_yaml(self):
  694. dataset = {
  695. "foo": True,
  696. "bar": 42,
  697. "baz": [1, 2, 3],
  698. "qux": 2.0,
  699. "spam": OrderedDict([("foo", OrderedDict([("bar", "baz"), ("qux", 42)]))]),
  700. }
  701. env = Environment(extensions=[SerializerExtension])
  702. rendered = env.from_string("{{ dataset|yaml }}").render(dataset=dataset)
  703. self.assertEqual(dataset, salt.utils.yaml.safe_load(rendered))
  704. def test_serialize_yaml_str(self):
  705. dataset = "str value"
  706. env = Environment(extensions=[SerializerExtension])
  707. rendered = env.from_string("{{ dataset|yaml }}").render(dataset=dataset)
  708. self.assertEqual(dataset, rendered)
  709. def test_serialize_yaml_unicode(self):
  710. dataset = "str value"
  711. env = Environment(extensions=[SerializerExtension])
  712. rendered = env.from_string("{{ dataset|yaml }}").render(dataset=dataset)
  713. self.assertEqual("str value", rendered)
  714. def test_serialize_python(self):
  715. dataset = {"foo": True, "bar": 42, "baz": [1, 2, 3], "qux": 2.0}
  716. env = Environment(extensions=[SerializerExtension])
  717. rendered = env.from_string("{{ dataset|python }}").render(dataset=dataset)
  718. self.assertEqual(rendered, pprint.pformat(dataset))
  719. def test_load_yaml(self):
  720. env = Environment(extensions=[SerializerExtension])
  721. rendered = env.from_string(
  722. '{% set document = "{foo: it works}"|load_yaml %}{{ document.foo }}'
  723. ).render()
  724. self.assertEqual(rendered, "it works")
  725. rendered = env.from_string(
  726. "{% set document = document|load_yaml %}" "{{ document.foo }}"
  727. ).render(document="{foo: it works}")
  728. self.assertEqual(rendered, "it works")
  729. with self.assertRaises((TypeError, exceptions.TemplateRuntimeError)):
  730. env.from_string(
  731. "{% set document = document|load_yaml %}" "{{ document.foo }}"
  732. ).render(document={"foo": "it works"})
  733. def test_load_tag(self):
  734. env = Environment(extensions=[SerializerExtension])
  735. source = (
  736. "{{ bar }}, "
  737. + "{% load_yaml as docu %}{foo: it works, {{ bar }}: baz}{% endload %}"
  738. + "{{ docu.foo }}"
  739. )
  740. rendered = env.from_string(source).render(bar="barred")
  741. self.assertEqual(rendered, "barred, it works")
  742. source = (
  743. '{{ bar }}, {% load_json as docu %}{"foo": "it works", "{{ bar }}": "baz"}{% endload %}'
  744. + "{{ docu.foo }}"
  745. )
  746. rendered = env.from_string(source).render(bar="barred")
  747. self.assertEqual(rendered, "barred, it works")
  748. with self.assertRaises(exceptions.TemplateSyntaxError):
  749. env.from_string(
  750. "{% load_yamle as document %}{foo, bar: it works}{% endload %}"
  751. ).render()
  752. with self.assertRaises(exceptions.TemplateRuntimeError):
  753. env.from_string(
  754. "{% load_json as document %}{foo, bar: it works}{% endload %}"
  755. ).render()
  756. def test_load_json(self):
  757. env = Environment(extensions=[SerializerExtension])
  758. rendered = env.from_string(
  759. '{% set document = \'{"foo": "it works"}\'|load_json %}'
  760. "{{ document.foo }}"
  761. ).render()
  762. self.assertEqual(rendered, "it works")
  763. rendered = env.from_string(
  764. "{% set document = document|load_json %}" "{{ document.foo }}"
  765. ).render(document='{"foo": "it works"}')
  766. self.assertEqual(rendered, "it works")
  767. # bad quotes
  768. with self.assertRaises(exceptions.TemplateRuntimeError):
  769. env.from_string("{{ document|load_json }}").render(
  770. document="{'foo': 'it works'}"
  771. )
  772. # not a string
  773. with self.assertRaises(exceptions.TemplateRuntimeError):
  774. env.from_string("{{ document|load_json }}").render(
  775. document={"foo": "it works"}
  776. )
  777. def test_load_yaml_template(self):
  778. loader = DictLoader({"foo": '{bar: "my god is blue", foo: [1, 2, 3]}'})
  779. env = Environment(extensions=[SerializerExtension], loader=loader)
  780. rendered = env.from_string(
  781. '{% import_yaml "foo" as doc %}{{ doc.bar }}'
  782. ).render()
  783. self.assertEqual(rendered, "my god is blue")
  784. with self.assertRaises(exceptions.TemplateNotFound):
  785. env.from_string('{% import_yaml "does not exists" as doc %}').render()
  786. def test_load_json_template(self):
  787. loader = DictLoader({"foo": '{"bar": "my god is blue", "foo": [1, 2, 3]}'})
  788. env = Environment(extensions=[SerializerExtension], loader=loader)
  789. rendered = env.from_string(
  790. '{% import_json "foo" as doc %}{{ doc.bar }}'
  791. ).render()
  792. self.assertEqual(rendered, "my god is blue")
  793. with self.assertRaises(exceptions.TemplateNotFound):
  794. env.from_string('{% import_json "does not exists" as doc %}').render()
  795. def test_load_text_template(self):
  796. loader = DictLoader({"foo": "Foo!"})
  797. env = Environment(extensions=[SerializerExtension], loader=loader)
  798. rendered = env.from_string('{% import_text "foo" as doc %}{{ doc }}').render()
  799. self.assertEqual(rendered, "Foo!")
  800. with self.assertRaises(exceptions.TemplateNotFound):
  801. env.from_string('{% import_text "does not exists" as doc %}').render()
  802. def test_catalog(self):
  803. loader = DictLoader(
  804. {
  805. "doc1": '{bar: "my god is blue"}',
  806. "doc2": '{% import_yaml "doc1" as local2 %} never exported',
  807. "doc3": '{% load_yaml as local3 %}{"foo": "it works"}{% endload %} me neither',
  808. "main1": '{% from "doc2" import local2 %}{{ local2.bar }}',
  809. "main2": '{% from "doc3" import local3 %}{{ local3.foo }}',
  810. "main3": """
  811. {% import "doc2" as imported2 %}
  812. {% import "doc3" as imported3 %}
  813. {{ imported2.local2.bar }}
  814. """,
  815. "main4": """
  816. {% import "doc2" as imported2 %}
  817. {% import "doc3" as imported3 %}
  818. {{ imported3.local3.foo }}
  819. """,
  820. "main5": """
  821. {% from "doc2" import local2 as imported2 %}
  822. {% from "doc3" import local3 as imported3 %}
  823. {{ imported2.bar }}
  824. """,
  825. "main6": """
  826. {% from "doc2" import local2 as imported2 %}
  827. {% from "doc3" import local3 as imported3 %}
  828. {{ imported3.foo }}
  829. """,
  830. }
  831. )
  832. env = Environment(extensions=[SerializerExtension], loader=loader)
  833. rendered = env.get_template("main1").render()
  834. self.assertEqual(rendered, "my god is blue")
  835. rendered = env.get_template("main2").render()
  836. self.assertEqual(rendered, "it works")
  837. rendered = env.get_template("main3").render().strip()
  838. self.assertEqual(rendered, "my god is blue")
  839. rendered = env.get_template("main4").render().strip()
  840. self.assertEqual(rendered, "it works")
  841. rendered = env.get_template("main5").render().strip()
  842. self.assertEqual(rendered, "my god is blue")
  843. rendered = env.get_template("main6").render().strip()
  844. self.assertEqual(rendered, "it works")
  845. def test_nested_structures(self):
  846. env = Environment(extensions=[SerializerExtension])
  847. rendered = env.from_string("{{ data }}").render(data="foo")
  848. self.assertEqual(rendered, "foo")
  849. data = OrderedDict([("foo", OrderedDict([("bar", "baz"), ("qux", 42)]))])
  850. rendered = env.from_string("{{ data }}").render(data=data)
  851. self.assertEqual(
  852. rendered, "{'foo': {'bar': 'baz', 'qux': 42}}",
  853. )
  854. rendered = env.from_string("{{ data }}").render(
  855. data=[OrderedDict(foo="bar",), OrderedDict(baz=42,)]
  856. )
  857. self.assertEqual(
  858. rendered, "[{'foo': 'bar'}, {'baz': 42}]",
  859. )
  860. def test_set_dict_key_value(self):
  861. """
  862. Test the `set_dict_key_value` Jinja filter.
  863. """
  864. rendered = render_jinja_tmpl(
  865. "{{ {} | set_dict_key_value('foo:bar:baz', 42) }}",
  866. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  867. )
  868. self.assertEqual(rendered, "{'foo': {'bar': {'baz': 42}}}")
  869. rendered = render_jinja_tmpl(
  870. "{{ {} | set_dict_key_value('foo.bar.baz', 42, delimiter='.') }}",
  871. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  872. )
  873. self.assertEqual(rendered, "{'foo': {'bar': {'baz': 42}}}")
  874. def test_update_dict_key_value(self):
  875. """
  876. Test the `update_dict_key_value` Jinja filter.
  877. """
  878. # Use OrderedDicts to avoid random key-order-switches in the rendered string.
  879. expected = OrderedDict(
  880. [("bar", OrderedDict([("baz", OrderedDict([("qux", 1), ("quux", 3)]))]))]
  881. )
  882. dataset = OrderedDict(
  883. [("bar", OrderedDict([("baz", OrderedDict([("qux", 1)]))]))]
  884. )
  885. dataset_exp = OrderedDict([("quux", 3)])
  886. rendered = render_jinja_tmpl(
  887. "{{ foo | update_dict_key_value('bar:baz', exp) }}",
  888. dict(
  889. foo=dataset,
  890. exp=dataset_exp,
  891. opts=self.local_opts,
  892. saltenv="test",
  893. salt=self.local_salt,
  894. ),
  895. )
  896. self.assertEqual(
  897. rendered, "{'bar': {'baz': {'qux': 1, 'quux': 3}}}",
  898. )
  899. # Test incorrect usage
  900. for update_with in [42, "foo", [42]]:
  901. template = "{{ {} | update_dict_key_value('bar:baz', update_with) }}"
  902. expected = r"Cannot update {} with a {}.".format(
  903. type({}), type(update_with)
  904. )
  905. self.assertRaisesRegex(
  906. SaltRenderError,
  907. expected,
  908. render_jinja_tmpl,
  909. template,
  910. dict(
  911. update_with=update_with,
  912. opts=self.local_opts,
  913. saltenv="test",
  914. salt=self.local_salt,
  915. ),
  916. )
  917. def test_append_dict_key_value(self):
  918. """
  919. Test the `append_dict_key_value` Jinja filter.
  920. """
  921. rendered = render_jinja_tmpl(
  922. "{{ {} | append_dict_key_value('foo:bar:baz', 42) }}",
  923. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  924. )
  925. self.assertEqual(rendered, "{'foo': {'bar': {'baz': [42]}}}")
  926. rendered = render_jinja_tmpl(
  927. "{{ foo | append_dict_key_value('bar:baz', 42) }}",
  928. dict(
  929. foo={"bar": {"baz": [1, 2]}},
  930. opts=self.local_opts,
  931. saltenv="test",
  932. salt=self.local_salt,
  933. ),
  934. )
  935. self.assertEqual(
  936. rendered, "{'bar': {'baz': [1, 2, 42]}}",
  937. )
  938. def test_extend_dict_key_value(self):
  939. """
  940. Test the `extend_dict_key_value` Jinja filter.
  941. """
  942. rendered = render_jinja_tmpl(
  943. "{{ {} | extend_dict_key_value('foo:bar:baz', [42]) }}",
  944. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  945. )
  946. self.assertEqual(rendered, "{'foo': {'bar': {'baz': [42]}}}")
  947. rendered = render_jinja_tmpl(
  948. "{{ foo | extend_dict_key_value('bar:baz', [42, 43]) }}",
  949. dict(
  950. foo={"bar": {"baz": [1, 2]}},
  951. opts=self.local_opts,
  952. saltenv="test",
  953. salt=self.local_salt,
  954. ),
  955. )
  956. self.assertEqual(
  957. rendered, "{'bar': {'baz': [1, 2, 42, 43]}}",
  958. )
  959. # Edge cases
  960. rendered = render_jinja_tmpl(
  961. "{{ {} | extend_dict_key_value('foo:bar:baz', 'quux') }}",
  962. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  963. )
  964. self.assertEqual(rendered, "{'foo': {'bar': {'baz': ['q', 'u', 'u', 'x']}}}")
  965. # Beware! When supplying a dict, the list gets extended with the dict coerced to a list,
  966. # which will only contain the keys of the dict.
  967. rendered = render_jinja_tmpl(
  968. "{{ {} | extend_dict_key_value('foo:bar:baz', {'foo': 'bar'}) }}",
  969. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  970. )
  971. self.assertEqual(rendered, "{'foo': {'bar': {'baz': ['foo']}}}")
  972. # Test incorrect usage
  973. template = "{{ {} | extend_dict_key_value('bar:baz', 42) }}"
  974. expected = r"Cannot extend {} with a {}.".format(type([]), type(42))
  975. self.assertRaisesRegex(
  976. SaltRenderError,
  977. expected,
  978. render_jinja_tmpl,
  979. template,
  980. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  981. )
  982. def test_sequence(self):
  983. env = Environment()
  984. env.filters["sequence"] = ensure_sequence_filter
  985. rendered = env.from_string("{{ data | sequence | length }}").render(data="foo")
  986. self.assertEqual(rendered, "1")
  987. rendered = env.from_string("{{ data | sequence | length }}").render(
  988. data=["foo", "bar"]
  989. )
  990. self.assertEqual(rendered, "2")
  991. rendered = env.from_string("{{ data | sequence | length }}").render(
  992. data=("foo", "bar")
  993. )
  994. self.assertEqual(rendered, "2")
  995. rendered = env.from_string("{{ data | sequence | length }}").render(
  996. data={"foo", "bar"}
  997. )
  998. self.assertEqual(rendered, "2")
  999. rendered = env.from_string("{{ data | sequence | length }}").render(
  1000. data={"foo": "bar"}
  1001. )
  1002. self.assertEqual(rendered, "1")
  1003. def test_camel_to_snake_case(self):
  1004. """
  1005. Test the `to_snake_case` Jinja filter.
  1006. """
  1007. rendered = render_jinja_tmpl(
  1008. "{{ 'abcdEfghhIjkLmnoP' | to_snake_case }}",
  1009. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1010. )
  1011. self.assertEqual(rendered, "abcd_efghh_ijk_lmno_p")
  1012. def test_snake_to_camel_case(self):
  1013. """
  1014. Test the `to_camelcase` Jinja filter.
  1015. """
  1016. rendered = render_jinja_tmpl(
  1017. "{{ 'the_fox_jumped_over_the_lazy_dog' | to_camelcase }}",
  1018. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1019. )
  1020. self.assertEqual(rendered, "theFoxJumpedOverTheLazyDog")
  1021. rendered = render_jinja_tmpl(
  1022. "{{ 'the_fox_jumped_over_the_lazy_dog' | to_camelcase(uppercamel=True) }}",
  1023. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1024. )
  1025. self.assertEqual(rendered, "TheFoxJumpedOverTheLazyDog")
  1026. def test_is_ip(self):
  1027. """
  1028. Test the `is_ip` Jinja filter.
  1029. """
  1030. rendered = render_jinja_tmpl(
  1031. "{{ '192.168.0.1' | is_ip }}",
  1032. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1033. )
  1034. self.assertEqual(rendered, "True")
  1035. rendered = render_jinja_tmpl(
  1036. "{{ 'FE80::' | is_ip }}",
  1037. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1038. )
  1039. self.assertEqual(rendered, "True")
  1040. rendered = render_jinja_tmpl(
  1041. "{{ 'random' | is_ip }}",
  1042. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1043. )
  1044. self.assertEqual(rendered, "False")
  1045. def test_is_ipv4(self):
  1046. """
  1047. Test the `is_ipv4` Jinja filter.
  1048. """
  1049. rendered = render_jinja_tmpl(
  1050. "{{ '192.168.0.1' | is_ipv4 }}",
  1051. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1052. )
  1053. self.assertEqual(rendered, "True")
  1054. rendered = render_jinja_tmpl(
  1055. "{{ 'FE80::' | is_ipv4 }}",
  1056. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1057. )
  1058. self.assertEqual(rendered, "False")
  1059. rendered = render_jinja_tmpl(
  1060. "{{ 'random' | is_ipv4 }}",
  1061. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1062. )
  1063. self.assertEqual(rendered, "False")
  1064. def test_is_ipv6(self):
  1065. """
  1066. Test the `is_ipv6` Jinja filter.
  1067. """
  1068. rendered = render_jinja_tmpl(
  1069. "{{ '192.168.0.1' | is_ipv6 }}",
  1070. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1071. )
  1072. self.assertEqual(rendered, "False")
  1073. rendered = render_jinja_tmpl(
  1074. "{{ 'fe80::20d:b9ff:fe01:ea8%eth0' | is_ipv6 }}",
  1075. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1076. )
  1077. self.assertEqual(rendered, "True")
  1078. rendered = render_jinja_tmpl(
  1079. "{{ 'FE80::' | is_ipv6 }}",
  1080. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1081. )
  1082. self.assertEqual(rendered, "True")
  1083. rendered = render_jinja_tmpl(
  1084. "{{ 'random' | is_ipv6 }}",
  1085. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1086. )
  1087. self.assertEqual(rendered, "False")
  1088. def test_ipaddr(self):
  1089. """
  1090. Test the `ipaddr` Jinja filter.
  1091. """
  1092. rendered = render_jinja_tmpl(
  1093. "{{ '::' | ipaddr }}",
  1094. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1095. )
  1096. self.assertEqual(rendered, "::")
  1097. rendered = render_jinja_tmpl(
  1098. "{{ '192.168.0.1' | ipaddr }}",
  1099. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1100. )
  1101. self.assertEqual(rendered, "192.168.0.1")
  1102. # provides a list with valid IP addresses only
  1103. rendered = render_jinja_tmpl(
  1104. "{{ ['192.168.0.1', '172.17.17.1', 'foo', 'bar', '::'] | ipaddr | join(', ') }}",
  1105. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1106. )
  1107. self.assertEqual(rendered, "192.168.0.1, 172.17.17.1, ::")
  1108. # return only multicast addresses
  1109. rendered = render_jinja_tmpl(
  1110. "{{ ['224.0.0.1', 'FF01::1', '::'] | ipaddr(options='multicast') | join(', ') }}",
  1111. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1112. )
  1113. self.assertEqual(rendered, "224.0.0.1, ff01::1")
  1114. def test_ipv4(self):
  1115. """
  1116. Test the `ipv4` Jinja filter.
  1117. """
  1118. rendered = render_jinja_tmpl(
  1119. "{{ '192.168.0.1' | ipv4 }}",
  1120. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1121. )
  1122. self.assertEqual(rendered, "192.168.0.1")
  1123. rendered = render_jinja_tmpl(
  1124. "{{ ['192.168.0.1', '172.17.17.1'] | ipv4 | join(', ')}}",
  1125. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1126. )
  1127. self.assertEqual(rendered, "192.168.0.1, 172.17.17.1")
  1128. rendered = render_jinja_tmpl(
  1129. "{{ 'fe80::' | ipv4 }}",
  1130. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1131. )
  1132. self.assertEqual(rendered, "None")
  1133. rendered = render_jinja_tmpl(
  1134. "{{ 'random' | ipv4 }}",
  1135. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1136. )
  1137. self.assertEqual(rendered, "None")
  1138. rendered = render_jinja_tmpl(
  1139. "{{ '192.168.0.1' | ipv4(options='lo') }}",
  1140. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1141. )
  1142. self.assertEqual(rendered, "None")
  1143. rendered = render_jinja_tmpl(
  1144. "{{ '127.0.0.1' | ipv4(options='lo') }}",
  1145. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1146. )
  1147. self.assertEqual(rendered, "127.0.0.1")
  1148. def test_ipv6(self):
  1149. """
  1150. Test the `ipv6` Jinja filter.
  1151. """
  1152. rendered = render_jinja_tmpl(
  1153. "{{ '192.168.0.1' | ipv6 }}",
  1154. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1155. )
  1156. self.assertEqual(rendered, "None")
  1157. rendered = render_jinja_tmpl(
  1158. "{{ 'random' | ipv6 }}",
  1159. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1160. )
  1161. self.assertEqual(rendered, "None")
  1162. # returns the standard format value
  1163. rendered = render_jinja_tmpl(
  1164. "{{ 'FE80:0:0::0' | ipv6 }}",
  1165. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1166. )
  1167. self.assertEqual(rendered, "fe80::")
  1168. # fe80:: is link local therefore will be returned
  1169. rendered = render_jinja_tmpl(
  1170. "{{ 'fe80::' | ipv6(options='ll') }}",
  1171. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1172. )
  1173. self.assertEqual(rendered, "fe80::")
  1174. # fe80:: is not loopback
  1175. rendered = render_jinja_tmpl(
  1176. "{{ 'fe80::' | ipv6(options='lo') }}",
  1177. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1178. )
  1179. self.assertEqual(rendered, "None")
  1180. # returns only IPv6 addresses in the list
  1181. rendered = render_jinja_tmpl(
  1182. "{{ ['fe80::', '192.168.0.1'] | ipv6 | join(', ') }}",
  1183. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1184. )
  1185. self.assertEqual(rendered, "fe80::")
  1186. rendered = render_jinja_tmpl(
  1187. "{{ ['fe80::', '::'] | ipv6 | join(', ') }}",
  1188. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1189. )
  1190. self.assertEqual(rendered, "fe80::, ::")
  1191. def test_network_hosts(self):
  1192. """
  1193. Test the `network_hosts` Jinja filter.
  1194. """
  1195. rendered = render_jinja_tmpl(
  1196. "{{ '192.168.0.1/30' | network_hosts | join(', ') }}",
  1197. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1198. )
  1199. self.assertEqual(rendered, "192.168.0.1, 192.168.0.2")
  1200. def test_network_size(self):
  1201. """
  1202. Test the `network_size` Jinja filter.
  1203. """
  1204. rendered = render_jinja_tmpl(
  1205. "{{ '192.168.0.1' | network_size }}",
  1206. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1207. )
  1208. self.assertEqual(rendered, "1")
  1209. rendered = render_jinja_tmpl(
  1210. "{{ '192.168.0.1/8' | network_size }}",
  1211. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1212. )
  1213. self.assertEqual(rendered, "16777216")
  1214. @requires_network()
  1215. def test_http_query(self):
  1216. """
  1217. Test the `http_query` Jinja filter.
  1218. """
  1219. urls = (
  1220. # These cannot be HTTPS urls since urllib2 chokes on those
  1221. "http://saltstack.com",
  1222. "http://community.saltstack.com",
  1223. "http://google.com",
  1224. "http://duckduckgo.com",
  1225. )
  1226. for backend in ("requests", "tornado", "urllib2"):
  1227. rendered = render_jinja_tmpl(
  1228. "{{ '"
  1229. + random.choice(urls)
  1230. + "' | http_query(backend='"
  1231. + backend
  1232. + "') }}",
  1233. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1234. )
  1235. self.assertIsInstance(
  1236. rendered, str, "Failed with rendered template: {}".format(rendered)
  1237. )
  1238. dict_reply = ast.literal_eval(rendered)
  1239. self.assertIsInstance(
  1240. dict_reply, dict, "Failed with rendered template: {}".format(rendered)
  1241. )
  1242. self.assertIn(
  1243. "body",
  1244. dict_reply,
  1245. "'body' not found in request response({}). Rendered template: {!r}".format(
  1246. dict_reply, rendered
  1247. ),
  1248. )
  1249. self.assertIsInstance(
  1250. dict_reply["body"],
  1251. str,
  1252. "Failed with rendered template: {}".format(rendered),
  1253. )
  1254. def test_to_bool(self):
  1255. """
  1256. Test the `to_bool` Jinja filter.
  1257. """
  1258. rendered = render_jinja_tmpl(
  1259. "{{ 1 | to_bool }}",
  1260. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1261. )
  1262. self.assertEqual(rendered, "True")
  1263. rendered = render_jinja_tmpl(
  1264. "{{ 'True' | to_bool }}",
  1265. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1266. )
  1267. self.assertEqual(rendered, "True")
  1268. rendered = render_jinja_tmpl(
  1269. "{{ 0 | to_bool }}",
  1270. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1271. )
  1272. self.assertEqual(rendered, "False")
  1273. rendered = render_jinja_tmpl(
  1274. "{{ 'Yes' | to_bool }}",
  1275. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1276. )
  1277. self.assertEqual(rendered, "True")
  1278. def test_quote(self):
  1279. """
  1280. Test the `quote` Jinja filter.
  1281. """
  1282. rendered = render_jinja_tmpl(
  1283. "{{ 'random' | quote }}",
  1284. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1285. )
  1286. self.assertEqual(rendered, "random")
  1287. def test_regex_search(self):
  1288. """
  1289. Test the `regex_search` Jinja filter.
  1290. """
  1291. rendered = render_jinja_tmpl(
  1292. "{{ 'abcdefabcdef' | regex_search('BC(.*)', ignorecase=True) }}",
  1293. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1294. )
  1295. self.assertEqual(
  1296. rendered, "('defabcdef',)"
  1297. ) # because search looks only at the beginning
  1298. def test_regex_match(self):
  1299. """
  1300. Test the `regex_match` Jinja filter.
  1301. """
  1302. rendered = render_jinja_tmpl(
  1303. "{{ 'abcdefabcdef' | regex_match('BC(.*)', ignorecase=True)}}",
  1304. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1305. )
  1306. self.assertEqual(rendered, "None")
  1307. def test_regex_replace(self):
  1308. """
  1309. Test the `regex_replace` Jinja filter.
  1310. """
  1311. rendered = render_jinja_tmpl(
  1312. r"{{ 'lets replace spaces' | regex_replace('\s+', '__') }}",
  1313. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1314. )
  1315. self.assertEqual(rendered, "lets__replace__spaces")
  1316. def test_uuid(self):
  1317. """
  1318. Test the `uuid` Jinja filter.
  1319. """
  1320. rendered = render_jinja_tmpl(
  1321. "{{ 'random' | uuid }}",
  1322. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1323. )
  1324. self.assertEqual(rendered, "3652b285-26ad-588e-a5dc-c2ee65edc804")
  1325. def test_min(self):
  1326. """
  1327. Test the `min` Jinja filter.
  1328. """
  1329. rendered = render_jinja_tmpl(
  1330. "{{ [1, 2, 3] | min }}",
  1331. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1332. )
  1333. self.assertEqual(rendered, "1")
  1334. def test_max(self):
  1335. """
  1336. Test the `max` Jinja filter.
  1337. """
  1338. rendered = render_jinja_tmpl(
  1339. "{{ [1, 2, 3] | max }}",
  1340. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1341. )
  1342. self.assertEqual(rendered, "3")
  1343. def test_avg(self):
  1344. """
  1345. Test the `avg` Jinja filter.
  1346. """
  1347. rendered = render_jinja_tmpl(
  1348. "{{ [1, 2, 3] | avg }}",
  1349. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1350. )
  1351. self.assertEqual(rendered, "2.0")
  1352. def test_union(self):
  1353. """
  1354. Test the `union` Jinja filter.
  1355. """
  1356. rendered = render_jinja_tmpl(
  1357. "{{ [1, 2, 3] | union([2, 3, 4]) | join(', ') }}",
  1358. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1359. )
  1360. self.assertEqual(rendered, "1, 2, 3, 4")
  1361. def test_intersect(self):
  1362. """
  1363. Test the `intersect` Jinja filter.
  1364. """
  1365. rendered = render_jinja_tmpl(
  1366. "{{ [1, 2, 3] | intersect([2, 3, 4]) | join(', ') }}",
  1367. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1368. )
  1369. self.assertEqual(rendered, "2, 3")
  1370. def test_difference(self):
  1371. """
  1372. Test the `difference` Jinja filter.
  1373. """
  1374. rendered = render_jinja_tmpl(
  1375. "{{ [1, 2, 3] | difference([2, 3, 4]) | join(', ') }}",
  1376. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1377. )
  1378. self.assertEqual(rendered, "1")
  1379. def test_symmetric_difference(self):
  1380. """
  1381. Test the `symmetric_difference` Jinja filter.
  1382. """
  1383. rendered = render_jinja_tmpl(
  1384. "{{ [1, 2, 3] | symmetric_difference([2, 3, 4]) | join(', ') }}",
  1385. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1386. )
  1387. self.assertEqual(rendered, "1, 4")
  1388. def test_method_call(self):
  1389. """
  1390. Test the `method_call` Jinja filter.
  1391. """
  1392. rendered = render_jinja_tmpl(
  1393. "{{ 6|method_call('bit_length') }}",
  1394. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1395. )
  1396. self.assertEqual(rendered, "3")
  1397. rendered = render_jinja_tmpl(
  1398. "{{ 6.7|method_call('is_integer') }}",
  1399. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1400. )
  1401. self.assertEqual(rendered, "False")
  1402. rendered = render_jinja_tmpl(
  1403. "{{ 'absaltba'|method_call('strip', 'ab') }}",
  1404. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1405. )
  1406. self.assertEqual(rendered, "salt")
  1407. rendered = render_jinja_tmpl(
  1408. "{{ [1, 2, 1, 3, 4]|method_call('index', 1, 1, 3) }}",
  1409. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1410. )
  1411. self.assertEqual(rendered, "2")
  1412. # have to use `dictsort` to keep test result deterministic
  1413. rendered = render_jinja_tmpl(
  1414. "{{ {}|method_call('fromkeys', ['a', 'b', 'c'], 0)|dictsort }}",
  1415. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1416. )
  1417. self.assertEqual(rendered, "[('a', 0), ('b', 0), ('c', 0)]")
  1418. # missing object method test
  1419. rendered = render_jinja_tmpl(
  1420. "{{ 6|method_call('bit_width') }}",
  1421. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1422. )
  1423. self.assertEqual(rendered, "None")
  1424. def test_md5(self):
  1425. """
  1426. Test the `md5` Jinja filter.
  1427. """
  1428. rendered = render_jinja_tmpl(
  1429. "{{ 'random' | md5 }}",
  1430. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1431. )
  1432. self.assertEqual(rendered, "7ddf32e17a6ac5ce04a8ecbf782ca509")
  1433. def test_sha256(self):
  1434. """
  1435. Test the `sha256` Jinja filter.
  1436. """
  1437. rendered = render_jinja_tmpl(
  1438. "{{ 'random' | sha256 }}",
  1439. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1440. )
  1441. self.assertEqual(
  1442. rendered, "a441b15fe9a3cf56661190a0b93b9dec7d04127288cc87250967cf3b52894d11"
  1443. )
  1444. def test_sha512(self):
  1445. """
  1446. Test the `sha512` Jinja filter.
  1447. """
  1448. rendered = render_jinja_tmpl(
  1449. "{{ 'random' | sha512 }}",
  1450. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1451. )
  1452. self.assertEqual(
  1453. rendered,
  1454. str(
  1455. "811a90e1c8e86c7b4c0eef5b2c0bf0ec1b19c4b1b5a242e6455be93787cb473cb7bc"
  1456. "9b0fdeb960d00d5c6881c2094dd63c5c900ce9057255e2a4e271fc25fef1"
  1457. ),
  1458. )
  1459. def test_hmac(self):
  1460. """
  1461. Test the `hmac` Jinja filter.
  1462. """
  1463. rendered = render_jinja_tmpl(
  1464. "{{ 'random' | hmac('secret', 'blah') }}",
  1465. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1466. )
  1467. self.assertEqual(rendered, "False")
  1468. rendered = render_jinja_tmpl(
  1469. (
  1470. "{{ 'get salted' | "
  1471. "hmac('shared secret', 'eBWf9bstXg+NiP5AOwppB5HMvZiYMPzEM9W5YMm/AmQ=') }}"
  1472. ),
  1473. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1474. )
  1475. self.assertEqual(rendered, "True")
  1476. def test_base64_encode(self):
  1477. """
  1478. Test the `base64_encode` Jinja filter.
  1479. """
  1480. rendered = render_jinja_tmpl(
  1481. "{{ 'random' | base64_encode }}",
  1482. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1483. )
  1484. self.assertEqual(rendered, "cmFuZG9t")
  1485. def test_base64_decode(self):
  1486. """
  1487. Test the `base64_decode` Jinja filter.
  1488. """
  1489. rendered = render_jinja_tmpl(
  1490. "{{ 'cmFuZG9t' | base64_decode }}",
  1491. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1492. )
  1493. self.assertEqual(rendered, "random")
  1494. def test_json_query(self):
  1495. """
  1496. Test the `json_query` Jinja filter.
  1497. """
  1498. rendered = render_jinja_tmpl(
  1499. "{{ [1, 2, 3] | json_query('[1]')}}",
  1500. dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
  1501. )
  1502. self.assertEqual(rendered, "2")
  1503. # def test_print(self):
  1504. # env = Environment(extensions=[SerializerExtension])
  1505. # source = '{% import_yaml "toto.foo" as docu %}'
  1506. # name, filename = None, '<filename>'
  1507. # parsed = env._parse(source, name, filename)
  1508. # print parsed
  1509. # print
  1510. # compiled = env._generate(parsed, name, filename)
  1511. # print compiled
  1512. # return
  1513. class TestDotNotationLookup(ModuleCase):
  1514. """
  1515. Tests to call Salt functions via Jinja with various lookup syntaxes
  1516. """
  1517. def setUp(self):
  1518. functions = {
  1519. "mocktest.ping": lambda: True,
  1520. "mockgrains.get": lambda x: "jerry",
  1521. }
  1522. minion_opts = salt.config.minion_config(
  1523. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "minion")
  1524. )
  1525. render = salt.loader.render(minion_opts, functions)
  1526. self.jinja = render.get("jinja")
  1527. def tearDown(self):
  1528. del self.jinja
  1529. def render(self, tmpl_str, context=None):
  1530. return self.jinja(tmpl_str, context=context or {}, argline="-s").read()
  1531. def test_normlookup(self):
  1532. """
  1533. Sanity-check the normal dictionary-lookup syntax for our stub function
  1534. """
  1535. tmpl_str = """Hello, {{ salt['mocktest.ping']() }}."""
  1536. with patch.object(SaltCacheLoader, "file_client", Mock()):
  1537. ret = self.render(tmpl_str)
  1538. self.assertEqual(ret, "Hello, True.")
  1539. def test_dotlookup(self):
  1540. """
  1541. Check calling a stub function using awesome dot-notation
  1542. """
  1543. tmpl_str = """Hello, {{ salt.mocktest.ping() }}."""
  1544. with patch.object(SaltCacheLoader, "file_client", Mock()):
  1545. ret = self.render(tmpl_str)
  1546. self.assertEqual(ret, "Hello, True.")
  1547. def test_shadowed_dict_method(self):
  1548. """
  1549. Check calling a stub function with a name that shadows a ``dict``
  1550. method name
  1551. """
  1552. tmpl_str = """Hello, {{ salt.mockgrains.get('id') }}."""
  1553. with patch.object(SaltCacheLoader, "file_client", Mock()):
  1554. ret = self.render(tmpl_str)
  1555. self.assertEqual(ret, "Hello, jerry.")