From 1e451b0213016be446644d698aee6e4210e9275d Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 7 Mar 2026 23:36:09 +0900 Subject: [PATCH 1/9] refactor: Move `gc` and `time` imports to top level in `test_eventinterface.py`. - Relocate `import gc` and `import time` to the top of the file. - Removes redundant inline imports from `tearDown` and test methods. --- comtypes/test/test_eventinterface.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/comtypes/test/test_eventinterface.py b/comtypes/test/test_eventinterface.py index 9bfca80d..4651a936 100644 --- a/comtypes/test/test_eventinterface.py +++ b/comtypes/test/test_eventinterface.py @@ -1,3 +1,5 @@ +import gc +import time import unittest as ut from ctypes import byref from ctypes.wintypes import MSG @@ -57,11 +59,7 @@ def PumpWaitingMessages(): class Test(ut.TestCase): def tearDown(self): - import gc - gc.collect() - import time - time.sleep(2) @ut.skip( @@ -74,7 +72,6 @@ def test_default_eventinterface(self): conn = GetEvents(ie, sink=sink) ie.Visible = True ie.Navigate2(URL="http://docs.python.org/", Flags=0) - import time for i in range(50): PumpWaitingMessages() @@ -109,7 +106,6 @@ def test_nondefault_eventinterface(self): ie.Visible = True ie.Navigate2(Flags=0, URL="http://docs.python.org/") - import time for i in range(50): PumpWaitingMessages() From f2f3b74fa179106724118e515604fc39f6e2b710 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 7 Mar 2026 23:36:09 +0900 Subject: [PATCH 2/9] test: Replace `InternetExplorer` with `MSXML` in `test_eventinterface.py`. - Replaces `InternetExplorer.Application` with `Msxml2.DOMDocument`. - Introduces `IPropertyNotifySink` and `MSXMLDocumentSink` for testing. - Updates test methods to verify default and non-default event interfaces using `MSXML`. --- comtypes/test/test_eventinterface.py | 108 ++++++++++----------------- 1 file changed, 41 insertions(+), 67 deletions(-) diff --git a/comtypes/test/test_eventinterface.py b/comtypes/test/test_eventinterface.py index 4651a936..c5055a73 100644 --- a/comtypes/test/test_eventinterface.py +++ b/comtypes/test/test_eventinterface.py @@ -1,9 +1,11 @@ import gc import time import unittest as ut -from ctypes import byref +from ctypes import HRESULT, byref from ctypes.wintypes import MSG +from comtypes import COMMETHOD, GUID, IUnknown +from comtypes.automation import DISPID from comtypes.client import CreateObject, GetEvents from comtypes.messageloop import ( PM_REMOVE, @@ -12,42 +14,35 @@ TranslateMessage, ) -# FIXME: External test dependencies like this seem bad. Find a different -# built-in win32 API to use. # The primary goal is to verify how `GetEvents` behaves when the # `interface` argument is explicitly specified versus when it is omitted, # using an object that has multiple outgoing event interfaces. -class EventSink: - def __init__(self): - self._events = [] +class IPropertyNotifySink(IUnknown): + # https://learn.microsoft.com/en-us/windows/win32/api/ocidl/nn-ocidl-ipropertynotifysink + _iid_ = GUID("{9BFBBC02-EFF1-101A-84ED-00AA00341D07}") + _methods_ = [ + COMMETHOD([], HRESULT, "OnChanged", (["in"], DISPID, "dispid")), + COMMETHOD([], HRESULT, "OnRequestEdit", (["in"], DISPID, "dispid")), + ] - # some DWebBrowserEvents - def OnVisible(self, this, *args): - # print "OnVisible", args - self._events.append("OnVisible") - def BeforeNavigate(self, this, *args): - # print "BeforeNavigate", args - self._events.append("BeforeNavigate") +class MSXMLDocumentSink: + def __init__(self): + self._events = [] - def NavigateComplete(self, this, *args): - # print "NavigateComplete", args - self._events.append("NavigateComplete") + def onreadystatechange(self, this, *args): + self._events.append("onreadystatechange") - # some DWebBrowserEvents2 - def BeforeNavigate2(self, this, *args): - # print "BeforeNavigate2", args - self._events.append("BeforeNavigate2") + def ondataavailable(self, this, *args): + self._events.append("ondataavailable") - def NavigateComplete2(self, this, *args): - # print "NavigateComplete2", args - self._events.append("NavigateComplete2") + def OnChanged(self, this, *args): + self._events.append("OnChanged") - def DocumentComplete(self, this, *args): - # print "DocumentComplete", args - self._events.append("DocumentComplete") + def OnRequestEdit(self, this, *args): + self._events.append("OnRequestEdit") def PumpWaitingMessages(): @@ -57,64 +52,43 @@ def PumpWaitingMessages(): DispatchMessage(byref(msg)) -class Test(ut.TestCase): +class Test_MSXML(ut.TestCase): + def setUp(self): + self.doc = CreateObject("Msxml2.DOMDocument") + self.doc.async_ = True + def tearDown(self): + del self.doc gc.collect() time.sleep(2) - @ut.skip( - "External test dependencies like this seem bad. Find a different built-in " - "win32 API to use." - ) def test_default_eventinterface(self): - sink = EventSink() - ie = CreateObject("InternetExplorer.Application") - conn = GetEvents(ie, sink=sink) - ie.Visible = True - ie.Navigate2(URL="http://docs.python.org/", Flags=0) + sink = MSXMLDocumentSink() + conn = GetEvents(self.doc, sink) + self.doc.loadXML("") - for i in range(50): + for _ in range(50): PumpWaitingMessages() time.sleep(0.1) - ie.Visible = False - ie.Quit() - - self.assertEqual( - sink._events, - [ - "OnVisible", - "BeforeNavigate2", - "NavigateComplete2", - "DocumentComplete", - "OnVisible", - ], - ) - - del ie + self.assertIn("onreadystatechange", sink._events) + self.assertNotIn("OnChanged", sink._events) + del conn - @ut.skip( - "External test dependencies like this seem bad. Find a different built-in " - "win32 API to use." - ) def test_nondefault_eventinterface(self): - sink = EventSink() - ie = CreateObject("InternetExplorer.Application") - import comtypes.gen.SHDocVw as mod + sink = MSXMLDocumentSink() - conn = GetEvents(ie, sink, interface=mod.DWebBrowserEvents) + conn = GetEvents(self.doc, sink, interface=IPropertyNotifySink) - ie.Visible = True - ie.Navigate2(Flags=0, URL="http://docs.python.org/") + self.doc.loadXML("") - for i in range(50): + for _ in range(50): PumpWaitingMessages() time.sleep(0.1) - ie.Visible = False - ie.Quit() + self.assertNotIn("onreadystatechange", sink._events) + self.assertIn("OnChanged", sink._events) - self.assertEqual(sink._events, ["BeforeNavigate", "NavigateComplete"]) - del ie + del conn if __name__ == "__main__": From 27d22d5c4f0289fe08757f65a753bf27825cb0f5 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 7 Mar 2026 23:36:09 +0900 Subject: [PATCH 3/9] test: Add comments to `IPropertyNotifySink` methods in `test_eventinterface.py`. - Document `OnChanged` and `OnRequestEdit` methods in `IPropertyNotifySink` with descriptions from official documentation to improve code clarity. --- comtypes/test/test_eventinterface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/comtypes/test/test_eventinterface.py b/comtypes/test/test_eventinterface.py index c5055a73..a0dce0c8 100644 --- a/comtypes/test/test_eventinterface.py +++ b/comtypes/test/test_eventinterface.py @@ -23,7 +23,9 @@ class IPropertyNotifySink(IUnknown): # https://learn.microsoft.com/en-us/windows/win32/api/ocidl/nn-ocidl-ipropertynotifysink _iid_ = GUID("{9BFBBC02-EFF1-101A-84ED-00AA00341D07}") _methods_ = [ + # Called when a property has changed. COMMETHOD([], HRESULT, "OnChanged", (["in"], DISPID, "dispid")), + # Called when an object wants to know if it's okay to change a property. COMMETHOD([], HRESULT, "OnRequestEdit", (["in"], DISPID, "dispid")), ] From ea8726f371bf3f780afed15aee2d23b8ec1fe8b3 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 7 Mar 2026 23:36:09 +0900 Subject: [PATCH 4/9] test: Document event sink methods in `test_eventinterface.py`. - Add descriptive comments to `IPropertyNotifySink` and `MSXMLDocumentSink` methods. - Clarify whether methods originate from the default dispatch interface or the `IPropertyNotifySink` interface. --- comtypes/test/test_eventinterface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/comtypes/test/test_eventinterface.py b/comtypes/test/test_eventinterface.py index a0dce0c8..f11c7eaf 100644 --- a/comtypes/test/test_eventinterface.py +++ b/comtypes/test/test_eventinterface.py @@ -34,12 +34,14 @@ class MSXMLDocumentSink: def __init__(self): self._events = [] + # Events from the default dispatch interface def onreadystatechange(self, this, *args): self._events.append("onreadystatechange") def ondataavailable(self, this, *args): self._events.append("ondataavailable") + # Events from `IPropertyNotifySink` def OnChanged(self, this, *args): self._events.append("OnChanged") From e0a5b9f5131b7a9f2b5f04467f263a2595f059fa Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 7 Mar 2026 23:36:09 +0900 Subject: [PATCH 5/9] test: Add comment explaining `MSXML` usage in `test_eventinterface.py`. - Clarify in `Test_MSXML.setUp` why `Msxml2.DOMDocument` is chosen as the test object, highlighting its support for multiple connection points. --- comtypes/test/test_eventinterface.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/comtypes/test/test_eventinterface.py b/comtypes/test/test_eventinterface.py index f11c7eaf..5ec64bd3 100644 --- a/comtypes/test/test_eventinterface.py +++ b/comtypes/test/test_eventinterface.py @@ -58,6 +58,9 @@ def PumpWaitingMessages(): class Test_MSXML(ut.TestCase): def setUp(self): + # We use `Msxml2.DOMDocument` because it is a built-in Windows + # component that supports both a default source interface and the + # `IPropertyNotifySink` connection point. self.doc = CreateObject("Msxml2.DOMDocument") self.doc.async_ = True From 3b8edc2e70e1d907238da27274d49c9b20888fad Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 7 Mar 2026 23:36:10 +0900 Subject: [PATCH 6/9] test: Document `GetEvents` usage in `test_eventinterface.py`. - Add comments to `Test_MSXML` methods explaining the behavior of `GetEvents` when connecting to default vs. non-default source interfaces. --- comtypes/test/test_eventinterface.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/comtypes/test/test_eventinterface.py b/comtypes/test/test_eventinterface.py index 5ec64bd3..0c054350 100644 --- a/comtypes/test/test_eventinterface.py +++ b/comtypes/test/test_eventinterface.py @@ -70,6 +70,9 @@ def tearDown(self): time.sleep(2) def test_default_eventinterface(self): + # Verify that `GetEvents` automatically connects to the default source + # interface (dispatch events like `onreadystatechange`) when no + # interface is explicitly requested. sink = MSXMLDocumentSink() conn = GetEvents(self.doc, sink) self.doc.loadXML("") @@ -83,6 +86,8 @@ def test_default_eventinterface(self): del conn def test_nondefault_eventinterface(self): + # Verify that `GetEvents` can connect to a non-default interface + # (like `IPropertyNotifySink`) when it is explicitly provided. sink = MSXMLDocumentSink() conn = GetEvents(self.doc, sink, interface=IPropertyNotifySink) From eafb3863825d9dd1a9c80ef15a81e6fa3cbcbfb0 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 7 Mar 2026 23:36:10 +0900 Subject: [PATCH 7/9] test: Document resource cleanup in `test_eventinterface.py`. - Add a comment explaining the use of `gc.collect()` and `time.sleep()` in `Test_MSXML.tearDown` to ensure reliable COM resource release. --- comtypes/test/test_eventinterface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/comtypes/test/test_eventinterface.py b/comtypes/test/test_eventinterface.py index 0c054350..6a603f55 100644 --- a/comtypes/test/test_eventinterface.py +++ b/comtypes/test/test_eventinterface.py @@ -66,6 +66,8 @@ def setUp(self): def tearDown(self): del self.doc + # Force garbage collection and wait slightly to ensure COM resources + # are released properly between tests. gc.collect() time.sleep(2) From 2253fcc0897eed706bf63598d02c08d66d66d269 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 7 Mar 2026 23:36:10 +0900 Subject: [PATCH 8/9] test: Document message loop and event expectations in `test_eventinterface.py`. - Clarify message loop processing during event wait. - Document expected event arrivals for default vs. non-default interfaces. --- comtypes/test/test_eventinterface.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/comtypes/test/test_eventinterface.py b/comtypes/test/test_eventinterface.py index 6a603f55..4ce6214b 100644 --- a/comtypes/test/test_eventinterface.py +++ b/comtypes/test/test_eventinterface.py @@ -79,9 +79,12 @@ def test_default_eventinterface(self): conn = GetEvents(self.doc, sink) self.doc.loadXML("") + # Give the message loop time to process incoming events. for _ in range(50): PumpWaitingMessages() time.sleep(0.1) + # Should receive events from the default dispatch interface, but not + # events from `IPropertyNotifySink`. self.assertIn("onreadystatechange", sink._events) self.assertNotIn("OnChanged", sink._events) @@ -96,9 +99,12 @@ def test_nondefault_eventinterface(self): self.doc.loadXML("") + # Give the message loop time to process incoming events. for _ in range(50): PumpWaitingMessages() time.sleep(0.1) + # Should receive events from `IPropertyNotifySink`, but not events from + # the default dispatch interface. self.assertNotIn("onreadystatechange", sink._events) self.assertIn("OnChanged", sink._events) From 492d6f778502d52f649c89d08cc2600379b9b2d1 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 7 Mar 2026 23:48:27 +0900 Subject: [PATCH 9/9] test: Revert the event sink name in `test_eventinterface.py`. --- comtypes/test/test_eventinterface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/comtypes/test/test_eventinterface.py b/comtypes/test/test_eventinterface.py index 4ce6214b..d30c0339 100644 --- a/comtypes/test/test_eventinterface.py +++ b/comtypes/test/test_eventinterface.py @@ -30,7 +30,7 @@ class IPropertyNotifySink(IUnknown): ] -class MSXMLDocumentSink: +class EventSink: def __init__(self): self._events = [] @@ -75,7 +75,7 @@ def test_default_eventinterface(self): # Verify that `GetEvents` automatically connects to the default source # interface (dispatch events like `onreadystatechange`) when no # interface is explicitly requested. - sink = MSXMLDocumentSink() + sink = EventSink() conn = GetEvents(self.doc, sink) self.doc.loadXML("") @@ -93,7 +93,7 @@ def test_default_eventinterface(self): def test_nondefault_eventinterface(self): # Verify that `GetEvents` can connect to a non-default interface # (like `IPropertyNotifySink`) when it is explicitly provided. - sink = MSXMLDocumentSink() + sink = EventSink() conn = GetEvents(self.doc, sink, interface=IPropertyNotifySink)