Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion comtypes/test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
121 changes: 96 additions & 25 deletions comtypes/test/test_client_dynamic.py
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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__":
Expand Down
10 changes: 10 additions & 0 deletions comtypes/test/test_getactiveobj.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down