test_state.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. # -*- coding: utf-8 -*-
  2. """
  3. :codeauthor: Nicole Thomas <nicole@saltstack.com>
  4. """
  5. from __future__ import absolute_import, print_function, unicode_literals
  6. import os
  7. import shutil
  8. import tempfile
  9. import pytest
  10. import salt.exceptions
  11. import salt.state
  12. import salt.utils.files
  13. import salt.utils.platform
  14. from salt.utils.decorators import state as statedecorators
  15. from salt.utils.odict import OrderedDict
  16. from tests.support.helpers import with_tempfile
  17. from tests.support.mixins import AdaptedConfigurationTestCaseMixin
  18. from tests.support.mock import MagicMock, patch
  19. from tests.support.runtests import RUNTIME_VARS
  20. from tests.support.unit import TestCase
  21. class StateCompilerTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
  22. """
  23. TestCase for the state compiler.
  24. """
  25. def test_format_log_non_ascii_character(self):
  26. """
  27. Tests running a non-ascii character through the state.format_log
  28. function. See Issue #33605.
  29. """
  30. # There is no return to test against as the format_log
  31. # function doesn't return anything. However, we do want
  32. # to make sure that the function doesn't stacktrace when
  33. # called.
  34. ret = {
  35. "changes": {"Français": {"old": "something old", "new": "something new"}},
  36. "result": True,
  37. }
  38. salt.state.format_log(ret)
  39. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  40. def test_render_error_on_invalid_requisite(self):
  41. """
  42. Test that the state compiler correctly deliver a rendering
  43. exception when a requisite cannot be resolved
  44. """
  45. with patch("salt.state.State._gather_pillar") as state_patch:
  46. high_data = {
  47. "git": OrderedDict(
  48. [
  49. (
  50. "pkg",
  51. [
  52. OrderedDict(
  53. [
  54. (
  55. "require",
  56. [
  57. OrderedDict(
  58. [
  59. (
  60. "file",
  61. OrderedDict(
  62. [("test1", "test")]
  63. ),
  64. )
  65. ]
  66. )
  67. ],
  68. )
  69. ]
  70. ),
  71. "installed",
  72. {"order": 10000},
  73. ],
  74. ),
  75. ("__sls__", "issue_35226"),
  76. ("__env__", "base"),
  77. ]
  78. )
  79. }
  80. minion_opts = self.get_temp_config("minion")
  81. minion_opts["pillar"] = {"git": OrderedDict([("test1", "test")])}
  82. state_obj = salt.state.State(minion_opts)
  83. with self.assertRaises(salt.exceptions.SaltRenderError):
  84. state_obj.call_high(high_data)
  85. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  86. def test_verify_onlyif_parse(self):
  87. low_data = {
  88. "onlyif": [{"fun": "test.arg", "args": ["arg1", "arg2"]}],
  89. "name": "mysql-server-5.7",
  90. "state": "debconf",
  91. "__id__": "set root password",
  92. "fun": "set",
  93. "__env__": "base",
  94. "__sls__": "debconf",
  95. "data": {
  96. "mysql-server/root_password": {"type": "password", "value": "temp123"}
  97. },
  98. "order": 10000,
  99. }
  100. expected_result = {"comment": "onlyif condition is true", "result": False}
  101. with patch("salt.state.State._gather_pillar") as state_patch:
  102. minion_opts = self.get_temp_config("minion")
  103. state_obj = salt.state.State(minion_opts)
  104. return_result = state_obj._run_check_onlyif(low_data, "")
  105. self.assertEqual(expected_result, return_result)
  106. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  107. def test_verify_unless_parse(self):
  108. low_data = {
  109. "unless": [{"fun": "test.arg", "args": ["arg1", "arg2"]}],
  110. "name": "mysql-server-5.7",
  111. "state": "debconf",
  112. "__id__": "set root password",
  113. "fun": "set",
  114. "__env__": "base",
  115. "__sls__": "debconf",
  116. "data": {
  117. "mysql-server/root_password": {"type": "password", "value": "temp123"}
  118. },
  119. "order": 10000,
  120. }
  121. expected_result = {
  122. "comment": "unless condition is true",
  123. "result": True,
  124. "skip_watch": True,
  125. }
  126. with patch("salt.state.State._gather_pillar") as state_patch:
  127. minion_opts = self.get_temp_config("minion")
  128. state_obj = salt.state.State(minion_opts)
  129. return_result = state_obj._run_check_unless(low_data, "")
  130. self.assertEqual(expected_result, return_result)
  131. def _expand_win_path(self, path):
  132. """
  133. Expand C:/users/admini~1/appdata/local/temp/salt-tests-tmpdir/...
  134. into C:/users/adminitrator/appdata/local/temp/salt-tests-tmpdir/...
  135. to prevent file.search from expanding the "~" with os.path.expanduser
  136. """
  137. if salt.utils.platform.is_windows():
  138. import win32file
  139. return win32file.GetLongPathName(path).replace("\\", "/")
  140. else:
  141. return path
  142. @with_tempfile()
  143. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  144. def test_verify_onlyif_parse_slots(self, name):
  145. with salt.utils.files.fopen(name, "w") as fp:
  146. fp.write("file-contents")
  147. low_data = {
  148. "onlyif": [
  149. {
  150. "fun": "file.search",
  151. "args": [
  152. "__slot__:salt:test.echo({})".format(
  153. self._expand_win_path(name)
  154. ),
  155. ],
  156. "pattern": "__slot__:salt:test.echo(file-contents)",
  157. }
  158. ],
  159. "name": "mysql-server-5.7",
  160. "state": "debconf",
  161. "__id__": "set root password",
  162. "fun": "set",
  163. "__env__": "base",
  164. "__sls__": "debconf",
  165. "data": {
  166. "mysql-server/root_password": {"type": "password", "value": "temp123"}
  167. },
  168. "order": 10000,
  169. }
  170. expected_result = {"comment": "onlyif condition is true", "result": False}
  171. with patch("salt.state.State._gather_pillar") as state_patch:
  172. minion_opts = self.get_temp_config("minion")
  173. state_obj = salt.state.State(minion_opts)
  174. return_result = state_obj._run_check_onlyif(low_data, "")
  175. self.assertEqual(expected_result, return_result)
  176. @with_tempfile()
  177. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  178. def test_verify_unless_parse_slots(self, name):
  179. with salt.utils.files.fopen(name, "w") as fp:
  180. fp.write("file-contents")
  181. low_data = {
  182. "unless": [
  183. {
  184. "fun": "file.search",
  185. "args": [
  186. "__slot__:salt:test.echo({})".format(
  187. self._expand_win_path(name)
  188. ),
  189. ],
  190. "pattern": "__slot__:salt:test.echo(file-contents)",
  191. }
  192. ],
  193. "name": "mysql-server-5.7",
  194. "state": "debconf",
  195. "__id__": "set root password",
  196. "fun": "set",
  197. "__env__": "base",
  198. "__sls__": "debconf",
  199. "data": {
  200. "mysql-server/root_password": {"type": "password", "value": "temp123"}
  201. },
  202. "order": 10000,
  203. }
  204. expected_result = {
  205. "comment": "unless condition is true",
  206. "result": True,
  207. "skip_watch": True,
  208. }
  209. with patch("salt.state.State._gather_pillar") as state_patch:
  210. minion_opts = self.get_temp_config("minion")
  211. state_obj = salt.state.State(minion_opts)
  212. return_result = state_obj._run_check_unless(low_data, "")
  213. self.assertEqual(expected_result, return_result)
  214. def test_verify_retry_parsing(self):
  215. low_data = {
  216. "state": "file",
  217. "name": "/tmp/saltstack.README.rst",
  218. "__sls__": "demo.download",
  219. "__env__": "base",
  220. "__id__": "download sample data",
  221. "retry": {"attempts": 5, "interval": 5},
  222. "unless": ["test -f /tmp/saltstack.README.rst"],
  223. "source": [
  224. "https://raw.githubusercontent.com/saltstack/salt/develop/README.rst"
  225. ],
  226. "source_hash": "f2bc8c0aa2ae4f5bb5c2051686016b48",
  227. "order": 10000,
  228. "fun": "managed",
  229. }
  230. expected_result = {
  231. "__id__": "download sample data",
  232. "__run_num__": 0,
  233. "__sls__": "demo.download",
  234. "changes": {},
  235. "comment": "['unless condition is true'] The state would be retried every 5 "
  236. "seconds (with a splay of up to 0 seconds) a maximum of 5 times or "
  237. "until a result of True is returned",
  238. "name": "/tmp/saltstack.README.rst",
  239. "result": True,
  240. "skip_watch": True,
  241. }
  242. with patch("salt.state.State._gather_pillar") as state_patch:
  243. minion_opts = self.get_temp_config("minion")
  244. minion_opts["test"] = True
  245. minion_opts["file_client"] = "local"
  246. state_obj = salt.state.State(minion_opts)
  247. mock = {
  248. "result": True,
  249. "comment": ["unless condition is true"],
  250. "skip_watch": True,
  251. }
  252. with patch.object(state_obj, "_run_check", return_value=mock):
  253. self.assertDictContainsSubset(expected_result, state_obj.call(low_data))
  254. class HighStateTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
  255. def setUp(self):
  256. root_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  257. self.state_tree_dir = os.path.join(root_dir, "state_tree")
  258. cache_dir = os.path.join(root_dir, "cachedir")
  259. for dpath in (root_dir, self.state_tree_dir, cache_dir):
  260. if not os.path.isdir(dpath):
  261. os.makedirs(dpath)
  262. overrides = {}
  263. overrides["root_dir"] = root_dir
  264. overrides["state_events"] = False
  265. overrides["id"] = "match"
  266. overrides["file_client"] = "local"
  267. overrides["file_roots"] = dict(base=[self.state_tree_dir])
  268. overrides["cachedir"] = cache_dir
  269. overrides["test"] = False
  270. self.config = self.get_temp_config("minion", **overrides)
  271. self.addCleanup(delattr, self, "config")
  272. self.highstate = salt.state.HighState(self.config)
  273. self.addCleanup(delattr, self, "highstate")
  274. self.highstate.push_active()
  275. def tearDown(self):
  276. self.highstate.pop_active()
  277. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  278. def test_top_matches_with_list(self):
  279. top = {"env": {"match": ["state1", "state2"], "nomatch": ["state3"]}}
  280. matches = self.highstate.top_matches(top)
  281. self.assertEqual(matches, {"env": ["state1", "state2"]})
  282. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  283. def test_top_matches_with_string(self):
  284. top = {"env": {"match": "state1", "nomatch": "state2"}}
  285. matches = self.highstate.top_matches(top)
  286. self.assertEqual(matches, {"env": ["state1"]})
  287. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  288. def test_matches_whitelist(self):
  289. matches = {"env": ["state1", "state2", "state3"]}
  290. matches = self.highstate.matches_whitelist(matches, ["state2"])
  291. self.assertEqual(matches, {"env": ["state2"]})
  292. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  293. def test_matches_whitelist_with_string(self):
  294. matches = {"env": ["state1", "state2", "state3"]}
  295. matches = self.highstate.matches_whitelist(matches, "state2,state3")
  296. self.assertEqual(matches, {"env": ["state2", "state3"]})
  297. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  298. def test_show_state_usage(self):
  299. # monkey patch sub methods
  300. self.highstate.avail = {"base": ["state.a", "state.b", "state.c"]}
  301. def verify_tops(*args, **kwargs):
  302. return []
  303. def get_top(*args, **kwargs):
  304. return None
  305. def top_matches(*args, **kwargs):
  306. return {"base": ["state.a", "state.b"]}
  307. self.highstate.verify_tops = verify_tops
  308. self.highstate.get_top = get_top
  309. self.highstate.top_matches = top_matches
  310. # get compile_state_usage() result
  311. state_usage_dict = self.highstate.compile_state_usage()
  312. self.assertEqual(state_usage_dict["base"]["count_unused"], 1)
  313. self.assertEqual(state_usage_dict["base"]["count_used"], 2)
  314. self.assertEqual(state_usage_dict["base"]["count_all"], 3)
  315. self.assertEqual(state_usage_dict["base"]["used"], ["state.a", "state.b"])
  316. self.assertEqual(state_usage_dict["base"]["unused"], ["state.c"])
  317. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  318. def test_find_sls_ids_with_exclude(self):
  319. """
  320. See https://github.com/saltstack/salt/issues/47182
  321. """
  322. sls_dir = "issue-47182"
  323. shutil.copytree(
  324. os.path.join(RUNTIME_VARS.BASE_FILES, sls_dir),
  325. os.path.join(self.state_tree_dir, sls_dir),
  326. )
  327. shutil.move(
  328. os.path.join(self.state_tree_dir, sls_dir, "top.sls"), self.state_tree_dir
  329. )
  330. # Manually compile the high data. We don't have to worry about all of
  331. # the normal error checking we do here since we know that all the SLS
  332. # files exist and there is no whitelist/blacklist being used.
  333. top = self.highstate.get_top() # pylint: disable=assignment-from-none
  334. matches = self.highstate.top_matches(top)
  335. high, _ = self.highstate.render_highstate(matches)
  336. ret = salt.state.find_sls_ids("issue-47182.stateA.newer", high)
  337. self.assertEqual(ret, [("somestuff", "cmd")])
  338. class StateReturnsTestCase(TestCase):
  339. """
  340. TestCase for code handling state returns.
  341. """
  342. def test_state_output_check_changes_is_dict(self):
  343. """
  344. Test that changes key contains a dictionary.
  345. :return:
  346. """
  347. data = {"changes": []}
  348. out = statedecorators.OutputUnifier("content_check")(lambda: data)()
  349. assert "'Changes' should be a dictionary" in out["comment"]
  350. assert not out["result"]
  351. def test_state_output_check_return_is_dict(self):
  352. """
  353. Test for the entire return is a dictionary
  354. :return:
  355. """
  356. data = ["whatever"]
  357. out = statedecorators.OutputUnifier("content_check")(lambda: data)()
  358. assert (
  359. "Malformed state return. Data must be a dictionary type" in out["comment"]
  360. )
  361. assert not out["result"]
  362. def test_state_output_check_return_has_nrc(self):
  363. """
  364. Test for name/result/comment keys are inside the return.
  365. :return:
  366. """
  367. data = {"arbitrary": "data", "changes": {}}
  368. out = statedecorators.OutputUnifier("content_check")(lambda: data)()
  369. assert (
  370. " The following keys were not present in the state return: name, result, comment"
  371. in out["comment"]
  372. )
  373. assert not out["result"]
  374. def test_state_output_unifier_comment_is_not_list(self):
  375. """
  376. Test for output is unified so the comment is converted to a multi-line string
  377. :return:
  378. """
  379. data = {
  380. "comment": ["data", "in", "the", "list"],
  381. "changes": {},
  382. "name": None,
  383. "result": "fantastic!",
  384. }
  385. expected = {
  386. "comment": "data\nin\nthe\nlist",
  387. "changes": {},
  388. "name": None,
  389. "result": True,
  390. }
  391. assert statedecorators.OutputUnifier("unify")(lambda: data)() == expected
  392. data = {
  393. "comment": ["data", "in", "the", "list"],
  394. "changes": {},
  395. "name": None,
  396. "result": None,
  397. }
  398. expected = "data\nin\nthe\nlist"
  399. assert (
  400. statedecorators.OutputUnifier("unify")(lambda: data)()["comment"]
  401. == expected
  402. )
  403. def test_state_output_unifier_result_converted_to_true(self):
  404. """
  405. Test for output is unified so the result is converted to True
  406. :return:
  407. """
  408. data = {
  409. "comment": ["data", "in", "the", "list"],
  410. "changes": {},
  411. "name": None,
  412. "result": "Fantastic",
  413. }
  414. assert statedecorators.OutputUnifier("unify")(lambda: data)()["result"] is True
  415. def test_state_output_unifier_result_converted_to_false(self):
  416. """
  417. Test for output is unified so the result is converted to False
  418. :return:
  419. """
  420. data = {
  421. "comment": ["data", "in", "the", "list"],
  422. "changes": {},
  423. "name": None,
  424. "result": "",
  425. }
  426. assert statedecorators.OutputUnifier("unify")(lambda: data)()["result"] is False
  427. class StateFormatSlotsTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
  428. """
  429. TestCase for code handling slots
  430. """
  431. def setUp(self):
  432. with patch("salt.state.State._gather_pillar"):
  433. minion_opts = self.get_temp_config("minion")
  434. self.state_obj = salt.state.State(minion_opts)
  435. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  436. def test_format_slots_no_slots(self):
  437. """
  438. Test the format slots keeps data without slots untouched.
  439. """
  440. cdata = {"args": ["arg"], "kwargs": {"key": "val"}}
  441. self.state_obj.format_slots(cdata)
  442. self.assertEqual(cdata, {"args": ["arg"], "kwargs": {"key": "val"}})
  443. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  444. def test_format_slots_arg(self):
  445. """
  446. Test the format slots is calling a slot specified in args with corresponding arguments.
  447. """
  448. cdata = {
  449. "args": ["__slot__:salt:mod.fun(fun_arg, fun_key=fun_val)"],
  450. "kwargs": {"key": "val"},
  451. }
  452. mock = MagicMock(return_value="fun_return")
  453. with patch.dict(self.state_obj.functions, {"mod.fun": mock}):
  454. self.state_obj.format_slots(cdata)
  455. mock.assert_called_once_with("fun_arg", fun_key="fun_val")
  456. self.assertEqual(cdata, {"args": ["fun_return"], "kwargs": {"key": "val"}})
  457. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  458. def test_format_slots_dict_arg(self):
  459. """
  460. Test the format slots is calling a slot specified in dict arg.
  461. """
  462. cdata = {
  463. "args": [{"subarg": "__slot__:salt:mod.fun(fun_arg, fun_key=fun_val)"}],
  464. "kwargs": {"key": "val"},
  465. }
  466. mock = MagicMock(return_value="fun_return")
  467. with patch.dict(self.state_obj.functions, {"mod.fun": mock}):
  468. self.state_obj.format_slots(cdata)
  469. mock.assert_called_once_with("fun_arg", fun_key="fun_val")
  470. self.assertEqual(
  471. cdata, {"args": [{"subarg": "fun_return"}], "kwargs": {"key": "val"}}
  472. )
  473. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  474. def test_format_slots_listdict_arg(self):
  475. """
  476. Test the format slots is calling a slot specified in list containing a dict.
  477. """
  478. cdata = {
  479. "args": [[{"subarg": "__slot__:salt:mod.fun(fun_arg, fun_key=fun_val)"}]],
  480. "kwargs": {"key": "val"},
  481. }
  482. mock = MagicMock(return_value="fun_return")
  483. with patch.dict(self.state_obj.functions, {"mod.fun": mock}):
  484. self.state_obj.format_slots(cdata)
  485. mock.assert_called_once_with("fun_arg", fun_key="fun_val")
  486. self.assertEqual(
  487. cdata, {"args": [[{"subarg": "fun_return"}]], "kwargs": {"key": "val"}}
  488. )
  489. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  490. def test_format_slots_liststr_arg(self):
  491. """
  492. Test the format slots is calling a slot specified in list containing a dict.
  493. """
  494. cdata = {
  495. "args": [["__slot__:salt:mod.fun(fun_arg, fun_key=fun_val)"]],
  496. "kwargs": {"key": "val"},
  497. }
  498. mock = MagicMock(return_value="fun_return")
  499. with patch.dict(self.state_obj.functions, {"mod.fun": mock}):
  500. self.state_obj.format_slots(cdata)
  501. mock.assert_called_once_with("fun_arg", fun_key="fun_val")
  502. self.assertEqual(cdata, {"args": [["fun_return"]], "kwargs": {"key": "val"}})
  503. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  504. def test_format_slots_kwarg(self):
  505. """
  506. Test the format slots is calling a slot specified in kwargs with corresponding arguments.
  507. """
  508. cdata = {
  509. "args": ["arg"],
  510. "kwargs": {"key": "__slot__:salt:mod.fun(fun_arg, fun_key=fun_val)"},
  511. }
  512. mock = MagicMock(return_value="fun_return")
  513. with patch.dict(self.state_obj.functions, {"mod.fun": mock}):
  514. self.state_obj.format_slots(cdata)
  515. mock.assert_called_once_with("fun_arg", fun_key="fun_val")
  516. self.assertEqual(cdata, {"args": ["arg"], "kwargs": {"key": "fun_return"}})
  517. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  518. def test_format_slots_multi(self):
  519. """
  520. Test the format slots is calling all slots with corresponding arguments when multiple slots
  521. specified.
  522. """
  523. cdata = {
  524. "args": [
  525. "__slot__:salt:test_mod.fun_a(a_arg, a_key=a_kwarg)",
  526. "__slot__:salt:test_mod.fun_b(b_arg, b_key=b_kwarg)",
  527. ],
  528. "kwargs": {
  529. "kw_key_1": "__slot__:salt:test_mod.fun_c(c_arg, c_key=c_kwarg)",
  530. "kw_key_2": "__slot__:salt:test_mod.fun_d(d_arg, d_key=d_kwarg)",
  531. },
  532. }
  533. mock_a = MagicMock(return_value="fun_a_return")
  534. mock_b = MagicMock(return_value="fun_b_return")
  535. mock_c = MagicMock(return_value="fun_c_return")
  536. mock_d = MagicMock(return_value="fun_d_return")
  537. with patch.dict(
  538. self.state_obj.functions,
  539. {
  540. "test_mod.fun_a": mock_a,
  541. "test_mod.fun_b": mock_b,
  542. "test_mod.fun_c": mock_c,
  543. "test_mod.fun_d": mock_d,
  544. },
  545. ):
  546. self.state_obj.format_slots(cdata)
  547. mock_a.assert_called_once_with("a_arg", a_key="a_kwarg")
  548. mock_b.assert_called_once_with("b_arg", b_key="b_kwarg")
  549. mock_c.assert_called_once_with("c_arg", c_key="c_kwarg")
  550. mock_d.assert_called_once_with("d_arg", d_key="d_kwarg")
  551. self.assertEqual(
  552. cdata,
  553. {
  554. "args": ["fun_a_return", "fun_b_return"],
  555. "kwargs": {"kw_key_1": "fun_c_return", "kw_key_2": "fun_d_return"},
  556. },
  557. )
  558. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  559. def test_format_slots_malformed(self):
  560. """
  561. Test the format slots keeps malformed slots untouched.
  562. """
  563. sls_data = {
  564. "args": [
  565. "__slot__:NOT_SUPPORTED:not.called()",
  566. "__slot__:salt:not.called(",
  567. "__slot__:salt:",
  568. "__slot__:salt",
  569. "__slot__:",
  570. "__slot__",
  571. ],
  572. "kwargs": {
  573. "key3": "__slot__:NOT_SUPPORTED:not.called()",
  574. "key4": "__slot__:salt:not.called(",
  575. "key5": "__slot__:salt:",
  576. "key6": "__slot__:salt",
  577. "key7": "__slot__:",
  578. "key8": "__slot__",
  579. },
  580. }
  581. cdata = sls_data.copy()
  582. mock = MagicMock(return_value="return")
  583. with patch.dict(self.state_obj.functions, {"not.called": mock}):
  584. self.state_obj.format_slots(cdata)
  585. mock.assert_not_called()
  586. self.assertEqual(cdata, sls_data)
  587. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  588. def test_slot_traverse_dict(self):
  589. """
  590. Test the slot parsing of dict response.
  591. """
  592. cdata = {
  593. "args": ["arg"],
  594. "kwargs": {"key": "__slot__:salt:mod.fun(fun_arg, fun_key=fun_val).key1"},
  595. }
  596. return_data = {"key1": "value1"}
  597. mock = MagicMock(return_value=return_data)
  598. with patch.dict(self.state_obj.functions, {"mod.fun": mock}):
  599. self.state_obj.format_slots(cdata)
  600. mock.assert_called_once_with("fun_arg", fun_key="fun_val")
  601. self.assertEqual(cdata, {"args": ["arg"], "kwargs": {"key": "value1"}})
  602. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  603. def test_slot_append(self):
  604. """
  605. Test the slot parsing of dict response.
  606. """
  607. cdata = {
  608. "args": ["arg"],
  609. "kwargs": {
  610. "key": "__slot__:salt:mod.fun(fun_arg, fun_key=fun_val).key1 ~ thing~",
  611. },
  612. }
  613. return_data = {"key1": "value1"}
  614. mock = MagicMock(return_value=return_data)
  615. with patch.dict(self.state_obj.functions, {"mod.fun": mock}):
  616. self.state_obj.format_slots(cdata)
  617. mock.assert_called_once_with("fun_arg", fun_key="fun_val")
  618. self.assertEqual(cdata, {"args": ["arg"], "kwargs": {"key": "value1thing~"}})
  619. # Skip on windows like integration.modules.test_state.StateModuleTest.test_parallel_state_with_long_tag
  620. @pytest.mark.skip_on_windows(
  621. reason="Skipped until parallel states can be fixed on Windows",
  622. )
  623. def test_format_slots_parallel(self):
  624. """
  625. Test if slots work with "parallel: true".
  626. """
  627. high_data = {
  628. "always-changes-and-succeeds": {
  629. "test": [
  630. {"changes": True},
  631. {"comment": "__slot__:salt:test.echo(fun_return)"},
  632. {"parallel": True},
  633. "configurable_test_state",
  634. {"order": 10000},
  635. ],
  636. "__env__": "base",
  637. "__sls__": "parallel_slots",
  638. }
  639. }
  640. self.state_obj.jid = "123"
  641. res = self.state_obj.call_high(high_data)
  642. self.state_obj.jid = None
  643. [(_, data)] = res.items()
  644. self.assertEqual(data["comment"], "fun_return")