From eac707cad4281b6eba06493d0febcd548607a89f Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Thu, 11 Jan 2024 00:45:26 +0100 Subject: [PATCH] Fix old index bug in `call_with_inout` (#473) * bugfix in `call_with_inout` * minor cleanup * handling the case of no in and no out * Test case for _fix_inout_args * additional cleanup and error handling * code formatting fixed * fix python 3.7 and 3.8 compatibility * Temporary addition of real-world test * code cleanup * intermediate commit, do not review * Refactor of unit test, removing portdevice test * fix global side-effect of other skipped test * Update comtypes/test/test_outparam.py Co-authored-by: Jun Komoda <45822440+junkmd@users.noreply.github.com> * work on tests for inout_args and outparam - cleanup for test_outparam.py - improvements to test_inout_args.py - comments on a possible error in _memberspec.py * removing dead code * rename variables and add assertions * pass `MagicMock` instead of `ut.TestCase` * make tests for each argument passing patterns * remove duplicated comments * update test code for readability - remove name from mock - move line breaks to between mock preparations and assertions * split the testcases * add `Test_Error` * minor corrections, remove redundancy, migration - rewrite the permutations test - missing direction and omitted name redundant - migrate autogenerated keywords - TBD: more real life tests * Add tests covering 24 patterns - instead of using `if` statements and `permutations` * update test name * add real world tests, remove old code * formatting issue * Update comtypes/_memberspec.py dict type annotation Co-authored-by: Jun Komoda <45822440+junkmd@users.noreply.github.com> * Change missing 'in' or 'out' to be treated as 'in' * Add real-world test: param without 'in' or 'out' * add `contextlib.redirect_stdout` to supress warnings * apply review feedback * update comments * add line breaks to lines longer than 88 characters * Update comtypes/test/test_inout_args.py --------- Co-authored-by: jonschz Co-authored-by: Jonathan Co-authored-by: Jun Komoda <45822440+junkmd@users.noreply.github.com> (cherry picked from commit 876801f4061596bc744be1316fce0a2c58ab29a8) --- comtypes/_memberspec.py | 91 ++++-- comtypes/test/test_imfattributes.py | 29 ++ comtypes/test/test_inout_args.py | 490 ++++++++++++++++++++++++++++ comtypes/test/test_outparam.py | 6 +- 4 files changed, 584 insertions(+), 32 deletions(-) create mode 100644 comtypes/test/test_imfattributes.py create mode 100644 comtypes/test/test_inout_args.py diff --git a/comtypes/_memberspec.py b/comtypes/_memberspec.py index 7d2c7596..b2b80bf9 100644 --- a/comtypes/_memberspec.py +++ b/comtypes/_memberspec.py @@ -151,60 +151,93 @@ def _fix_inout_args( def call_with_inout(self, *args, **kw): args = list(args) # Indexed by order in the output - outargs = {} + outargs: Dict[int, _UnionT[_CData, "ctypes._CArgObject"]] = {} outnum = 0 + param_index = 0 + # Go through all expected arguments and match them to the provided arguments. + # `param_index` first counts through the positional and then + # through the keyword arguments. for i, info in enumerate(paramflags): direction = info[0] - if direction & 3 == 3: + dir_in = direction & 1 == 1 + dir_out = direction & 2 == 2 + is_positional = param_index < len(args) + if not (dir_in or dir_out): + # The original code here did not check for this special case and + # effectively treated `(dir_in, dir_out) == (False, False)` and + # `(dir_in, dir_out) == (True, False)` the same. + # In order not to break legacy code we do the same. + # One example of a function that has neither `dir_in` nor `dir_out` + # set is `IMFAttributes.GetString`. + dir_in = True + if dir_in and dir_out: # This is an [in, out] parameter. # # Determine name and required type of the parameter. name = info[1] # [in, out] parameters are passed as pointers, # this is the pointed-to type: - atyp = argtypes[i]._type_ + atyp: Type[_CData] = getattr(argtypes[i], "_type_") # Get the actual parameter, either as positional or # keyword arg. - try: - try: - v = args[i] - except IndexError: - v = kw[name] - except KeyError: - # no parameter was passed, make an empty one - # of the required type - v = atyp() - else: - # parameter was passed, call .from_param() to - # convert it to a ctypes type. + + def prepare_parameter(v): + # parameter was passed, call `from_param()` to + # convert it to a `ctypes` type. if getattr(v, "_type_", None) is atyp: - # Array of or pointer to type 'atyp' was - # passed, pointer to 'atyp' expected. + # Array of or pointer to type `atyp` was passed, + # pointer to `atyp` expected. pass elif type(atyp) is SIMPLETYPE: - # The from_param method of simple types - # (c_int, c_double, ...) returns a byref() - # object which we cannot use since later - # it will be wrapped in a pointer. Simply - # call the constructor with the argument - # in that case. + # The `from_param` method of simple types + # (`c_int`, `c_double`, ...) returns a `byref` object which + # we cannot use since later it will be wrapped in a pointer. + # Simply call the constructor with the argument in that case. v = atyp(v) else: v = atyp.from_param(v) assert not isinstance(v, BYREFTYPE) - outargs[outnum] = v - outnum += 1 - if len(args) > i: - args[i] = v - else: + return v + + if is_positional: + v = prepare_parameter(args[param_index]) + args[param_index] = v + elif name in kw: + v = prepare_parameter(kw[name]) kw[name] = v - elif direction & 2 == 2: + else: + # no parameter was passed, make an empty one of the required type + # and pass it as a keyword argument + v = atyp() + if name is not None: + kw[name] = v + else: + raise TypeError("Unnamed inout parameters cannot be omitted") + outargs[outnum] = v + if dir_out: outnum += 1 + if dir_in: + param_index += 1 + rescode = func(self, *args, **kw) # If there is only a single output value, then do not expect it to # be iterable. + + # Our interpretation of this code + # (jonschz, junkmd, see https://github.com/enthought/comtypes/pull/473): + # - `outnum` counts the total number of 'out' and 'inout' arguments. + # - `outargs` is a dict consisting of the supplied 'inout' arguments. + # - The call to `func()` returns the 'out' and 'inout' arguments. + # Furthermore, it changes the variables in 'outargs' as a "side effect" + # - In a perfect world, it should be fine to just return `rescode`. + # But we assume there is a reason why the original authors did not do that. + # Instead, they replace the 'inout' variables in `rescode` by those in + # 'outargs', and call `__ctypes_from_outparam__()` on them. + if outnum == 1: # rescode is not iterable + # In this case, it is little faster than creating list with + # `rescode = [rescode]` and getting item with index from the list. if len(outargs) == 1: rescode = rescode.__ctypes_from_outparam__() return rescode diff --git a/comtypes/test/test_imfattributes.py b/comtypes/test/test_imfattributes.py new file mode 100644 index 00000000..c3c2dd03 --- /dev/null +++ b/comtypes/test/test_imfattributes.py @@ -0,0 +1,29 @@ +import contextlib +import unittest as ut + +from ctypes import POINTER, pointer, windll +from comtypes import GUID +import comtypes.client + + +class Test_IMFAttributes(ut.TestCase): + def test_imfattributes(self): + with contextlib.redirect_stdout(None): # supress warnings, see test_client.py + comtypes.client.GetModule("msvidctl.dll") + from comtypes.gen import MSVidCtlLib + + imf_attrs = POINTER(MSVidCtlLib.IMFAttributes)() + hres = windll.mfplat.MFCreateAttributes(pointer(imf_attrs), 2) + self.assertEqual(hres, 0) + + MF_TRANSCODE_ADJUST_PROFILE = GUID("{9c37c21b-060f-487c-a690-80d7f50d1c72}") + set_int_value = 1 + # IMFAttributes.SetUINT32() is an example of a function that has a parameter + # without an `in` or `out` direction; see also test_inout_args.py + imf_attrs.SetUINT32(MF_TRANSCODE_ADJUST_PROFILE, set_int_value) + get_int_value = imf_attrs.GetUINT32(MF_TRANSCODE_ADJUST_PROFILE) + self.assertEqual(set_int_value, get_int_value) + + +if __name__ == "__main__": + ut.main() diff --git a/comtypes/test/test_inout_args.py b/comtypes/test/test_inout_args.py new file mode 100644 index 00000000..0a30c786 --- /dev/null +++ b/comtypes/test/test_inout_args.py @@ -0,0 +1,490 @@ +from typing import Any, Callable, List, NamedTuple, Tuple, Type +from ctypes import POINTER, pointer, Structure, HRESULT, c_ulong, c_wchar_p, c_int +import unittest as ut +from unittest.mock import MagicMock + +import comtypes +from comtypes.client import IUnknown +from comtypes._memberspec import _fix_inout_args, _ArgSpecElmType + +WSTRING = c_wchar_p + + +class Test_RealWorldExamples(ut.TestCase): + def test_IUrlHistoryStg(self): + class Mock_STATURL(Structure): + _fields_ = [] + + spec = comtypes.COMMETHOD( + [], + HRESULT, + "QueryUrl", + (["in"], WSTRING, "pocsUrl"), + (["in"], c_ulong, "dwFlags"), + (["in", "out"], POINTER(Mock_STATURL), "lpSTATURL"), + ) + orig = MagicMock() + orig.return_value = MagicMock(spec=Mock_STATURL, name="lpSTATURL") + fixed = _fix_inout_args(orig, spec.argtypes, spec.paramflags) + + self_ = MagicMock(name="Self") + pocs_url = "ghi" + dw_flags = 8 + lp_staturl = Mock_STATURL() + ret_val = fixed(self_, pocs_url, dw_flags, lp_staturl) + + # Here we encounter a quirk of _fix_inout_args: + # + # When the function has only one return value, + # _fix_inout_args will call __ctypes_from_outparam__ on the return value of the function. + # When there is more than one return value, + # _fix_inout_args will call __ctypes_from_outparam__ on the input inout parameters. + # + # This may be a bug, but due to backwards compatibility we won't change it + # unless someone can demonstrate that it causes problems. + + orig.assert_called_once_with(self_, pocs_url, dw_flags, lp_staturl) + + # Not too happy about using underscore attributes, but I didn't find a cleaner way + self.assertEqual(ret_val._mock_new_name, "()") + ret_val_parent = ret_val._mock_new_parent + self.assertEqual(ret_val_parent._mock_new_name, "__ctypes_from_outparam__") + self.assertIs(ret_val_parent._mock_new_parent, orig.return_value) + + # TODO Alternative: + self.assertEqual( + ret_val._extract_mock_name(), "lpSTATURL.__ctypes_from_outparam__()" + ) + + def test_IMoniker(self): + # memberspec of IMoniker.Reduce + spec = comtypes.COMMETHOD( + [], + HRESULT, + "Reduce", + (["in"], POINTER(IUnknown), "pbc"), + (["in"], c_ulong, "dwReduceHowFar"), + (["in", "out"], POINTER(POINTER(IUnknown)), "ppmkToLeft"), + (["out"], POINTER(POINTER(IUnknown)), "ppmkReduced"), + ) + orig = MagicMock() + ppmk_reduced = MagicMock(spec=POINTER(IUnknown), name="ppmkReduced") + orig.return_value = (..., ppmk_reduced) + fixed = _fix_inout_args(orig, spec.argtypes, spec.paramflags) + + self_ = MagicMock(name="Self") + pbc = POINTER(IUnknown)() + dw_reduce_how_far = 15 + ppmk_to_left = POINTER(IUnknown)() + ret_val = fixed(self_, pbc, dw_reduce_how_far, ppmk_to_left) + + orig.assert_called_once() + # (orig_0th, orig_1st, orig_2nd, orig_3rd, orig_4th), orig_kw = orig.call_args + + self.assertEqual(orig.call_args[1], {}) + self.assertTupleEqual( + orig.call_args[0], (self_, pbc, dw_reduce_how_far, ppmk_to_left) + ) + self.assertListEqual(ret_val, [ppmk_to_left, ppmk_reduced]) + + def test_IPin(self): + spec = comtypes.COMMETHOD( + [], + HRESULT, + "QueryInternalConnections", + (["out"], POINTER(POINTER(IUnknown)), "apPin"), # IPin + (["in", "out"], POINTER(c_ulong), "nPin"), + ) + orig = MagicMock() + apPin = MagicMock(spec=POINTER(IUnknown), name="apPin") + orig.return_value = (apPin, ...) + fixed = _fix_inout_args(orig, spec.argtypes, spec.paramflags) + + self_ = MagicMock(name="Self") + # test passing in a pointer of the right type + n_pin = pointer(c_ulong(26)) + ret_val = fixed(self_, n_pin) + + orig.assert_called_once_with(self_, n_pin) + self.assertEqual(orig.call_args[1], {}) + self.assertListEqual(ret_val, [apPin, n_pin]) + + def test_IMFAttributes(self): + self_ = MagicMock(name="Self") + # a memberspec of `MSVidCtlLib.IMFAttributes` + # Notably, for the first parameters, neither 'in' nor 'out' is specified. + # For compatibility with legacy code this should be treated as 'in'. + spec = comtypes.COMMETHOD( + [], + HRESULT, + "GetItemType", + ([], POINTER(comtypes.GUID), "guidKey"), + (["out"], POINTER(c_int), "pType"), + ) + orig = MagicMock(__name__="orig") + guidKey = comtypes.GUID("{00000000-0000-0000-0000-000000000000}") + pType = 4 + orig.return_value = pType + fixed = _fix_inout_args(orig, spec.argtypes, spec.paramflags) + ret_val = fixed(self_, guidKey) + + orig.assert_called_once_with(self_, guidKey) + self.assertEqual(ret_val, pType) + + +class Test_ArgsKwargsCombinations(ut.TestCase): + def setUp(self): + # a memberspec of `PortableDeviceApiLib.IPortableDeviceContent` + spec = comtypes.COMMETHOD( + [], + HRESULT, + "CreateObjectWithPropertiesAndData", + (["in"], POINTER(IUnknown), "pValues"), # IPortableDeviceValues + (["out"], POINTER(POINTER(IUnknown)), "ppData"), # IStream + (["in", "out"], POINTER(c_ulong), "pdwOptimalWriteBufferSize"), + (["in", "out"], POINTER(WSTRING), "ppszCookie"), + ) + self.orig = MagicMock() + self.fixed = _fix_inout_args(self.orig, spec.argtypes, spec.paramflags) + # out and inout argument are dereferenced once before being returned + self.pp_data = POINTER(IUnknown)() + self.orig.return_value = (self.pp_data, ..., ...) + + def test_positionals_only(self): + self_ = MagicMock(name="Self") + p_val = MagicMock(spec=POINTER(IUnknown))() + buf_size = 5 + cookie = "abc" + ret_val = self.fixed(self_, p_val, buf_size, cookie) + + self.assertEqual(ret_val, [self.pp_data, buf_size, cookie]) + self.orig.assert_called_once() + (orig_0th, orig_1st, orig_2nd, orig_3rd), orig_kw = self.orig.call_args + self.assertIs(orig_0th, self_) + self.assertEqual(orig_1st, p_val) + self.assertIsInstance(orig_2nd, c_ulong) + self.assertEqual(orig_2nd.value, buf_size) + self.assertIsInstance(orig_3rd, WSTRING) + self.assertEqual(orig_3rd.value, cookie) + self.assertEqual(orig_kw, {}) + + def test_keywords_only(self): + self_ = MagicMock(name="Self") + p_val = MagicMock(spec=POINTER(IUnknown))() + buf_size = 4 + cookie = "efg" + ret_val = self.fixed( + self_, pValues=p_val, pdwOptimalWriteBufferSize=buf_size, ppszCookie=cookie + ) + + self.assertEqual(ret_val, [self.pp_data, buf_size, cookie]) + self.orig.assert_called_once() + (orig_0th,), orig_kw = self.orig.call_args + self.assertIs(orig_0th, self_) + self.assertEqual( + set(orig_kw), {"pValues", "pdwOptimalWriteBufferSize", "ppszCookie"} + ) + self.assertEqual(orig_kw["pValues"], p_val) + self.assertIsInstance(orig_kw["pdwOptimalWriteBufferSize"], c_ulong) + self.assertEqual(orig_kw["pdwOptimalWriteBufferSize"].value, buf_size) + self.assertIsInstance(orig_kw["ppszCookie"], WSTRING) + self.assertEqual(orig_kw["ppszCookie"].value, cookie) + + def test_mixed_args_1(self): + self_ = MagicMock(name="Self") + p_val = MagicMock(spec=POINTER(IUnknown))() + buf_size = 3 + cookie = "h" + ret_val = self.fixed( + self_, p_val, ppszCookie=cookie, pdwOptimalWriteBufferSize=buf_size + ) + + self.assertEqual(ret_val, [self.pp_data, buf_size, cookie]) + self.orig.assert_called_once() + (orig_0th, orig_1st), orig_kw = self.orig.call_args + self.assertIs(orig_0th, self_) + self.assertEqual(orig_1st, p_val) + self.assertEqual(set(orig_kw), {"pdwOptimalWriteBufferSize", "ppszCookie"}) + self.assertIsInstance(orig_kw["pdwOptimalWriteBufferSize"], c_ulong) + self.assertEqual(orig_kw["pdwOptimalWriteBufferSize"].value, buf_size) + self.assertIsInstance(orig_kw["ppszCookie"], WSTRING) + self.assertEqual(orig_kw["ppszCookie"].value, cookie) + + def test_mixed_args_2(self): + self_ = MagicMock(name="Self") + p_val = MagicMock(spec=POINTER(IUnknown))() + buf_size = 2 + cookie = "ij" + ret_val = self.fixed(self_, p_val, buf_size, ppszCookie=cookie) + + self.assertEqual(ret_val, [self.pp_data, buf_size, cookie]) + self.orig.assert_called_once() + (orig_0th, orig_1st, orig_2nd), orig_kw = self.orig.call_args + self.assertIs(orig_0th, self_) + self.assertEqual(orig_1st, p_val) + self.assertEqual(set(orig_kw), {"ppszCookie"}) + self.assertIsInstance(orig_2nd, c_ulong) + self.assertEqual(orig_2nd.value, buf_size) + self.assertIsInstance(orig_kw["ppszCookie"], WSTRING) + self.assertEqual(orig_kw["ppszCookie"].value, cookie) + + def test_omitted_arguments_autogen(self): + self_ = MagicMock(name="Self") + p_val = MagicMock(spec=POINTER(IUnknown))() + ret_val = self.fixed(self_, pValues=p_val) + + self.orig.assert_called_once() + (orig_0th,), orig_kw = self.orig.call_args + self.assertEqual( + set(orig_kw), {"pValues", "pdwOptimalWriteBufferSize", "ppszCookie"} + ) + self.assertIs(orig_0th, self_) + self.assertIs(orig_kw["pValues"], p_val) + self.assertIsInstance(orig_kw["pdwOptimalWriteBufferSize"], c_ulong) + self.assertEqual(orig_kw["pdwOptimalWriteBufferSize"].value, c_ulong().value) + self.assertIsInstance(orig_kw["ppszCookie"], WSTRING) + self.assertEqual(orig_kw["ppszCookie"].value, WSTRING().value) + + self.assertEqual( + ret_val, + [ + self.pp_data, + orig_kw["pdwOptimalWriteBufferSize"].value, + orig_kw["ppszCookie"].value, + ], + ) + + +class PermutedArgspecTestingParams(NamedTuple): + argspec: Tuple[_ArgSpecElmType, _ArgSpecElmType, _ArgSpecElmType, _ArgSpecElmType] + args: Tuple[Any, Any, Any] + orig_ret_val: Tuple[Any, Any, Any] + fixed_ret_val: List[Any] + call_args_validators: List[Tuple[Type[Any], Callable[[Any], Any], Any]] + + +class Test_ArgspecPermutations(ut.TestCase): + def test_permutations(self): + for testing_params in self._get_params(): + with self.subTest(testing_params.argspec): + self_ = MagicMock(name="Self") + orig = MagicMock(return_value=testing_params.orig_ret_val) + spec = comtypes.COMMETHOD([], HRESULT, "foo", *testing_params.argspec) + fixed = _fix_inout_args(orig, spec.argtypes, spec.paramflags) + ret_val = fixed(self_, *testing_params.args) + + self.assertEqual(ret_val, testing_params.fixed_ret_val) + orig.assert_called_once() + (orig_0th, *orig_call_args), orig_kw = orig.call_args + self.assertEqual(orig_kw, {}) + self.assertIs(orig_0th, self_) + for ((typ, f, val), orig) in zip( + testing_params.call_args_validators, orig_call_args + ): + self.assertIsInstance(orig, typ) + self.assertEqual(f(orig), val) + + def _get_params(self) -> List[PermutedArgspecTestingParams]: + in_ = MagicMock(spec=POINTER(IUnknown))() + out = POINTER(IUnknown)() + inout1 = 5 + inout2 = "abc" + IN_ARGSPEC = (["in"], POINTER(comtypes.IUnknown), "in") + OUT_ARGSPEC = (["out"], POINTER(POINTER(comtypes.IUnknown)), "out") + INOUT1_ARGSPEC = (["in", "out"], POINTER(c_ulong), "inout1") + INOUT2_ARGSPEC = (["in", "out"], POINTER(WSTRING), "inout2") + IN_VALIDATOR = (MagicMock, lambda x: x, in_) + INOUT1_VALIDATOR = (c_ulong, lambda x: x.value, inout1) + INOUT2_VALIDATOR = (WSTRING, lambda x: x.value, inout2) + return [ + PermutedArgspecTestingParams( + (IN_ARGSPEC, OUT_ARGSPEC, INOUT1_ARGSPEC, INOUT2_ARGSPEC), + (in_, inout1, inout2), + (out, ..., ...), + [out, inout1, inout2], + [IN_VALIDATOR, INOUT1_VALIDATOR, INOUT2_VALIDATOR], + ), + PermutedArgspecTestingParams( + (IN_ARGSPEC, OUT_ARGSPEC, INOUT2_ARGSPEC, INOUT1_ARGSPEC), + (in_, inout2, inout1), + (out, ..., ...), + [out, inout2, inout1], + [IN_VALIDATOR, INOUT2_VALIDATOR, INOUT1_VALIDATOR], + ), + PermutedArgspecTestingParams( + (IN_ARGSPEC, INOUT1_ARGSPEC, OUT_ARGSPEC, INOUT2_ARGSPEC), + (in_, inout1, inout2), + (..., out, ...), + [inout1, out, inout2], + [IN_VALIDATOR, INOUT1_VALIDATOR, INOUT2_VALIDATOR], + ), + PermutedArgspecTestingParams( + (IN_ARGSPEC, INOUT1_ARGSPEC, INOUT2_ARGSPEC, OUT_ARGSPEC), + (in_, inout1, inout2), + (..., ..., out), + [inout1, inout2, out], + [IN_VALIDATOR, INOUT1_VALIDATOR, INOUT2_VALIDATOR], + ), + PermutedArgspecTestingParams( + (IN_ARGSPEC, INOUT2_ARGSPEC, OUT_ARGSPEC, INOUT1_ARGSPEC), + (in_, inout2, inout1), + (..., out, ...), + [inout2, out, inout1], + [IN_VALIDATOR, INOUT2_VALIDATOR, INOUT1_VALIDATOR], + ), + PermutedArgspecTestingParams( + (IN_ARGSPEC, INOUT2_ARGSPEC, INOUT1_ARGSPEC, OUT_ARGSPEC), + (in_, inout2, inout1), + (..., ..., out), + [inout2, inout1, out], + [IN_VALIDATOR, INOUT2_VALIDATOR, INOUT1_VALIDATOR], + ), + PermutedArgspecTestingParams( + (OUT_ARGSPEC, IN_ARGSPEC, INOUT1_ARGSPEC, INOUT2_ARGSPEC), + (in_, inout1, inout2), + (out, ..., ...), + [out, inout1, inout2], + [IN_VALIDATOR, INOUT1_VALIDATOR, INOUT2_VALIDATOR], + ), + PermutedArgspecTestingParams( + (OUT_ARGSPEC, IN_ARGSPEC, INOUT2_ARGSPEC, INOUT1_ARGSPEC), + (in_, inout2, inout1), + (out, ..., ...), + [out, inout2, inout1], + [IN_VALIDATOR, INOUT2_VALIDATOR, INOUT1_VALIDATOR], + ), + PermutedArgspecTestingParams( + (OUT_ARGSPEC, INOUT1_ARGSPEC, IN_ARGSPEC, INOUT2_ARGSPEC), + (inout1, in_, inout2), + (out, ..., ...), + [out, inout1, inout2], + [INOUT1_VALIDATOR, IN_VALIDATOR, INOUT2_VALIDATOR], + ), + PermutedArgspecTestingParams( + (OUT_ARGSPEC, INOUT1_ARGSPEC, INOUT2_ARGSPEC, IN_ARGSPEC), + (inout1, inout2, in_), + (out, ..., ...), + [out, inout1, inout2], + [INOUT1_VALIDATOR, INOUT2_VALIDATOR, IN_VALIDATOR], + ), + PermutedArgspecTestingParams( + (OUT_ARGSPEC, INOUT2_ARGSPEC, IN_ARGSPEC, INOUT1_ARGSPEC), + (inout2, in_, inout1), + (out, ..., ...), + [out, inout2, inout1], + [INOUT2_VALIDATOR, IN_VALIDATOR, INOUT1_VALIDATOR], + ), + PermutedArgspecTestingParams( + (OUT_ARGSPEC, INOUT2_ARGSPEC, INOUT1_ARGSPEC, IN_ARGSPEC), + (inout2, inout1, in_), + (out, ..., ...), + [out, inout2, inout1], + [INOUT2_VALIDATOR, INOUT1_VALIDATOR, IN_VALIDATOR], + ), + PermutedArgspecTestingParams( + (INOUT1_ARGSPEC, IN_ARGSPEC, OUT_ARGSPEC, INOUT2_ARGSPEC), + (inout1, in_, inout2), + (..., out, ...), + [inout1, out, inout2], + [INOUT1_VALIDATOR, IN_VALIDATOR, INOUT2_VALIDATOR], + ), + PermutedArgspecTestingParams( + (INOUT1_ARGSPEC, IN_ARGSPEC, INOUT2_ARGSPEC, OUT_ARGSPEC), + (inout1, in_, inout2), + (..., ..., out), + [inout1, inout2, out], + [INOUT1_VALIDATOR, IN_VALIDATOR, INOUT2_VALIDATOR], + ), + PermutedArgspecTestingParams( + (INOUT1_ARGSPEC, OUT_ARGSPEC, IN_ARGSPEC, INOUT2_ARGSPEC), + (inout1, in_, inout2), + (..., out, ...), + [inout1, out, inout2], + [INOUT1_VALIDATOR, IN_VALIDATOR, INOUT2_VALIDATOR], + ), + PermutedArgspecTestingParams( + (INOUT1_ARGSPEC, OUT_ARGSPEC, INOUT2_ARGSPEC, IN_ARGSPEC), + (inout1, inout2, in_), + (..., out, ...), + [inout1, out, inout2], + [INOUT1_VALIDATOR, INOUT2_VALIDATOR, IN_VALIDATOR], + ), + PermutedArgspecTestingParams( + (INOUT1_ARGSPEC, INOUT2_ARGSPEC, IN_ARGSPEC, OUT_ARGSPEC), + (inout1, inout2, in_), + (..., ..., out), + [inout1, inout2, out], + [INOUT1_VALIDATOR, INOUT2_VALIDATOR, IN_VALIDATOR], + ), + PermutedArgspecTestingParams( + (INOUT1_ARGSPEC, INOUT2_ARGSPEC, OUT_ARGSPEC, IN_ARGSPEC), + (inout1, inout2, in_), + (..., ..., out), + [inout1, inout2, out], + [INOUT1_VALIDATOR, INOUT2_VALIDATOR, IN_VALIDATOR], + ), + PermutedArgspecTestingParams( + (INOUT2_ARGSPEC, IN_ARGSPEC, OUT_ARGSPEC, INOUT1_ARGSPEC), + (inout2, in_, inout1), + (..., out, ...), + [inout2, out, inout1], + [INOUT2_VALIDATOR, IN_VALIDATOR, INOUT1_VALIDATOR], + ), + PermutedArgspecTestingParams( + (INOUT2_ARGSPEC, IN_ARGSPEC, INOUT1_ARGSPEC, OUT_ARGSPEC), + (inout2, in_, inout1), + (..., ..., out), + [inout2, inout1, out], + [INOUT2_VALIDATOR, IN_VALIDATOR, INOUT1_VALIDATOR], + ), + PermutedArgspecTestingParams( + (INOUT2_ARGSPEC, OUT_ARGSPEC, IN_ARGSPEC, INOUT1_ARGSPEC), + (inout2, in_, inout1), + (..., out, ...), + [inout2, out, inout1], + [INOUT2_VALIDATOR, IN_VALIDATOR, INOUT1_VALIDATOR], + ), + PermutedArgspecTestingParams( + (INOUT2_ARGSPEC, OUT_ARGSPEC, INOUT1_ARGSPEC, IN_ARGSPEC), + (inout2, inout1, in_), + (..., out, ...), + [inout2, out, inout1], + [INOUT2_VALIDATOR, INOUT1_VALIDATOR, IN_VALIDATOR], + ), + PermutedArgspecTestingParams( + (INOUT2_ARGSPEC, INOUT1_ARGSPEC, IN_ARGSPEC, OUT_ARGSPEC), + (inout2, inout1, in_), + (..., ..., out), + [inout2, inout1, out], + [INOUT2_VALIDATOR, INOUT1_VALIDATOR, IN_VALIDATOR], + ), + PermutedArgspecTestingParams( + (INOUT2_ARGSPEC, INOUT1_ARGSPEC, OUT_ARGSPEC, IN_ARGSPEC), + (inout2, inout1, in_), + (..., ..., out), + [inout2, inout1, out], + [INOUT2_VALIDATOR, INOUT1_VALIDATOR, IN_VALIDATOR], + ), + ] + + +class Test_Error(ut.TestCase): + def test_missing_name_omitted(self): + self_ = MagicMock(name="Self") + spec = comtypes.COMMETHOD( + [], + HRESULT, + "Foo", + (["in", "out"], POINTER(c_ulong)), + ) + orig = MagicMock() + fixed = _fix_inout_args(orig, spec.argtypes, spec.paramflags) + with self.assertRaises(TypeError) as cm: + fixed(self_) + self.assertEqual( + str(cm.exception), "Unnamed inout parameters cannot be omitted" + ) + + +if __name__ == "__main__": + ut.main() diff --git a/comtypes/test/test_outparam.py b/comtypes/test/test_outparam.py index de349670..6f743c5f 100644 --- a/comtypes/test/test_outparam.py +++ b/comtypes/test/test_outparam.py @@ -1,6 +1,7 @@ import sys import unittest from ctypes import * +from unittest.mock import patch import comtypes.test @@ -41,9 +42,6 @@ def from_outparm(self): return result -c_wchar_p.__ctypes_from_outparam__ = from_outparm - - def comstring(text, typ=c_wchar_p): text = text_type(text) size = (len(text) + 1) * sizeof(c_wchar) @@ -56,6 +54,8 @@ def comstring(text, typ=c_wchar_p): class Test(unittest.TestCase): @unittest.skip("This fails for reasons I don't understand yet") + # TODO untested changes; this was modified because it had global effects on other tests + @patch.object(c_wchar_p, "__ctypes_from_outparam__", from_outparm) def test_c_char(self): # ptr = c_wchar_p("abc") # self.failUnlessEqual(ptr.__ctypes_from_outparam__(),