Merge pull request #43868 from rallytime/bp-43847

Back-port #43847 to 2017.7.2
This commit is contained in:
Mike Place 2017-10-03 14:00:51 +02:00 committed by GitHub
commit dd0b3388cf
2 changed files with 84 additions and 23 deletions

View file

@ -178,6 +178,7 @@ from __future__ import absolute_import
import salt.loader
import salt.utils
import salt.utils.jid
from salt.ext import six
from salt.ext.six.moves import range
from salt.ext.six.moves import zip
from salt.exceptions import SaltInvocationError
@ -314,22 +315,31 @@ def _call_function(name, returner=None, **kwargs):
:return:
'''
argspec = salt.utils.args.get_function_argspec(__salt__[name])
# func_kw is initialized to a dictionary of keyword arguments the function to be run accepts
func_kw = dict(zip(argspec.args[-len(argspec.defaults or []):], # pylint: disable=incompatible-py3-code
argspec.defaults or []))
# func_args is initialized to a list of positional arguments that the function to be run accepts
func_args = argspec.args[:len(argspec.args or []) - len(argspec.defaults or [])]
arg_type, na_type, kw_type = [], {}, False
for funcset in reversed(kwargs.get('func_args') or []):
if not isinstance(funcset, dict):
kw_type = True
if kw_type:
if isinstance(funcset, dict):
arg_type += funcset.values()
na_type.update(funcset)
else:
arg_type.append(funcset)
# We are just receiving a list of args to the function to be run, so just append
# those to the arg list that we will pass to the func.
arg_type.append(funcset)
else:
func_kw.update(funcset)
for kwarg_key in six.iterkeys(funcset):
# We are going to pass in a keyword argument. The trick here is to make certain
# that if we find that in the *args* list that we pass it there and not as a kwarg
if kwarg_key in func_args:
arg_type.append(funcset[kwarg_key])
continue
else:
# Otherwise, we're good and just go ahead and pass the keyword/value pair into
# the kwargs list to be run.
func_kw.update(funcset)
arg_type.reverse()
_exp_prm = len(argspec.args or []) - len(argspec.defaults or [])
_passed_prm = len(arg_type)
missing = []

View file

@ -6,6 +6,7 @@
# Import Python Libs
from __future__ import absolute_import
from inspect import ArgSpec
import logging
# Import Salt Libs
import salt.states.module as module
@ -20,6 +21,8 @@ from tests.support.mock import (
patch
)
log = logging.getLogger(__name__)
CMD = 'foo.bar'
@ -91,8 +94,9 @@ class ModuleStateTest(TestCase, LoaderModuleMockMixin):
with patch.dict(module.__salt__, {}, clear=True):
with patch.dict(module.__opts__, {'use_superseded': ['module.run']}):
ret = module.run(**{CMD: None})
assert ret['comment'] == "Unavailable function: {0}.".format(CMD)
assert not ret['result']
if ret['comment'] != "Unavailable function: {0}.".format(CMD) \
or ret['result']:
self.fail('module.run did not fail as expected: {0}'.format(ret))
def test_module_run_hidden_varargs(self):
'''
@ -111,8 +115,9 @@ class ModuleStateTest(TestCase, LoaderModuleMockMixin):
'''
with patch.dict(module.__opts__, {'test': True, 'use_superseded': ['module.run']}):
ret = module.run(**{CMD: None})
assert ret['comment'] == "Function {0} to be executed.".format(CMD)
assert ret['result']
if ret['comment'] != "Function {0} to be executed.".format(CMD) \
or not ret['result']:
self.fail('module.run failed: {0}'.format(ret))
def test_run_missing_arg(self):
'''
@ -122,7 +127,10 @@ class ModuleStateTest(TestCase, LoaderModuleMockMixin):
with patch.dict(module.__salt__, {CMD: _mocked_func_named}):
with patch.dict(module.__opts__, {'use_superseded': ['module.run']}):
ret = module.run(**{CMD: None})
assert ret['comment'] == "'{0}' failed: Function expects 1 parameters, got only 0".format(CMD)
expected_comment = \
"'{0}' failed: Function expects 1 parameters, got only 0".format(CMD)
if ret['comment'] != expected_comment:
self.fail('module.run did not fail as expected: {0}'.format(ret))
def test_run_correct_arg(self):
'''
@ -132,16 +140,17 @@ class ModuleStateTest(TestCase, LoaderModuleMockMixin):
with patch.dict(module.__salt__, {CMD: _mocked_func_named}):
with patch.dict(module.__opts__, {'use_superseded': ['module.run']}):
ret = module.run(**{CMD: ['Fred']})
assert ret['comment'] == '{0}: Success'.format(CMD)
assert ret['result']
if ret['comment'] != '{0}: Success'.format(CMD) or not ret['result']:
self.fail('module.run failed: {0}'.format(ret))
def test_run_unexpected_keywords(self):
with patch.dict(module.__salt__, {CMD: _mocked_func_args}):
with patch.dict(module.__opts__, {'use_superseded': ['module.run']}):
ret = module.run(**{CMD: [{'foo': 'bar'}]})
assert ret['comment'] == "'{0}' failed: {1}() got an unexpected keyword argument " \
"'foo'".format(CMD, module.__salt__[CMD].__name__)
assert not ret['result']
expected_comment = "'{0}' failed: {1}() got an unexpected keyword argument " \
"'foo'".format(CMD, module.__salt__[CMD].__name__)
if ret['comment'] != expected_comment or ret['result']:
self.fail('module.run did not fail as expected: {0}'.format(ret))
def test_run_args(self):
'''
@ -150,7 +159,17 @@ class ModuleStateTest(TestCase, LoaderModuleMockMixin):
'''
with patch.dict(module.__salt__, {CMD: _mocked_func_args}):
with patch.dict(module.__opts__, {'use_superseded': ['module.run']}):
assert module.run(**{CMD: ['foo', 'bar']})['result']
try:
ret = module.run(**{CMD: ['foo', 'bar']})
except Exception as exc:
log.exception('test_run_none_return: raised exception')
self.fail('module.run raised exception: {0}'.format(exc))
if not ret['result']:
log.exception(
'test_run_none_return: test failed, result: %s',
ret
)
self.fail('module.run failed: {0}'.format(ret))
def test_run_none_return(self):
'''
@ -159,7 +178,17 @@ class ModuleStateTest(TestCase, LoaderModuleMockMixin):
'''
with patch.dict(module.__salt__, {CMD: _mocked_none_return}):
with patch.dict(module.__opts__, {'use_superseded': ['module.run']}):
assert module.run(**{CMD: None})['result']
try:
ret = module.run(**{CMD: None})
except Exception as exc:
log.exception('test_run_none_return: raised exception')
self.fail('module.run raised exception: {0}'.format(exc))
if not ret['result']:
log.exception(
'test_run_none_return: test failed, result: %s',
ret
)
self.fail('module.run failed: {0}'.format(ret))
def test_run_typed_return(self):
'''
@ -169,7 +198,18 @@ class ModuleStateTest(TestCase, LoaderModuleMockMixin):
for val in [1, 0, 'a', '', (1, 2,), (), [1, 2], [], {'a': 'b'}, {}, True, False]:
with patch.dict(module.__salt__, {CMD: _mocked_none_return}):
with patch.dict(module.__opts__, {'use_superseded': ['module.run']}):
assert module.run(**{CMD: [{'ret': val}]})['result']
log.debug('test_run_typed_return: trying %s', val)
try:
ret = module.run(**{CMD: [{'ret': val}]})
except Exception as exc:
log.exception('test_run_typed_return: raised exception')
self.fail('module.run raised exception: {0}'.format(exc))
if not ret['result']:
log.exception(
'test_run_typed_return: test failed, result: %s',
ret
)
self.fail('module.run failed: {0}'.format(ret))
def test_run_batch_call(self):
'''
@ -182,7 +222,18 @@ class ModuleStateTest(TestCase, LoaderModuleMockMixin):
'second': _mocked_none_return,
'third': _mocked_none_return}, clear=True):
for f_name in module.__salt__:
assert module.run(**{f_name: None})['result']
log.debug('test_run_batch_call: trying %s', f_name)
try:
ret = module.run(**{f_name: None})
except Exception as exc:
log.exception('test_run_batch_call: raised exception')
self.fail('module.run raised exception: {0}'.format(exc))
if not ret['result']:
log.exception(
'test_run_batch_call: test failed, result: %s',
ret
)
self.fail('module.run failed: {0}'.format(ret))
def test_module_run_module_not_available(self):
'''