From a722e2dba1094432aa36f68b62a202dd7564be54 Mon Sep 17 00:00:00 2001 From: kschopmeyer Date: Mon, 22 Jan 2018 08:56:54 -0600 Subject: [PATCH] Fix invoke method interface. I misunderstood the Params vs params and did not incorporate it all. --- pywbem_mock/_wbemconnection_mock.py | 49 ++++--- testsuite/test_wbemconnection_mock.py | 176 ++++++++++++++++++-------- 2 files changed, 149 insertions(+), 76 deletions(-) diff --git a/pywbem_mock/_wbemconnection_mock.py b/pywbem_mock/_wbemconnection_mock.py index eeaaff3ab..e897414a5 100644 --- a/pywbem_mock/_wbemconnection_mock.py +++ b/pywbem_mock/_wbemconnection_mock.py @@ -91,8 +91,8 @@ def _display(dest, text): f.close() -def method_callback_interface(conn, objectname, methodname, params): - # pylint: disable=unused-argument +def method_callback_interface(conn, objectname, methodname, Params, **params): + # pylint: disable=unused-argument, invalid-name """ **Experimental:** *New in pywbem 0.12 as experimental.* @@ -137,7 +137,7 @@ class and methodname is received. This method will be called a single callback function to be used as the responder for multiple methods. - params (:term:`py:iterable` of tuples of name,value): + Params (:term:`py:iterable` of tuples of name,value): Input parameters from the InvokeMethod Params input. An iterable of input parameters for the CIM method. @@ -151,6 +151,16 @@ class and methodname is received. This method will be called * value (:term:`CIM data type`): Parameter value + Keyword Arguments: + + : Each keyword parameter represents a single input parameter for the + CIM method, with: + + * key (:term:`string`): + Parameter name (case independent) + * value (:term:`CIM data type`): + Parameter value + Returns: * The user written callback method must return a tuple @@ -601,16 +611,15 @@ def add_method_callback(self, classname, methodname, method_callback, if namespace is None: namespace = self.default_namespace - if not self.methods: + if namespace not in self.methods: self.methods[namespace] = NocaseDict() + + if classname not in self.methods[namespace]: self.methods[namespace][classname] = NocaseDict() - # Test if dictionary entry already exists. If not, create it - try: - self.methods[namespace][classname][methodname] = method_callback - except KeyError: - mth = NocaseDict(NocaseDict({methodname: method_callback})) - self.methods[namespace][classname] = NocaseDict({methodname: mth}) + if methodname in self.methods[namespace][classname]: + raise ValueError("Duplicate method specification") + self.methods[namespace][classname][methodname] = method_callback def display_repository(self, namespaces=None, dest=None, summary=False, output_format='mof'): @@ -738,6 +747,7 @@ def _display_objects(obj_type, objects_repo, namespace, cmt_begin, cmt_end, elif obj_type == 'Methods': try: methods = objects_repo[namespace] + except KeyError: return @@ -869,11 +879,10 @@ def _mock_methodcall(self, methodname, localobject, Params=None, **params): 'response_params_rqd=%s\nparams=%s', methodname, localobject, Params, params) - result = self._fake_invokemethod(methodname, - localobject, - Params=Params, + result = self._fake_invokemethod(methodname, localobject, Params, **params) - # sleep for defined number of seconds + + # Sleep for defined number of seconds if self._response_delay: time.sleep(self._response_delay) @@ -2803,8 +2812,7 @@ def _fake_closeenumeration(self, namespace, **params): # ##################################################################### - def _fake_invokemethod(self, methodname, objectname, Params=None, - **params): + def _fake_invokemethod(self, methodname, objectname, Params, **params): # pylint: disable=invalid-name """ Implements a mock WBEM server responder for @@ -2861,7 +2869,7 @@ def _fake_invokemethod(self, methodname, objectname, Params=None, try: methods = methodsrepo[target_class] except KeyError: - raise CIMError(CIM_ERR_NOT_FOUND, 'Class %s for Method %s in' + raise CIMError(CIM_ERR_NOT_FOUND, 'Class %s for Method %s in ' 'namespace %s not ' 'registered in repo' % (localobject.classname, methodname, namespace)) @@ -2880,9 +2888,10 @@ def _fake_invokemethod(self, methodname, objectname, Params=None, # Call the registered method and catch exceptions. try: - result = bound_method(self, methodname, localobject, params=Params) + result = bound_method(self, methodname, localobject, Params, + **params) - # TODO Test return: assert isinstance(result, (list, tuple)) + assert isinstance(result, (list, tuple)) # Map output params to NocaseDict to be compatible with return # from _methodcall @@ -2903,5 +2912,5 @@ def _fake_invokemethod(self, methodname, objectname, Params=None, raise CIMError(CIM_ERR_FAILED, 'Exception failure of invoked ' 'method %s in namespace %s with ' 'input localobject %r, parameters ' - '%s. Exception: %r\nTraceback\n%s' % + '%r. Exception: %r\nTraceback\n%s' % (methodname, namespace, localobject, params, ex, tb)) diff --git a/testsuite/test_wbemconnection_mock.py b/testsuite/test_wbemconnection_mock.py index 5b620d4e5..e19e55f46 100644 --- a/testsuite/test_wbemconnection_mock.py +++ b/testsuite/test_wbemconnection_mock.py @@ -341,14 +341,11 @@ class CIM_Foo { [IN, Description("FuzzyMethod Param")] string FuzzyParameter, - [IN, Description ( "Test of ref input parameter")] + [IN, OUT, Description ( "Test of ref in/out parameter")] CIM_Foo REF Foo, [IN ( false ), OUT, Description("TestMethod Param")] - string OutputParam, - - [IN ( false ), OUT, Description ( "Fuzy method ref param out")] - CIM_Foo REF Foo); + string OutputParam); [ Description("Method with no Parameters") ] uint32 DeleteNothing(); @@ -924,30 +921,42 @@ def method2_callback(conn, methodname, object_name, params=None): def method1_callback(conn, methodname, object_name, params=None): pass + @staticmethod + def fuzzy_callback(conn, methodname, object_name, params=None): + pass + def test_display_repository(self, conn, tst_instances_mof): """ Test the display of the repository with it various options. """ + # Create the objects in all namespaces namespaces = ['root/blah', 'interop'] for ns in namespaces: conn.compile_mof_str(tst_instances_mof, namespace=ns) - # Subscribe to InvokeMethod callback methods in the class. + # Subscribe to InvokeMethod callback methods in the class. - conn.add_method_callback('CIM_Foo_sub_sub', 'Method1', - self.method1_callback, - namespace=ns) - conn.add_method_callback('CIM_Foo_sub_sub', 'Method2', - self.method2_callback, + conn.add_method_callback('CIM_Foo_sub_sub', 'Method1', + self.method1_callback, + namespace=ns) + conn.add_method_callback('CIM_Foo_sub_sub', 'Method2', + self.method2_callback, + namespace=ns) + + conn.add_method_callback('CIM_Foo', 'Fuzzy', + self.fuzzy_callback, namespace=ns) # pylint: disable=unused-variable + # Test various display_repository input and output options with OutputCapture() as output: # noqa: F841 conn.display_repository() for param in ('xml', 'mof', 'repr'): conn.display_repository(output_format=param) - conn.display_repository(namespaces=ns) + conn.display_repository(namespaces=namespaces) + ns = namespaces[0] conn.display_repository(namespaces=[ns]) + conn.display_repository(namespaces=ns) with pytest.raises(ValueError): conn.display_repository(output_format='blah') @@ -959,6 +968,9 @@ def test_display_repository(self, conn, tst_instances_mof): assert os.path.isfile(tst_file) os.remove(tst_file) + # TODO: Add test of format of the outut with a very simple schema to keep + # the comparison small. + @pytest.mark.parametrize( "ns", [DEFAULT_NAMESPACE, 'root/blah']) def test_addcimobject(self, conn, ns, tst_classes, tst_instances, @@ -972,6 +984,7 @@ def test_addcimobject(self, conn, ns, tst_classes, tst_instances, conn.add_cimobjects(tst_instances, namespace=ns) conn.add_cimobjects(tst_insts_big, namespace=ns) + # pylint: disable=protected-access class_repo = conn._get_class_repo(ns) assert len(class_repo) == len(tst_classes) @@ -1758,6 +1771,7 @@ def test_enumerateinstnames_lite(self, conn_lite, ns, tst_instances): nsx = conn_lite.default_namespace if ns is None else ns + # pylint: disable=protected-access request_inst_names = [i.path for i in conn_lite._get_instance_repo(nsx) if i.classname == cln] @@ -2191,13 +2205,13 @@ def test_createinstance(self, conn, ns, tst, new_inst, exp_err, tst_classes, assert False, "The tst parameter %s not defined" % tst if not exp_err: - for new_inst in new_insts: - rtn_inst_name = conn.CreateInstance(new_inst, ns) + for inst in new_insts: + rtn_inst_name = conn.CreateInstance(inst, ns) rtn_inst = conn.GetInstance(rtn_inst_name) - new_inst.path.namespace = rtn_inst.path.namespace - assert rtn_inst.path == new_inst.path - assert rtn_inst == new_inst + inst.path.namespace = rtn_inst.path.namespace + assert rtn_inst.path == inst.path + assert rtn_inst == inst else: with pytest.raises(CIMError) as exec_info: @@ -3623,7 +3637,8 @@ class TestInvokeMethod(object): """ Test invoking extrinsic methods in Fake_WBEMConnection """ - def method1_callback(self, conn, methodname, object_name, params=None): + def method1_callback(self, conn, methodname, object_name, Params, **params): + # pylint: disable=unused-argument, invalid-name """ Callback for InvokeMethod with method name method1. This callback is defined by a add_method_callback method call in the test method. @@ -3637,7 +3652,7 @@ def method1_callback(self, conn, methodname, object_name, params=None): """ # pylint: disable=attribute-defined-outside-init self.executed_method = 'Method1' - assert params == self.input_params + assert Params == self.input_Params # Test for valid conn by accessing repository for object defined by # object_name. This test should never happen since already @@ -3652,8 +3667,8 @@ def method1_callback(self, conn, methodname, object_name, params=None): else: raise CIMError(CIM_ERR_FAILED, 'Callback Method1 failed because input object_name ' - '%r invalid type %s' % - object_name, type(object_name)) + '%s invalid type %s' % + (object_name, type(object_name))) assert object_name.namespace == self.test_namespace return_value = self.return_value @@ -3661,7 +3676,8 @@ def method1_callback(self, conn, methodname, object_name, params=None): return (return_value, return_params) - def method2_callback(self, conn, methodname, object_name, params=None): + def method2_callback(self, conn, methodname, object_name, Params, **params): + # pylint: disable=unused-argument, invalid-name """ InvokeMethod callback. This is a smiple callback that just tests methodname and then returns returnvalue and params from the @@ -3672,13 +3688,13 @@ def method2_callback(self, conn, methodname, object_name, params=None): # pylint: disable=attribute-defined-outside-init self.executed_method = 'Method2' - assert params == self.input_params + assert Params == self.input_Params assert object_name.namespace == self.test_namespace # if inputparam 1 has defined value, execute exception to test # exception passback. - for param in params: + for param in Params: if param[0] == 'TestCIMErrorException': # TODO extend so generates whatever exception defined if param[1] == 'CIM_ERR_FAILED': @@ -3689,16 +3705,19 @@ def method2_callback(self, conn, methodname, object_name, params=None): return (return_value, return_params) - def fuzzy_callback(self, conn, methodname, object_name, params=None): + def fuzzy_callback(self, conn, methodname, object_name, Params, **params): + # pylint: disable=attribute-defined-outside-init, unused-argument + # pylint: disable=invalid-name """ InvokeMethod callback. This is a smiple callback that just tests methodname and then returns returnvalue and params from the TestInvokeMethod object attributes. """ - # pylint: disable=attribute-defined-outside-init - self.executed_method = 'fuzzy' - assert methodname == self.executed_method - assert params == self.input_params + self.executed_method = 'Fuzzy' + + # Test should be subclass + # assert methodname == self.executed_method + assert Params == self.input_Params assert object_name.namespace == self.test_namespace @@ -3710,31 +3729,34 @@ def fuzzy_callback(self, conn, methodname, object_name, params=None): @pytest.mark.parametrize( "ns", [None, 'root/blah']) @pytest.mark.parametrize( - # description: description of test - # inputs: dictionary of input object_name, methodname, and params + # description: description of test. + # inputs: dictionary of input object_name, methodname, Params and + # optionally params. # exp_output: dictionary of expected returnvalue ('return') and output # params('params') as list of tuples. # exp_exception: None or expected exception. - # exc_exc_data: None or expected CIMError status msg + # exc_exc_data: None or expected CIMError status msg if exp_exception + # is not None. "description, inputs, exp_output, exp_exception, exp_exc_data", [ ['Execution of Method1 method with single input param', {'object_name': CIMClassName('CIM_Foo_sub_sub'), 'methodname': 'Method1', - 'params': [('InputParam1', 'FirstData')], }, + 'Params': [('InputParam1', 'FirstData')], }, {'return': 0, 'params': [('OutPutParam1', 'SomeString')]}, None, None], ['Execution of Method1 method with objectname string', {'object_name': 'CIM_Foo_sub_sub', 'methodname': 'Method1', - 'params': [('InputParam1', 'FirstData')], }, + 'Params': [('InputParam1', 'FirstData')], + 'params': {}, }, {'return': 0, 'params': [('OutPutParam1', 'SomeString')]}, None, None], ['Execution of Method1 method with multiple input params', {'object_name': CIMClassName('CIM_Foo_sub_sub'), 'methodname': 'Method1', - 'params': [('InputParam1', 'FirstData'), + 'Params': [('InputParam1', 'FirstData'), ('InputParam2', 'SecondData')], }, {'return': 0, 'params': [('OutPutParam1', 'SomeString')]}, None, None], @@ -3742,42 +3764,42 @@ def fuzzy_callback(self, conn, methodname, object_name, params=None): ['Simple Execution of Method2 method with single input param', {'object_name': CIMClassName('CIM_Foo_sub_sub'), 'methodname': 'Method2', - 'params': [('InputParam1', 'FirstData')], }, + 'Params': [('InputParam1', 'FirstData')], }, {'return': 0, 'params': [('OutPutParam1', 'SomeString')]}, None, None], ['Execute Method1 with no input parameters', {'object_name': CIMClassName('CIM_Foo_sub_sub'), 'methodname': 'Method1', - 'params': [], }, + 'Params': [], }, {'return': 0, 'params': [('OutPutParam1', 'SomeString')]}, None, None], ['Execute Method1 with no output parameters', {'object_name': CIMClassName('CIM_Foo_sub_sub'), 'methodname': 'Method1', - 'params': [('InputParam1', 'FirstData')], }, + 'Params': [('InputParam1', 'FirstData')], }, {'return': 0, 'params': []}, None, None], ['Execute Method1 with no input/output parameters', {'object_name': CIMClassName('CIM_Foo_sub_sub'), 'methodname': 'Method1', - 'params': [], }, + 'Params': [], }, {'return': 0, 'params': []}, None, None], ['Execute Method1 with invalid namespace', {'object_name': CIMClassName('CIM_Foo_sub_sub'), 'methodname': 'Method1', - 'params': [], }, + 'Params': [], }, {'return': 0, 'params': []}, CIMError, 'CIM_ERR_INVALID_NAMESPACE'], ['Execute Method2 with invalid namespace', {'object_name': CIMClassName('CIM_Foo_sub_sub'), 'methodname': 'Method2', - 'params': [], }, + 'Params': [], }, {'return': 0, 'params': []}, CIMError, 'CIM_ERR_INVALID_NAMESPACE'], @@ -3786,7 +3808,7 @@ def fuzzy_callback(self, conn, methodname, object_name, params=None): CIMInstanceName('CIM_Foo_sub_sub', keybindings={'InstanceID': 'CIM_Foo_sub_sub21'}), 'methodname': 'Method1', - 'params': [], }, + 'Params': [], }, {'return': 0, 'params': []}, None, None], @@ -3795,43 +3817,73 @@ def fuzzy_callback(self, conn, methodname, object_name, params=None): keybindings={'InstanceID': 'blah'}), 'methodname': 'Method1', - 'params': [], }, + 'Params': [], }, {'return': 0, 'params': []}, CIMError, 'CIM_ERR_NOT_FOUND'], ['Execute with mathodname that is not in repository', {'object_name': CIMClassName('CIM_Foo_sub_sub'), 'methodname': 'Methodx', - 'params': [], }, + 'Params': [], }, {'return': 0, 'params': []}, CIMError, 'CIM_ERR_NOT_FOUND'], ['Execute method name with invalid classname', {'object_name': CIMClassName('CIM_Foo_sub_subx'), 'methodname': 'Method1', - 'params': [], }, + 'Params': [], }, {'return': 0, 'params': []}, CIMError, 'CIM_ERR_NOT_FOUND'], ['Execute objectname invalid type', {'object_name': CIMQualifierDeclaration('Key', 'string'), 'methodname': 'Method1', - 'params': [], }, + 'Params': [], }, {'return': 0, 'params': []}, TypeError, None], ['Execute Method2 with input param flag to cause exception', {'object_name': CIMClassName('CIM_Foo_sub_sub'), 'methodname': 'Method2', - 'params': [('TestCIMErrorException', 'CIM_ERR_FAILED')], }, + 'Params': [('TestCIMErrorException', 'CIM_ERR_FAILED')], }, {'return': 0, 'params': []}, CIMError, 'CIM_ERR_FAILED'], - ['Execute Fuzzy method', + + ['Execute Fuzzy method with simple input params', {'object_name': CIMClassName('CIM_Foo'), 'methodname': 'Fuzzy', - 'params': [('FuzzyParameter', 'Some data', )], }, - {'return': 0, 'params': [('OutputParam', 'Some data'), ]}, - CIMError, 'CIM_ERR_FAILED'], + 'Params': [], }, + {'return': 0, + 'params': [('OutputParam', 'Some data'), + ('foo', + CIMInstanceName('CIM_Foo', + {'InstanceID': 'CIM_F001'}))]}, + None, None], + + ['Execute Fuzzy method with where method call is for subclass', + {'object_name': CIMClassName('CIM_Foo_sub_sub'), + 'methodname': 'Fuzzy', + 'Params': [], }, + {'return': 0, + 'params': [('OutputParam', 'Some data'), + ('foo', + CIMInstanceName('CIM_Foo', + {'InstanceID': 'CIM_F001'}))]}, + None, None], + + ['Execute Fuzzy method with CIMInstanceName in input params', + {'object_name': CIMClassName('CIM_Foo'), + 'methodname': 'Fuzzy', + 'Params': [('FuzzyParameter', 'Some data'), + ('foo', + CIMInstanceName('CIM_Foo', + {'InstanceID': 'CIM_F001'}, ), )], }, + {'return': 0, + 'params': [('OutputParam', 'Some data'), + ('foo', + CIMInstanceName('CIM_Foo', + {'InstanceID': 'CIM_F001'}))]}, + None, None], ] ) def test_invokemethod(self, conn, ns, description, inputs, exp_output, @@ -3844,9 +3896,12 @@ def test_invokemethod(self, conn, ns, description, inputs, exp_output, # Save expected info so that callbacks can use in in returns and tests # pylint: disable=attribute-defined-outside-init + self.input_Params = inputs['Params'] # pylint: disable=invalid-name self.return_value = exp_output['return'] + + # provide for cases where 'params' does not exist. + self.input_params = inputs['params'] if 'params' in inputs else {} self.return_params = exp_output['params'] - self.input_params = inputs['params'] # Add to InvokeMethod callback methods in the class. conn.add_method_callback('CIM_Foo_sub_sub', 'Method1', @@ -3874,9 +3929,17 @@ def test_invokemethod(self, conn, ns, description, inputs, exp_output, return if not exp_exception: - result = conn.InvokeMethod(inputs['methodname'], - object_name, - Params=inputs['params']) + + if self.input_params: + result = conn.InvokeMethod(inputs['methodname'], + object_name, + inputs['Params'], + self.input_params) + else: + result = conn.InvokeMethod(inputs['methodname'], + object_name, + inputs['Params'],) + # Test return values and confirm correct method executed assert result[0] == exp_output['return'] @@ -3894,7 +3957,8 @@ def test_invokemethod(self, conn, ns, description, inputs, exp_output, with pytest.raises(exp_exception) as exec_info: conn.InvokeMethod(inputs['methodname'], object_name, - Params=inputs['params']) + inputs['Params'], ) + exc = exec_info.value if isinstance(exp_exception, CIMError): assert exc.status_code_name == exp_exc_data