diff --git a/comtypes/test/test_client.py b/comtypes/test/test_client.py index 3a75252c..9191d7f4 100644 --- a/comtypes/test/test_client.py +++ b/comtypes/test/test_client.py @@ -9,7 +9,8 @@ # create the typelib wrapper and import it comtypes.client.GetModule("scrrun.dll") -from comtypes.gen import Scripting +comtypes.client.GetModule("wbemdisp.tlb") +from comtypes.gen import Scripting, WbemScripting class Test_GetModule(ut.TestCase): @@ -218,6 +219,38 @@ def test_server_info(self): self.assertIsInstance(iuia.GetRootElement(), POINTER(IUIAutomationElement)) self.assertIsInstance(iuia.GetRootElement(), IUIAutomationElement) + def test_raises_valueerror_if_takes_dynamic_true_and_interface(self): + with self.assertRaises(ValueError): + comtypes.client.CreateObject( + "Scripting.Dictionary", + interface=Scripting.IDictionary, + dynamic=True, # type: ignore + ) + + +class Test_CoGetObject(ut.TestCase): + def test_returns_interface_pointer(self): + wmi = comtypes.client.CoGetObject( + "winmgmts:", interface=WbemScripting.ISWbemServices + ) + self.assertIsInstance(wmi, WbemScripting.ISWbemServices) + disks = wmi.InstancesOf("Win32_LogicalDisk") + self.assertGreaterEqual(len(disks), 0) + + def test_returns_dynamic_dispatch_object(self): + wmi = comtypes.client.CoGetObject("winmgmts:", dynamic=True) + self.assertIsInstance(wmi, comtypes.client.lazybind.Dispatch) + disks = wmi.InstancesOf("Win32_LogicalDisk") + self.assertGreaterEqual(disks.Count, 0) + + def test_raises_valueerror_if_takes_dynamic_true_and_interface(self): + with self.assertRaises(ValueError): + comtypes.client.CoGetObject( + "winmgmts:", + interface=WbemScripting.ISWbemServices, # type: ignore + dynamic=True, # type: ignore + ) + class Test_Constants(ut.TestCase): def test_punk(self): diff --git a/comtypes/test/test_client_dynamic.py b/comtypes/test/test_client_dynamic.py index 1825c0dc..a9e48bf9 100644 --- a/comtypes/test/test_client_dynamic.py +++ b/comtypes/test/test_client_dynamic.py @@ -1,40 +1,39 @@ import ctypes import unittest as ut -from unittest import mock -from comtypes import COMError, IUnknown, automation +from comtypes import GUID, COMError, IUnknown, automation, hresult, typeinfo from comtypes.client import CreateObject, GetModule, dynamic, lazybind class Test_Dispatch_Function(ut.TestCase): - # It is difficult to cause intentionally errors "in the regular way". - # So `mock` is used to cover conditional branches. - def test_returns_dynamic_Dispatch_if_takes_dynamic_Dispatch(self): - obj = mock.MagicMock(spec=dynamic._Dispatch) - self.assertIs(dynamic.Dispatch(obj), obj) - - def test_returns_lazybind_Dispatch_if_takes_ptrIDispatch(self): - # Conditional branches that return `lazybind.Dispatch` are also covered by - # `test_dyndispatch` and others. - obj = mock.MagicMock(spec=ctypes.POINTER(automation.IDispatch)) - self.assertIsInstance(dynamic.Dispatch(obj), lazybind.Dispatch) + def test_returns_lazybind_Dispatch(self): + # When `dynamic=True`, objects providing type information will return a + # `lazybind.Dispatch` instance. + orig = CreateObject("Scripting.Dictionary", interface=automation.IDispatch) + disp = dynamic.Dispatch(orig) + self.assertIsInstance(disp, lazybind.Dispatch) + # Calling `dynamic.Dispatch` with an already dispatched object should + # return the same instance. + self.assertIs(disp, dynamic.Dispatch(disp)) - def test_returns_dynamic_Dispatch_if_takes_ptrIDispatch_and_raised_comerr(self): - obj = mock.MagicMock(spec=ctypes.POINTER(automation.IDispatch)) - obj.GetTypeInfo.side_effect = COMError(0, "test", ("", "", "", 0, 0)) - self.assertIsInstance(dynamic.Dispatch(obj), dynamic._Dispatch) + def test_returns_dynamic_Dispatch(self): + # When `dynamic=True`, objects that do NOT provide type information (or + # fail to provide it) will return a `dynamic._Dispatch` instance. + orig = CreateObject( + "WindowsInstaller.Installer", interface=automation.IDispatch + ) + disp = dynamic.Dispatch(orig) + self.assertIsInstance(disp, dynamic._Dispatch) + # Calling `dynamic.Dispatch` on an already dispatched object should + # return the same instance. + self.assertIs(disp, dynamic.Dispatch(disp)) - def test_returns_dynamic_Dispatch_if_takes_ptrIDispatch_and_raised_winerr(self): - obj = mock.MagicMock(spec=ctypes.POINTER(automation.IDispatch)) - obj.GetTypeInfo.side_effect = OSError() - self.assertIsInstance(dynamic.Dispatch(obj), dynamic._Dispatch) - def test_returns_what_is_took_if_takes_other(self): - obj = object() - self.assertIs(dynamic.Dispatch(obj), obj) +HKCU = 1 # HKEY_CURRENT_USER +msiInstallStateUnknown = -1 -class Test_Dispatch_Class(ut.TestCase): +class Test_dynamic_Dispatch(ut.TestCase): # `MethodCaller` and `_Collection` are indirectly covered in this. def test_dict(self): # The following conditional branches are not covered; @@ -57,10 +56,82 @@ def test_dict(self): scr_dict = d.QueryInterface(scrrun.IDictionary) self.assertIsInstance(scr_dict, scrrun.IDictionary) d.Item["qux"] = scr_dict + # `dynamic._Dispatch` reflects the underlying COM object's behavior. + # For `Scripting.Dictionary`, out-of-bounds index access via `IDispatch` + # typically results in a `COMError`, which is wrapped as `IndexError`. with self.assertRaises(IndexError): d[4] with self.assertRaises(AttributeError): d.__foo__ + with self.assertRaises(COMError) as cm: + # Access a member that definitely does not exist. + _ = d.DefinitelyNonExistentMember + self.assertEqual(cm.exception.hresult, hresult.DISP_E_UNKNOWNNAME) + + def test_installer(self): + orig = CreateObject( + "WindowsInstaller.Installer", interface=automation.IDispatch + ) + installer = dynamic._Dispatch(orig) + # Access a known property and method + self.assertIsInstance(installer.Version, str) + self.assertTrue(installer.RegistryValue(HKCU, r"Control Panel\Desktop")) + # Test that calling `ProductState` as a method raises a `COMError` + with self.assertRaises(COMError): + installer.ProductState(str(GUID())) + # Test `ProductState` as an item access + self.assertEqual(msiInstallStateUnknown, installer.ProductState[str(GUID())]) + # Accessing a non-existent attribute should raise `AttributeError` + with self.assertRaises(AttributeError): + installer.__foo__ + + +class Test_lazybind_Dispatch(ut.TestCase): + def test_dict(self): + orig = CreateObject("Scripting.Dictionary", interface=automation.IDispatch) + tinfo = orig.GetTypeInfo(0) + d = lazybind.Dispatch(orig, tinfo) + d.CompareMode = 42 + d.Item["foo"] = 1 + d.Item["bar"] = "spam foo" + d.Item["baz"] = 3.14 + self.assertEqual(d.Item["foo"], 1) + self.assertEqual([k for k in iter(d)], ["foo", "bar", "baz"]) + self.assertIsInstance(hash(d), int) + # No `_FlagAsMethod` in `lazybind.Dispatch` + self.assertIs(type(d._NewEnum()), ctypes.POINTER(IUnknown)) + scrrun = GetModule("scrrun.dll") + scr_dict = d.QueryInterface(scrrun.IDictionary) + self.assertIsInstance(scr_dict, scrrun.IDictionary) + d.Item["qux"] = scr_dict + # `lazybind.Dispatch`, using type information, might return `None` for + # non-existent keys when accessed via direct index (`d[4]`), + # as it doesn't directly map to the `Item` property's error handling. + self.assertIsNone(d[4]) + with self.assertRaises(AttributeError): + d.__foo__ + with self.assertRaises(NameError): + # Access a member that definitely does not exist. + _ = d.DefinitelyNonExistentMember + + def test_installer(self): + IID_Installer = GUID("{000C1090-0000-0000-C000-000000000046}") + tlib = typeinfo.LoadTypeLibEx("msi.dll") + tinfo = tlib.GetTypeInfoOfGuid(IID_Installer) + orig = CreateObject( + "WindowsInstaller.Installer", interface=automation.IDispatch + ) + installer = lazybind.Dispatch(orig, tinfo) + # Access a known property and method + self.assertIsInstance(installer.Version, str) + self.assertTrue(installer.RegistryValue(HKCU, r"Control Panel\Desktop")) + # Test `ProductState` as a method call + self.assertEqual(msiInstallStateUnknown, installer.ProductState(str(GUID()))) + # Test `ProductState` as an item access + self.assertEqual(msiInstallStateUnknown, installer.ProductState[str(GUID())]) + # Accessing a non-existent attribute should raise `AttributeError` + with self.assertRaises(AttributeError): + installer.__foo__ if __name__ == "__main__": diff --git a/comtypes/test/test_getactiveobj.py b/comtypes/test/test_getactiveobj.py index 2dcee256..495e2c12 100644 --- a/comtypes/test/test_getactiveobj.py +++ b/comtypes/test/test_getactiveobj.py @@ -73,12 +73,22 @@ def test(self): class Test_MSVidCtlLib(unittest.TestCase): def test_register_and_revoke(self): + CLSID_MSVidCtl = msvidctl.MSVidCtl._reg_clsid_ vidctl = comtypes.client.CreateObject(msvidctl.MSVidCtl) with self.assertRaises(WindowsError): comtypes.client.GetActiveObject(msvidctl.MSVidCtl) handle = comtypes.client.RegisterActiveObject(vidctl, msvidctl.MSVidCtl) + with self.assertRaises(ValueError): + comtypes.client.GetActiveObject( + CLSID_MSVidCtl, + interface=msvidctl.IMSVidCtl, + dynamic=True, # type: ignore + ) activeobj = comtypes.client.GetActiveObject(msvidctl.MSVidCtl) self.assertEqual(vidctl, activeobj) + dynamicobj = comtypes.client.GetActiveObject(CLSID_MSVidCtl, dynamic=True) + self.assertIsInstance(dynamicobj, comtypes.client.lazybind.Dispatch) + self.assertEqual(hash(vidctl), hash(dynamicobj)) comtypes.client.RevokeActiveObject(handle) with self.assertRaises(WindowsError): comtypes.client.GetActiveObject(msvidctl.MSVidCtl)