Stav 23.06.2026
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
#
|
||||
# Generate scripts needed for serious testing!
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import pythoncom
|
||||
import win32com
|
||||
import win32com.client.makepy
|
||||
import win32com.test
|
||||
|
||||
genList = [
|
||||
("msword8", "{00020905-0000-0000-C000-000000000046}", 1033, 8, 0),
|
||||
]
|
||||
|
||||
genDir = "Generated4Test"
|
||||
|
||||
|
||||
def GetGenPath():
|
||||
import win32api
|
||||
|
||||
return os.path.join(
|
||||
win32api.GetFullPathName(next(iter(win32com.test.__path__))), genDir
|
||||
)
|
||||
|
||||
|
||||
def GenerateFromRegistered(fname, *loadArgs):
|
||||
# tlb = apply(pythoncom.LoadRegTypeLib, loadArgs)
|
||||
genPath = GetGenPath()
|
||||
try:
|
||||
os.stat(genPath)
|
||||
except OSError:
|
||||
os.mkdir(genPath)
|
||||
# Ensure an __init__ exists.
|
||||
open(os.path.join(genPath, "__init__.py"), "w").close()
|
||||
print(fname, ": generating -", end=" ")
|
||||
f = open(os.path.join(genPath, fname + ".py"), "w")
|
||||
win32com.client.makepy.GenerateFromTypeLibSpec(
|
||||
loadArgs, f, bQuiet=1, bGUIProgress=1
|
||||
)
|
||||
f.close()
|
||||
print("compiling -", end=" ")
|
||||
fullModName = f"win32com.test.{genDir}.{fname}"
|
||||
exec("import " + fullModName)
|
||||
# Inject the generated module as a top level module.
|
||||
sys.modules[fname] = sys.modules[fullModName]
|
||||
print("done")
|
||||
|
||||
|
||||
def GenerateAll():
|
||||
for args in genList:
|
||||
try:
|
||||
GenerateFromRegistered(*args)
|
||||
except KeyboardInterrupt:
|
||||
print("** Interrupted ***")
|
||||
break
|
||||
except pythoncom.com_error:
|
||||
print("** Could not generate test code for ", args[0])
|
||||
|
||||
|
||||
def CleanAll():
|
||||
print("Cleaning generated test scripts...")
|
||||
traceback.clear_frames(sys.exc_info()[2]) # Clear exceptions!
|
||||
genPath = GetGenPath()
|
||||
for args in genList:
|
||||
try:
|
||||
name = args[0] + ".py"
|
||||
os.unlink(os.path.join(genPath, name))
|
||||
except OSError as details:
|
||||
if isinstance(details, tuple) and details[0] != 2:
|
||||
print("Could not deleted generated", name, details)
|
||||
try:
|
||||
name = args[0] + ".pyc"
|
||||
os.unlink(os.path.join(genPath, name))
|
||||
except OSError as details:
|
||||
if isinstance(details, tuple) and details[0] != 2:
|
||||
print("Could not deleted generated", name, details)
|
||||
try:
|
||||
os.unlink(os.path.join(genPath, "__init__.py"))
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
os.unlink(os.path.join(genPath, "__init__.pyc"))
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
os.rmdir(genPath)
|
||||
except OSError as details:
|
||||
print("Could not delete test directory -", details)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
GenerateAll()
|
||||
CleanAll()
|
||||
@@ -0,0 +1,63 @@
|
||||
<scriptlet>
|
||||
|
||||
<Registration
|
||||
Description="TestPys"
|
||||
ProgID="TestPys.Scriptlet"
|
||||
Version="1"
|
||||
ClassID="{2eeb6080-cd58-11d1-b81e-00a0240b2fef}">
|
||||
|
||||
<SCRIPT LANGUAGE="VBScript">
|
||||
Function Register()
|
||||
Msgbox "Scriptlet 'Test' registered."
|
||||
End Function
|
||||
|
||||
Function Unregister()
|
||||
Msgbox "Scriptlet 'Test' unregistered."
|
||||
End Function
|
||||
</SCRIPT>
|
||||
</Registration>
|
||||
|
||||
<implements id=Automation type=Automation>
|
||||
<property name=PyProp1>
|
||||
<get/>
|
||||
<put/>
|
||||
</property>
|
||||
<property name=PyProp2>
|
||||
<get/>
|
||||
<put/>
|
||||
</property>
|
||||
<method name=PyMethod1>
|
||||
</method>
|
||||
|
||||
<method name=PyMethod2>
|
||||
</method>
|
||||
</implements>
|
||||
|
||||
<script language=python>
|
||||
|
||||
PyProp1 = "PyScript Property1";
|
||||
PyProp2 = "PyScript Property2";
|
||||
|
||||
def get_PyProp1():
|
||||
return PyProp1
|
||||
|
||||
def put_PyProp1(newValue):
|
||||
global PyProp1
|
||||
PyProp1 = newValue
|
||||
|
||||
def get_PyProp2():
|
||||
return PyProp2
|
||||
|
||||
def put_PyProp2(newValue):
|
||||
global PyProp2
|
||||
PyProp2 = newValue
|
||||
|
||||
def PyMethod1():
|
||||
return "PyMethod1 called"
|
||||
|
||||
def PyMethod2():
|
||||
return "PyMethod2 called"
|
||||
|
||||
</script>
|
||||
|
||||
</scriptlet>
|
||||
@@ -0,0 +1 @@
|
||||
# Empty file to designate a Python package
|
||||
@@ -0,0 +1,88 @@
|
||||
# import dao3032
|
||||
# No longer imported here - callers responsibility to load
|
||||
#
|
||||
import pythoncom
|
||||
import win32com.client
|
||||
|
||||
|
||||
def DumpDB(db, bDeep=1):
|
||||
# MUST be a DB object.
|
||||
DumpTables(db, bDeep)
|
||||
DumpRelations(db, bDeep)
|
||||
DumpAllContainers(db, bDeep)
|
||||
|
||||
|
||||
def DumpTables(db, bDeep=1):
|
||||
for tab in db.TableDefs:
|
||||
tab = db.TableDefs(tab.Name) # Redundant lookup for testing purposes.
|
||||
print(
|
||||
"Table %s - Fields: %d, Attributes:%d"
|
||||
% (tab.Name, len(tab.Fields), tab.Attributes)
|
||||
)
|
||||
if bDeep:
|
||||
DumpFields(tab.Fields)
|
||||
|
||||
|
||||
def DumpFields(fields):
|
||||
for field in fields:
|
||||
print(
|
||||
" %s, size=%d, reqd=%d, type=%d, defVal=%s"
|
||||
% (
|
||||
field.Name,
|
||||
field.Size,
|
||||
field.Required,
|
||||
field.Type,
|
||||
str(field.DefaultValue),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def DumpRelations(db, bDeep=1):
|
||||
for relation in db.Relations:
|
||||
print(f"Relation {relation.Name} - {relation.Table}->{relation.ForeignTable}")
|
||||
|
||||
|
||||
#### This don't work. TLB says it is a Fields collection, but apparently not!
|
||||
#### if bDeep: DumpFields(relation.Fields)
|
||||
|
||||
|
||||
def DumpAllContainers(db, bDeep=1):
|
||||
for cont in db.Containers:
|
||||
print("Container %s - %d documents" % (cont.Name, len(cont.Documents)))
|
||||
if bDeep:
|
||||
DumpContainerDocuments(cont)
|
||||
|
||||
|
||||
def DumpContainerDocuments(container):
|
||||
for doc in container.Documents:
|
||||
import time
|
||||
|
||||
timeStr = time.ctime(int(doc.LastUpdated))
|
||||
print(f" {doc.Name} - updated {timeStr} (", end=" ")
|
||||
print(doc.LastUpdated, ")") # test the _print_ method?
|
||||
|
||||
|
||||
def TestEngine(engine):
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
dbName = sys.argv[1]
|
||||
else:
|
||||
dbName = "e:\\temp\\TestPython.mdb"
|
||||
db = engine.OpenDatabase(dbName)
|
||||
DumpDB(db)
|
||||
|
||||
|
||||
def test():
|
||||
for progid in ("DAO.DBEngine.36", "DAO.DBEngine.35", "DAO.DBEngine.30"):
|
||||
try:
|
||||
ob = win32com.client.gencache.EnsureDispatch(progid)
|
||||
except pythoncom.com_error:
|
||||
print(progid, "does not seem to be installed")
|
||||
else:
|
||||
TestEngine(ob)
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
@@ -0,0 +1,243 @@
|
||||
# errorSemantics.py
|
||||
|
||||
# Test the Python error handling semantics. Specifically:
|
||||
#
|
||||
# * When a Python COM object is called via IDispatch, the nominated
|
||||
# scode is placed in the exception tuple, and the HRESULT is
|
||||
# DISP_E_EXCEPTION
|
||||
# * When the same interface is called via IWhatever, the
|
||||
# nominated scode is returned directly (with the scode also
|
||||
# reflected in the exception tuple)
|
||||
# * In all cases, the description etc end up in the exception tuple
|
||||
# * "Normal" Python exceptions resolve to an E_FAIL "internal error"
|
||||
|
||||
import pythoncom
|
||||
import winerror
|
||||
from win32com.client import Dispatch
|
||||
from win32com.server.exception import COMException
|
||||
from win32com.server.util import wrap
|
||||
from win32com.test.util import CaptureWriter
|
||||
|
||||
|
||||
# Our COM server.
|
||||
class TestServer:
|
||||
_public_methods_ = ["Clone", "Commit", "LockRegion", "Read"]
|
||||
_com_interfaces_ = [pythoncom.IID_IStream]
|
||||
|
||||
def Clone(self):
|
||||
raise COMException("Not today", scode=winerror.E_UNEXPECTED)
|
||||
|
||||
def Commit(self, flags):
|
||||
# Testing unicode: 1F600 '😀'; GRINNING FACE
|
||||
# Use the 'name' just for fun!
|
||||
if flags == 0:
|
||||
# A non com-specific exception.
|
||||
raise Exception("\N{GRINNING FACE}")
|
||||
# An explicit com_error, which is a bit of an edge-case, but might happen if
|
||||
# a COM server itself calls another COM object and it fails.
|
||||
excepinfo = (
|
||||
winerror.E_UNEXPECTED,
|
||||
"source",
|
||||
"\N{GRINNING FACE}",
|
||||
"helpfile",
|
||||
1,
|
||||
winerror.E_FAIL,
|
||||
)
|
||||
raise pythoncom.com_error(winerror.E_UNEXPECTED, "desc", excepinfo, None)
|
||||
|
||||
|
||||
def test():
|
||||
# Call via a native interface.
|
||||
com_server = wrap(TestServer(), pythoncom.IID_IStream)
|
||||
try:
|
||||
com_server.Clone()
|
||||
raise AssertionError("Expecting this call to fail!")
|
||||
except pythoncom.com_error as com_exc:
|
||||
assert com_exc.hresult == winerror.E_UNEXPECTED, (
|
||||
"Calling the object natively did not yield the correct scode",
|
||||
str(com_exc),
|
||||
)
|
||||
exc = com_exc.excepinfo
|
||||
assert exc and exc[-1] == winerror.E_UNEXPECTED, (
|
||||
"The scode element of the exception tuple did not yield the correct scode",
|
||||
str(com_exc),
|
||||
)
|
||||
assert exc[2] == "Not today", (
|
||||
"The description in the exception tuple did not yield the correct string",
|
||||
str(com_exc),
|
||||
)
|
||||
cap = CaptureWriter()
|
||||
try:
|
||||
cap.capture()
|
||||
try:
|
||||
com_server.Commit(0)
|
||||
finally:
|
||||
cap.release()
|
||||
raise AssertionError("Expecting this call to fail!")
|
||||
except pythoncom.com_error as com_exc:
|
||||
assert com_exc.hresult == winerror.E_FAIL, (
|
||||
"The hresult was not E_FAIL for an internal error",
|
||||
str(com_exc),
|
||||
)
|
||||
assert com_exc.excepinfo[1] == "Python COM Server Internal Error", (
|
||||
"The description in the exception tuple did not yield the correct string",
|
||||
str(com_exc),
|
||||
)
|
||||
# Check we saw a traceback in stderr
|
||||
assert cap.get_captured().find("Traceback") >= 0, (
|
||||
f"Could not find a traceback in stderr: {cap.get_captured()!r}"
|
||||
)
|
||||
|
||||
# Now do it all again, but using IDispatch
|
||||
com_server = Dispatch(wrap(TestServer()))
|
||||
try:
|
||||
com_server.Clone()
|
||||
raise AssertionError("Expecting this call to fail!")
|
||||
except pythoncom.com_error as com_exc:
|
||||
assert com_exc.hresult == winerror.DISP_E_EXCEPTION, (
|
||||
"Calling the object via IDispatch did not yield the correct scode",
|
||||
str(com_exc),
|
||||
)
|
||||
exc = com_exc.excepinfo
|
||||
assert exc and exc[-1] == winerror.E_UNEXPECTED, (
|
||||
"The scode element of the exception tuple did not yield the correct scode",
|
||||
str(com_exc),
|
||||
)
|
||||
assert exc[2] == "Not today", (
|
||||
"The description in the exception tuple did not yield the correct string",
|
||||
str(com_exc),
|
||||
)
|
||||
|
||||
cap.clear()
|
||||
try:
|
||||
cap.capture()
|
||||
try:
|
||||
com_server.Commit(0)
|
||||
finally:
|
||||
cap.release()
|
||||
raise AssertionError("Expecting this call to fail!")
|
||||
except pythoncom.com_error as com_exc:
|
||||
assert com_exc.hresult == winerror.DISP_E_EXCEPTION, (
|
||||
"Calling the object via IDispatch did not yield the correct scode",
|
||||
str(com_exc),
|
||||
)
|
||||
exc = com_exc.excepinfo
|
||||
assert exc and exc[-1] == winerror.E_FAIL, (
|
||||
"The scode element of the exception tuple did not yield the correct scode",
|
||||
str(com_exc),
|
||||
)
|
||||
assert exc[1] == "Python COM Server Internal Error", (
|
||||
"The description in the exception tuple did not yield the correct string",
|
||||
str(com_exc),
|
||||
)
|
||||
# Check we saw a traceback in stderr
|
||||
assert cap.get_captured().find("Traceback") >= 0, (
|
||||
f"Could not find a traceback in stderr: {cap.get_captured()!r}"
|
||||
)
|
||||
|
||||
# And an explicit com_error
|
||||
cap.clear()
|
||||
try:
|
||||
cap.capture()
|
||||
try:
|
||||
com_server.Commit(1)
|
||||
finally:
|
||||
cap.release()
|
||||
raise AssertionError("Expecting this call to fail!")
|
||||
except pythoncom.com_error as com_exc:
|
||||
assert com_exc.hresult == winerror.DISP_E_EXCEPTION, (
|
||||
"Calling the object via IDispatch did not yield the correct scode",
|
||||
str(com_exc),
|
||||
)
|
||||
exc = com_exc.excepinfo
|
||||
assert exc and exc[-1] == winerror.E_FAIL, (
|
||||
"The scode element of the exception tuple did not yield the correct scode",
|
||||
str(com_exc),
|
||||
)
|
||||
assert exc[1] == "source", (
|
||||
"The source in the exception tuple did not yield the correct string",
|
||||
str(com_exc),
|
||||
)
|
||||
assert exc[2] == "\U0001f600", (
|
||||
"The description in the exception tuple did not yield the correct string",
|
||||
str(com_exc),
|
||||
)
|
||||
assert exc[3] == "helpfile", (
|
||||
"The helpfile in the exception tuple did not yield the correct string",
|
||||
str(com_exc),
|
||||
)
|
||||
assert exc[4] == 1, (
|
||||
"The help context in the exception tuple did not yield the correct string",
|
||||
str(com_exc),
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
import logging
|
||||
except ImportError:
|
||||
logging = None
|
||||
if logging is not None:
|
||||
import win32com
|
||||
|
||||
class TestLogHandler(logging.Handler):
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
logging.Handler.__init__(self)
|
||||
|
||||
def reset(self):
|
||||
self.num_emits = 0
|
||||
self.last_record = None
|
||||
|
||||
def emit(self, record):
|
||||
self.num_emits += 1
|
||||
self.last_record = self.format(record)
|
||||
return
|
||||
print("--- record start")
|
||||
print(self.last_record)
|
||||
print("--- record end")
|
||||
|
||||
def testLogger():
|
||||
assert not hasattr(win32com, "logger")
|
||||
handler = TestLogHandler()
|
||||
formatter = logging.Formatter("%(message)s")
|
||||
handler.setFormatter(formatter)
|
||||
log = logging.getLogger("win32com_test")
|
||||
log.addHandler(handler)
|
||||
win32com.logger = log
|
||||
# Now throw some exceptions!
|
||||
# Native interfaces
|
||||
com_server = wrap(TestServer(), pythoncom.IID_IStream)
|
||||
try:
|
||||
com_server.Commit(0)
|
||||
raise AssertionError("should have failed")
|
||||
except pythoncom.error as exc:
|
||||
# `excepinfo` is a tuple with elt 2 being the traceback we captured.
|
||||
message = exc.excepinfo[2]
|
||||
assert message.endswith("Exception: \U0001f600\n")
|
||||
assert handler.num_emits == 1, handler.num_emits
|
||||
assert handler.last_record.startswith(
|
||||
"pythoncom error: Unexpected exception in gateway method 'Commit'"
|
||||
)
|
||||
handler.reset()
|
||||
|
||||
# IDispatch
|
||||
com_server = Dispatch(wrap(TestServer()))
|
||||
try:
|
||||
com_server.Commit(0)
|
||||
raise AssertionError("should have failed")
|
||||
except pythoncom.error as exc:
|
||||
# `excepinfo` is a tuple with elt 2 being the traceback we captured.
|
||||
message = exc.excepinfo[2]
|
||||
assert message.endswith("Exception: \U0001f600\n")
|
||||
assert handler.num_emits == 1, handler.num_emits
|
||||
handler.reset()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
if logging is not None:
|
||||
testLogger()
|
||||
from win32com.test.util import CheckClean
|
||||
|
||||
CheckClean()
|
||||
print("error semantic tests worked")
|
||||
@@ -0,0 +1,66 @@
|
||||
// TestServer.idl : IDL source for TestServer.dll
|
||||
//
|
||||
|
||||
// This file will be processed by the MIDL tool to
|
||||
// produce the type library (TestServer.tlb) and marshalling code.
|
||||
|
||||
import "oaidl.idl";
|
||||
import "ocidl.idl";
|
||||
[
|
||||
object,
|
||||
uuid(50086EE8-F535-464B-806E-365ADBB727CF),
|
||||
dual,
|
||||
helpstring("ITestServerApp Interface"),
|
||||
pointer_default(unique)
|
||||
]
|
||||
interface ITestServerApp : IDispatch
|
||||
{
|
||||
[id(1), helpstring("method Test1")] HRESULT Test1([out, retval] ITestServerApp **pVal);
|
||||
[id(2), helpstring("method Test2")] HRESULT Test2([out, retval] VARIANT *pVar);
|
||||
[propget, id(3), helpstring("property MyProp1")] HRESULT MyProp1([out, retval] long *pVal);
|
||||
};
|
||||
[
|
||||
object,
|
||||
uuid(618DB2A3-D5BD-4850-B66A-828727EB37E5),
|
||||
dual,
|
||||
helpstring("IPippo Interface"),
|
||||
pointer_default(unique)
|
||||
]
|
||||
interface IPippo : IDispatch
|
||||
{
|
||||
[id(1), helpstring("method Method1")] HRESULT Method1([out, retval] IPippo **val);
|
||||
[propget, id(2), helpstring("property MyProp1")] HRESULT MyProp1([out, retval] long *pVal);
|
||||
[id(3), helpstring("method Method2")] HRESULT Method2([in] long in1, [in, out] long *inout1,
|
||||
[out, retval] long *val);
|
||||
[id(4), helpstring("method Method3")] HRESULT Method3([in] VARIANT in1,
|
||||
[out, retval] VARIANT *val);
|
||||
};
|
||||
|
||||
[
|
||||
uuid(7783054E-9A20-4584-8C62-6ED2A08F6AC6),
|
||||
version(1.0),
|
||||
helpstring("TestServer 1.0 Type Library")
|
||||
]
|
||||
library TESTSERVERLib
|
||||
{
|
||||
importlib("stdole32.tlb");
|
||||
importlib("stdole2.tlb");
|
||||
importlib("msado15.dll");
|
||||
|
||||
[
|
||||
uuid(49E44E89-5A72-4456-B1D5-68268A19E798),
|
||||
helpstring("TestServerApp Class")
|
||||
]
|
||||
coclass TestServerApp
|
||||
{
|
||||
[default] interface ITestServerApp;
|
||||
};
|
||||
[
|
||||
uuid(1F0F75D6-BD63-41B9-9F88-2D9D2E1AA5C3),
|
||||
helpstring("Pippo Class")
|
||||
]
|
||||
coclass Pippo
|
||||
{
|
||||
[default] interface IPippo;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
# A little test server, complete with typelib, we can use for testing.
|
||||
# Originally submitted with bug:
|
||||
# [ 753154 ] memory leak wrapping object having _typelib_guid_ attribute
|
||||
# but modified by mhammond for use as part of the test suite.
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pythoncom
|
||||
import win32com
|
||||
import winerror
|
||||
from win32com.server.util import wrap
|
||||
|
||||
|
||||
class CPippo:
|
||||
#
|
||||
# COM declarations
|
||||
#
|
||||
_reg_clsid_ = "{1F0F75D6-BD63-41B9-9F88-2D9D2E1AA5C3}"
|
||||
_reg_desc_ = "Pippo Python test object"
|
||||
_reg_progid_ = "Python.Test.Pippo"
|
||||
# _reg_clsctx_ = pythoncom.CLSCTX_LOCAL_SERVER
|
||||
###
|
||||
### Link to typelib
|
||||
_typelib_guid_ = "{7783054E-9A20-4584-8C62-6ED2A08F6AC6}"
|
||||
_typelib_version_ = 1, 0
|
||||
_com_interfaces_ = ["IPippo"]
|
||||
|
||||
def __init__(self):
|
||||
self.MyProp1 = 10
|
||||
|
||||
def Method1(self):
|
||||
return wrap(CPippo())
|
||||
|
||||
def Method2(self, in1, inout1):
|
||||
return in1, inout1 * 2
|
||||
|
||||
def Method3(self, in1):
|
||||
# in1 will be a tuple, not a list.
|
||||
# Yet, we are not allowed to return a tuple, but need to convert it to a list first. (Bug?)
|
||||
return list(in1)
|
||||
|
||||
|
||||
def BuildTypelib():
|
||||
from setuptools.modified import newer
|
||||
|
||||
this_dir = os.path.dirname(__file__)
|
||||
idl = os.path.abspath(os.path.join(this_dir, "pippo.idl"))
|
||||
tlb = os.path.splitext(idl)[0] + ".tlb"
|
||||
if newer(idl, tlb):
|
||||
print(f"Compiling {idl}")
|
||||
rc = os.system(f'midl "{idl}"')
|
||||
assert not rc, "Compiling MIDL failed!"
|
||||
# Can't work out how to prevent MIDL from generating the stubs.
|
||||
# just nuke them
|
||||
for fname in "dlldata.c pippo_i.c pippo_p.c pippo.h".split():
|
||||
os.remove(os.path.join(this_dir, fname))
|
||||
|
||||
print(f"Registering {tlb}")
|
||||
tli = pythoncom.LoadTypeLib(tlb)
|
||||
pythoncom.RegisterTypeLib(tli, tlb)
|
||||
|
||||
|
||||
def UnregisterTypelib():
|
||||
k = CPippo
|
||||
try:
|
||||
pythoncom.UnRegisterTypeLib(
|
||||
k._typelib_guid_,
|
||||
k._typelib_version_[0],
|
||||
k._typelib_version_[1],
|
||||
0,
|
||||
pythoncom.SYS_WIN32,
|
||||
)
|
||||
print("Unregistered typelib")
|
||||
except pythoncom.error as details:
|
||||
if details[0] == winerror.TYPE_E_REGISTRYACCESS:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
if "--unregister" in argv:
|
||||
# Unregister the type-libraries.
|
||||
UnregisterTypelib()
|
||||
else:
|
||||
# Build and register the type-libraries.
|
||||
BuildTypelib()
|
||||
import win32com.server.register
|
||||
|
||||
win32com.server.register.UseCommandLine(CPippo)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -0,0 +1,115 @@
|
||||
import unittest
|
||||
|
||||
import pythoncom
|
||||
import win32com.client
|
||||
import win32com.server.util
|
||||
import win32com.test.util
|
||||
import winerror
|
||||
|
||||
|
||||
# An object representing a list of numbers
|
||||
class PythonSemanticClass:
|
||||
_public_methods_ = ["In"] # DISPIDs are allocated.
|
||||
_dispid_to_func_ = {10: "Add", 11: "Remove"} # DISPIDs specified by the object.
|
||||
|
||||
def __init__(self):
|
||||
self.list = []
|
||||
|
||||
def _NewEnum(self):
|
||||
return win32com.server.util.NewEnum(self.list)
|
||||
|
||||
def _value_(self):
|
||||
# should return an array.
|
||||
return self.list
|
||||
|
||||
def _Evaluate(self):
|
||||
# return the sum
|
||||
return sum(self.list)
|
||||
|
||||
def In(self, value):
|
||||
return value in self.list
|
||||
|
||||
def Add(self, value):
|
||||
self.list.append(value)
|
||||
|
||||
def Remove(self, value):
|
||||
self.list.remove(value)
|
||||
|
||||
|
||||
def DispExTest(ob):
|
||||
if not __debug__:
|
||||
print("WARNING: Tests dressed up as assertions are being skipped!")
|
||||
assert ob.GetDispID("Add", 0) == 10, "Policy did not honour the dispid"
|
||||
# Not impl
|
||||
# assert ob.GetMemberName(10, 0)=="add", "Policy did not give me the correct function for the dispid"
|
||||
assert ob.GetDispID("Remove", 0) == 11, "Policy did not honour the dispid"
|
||||
assert ob.GetDispID("In", 0) == 1000, "Allocated dispid unexpected value"
|
||||
assert ob.GetDispID("_NewEnum", 0) == pythoncom.DISPID_NEWENUM, (
|
||||
"_NewEnum() got unexpected DISPID"
|
||||
)
|
||||
dispids = []
|
||||
dispid = -1
|
||||
while 1:
|
||||
try:
|
||||
dispid = ob.GetNextDispID(0, dispid)
|
||||
dispids.append(dispid)
|
||||
except pythoncom.com_error as xxx_todo_changeme:
|
||||
(hr, desc, exc, arg) = xxx_todo_changeme.args
|
||||
assert hr == winerror.S_FALSE, "Bad result at end of enum"
|
||||
break
|
||||
dispids.sort()
|
||||
assert dispids == [
|
||||
pythoncom.DISPID_EVALUATE,
|
||||
pythoncom.DISPID_NEWENUM,
|
||||
10,
|
||||
11,
|
||||
1000,
|
||||
], f"Got back the wrong dispids: {dispids}"
|
||||
|
||||
|
||||
def SemanticTest(ob):
|
||||
# First just check our object "generally" as expected.
|
||||
ob.Add(1)
|
||||
ob.Add(2)
|
||||
ob.Add(3)
|
||||
# invoke _value_
|
||||
assert ob() == (1, 2, 3), f"Bad result - got {ob()!r}"
|
||||
|
||||
dispob = ob._oleobj_
|
||||
|
||||
rc = dispob.Invoke(
|
||||
pythoncom.DISPID_EVALUATE,
|
||||
0,
|
||||
pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET,
|
||||
1,
|
||||
)
|
||||
assert rc == 6, f"Evaluate returned {rc}"
|
||||
|
||||
|
||||
class Tester(win32com.test.util.TestCase):
|
||||
def setUp(self):
|
||||
debug = 0
|
||||
import win32com.server.dispatcher
|
||||
|
||||
if debug:
|
||||
dispatcher = win32com.server.dispatcher.DefaultDebugDispatcher
|
||||
else:
|
||||
dispatcher = None
|
||||
disp = win32com.server.util.wrap(
|
||||
PythonSemanticClass(), useDispatcher=dispatcher
|
||||
)
|
||||
self.ob = win32com.client.Dispatch(disp)
|
||||
|
||||
def tearDown(self):
|
||||
self.ob = None
|
||||
|
||||
def testSemantics(self):
|
||||
SemanticTest(self.ob)
|
||||
|
||||
def testIDispatchEx(self):
|
||||
dispexob = self.ob._oleobj_.QueryInterface(pythoncom.IID_IDispatchEx)
|
||||
DispExTest(dispexob)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,19 @@
|
||||
COM Test Suite Readme
|
||||
---------------------
|
||||
|
||||
Running the test suite:
|
||||
-----------------------
|
||||
* Open a command prompt
|
||||
* Change to the "win32com\test" directory.
|
||||
* run "testall.py". This will perform level 1 testing.
|
||||
You may specify 1, 2, or 3 on the command line ("testutil 3")
|
||||
to execute more tests.
|
||||
|
||||
In general, this should just run the best it can, utilizing what is available
|
||||
on the machine. It is likely some tests will refuse to run due to objects not
|
||||
being locally available - this is normal.
|
||||
|
||||
The `com/TestSources/` directory has source code to a C++ and VB component used purely
|
||||
for testing. You may like to build and register these, particularly if you
|
||||
are doing anything related to argument/result handling.
|
||||
You can run `com/TestSources/PyCOMTest/buildAndRegister.bat` directly.
|
||||
@@ -0,0 +1,100 @@
|
||||
import os
|
||||
import time
|
||||
|
||||
import pythoncom
|
||||
from win32com.client import DispatchWithEvents, constants
|
||||
|
||||
finished = 0 # Flag for the wait loop from (3) to test
|
||||
|
||||
|
||||
class ADOEvents: # event handler class
|
||||
def OnWillConnect(self, str, user, pw, opt, sts, cn):
|
||||
# Must have this event, as if it is not handled, ADO assumes the
|
||||
# operation is cancelled, and raises an error (Operation cancelled
|
||||
# by the user)
|
||||
pass
|
||||
|
||||
def OnConnectComplete(self, error, status, connection):
|
||||
# Assume no errors, until we have the basic stuff
|
||||
# working. Now, "connection" should be an open
|
||||
# connection to my data source
|
||||
# Do the "something" from (2). For now, just
|
||||
# print the connection data source
|
||||
print("connection is", connection)
|
||||
print("Connected to", connection.Properties("Data Source"))
|
||||
# OK, our work is done. Let the main loop know
|
||||
global finished
|
||||
finished = 1
|
||||
|
||||
def OnCommitTransComplete(self, pError, adStatus, pConnection):
|
||||
pass
|
||||
|
||||
def OnInfoMessage(self, pError, adStatus, pConnection):
|
||||
pass
|
||||
|
||||
def OnDisconnect(self, adStatus, pConnection):
|
||||
pass
|
||||
|
||||
def OnBeginTransComplete(self, TransactionLevel, pError, adStatus, pConnection):
|
||||
pass
|
||||
|
||||
def OnRollbackTransComplete(self, pError, adStatus, pConnection):
|
||||
pass
|
||||
|
||||
def OnExecuteComplete(
|
||||
self, RecordsAffected, pError, adStatus, pCommand, pRecordset, pConnection
|
||||
):
|
||||
pass
|
||||
|
||||
def OnWillExecute(
|
||||
self,
|
||||
Source,
|
||||
CursorType,
|
||||
LockType,
|
||||
Options,
|
||||
adStatus,
|
||||
pCommand,
|
||||
pRecordset,
|
||||
pConnection,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def TestConnection(dbname):
|
||||
# Create the ADO connection object, and link the event
|
||||
# handlers into it
|
||||
c = DispatchWithEvents("ADODB.Connection", ADOEvents)
|
||||
|
||||
# Initiate the asynchronous open
|
||||
dsn = "Driver={Microsoft Access Driver (*.mdb)};Dbq=%s" % dbname
|
||||
user = "system"
|
||||
pw = "manager"
|
||||
c.Open(dsn, user, pw, constants.adAsyncConnect)
|
||||
|
||||
# Sit in a loop, until our event handler (above) sets the
|
||||
# "finished" flag or we time out.
|
||||
end_time = time.clock() + 10
|
||||
while time.clock() < end_time:
|
||||
# Pump messages so that COM gets a look in
|
||||
pythoncom.PumpWaitingMessages()
|
||||
if not finished:
|
||||
print("XXX - Failed to connect!")
|
||||
|
||||
|
||||
def Test():
|
||||
from . import testAccess
|
||||
|
||||
try:
|
||||
testAccess.GenerateSupport()
|
||||
except pythoncom.com_error:
|
||||
print("*** Can not import the MSAccess type libraries - tests skipped")
|
||||
return
|
||||
dbname = testAccess.CreateTestAccessDatabase()
|
||||
try:
|
||||
TestConnection(dbname)
|
||||
finally:
|
||||
os.unlink(dbname)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Test()
|
||||
@@ -0,0 +1,44 @@
|
||||
# Test AXScripting the best we can in an automated fashion...
|
||||
import os
|
||||
import sys
|
||||
|
||||
import win32api
|
||||
import win32com.axscript
|
||||
import win32com.axscript.client
|
||||
import win32com.test.util
|
||||
|
||||
verbose = "-v" in sys.argv
|
||||
|
||||
|
||||
class AXScript(win32com.test.util.TestCase):
|
||||
def setUp(self):
|
||||
file = win32api.GetFullPathName(
|
||||
os.path.join(next(iter(win32com.axscript.client.__path__)), "pyscript.py")
|
||||
)
|
||||
|
||||
self.verbose = verbose
|
||||
win32com.test.util.RegisterPythonServer(file, "python", verbose=self.verbose)
|
||||
|
||||
def testHost(self):
|
||||
file = win32api.GetFullPathName(
|
||||
os.path.join(next(iter(win32com.axscript.__path__)), "test\\testHost.py")
|
||||
)
|
||||
cmd = f'{win32api.GetModuleFileName(0)} "{file}"'
|
||||
if verbose:
|
||||
print("Testing Python Scripting host")
|
||||
win32com.test.util.ExecuteShellCommand(cmd, self)
|
||||
|
||||
def testCScript(self):
|
||||
file = win32api.GetFullPathName(
|
||||
os.path.join(
|
||||
next(iter(win32com.axscript.__path__)), "Demos\\Client\\wsh\\test.pys"
|
||||
)
|
||||
)
|
||||
cmd = 'cscript.exe "%s"' % (file)
|
||||
if verbose:
|
||||
print("Testing Windows Scripting host with Python script")
|
||||
win32com.test.util.ExecuteShellCommand(cmd, self)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
win32com.test.util.testmain()
|
||||
@@ -0,0 +1,185 @@
|
||||
#
|
||||
# This assumes that you have MSAccess and DAO installed.
|
||||
# You need to run makepy.py over "msaccess.tlb" and
|
||||
# "dao3032.dll", and ensure the generated files are on the
|
||||
# path.
|
||||
|
||||
# You can run this with no args, and a test database will be generated.
|
||||
# You can optionally pass a dbname on the command line, in which case it will be dumped.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pythoncom
|
||||
import win32api
|
||||
from win32com.client import Dispatch, constants, gencache
|
||||
|
||||
|
||||
def CreateTestAccessDatabase(dbname=None):
|
||||
# Creates a test access database - returns the filename.
|
||||
if dbname is None:
|
||||
dbname = os.path.join(win32api.GetTempPath(), "COMTestSuiteTempDatabase.mdb")
|
||||
|
||||
access = Dispatch("Access.Application")
|
||||
dbEngine = access.DBEngine
|
||||
workspace = dbEngine.Workspaces(0)
|
||||
|
||||
try:
|
||||
os.unlink(dbname)
|
||||
except OSError:
|
||||
print(
|
||||
"WARNING - Unable to delete old test database - expect a COM exception RSN!"
|
||||
)
|
||||
|
||||
newdb = workspace.CreateDatabase(
|
||||
dbname, constants.dbLangGeneral, constants.dbEncrypt
|
||||
)
|
||||
|
||||
# Create one test table.
|
||||
table = newdb.CreateTableDef("Test Table 1")
|
||||
table.Fields.Append(table.CreateField("First Name", constants.dbText))
|
||||
table.Fields.Append(table.CreateField("Last Name", constants.dbText))
|
||||
|
||||
index = table.CreateIndex("UniqueIndex")
|
||||
index.Fields.Append(index.CreateField("First Name"))
|
||||
index.Fields.Append(index.CreateField("Last Name"))
|
||||
index.Unique = -1
|
||||
table.Indexes.Append(index)
|
||||
|
||||
newdb.TableDefs.Append(table)
|
||||
|
||||
# Create a second test table.
|
||||
table = newdb.CreateTableDef("Test Table 2")
|
||||
table.Fields.Append(table.CreateField("First Name", constants.dbText))
|
||||
table.Fields.Append(table.CreateField("Last Name", constants.dbText))
|
||||
|
||||
newdb.TableDefs.Append(table)
|
||||
|
||||
# Create a relationship between them
|
||||
relation = newdb.CreateRelation("TestRelationship")
|
||||
relation.Table = "Test Table 1"
|
||||
relation.ForeignTable = "Test Table 2"
|
||||
|
||||
field = relation.CreateField("First Name")
|
||||
field.ForeignName = "First Name"
|
||||
relation.Fields.Append(field)
|
||||
|
||||
field = relation.CreateField("Last Name")
|
||||
field.ForeignName = "Last Name"
|
||||
relation.Fields.Append(field)
|
||||
|
||||
relation.Attributes = (
|
||||
constants.dbRelationDeleteCascade + constants.dbRelationUpdateCascade
|
||||
)
|
||||
|
||||
newdb.Relations.Append(relation)
|
||||
|
||||
# Finally we can add some data to the table.
|
||||
tab1 = newdb.OpenRecordset("Test Table 1")
|
||||
tab1.AddNew()
|
||||
tab1.Fields("First Name").Value = "Mark"
|
||||
tab1.Fields("Last Name").Value = "Hammond"
|
||||
tab1.Update()
|
||||
|
||||
tab1.MoveFirst()
|
||||
# We do a simple bookmark test which tests our optimized VT_SAFEARRAY|VT_UI1 support.
|
||||
# The bookmark will be a buffer object - remember it for later.
|
||||
bk = tab1.Bookmark
|
||||
|
||||
# Add a second record.
|
||||
tab1.AddNew()
|
||||
tab1.Fields("First Name").Value = "Second"
|
||||
tab1.Fields("Last Name").Value = "Person"
|
||||
tab1.Update()
|
||||
|
||||
# Reset the bookmark to the one we saved.
|
||||
# But first check the test is actually doing something!
|
||||
tab1.MoveLast()
|
||||
assert tab1.Fields("First Name").Value == "Second", (
|
||||
"Unexpected record is last - makes bookmark test pointless!"
|
||||
)
|
||||
|
||||
tab1.Bookmark = bk
|
||||
assert tab1.Bookmark == bk, "The bookmark data is not the same"
|
||||
assert tab1.Fields("First Name").Value == "Mark", (
|
||||
"The bookmark did not reset the record pointer correctly"
|
||||
)
|
||||
|
||||
return dbname
|
||||
|
||||
|
||||
def DoDumpAccessInfo(dbname):
|
||||
from . import daodump
|
||||
|
||||
a = forms = None
|
||||
try:
|
||||
sys.stderr.write("Creating Access Application...\n")
|
||||
a = Dispatch("Access.Application")
|
||||
print("Opening database %s" % dbname)
|
||||
a.OpenCurrentDatabase(dbname)
|
||||
db = a.CurrentDb()
|
||||
daodump.DumpDB(db, 1)
|
||||
forms = a.Forms
|
||||
print("There are %d forms open." % (len(forms)))
|
||||
# Uncommenting these lines means Access remains open.
|
||||
# for form in forms:
|
||||
# print(f" {form.Name}")
|
||||
reports = a.Reports
|
||||
print("There are %d reports open" % (len(reports)))
|
||||
finally:
|
||||
if not a is None:
|
||||
sys.stderr.write("Closing database\n")
|
||||
try:
|
||||
a.CloseCurrentDatabase()
|
||||
except pythoncom.com_error:
|
||||
pass
|
||||
|
||||
|
||||
# Generate all the support we can.
|
||||
def GenerateSupport():
|
||||
# dao
|
||||
gencache.EnsureModule("{00025E01-0000-0000-C000-000000000046}", 0, 4, 0)
|
||||
# Access
|
||||
# gencache.EnsureModule("{4AFFC9A0-5F99-101B-AF4E-00AA003F0F07}", 0, 8, 0)
|
||||
gencache.EnsureDispatch("Access.Application")
|
||||
|
||||
|
||||
def DumpAccessInfo(dbname):
|
||||
amod = gencache.GetModuleForProgID("Access.Application")
|
||||
dmod = gencache.GetModuleForProgID("DAO.DBEngine.35")
|
||||
if amod is None and dmod is None:
|
||||
DoDumpAccessInfo(dbname)
|
||||
# Now generate all the support we can.
|
||||
GenerateSupport()
|
||||
else:
|
||||
sys.stderr.write(
|
||||
"testAccess not doing dynamic test, as generated code already exists\n"
|
||||
)
|
||||
# Now a generated version.
|
||||
DoDumpAccessInfo(dbname)
|
||||
|
||||
|
||||
def test(dbname=None):
|
||||
if dbname is None:
|
||||
# We need makepy support to create a database (just for the constants!)
|
||||
try:
|
||||
GenerateSupport()
|
||||
except pythoncom.com_error:
|
||||
print("*** Can not import the MSAccess type libraries - tests skipped")
|
||||
return
|
||||
dbname = CreateTestAccessDatabase()
|
||||
print("A test database at '%s' was created" % dbname)
|
||||
|
||||
DumpAccessInfo(dbname)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from .util import CheckClean
|
||||
|
||||
dbname = None
|
||||
if len(sys.argv) > 1:
|
||||
dbname = sys.argv[1]
|
||||
|
||||
test(dbname)
|
||||
|
||||
CheckClean()
|
||||
@@ -0,0 +1,99 @@
|
||||
# Originally contributed by Stefan Schukat as part of this arbitrary-sized
|
||||
# arrays patch.
|
||||
|
||||
from win32com.client import gencache
|
||||
from win32com.test import util
|
||||
|
||||
ZeroD = 0
|
||||
OneDEmpty = []
|
||||
OneD = [1, 2, 3]
|
||||
TwoD = [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
|
||||
|
||||
TwoD1 = [[[1, 2, 3, 5], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]
|
||||
|
||||
OneD1 = [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3]]]
|
||||
|
||||
OneD2 = [
|
||||
[1, 2, 3],
|
||||
[1, 2, 3, 4, 5],
|
||||
[[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]],
|
||||
]
|
||||
|
||||
|
||||
ThreeD = [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]
|
||||
|
||||
FourD = [
|
||||
[
|
||||
[[1, 2, 3], [1, 2, 3], [1, 2, 3]],
|
||||
[[1, 2, 3], [1, 2, 3], [1, 2, 3]],
|
||||
[[1, 2, 3], [1, 2, 3], [1, 2, 3]],
|
||||
],
|
||||
[
|
||||
[[1, 2, 3], [1, 2, 3], [1, 2, 3]],
|
||||
[[1, 2, 3], [1, 2, 3], [1, 2, 3]],
|
||||
[[1, 2, 3], [1, 2, 3], [1, 2, 3]],
|
||||
],
|
||||
]
|
||||
|
||||
LargeD = [
|
||||
[[list(range(10))] * 10],
|
||||
] * 512
|
||||
|
||||
|
||||
def _normalize_array(a):
|
||||
if not isinstance(a, tuple):
|
||||
return a
|
||||
ret = []
|
||||
for i in a:
|
||||
ret.append(_normalize_array(i))
|
||||
return ret
|
||||
|
||||
|
||||
class ArrayTest(util.TestCase):
|
||||
def setUp(self):
|
||||
self.arr = gencache.EnsureDispatch("PyCOMTest.ArrayTest", bForDemand=False)
|
||||
|
||||
def tearDown(self):
|
||||
self.arr = None
|
||||
|
||||
def _doTest(self, array):
|
||||
self.arr.Array = array
|
||||
self.assertEqual(_normalize_array(self.arr.Array), array)
|
||||
|
||||
def testZeroD(self):
|
||||
self._doTest(ZeroD)
|
||||
|
||||
def testOneDEmpty(self):
|
||||
self._doTest(OneDEmpty)
|
||||
|
||||
def testOneD(self):
|
||||
self._doTest(OneD)
|
||||
|
||||
def testTwoD(self):
|
||||
self._doTest(TwoD)
|
||||
|
||||
def testThreeD(self):
|
||||
self._doTest(ThreeD)
|
||||
|
||||
def testFourD(self):
|
||||
self._doTest(FourD)
|
||||
|
||||
def testTwoD1(self):
|
||||
self._doTest(TwoD1)
|
||||
|
||||
def testOneD1(self):
|
||||
self._doTest(OneD1)
|
||||
|
||||
def testOneD2(self):
|
||||
self._doTest(OneD2)
|
||||
|
||||
def testLargeD(self):
|
||||
self._doTest(LargeD)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
util.testmain()
|
||||
except SystemExit as rc:
|
||||
if not rc:
|
||||
raise
|
||||
@@ -0,0 +1,169 @@
|
||||
# testClipboard.py
|
||||
import unittest
|
||||
|
||||
import pythoncom
|
||||
import win32clipboard
|
||||
import win32con
|
||||
import winerror
|
||||
from win32com.server.exception import COMException
|
||||
from win32com.server.util import NewEnum, wrap
|
||||
|
||||
IDataObject_Methods = """GetData GetDataHere QueryGetData
|
||||
GetCanonicalFormatEtc SetData EnumFormatEtc
|
||||
DAdvise DUnadvise EnumDAdvise""".split()
|
||||
|
||||
# A COM object implementing IDataObject used for basic testing.
|
||||
num_do_objects = 0
|
||||
|
||||
|
||||
def WrapCOMObject(ob, iid=None):
|
||||
return wrap(ob, iid=iid, useDispatcher=0)
|
||||
|
||||
|
||||
class TestDataObject:
|
||||
_com_interfaces_ = [pythoncom.IID_IDataObject]
|
||||
_public_methods_ = IDataObject_Methods
|
||||
|
||||
def __init__(self, bytesval):
|
||||
global num_do_objects
|
||||
num_do_objects += 1
|
||||
self.bytesval = bytesval
|
||||
self.supported_fe = []
|
||||
for cf in (win32con.CF_TEXT, win32con.CF_UNICODETEXT):
|
||||
fe = cf, None, pythoncom.DVASPECT_CONTENT, -1, pythoncom.TYMED_HGLOBAL
|
||||
self.supported_fe.append(fe)
|
||||
|
||||
def __del__(self):
|
||||
global num_do_objects
|
||||
num_do_objects -= 1
|
||||
|
||||
def _query_interface_(self, iid):
|
||||
if iid == pythoncom.IID_IEnumFORMATETC:
|
||||
return NewEnum(self.supported_fe, iid=iid)
|
||||
|
||||
def GetData(self, fe):
|
||||
ret_stg = None
|
||||
cf, target, aspect, index, tymed = fe
|
||||
if aspect & pythoncom.DVASPECT_CONTENT and tymed == pythoncom.TYMED_HGLOBAL:
|
||||
if cf == win32con.CF_TEXT:
|
||||
ret_stg = pythoncom.STGMEDIUM()
|
||||
ret_stg.set(pythoncom.TYMED_HGLOBAL, self.bytesval)
|
||||
elif cf == win32con.CF_UNICODETEXT:
|
||||
ret_stg = pythoncom.STGMEDIUM()
|
||||
ret_stg.set(pythoncom.TYMED_HGLOBAL, self.bytesval.decode("latin1"))
|
||||
|
||||
if ret_stg is None:
|
||||
raise COMException(hresult=winerror.E_NOTIMPL)
|
||||
return ret_stg
|
||||
|
||||
def GetDataHere(self, fe):
|
||||
raise COMException(hresult=winerror.E_NOTIMPL)
|
||||
|
||||
def QueryGetData(self, fe):
|
||||
cf, target, aspect, index, tymed = fe
|
||||
if aspect & pythoncom.DVASPECT_CONTENT == 0:
|
||||
raise COMException(hresult=winerror.DV_E_DVASPECT)
|
||||
if tymed != pythoncom.TYMED_HGLOBAL:
|
||||
raise COMException(hresult=winerror.DV_E_TYMED)
|
||||
return None # should check better
|
||||
|
||||
def GetCanonicalFormatEtc(self, fe):
|
||||
raise COMException(winerror.DATA_S_SAMEFORMATETC)
|
||||
|
||||
def SetData(self, fe, medium):
|
||||
raise COMException(hresult=winerror.E_NOTIMPL)
|
||||
|
||||
def EnumFormatEtc(self, direction):
|
||||
if direction != pythoncom.DATADIR_GET:
|
||||
raise COMException(hresult=winerror.E_NOTIMPL)
|
||||
return NewEnum(self.supported_fe, iid=pythoncom.IID_IEnumFORMATETC)
|
||||
|
||||
def DAdvise(self, fe, flags, sink):
|
||||
raise COMException(hresult=winerror.E_NOTIMPL)
|
||||
|
||||
def DUnadvise(self, connection):
|
||||
raise COMException(hresult=winerror.E_NOTIMPL)
|
||||
|
||||
def EnumDAdvise(self):
|
||||
raise COMException(hresult=winerror.E_NOTIMPL)
|
||||
|
||||
|
||||
class ClipboardTester(unittest.TestCase):
|
||||
def setUp(self):
|
||||
pythoncom.OleInitialize()
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
pythoncom.OleFlushClipboard()
|
||||
except pythoncom.com_error:
|
||||
# We never set anything!
|
||||
pass
|
||||
|
||||
def testIsCurrentClipboard(self):
|
||||
do = TestDataObject(b"Hello from Python")
|
||||
do = WrapCOMObject(do, iid=pythoncom.IID_IDataObject)
|
||||
pythoncom.OleSetClipboard(do)
|
||||
self.assertTrue(pythoncom.OleIsCurrentClipboard(do))
|
||||
|
||||
def testComToWin32(self):
|
||||
# Set the data via our DataObject
|
||||
do = TestDataObject(b"Hello from Python")
|
||||
do = WrapCOMObject(do, iid=pythoncom.IID_IDataObject)
|
||||
pythoncom.OleSetClipboard(do)
|
||||
# Then get it back via the standard win32 clipboard functions.
|
||||
win32clipboard.OpenClipboard()
|
||||
got = win32clipboard.GetClipboardData(win32con.CF_TEXT)
|
||||
# CF_TEXT gives bytes.
|
||||
expected = b"Hello from Python"
|
||||
self.assertEqual(got, expected)
|
||||
# Now check unicode
|
||||
got = win32clipboard.GetClipboardData(win32con.CF_UNICODETEXT)
|
||||
self.assertEqual(got, "Hello from Python")
|
||||
win32clipboard.CloseClipboard()
|
||||
|
||||
def testWin32ToCom(self):
|
||||
# Set the data via the std win32 clipboard functions.
|
||||
val = b"Hello again!" # always bytes
|
||||
win32clipboard.OpenClipboard()
|
||||
win32clipboard.SetClipboardData(win32con.CF_TEXT, val)
|
||||
win32clipboard.CloseClipboard()
|
||||
# and get it via an IDataObject provided by COM
|
||||
do = pythoncom.OleGetClipboard()
|
||||
cf = (
|
||||
win32con.CF_TEXT,
|
||||
None,
|
||||
pythoncom.DVASPECT_CONTENT,
|
||||
-1,
|
||||
pythoncom.TYMED_HGLOBAL,
|
||||
)
|
||||
stg = do.GetData(cf)
|
||||
got = stg.data
|
||||
# The data we get back has the \0, as our STGMEDIUM has no way of
|
||||
# knowing if it meant to be a string, or a binary buffer, so
|
||||
# it must return it too.
|
||||
self.assertTrue(got, b"Hello again!\0")
|
||||
|
||||
def testDataObjectFlush(self):
|
||||
do = TestDataObject(b"Hello from Python")
|
||||
do = WrapCOMObject(do, iid=pythoncom.IID_IDataObject)
|
||||
pythoncom.OleSetClipboard(do)
|
||||
self.assertEqual(num_do_objects, 1)
|
||||
|
||||
do = None # clear my ref!
|
||||
pythoncom.OleFlushClipboard()
|
||||
self.assertEqual(num_do_objects, 0)
|
||||
|
||||
def testDataObjectReset(self):
|
||||
do = TestDataObject(b"Hello from Python")
|
||||
do = WrapCOMObject(do)
|
||||
pythoncom.OleSetClipboard(do)
|
||||
do = None # clear my ref!
|
||||
self.assertEqual(num_do_objects, 1)
|
||||
pythoncom.OleSetClipboard(None)
|
||||
self.assertEqual(num_do_objects, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from win32com.test import util
|
||||
|
||||
util.testmain()
|
||||
@@ -0,0 +1,159 @@
|
||||
# testCollections.py
|
||||
#
|
||||
# This code tests both the client and server side of collections
|
||||
# and enumerators.
|
||||
#
|
||||
# Also has the side effect of testing some of the PythonCOM error semantics.
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import pythoncom
|
||||
import win32com.client
|
||||
import win32com.server.util
|
||||
import win32com.test.util
|
||||
import winerror
|
||||
|
||||
|
||||
def MakeEmptyEnum():
|
||||
# create the Python enumerator object as a real COM object
|
||||
o = win32com.server.util.wrap(win32com.server.util.Collection())
|
||||
return win32com.client.Dispatch(o)
|
||||
|
||||
|
||||
def MakeTestEnum():
|
||||
# create a sub-collection, just to make sure it works :-)
|
||||
sub = win32com.server.util.wrap(
|
||||
win32com.server.util.Collection(["Sub1", 2, "Sub3"])
|
||||
)
|
||||
# create the Python enumerator object as a real COM object
|
||||
o = win32com.server.util.wrap(win32com.server.util.Collection([1, "Two", 3, sub]))
|
||||
return win32com.client.Dispatch(o)
|
||||
|
||||
|
||||
def TestEnumAgainst(o, check):
|
||||
for i in range(len(check)):
|
||||
assert o(i) == check[i], (
|
||||
f"Using default method gave the incorrect value - {o(i)!r}/{check[i]!r}"
|
||||
)
|
||||
|
||||
for i in range(len(check)):
|
||||
assert o.Item(i) == check[i], (
|
||||
f"Using Item method gave the incorrect value - {o(i)!r}/{check[i]!r}"
|
||||
)
|
||||
|
||||
# First try looping.
|
||||
cmp = []
|
||||
for s in o:
|
||||
cmp.append(s)
|
||||
|
||||
assert cmp[: len(check)] == check, (
|
||||
f"Result after looping isn't correct - {cmp[: len(check)]!r}/{check!r}"
|
||||
)
|
||||
|
||||
for i in range(len(check)):
|
||||
assert o[i] == check[i], "Using indexing gave the incorrect value"
|
||||
|
||||
|
||||
def TestEnum(quiet=None):
|
||||
if quiet is None:
|
||||
quiet = not "-v" in sys.argv
|
||||
if not quiet:
|
||||
print("Simple enum test")
|
||||
o = MakeTestEnum()
|
||||
check = [1, "Two", 3]
|
||||
TestEnumAgainst(o, check)
|
||||
|
||||
if not quiet:
|
||||
print("sub-collection test")
|
||||
sub = o[3]
|
||||
TestEnumAgainst(sub, ["Sub1", 2, "Sub3"])
|
||||
|
||||
# Remove the sublist for this test!
|
||||
o.Remove(o.Count() - 1)
|
||||
|
||||
if not quiet:
|
||||
print("Remove item test")
|
||||
del check[1]
|
||||
o.Remove(1)
|
||||
TestEnumAgainst(o, check)
|
||||
|
||||
if not quiet:
|
||||
print("Add item test")
|
||||
o.Add("New Item")
|
||||
check.append("New Item")
|
||||
TestEnumAgainst(o, check)
|
||||
|
||||
if not quiet:
|
||||
print("Insert item test")
|
||||
o.Insert(2, -1)
|
||||
check.insert(2, -1)
|
||||
TestEnumAgainst(o, check)
|
||||
|
||||
### This does not work!
|
||||
# if not quiet: print("Indexed replace item test")
|
||||
# o[2] = 'Replaced Item'
|
||||
# check[2] = 'Replaced Item'
|
||||
# TestEnumAgainst(o, check)
|
||||
|
||||
try:
|
||||
o()
|
||||
raise AssertionError(
|
||||
"default method with no args worked when it shouldn't have!"
|
||||
)
|
||||
except pythoncom.com_error as exc:
|
||||
assert exc.hresult == winerror.DISP_E_BADPARAMCOUNT, (
|
||||
f"Expected DISP_E_BADPARAMCOUNT - got {exc}"
|
||||
)
|
||||
|
||||
try:
|
||||
o.Insert("foo", 2)
|
||||
raise AssertionError("Insert worked when it shouldn't have!")
|
||||
except pythoncom.com_error as exc:
|
||||
assert exc.hresult == winerror.DISP_E_TYPEMISMATCH, (
|
||||
f"Expected DISP_E_TYPEMISMATCH - got {exc}"
|
||||
)
|
||||
|
||||
# Remove the sublist for this test!
|
||||
try:
|
||||
o.Remove(o.Count())
|
||||
raise AssertionError("Remove worked when it shouldn't have!")
|
||||
except pythoncom.com_error as exc:
|
||||
assert exc.hresult == winerror.DISP_E_BADINDEX, (
|
||||
f"Expected DISP_E_BADINDEX - got {exc}"
|
||||
)
|
||||
|
||||
# Test an empty collection
|
||||
if not quiet:
|
||||
print("Empty collection test")
|
||||
o = MakeEmptyEnum()
|
||||
for item in o:
|
||||
raise AssertionError("Empty list performed an iteration")
|
||||
|
||||
try:
|
||||
ob = o[1]
|
||||
raise AssertionError("Empty list could be indexed")
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
try:
|
||||
ob = o[0]
|
||||
raise AssertionError("Empty list could be indexed")
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
try:
|
||||
ob = o(0)
|
||||
raise AssertionError("Empty list could be indexed")
|
||||
except pythoncom.com_error as exc:
|
||||
assert exc.hresult == winerror.DISP_E_BADINDEX, (
|
||||
f"Expected DISP_E_BADINDEX - got {exc}"
|
||||
)
|
||||
|
||||
|
||||
class TestCase(win32com.test.util.TestCase):
|
||||
def testEnum(self):
|
||||
TestEnum()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,35 @@
|
||||
import unittest
|
||||
|
||||
import win32com.client
|
||||
import win32com.server.util
|
||||
import win32com.test.util
|
||||
|
||||
|
||||
class Tester:
|
||||
_public_methods_ = ["TestValue"]
|
||||
|
||||
def TestValue(self, v):
|
||||
pass
|
||||
|
||||
|
||||
def test_ob():
|
||||
return win32com.client.Dispatch(win32com.server.util.wrap(Tester()))
|
||||
|
||||
|
||||
class TestException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# The object we try and pass - pywin32 will call __float__ as a last resort.
|
||||
class BadConversions:
|
||||
def __float__(self):
|
||||
raise TestException
|
||||
|
||||
|
||||
class TestCase(win32com.test.util.TestCase):
|
||||
def test_float(self):
|
||||
self.assertRaises(TestException, test_ob().TestValue, BadConversions())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,50 @@
|
||||
# testDCOM
|
||||
usage = """\
|
||||
testDCOM.py - Simple DCOM test
|
||||
Usage: testDCOM.py serverName
|
||||
|
||||
Attempts to start the Python.Interpreter object on the named machine,
|
||||
and checks that the object is indeed running remotely.
|
||||
|
||||
Requires the named server be configured to run DCOM (using dcomcnfg.exe),
|
||||
and the Python.Interpreter object installed and registered on that machine.
|
||||
|
||||
The Python.Interpreter object must be installed on the local machine,
|
||||
but no special DCOM configuration should be necessary.
|
||||
"""
|
||||
import sys
|
||||
|
||||
# NOTE: If you configured the object locally using dcomcnfg, you could
|
||||
# simple use Dispatch rather than DispatchEx.
|
||||
import pythoncom
|
||||
import win32api
|
||||
import win32com.client
|
||||
|
||||
|
||||
def test(serverName):
|
||||
if serverName.lower() == win32api.GetComputerName().lower():
|
||||
print("You must specify a remote server name, not the local machine!")
|
||||
return
|
||||
|
||||
# Hack to overcome a DCOM limitation. As the Python.Interpreter object
|
||||
# is probably installed locally as an InProc object, DCOM seems to ignore
|
||||
# all settings, and use the local object.
|
||||
clsctx = pythoncom.CLSCTX_SERVER & ~pythoncom.CLSCTX_INPROC_SERVER
|
||||
ob = win32com.client.DispatchEx("Python.Interpreter", serverName, clsctx=clsctx)
|
||||
ob.Exec("import win32api")
|
||||
actualName = ob.Eval("win32api.GetComputerName()")
|
||||
if serverName.lower() != actualName.lower():
|
||||
print(
|
||||
"Error: The object created on server '{}' reported its name as '{}'".format(
|
||||
serverName, actualName
|
||||
)
|
||||
)
|
||||
else:
|
||||
print("Object created and tested OK on server '%s'" % serverName)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) == 2:
|
||||
test(sys.argv[1])
|
||||
else:
|
||||
print(usage)
|
||||
@@ -0,0 +1,74 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
|
||||
import pywintypes
|
||||
import win32com.client
|
||||
import win32com.server.util
|
||||
import win32com.test.util
|
||||
from win32timezone import TimeZoneInfo
|
||||
|
||||
|
||||
# A COM object so we can pass dates to and from the COM boundary.
|
||||
class Tester:
|
||||
_public_methods_ = ["TestDate"]
|
||||
|
||||
def TestDate(self, d):
|
||||
assert isinstance(d, datetime)
|
||||
return d
|
||||
|
||||
|
||||
def test_ob():
|
||||
return win32com.client.Dispatch(win32com.server.util.wrap(Tester()))
|
||||
|
||||
|
||||
class TestCase(win32com.test.util.TestCase):
|
||||
def check(self, d, expected=None):
|
||||
if not issubclass(pywintypes.TimeType, datetime):
|
||||
self.skipTest("this is testing pywintypes and datetime")
|
||||
got = test_ob().TestDate(d)
|
||||
self.assertEqual(got, expected or d)
|
||||
|
||||
def testUTC(self):
|
||||
self.check(
|
||||
datetime(
|
||||
year=2000,
|
||||
month=12,
|
||||
day=25,
|
||||
microsecond=500000,
|
||||
tzinfo=TimeZoneInfo.utc(),
|
||||
)
|
||||
)
|
||||
|
||||
def testLocal(self):
|
||||
self.check(
|
||||
datetime(
|
||||
year=2000,
|
||||
month=12,
|
||||
day=25,
|
||||
microsecond=500000,
|
||||
tzinfo=TimeZoneInfo.local(),
|
||||
)
|
||||
)
|
||||
|
||||
def testMSTruncated(self):
|
||||
# milliseconds are kept but microseconds are lost after rounding.
|
||||
self.check(
|
||||
datetime(
|
||||
year=2000,
|
||||
month=12,
|
||||
day=25,
|
||||
microsecond=500500,
|
||||
tzinfo=TimeZoneInfo.utc(),
|
||||
),
|
||||
datetime(
|
||||
year=2000,
|
||||
month=12,
|
||||
day=25,
|
||||
microsecond=500000,
|
||||
tzinfo=TimeZoneInfo.utc(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,101 @@
|
||||
# testDictionary.py
|
||||
#
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import pythoncom
|
||||
import pywintypes
|
||||
import win32com.client
|
||||
import win32com.server.util
|
||||
import win32com.test.util
|
||||
import win32timezone
|
||||
import winerror
|
||||
|
||||
|
||||
def MakeTestDictionary():
|
||||
return win32com.client.Dispatch("Python.Dictionary")
|
||||
|
||||
|
||||
def TestDictAgainst(dict, check):
|
||||
for key, value in check.items():
|
||||
assert dict(key) == value, (
|
||||
f"Indexing for '{key!r}' gave the incorrect value - {dict[key]!r}/{check[key]!r}"
|
||||
)
|
||||
|
||||
|
||||
# Ensure we have the correct version registered.
|
||||
def Register(quiet):
|
||||
import win32com.servers.dictionary
|
||||
|
||||
win32com.test.util.RegisterPythonServer(
|
||||
win32com.servers.dictionary.__file__, "Python.Dictionary"
|
||||
)
|
||||
|
||||
|
||||
def TestDict(quiet=None):
|
||||
if quiet is None:
|
||||
quiet = not "-v" in sys.argv
|
||||
Register(quiet)
|
||||
|
||||
if not quiet:
|
||||
print("Simple enum test")
|
||||
dict = MakeTestDictionary()
|
||||
checkDict = {}
|
||||
TestDictAgainst(dict, checkDict)
|
||||
|
||||
dict["NewKey"] = "NewValue"
|
||||
checkDict["NewKey"] = "NewValue"
|
||||
TestDictAgainst(dict, checkDict)
|
||||
|
||||
dict["NewKey"] = None
|
||||
del checkDict["NewKey"]
|
||||
TestDictAgainst(dict, checkDict)
|
||||
|
||||
now = win32timezone.now()
|
||||
# We want to keep the milliseconds but discard microseconds as they
|
||||
# don't survive the conversion.
|
||||
now = now.replace(microsecond=round(now.microsecond / 1000) * 1000)
|
||||
dict["Now"] = now
|
||||
checkDict["Now"] = now
|
||||
TestDictAgainst(dict, checkDict)
|
||||
|
||||
if not quiet:
|
||||
print("Failure tests")
|
||||
try:
|
||||
dict()
|
||||
raise Exception("default method with no args worked when it shouldn't have!")
|
||||
except pythoncom.com_error as xxx_todo_changeme:
|
||||
(hr, desc, exc, argErr) = xxx_todo_changeme.args
|
||||
assert hr == winerror.DISP_E_BADPARAMCOUNT, (
|
||||
f"Expected DISP_E_BADPARAMCOUNT - got {hr} ({desc})"
|
||||
)
|
||||
|
||||
try:
|
||||
dict("hi", "there")
|
||||
raise Exception("multiple args worked when it shouldn't have!")
|
||||
except pythoncom.com_error as xxx_todo_changeme1:
|
||||
(hr, desc, exc, argErr) = xxx_todo_changeme1.args
|
||||
assert hr == winerror.DISP_E_BADPARAMCOUNT, (
|
||||
f"Expected DISP_E_BADPARAMCOUNT - got {hr} ({desc})"
|
||||
)
|
||||
|
||||
try:
|
||||
dict(0)
|
||||
raise Exception("int key worked when it shouldn't have!")
|
||||
except pythoncom.com_error as xxx_todo_changeme2:
|
||||
(hr, desc, exc, argErr) = xxx_todo_changeme2.args
|
||||
assert hr == winerror.DISP_E_TYPEMISMATCH, (
|
||||
f"Expected DISP_E_TYPEMISMATCH - got {hr} ({desc})"
|
||||
)
|
||||
|
||||
if not quiet:
|
||||
print("Python.Dictionary tests complete.")
|
||||
|
||||
|
||||
class TestCase(win32com.test.util.TestCase):
|
||||
def testDict(self):
|
||||
TestDict()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,24 @@
|
||||
' Test Pyhon.Dictionary using VBScript - this uses
|
||||
' IDispatchEx, so is an interesting test.
|
||||
|
||||
set ob = CreateObject("Python.Dictionary")
|
||||
ob("hello") = "there"
|
||||
' Our keys are case insensitive.
|
||||
ob.Item("hi") = ob("HELLO")
|
||||
|
||||
dim ok
|
||||
ok = true
|
||||
|
||||
if ob("hello") <> "there" then
|
||||
WScript.Echo "**** The dictionary value was wrong!!"
|
||||
ok = false
|
||||
end if
|
||||
|
||||
if ob("hi") <> "there" then
|
||||
WScript.Echo "**** The other dictionary value was wrong!!"
|
||||
ok = false
|
||||
end if
|
||||
|
||||
if ok then
|
||||
WScript.Echo "VBScript has successfully tested Python.Dictionary"
|
||||
end if
|
||||
@@ -0,0 +1,84 @@
|
||||
# Test dynamic policy, and running object table.
|
||||
|
||||
import pythoncom
|
||||
import pywintypes
|
||||
import winerror
|
||||
from win32com.server.exception import COMException
|
||||
|
||||
iid = pywintypes.IID("{b48969a0-784b-11d0-ae71-d23f56000000}")
|
||||
|
||||
|
||||
class VeryPermissive:
|
||||
def _dynamic_(self, name, lcid, wFlags, args):
|
||||
if wFlags & pythoncom.DISPATCH_METHOD:
|
||||
return getattr(self, name)(*args)
|
||||
|
||||
if wFlags & pythoncom.DISPATCH_PROPERTYGET:
|
||||
try:
|
||||
# to avoid problems with byref param handling, tuple results are converted to lists.
|
||||
ret = self.__dict__[name]
|
||||
if isinstance(ret, tuple):
|
||||
ret = list(ret)
|
||||
return ret
|
||||
except KeyError: # Probably a method request.
|
||||
raise COMException(scode=winerror.DISP_E_MEMBERNOTFOUND)
|
||||
|
||||
if wFlags & (
|
||||
pythoncom.DISPATCH_PROPERTYPUT | pythoncom.DISPATCH_PROPERTYPUTREF
|
||||
):
|
||||
setattr(self, name, args[0])
|
||||
return
|
||||
|
||||
raise COMException(scode=winerror.E_INVALIDARG, desc="invalid wFlags")
|
||||
|
||||
def write(self, *args):
|
||||
if len(args) == 0:
|
||||
raise COMException(
|
||||
scode=winerror.DISP_E_BADPARAMCOUNT
|
||||
) # Probably call as PROPGET.
|
||||
|
||||
for arg in args[:-1]:
|
||||
print(arg, end=" ")
|
||||
print(args[-1])
|
||||
|
||||
|
||||
def Test():
|
||||
import win32com.server.policy
|
||||
import win32com.server.util
|
||||
|
||||
# import win32dbg;win32dbg.brk()
|
||||
ob = win32com.server.util.wrap(
|
||||
VeryPermissive(), usePolicy=win32com.server.policy.DynamicPolicy
|
||||
)
|
||||
try:
|
||||
handle = pythoncom.RegisterActiveObject(ob, iid, 0)
|
||||
except pythoncom.com_error as details:
|
||||
print("Warning - could not register the object in the ROT:", details)
|
||||
handle = None
|
||||
try:
|
||||
import win32com.client.dynamic
|
||||
|
||||
client = win32com.client.dynamic.Dispatch(iid)
|
||||
client.ANewAttr = "Hello"
|
||||
assert client.ANewAttr == "Hello", "Could not set dynamic property"
|
||||
|
||||
v = ["Hello", "From", "Python", 1.4]
|
||||
client.TestSequence = v
|
||||
assert v == list(client.TestSequence), (
|
||||
f"Dynamic sequences not working! {v!r}/{client.testSequence!r}"
|
||||
)
|
||||
|
||||
client.write("This", "output", "has", "come", "via", "testDynamic.py")
|
||||
# Check our new "_FlagAsMethod" works (kinda!)
|
||||
client._FlagAsMethod("NotReallyAMethod")
|
||||
assert callable(client.NotReallyAMethod), "Method I flagged as callable isn't!"
|
||||
|
||||
client = None
|
||||
finally:
|
||||
if handle is not None:
|
||||
pythoncom.RevokeActiveObject(handle)
|
||||
print("Test worked!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Test()
|
||||
@@ -0,0 +1,121 @@
|
||||
# TestExchange = Exchange Server Dump
|
||||
# Note that this code uses "CDO", which is unlikely to get the best choice.
|
||||
# You should use the Outlook object model, or
|
||||
# the win32com.mapi examples for a low-level interface.
|
||||
|
||||
import os
|
||||
|
||||
import pythoncom
|
||||
from win32com.client import gencache
|
||||
|
||||
ammodule = None # was the generated module!
|
||||
|
||||
|
||||
def GetDefaultProfileName():
|
||||
import win32api
|
||||
import win32con
|
||||
|
||||
try:
|
||||
key = win32api.RegOpenKey(
|
||||
win32con.HKEY_CURRENT_USER,
|
||||
"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows Messaging Subsystem\\Profiles",
|
||||
)
|
||||
try:
|
||||
return win32api.RegQueryValueEx(key, "DefaultProfile")[0]
|
||||
finally:
|
||||
key.Close()
|
||||
except win32api.error:
|
||||
return None
|
||||
|
||||
|
||||
#
|
||||
# Recursive dump of folders.
|
||||
#
|
||||
def DumpFolder(folder, indent=0):
|
||||
print(" " * indent, folder.Name)
|
||||
folders = folder.Folders
|
||||
folder = folders.GetFirst()
|
||||
while folder:
|
||||
DumpFolder(folder, indent + 1)
|
||||
folder = folders.GetNext()
|
||||
|
||||
|
||||
def DumpFolders(session):
|
||||
try:
|
||||
infostores = session.InfoStores
|
||||
except AttributeError:
|
||||
# later outlook?
|
||||
store = session.DefaultStore
|
||||
folder = store.GetRootFolder()
|
||||
DumpFolder(folder)
|
||||
return
|
||||
|
||||
print(infostores)
|
||||
print("There are %d infostores" % infostores.Count)
|
||||
for i in range(infostores.Count):
|
||||
infostore = infostores[i + 1]
|
||||
print("Infostore = ", infostore.Name)
|
||||
try:
|
||||
folder = infostore.RootFolder
|
||||
except pythoncom.com_error as details:
|
||||
hr, msg, exc, arg = details
|
||||
# -2147221219 == MAPI_E_FAILONEPROVIDER - a single provider temporarily not available.
|
||||
if exc and exc[-1] == -2147221219:
|
||||
print("This info store is currently not available")
|
||||
continue
|
||||
DumpFolder(folder)
|
||||
|
||||
|
||||
# Build a dictionary of property tags, so I can reverse look-up
|
||||
#
|
||||
PropTagsById = {}
|
||||
if ammodule:
|
||||
for name, val in ammodule.constants.__dict__.items():
|
||||
PropTagsById[val] = name
|
||||
|
||||
|
||||
def TestAddress(session):
|
||||
# entry = session.GetAddressEntry("Skip")
|
||||
# print(entry)
|
||||
pass
|
||||
|
||||
|
||||
def TestUser(session):
|
||||
ae = session.CurrentUser
|
||||
fields = getattr(ae, "Fields", [])
|
||||
print("User has %d fields" % len(fields))
|
||||
for f in range(len(fields)):
|
||||
field = fields[f + 1]
|
||||
id = PropTagsById.get(field.ID, field.ID)
|
||||
print(f"{field.Name}/{id}={field.Value}")
|
||||
|
||||
|
||||
def test():
|
||||
oldcwd = os.getcwd()
|
||||
try:
|
||||
session = gencache.EnsureDispatch("MAPI.Session")
|
||||
try:
|
||||
session.Logon(GetDefaultProfileName())
|
||||
except pythoncom.com_error as details:
|
||||
print("Could not log on to MAPI:", details)
|
||||
return
|
||||
except pythoncom.error:
|
||||
# no mapi.session - let's try outlook
|
||||
app = gencache.EnsureDispatch("Outlook.Application")
|
||||
session = app.Session
|
||||
|
||||
try:
|
||||
TestUser(session)
|
||||
TestAddress(session)
|
||||
DumpFolders(session)
|
||||
finally:
|
||||
session.Logoff()
|
||||
# It appears Exchange will change the cwd on us :(
|
||||
os.chdir(oldcwd)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from .util import CheckClean
|
||||
|
||||
test()
|
||||
CheckClean()
|
||||
@@ -0,0 +1,139 @@
|
||||
# testExplorer -
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
import pythoncom
|
||||
import win32api
|
||||
import win32com.client.dynamic
|
||||
import win32con
|
||||
import win32gui
|
||||
import winerror
|
||||
from win32com.client import Dispatch
|
||||
from win32com.test.util import CheckClean
|
||||
|
||||
bVisibleEventFired = 0
|
||||
|
||||
# These are errors we might see when this is run in automation (eg, on GitHub)
|
||||
# Not sure exactly what -2125463506 is, but google shows it's a common error
|
||||
# possibly related to how IE is configured WRT site permissions etc.
|
||||
HRESULTS_IN_AUTOMATION = [-2125463506, winerror.MK_E_UNAVAILABLE]
|
||||
|
||||
|
||||
class ExplorerEvents:
|
||||
def OnVisible(self, visible):
|
||||
global bVisibleEventFired
|
||||
bVisibleEventFired = 1
|
||||
|
||||
|
||||
def TestExplorerEvents():
|
||||
global bVisibleEventFired
|
||||
try:
|
||||
iexplore = win32com.client.DispatchWithEvents(
|
||||
"InternetExplorer.Application", ExplorerEvents
|
||||
)
|
||||
except pythoncom.com_error as exc:
|
||||
# In automation we see this error trying to connect to events
|
||||
# It's a little surprising that the non-event tests seem to work, but
|
||||
# whatever...
|
||||
if exc.hresult not in HRESULTS_IN_AUTOMATION:
|
||||
raise
|
||||
print("IE events appear to not be available, so skipping this test")
|
||||
return
|
||||
|
||||
iexplore.Visible = 1
|
||||
assert bVisibleEventFired, "The IE event did not appear to fire!"
|
||||
iexplore.Quit()
|
||||
iexplore = None
|
||||
|
||||
bVisibleEventFired = 0
|
||||
ie = win32com.client.Dispatch("InternetExplorer.Application")
|
||||
ie_events = win32com.client.DispatchWithEvents(ie, ExplorerEvents)
|
||||
ie.Visible = 1
|
||||
assert bVisibleEventFired, "The IE event did not appear to fire!"
|
||||
ie.Quit()
|
||||
ie = None
|
||||
print("IE Event tests worked.")
|
||||
|
||||
|
||||
def TestObjectFromWindow():
|
||||
# Check we can use ObjectFromLresult to get the COM object from the
|
||||
hwnd = win32gui.FindWindow("IEFrame", None)
|
||||
# Thanks https://stackoverflow.com/a/10154498/18450412 for the child stack on IE8+
|
||||
for child_class in (
|
||||
"Frame Tab",
|
||||
"TabWindowClass",
|
||||
"Shell DocObject View",
|
||||
"Internet Explorer_Server",
|
||||
):
|
||||
hwnd = win32gui.FindWindowEx(hwnd, 0, child_class, None)
|
||||
# Once you have an 'Internet Explorer_Server',
|
||||
# you can send a message and use ObjectFromLresult to get it back.
|
||||
msg = win32gui.RegisterWindowMessage("WM_HTML_GETOBJECT")
|
||||
rc, result = win32gui.SendMessageTimeout(
|
||||
hwnd, msg, 0, 0, win32con.SMTO_ABORTIFHUNG, 1000
|
||||
)
|
||||
ob = pythoncom.ObjectFromLresult(result, pythoncom.IID_IDispatch, 0)
|
||||
doc = Dispatch(ob)
|
||||
# just to prove it works, set the background color of the document.
|
||||
for color in "red green blue orange white".split():
|
||||
doc.bgColor = color
|
||||
time.sleep(0.2)
|
||||
|
||||
|
||||
def TestExplorer(iexplore):
|
||||
if not iexplore.Visible:
|
||||
iexplore.Visible = -1
|
||||
filename = os.path.join(os.path.dirname(__file__), "..\\readme.html")
|
||||
iexplore.Navigate(win32api.GetFullPathName(filename))
|
||||
win32api.Sleep(1000)
|
||||
TestObjectFromWindow()
|
||||
win32api.Sleep(3000)
|
||||
try:
|
||||
iexplore.Quit()
|
||||
except (AttributeError, pythoncom.com_error):
|
||||
# User got sick of waiting :)
|
||||
pass
|
||||
|
||||
|
||||
def TestAll():
|
||||
try:
|
||||
try:
|
||||
try:
|
||||
iexplore = win32com.client.dynamic.Dispatch(
|
||||
"InternetExplorer.Application"
|
||||
)
|
||||
except pythoncom.com_error as exc:
|
||||
if exc.hresult not in HRESULTS_IN_AUTOMATION:
|
||||
raise
|
||||
print("IE appears to not be available, so skipping this test")
|
||||
return
|
||||
|
||||
TestExplorer(iexplore)
|
||||
|
||||
win32api.Sleep(1000)
|
||||
iexplore = None
|
||||
|
||||
# Test IE events.
|
||||
TestExplorerEvents()
|
||||
# Give IE a chance to shutdown, else it can get upset on fast machines.
|
||||
time.sleep(2)
|
||||
|
||||
# Note that the TextExplorerEvents will force makepy - hence
|
||||
# this gencache is really no longer needed.
|
||||
|
||||
from win32com.client import gencache
|
||||
|
||||
gencache.EnsureModule("{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}", 0, 1, 1)
|
||||
iexplore = win32com.client.Dispatch("InternetExplorer.Application")
|
||||
TestExplorer(iexplore)
|
||||
except pythoncom.com_error as exc:
|
||||
if exc.hresult != winerror.RPC_E_DISCONNECTED: # user closed the app!
|
||||
raise
|
||||
finally:
|
||||
iexplore = None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
TestAll()
|
||||
CheckClean()
|
||||
@@ -0,0 +1,142 @@
|
||||
"""Testing pasing object between multiple COM threads
|
||||
|
||||
Uses standard COM marshalling to pass objects between threads. Even
|
||||
though Python generally seems to work when you just pass COM objects
|
||||
between threads, it shouldn't.
|
||||
|
||||
This shows the "correct" way to do it.
|
||||
|
||||
It shows that although we create new threads to use the Python.Interpreter,
|
||||
COM marshalls back all calls to that object to the main Python thread,
|
||||
which must be running a message loop (as this sample does).
|
||||
|
||||
When this test is run in "free threaded" mode (at this stage, you must
|
||||
manually mark the COM objects as "ThreadingModel=Free", or run from a
|
||||
service which has marked itself as free-threaded), then no marshalling
|
||||
is done, and the Python.Interpreter object start doing the "expected" thing
|
||||
- ie, it reports being on the same thread as its caller!
|
||||
|
||||
Python.exe needs a good way to mark itself as FreeThreaded - at the moment
|
||||
this is a pain in the but!
|
||||
"""
|
||||
|
||||
import _thread
|
||||
import traceback
|
||||
|
||||
import pythoncom
|
||||
import win32api
|
||||
import win32com.client
|
||||
import win32event
|
||||
|
||||
|
||||
def TestInterp(interp):
|
||||
assert interp.Eval("1+1") == 2, "The interpreter returned the wrong result."
|
||||
try:
|
||||
interp.Eval(1 + 1)
|
||||
raise AssertionError("The interpreter did not raise an exception")
|
||||
except pythoncom.com_error as details:
|
||||
import winerror
|
||||
|
||||
assert details[0] == winerror.DISP_E_TYPEMISMATCH, (
|
||||
"The interpreter exception was not winerror.DISP_E_TYPEMISMATCH."
|
||||
)
|
||||
|
||||
|
||||
def TestInterpInThread(stopEvent, cookie):
|
||||
try:
|
||||
DoTestInterpInThread(cookie)
|
||||
finally:
|
||||
win32event.SetEvent(stopEvent)
|
||||
|
||||
|
||||
def CreateGIT():
|
||||
return pythoncom.CoCreateInstance(
|
||||
pythoncom.CLSID_StdGlobalInterfaceTable,
|
||||
None,
|
||||
pythoncom.CLSCTX_INPROC,
|
||||
pythoncom.IID_IGlobalInterfaceTable,
|
||||
)
|
||||
|
||||
|
||||
def DoTestInterpInThread(cookie):
|
||||
try:
|
||||
pythoncom.CoInitialize()
|
||||
myThread = win32api.GetCurrentThreadId()
|
||||
GIT = CreateGIT()
|
||||
|
||||
interp = GIT.GetInterfaceFromGlobal(cookie, pythoncom.IID_IDispatch)
|
||||
interp = win32com.client.Dispatch(interp)
|
||||
|
||||
TestInterp(interp)
|
||||
interp.Exec("import win32api")
|
||||
print(
|
||||
"The test thread id is %d, Python.Interpreter's thread ID is %d"
|
||||
% (myThread, interp.Eval("win32api.GetCurrentThreadId()"))
|
||||
)
|
||||
interp = None
|
||||
pythoncom.CoUninitialize()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def BeginThreadsSimpleMarshal(numThreads, cookie):
|
||||
"""Creates multiple threads using simple (but slower) marshalling.
|
||||
|
||||
Single interpreter object, but a new stream is created per thread.
|
||||
|
||||
Returns the handles the threads will set when complete.
|
||||
"""
|
||||
ret = []
|
||||
for i in range(numThreads):
|
||||
hEvent = win32event.CreateEvent(None, 0, 0, None)
|
||||
_thread.start_new(TestInterpInThread, (hEvent, cookie))
|
||||
ret.append(hEvent)
|
||||
return ret
|
||||
|
||||
|
||||
def test(fn):
|
||||
print("The main thread is %d" % (win32api.GetCurrentThreadId()))
|
||||
GIT = CreateGIT()
|
||||
interp = win32com.client.Dispatch("Python.Interpreter")
|
||||
cookie = GIT.RegisterInterfaceInGlobal(interp._oleobj_, pythoncom.IID_IDispatch)
|
||||
|
||||
events = fn(4, cookie)
|
||||
numFinished = 0
|
||||
while 1:
|
||||
try:
|
||||
rc = win32event.MsgWaitForMultipleObjects(
|
||||
events, 0, 2000, win32event.QS_ALLINPUT
|
||||
)
|
||||
if rc >= win32event.WAIT_OBJECT_0 and rc < win32event.WAIT_OBJECT_0 + len(
|
||||
events
|
||||
):
|
||||
numFinished += 1
|
||||
if numFinished >= len(events):
|
||||
break
|
||||
elif rc == win32event.WAIT_OBJECT_0 + len(events): # a message
|
||||
# This is critical - whole apartment model demo will hang.
|
||||
pythoncom.PumpWaitingMessages()
|
||||
else: # Timeout
|
||||
print(
|
||||
"Waiting for thread to stop with interfaces=%d, gateways=%d"
|
||||
% (pythoncom._GetInterfaceCount(), pythoncom._GetGatewayCount())
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
GIT.RevokeInterfaceFromGlobal(cookie)
|
||||
del interp
|
||||
del GIT
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test(BeginThreadsSimpleMarshal)
|
||||
win32api.Sleep(500)
|
||||
# Doing CoUninit here stop pythoncom.dll hanging when DLLMain shuts-down the process
|
||||
pythoncom.CoUninitialize()
|
||||
if pythoncom._GetInterfaceCount() != 0 or pythoncom._GetGatewayCount() != 0:
|
||||
print(
|
||||
"Done with interfaces=%d, gateways=%d"
|
||||
% (pythoncom._GetInterfaceCount(), pythoncom._GetGatewayCount())
|
||||
)
|
||||
else:
|
||||
print("Done.")
|
||||
@@ -0,0 +1,149 @@
|
||||
# The purpose of this test is to ensure that the gateways objects
|
||||
# do the right thing WRT COM rules about object identity etc.
|
||||
|
||||
# Also includes a basic test that we support inheritance correctly in
|
||||
# gateway interfaces.
|
||||
|
||||
# For our test, we create an object of type IID_IPersistStorage
|
||||
# This interface derives from IPersist.
|
||||
# Therefore, QI's for IID_IDispatch, IID_IUnknown, IID_IPersist and
|
||||
# IID_IPersistStorage should all return the same gateway object.
|
||||
#
|
||||
# In addition, the interface should only need to declare itself as
|
||||
# using the IPersistStorage interface, and as the gateway derives
|
||||
# from IPersist, it should automatically be available without declaration.
|
||||
#
|
||||
# We also create an object of type IID_I??, and perform a QI for it.
|
||||
# We then jump through a number of hoops, ensuring that the objects
|
||||
# returned by the QIs follow all the rules.
|
||||
#
|
||||
# Here is Gregs summary of the rules:
|
||||
# 1) the set of supported interfaces is static and unchanging
|
||||
# 2) symmetric: if you QI an interface for that interface, it succeeds
|
||||
# 3) reflexive: if you QI against A for B, the new pointer must succeed
|
||||
# for a QI for A
|
||||
# 4) transitive: if you QI for B, then QI that for C, then QI'ing A for C
|
||||
# must succeed
|
||||
#
|
||||
#
|
||||
# Note that 1) Requires cooperation of the Python programmer. The rule to keep is:
|
||||
# "whenever you return an _object_ from _query_interface_(), you must return the
|
||||
# same object each time for a given IID. Note that you must return the same
|
||||
# _wrapped_ object
|
||||
# you
|
||||
# The rest are tested here.
|
||||
|
||||
|
||||
import pythoncom
|
||||
from win32com.server.util import wrap
|
||||
|
||||
from .util import CheckClean
|
||||
|
||||
numErrors = 0
|
||||
|
||||
|
||||
# Check that the 2 objects both have identical COM pointers.
|
||||
def CheckSameCOMObject(ob1, ob2):
|
||||
addr1 = repr(ob1).split()[6][:-1]
|
||||
addr2 = repr(ob2).split()[6][:-1]
|
||||
return addr1 == addr2
|
||||
|
||||
|
||||
# Check that the objects conform to COM identity rules.
|
||||
def CheckObjectIdentity(ob1, ob2):
|
||||
u1 = ob1.QueryInterface(pythoncom.IID_IUnknown)
|
||||
u2 = ob2.QueryInterface(pythoncom.IID_IUnknown)
|
||||
return CheckSameCOMObject(u1, u2)
|
||||
|
||||
|
||||
def FailObjectIdentity(ob1, ob2, when):
|
||||
if not CheckObjectIdentity(ob1, ob2):
|
||||
global numErrors
|
||||
numErrors += 1
|
||||
print(f"{when} are not identical ({ob1!r}, {ob2!r})")
|
||||
|
||||
|
||||
class Dummy:
|
||||
_public_methods_ = [] # We never attempt to make a call on this object.
|
||||
_com_interfaces_ = [pythoncom.IID_IPersistStorage]
|
||||
|
||||
|
||||
class Dummy2:
|
||||
_public_methods_ = [] # We never attempt to make a call on this object.
|
||||
_com_interfaces_ = [
|
||||
pythoncom.IID_IPersistStorage,
|
||||
pythoncom.IID_IExternalConnection,
|
||||
]
|
||||
|
||||
|
||||
class DelegatedDummy:
|
||||
_public_methods_ = []
|
||||
|
||||
|
||||
class Dummy3:
|
||||
_public_methods_ = [] # We never attempt to make a call on this object.
|
||||
_com_interfaces_ = [pythoncom.IID_IPersistStorage]
|
||||
|
||||
def _query_interface_(self, iid):
|
||||
if iid == pythoncom.IID_IExternalConnection:
|
||||
# This will NEVER work - can only wrap the object once!
|
||||
return wrap(DelegatedDummy())
|
||||
|
||||
|
||||
def TestGatewayInheritance():
|
||||
# By default, wrap() creates and discards a temporary object.
|
||||
# This is not necessary, but just the current implementation of wrap.
|
||||
# As the object is correctly discarded, it doesn't affect this test.
|
||||
o = wrap(Dummy(), pythoncom.IID_IPersistStorage)
|
||||
o2 = o.QueryInterface(pythoncom.IID_IUnknown)
|
||||
FailObjectIdentity(o, o2, "IID_IPersistStorage->IID_IUnknown")
|
||||
|
||||
o3 = o2.QueryInterface(pythoncom.IID_IDispatch)
|
||||
|
||||
FailObjectIdentity(o2, o3, "IID_IUnknown->IID_IDispatch")
|
||||
FailObjectIdentity(o, o3, "IID_IPersistStorage->IID_IDispatch")
|
||||
|
||||
o4 = o3.QueryInterface(pythoncom.IID_IPersistStorage)
|
||||
FailObjectIdentity(o, o4, "IID_IPersistStorage->IID_IPersistStorage(2)")
|
||||
FailObjectIdentity(o2, o4, "IID_IUnknown->IID_IPersistStorage(2)")
|
||||
FailObjectIdentity(o3, o4, "IID_IDispatch->IID_IPersistStorage(2)")
|
||||
|
||||
o5 = o4.QueryInterface(pythoncom.IID_IPersist)
|
||||
FailObjectIdentity(o, o5, "IID_IPersistStorage->IID_IPersist")
|
||||
FailObjectIdentity(o2, o5, "IID_IUnknown->IID_IPersist")
|
||||
FailObjectIdentity(o3, o5, "IID_IDispatch->IID_IPersist")
|
||||
FailObjectIdentity(o4, o5, "IID_IPersistStorage(2)->IID_IPersist")
|
||||
|
||||
|
||||
def TestMultiInterface():
|
||||
o = wrap(Dummy2(), pythoncom.IID_IPersistStorage)
|
||||
o2 = o.QueryInterface(pythoncom.IID_IExternalConnection)
|
||||
|
||||
FailObjectIdentity(o, o2, "IID_IPersistStorage->IID_IExternalConnection")
|
||||
|
||||
# Make the same QI again, to make sure it is stable.
|
||||
o22 = o.QueryInterface(pythoncom.IID_IExternalConnection)
|
||||
FailObjectIdentity(o, o22, "IID_IPersistStorage->IID_IExternalConnection")
|
||||
FailObjectIdentity(
|
||||
o2, o22, "IID_IPersistStorage->IID_IExternalConnection (stability)"
|
||||
)
|
||||
|
||||
o3 = o2.QueryInterface(pythoncom.IID_IPersistStorage)
|
||||
FailObjectIdentity(o2, o3, "IID_IExternalConnection->IID_IPersistStorage")
|
||||
FailObjectIdentity(
|
||||
o, o3, "IID_IPersistStorage->IID_IExternalConnection->IID_IPersistStorage"
|
||||
)
|
||||
|
||||
|
||||
def test():
|
||||
TestGatewayInheritance()
|
||||
TestMultiInterface()
|
||||
if numErrors == 0:
|
||||
print("Worked ok")
|
||||
else:
|
||||
print("There were", numErrors, "errors.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
CheckClean()
|
||||
@@ -0,0 +1,11 @@
|
||||
set o = CreateObject("Python.Interpreter")
|
||||
if o.Eval("1+1") <> 2 Then
|
||||
WScript.Echo "Eval('1+1') failed"
|
||||
bFailed = True
|
||||
end if
|
||||
|
||||
if bFailed then
|
||||
WScript.Echo "*********** VBScript tests failed *********"
|
||||
else
|
||||
WScript.Echo "VBScript test worked OK"
|
||||
end if
|
||||
@@ -0,0 +1,140 @@
|
||||
# Some raw iter tests. Some "high-level" iterator tests can be found in
|
||||
# testvb.py and testOutlook.py
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import pythoncom
|
||||
import win32com.server.util
|
||||
import win32com.test.util
|
||||
from win32com.client import Dispatch
|
||||
from win32com.client.gencache import EnsureDispatch
|
||||
|
||||
|
||||
class _BaseTestCase(win32com.test.util.TestCase):
|
||||
def test_enumvariant_vb(self):
|
||||
ob, iter = self.iter_factory()
|
||||
got = []
|
||||
for v in iter:
|
||||
got.append(v)
|
||||
self.assertEqual(got, self.expected_data)
|
||||
|
||||
def test_yield(self):
|
||||
ob, i = self.iter_factory()
|
||||
got = []
|
||||
for v in iter(i):
|
||||
got.append(v)
|
||||
self.assertEqual(got, self.expected_data)
|
||||
|
||||
def _do_test_nonenum(self, object):
|
||||
try:
|
||||
for i in object:
|
||||
pass
|
||||
self.fail("Could iterate over a non-iterable object")
|
||||
except TypeError:
|
||||
pass # this is expected.
|
||||
self.assertRaises(TypeError, iter, object)
|
||||
self.assertRaises(AttributeError, getattr, object, "next")
|
||||
|
||||
def test_nonenum_wrapper(self):
|
||||
# Check our raw PyIDispatch
|
||||
ob = self.object._oleobj_
|
||||
try:
|
||||
for i in ob:
|
||||
pass
|
||||
self.fail("Could iterate over a non-iterable object")
|
||||
except TypeError:
|
||||
pass # this is expected.
|
||||
self.assertRaises(TypeError, iter, ob)
|
||||
self.assertRaises(AttributeError, getattr, ob, "next")
|
||||
|
||||
# And our Dispatch wrapper
|
||||
ob = self.object
|
||||
try:
|
||||
for i in ob:
|
||||
pass
|
||||
self.fail("Could iterate over a non-iterable object")
|
||||
except TypeError:
|
||||
pass # this is expected.
|
||||
# Note that as our object may be dynamic, we *do* have a __getitem__
|
||||
# method, meaning we *can* call iter() on the object. In this case
|
||||
# actual iteration is what fails.
|
||||
# So either the 'iter(); will raise a type error, or an attempt to
|
||||
# fetch it
|
||||
try:
|
||||
next(iter(ob))
|
||||
self.fail("Expected a TypeError fetching this iterator")
|
||||
except TypeError:
|
||||
pass
|
||||
# And it should never have a 'next' method
|
||||
self.assertRaises(AttributeError, getattr, ob, "next")
|
||||
|
||||
|
||||
class VBTestCase(_BaseTestCase):
|
||||
def setUp(self):
|
||||
def factory():
|
||||
# Our VB test harness exposes a property with IEnumVariant.
|
||||
ob = self.object.EnumerableCollectionProperty
|
||||
for i in self.expected_data:
|
||||
ob.Add(i)
|
||||
# Get the raw IEnumVARIANT.
|
||||
invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET
|
||||
iter = ob._oleobj_.InvokeTypes(
|
||||
pythoncom.DISPID_NEWENUM, 0, invkind, (13, 10), ()
|
||||
)
|
||||
return ob, iter.QueryInterface(pythoncom.IID_IEnumVARIANT)
|
||||
|
||||
# We *need* generated dispatch semantics, so dynamic __getitem__ etc
|
||||
# don't get in the way of our tests.
|
||||
self.object = EnsureDispatch("PyCOMVBTest.Tester")
|
||||
self.expected_data = [1, "Two", "3"]
|
||||
self.iter_factory = factory
|
||||
|
||||
def tearDown(self):
|
||||
self.object = None
|
||||
|
||||
|
||||
# Test our client semantics, but using a wrapped Python list object.
|
||||
# This has the effect of re-using our client specific tests, but in this
|
||||
# case is exercising the server side.
|
||||
class SomeObject:
|
||||
_public_methods_ = ["GetCollection"]
|
||||
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def GetCollection(self):
|
||||
return win32com.server.util.NewCollection(self.data)
|
||||
|
||||
|
||||
class WrappedPythonCOMServerTestCase(_BaseTestCase):
|
||||
def setUp(self):
|
||||
def factory():
|
||||
ob = self.object.GetCollection()
|
||||
flags = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET
|
||||
enum = ob._oleobj_.Invoke(pythoncom.DISPID_NEWENUM, 0, flags, 1)
|
||||
return ob, enum.QueryInterface(pythoncom.IID_IEnumVARIANT)
|
||||
|
||||
self.expected_data = [1, "Two", 3]
|
||||
sv = win32com.server.util.wrap(SomeObject(self.expected_data))
|
||||
self.object = Dispatch(sv)
|
||||
self.iter_factory = factory
|
||||
|
||||
def tearDown(self):
|
||||
self.object = None
|
||||
|
||||
|
||||
def suite():
|
||||
# We don't want our base class run
|
||||
suite = unittest.TestSuite()
|
||||
for item in globals().values():
|
||||
if (
|
||||
isinstance(item, type)
|
||||
and issubclass(item, unittest.TestCase)
|
||||
and item != _BaseTestCase
|
||||
):
|
||||
suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(item))
|
||||
return suite
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=sys.argv + ["suite"])
|
||||
@@ -0,0 +1,184 @@
|
||||
# Test MSOffice
|
||||
#
|
||||
# Main purpose of test is to ensure that Dynamic COM objects
|
||||
# work as expected.
|
||||
|
||||
# Assumes Word and Excel installed on your machine.
|
||||
|
||||
import traceback
|
||||
|
||||
import pythoncom
|
||||
import win32api
|
||||
import win32com
|
||||
import win32com.client.dynamic
|
||||
from win32com.client import gencache
|
||||
from win32com.test.util import CheckClean
|
||||
|
||||
|
||||
# Test a few of the MSOffice components.
|
||||
def TestWord():
|
||||
try:
|
||||
# Office 97 - _totally_ different object model!
|
||||
word7 = win32com.client.Dispatch("Word.Basic")
|
||||
# Check if any property needed by TestWord7 is not None
|
||||
if word7.FileNew:
|
||||
print("Starting Word 7 for dynamic test")
|
||||
TestWord7(word7)
|
||||
else:
|
||||
# NOTE - using "client.Dispatch" would return an msword8.py instance!
|
||||
print("Starting Word 8 for dynamic test")
|
||||
word = win32com.client.dynamic.Dispatch("Word.Application")
|
||||
TestWord8(word)
|
||||
|
||||
word = None
|
||||
# Now we will test Dispatch without the new "lazy" capabilities
|
||||
print("Starting Word 8 for non-lazy dynamic test")
|
||||
dispatch = win32com.client.dynamic._GetGoodDispatch("Word.Application")
|
||||
typeinfo = dispatch.GetTypeInfo()
|
||||
attr = typeinfo.GetTypeAttr()
|
||||
olerepr = win32com.client.build.DispatchItem(typeinfo, attr, None, 0)
|
||||
word = win32com.client.dynamic.CDispatch(dispatch, olerepr)
|
||||
dispatch = typeinfo = attr = olerepr = None
|
||||
TestWord8(word)
|
||||
except Exception as e:
|
||||
print("Word dynamic tests failed", e)
|
||||
traceback.print_exc()
|
||||
|
||||
print("Starting MSWord for generated test")
|
||||
try:
|
||||
from win32com.client import gencache
|
||||
|
||||
word = gencache.EnsureDispatch("Word.Application.8")
|
||||
TestWord8(word)
|
||||
except Exception as e:
|
||||
print("Word generated tests failed", e)
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def TestWord7(word):
|
||||
word.FileNew()
|
||||
# If not shown, show the app.
|
||||
if not word.AppShow():
|
||||
word._proc_("AppShow")
|
||||
|
||||
for i in range(12):
|
||||
word.FormatFont(Color=i + 1, Points=i + 12)
|
||||
word.Insert("Hello from Python %d\n" % i)
|
||||
|
||||
word.FileClose(2)
|
||||
|
||||
|
||||
def TestWord8(word):
|
||||
word.Visible = 1
|
||||
doc = word.Documents.Add()
|
||||
wrange = doc.Range()
|
||||
for i in range(10):
|
||||
wrange.InsertAfter(f"Hello from Python {i + 1}\n")
|
||||
paras = doc.Paragraphs
|
||||
if int(word.Version.split(".")[0]) >= 16:
|
||||
# With Word 16 / Word 2019
|
||||
for i, p in enumerate(paras):
|
||||
p.Range.Font.ColorIndex = i + 1
|
||||
p.Range.Font.Size = 12 + (4 * i)
|
||||
else:
|
||||
# NOTE: Iterating on paras doesn't seem to work - no error, just doesn't work
|
||||
# for para in paras:
|
||||
# para().Font...
|
||||
for i in range(len(paras)):
|
||||
p = paras(i + 1)
|
||||
p.Font.ColorIndex = i + 1
|
||||
p.Font.Size = 12 + (4 * i)
|
||||
doc.Close(SaveChanges=False)
|
||||
word.Quit()
|
||||
win32api.Sleep(1000) # Wait for word to close, else we may get OA error.
|
||||
|
||||
|
||||
def TestWord8OldStyle():
|
||||
try:
|
||||
import win32com.test.Generated4Test.msword8
|
||||
except ImportError:
|
||||
print("Can not do old style test")
|
||||
|
||||
|
||||
def TextExcel(xl):
|
||||
xl.Visible = 0
|
||||
assert not xl.Visible, "Visible property is true."
|
||||
xl.Visible = 1
|
||||
assert xl.Visible, "Visible property not true."
|
||||
|
||||
xl.Workbooks.Add()
|
||||
|
||||
xl.Range("A1:C1").Value = (1, 2, 3)
|
||||
xl.Range("A2:C2").Value = ("x", "y", "z")
|
||||
xl.Range("A3:C3").Value = ("3", "2", "1")
|
||||
|
||||
for i in range(20):
|
||||
xl.Cells(i + 1, i + 1).Value = "Hi %d" % i
|
||||
|
||||
assert xl.Range("A1").Value == "Hi 0", "Single cell range failed"
|
||||
assert xl.Range("A1:B1").Value == (("Hi 0", 2),), (
|
||||
"flat-horizontal cell range failed"
|
||||
)
|
||||
assert xl.Range("A1:A2").Value == (
|
||||
("Hi 0",),
|
||||
("x",),
|
||||
), "flat-vertical cell range failed"
|
||||
assert xl.Range("A1:C3").Value == (
|
||||
("Hi 0", 2, 3),
|
||||
("x", "Hi 1", "z"),
|
||||
(3, 2, "Hi 2"),
|
||||
), "square cell range failed"
|
||||
|
||||
xl.Range("A1:C3").Value = ((3, 2, 1), ("x", "y", "z"), (1, 2, 3))
|
||||
|
||||
assert xl.Range("A1:C3").Value == (
|
||||
(3, 2, 1),
|
||||
("x", "y", "z"),
|
||||
(1, 2, 3),
|
||||
), "Range was not what I set it to!"
|
||||
|
||||
# test dates out with Excel
|
||||
xl.Cells(5, 1).Value = "Excel time"
|
||||
xl.Cells(5, 2).Formula = "=Now()"
|
||||
|
||||
import time
|
||||
|
||||
xl.Cells(6, 1).Value = "Python time"
|
||||
xl.Cells(6, 2).Value = pythoncom.MakeTime(time.time())
|
||||
xl.Cells(6, 2).NumberFormat = "d/mm/yy h:mm"
|
||||
xl.Columns("A:B").EntireColumn.AutoFit()
|
||||
|
||||
xl.Workbooks(1).Close(0)
|
||||
xl.Quit()
|
||||
|
||||
|
||||
def TestAll():
|
||||
TestWord()
|
||||
|
||||
try:
|
||||
print("Starting Excel for Dynamic test...")
|
||||
xl = win32com.client.dynamic.Dispatch("Excel.Application")
|
||||
TextExcel(xl)
|
||||
except Exception as e:
|
||||
worked = False
|
||||
print("Excel tests failed", e)
|
||||
traceback.print_exc()
|
||||
|
||||
try:
|
||||
print("Starting Excel 8 for generated excel8.py test...")
|
||||
mod = gencache.EnsureModule(
|
||||
"{00020813-0000-0000-C000-000000000046}", 0, 1, 2, bForDemand=True
|
||||
)
|
||||
xl = win32com.client.Dispatch("Excel.Application")
|
||||
TextExcel(xl)
|
||||
except ImportError:
|
||||
print("Could not import the generated Excel 97 wrapper")
|
||||
except Exception as e:
|
||||
print("Generated Excel tests failed", e)
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
TestAll()
|
||||
CheckClean()
|
||||
pythoncom.CoUninitialize()
|
||||
@@ -0,0 +1,138 @@
|
||||
# OfficeEvents - test/demonstrate events with Word and Excel.
|
||||
import msvcrt
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import types
|
||||
|
||||
import pythoncom
|
||||
from win32com.client import DispatchWithEvents
|
||||
|
||||
stopEvent = threading.Event()
|
||||
|
||||
|
||||
def TestExcel():
|
||||
class ExcelEvents:
|
||||
def OnNewWorkbook(self, wb):
|
||||
if not isinstance(wb, types.InstanceType):
|
||||
raise RuntimeError(
|
||||
"The transformer doesn't appear to have translated this for us!"
|
||||
)
|
||||
self.seen_events["OnNewWorkbook"] = None
|
||||
|
||||
def OnWindowActivate(self, wb, wn):
|
||||
if not isinstance(wb, types.InstanceType) or not isinstance(
|
||||
wn, types.InstanceType
|
||||
):
|
||||
raise RuntimeError(
|
||||
"The transformer doesn't appear to have translated this for us!"
|
||||
)
|
||||
self.seen_events["OnWindowActivate"] = None
|
||||
|
||||
def OnWindowDeactivate(self, wb, wn):
|
||||
self.seen_events["OnWindowDeactivate"] = None
|
||||
|
||||
def OnSheetDeactivate(self, sh):
|
||||
self.seen_events["OnSheetDeactivate"] = None
|
||||
|
||||
def OnSheetBeforeDoubleClick(self, Sh, Target, Cancel):
|
||||
if Target.Column % 2 == 0:
|
||||
print("You can double-click there...")
|
||||
else:
|
||||
print("You can not double-click there...")
|
||||
# This function is a void, so the result ends up in
|
||||
# the only ByRef - Cancel.
|
||||
return 1
|
||||
|
||||
class WorkbookEvents:
|
||||
def OnActivate(self):
|
||||
print("workbook OnActivate")
|
||||
|
||||
def OnBeforeRightClick(self, Target, Cancel):
|
||||
print("It's a Worksheet Event")
|
||||
|
||||
e = DispatchWithEvents("Excel.Application", ExcelEvents)
|
||||
e.seen_events = {}
|
||||
e.Visible = 1
|
||||
book = e.Workbooks.Add()
|
||||
book = DispatchWithEvents(book, WorkbookEvents)
|
||||
print("Have book", book)
|
||||
# sheet = e.Worksheets(1)
|
||||
# sheet = DispatchWithEvents(sheet, WorksheetEvents)
|
||||
|
||||
print("Double-click in a few of the Excel cells...")
|
||||
print("Press any key when finished with Excel, or wait 10 seconds...")
|
||||
if not _WaitForFinish(e, 10):
|
||||
e.Quit()
|
||||
if not _CheckSeenEvents(e, ["OnNewWorkbook", "OnWindowActivate"]):
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def TestWord():
|
||||
class WordEvents:
|
||||
def OnDocumentChange(self):
|
||||
self.seen_events["OnDocumentChange"] = None
|
||||
|
||||
def OnWindowActivate(self, doc, wn):
|
||||
self.seen_events["OnWindowActivate"] = None
|
||||
|
||||
def OnQuit(self):
|
||||
self.seen_events["OnQuit"] = None
|
||||
stopEvent.set()
|
||||
|
||||
w = DispatchWithEvents("Word.Application", WordEvents)
|
||||
w.seen_events = {}
|
||||
w.Visible = 1
|
||||
w.Documents.Add()
|
||||
print("Press any key when finished with Word, or wait 10 seconds...")
|
||||
if not _WaitForFinish(w, 10):
|
||||
w.Quit()
|
||||
if not _CheckSeenEvents(w, ["OnDocumentChange", "OnWindowActivate"]):
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _WaitForFinish(ob, timeout):
|
||||
end = time.time() + timeout
|
||||
while 1:
|
||||
if msvcrt.kbhit():
|
||||
msvcrt.getch()
|
||||
break
|
||||
pythoncom.PumpWaitingMessages()
|
||||
stopEvent.wait(0.2)
|
||||
if stopEvent.is_set():
|
||||
stopEvent.clear()
|
||||
break
|
||||
try:
|
||||
if not ob.Visible:
|
||||
# Gone invisible - we need to pretend we timed
|
||||
# out, so the app is quit.
|
||||
return 0
|
||||
except pythoncom.com_error:
|
||||
# Excel is busy (eg, editing the cell) - ignore
|
||||
pass
|
||||
if time.time() > end:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
def _CheckSeenEvents(o, events):
|
||||
rc = 1
|
||||
for e in events:
|
||||
if e not in o.seen_events:
|
||||
print("ERROR: Expected event did not trigger", e)
|
||||
rc = 0
|
||||
return rc
|
||||
|
||||
|
||||
def test():
|
||||
import sys
|
||||
|
||||
if "noword" not in sys.argv[1:]:
|
||||
TestWord()
|
||||
if "noexcel" not in sys.argv[1:]:
|
||||
TestExcel()
|
||||
print("Word and Excel event tests passed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
@@ -0,0 +1,164 @@
|
||||
"""Testing pasing object between multiple COM threads
|
||||
|
||||
Uses standard COM marshalling to pass objects between threads. Even
|
||||
though Python generally seems to work when you just pass COM objects
|
||||
between threads, it shouldn't.
|
||||
|
||||
This shows the "correct" way to do it.
|
||||
|
||||
It shows that although we create new threads to use the Python.Interpreter,
|
||||
COM marshalls back all calls to that object to the main Python thread,
|
||||
which must be running a message loop (as this sample does).
|
||||
|
||||
When this test is run in "free threaded" mode (at this stage, you must
|
||||
manually mark the COM objects as "ThreadingModel=Free", or run from a
|
||||
service which has marked itself as free-threaded), then no marshalling
|
||||
is done, and the Python.Interpreter object start doing the "expected" thing
|
||||
- ie, it reports being on the same thread as its caller!
|
||||
|
||||
Python.exe needs a good way to mark itself as FreeThreaded - at the moment
|
||||
this is a pain in the but!
|
||||
|
||||
"""
|
||||
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
import pythoncom
|
||||
import win32api
|
||||
import win32com.client
|
||||
import win32event
|
||||
|
||||
from .testServers import InterpCase
|
||||
|
||||
freeThreaded = 1
|
||||
|
||||
|
||||
class ThreadInterpCase(InterpCase):
|
||||
def _testInterpInThread(self, stopEvent, interp):
|
||||
try:
|
||||
self._doTestInThread(interp)
|
||||
finally:
|
||||
win32event.SetEvent(stopEvent)
|
||||
|
||||
def _doTestInThread(self, interp):
|
||||
pythoncom.CoInitialize()
|
||||
myThread = win32api.GetCurrentThreadId()
|
||||
|
||||
if freeThreaded:
|
||||
interp = pythoncom.CoGetInterfaceAndReleaseStream(
|
||||
interp, pythoncom.IID_IDispatch
|
||||
)
|
||||
interp = win32com.client.Dispatch(interp)
|
||||
|
||||
interp.Exec("import win32api")
|
||||
# print(f"The test thread id is {myThread}, Python.Interpreter's thread ID is {interp.Eval('win32api.GetCurrentThreadId()')}")
|
||||
pythoncom.CoUninitialize()
|
||||
|
||||
def BeginThreadsSimpleMarshal(self, numThreads):
|
||||
"""Creates multiple threads using simple (but slower) marshalling.
|
||||
|
||||
Single interpreter object, but a new stream is created per thread.
|
||||
|
||||
Returns the handles the threads will set when complete.
|
||||
"""
|
||||
interp = win32com.client.Dispatch("Python.Interpreter")
|
||||
events = []
|
||||
threads = []
|
||||
for i in range(numThreads):
|
||||
hEvent = win32event.CreateEvent(None, 0, 0, None)
|
||||
events.append(hEvent)
|
||||
interpStream = pythoncom.CoMarshalInterThreadInterfaceInStream(
|
||||
pythoncom.IID_IDispatch, interp._oleobj_
|
||||
)
|
||||
t = threading.Thread(
|
||||
target=self._testInterpInThread,
|
||||
args=(hEvent, interpStream),
|
||||
daemon=True, # so errors don't cause shutdown hang
|
||||
)
|
||||
t.start()
|
||||
threads.append(t)
|
||||
interp = None
|
||||
return threads, events
|
||||
|
||||
#
|
||||
# NOTE - this doesn't quite work - I'm not even sure it should, but Greg reckons
|
||||
# you should be able to avoid the marshal per thread!
|
||||
# I think that refers to CoMarshalInterface though...
|
||||
def BeginThreadsFastMarshal(self, numThreads):
|
||||
"""Creates multiple threads using fast (but complex) marshalling.
|
||||
|
||||
The marshal stream is created once, and each thread uses the same stream
|
||||
|
||||
Returns the handles the threads will set when complete.
|
||||
"""
|
||||
interp = win32com.client.Dispatch("Python.Interpreter")
|
||||
if freeThreaded:
|
||||
interp = pythoncom.CoMarshalInterThreadInterfaceInStream(
|
||||
pythoncom.IID_IDispatch, interp._oleobj_
|
||||
)
|
||||
events = []
|
||||
threads = []
|
||||
for i in range(numThreads):
|
||||
hEvent = win32event.CreateEvent(None, 0, 0, None)
|
||||
t = threading.Thread(
|
||||
target=self._testInterpInThread,
|
||||
args=(hEvent, interp),
|
||||
daemon=True, # so errors don't cause shutdown hang
|
||||
)
|
||||
t.start()
|
||||
events.append(hEvent)
|
||||
threads.append(t)
|
||||
return threads, events
|
||||
|
||||
def _DoTestMarshal(self, fn, bCoWait=0):
|
||||
# print(f"The main thread is {win32api.GetCurrentThreadId()}")
|
||||
threads, events = fn(2)
|
||||
numFinished = 0
|
||||
while 1:
|
||||
try:
|
||||
if bCoWait:
|
||||
rc = pythoncom.CoWaitForMultipleHandles(0, 2000, events)
|
||||
else:
|
||||
# Specifying "bWaitAll" here will wait for messages *and* all events
|
||||
# (which is pretty useless)
|
||||
rc = win32event.MsgWaitForMultipleObjects(
|
||||
events, 0, 2000, win32event.QS_ALLINPUT
|
||||
)
|
||||
if (
|
||||
rc >= win32event.WAIT_OBJECT_0
|
||||
and rc < win32event.WAIT_OBJECT_0 + len(events)
|
||||
):
|
||||
numFinished += 1
|
||||
if numFinished >= len(events):
|
||||
break
|
||||
elif rc == win32event.WAIT_OBJECT_0 + len(events): # a message
|
||||
# This is critical - whole apartment model demo will hang.
|
||||
pythoncom.PumpWaitingMessages()
|
||||
else: # Timeout
|
||||
print(
|
||||
"Waiting for thread to stop with interfaces=%d, gateways=%d"
|
||||
% (pythoncom._GetInterfaceCount(), pythoncom._GetGatewayCount())
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
for t in threads:
|
||||
t.join(2)
|
||||
self.assertFalse(t.is_alive(), "thread failed to stop!?")
|
||||
threads = None # threads hold references to args
|
||||
# Seems to be a leak here I can't locate :(
|
||||
# self.assertEqual(pythoncom._GetInterfaceCount(), 0)
|
||||
# self.assertEqual(pythoncom._GetGatewayCount(), 0)
|
||||
|
||||
def testSimpleMarshal(self):
|
||||
self._DoTestMarshal(self.BeginThreadsSimpleMarshal)
|
||||
|
||||
def testSimpleMarshalCoWait(self):
|
||||
self._DoTestMarshal(self.BeginThreadsSimpleMarshal, 1)
|
||||
|
||||
|
||||
# def testFastMarshal(self):
|
||||
# self._DoTestMarshal(self.BeginThreadsFastMarshal)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main("testMarshal")
|
||||
@@ -0,0 +1,227 @@
|
||||
import os
|
||||
|
||||
import pythoncom
|
||||
import pywintypes
|
||||
import win32api
|
||||
import win32com
|
||||
import win32com.client
|
||||
import win32com.client.dynamic
|
||||
import win32com.server.util
|
||||
import win32timezone
|
||||
import win32ui
|
||||
from win32com import storagecon
|
||||
from win32com.axcontrol import axcontrol
|
||||
from win32com.test.util import CheckClean
|
||||
|
||||
S_OK = 0
|
||||
|
||||
now = win32timezone.now()
|
||||
|
||||
|
||||
class LockBytes:
|
||||
_public_methods_ = [
|
||||
"ReadAt",
|
||||
"WriteAt",
|
||||
"Flush",
|
||||
"SetSize",
|
||||
"LockRegion",
|
||||
"UnlockRegion",
|
||||
"Stat",
|
||||
]
|
||||
_com_interfaces_ = [pythoncom.IID_ILockBytes]
|
||||
|
||||
def __init__(self, data=b""):
|
||||
self.data = data
|
||||
self.ctime = now
|
||||
self.mtime = now
|
||||
self.atime = now
|
||||
|
||||
def ReadAt(self, offset, cb):
|
||||
print("ReadAt")
|
||||
result = self.data[offset : offset + cb]
|
||||
return result
|
||||
|
||||
def WriteAt(self, offset, data):
|
||||
print("WriteAt", offset)
|
||||
print("len", len(data))
|
||||
print("data:")
|
||||
# print(data)
|
||||
if len(self.data) >= offset:
|
||||
newdata = self.data[0:offset] + data
|
||||
print(len(newdata))
|
||||
if len(self.data) >= offset + len(data):
|
||||
newdata += self.data[offset + len(data) :]
|
||||
print(len(newdata))
|
||||
self.data = newdata
|
||||
return len(data)
|
||||
|
||||
def Flush(self, whatsthis=0):
|
||||
print("Flush", whatsthis)
|
||||
fname = os.path.join(win32api.GetTempPath(), "persist.doc")
|
||||
open(fname, "wb").write(self.data)
|
||||
return S_OK
|
||||
|
||||
def SetSize(self, size):
|
||||
print("Set Size", size)
|
||||
if size > len(self.data):
|
||||
self.data += b"\000" * (size - len(self.data))
|
||||
else:
|
||||
self.data = self.data[0:size]
|
||||
return S_OK
|
||||
|
||||
def LockRegion(self, offset, size, locktype):
|
||||
print("LockRegion")
|
||||
|
||||
def UnlockRegion(self, offset, size, locktype):
|
||||
print("UnlockRegion")
|
||||
|
||||
def Stat(self, statflag):
|
||||
print("returning Stat", statflag)
|
||||
return (
|
||||
"PyMemBytes",
|
||||
storagecon.STGTY_LOCKBYTES,
|
||||
len(self.data),
|
||||
self.mtime,
|
||||
self.ctime,
|
||||
self.atime,
|
||||
storagecon.STGM_DIRECT | storagecon.STGM_READWRITE | storagecon.STGM_CREATE,
|
||||
storagecon.STGM_SHARE_EXCLUSIVE,
|
||||
"{00020905-0000-0000-C000-000000000046}",
|
||||
0, # statebits ?
|
||||
0,
|
||||
)
|
||||
|
||||
|
||||
class OleClientSite:
|
||||
_public_methods_ = [
|
||||
"SaveObject",
|
||||
"GetMoniker",
|
||||
"GetContainer",
|
||||
"ShowObject",
|
||||
"OnShowWindow",
|
||||
"RequestNewObjectLayout",
|
||||
]
|
||||
_com_interfaces_ = [axcontrol.IID_IOleClientSite]
|
||||
|
||||
def __init__(self, data=""):
|
||||
self.IPersistStorage = None
|
||||
self.IStorage = None
|
||||
|
||||
def SetIPersistStorage(self, IPersistStorage):
|
||||
self.IPersistStorage = IPersistStorage
|
||||
|
||||
def SetIStorage(self, IStorage):
|
||||
self.IStorage = IStorage
|
||||
|
||||
def SaveObject(self):
|
||||
print("SaveObject")
|
||||
if self.IPersistStorage is not None and self.IStorage is not None:
|
||||
self.IPersistStorage.Save(self.IStorage, 1)
|
||||
self.IStorage.Commit(0)
|
||||
return S_OK
|
||||
|
||||
def GetMoniker(self, dwAssign, dwWhichMoniker):
|
||||
print("GetMoniker", dwAssign, dwWhichMoniker)
|
||||
|
||||
def GetContainer(self):
|
||||
print("GetContainer")
|
||||
|
||||
def ShowObject(self):
|
||||
print("ShowObject")
|
||||
|
||||
def OnShowWindow(self, fShow):
|
||||
print("ShowObject", fShow)
|
||||
|
||||
def RequestNewObjectLayout(self):
|
||||
print("RequestNewObjectLayout")
|
||||
|
||||
|
||||
def test():
|
||||
# create a LockBytes object and
|
||||
# wrap it as a COM object
|
||||
# import win32com.server.dispatcher
|
||||
lbcom = win32com.server.util.wrap(
|
||||
LockBytes(), pythoncom.IID_ILockBytes
|
||||
) # , useDispatcher=win32com.server.dispatcher.DispatcherWin32trace)
|
||||
|
||||
# create a structured storage on the ILockBytes object
|
||||
stcom = pythoncom.StgCreateDocfileOnILockBytes(
|
||||
lbcom,
|
||||
storagecon.STGM_DIRECT
|
||||
| storagecon.STGM_CREATE
|
||||
| storagecon.STGM_READWRITE
|
||||
| storagecon.STGM_SHARE_EXCLUSIVE,
|
||||
0,
|
||||
)
|
||||
|
||||
# create our ClientSite
|
||||
ocs = OleClientSite()
|
||||
# wrap it as a COM object
|
||||
ocscom = win32com.server.util.wrap(ocs, axcontrol.IID_IOleClientSite)
|
||||
|
||||
# create a Word OLE Document, connect it to our site and our storage
|
||||
oocom = axcontrol.OleCreate(
|
||||
"{00020906-0000-0000-C000-000000000046}",
|
||||
axcontrol.IID_IOleObject,
|
||||
0,
|
||||
(0,),
|
||||
ocscom,
|
||||
stcom,
|
||||
)
|
||||
|
||||
mf = win32ui.GetMainFrame()
|
||||
hwnd = mf.GetSafeHwnd()
|
||||
|
||||
# Set the host and document name
|
||||
# for unknown reason document name becomes hostname, and document name
|
||||
# is not set, debugged it, but don't know where the problem is?
|
||||
oocom.SetHostNames("OTPython", "This is Cool")
|
||||
|
||||
# activate the OLE document
|
||||
oocom.DoVerb(-1, ocscom, 0, hwnd, mf.GetWindowRect())
|
||||
|
||||
# set the hostnames again
|
||||
oocom.SetHostNames("OTPython2", "ThisisCool2")
|
||||
|
||||
# get IDispatch of Word
|
||||
doc = win32com.client.Dispatch(oocom.QueryInterface(pythoncom.IID_IDispatch))
|
||||
|
||||
# get IPersistStorage of Word
|
||||
dpcom = oocom.QueryInterface(pythoncom.IID_IPersistStorage)
|
||||
|
||||
# let our ClientSite know the interfaces
|
||||
ocs.SetIPersistStorage(dpcom)
|
||||
ocs.SetIStorage(stcom)
|
||||
|
||||
# use IDispatch to do the Office Word test
|
||||
# pasted from TestOffice.py
|
||||
|
||||
wrange = doc.Range()
|
||||
for i in range(10):
|
||||
wrange.InsertAfter("Hello from Python %d\n" % i)
|
||||
paras = doc.Paragraphs
|
||||
for i in range(len(paras)):
|
||||
paras[i]().Font.ColorIndex = i + 1
|
||||
paras[i]().Font.Size = 12 + (4 * i)
|
||||
# XXX - note that
|
||||
# for para in paras:
|
||||
# para().Font...
|
||||
# doesn't seem to work - no error, just doesn't work
|
||||
# Should check if it works for VB!
|
||||
|
||||
dpcom.Save(stcom, 0)
|
||||
dpcom.HandsOffStorage()
|
||||
# oocom.Close(axcontrol.OLECLOSE_NOSAVE) # or OLECLOSE_SAVEIFDIRTY, but it fails???
|
||||
|
||||
# Save the ILockBytes data to "persist2.doc"
|
||||
lbcom.Flush()
|
||||
|
||||
# exiting Winword will automatically update the ILockBytes data
|
||||
# and flush it to "%TEMP%\persist.doc"
|
||||
doc.Application.Quit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
pythoncom.CoUninitialize()
|
||||
CheckClean()
|
||||
@@ -0,0 +1,80 @@
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import pythoncom
|
||||
from win32com.client import Dispatch
|
||||
from win32com.client.gencache import EnsureDispatch
|
||||
|
||||
|
||||
class PippoTester(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from win32com.test import pippo_server
|
||||
from win32com.test.util import RegisterPythonServer
|
||||
|
||||
RegisterPythonServer(pippo_server.__file__, "Python.Test.Pippo")
|
||||
# create it.
|
||||
self.object = Dispatch("Python.Test.Pippo")
|
||||
|
||||
def testLeaks(self):
|
||||
try:
|
||||
gtrc = sys.gettotalrefcount
|
||||
except AttributeError:
|
||||
print("Please run this with python_d for leak tests")
|
||||
gtrc = lambda: 0
|
||||
# note creating self.object() should have consumed our "one time" leaks
|
||||
self.object.Method1()
|
||||
start = gtrc()
|
||||
for i in range(1000):
|
||||
object = Dispatch("Python.Test.Pippo")
|
||||
object.Method1()
|
||||
object = None
|
||||
end = gtrc()
|
||||
if end - start > 5:
|
||||
self.fail("We lost %d references!" % (end - start,))
|
||||
|
||||
def testResults(self):
|
||||
rc, out1 = self.object.Method2(123, 111)
|
||||
self.assertEqual(rc, 123)
|
||||
self.assertEqual(out1, 222)
|
||||
|
||||
def testPythonArrays(self):
|
||||
self._testArray([-3, -2, -1, 0, 1, 2, 3])
|
||||
self._testArray([-3.14, -2, -0.1, 0.0, 1.1, 2.5, 3])
|
||||
|
||||
def testNumpyArrays(self):
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
print("Numpy test not possible because numpy module failed to import")
|
||||
return
|
||||
self._testArray(numpy.array([-3, -2, -1, 0, 1, 2, 3]))
|
||||
self._testArray(numpy.array([-3.14, -2, -0.1, 0.0, 1.1, 2.5, 3]))
|
||||
|
||||
def testByteArrays(self):
|
||||
self._testArray(b"abcdef")
|
||||
self._testArray(bytearray(b"abcdef"))
|
||||
|
||||
def _testArray(self, inArray):
|
||||
outArray = self.object.Method3(inArray)
|
||||
self.assertEqual(list(outArray), list(inArray))
|
||||
|
||||
def testLeaksGencache(self):
|
||||
try:
|
||||
gtrc = sys.gettotalrefcount
|
||||
except AttributeError:
|
||||
print("Please run this with python_d for leak tests")
|
||||
gtrc = lambda: 0
|
||||
# note creating self.object() should have consumed our "one time" leaks
|
||||
object = EnsureDispatch("Python.Test.Pippo")
|
||||
start = gtrc()
|
||||
for i in range(1000):
|
||||
object = EnsureDispatch("Python.Test.Pippo")
|
||||
object.Method1()
|
||||
object = None
|
||||
end = gtrc()
|
||||
if end - start > 10:
|
||||
self.fail("We lost %d references!" % (end - start,))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,963 @@
|
||||
# NOTE - Still seems to be a leak here somewhere
|
||||
# gateway count doesn't hit zero. Hence the print statements!
|
||||
|
||||
import sys
|
||||
|
||||
sys.coinit_flags = 0 # Must be free-threaded!
|
||||
import datetime
|
||||
import decimal
|
||||
import os
|
||||
import time
|
||||
|
||||
import pythoncom
|
||||
import win32com
|
||||
import win32com.test.util
|
||||
import win32timezone
|
||||
import winerror
|
||||
from win32api import CloseHandle, GetCurrentProcessId, OpenProcess
|
||||
from win32com import universal
|
||||
from win32com.client import (
|
||||
VARIANT,
|
||||
CastTo,
|
||||
DispatchBaseClass,
|
||||
Record,
|
||||
constants,
|
||||
gencache,
|
||||
register_record_class,
|
||||
)
|
||||
from win32process import GetProcessMemoryInfo
|
||||
|
||||
# This test uses a Python implemented COM server - ensure correctly registered.
|
||||
win32com.test.util.RegisterPythonServer(
|
||||
os.path.join(os.path.dirname(__file__), "..", "servers", "test_pycomtest.py"),
|
||||
"Python.Test.PyCOMTest",
|
||||
)
|
||||
|
||||
try:
|
||||
gencache.EnsureModule(
|
||||
"{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1, bForDemand=False
|
||||
)
|
||||
except pythoncom.com_error as error:
|
||||
importMsg = """*** PyCOMTest is not installed ***
|
||||
PyCOMTest is a Python test specific COM client and server.
|
||||
It is likely this server is not installed on this machine
|
||||
To install the server, you must get the win32com sources
|
||||
and build it using MS Visual C++"""
|
||||
print(f"The PyCOMTest module can not be located or generated.\n{importMsg}\n")
|
||||
raise RuntimeError(importMsg) from error
|
||||
|
||||
# We had a bg where RegisterInterfaces would fail if gencache had
|
||||
# already been run - exercise that here
|
||||
universal.RegisterInterfaces("{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1)
|
||||
|
||||
verbose = 0
|
||||
|
||||
|
||||
# Subclasses of pythoncom.com_record.
|
||||
# Registration is performed in 'TestGenerated'.
|
||||
class TestStruct1(pythoncom.com_record):
|
||||
__slots__ = ()
|
||||
TLBID = "{6BCDCB60-5605-11D0-AE5F-CADD4C000000}"
|
||||
MJVER = 1
|
||||
MNVER = 1
|
||||
LCID = 0
|
||||
GUID = "{7A4CE6A7-7959-4E85-A3C0-B41442FF0F67}"
|
||||
|
||||
|
||||
class TestStruct2(pythoncom.com_record):
|
||||
__slots__ = ()
|
||||
TLBID = "{6BCDCB60-5605-11D0-AE5F-CADD4C000000}"
|
||||
MJVER = 1
|
||||
MNVER = 1
|
||||
LCID = 0
|
||||
GUID = "{78F0EA07-B7CF-42EA-A251-A4C6269F76AF}"
|
||||
|
||||
|
||||
# We don't need to stick with the struct name in the TypeLibrary for the subclass name.
|
||||
# The following class has the same GUID as TestStruct2 from the TypeLibrary.
|
||||
class ArrayOfStructsTestStruct(pythoncom.com_record):
|
||||
__slots__ = ()
|
||||
TLBID = "{6BCDCB60-5605-11D0-AE5F-CADD4C000000}"
|
||||
MJVER = 1
|
||||
MNVER = 1
|
||||
LCID = 0
|
||||
GUID = "{78F0EA07-B7CF-42EA-A251-A4C6269F76AF}"
|
||||
|
||||
|
||||
class NotInTypeLibraryTestStruct(pythoncom.com_record):
|
||||
__slots__ = ()
|
||||
TLBID = "{6BCDCB60-5605-11D0-AE5F-CADD4C000000}"
|
||||
MJVER = 1
|
||||
MNVER = 1
|
||||
LCID = 0
|
||||
GUID = "{79BB6AC3-12DE-4AC5-88AC-225C29A58043}"
|
||||
|
||||
|
||||
def check_get_set(func, arg):
|
||||
got = func(arg)
|
||||
assert got == arg, f"{func} failed - expected {arg!r}, got {got!r}"
|
||||
|
||||
|
||||
def check_get_set_raises(exc, func, arg):
|
||||
try:
|
||||
got = func(arg)
|
||||
except exc as e:
|
||||
pass # what we expect!
|
||||
else:
|
||||
raise AssertionError(
|
||||
f"{func} with arg {arg!r} didn't raise {exc} - returned {got!r}"
|
||||
)
|
||||
|
||||
|
||||
def progress(*args):
|
||||
if verbose:
|
||||
for arg in args:
|
||||
print(arg, end=" ")
|
||||
print()
|
||||
|
||||
|
||||
def TestApplyResult(fn, args, result):
|
||||
try:
|
||||
fnName = str(fn).split()[1]
|
||||
except:
|
||||
fnName = str(fn)
|
||||
progress("Testing ", fnName)
|
||||
pref = "function " + fnName
|
||||
rc = fn(*args)
|
||||
assert rc == result, f"{pref} failed - result not {result!r} but {rc!r}"
|
||||
|
||||
|
||||
def TestConstant(constName, pyConst):
|
||||
try:
|
||||
comConst = getattr(constants, constName)
|
||||
except:
|
||||
raise AssertionError(f"Constant {constName} missing")
|
||||
assert comConst == pyConst, (
|
||||
f"Constant value wrong for {constName} - got {comConst}, wanted {pyConst}"
|
||||
)
|
||||
|
||||
|
||||
def GetMemoryUsage():
|
||||
pid = GetCurrentProcessId()
|
||||
PROCESS_QUERY_INFORMATION = 0x0400
|
||||
PROCESS_VM_READ = 0x0010
|
||||
hprocess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, pid)
|
||||
mem_info = GetProcessMemoryInfo(hprocess)
|
||||
CloseHandle(hprocess)
|
||||
return mem_info["WorkingSetSize"]
|
||||
|
||||
|
||||
# Simple handler class. This demo only fires one event.
|
||||
class RandomEventHandler:
|
||||
def _Init(self):
|
||||
self.fireds = {}
|
||||
|
||||
def OnFire(self, no):
|
||||
try:
|
||||
self.fireds[no] += 1
|
||||
except KeyError:
|
||||
self.fireds[no] = 0
|
||||
|
||||
def OnFireWithNamedParams(self, no, a_bool, out1, out2):
|
||||
# This test exists mainly to help with an old bug, where named
|
||||
# params would come in reverse.
|
||||
Missing = pythoncom.Missing
|
||||
if no is not Missing:
|
||||
# We know our impl called 'OnFire' with the same ID
|
||||
assert no in self.fireds
|
||||
assert no + 1 == out1, "expecting 'out1' param to be ID+1"
|
||||
assert no + 2 == out2, "expecting 'out2' param to be ID+2"
|
||||
# The middle must be a boolean.
|
||||
assert a_bool is Missing or isinstance(a_bool, bool), "middle param not a bool"
|
||||
return out1 + 2, out2 + 2
|
||||
|
||||
def _DumpFireds(self):
|
||||
if not self.fireds:
|
||||
print("ERROR: Nothing was received!")
|
||||
for firedId, no in self.fireds.items():
|
||||
progress("ID %d fired %d times" % (firedId, no))
|
||||
|
||||
|
||||
# Test everything which can be tested using both the "dynamic" and "generated"
|
||||
# COM objects (or when there are very subtle differences)
|
||||
def TestCommon(o, is_generated):
|
||||
progress("Getting counter")
|
||||
counter = o.GetSimpleCounter()
|
||||
TestCounter(counter, is_generated)
|
||||
|
||||
progress("Checking default args")
|
||||
rc = o.TestOptionals()
|
||||
assert rc[:-1] == ("def", 0, 1) and abs(rc[-1] - 3.14) <= 0.01, (
|
||||
"Did not get the optional values correctly",
|
||||
rc,
|
||||
)
|
||||
rc = o.TestOptionals("Hi", 2, 3, 1.1)
|
||||
assert rc[:-1] == ("Hi", 2, 3) and abs(rc[-1] - 1.1) <= 0.01, (
|
||||
"Did not get the specified optional values correctly",
|
||||
rc,
|
||||
)
|
||||
rc = o.TestOptionals2(0)
|
||||
assert rc == (0, "", 1), ("Did not get the optional2 values correctly", rc)
|
||||
rc = o.TestOptionals2(1.1, "Hi", 2)
|
||||
assert rc[1:] == ("Hi", 2) and abs(rc[0] - 1.1) <= 0.01, (
|
||||
"Did not get the specified optional2 values correctly",
|
||||
rc,
|
||||
)
|
||||
|
||||
progress("Checking getting/passing IUnknown")
|
||||
check_get_set(o.GetSetUnknown, o)
|
||||
progress("Checking getting/passing IDispatch")
|
||||
# This might be called with either the interface or the CoClass - but these
|
||||
# functions always return from the interface.
|
||||
expected_class = o.__class__
|
||||
# CoClass instances have `default_interface`
|
||||
expected_class = getattr(expected_class, "default_interface", expected_class)
|
||||
assert isinstance(o.GetSetDispatch(o), expected_class), (
|
||||
f"GetSetDispatch failed: {o.GetSetDispatch(o)!r}"
|
||||
)
|
||||
progress("Checking getting/passing IDispatch of known type")
|
||||
expected_class = o.__class__
|
||||
expected_class = getattr(expected_class, "default_interface", expected_class)
|
||||
assert o.GetSetInterface(o).__class__ == expected_class, "GetSetDispatch failed"
|
||||
|
||||
progress("Checking misc args")
|
||||
check_get_set(o.GetSetVariant, 4)
|
||||
check_get_set(o.GetSetVariant, "foo")
|
||||
check_get_set(o.GetSetVariant, o)
|
||||
|
||||
# signed/unsigned.
|
||||
check_get_set(o.GetSetInt, 0)
|
||||
check_get_set(o.GetSetInt, -1)
|
||||
check_get_set(o.GetSetInt, 1)
|
||||
|
||||
check_get_set(o.GetSetUnsignedInt, 0)
|
||||
check_get_set(o.GetSetUnsignedInt, 1)
|
||||
check_get_set(o.GetSetUnsignedInt, 0x80000000)
|
||||
# -1 is a special case - we accept a negative int (silently converting to unsigned)
|
||||
# but when getting it back we convert it to a long.
|
||||
assert o.GetSetUnsignedInt(-1) == 0xFFFFFFFF, "unsigned -1 failed"
|
||||
|
||||
check_get_set(o.GetSetLong, 0)
|
||||
check_get_set(o.GetSetLong, -1)
|
||||
check_get_set(o.GetSetLong, 1)
|
||||
|
||||
check_get_set(o.GetSetUnsignedLong, 0)
|
||||
check_get_set(o.GetSetUnsignedLong, 1)
|
||||
check_get_set(o.GetSetUnsignedLong, 0x80000000)
|
||||
# -1 is a special case - see above.
|
||||
assert o.GetSetUnsignedLong(-1) == 0xFFFFFFFF, "unsigned -1 failed"
|
||||
|
||||
# We want to explicitly test > 32 bits.
|
||||
# 'maxsize+1' is no good on 64bit platforms as it's 65 bits!
|
||||
big = 2147483647
|
||||
for l in big, big + 1, 1 << 65:
|
||||
check_get_set(o.GetSetVariant, l)
|
||||
|
||||
progress("Checking structs")
|
||||
r = o.GetStruct()
|
||||
assert r.int_value == 99 and str(r.str_value) == "Hello from C++"
|
||||
assert o.DoubleString("foo") == "foofoo"
|
||||
|
||||
progress("Checking var args")
|
||||
o.SetVarArgs("Hi", "There", "From", "Python", 1)
|
||||
assert o.GetLastVarArgs() == (
|
||||
"Hi",
|
||||
"There",
|
||||
"From",
|
||||
"Python",
|
||||
1,
|
||||
), f"VarArgs failed -{o.GetLastVarArgs()}"
|
||||
|
||||
progress("Checking arrays")
|
||||
l = []
|
||||
TestApplyResult(o.SetVariantSafeArray, (l,), len(l))
|
||||
l = [1, 2, 3, 4]
|
||||
TestApplyResult(o.SetVariantSafeArray, (l,), len(l))
|
||||
TestApplyResult(
|
||||
o.CheckVariantSafeArray,
|
||||
(
|
||||
(
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
),
|
||||
),
|
||||
1,
|
||||
)
|
||||
|
||||
# and binary
|
||||
TestApplyResult(o.SetBinSafeArray, (memoryview(b"foo\0bar"),), 7)
|
||||
|
||||
progress("Checking properties")
|
||||
o.LongProp = 3
|
||||
assert o.LongProp == o.IntProp == 3, (
|
||||
f"Property value wrong - got {o.LongProp}/{o.IntProp}"
|
||||
)
|
||||
o.LongProp = o.IntProp = -3
|
||||
assert o.LongProp == o.IntProp == -3, (
|
||||
f"Property value wrong - got {o.LongProp}/{o.IntProp}"
|
||||
)
|
||||
# This number fits in an unsigned long. Attempting to set it to a normal
|
||||
# long will involve overflow, which is to be expected. But we do
|
||||
# expect it to work in a property explicitly a VT_UI4.
|
||||
check = 3 * 10**9
|
||||
o.ULongProp = check
|
||||
assert o.ULongProp == check, (
|
||||
f"Property value wrong - got {o.ULongProp} (expected {check})"
|
||||
)
|
||||
TestApplyResult(o.Test, ("Unused", 99), 1) # A bool function
|
||||
TestApplyResult(o.Test, ("Unused", -1), 1) # A bool function
|
||||
TestApplyResult(o.Test, ("Unused", True), 1) # A bool function
|
||||
TestApplyResult(o.Test, ("Unused", 0), 0)
|
||||
TestApplyResult(o.Test, ("Unused", False), 0)
|
||||
|
||||
assert o.DoubleString("foo") == "foofoo"
|
||||
|
||||
TestConstant("ULongTest1", 0xFFFFFFFF)
|
||||
TestConstant("ULongTest2", 0x7FFFFFFF)
|
||||
TestConstant("LongTest1", -0x7FFFFFFF)
|
||||
TestConstant("LongTest2", 0x7FFFFFFF)
|
||||
TestConstant("UCharTest", 255)
|
||||
TestConstant("CharTest", -1)
|
||||
# 'Hello World', but the 'r' is the "Registered" sign (\xae)
|
||||
TestConstant("StringTest", "Hello Wo\xaeld")
|
||||
|
||||
progress("Checking dates and times")
|
||||
# For now *all* times passed must be tz-aware.
|
||||
now = win32timezone.now()
|
||||
# but conversion to and from a VARIANT loses sub-second...
|
||||
now = now.replace(microsecond=0)
|
||||
later = now + datetime.timedelta(seconds=1)
|
||||
TestApplyResult(o.EarliestDate, (now, later), now)
|
||||
|
||||
# The below used to fail with `ValueError: microsecond must be in 0..999999` - see #1655
|
||||
# https://planetcalc.com/7027/ says that float is: Sun, 25 Mar 1951 7:23:49 am
|
||||
assert o.MakeDate(18712.308206013888) == datetime.datetime.fromisoformat(
|
||||
"1951-03-25 07:23:49+00:00"
|
||||
)
|
||||
|
||||
progress("Checking currency")
|
||||
# currency.
|
||||
pythoncom.__future_currency__ = 1
|
||||
assert o.CurrencyProp == 0, f"Expecting 0, got {o.CurrencyProp!r}"
|
||||
for val in ("1234.5678", "1234.56", "1234"):
|
||||
o.CurrencyProp = decimal.Decimal(val)
|
||||
assert o.CurrencyProp == decimal.Decimal(val), f"{val} got {o.CurrencyProp!r}"
|
||||
v1 = decimal.Decimal("1234.5678")
|
||||
TestApplyResult(o.DoubleCurrency, (v1,), v1 * 2)
|
||||
|
||||
v2 = decimal.Decimal("9012.3456")
|
||||
TestApplyResult(o.AddCurrencies, (v1, v2), v1 + v2)
|
||||
|
||||
progress("Checking decimal type")
|
||||
assert o.DecimalProp == 0, f"Expecting 0, got {o.DecimalProp!r}"
|
||||
for val in (
|
||||
"1234",
|
||||
"123456789.1234",
|
||||
"-987654321.9876",
|
||||
"0.1234",
|
||||
"-0.1234",
|
||||
):
|
||||
o.DecimalProp = decimal.Decimal(val)
|
||||
assert o.DecimalProp == decimal.Decimal(val), f"{val} got {o.DecimalProp!r}"
|
||||
v1 = decimal.Decimal("1234.5678")
|
||||
TestApplyResult(o.DoubleDecimal, (v1,), v1 * 2)
|
||||
|
||||
v2 = decimal.Decimal("654.321")
|
||||
TestApplyResult(o.AddDecimals, (v1, v2), v1 + v2)
|
||||
|
||||
TestTrickyTypesWithVariants(o, is_generated)
|
||||
progress("Checking win32com.client.VARIANT")
|
||||
TestPyVariant(o, is_generated)
|
||||
|
||||
|
||||
def TestTrickyTypesWithVariants(o, is_generated):
|
||||
# Test tricky stuff with type handling and generally only works with
|
||||
# "generated" support but can be worked around using VARIANT.
|
||||
if is_generated:
|
||||
got = o.TestByRefVariant(2)
|
||||
else:
|
||||
v = VARIANT(pythoncom.VT_BYREF | pythoncom.VT_VARIANT, 2)
|
||||
o.TestByRefVariant(v)
|
||||
got = v.value
|
||||
assert got == 4, "TestByRefVariant failed"
|
||||
|
||||
if is_generated:
|
||||
got = o.TestByRefString("Foo")
|
||||
else:
|
||||
v = VARIANT(pythoncom.VT_BYREF | pythoncom.VT_BSTR, "Foo")
|
||||
o.TestByRefString(v)
|
||||
got = v.value
|
||||
assert got == "FooFoo", "TestByRefString failed"
|
||||
|
||||
# check we can pass ints as a VT_UI1
|
||||
vals = [1, 2, 3, 4]
|
||||
if is_generated:
|
||||
arg = vals
|
||||
else:
|
||||
arg = VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_UI1, vals)
|
||||
TestApplyResult(o.SetBinSafeArray, (arg,), len(vals))
|
||||
|
||||
# safearrays of doubles and floats
|
||||
vals = [0, 1.1, 2.2, 3.3]
|
||||
if is_generated:
|
||||
arg = vals
|
||||
else:
|
||||
arg = VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, vals)
|
||||
TestApplyResult(o.SetDoubleSafeArray, (arg,), len(vals))
|
||||
|
||||
if is_generated:
|
||||
arg = vals
|
||||
else:
|
||||
arg = VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R4, vals)
|
||||
TestApplyResult(o.SetFloatSafeArray, (arg,), len(vals))
|
||||
|
||||
vals = [1.1, 2.2, 3.3, 4.4]
|
||||
expected = (1.1 * 2, 2.2 * 2, 3.3 * 2, 4.4 * 2)
|
||||
if is_generated:
|
||||
TestApplyResult(o.ChangeDoubleSafeArray, (vals,), expected)
|
||||
else:
|
||||
arg = VARIANT(pythoncom.VT_BYREF | pythoncom.VT_ARRAY | pythoncom.VT_R8, vals)
|
||||
o.ChangeDoubleSafeArray(arg)
|
||||
assert arg.value == expected, "ChangeDoubleSafeArray got the wrong value"
|
||||
|
||||
if is_generated:
|
||||
got = o.DoubleInOutString("foo")
|
||||
else:
|
||||
v = VARIANT(pythoncom.VT_BYREF | pythoncom.VT_BSTR, "foo")
|
||||
o.DoubleInOutString(v)
|
||||
got = v.value
|
||||
assert got == "foofoo", got
|
||||
|
||||
val = decimal.Decimal("1234.5678")
|
||||
if is_generated:
|
||||
got = o.DoubleCurrencyByVal(val)
|
||||
else:
|
||||
v = VARIANT(pythoncom.VT_BYREF | pythoncom.VT_CY, val)
|
||||
o.DoubleCurrencyByVal(v)
|
||||
got = v.value
|
||||
assert got == val * 2
|
||||
|
||||
val = decimal.Decimal("123456789.1234")
|
||||
if is_generated:
|
||||
got = o.DoubleDecimalByVal(val)
|
||||
else:
|
||||
v = VARIANT(pythoncom.VT_BYREF | pythoncom.VT_DECIMAL, val)
|
||||
o.DoubleDecimalByVal(v)
|
||||
got = v.value
|
||||
assert got == val * 2
|
||||
|
||||
|
||||
def TestDynamic():
|
||||
progress("Testing Dynamic")
|
||||
import win32com.client.dynamic
|
||||
|
||||
o = win32com.client.dynamic.DumbDispatch("PyCOMTest.PyCOMTest")
|
||||
TestCommon(o, False)
|
||||
|
||||
counter = win32com.client.dynamic.DumbDispatch("PyCOMTest.SimpleCounter")
|
||||
TestCounter(counter, False)
|
||||
|
||||
# Dynamic doesn't know this should be an int, so we get a COM
|
||||
# TypeMismatch error.
|
||||
try:
|
||||
check_get_set_raises(ValueError, o.GetSetInt, "foo")
|
||||
raise AssertionError("no exception raised")
|
||||
except pythoncom.com_error as exc:
|
||||
if exc.hresult != winerror.DISP_E_TYPEMISMATCH:
|
||||
raise
|
||||
|
||||
arg1 = VARIANT(pythoncom.VT_R4 | pythoncom.VT_BYREF, 2.0)
|
||||
arg2 = VARIANT(pythoncom.VT_BOOL | pythoncom.VT_BYREF, True)
|
||||
arg3 = VARIANT(pythoncom.VT_I4 | pythoncom.VT_BYREF, 4)
|
||||
o.TestInOut(arg1, arg2, arg3)
|
||||
assert arg1.value == 4.0, arg1
|
||||
assert arg2.value == False
|
||||
assert arg3.value == 8
|
||||
|
||||
# damn - props with params don't work for dynamic objects :(
|
||||
# o.SetParamProp(0, 1)
|
||||
# assert o.ParamProp(0) == 1, o.paramProp(0)
|
||||
|
||||
|
||||
def TestStructByref(o, r):
|
||||
progress("Checking struct byref as [ in, out ] parameter")
|
||||
mod_r = o.ModifyStruct(r)
|
||||
# If 'TestStruct1' was registered as an instantiable subclass
|
||||
# of pythoncom.com_record, the return value should have this type.
|
||||
if isinstance(r, TestStruct1):
|
||||
assert type(mod_r) is TestStruct1
|
||||
else:
|
||||
assert type(mod_r) is pythoncom.com_record
|
||||
# We expect the input value to stay unchanged
|
||||
assert r.int_value == 99 and str(r.str_value) == "Hello from C++"
|
||||
# and the return value to reflect the modifications performed on the COM server side
|
||||
assert (
|
||||
mod_r.int_value == 100
|
||||
and str(mod_r.str_value) == "Nothing is as constant as change"
|
||||
)
|
||||
|
||||
|
||||
def TestArrayOfStructs(o, test_rec):
|
||||
progress("Testing struct with SAFEARRAY(VT_RECORD) fields.")
|
||||
rec_list = []
|
||||
for i in range(3):
|
||||
# If 'ArrayOfStructsTestStruct' and 'TestStruct1' were registered as instantiable
|
||||
# subclasses of pythoncom.com_record, we expect to work with these types.
|
||||
if isinstance(test_rec, ArrayOfStructsTestStruct):
|
||||
rec = TestStruct1()
|
||||
assert type(rec) is TestStruct1
|
||||
else:
|
||||
rec = Record("TestStruct1", o)
|
||||
assert type(rec) is pythoncom.com_record
|
||||
rec.str_value = "This is record number"
|
||||
rec.int_value = i + 1
|
||||
rec_list.append(rec)
|
||||
test_rec.array_of_records = rec_list
|
||||
test_rec.rec_count = i + 1
|
||||
assert o.VerifyArrayOfStructs(test_rec)
|
||||
|
||||
|
||||
def TestGenerated():
|
||||
# Create an instance of the server.
|
||||
from win32com.client.gencache import EnsureDispatch
|
||||
|
||||
o = EnsureDispatch("PyCOMTest.PyCOMTest")
|
||||
TestCommon(o, True)
|
||||
|
||||
counter = EnsureDispatch("PyCOMTest.SimpleCounter")
|
||||
TestCounter(counter, True)
|
||||
|
||||
# This dance lets us get a CoClass even though it's not explicitly registered.
|
||||
# This is `CoPyComTest`
|
||||
from win32com.client.CLSIDToClass import GetClass
|
||||
|
||||
coclass_o = GetClass("{8EE0C520-5605-11D0-AE5F-CADD4C000000}")()
|
||||
TestCommon(coclass_o, True)
|
||||
|
||||
# Test the regression reported in #1753
|
||||
assert bool(coclass_o)
|
||||
|
||||
# This is `CoSimpleCounter` and the counter tests should work.
|
||||
coclass = GetClass("{B88DD310-BAE8-11D0-AE86-76F2C1000000}")()
|
||||
TestCounter(coclass, True)
|
||||
|
||||
# Test plain pythoncom.com_record structs.
|
||||
progress("Testing baseclass pythoncom.com_record structs.")
|
||||
r = o.GetStruct()
|
||||
assert type(r) is pythoncom.com_record
|
||||
TestStructByref(o, r)
|
||||
test_rec = Record("TestStruct2", o)
|
||||
assert type(test_rec) is pythoncom.com_record
|
||||
TestArrayOfStructs(o, test_rec)
|
||||
|
||||
progress("Testing registration of pythoncom.com_record subclasses.")
|
||||
# Instantiating a pythoncom.com_record subclass, which has proper GUID attributes,
|
||||
# does raise a TypeError, as long as we have not registered it.
|
||||
try:
|
||||
r_sub = TestStruct1()
|
||||
except TypeError:
|
||||
pass
|
||||
except Exception as e:
|
||||
raise AssertionError from e
|
||||
else:
|
||||
raise AssertionError
|
||||
# Register the subclasses in pythoncom.
|
||||
register_record_class(TestStruct1)
|
||||
register_record_class(ArrayOfStructsTestStruct)
|
||||
# Now the type of the instance is the registered subclass.
|
||||
r_sub = TestStruct1()
|
||||
assert type(r_sub) is TestStruct1
|
||||
# Now also the 'Record' factory function returns an instance of the registered subtype.
|
||||
r_sub = Record("TestStruct1", o)
|
||||
assert type(r_sub) is TestStruct1
|
||||
# It should not be possible to register multiple classes with the same GUID, e.g.
|
||||
# 'TestStruct2' has the same GUID class attribute value as 'ArrayOfStructsTestStruct'.
|
||||
check_get_set_raises(ValueError, register_record_class, TestStruct2)
|
||||
# Also registering a class with a GUID that is not in the TypeLibrary should fail.
|
||||
check_get_set_raises(TypeError, register_record_class, NotInTypeLibraryTestStruct)
|
||||
|
||||
# Perform the 'Byref' and 'ArrayOfStruct tests using the registered subclasses.
|
||||
progress("Testing subclasses of pythoncom.com_record.")
|
||||
r = o.GetStruct()
|
||||
# After 'TestStruct1' was registered as an instantiable subclass
|
||||
# of pythoncom.com_record, the return value should have this type.
|
||||
assert type(r) is TestStruct1
|
||||
TestStructByref(o, r)
|
||||
test_rec = ArrayOfStructsTestStruct()
|
||||
assert type(test_rec) is ArrayOfStructsTestStruct
|
||||
TestArrayOfStructs(o, test_rec)
|
||||
|
||||
# Test initialization of registered pythoncom.com_record subclasses.
|
||||
progress("Testing initialization of pythoncom.com_record subclasses.")
|
||||
buf = o.GetStruct().__reduce__()[1][5]
|
||||
test_rec = TestStruct1(buf)
|
||||
assert test_rec.int_value == 99 and str(test_rec.str_value) == "Hello from C++"
|
||||
|
||||
# XXX - this is failing in dynamic tests, but should work fine.
|
||||
i1, i2 = o.GetMultipleInterfaces()
|
||||
# Yay - is now an instance returned!
|
||||
assert isinstance(i1, DispatchBaseClass) and isinstance(i2, DispatchBaseClass), (
|
||||
f"GetMultipleInterfaces did not return instances - got '{i1}', '{i2}'"
|
||||
)
|
||||
del i1
|
||||
del i2
|
||||
|
||||
# Generated knows to only pass a 32bit int, so should fail.
|
||||
check_get_set_raises(OverflowError, o.GetSetInt, 0x80000000)
|
||||
check_get_set_raises(OverflowError, o.GetSetLong, 0x80000000)
|
||||
|
||||
# Generated knows this should be an int, so raises ValueError
|
||||
check_get_set_raises(ValueError, o.GetSetInt, "foo")
|
||||
check_get_set_raises(ValueError, o.GetSetLong, "foo")
|
||||
|
||||
# Pass some non-sequence objects to our array decoder, and watch it fail.
|
||||
try:
|
||||
o.SetVariantSafeArray("foo")
|
||||
raise AssertionError("Expected a type error")
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
o.SetVariantSafeArray(666)
|
||||
raise AssertionError("Expected a type error")
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
o.GetSimpleSafeArray(None)
|
||||
TestApplyResult(o.GetSimpleSafeArray, (None,), tuple(range(10)))
|
||||
resultCheck = tuple(range(5)), tuple(range(10)), tuple(range(20))
|
||||
TestApplyResult(o.GetSafeArrays, (None, None, None), resultCheck)
|
||||
|
||||
l = []
|
||||
TestApplyResult(o.SetIntSafeArray, (l,), len(l))
|
||||
l = [1, 2, 3, 4]
|
||||
TestApplyResult(o.SetIntSafeArray, (l,), len(l))
|
||||
ll = [1, 2, 3, 0x100000000]
|
||||
TestApplyResult(o.SetLongLongSafeArray, (ll,), len(ll))
|
||||
TestApplyResult(o.SetULongLongSafeArray, (ll,), len(ll))
|
||||
|
||||
# check freeing of safe arrays
|
||||
mem_before = GetMemoryUsage()
|
||||
o.GetByteArray(50 * 1024 * 1024)
|
||||
mem_after = GetMemoryUsage()
|
||||
delta = mem_after - mem_before
|
||||
assert delta < 1024 * 1024, f"Memory not freed - delta {delta / (1024 * 1024)} MB"
|
||||
|
||||
# Tell the server to do what it does!
|
||||
TestApplyResult(o.Test2, (constants.Attr2,), constants.Attr2)
|
||||
TestApplyResult(o.Test3, (constants.Attr2,), constants.Attr2)
|
||||
TestApplyResult(o.Test4, (constants.Attr2,), constants.Attr2)
|
||||
TestApplyResult(o.Test5, (constants.Attr2,), constants.Attr2)
|
||||
|
||||
TestApplyResult(o.Test6, (constants.WideAttr1,), constants.WideAttr1)
|
||||
TestApplyResult(o.Test6, (constants.WideAttr2,), constants.WideAttr2)
|
||||
TestApplyResult(o.Test6, (constants.WideAttr3,), constants.WideAttr3)
|
||||
TestApplyResult(o.Test6, (constants.WideAttr4,), constants.WideAttr4)
|
||||
TestApplyResult(o.Test6, (constants.WideAttr5,), constants.WideAttr5)
|
||||
|
||||
TestApplyResult(o.TestInOut, (2.0, True, 4), (4.0, False, 8))
|
||||
|
||||
o.SetParamProp(0, 1)
|
||||
assert o.ParamProp(0) == 1, o.paramProp(0)
|
||||
|
||||
# Make sure CastTo works - even though it is only casting it to itself!
|
||||
o2 = CastTo(o, "IPyCOMTest")
|
||||
assert o == o2, "CastTo should have returned the same object"
|
||||
|
||||
# Do the connection point thing...
|
||||
# Create a connection object.
|
||||
progress("Testing connection points")
|
||||
o2 = win32com.client.DispatchWithEvents(o, RandomEventHandler)
|
||||
TestEvents(o2, o2)
|
||||
# and a plain "WithEvents".
|
||||
handler = win32com.client.WithEvents(o, RandomEventHandler)
|
||||
TestEvents(o, handler)
|
||||
progress("Finished generated .py test.")
|
||||
|
||||
|
||||
def TestEvents(o, handler):
|
||||
sessions = []
|
||||
handler._Init()
|
||||
try:
|
||||
for i in range(3):
|
||||
session = o.Start()
|
||||
sessions.append(session)
|
||||
time.sleep(0.5)
|
||||
finally:
|
||||
# Stop the servers
|
||||
for session in sessions:
|
||||
o.Stop(session)
|
||||
handler._DumpFireds()
|
||||
handler.close()
|
||||
|
||||
|
||||
def _TestPyVariant(o, is_generated, val, checker=None):
|
||||
if is_generated:
|
||||
vt, got = o.GetVariantAndType(val)
|
||||
else:
|
||||
# Gotta supply all 3 args with the last 2 being explicit variants to
|
||||
# get the byref behaviour.
|
||||
var_vt = VARIANT(pythoncom.VT_UI2 | pythoncom.VT_BYREF, 0)
|
||||
var_result = VARIANT(pythoncom.VT_VARIANT | pythoncom.VT_BYREF, 0)
|
||||
o.GetVariantAndType(val, var_vt, var_result)
|
||||
vt = var_vt.value
|
||||
got = var_result.value
|
||||
if checker is not None:
|
||||
checker(got)
|
||||
return
|
||||
# default checking.
|
||||
assert vt == val.varianttype, (vt, val.varianttype)
|
||||
# Handle our safe-array test - if the passed value is a list of variants,
|
||||
# compare against the actual values.
|
||||
if isinstance(val.value, (tuple, list)):
|
||||
check = [v.value if isinstance(v, VARIANT) else v for v in val.value]
|
||||
# pythoncom always returns arrays as tuples.
|
||||
got = list(got)
|
||||
else:
|
||||
check = val.value
|
||||
assert type(check) == type(got), (type(check), type(got))
|
||||
assert check == got, (check, got)
|
||||
|
||||
|
||||
def _TestPyVariantFails(o, is_generated, val, exc):
|
||||
try:
|
||||
_TestPyVariant(o, is_generated, val)
|
||||
raise AssertionError(f"Setting {val!r} didn't raise {exc}")
|
||||
except exc:
|
||||
pass
|
||||
|
||||
|
||||
def TestPyVariant(o, is_generated):
|
||||
_TestPyVariant(o, is_generated, VARIANT(pythoncom.VT_UI1, 1))
|
||||
_TestPyVariant(
|
||||
o, is_generated, VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_UI4, [1, 2, 3])
|
||||
)
|
||||
_TestPyVariant(o, is_generated, VARIANT(pythoncom.VT_BSTR, "hello"))
|
||||
_TestPyVariant(
|
||||
o,
|
||||
is_generated,
|
||||
VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_BSTR, ["hello", "there"]),
|
||||
)
|
||||
|
||||
def check_dispatch(got):
|
||||
assert isinstance(got._oleobj_, pythoncom.TypeIIDs[pythoncom.IID_IDispatch])
|
||||
|
||||
_TestPyVariant(o, is_generated, VARIANT(pythoncom.VT_DISPATCH, o), check_dispatch)
|
||||
_TestPyVariant(
|
||||
o, is_generated, VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_DISPATCH, [o])
|
||||
)
|
||||
# an array of variants each with a specific type.
|
||||
v = VARIANT(
|
||||
pythoncom.VT_ARRAY | pythoncom.VT_VARIANT,
|
||||
[
|
||||
VARIANT(pythoncom.VT_UI4, 1),
|
||||
VARIANT(pythoncom.VT_UI4, 2),
|
||||
VARIANT(pythoncom.VT_UI4, 3),
|
||||
],
|
||||
)
|
||||
_TestPyVariant(o, is_generated, v)
|
||||
|
||||
# and failures
|
||||
_TestPyVariantFails(o, is_generated, VARIANT(pythoncom.VT_UI1, "foo"), ValueError)
|
||||
|
||||
|
||||
def TestCounter(counter, bIsGenerated):
|
||||
# Test random access into container
|
||||
progress(f"Testing counter {counter!r}")
|
||||
import random
|
||||
|
||||
for i in range(50):
|
||||
num = int(random.random() * len(counter))
|
||||
try:
|
||||
# XXX - this appears broken by commit 08a14d4deb374eaa06378509cf44078ad467b9dc -
|
||||
# We shouldn't need to do generated differently than dynamic.
|
||||
if bIsGenerated:
|
||||
ret = counter.Item(num + 1)
|
||||
else:
|
||||
ret = counter[num]
|
||||
assert ret == num + 1, (
|
||||
f"Random access into element {num} failed - return was {ret!r}"
|
||||
)
|
||||
except IndexError:
|
||||
raise AssertionError(f"** IndexError accessing collection element {num}")
|
||||
|
||||
num = 0
|
||||
if bIsGenerated:
|
||||
counter.SetTestProperty(1)
|
||||
counter.TestProperty = 1 # Note this has a second, default arg.
|
||||
counter.SetTestProperty(1, 2)
|
||||
assert counter.TestPropertyWithDef == 0, "Unexpected property set value!"
|
||||
assert counter.TestPropertyNoDef(1) == 1, "Unexpected property set value!"
|
||||
else:
|
||||
pass
|
||||
# counter.TestProperty = 1
|
||||
|
||||
counter.LBound = 1
|
||||
counter.UBound = 10
|
||||
if counter.LBound != 1 or counter.UBound != 10:
|
||||
print("** Error - counter did not keep its properties")
|
||||
|
||||
if bIsGenerated:
|
||||
bounds = counter.GetBounds()
|
||||
assert bounds[0] == 1 and bounds[1] == 10, (
|
||||
"** Error - counter did not give the same properties back"
|
||||
)
|
||||
counter.SetBounds(bounds[0], bounds[1])
|
||||
|
||||
for item in counter:
|
||||
num += 1
|
||||
assert num == len(counter), (
|
||||
"*** Length of counter and loop iterations don't match ***"
|
||||
)
|
||||
assert num == 10, "*** Unexpected number of loop iterations ***"
|
||||
|
||||
try:
|
||||
counter = iter(counter)._iter_.Clone() # Test Clone() and enum directly
|
||||
except AttributeError:
|
||||
# *sob* - sometimes this is a real iterator and sometimes not :/
|
||||
progress("Finished testing counter (but skipped the iterator stuff")
|
||||
return
|
||||
counter.Reset()
|
||||
num = 0
|
||||
for item in counter:
|
||||
num += 1
|
||||
assert num == 10, f"*** Unexpected number of loop iterations - got {num} ***"
|
||||
progress("Finished testing counter")
|
||||
|
||||
|
||||
def TestLocalVTable(ob):
|
||||
# Python doesn't fully implement this interface.
|
||||
assert ob.DoubleString("foo") == "foofoo", "couldn't foofoo"
|
||||
|
||||
|
||||
###############################
|
||||
##
|
||||
## Some vtable tests of the interface
|
||||
##
|
||||
def TestVTable(clsctx=pythoncom.CLSCTX_ALL):
|
||||
# Any vtable interfaces marked as dual *should* be able to be
|
||||
# correctly implemented as IDispatch.
|
||||
ob = win32com.client.Dispatch("Python.Test.PyCOMTest")
|
||||
TestLocalVTable(ob)
|
||||
# Now test it via vtable - use some C++ code to help here as Python can't do it directly yet.
|
||||
tester = win32com.client.Dispatch("PyCOMTest.PyCOMTest")
|
||||
testee = pythoncom.CoCreateInstance(
|
||||
"Python.Test.PyCOMTest", None, clsctx, pythoncom.IID_IUnknown
|
||||
)
|
||||
# check we fail gracefully with None passed.
|
||||
try:
|
||||
tester.TestMyInterface(None)
|
||||
except pythoncom.com_error as details:
|
||||
pass
|
||||
# and a real object.
|
||||
tester.TestMyInterface(testee)
|
||||
|
||||
|
||||
def TestVTable2():
|
||||
# We once crashed creating our object with the native interface as
|
||||
# the first IID specified. We must do it _after_ the tests, so that
|
||||
# Python has already had the gateway registered from last run.
|
||||
ob = win32com.client.Dispatch("Python.Test.PyCOMTest")
|
||||
iid = pythoncom.InterfaceNames["IPyCOMTest"]
|
||||
clsid = "Python.Test.PyCOMTest"
|
||||
clsctx = pythoncom.CLSCTX_SERVER
|
||||
try:
|
||||
testee = pythoncom.CoCreateInstance(clsid, None, clsctx, iid)
|
||||
except TypeError:
|
||||
# Python can't actually _use_ this interface yet, so this is
|
||||
# "expected". Any COM error is not.
|
||||
pass
|
||||
|
||||
|
||||
def TestVTableMI():
|
||||
clsctx = pythoncom.CLSCTX_SERVER
|
||||
ob = pythoncom.CoCreateInstance(
|
||||
"Python.Test.PyCOMTestMI", None, clsctx, pythoncom.IID_IUnknown
|
||||
)
|
||||
# This inherits from IStream.
|
||||
ob.QueryInterface(pythoncom.IID_IStream)
|
||||
# This implements IStorage, specifying the IID as a string
|
||||
ob.QueryInterface(pythoncom.IID_IStorage)
|
||||
# IDispatch should always work
|
||||
ob.QueryInterface(pythoncom.IID_IDispatch)
|
||||
|
||||
iid = pythoncom.InterfaceNames["IPyCOMTest"]
|
||||
try:
|
||||
ob.QueryInterface(iid)
|
||||
except TypeError:
|
||||
# Python can't actually _use_ this interface yet, so this is
|
||||
# "expected". Any COM error is not.
|
||||
pass
|
||||
|
||||
|
||||
def TestQueryInterface(long_lived_server=0, iterations=5):
|
||||
tester = win32com.client.Dispatch("PyCOMTest.PyCOMTest")
|
||||
if long_lived_server:
|
||||
# Create a local server
|
||||
t0 = win32com.client.Dispatch(
|
||||
"Python.Test.PyCOMTest", clsctx=pythoncom.CLSCTX_LOCAL_SERVER
|
||||
)
|
||||
# Request custom interfaces a number of times
|
||||
prompt = [
|
||||
"Testing QueryInterface without long-lived local-server #%d of %d...",
|
||||
"Testing QueryInterface with long-lived local-server #%d of %d...",
|
||||
]
|
||||
|
||||
for i in range(iterations):
|
||||
progress(prompt[long_lived_server != 0] % (i + 1, iterations))
|
||||
tester.TestQueryInterface()
|
||||
|
||||
|
||||
class Tester(win32com.test.util.TestCase):
|
||||
def testVTableInProc(self):
|
||||
# We used to crash running this the second time - do it a few times
|
||||
for i in range(3):
|
||||
progress("Testing VTables in-process #%d..." % (i + 1))
|
||||
TestVTable(pythoncom.CLSCTX_INPROC_SERVER)
|
||||
|
||||
def testVTableLocalServer(self):
|
||||
for i in range(3):
|
||||
progress("Testing VTables out-of-process #%d..." % (i + 1))
|
||||
TestVTable(pythoncom.CLSCTX_LOCAL_SERVER)
|
||||
|
||||
def testVTable2(self):
|
||||
for i in range(3):
|
||||
TestVTable2()
|
||||
|
||||
def testVTableMI(self):
|
||||
for i in range(3):
|
||||
TestVTableMI()
|
||||
|
||||
def testMultiQueryInterface(self):
|
||||
TestQueryInterface(0, 6)
|
||||
# When we use the custom interface in the presence of a long-lived
|
||||
# local server, i.e. a local server that is already running when
|
||||
# we request an instance of our COM object, and remains afterwards,
|
||||
# then after repeated requests to create an instance of our object
|
||||
# the custom interface disappears -- i.e. QueryInterface fails with
|
||||
# E_NOINTERFACE. Set the upper range of the following test to 2 to
|
||||
# pass this test, i.e. TestQueryInterface(1,2)
|
||||
TestQueryInterface(1, 6)
|
||||
|
||||
def testDynamic(self):
|
||||
TestDynamic()
|
||||
|
||||
def testGenerated(self):
|
||||
TestGenerated()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# XXX - todo - Complete hack to crank threading support.
|
||||
# Should NOT be necessary
|
||||
def NullThreadFunc():
|
||||
pass
|
||||
|
||||
import _thread
|
||||
|
||||
_thread.start_new(NullThreadFunc, ())
|
||||
|
||||
if "-v" in sys.argv:
|
||||
verbose = 1
|
||||
|
||||
win32com.test.util.testmain()
|
||||
@@ -0,0 +1,33 @@
|
||||
function print(msg)
|
||||
{
|
||||
WScript.Echo(msg) ;
|
||||
}
|
||||
|
||||
function check(condition, msg)
|
||||
{
|
||||
if (!condition) {
|
||||
print("***** testPyScriptlet.js failed *****");
|
||||
print(msg);
|
||||
}
|
||||
}
|
||||
|
||||
var thisScriptEngine = ScriptEngine() ;
|
||||
|
||||
var majorVersion = ScriptEngineMajorVersion() ;
|
||||
var minorVersion = ScriptEngineMinorVersion() ;
|
||||
var buildVersion = ScriptEngineBuildVersion() ;
|
||||
|
||||
WScript.Echo(thisScriptEngine + " Version " + majorVersion + "." + minorVersion + " Build " + buildVersion) ;
|
||||
|
||||
var scriptlet = new ActiveXObject("TestPys.Scriptlet") ;
|
||||
|
||||
check(scriptlet.PyProp1=="PyScript Property1", "PyProp1 wasn't correct initial value");
|
||||
scriptlet.PyProp1 = "New Value";
|
||||
check(scriptlet.PyProp1=="New Value", "PyProp1 wasn't correct new value");
|
||||
|
||||
check(scriptlet.PyProp2=="PyScript Property2", "PyProp2 wasn't correct initial value");
|
||||
scriptlet.PyProp2 = "Another New Value";
|
||||
check(scriptlet.PyProp2=="Another New Value", "PyProp2 wasn't correct new value");
|
||||
|
||||
check(scriptlet.PyMethod1()=="PyMethod1 called", "Method1 wrong value");
|
||||
check(scriptlet.PyMethod2()=="PyMethod2 called", "Method2 wrong value");
|
||||
@@ -0,0 +1,29 @@
|
||||
import unittest
|
||||
|
||||
import pythoncom
|
||||
import win32com.test.util
|
||||
import winerror
|
||||
|
||||
|
||||
class TestROT(win32com.test.util.TestCase):
|
||||
def testit(self):
|
||||
ctx = pythoncom.CreateBindCtx()
|
||||
rot = pythoncom.GetRunningObjectTable()
|
||||
num = 0
|
||||
for mk in rot:
|
||||
name = mk.GetDisplayName(ctx, None)
|
||||
num += 1
|
||||
# Monikers themselves can iterate their contents (sometimes :)
|
||||
try:
|
||||
for sub in mk:
|
||||
num += 1
|
||||
except pythoncom.com_error as exc:
|
||||
if exc.hresult != winerror.E_NOTIMPL:
|
||||
raise
|
||||
|
||||
# if num < 2:
|
||||
# print("Only", num, "objects in the ROT - this is unusual")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,51 @@
|
||||
import unittest
|
||||
|
||||
import pythoncom
|
||||
import win32com.client.dynamic
|
||||
import win32com.test.util
|
||||
import winerror
|
||||
|
||||
|
||||
def TestConnections():
|
||||
import win32com.demos.connect
|
||||
|
||||
win32com.demos.connect.test()
|
||||
|
||||
|
||||
class InterpCase(win32com.test.util.TestCase):
|
||||
def setUp(self):
|
||||
# Ensure the correct version registered.
|
||||
from win32com.servers import interp
|
||||
|
||||
win32com.test.util.RegisterPythonServer(interp.__file__, "Python.Interpreter")
|
||||
|
||||
def _testInterp(self, interp):
|
||||
self.assertEqual(interp.Eval("1+1"), 2)
|
||||
win32com.test.util.assertRaisesCOM_HRESULT(
|
||||
self, winerror.DISP_E_TYPEMISMATCH, interp.Eval, 2
|
||||
)
|
||||
|
||||
def testInproc(self):
|
||||
interp = win32com.client.dynamic.Dispatch(
|
||||
"Python.Interpreter", clsctx=pythoncom.CLSCTX_INPROC
|
||||
)
|
||||
self._testInterp(interp)
|
||||
|
||||
def testLocalServer(self):
|
||||
interp = win32com.client.dynamic.Dispatch(
|
||||
"Python.Interpreter", clsctx=pythoncom.CLSCTX_LOCAL_SERVER
|
||||
)
|
||||
self._testInterp(interp)
|
||||
|
||||
def testAny(self):
|
||||
interp = win32com.client.dynamic.Dispatch("Python.Interpreter")
|
||||
self._testInterp(interp)
|
||||
|
||||
|
||||
class ConnectionsTestCase(win32com.test.util.TestCase):
|
||||
def testConnections(self):
|
||||
TestConnections()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main("testServers")
|
||||
@@ -0,0 +1,272 @@
|
||||
import datetime
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import pythoncom
|
||||
import pywintypes
|
||||
import win32com.test.util
|
||||
import win32con
|
||||
import win32timezone
|
||||
from win32com.shell import shell
|
||||
from win32com.shell.shellcon import *
|
||||
from win32com.storagecon import *
|
||||
|
||||
|
||||
class ShellTester(win32com.test.util.TestCase):
|
||||
def testShellLink(self):
|
||||
desktop = str(shell.SHGetSpecialFolderPath(0, CSIDL_DESKTOP))
|
||||
num = 0
|
||||
shellLink = pythoncom.CoCreateInstance(
|
||||
shell.CLSID_ShellLink,
|
||||
None,
|
||||
pythoncom.CLSCTX_INPROC_SERVER,
|
||||
shell.IID_IShellLink,
|
||||
)
|
||||
persistFile = shellLink.QueryInterface(pythoncom.IID_IPersistFile)
|
||||
names = [os.path.join(desktop, n) for n in os.listdir(desktop)]
|
||||
programs = str(shell.SHGetSpecialFolderPath(0, CSIDL_PROGRAMS))
|
||||
names.extend([os.path.join(programs, n) for n in os.listdir(programs)])
|
||||
for name in names:
|
||||
try:
|
||||
persistFile.Load(name, STGM_READ)
|
||||
except pythoncom.com_error:
|
||||
continue
|
||||
# Resolve is slow - avoid it for our tests.
|
||||
# shellLink.Resolve(0, shell.SLR_ANY_MATCH | shell.SLR_NO_UI)
|
||||
fname, findData = shellLink.GetPath(0)
|
||||
unc = shellLink.GetPath(shell.SLGP_UNCPRIORITY)[0]
|
||||
num += 1
|
||||
if num == 0:
|
||||
# This isn't a fatal error, but is unlikely.
|
||||
print(
|
||||
"Could not find any links on your desktop or programs dir, which is unusual"
|
||||
)
|
||||
|
||||
def testShellFolder(self):
|
||||
sf = shell.SHGetDesktopFolder()
|
||||
names_1 = []
|
||||
for i in sf: # Magically calls EnumObjects
|
||||
name = sf.GetDisplayNameOf(i, SHGDN_NORMAL)
|
||||
names_1.append(name)
|
||||
|
||||
# And get the enumerator manually
|
||||
enum = sf.EnumObjects(
|
||||
0, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN
|
||||
)
|
||||
names_2 = []
|
||||
for i in enum:
|
||||
name = sf.GetDisplayNameOf(i, SHGDN_NORMAL)
|
||||
names_2.append(name)
|
||||
names_1.sort()
|
||||
names_2.sort()
|
||||
self.assertEqual(names_1, names_2)
|
||||
|
||||
|
||||
class PIDLTester(win32com.test.util.TestCase):
|
||||
def _rtPIDL(self, pidl):
|
||||
pidl_str = shell.PIDLAsString(pidl)
|
||||
pidl_rt = shell.StringAsPIDL(pidl_str)
|
||||
self.assertEqual(pidl_rt, pidl)
|
||||
pidl_str_rt = shell.PIDLAsString(pidl_rt)
|
||||
self.assertEqual(pidl_str_rt, pidl_str)
|
||||
|
||||
def _rtCIDA(self, parent, kids):
|
||||
cida = parent, kids
|
||||
cida_str = shell.CIDAAsString(cida)
|
||||
cida_rt = shell.StringAsCIDA(cida_str)
|
||||
self.assertEqual(cida, cida_rt)
|
||||
cida_str_rt = shell.CIDAAsString(cida_rt)
|
||||
self.assertEqual(cida_str_rt, cida_str)
|
||||
|
||||
def testPIDL(self):
|
||||
# A PIDL of "\1" is: cb + pidl + cb
|
||||
expect = b"\03\00" + b"\1" + b"\0\0"
|
||||
self.assertEqual(shell.PIDLAsString([b"\1"]), expect)
|
||||
self._rtPIDL([b"\0"])
|
||||
self._rtPIDL([b"\1", b"\2", b"\3"])
|
||||
self._rtPIDL([b"\0" * 2048] * 2048)
|
||||
# PIDL must be a list
|
||||
self.assertRaises(TypeError, shell.PIDLAsString, "foo")
|
||||
|
||||
def testCIDA(self):
|
||||
self._rtCIDA([b"\0"], [[b"\0"]])
|
||||
self._rtCIDA([b"\1"], [[b"\2"]])
|
||||
self._rtCIDA([b"\0"], [[b"\0"], [b"\1"], [b"\2"]])
|
||||
|
||||
def testBadShortPIDL(self):
|
||||
# A too-short child element: cb + pidl + cb
|
||||
pidl = b"\01\00" + b"\1"
|
||||
self.assertRaises(ValueError, shell.StringAsPIDL, pidl)
|
||||
|
||||
# ack - tried to test too long PIDLs, but a len of 0xFFFF may not
|
||||
# always fail.
|
||||
|
||||
|
||||
class FILEGROUPDESCRIPTORTester(win32com.test.util.TestCase):
|
||||
def _getTestTimes(self):
|
||||
if issubclass(pywintypes.TimeType, datetime.datetime):
|
||||
ctime = win32timezone.now()
|
||||
# FILETIME only has ms precision...
|
||||
ctime = ctime.replace(microsecond=ctime.microsecond // 1000 * 1000)
|
||||
atime = ctime + datetime.timedelta(seconds=1)
|
||||
wtime = atime + datetime.timedelta(seconds=1)
|
||||
else:
|
||||
ctime = pywintypes.Time(11)
|
||||
atime = pywintypes.Time(12)
|
||||
wtime = pywintypes.Time(13)
|
||||
return ctime, atime, wtime
|
||||
|
||||
def _testRT(self, fd):
|
||||
fgd_string = shell.FILEGROUPDESCRIPTORAsString([fd])
|
||||
fd2 = shell.StringAsFILEGROUPDESCRIPTOR(fgd_string)[0]
|
||||
|
||||
fd = fd.copy()
|
||||
fd2 = fd2.copy()
|
||||
|
||||
# The returned objects *always* have dwFlags and cFileName.
|
||||
if "dwFlags" not in fd:
|
||||
del fd2["dwFlags"]
|
||||
if "cFileName" not in fd:
|
||||
self.assertEqual(fd2["cFileName"], "")
|
||||
del fd2["cFileName"]
|
||||
|
||||
self.assertEqual(fd, fd2)
|
||||
|
||||
def _testSimple(self, make_unicode):
|
||||
fgd = shell.FILEGROUPDESCRIPTORAsString([], make_unicode)
|
||||
header = struct.pack("i", 0)
|
||||
self.assertEqual(header, fgd[: len(header)])
|
||||
self._testRT({})
|
||||
d = {}
|
||||
fgd = shell.FILEGROUPDESCRIPTORAsString([d], make_unicode)
|
||||
header = struct.pack("i", 1)
|
||||
self.assertEqual(header, fgd[: len(header)])
|
||||
self._testRT(d)
|
||||
|
||||
def testSimpleBytes(self):
|
||||
self._testSimple(False)
|
||||
|
||||
def testSimpleUnicode(self):
|
||||
self._testSimple(True)
|
||||
|
||||
def testComplex(self):
|
||||
clsid = pywintypes.IID("{CD637886-DB8B-4b04-98B5-25731E1495BE}")
|
||||
ctime, atime, wtime = self._getTestTimes()
|
||||
d = {
|
||||
"cFileName": "foo.txt",
|
||||
"clsid": clsid,
|
||||
"sizel": (1, 2),
|
||||
"pointl": (3, 4),
|
||||
"dwFileAttributes": win32con.FILE_ATTRIBUTE_NORMAL,
|
||||
"ftCreationTime": ctime,
|
||||
"ftLastAccessTime": atime,
|
||||
"ftLastWriteTime": wtime,
|
||||
"nFileSize": sys.maxsize + 1,
|
||||
}
|
||||
self._testRT(d)
|
||||
|
||||
def testUnicode(self):
|
||||
# exercise a bug fixed in build 210 - multiple unicode objects failed.
|
||||
ctime, atime, wtime = self._getTestTimes()
|
||||
d = [
|
||||
{
|
||||
"cFileName": "foo.txt",
|
||||
"sizel": (1, 2),
|
||||
"pointl": (3, 4),
|
||||
"dwFileAttributes": win32con.FILE_ATTRIBUTE_NORMAL,
|
||||
"ftCreationTime": ctime,
|
||||
"ftLastAccessTime": atime,
|
||||
"ftLastWriteTime": wtime,
|
||||
"nFileSize": sys.maxsize + 1,
|
||||
},
|
||||
{
|
||||
"cFileName": "foo2.txt",
|
||||
"sizel": (1, 2),
|
||||
"pointl": (3, 4),
|
||||
"dwFileAttributes": win32con.FILE_ATTRIBUTE_NORMAL,
|
||||
"ftCreationTime": ctime,
|
||||
"ftLastAccessTime": atime,
|
||||
"ftLastWriteTime": wtime,
|
||||
"nFileSize": sys.maxsize + 1,
|
||||
},
|
||||
{
|
||||
"cFileName": "foo\xa9.txt",
|
||||
"sizel": (1, 2),
|
||||
"pointl": (3, 4),
|
||||
"dwFileAttributes": win32con.FILE_ATTRIBUTE_NORMAL,
|
||||
"ftCreationTime": ctime,
|
||||
"ftLastAccessTime": atime,
|
||||
"ftLastWriteTime": wtime,
|
||||
"nFileSize": sys.maxsize + 1,
|
||||
},
|
||||
]
|
||||
s = shell.FILEGROUPDESCRIPTORAsString(d, 1)
|
||||
d2 = shell.StringAsFILEGROUPDESCRIPTOR(s)
|
||||
# clobber 'dwFlags' - they are not expected to be identical
|
||||
for t in d2:
|
||||
del t["dwFlags"]
|
||||
self.assertEqual(d, d2)
|
||||
|
||||
|
||||
class FileOperationTester(win32com.test.util.TestCase):
|
||||
def setUp(self):
|
||||
import tempfile
|
||||
|
||||
self.src_name = os.path.join(tempfile.gettempdir(), "pywin32_testshell")
|
||||
self.dest_name = os.path.join(tempfile.gettempdir(), "pywin32_testshell_dest")
|
||||
self.test_data = b"Hello from\0Python"
|
||||
f = open(self.src_name, "wb")
|
||||
f.write(self.test_data)
|
||||
f.close()
|
||||
try:
|
||||
os.unlink(self.dest_name)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
for fname in (self.src_name, self.dest_name):
|
||||
if os.path.isfile(fname):
|
||||
os.unlink(fname)
|
||||
|
||||
def testCopy(self):
|
||||
s = (0, FO_COPY, self.src_name, self.dest_name) # hwnd, # operation
|
||||
|
||||
rc, aborted = shell.SHFileOperation(s)
|
||||
self.assertTrue(not aborted)
|
||||
self.assertEqual(0, rc)
|
||||
self.assertTrue(os.path.isfile(self.src_name))
|
||||
self.assertTrue(os.path.isfile(self.dest_name))
|
||||
|
||||
def testRename(self):
|
||||
s = (0, FO_RENAME, self.src_name, self.dest_name) # hwnd, # operation
|
||||
rc, aborted = shell.SHFileOperation(s)
|
||||
self.assertTrue(not aborted)
|
||||
self.assertEqual(0, rc)
|
||||
self.assertTrue(os.path.isfile(self.dest_name))
|
||||
self.assertTrue(not os.path.isfile(self.src_name))
|
||||
|
||||
def testMove(self):
|
||||
s = (0, FO_MOVE, self.src_name, self.dest_name) # hwnd, # operation
|
||||
rc, aborted = shell.SHFileOperation(s)
|
||||
self.assertTrue(not aborted)
|
||||
self.assertEqual(0, rc)
|
||||
self.assertTrue(os.path.isfile(self.dest_name))
|
||||
self.assertTrue(not os.path.isfile(self.src_name))
|
||||
|
||||
def testDelete(self):
|
||||
s = (
|
||||
0, # hwnd,
|
||||
FO_DELETE, # operation
|
||||
self.src_name,
|
||||
None,
|
||||
FOF_NOCONFIRMATION,
|
||||
)
|
||||
rc, aborted = shell.SHFileOperation(s)
|
||||
self.assertTrue(not aborted)
|
||||
self.assertEqual(0, rc)
|
||||
self.assertTrue(not os.path.isfile(self.src_name))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
win32com.test.util.testmain()
|
||||
@@ -0,0 +1,88 @@
|
||||
import unittest
|
||||
|
||||
import pythoncom
|
||||
import win32api
|
||||
import win32com.test.util
|
||||
from win32com import storagecon
|
||||
|
||||
|
||||
class TestEnum(win32com.test.util.TestCase):
|
||||
def testit(self):
|
||||
fname, tmp = win32api.GetTempFileName(win32api.GetTempPath(), "stg")
|
||||
m = storagecon.STGM_READWRITE | storagecon.STGM_SHARE_EXCLUSIVE
|
||||
## file, mode, format, attrs (always 0), IID (IStorage or IPropertySetStorage, storage options(only used with STGFMT_DOCFILE)
|
||||
pss = pythoncom.StgOpenStorageEx(
|
||||
fname, m, storagecon.STGFMT_FILE, 0, pythoncom.IID_IPropertySetStorage
|
||||
)
|
||||
### {"Version":2,"reserved":0,"SectorSize":512,"TemplateFile":'somefilename'})
|
||||
|
||||
## FMTID_SummaryInformation FMTID_DocSummaryInformation FMTID_UserDefinedProperties
|
||||
psuser = pss.Create(
|
||||
pythoncom.FMTID_UserDefinedProperties,
|
||||
pythoncom.IID_IPropertySetStorage,
|
||||
storagecon.PROPSETFLAG_DEFAULT,
|
||||
storagecon.STGM_READWRITE
|
||||
| storagecon.STGM_CREATE
|
||||
| storagecon.STGM_SHARE_EXCLUSIVE,
|
||||
) ## it's very picky about flag combinations!
|
||||
psuser.WriteMultiple((3, 4), ("hey", "bubba"))
|
||||
psuser.WritePropertyNames((3, 4), ("property3", "property4"))
|
||||
expected_summaries = []
|
||||
expected_summaries.append(("property3", 3, pythoncom.VT_BSTR))
|
||||
expected_summaries.append(("property4", 4, pythoncom.VT_BSTR))
|
||||
psuser = None
|
||||
|
||||
pssum = pss.Create(
|
||||
pythoncom.FMTID_SummaryInformation,
|
||||
pythoncom.IID_IPropertySetStorage,
|
||||
storagecon.PROPSETFLAG_DEFAULT,
|
||||
storagecon.STGM_READWRITE
|
||||
| storagecon.STGM_CREATE
|
||||
| storagecon.STGM_SHARE_EXCLUSIVE,
|
||||
)
|
||||
pssum.WriteMultiple(
|
||||
(storagecon.PIDSI_AUTHOR, storagecon.PIDSI_COMMENTS), ("me", "comment")
|
||||
)
|
||||
|
||||
pssum = None
|
||||
pss = None ## doesn't seem to be a close or release method, and you can't even reopen it from the same process until previous object is gone
|
||||
|
||||
pssread = pythoncom.StgOpenStorageEx(
|
||||
fname,
|
||||
storagecon.STGM_READ | storagecon.STGM_SHARE_EXCLUSIVE,
|
||||
storagecon.STGFMT_FILE,
|
||||
0,
|
||||
pythoncom.IID_IPropertySetStorage,
|
||||
)
|
||||
found_summaries = []
|
||||
for psstat in pssread:
|
||||
ps = pssread.Open(
|
||||
psstat[0], storagecon.STGM_READ | storagecon.STGM_SHARE_EXCLUSIVE
|
||||
)
|
||||
for p in ps:
|
||||
p_val = ps.ReadMultiple((p[1],))[0]
|
||||
if (p[1] == storagecon.PIDSI_AUTHOR and p_val == "me") or (
|
||||
p[1] == storagecon.PIDSI_COMMENTS and p_val == "comment"
|
||||
):
|
||||
pass
|
||||
else:
|
||||
self.fail(f"Uxexpected property {p}/{p_val}")
|
||||
ps = None
|
||||
## FMTID_UserDefinedProperties can't exist without FMTID_DocSummaryInformation, and isn't returned independently from Enum
|
||||
## also can't be open at same time
|
||||
if psstat[0] == pythoncom.FMTID_DocSummaryInformation:
|
||||
ps = pssread.Open(
|
||||
pythoncom.FMTID_UserDefinedProperties,
|
||||
storagecon.STGM_READ | storagecon.STGM_SHARE_EXCLUSIVE,
|
||||
)
|
||||
for p in ps:
|
||||
found_summaries.append(p)
|
||||
ps = None
|
||||
psread = None
|
||||
expected_summaries.sort()
|
||||
found_summaries.sort()
|
||||
self.assertEqual(expected_summaries, found_summaries)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,147 @@
|
||||
import unittest
|
||||
|
||||
import pythoncom
|
||||
import win32com.server.util
|
||||
import win32com.test.util
|
||||
|
||||
|
||||
class Persists:
|
||||
_public_methods_ = [
|
||||
"GetClassID",
|
||||
"IsDirty",
|
||||
"Load",
|
||||
"Save",
|
||||
"GetSizeMax",
|
||||
"InitNew",
|
||||
]
|
||||
_com_interfaces_ = [pythoncom.IID_IPersistStreamInit]
|
||||
|
||||
def __init__(self):
|
||||
self.data = b"abcdefg"
|
||||
self.dirty = 1
|
||||
|
||||
def GetClassID(self):
|
||||
return pythoncom.IID_NULL
|
||||
|
||||
def IsDirty(self):
|
||||
return self.dirty
|
||||
|
||||
def Load(self, stream):
|
||||
self.data = stream.Read(26)
|
||||
|
||||
def Save(self, stream, clearDirty):
|
||||
stream.Write(self.data)
|
||||
if clearDirty:
|
||||
self.dirty = 0
|
||||
|
||||
def GetSizeMax(self):
|
||||
return 1024
|
||||
|
||||
def InitNew(self):
|
||||
pass
|
||||
|
||||
|
||||
class Stream:
|
||||
_public_methods_ = ["Read", "Write", "Seek"]
|
||||
_com_interfaces_ = [pythoncom.IID_IStream]
|
||||
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.index = 0
|
||||
|
||||
def Read(self, amount):
|
||||
result = self.data[self.index : self.index + amount]
|
||||
self.index += amount
|
||||
return result
|
||||
|
||||
def Write(self, data):
|
||||
self.data = data
|
||||
self.index = 0
|
||||
return len(data)
|
||||
|
||||
def Seek(self, dist, origin):
|
||||
if origin == pythoncom.STREAM_SEEK_SET:
|
||||
self.index = dist
|
||||
elif origin == pythoncom.STREAM_SEEK_CUR:
|
||||
self.index += dist
|
||||
elif origin == pythoncom.STREAM_SEEK_END:
|
||||
self.index = len(self.data) + dist
|
||||
else:
|
||||
raise ValueError("Unknown Seek type: " + str(origin))
|
||||
if self.index < 0:
|
||||
self.index = 0
|
||||
else:
|
||||
self.index = min(self.index, len(self.data))
|
||||
return self.index
|
||||
|
||||
|
||||
class BadStream(Stream):
|
||||
"""PyGStream::Read could formerly overflow buffer if the python implementation
|
||||
returned more data than requested.
|
||||
"""
|
||||
|
||||
def Read(self, amount):
|
||||
return b"x" * (amount + 1)
|
||||
|
||||
|
||||
class StreamTest(win32com.test.util.TestCase):
|
||||
def _readWrite(self, data, write_stream, read_stream=None):
|
||||
if read_stream is None:
|
||||
read_stream = write_stream
|
||||
write_stream.Write(data)
|
||||
read_stream.Seek(0, pythoncom.STREAM_SEEK_SET)
|
||||
got = read_stream.Read(len(data))
|
||||
self.assertEqual(data, got)
|
||||
read_stream.Seek(1, pythoncom.STREAM_SEEK_SET)
|
||||
got = read_stream.Read(len(data) - 2)
|
||||
self.assertEqual(data[1:-1], got)
|
||||
|
||||
def testit(self):
|
||||
mydata = b"abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
# First test the objects just as Python objects...
|
||||
s = Stream(mydata)
|
||||
p = Persists()
|
||||
|
||||
p.Load(s)
|
||||
p.Save(s, 0)
|
||||
self.assertEqual(s.data, mydata)
|
||||
|
||||
# Wrap the Python objects as COM objects, and make the calls as if
|
||||
# they were non-Python COM objects.
|
||||
s2 = win32com.server.util.wrap(s, pythoncom.IID_IStream)
|
||||
p2 = win32com.server.util.wrap(p, pythoncom.IID_IPersistStreamInit)
|
||||
|
||||
self._readWrite(mydata, s, s)
|
||||
self._readWrite(mydata, s, s2)
|
||||
self._readWrite(mydata, s2, s)
|
||||
self._readWrite(mydata, s2, s2)
|
||||
|
||||
self._readWrite(b"string with\0a NULL", s2, s2)
|
||||
# reset the stream
|
||||
s.Write(mydata)
|
||||
p2.Load(s2)
|
||||
p2.Save(s2, 0)
|
||||
self.assertEqual(s.data, mydata)
|
||||
|
||||
def testseek(self):
|
||||
s = Stream(b"yo")
|
||||
s = win32com.server.util.wrap(s, pythoncom.IID_IStream)
|
||||
# we used to die passing a value > 32bits
|
||||
s.Seek(0x100000000, pythoncom.STREAM_SEEK_SET)
|
||||
|
||||
def testerrors(self):
|
||||
# setup a test logger to capture tracebacks etc.
|
||||
records, old_log = win32com.test.util.setup_test_logger()
|
||||
## check for buffer overflow in Read method
|
||||
badstream = BadStream("Check for buffer overflow")
|
||||
badstream2 = win32com.server.util.wrap(badstream, pythoncom.IID_IStream)
|
||||
self.assertRaises(pythoncom.com_error, badstream2.Read, 10)
|
||||
win32com.test.util.restore_test_logger(old_log)
|
||||
# there's 1 error here
|
||||
self.assertEqual(len(records), 1)
|
||||
self.assertTrue(records[0].msg.startswith("pythoncom error"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,18 @@
|
||||
import unittest
|
||||
|
||||
import win32com.test.util
|
||||
from win32com.client import GetObject
|
||||
|
||||
|
||||
class Simple(win32com.test.util.TestCase):
|
||||
def testit(self):
|
||||
cses = GetObject("WinMgMts:").InstancesOf("Win32_Process")
|
||||
vals = []
|
||||
for cs in cses:
|
||||
val = cs.Properties_("Caption").Value
|
||||
vals.append(val)
|
||||
self.assertFalse(len(vals) < 5, "We only found %d processes!" % len(vals))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,321 @@
|
||||
import getopt
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
import unittest
|
||||
|
||||
try:
|
||||
this_file = __file__
|
||||
except NameError:
|
||||
this_file = sys.argv[0]
|
||||
|
||||
win32com_src_dir = os.path.abspath(os.path.join(this_file, "../.."))
|
||||
|
||||
import win32com
|
||||
|
||||
# We'd prefer the win32com namespace to be the parent of __file__ - ie, our source-tree,
|
||||
# rather than the version installed - otherwise every .py change needs a full install to
|
||||
# test!
|
||||
# We can't patch win32comext as most of them have a .pyd in their root :(
|
||||
# This clearly isn't ideal or perfect :)
|
||||
win32com.__path__[0] = win32com_src_dir
|
||||
|
||||
import pythoncom
|
||||
import win32com.client
|
||||
from pywin32_testutil import TestLoader, TestRunner
|
||||
from win32com.test.util import (
|
||||
CapturingFunctionTestCase,
|
||||
CheckClean,
|
||||
RegisterPythonServer,
|
||||
ShellTestCase,
|
||||
TestCase,
|
||||
)
|
||||
|
||||
verbosity = 1 # default unittest verbosity.
|
||||
|
||||
|
||||
def GenerateAndRunOldStyle():
|
||||
from . import GenTestScripts
|
||||
|
||||
GenTestScripts.GenerateAll()
|
||||
try:
|
||||
pass #
|
||||
finally:
|
||||
GenTestScripts.CleanAll()
|
||||
|
||||
|
||||
def CleanGenerated():
|
||||
import shutil
|
||||
|
||||
import win32com
|
||||
|
||||
if os.path.isdir(win32com.__gen_path__):
|
||||
if verbosity > 1:
|
||||
print("Deleting files from %s" % (win32com.__gen_path__))
|
||||
shutil.rmtree(win32com.__gen_path__)
|
||||
import win32com.client.gencache
|
||||
|
||||
win32com.client.gencache.__init__() # Reset
|
||||
|
||||
|
||||
def RemoveRefCountOutput(data):
|
||||
while 1:
|
||||
last_line_pos = data.rfind("\n")
|
||||
if not re.match(r"\[\d+ refs\]", data[last_line_pos + 1 :]):
|
||||
break
|
||||
if last_line_pos < 0:
|
||||
# All the output
|
||||
return ""
|
||||
data = data[:last_line_pos]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def ExecuteSilentlyIfOK(cmd, testcase):
|
||||
f = os.popen(cmd)
|
||||
data = f.read().strip()
|
||||
rc = f.close()
|
||||
if rc:
|
||||
print(data)
|
||||
testcase.fail("Executing '%s' failed (%d)" % (cmd, rc))
|
||||
# for "_d" builds, strip the '[xxx refs]' line
|
||||
return RemoveRefCountOutput(data)
|
||||
|
||||
|
||||
class PyCOMTest(TestCase):
|
||||
no_leak_tests = True # done by the test itself
|
||||
|
||||
def testit(self):
|
||||
# Check that the item is registered, so we get the correct
|
||||
# 'skipped' behaviour (and recorded as such) rather than either
|
||||
# error or silence due to non-registration.
|
||||
RegisterPythonServer(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__), "..", "servers", "test_pycomtest.py"
|
||||
),
|
||||
"Python.Test.PyCOMTest",
|
||||
)
|
||||
|
||||
# Execute testPyComTest in its own process so it can play
|
||||
# with the Python thread state
|
||||
fname = os.path.join(os.path.dirname(this_file), "testPyComTest.py")
|
||||
cmd = f'{sys.executable} "{fname}" -q 2>&1'
|
||||
data = ExecuteSilentlyIfOK(cmd, self)
|
||||
|
||||
|
||||
class PippoTest(TestCase):
|
||||
def testit(self):
|
||||
# Check we are registered before spawning the process.
|
||||
from win32com.test import pippo_server
|
||||
|
||||
RegisterPythonServer(pippo_server.__file__, "Python.Test.Pippo")
|
||||
|
||||
python = sys.executable
|
||||
fname = os.path.join(os.path.dirname(this_file), "testPippo.py")
|
||||
cmd = f'{python} "{fname}" 2>&1'
|
||||
ExecuteSilentlyIfOK(cmd, self)
|
||||
|
||||
|
||||
# This is a list of "win32com.test.???" module names, optionally with a
|
||||
# function in that module if the module isn't unitest based...
|
||||
unittest_modules = [
|
||||
# Level 1 tests - fast and few dependencies - good for CI!
|
||||
"""testIterators testvbscript_regexp testStorage
|
||||
testStreams testWMI policySemantics testShell testROT
|
||||
testxslt testCollections
|
||||
errorSemantics.test testArrays
|
||||
testClipboard
|
||||
testConversionErrors
|
||||
""".split(),
|
||||
# Level 2 tests - wants our demo COM objects registered.
|
||||
# (these are strange; on GitHub CI they get further than expected when
|
||||
# our objects are not installed, so fail to quietly fail with "can't
|
||||
# register" like they do locally. So really just a nod to CI)
|
||||
"""
|
||||
testAXScript testDictionary testServers testvb testMarshal
|
||||
""".split(),
|
||||
# Level 3 tests - Requires Office or other non-free stuff.
|
||||
"""testMSOffice.TestAll testMSOfficeEvents.test testAccess.test
|
||||
testExplorer.TestAll testExchange.test
|
||||
""".split(),
|
||||
# Level 4 tests - we try and run `makepy` over every typelib installed!
|
||||
"""testmakepy.TestAll
|
||||
""".split(),
|
||||
]
|
||||
|
||||
# A list of other unittest modules we use - these are fully qualified module
|
||||
# names and the module is assumed to be unittest based.
|
||||
unittest_other_modules = [
|
||||
# Level 1 tests.
|
||||
"""win32com.directsound.test.ds_test
|
||||
""".split(),
|
||||
# Level 2 tests.
|
||||
[],
|
||||
# Level 3 tests.
|
||||
[],
|
||||
# Level 4 tests.
|
||||
[],
|
||||
]
|
||||
|
||||
|
||||
output_checked_programs = [
|
||||
# Level 1 tests.
|
||||
[],
|
||||
# Level 2 tests.
|
||||
[
|
||||
("cscript.exe /nologo //E:vbscript testInterp.vbs", "VBScript test worked OK"),
|
||||
(
|
||||
"cscript.exe /nologo //E:vbscript testDictionary.vbs",
|
||||
"VBScript has successfully tested Python.Dictionary",
|
||||
),
|
||||
],
|
||||
# Level 3 tests
|
||||
[],
|
||||
# Level 4 tests.
|
||||
[],
|
||||
]
|
||||
|
||||
custom_test_cases = [
|
||||
# Level 1 tests.
|
||||
[
|
||||
PyCOMTest,
|
||||
],
|
||||
# Level 2 tests.
|
||||
[
|
||||
PippoTest,
|
||||
],
|
||||
# Level 3 tests
|
||||
[],
|
||||
# Level 4 tests.
|
||||
[],
|
||||
]
|
||||
|
||||
|
||||
def get_test_mod_and_func(test_name, import_failures):
|
||||
if test_name.find(".") > 0:
|
||||
mod_name, func_name = test_name.split(".")
|
||||
else:
|
||||
mod_name = test_name
|
||||
func_name = None
|
||||
fq_mod_name = "win32com.test." + mod_name
|
||||
try:
|
||||
__import__(fq_mod_name)
|
||||
mod = sys.modules[fq_mod_name]
|
||||
except:
|
||||
import_failures.append((mod_name, sys.exc_info()[:2]))
|
||||
return None, None
|
||||
func = None if func_name is None else getattr(mod, func_name)
|
||||
return mod, func
|
||||
|
||||
|
||||
# Return a test suite all loaded with the tests we want to run
|
||||
def make_test_suite(test_level=1):
|
||||
suite = unittest.TestSuite()
|
||||
import_failures = []
|
||||
loader = TestLoader()
|
||||
for i in range(testLevel):
|
||||
for mod_name in unittest_modules[i]:
|
||||
mod, func = get_test_mod_and_func(mod_name, import_failures)
|
||||
if mod is None:
|
||||
raise ModuleNotFoundError(f"no such module '{mod_name}'")
|
||||
if func is not None:
|
||||
test = CapturingFunctionTestCase(func, description=mod_name)
|
||||
else:
|
||||
if hasattr(mod, "suite"):
|
||||
test = mod.suite()
|
||||
else:
|
||||
test = loader.loadTestsFromModule(mod)
|
||||
assert test.countTestCases() > 0, f"No tests loaded from {mod!r}"
|
||||
suite.addTest(test)
|
||||
for cmd, output in output_checked_programs[i]:
|
||||
suite.addTest(ShellTestCase(cmd, output))
|
||||
|
||||
for test_class in custom_test_cases[i]:
|
||||
suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(test_class))
|
||||
# other "normal" unittest modules.
|
||||
for i in range(testLevel):
|
||||
for mod_name in unittest_other_modules[i]:
|
||||
try:
|
||||
__import__(mod_name)
|
||||
except:
|
||||
import_failures.append((mod_name, sys.exc_info()[:2]))
|
||||
continue
|
||||
|
||||
mod = sys.modules[mod_name]
|
||||
if hasattr(mod, "suite"):
|
||||
test = mod.suite()
|
||||
else:
|
||||
test = loader.loadTestsFromModule(mod)
|
||||
assert test.countTestCases() > 0, f"No tests loaded from {mod!r}"
|
||||
suite.addTest(test)
|
||||
|
||||
return suite, import_failures
|
||||
|
||||
|
||||
def usage(why):
|
||||
print(why)
|
||||
print()
|
||||
print("win32com test suite")
|
||||
print("usage: testall [-v] test_level")
|
||||
print(" where test_level is an integer 1-3. Level 1 tests are quick,")
|
||||
print(" level 2 tests invoke Word, IE etc, level 3 take ages!")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "v")
|
||||
except getopt.error as why:
|
||||
usage(why)
|
||||
for opt, val in opts:
|
||||
if opt == "-v":
|
||||
verbosity += 1
|
||||
testLevel = 2 # default to quick test with local objects
|
||||
test_names = []
|
||||
for arg in args:
|
||||
try:
|
||||
testLevel = int(arg)
|
||||
if testLevel < 0 or testLevel > 4:
|
||||
raise ValueError("Only levels 1-4 are supported")
|
||||
except ValueError:
|
||||
test_names.append(arg)
|
||||
if test_names:
|
||||
usage("Test names are not supported yet")
|
||||
CleanGenerated()
|
||||
|
||||
suite, import_failures = make_test_suite(testLevel)
|
||||
if verbosity:
|
||||
if hasattr(sys, "gettotalrefcount"):
|
||||
print("This is a debug build - memory leak tests will also be run.")
|
||||
print("These tests may take *many* minutes to run - be patient!")
|
||||
print("(running from python.exe will avoid these leak tests)")
|
||||
print(
|
||||
"Executing level %d tests - %d test cases will be run"
|
||||
% (testLevel, suite.countTestCases())
|
||||
)
|
||||
if verbosity == 1 and suite.countTestCases() < 70:
|
||||
# A little row of markers so the dots show how close to finished
|
||||
print("|" * suite.countTestCases())
|
||||
testRunner = TestRunner(verbosity=verbosity)
|
||||
testResult = testRunner.run(suite)
|
||||
if import_failures:
|
||||
testResult.stream.writeln(
|
||||
"*** The following test modules could not be imported ***"
|
||||
)
|
||||
for mod_name, (exc_type, exc_val) in import_failures:
|
||||
desc = "\n".join(traceback.format_exception_only(exc_type, exc_val))
|
||||
testResult.stream.write(f"{mod_name}: {desc}")
|
||||
testResult.stream.writeln(
|
||||
"*** %d test(s) could not be run ***" % len(import_failures)
|
||||
)
|
||||
|
||||
# re-print unit-test error here so it is noticed
|
||||
if not testResult.wasSuccessful():
|
||||
print("*" * 20, "- unittest tests FAILED")
|
||||
|
||||
CheckClean()
|
||||
pythoncom.CoUninitialize()
|
||||
CleanGenerated()
|
||||
if not testResult.wasSuccessful():
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,54 @@
|
||||
# Test makepy - try and run it over every OCX in the windows system directory.
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import pythoncom
|
||||
import win32com.test.util
|
||||
import winerror
|
||||
from win32com.client import gencache, makepy, selecttlb
|
||||
|
||||
|
||||
def TestBuildAll(verbose=1):
|
||||
num = 0
|
||||
tlbInfos = selecttlb.EnumTlbs()
|
||||
for info in tlbInfos:
|
||||
if verbose:
|
||||
print(f"{info.desc} ({info.dll})")
|
||||
try:
|
||||
makepy.GenerateFromTypeLibSpec(info)
|
||||
# sys.stderr.write("Attr typeflags for coclass referenced object %s=%d (%d), typekind=%d\n" % (name, refAttr.wTypeFlags, refAttr.wTypeFlags & pythoncom.TYPEFLAG_FDUAL,refAttr.typekind))
|
||||
num += 1
|
||||
except pythoncom.com_error as details:
|
||||
# Ignore these 2 errors, as the are very common and can obscure
|
||||
# useful warnings.
|
||||
if details.hresult not in [
|
||||
winerror.TYPE_E_CANTLOADLIBRARY,
|
||||
winerror.TYPE_E_LIBNOTREGISTERED,
|
||||
]:
|
||||
print("** COM error on", info.desc)
|
||||
print(details)
|
||||
except KeyboardInterrupt:
|
||||
print("Interrupted!")
|
||||
raise
|
||||
except:
|
||||
print("Failed:", info.desc)
|
||||
traceback.print_exc()
|
||||
if makepy.bForDemandDefault:
|
||||
# This only builds enums etc by default - build each
|
||||
# interface manually
|
||||
tinfo = (info.clsid, info.lcid, info.major, info.minor)
|
||||
mod = gencache.EnsureModule(info.clsid, info.lcid, info.major, info.minor)
|
||||
for name in mod.NamesToIIDMap:
|
||||
makepy.GenerateChildFromTypeLibSpec(name, tinfo)
|
||||
return num
|
||||
|
||||
|
||||
def TestAll(verbose=0):
|
||||
num = TestBuildAll(verbose)
|
||||
print("Generated and imported", num, "modules")
|
||||
win32com.test.util.CheckClean()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
TestAll("-q" not in sys.argv)
|
||||
@@ -0,0 +1,578 @@
|
||||
# Test code for a VB Program.
|
||||
#
|
||||
# This requires the PythonCOM VB Test Harness.
|
||||
#
|
||||
|
||||
import traceback
|
||||
from collections.abc import Callable
|
||||
|
||||
import pythoncom
|
||||
import win32com.client
|
||||
import win32com.client.dynamic
|
||||
import win32com.client.gencache
|
||||
import winerror
|
||||
from win32com.server.util import wrap
|
||||
from win32com.test import util
|
||||
|
||||
# for debugging
|
||||
useDispatcher = None
|
||||
# import win32com.server.dispatcher
|
||||
# useDispatcher = win32com.server.dispatcher.DefaultDebugDispatcher
|
||||
|
||||
|
||||
# Set up a COM object that VB will do some callbacks on. This is used
|
||||
# to test byref params for gateway IDispatch.
|
||||
class TestObject:
|
||||
_public_methods_ = [
|
||||
"CallbackVoidOneByRef",
|
||||
"CallbackResultOneByRef",
|
||||
"CallbackVoidTwoByRef",
|
||||
"CallbackString",
|
||||
"CallbackResultOneByRefButReturnNone",
|
||||
"CallbackVoidOneByRefButReturnNone",
|
||||
"CallbackArrayResult",
|
||||
"CallbackArrayResultOneArrayByRef",
|
||||
"CallbackArrayResultWrongSize",
|
||||
]
|
||||
|
||||
def CallbackVoidOneByRef(self, intVal):
|
||||
return intVal + 1
|
||||
|
||||
def CallbackResultOneByRef(self, intVal):
|
||||
return intVal, intVal + 1
|
||||
|
||||
def CallbackVoidTwoByRef(self, int1, int2):
|
||||
return int1 + int2, int1 - int2
|
||||
|
||||
def CallbackString(self, strVal):
|
||||
return 0, strVal + " has visited Python"
|
||||
|
||||
def CallbackArrayResult(self, arrayVal):
|
||||
ret = []
|
||||
for i in arrayVal:
|
||||
ret.append(i + 1)
|
||||
# returning as a list forces it be processed as a single result
|
||||
# (rather than a tuple, where it may be interpreted as
|
||||
# multiple results for byref unpacking)
|
||||
return ret
|
||||
|
||||
def CallbackArrayResultWrongSize(self, arrayVal):
|
||||
return list(arrayVal[:-1])
|
||||
|
||||
def CallbackArrayResultOneArrayByRef(self, arrayVal):
|
||||
ret = []
|
||||
for i in arrayVal:
|
||||
ret.append(i + 1)
|
||||
# See above for list processing.
|
||||
return list(arrayVal), ret
|
||||
|
||||
def CallbackResultOneByRefButReturnNone(self, intVal):
|
||||
return
|
||||
|
||||
def CallbackVoidOneByRefButReturnNone(self, intVal):
|
||||
return
|
||||
|
||||
|
||||
def TestVB(vbtest, bUseGenerated):
|
||||
vbtest.LongProperty = -1
|
||||
assert vbtest.LongProperty == -1, "Could not set the long property correctly."
|
||||
vbtest.IntProperty = 10
|
||||
assert vbtest.IntProperty == 10, "Could not set the integer property correctly."
|
||||
vbtest.VariantProperty = 10
|
||||
assert vbtest.VariantProperty == 10, (
|
||||
"Could not set the variant integer property correctly."
|
||||
)
|
||||
vbtest.VariantProperty = memoryview(b"raw\0data")
|
||||
assert vbtest.VariantProperty == memoryview(b"raw\0data"), (
|
||||
"Could not set the variant buffer property correctly."
|
||||
)
|
||||
vbtest.StringProperty = "Hello from Python"
|
||||
assert vbtest.StringProperty == "Hello from Python", (
|
||||
"Could not set the string property correctly."
|
||||
)
|
||||
vbtest.VariantProperty = "Hello from Python"
|
||||
assert vbtest.VariantProperty == "Hello from Python", (
|
||||
"Could not set the variant string property correctly."
|
||||
)
|
||||
vbtest.VariantProperty = (1.0, 2.0, 3.0)
|
||||
assert vbtest.VariantProperty == (1.0, 2.0, 3.0), (
|
||||
f"Could not set the variant property to an array of floats correctly - '{vbtest.VariantProperty}'."
|
||||
)
|
||||
|
||||
TestArrays(vbtest, bUseGenerated)
|
||||
TestStructs(vbtest)
|
||||
TestCollections(vbtest)
|
||||
|
||||
assert vbtest.TakeByValObject(vbtest) == vbtest
|
||||
|
||||
# Python doesn't support PUTREF properties without a typeref
|
||||
# (although we could)
|
||||
if bUseGenerated:
|
||||
ob = vbtest.TakeByRefObject(vbtest)
|
||||
assert ob[0] == vbtest and ob[1] == vbtest
|
||||
|
||||
# A property that only has PUTREF defined.
|
||||
vbtest.VariantPutref = vbtest
|
||||
assert vbtest.VariantPutref._oleobj_ == vbtest._oleobj_, (
|
||||
"Could not set the VariantPutref property correctly."
|
||||
)
|
||||
# Can't test further types for this VariantPutref, as only
|
||||
# COM objects can be stored ByRef.
|
||||
|
||||
# A "set" type property - only works for generated.
|
||||
# VB recognizes a collection via a few "private" interfaces that we
|
||||
# could later build support in for.
|
||||
# vbtest.CollectionProperty = NewCollection((1, 2, "3", "Four"))
|
||||
# assert vbtest.CollectionProperty == (
|
||||
# 1, 2, "3", "Four",
|
||||
# ), f"Could not set the Collection property correctly - got back {vbtest.CollectionProperty}"
|
||||
|
||||
# These are sub's that have a single byref param
|
||||
# Result should be just the byref.
|
||||
assert vbtest.IncrementIntegerParam(1) == 2, "Could not pass an integer byref"
|
||||
|
||||
# Sigh - we can't have *both* "ommited byref" and optional args
|
||||
# We really have to opt that args nominated as optional work as optional
|
||||
# rather than simply all byrefs working as optional.
|
||||
# assert vbtest.IncrementIntegerParam() == 1, "Could not pass an omitted integer byref"
|
||||
|
||||
assert vbtest.IncrementVariantParam(1) == 2, (
|
||||
f"Could not pass an int VARIANT byref: {vbtest.IncrementVariantParam(1)}"
|
||||
)
|
||||
assert vbtest.IncrementVariantParam(1.5) == 2.5, (
|
||||
"Could not pass a float VARIANT byref"
|
||||
)
|
||||
|
||||
# Can't test IncrementVariantParam with the param omitted as it
|
||||
# it not declared in the VB code as "Optional"
|
||||
callback_ob = wrap(TestObject(), useDispatcher=useDispatcher)
|
||||
vbtest.DoSomeCallbacks(callback_ob)
|
||||
|
||||
ret = vbtest.PassIntByVal(1)
|
||||
assert ret == 2, f"Could not increment the integer - {ret}"
|
||||
|
||||
TestVBInterface(vbtest)
|
||||
# Python doesn't support byrefs without some sort of generated support.
|
||||
if bUseGenerated:
|
||||
# This is a VB function that takes a single byref
|
||||
# Hence 2 return values - function and byref.
|
||||
ret = vbtest.PassIntByRef(1)
|
||||
assert ret == (1, 2), f"Could not increment the integer - {ret}"
|
||||
# Check you can leave a byref arg blank.
|
||||
|
||||
# see above
|
||||
# ret = vbtest.PassIntByRef()
|
||||
# assert ret == (0, 1), f"Could not increment the integer with default arg - {ret}"
|
||||
|
||||
|
||||
def _DoTestCollection(vbtest, col_name, expected):
|
||||
# It sucks that some objects allow "Count()", but others "Count"
|
||||
def _getcount(ob):
|
||||
r = getattr(ob, "Count")
|
||||
if isinstance(r, Callable):
|
||||
return r()
|
||||
return r
|
||||
|
||||
c = getattr(vbtest, col_name)
|
||||
check = []
|
||||
for item in c:
|
||||
check.append(item)
|
||||
assert check == list(expected), (
|
||||
f"Collection {col_name} didn't have {expected!r} (had {check!r})"
|
||||
)
|
||||
# Just looping over the collection again works (ie, is restartable)
|
||||
check = []
|
||||
for item in c:
|
||||
check.append(item)
|
||||
assert check == list(expected), (
|
||||
f"Collection 2nd time around {col_name} didn't have {expected!r} (had {check!r})"
|
||||
)
|
||||
|
||||
# Check we can get it via iter()
|
||||
i = iter(getattr(vbtest, col_name))
|
||||
check = []
|
||||
for item in i:
|
||||
check.append(item)
|
||||
assert check == list(expected), (
|
||||
f"Collection iterator {col_name} didn't have {expected!r} 2nd time around (had {check!r})"
|
||||
)
|
||||
# but an iterator is not restartable
|
||||
check = []
|
||||
for item in i:
|
||||
check.append(item)
|
||||
assert check == [], (
|
||||
"2nd time around Collection iterator {col_name} wasn't empty (had {check!r})"
|
||||
)
|
||||
# Check len()==Count()
|
||||
c = getattr(vbtest, col_name)
|
||||
assert len(c) == _getcount(c), (
|
||||
f"Collection {col_name} __len__({len(c)!r}) wasn't==Count({_getcount(c)!r})"
|
||||
)
|
||||
# Check we can do it with zero based indexing.
|
||||
c = getattr(vbtest, col_name)
|
||||
check = []
|
||||
for i in range(_getcount(c)):
|
||||
check.append(c[i])
|
||||
assert check == list(expected), (
|
||||
f"Collection {col_name} didn't have {expected!r} (had {check!r})"
|
||||
)
|
||||
|
||||
# Check we can do it with our old "Skip/Next" methods.
|
||||
c = getattr(vbtest, col_name)._NewEnum()
|
||||
check = []
|
||||
while 1:
|
||||
n = c.Next()
|
||||
if not n:
|
||||
break
|
||||
check.append(n[0])
|
||||
assert check == list(expected), (
|
||||
f"Collection {col_name} didn't have {expected!r} (had {check!r})"
|
||||
)
|
||||
|
||||
|
||||
def TestCollections(vbtest):
|
||||
_DoTestCollection(vbtest, "CollectionProperty", [1, "Two", "3"])
|
||||
# zero based indexing works for simple VB collections.
|
||||
assert vbtest.CollectionProperty[0] == 1, (
|
||||
"The CollectionProperty[0] element was not the default value"
|
||||
)
|
||||
|
||||
_DoTestCollection(vbtest, "EnumerableCollectionProperty", [])
|
||||
vbtest.EnumerableCollectionProperty.Add(1)
|
||||
vbtest.EnumerableCollectionProperty.Add("Two")
|
||||
vbtest.EnumerableCollectionProperty.Add("3")
|
||||
_DoTestCollection(vbtest, "EnumerableCollectionProperty", [1, "Two", "3"])
|
||||
|
||||
|
||||
def _DoTestArray(vbtest, data, expected_exception=None):
|
||||
try:
|
||||
vbtest.ArrayProperty = data
|
||||
assert expected_exception is None, f"Expected '{expected_exception}'"
|
||||
except expected_exception:
|
||||
return
|
||||
got = vbtest.ArrayProperty
|
||||
assert got == data, (
|
||||
f"Could not set the array data correctly - got {got!r}, expected {data!r}"
|
||||
)
|
||||
|
||||
|
||||
def TestArrays(vbtest, bUseGenerated):
|
||||
# Try and use a safe array (note that the VB code has this declared as a VARIANT
|
||||
# and I can't work out how to force it to use native arrays!
|
||||
# (NOTE Python will convert incoming arrays to tuples, so we pass a tuple, even tho
|
||||
# a list works fine - just makes it easier for us to compare the result!
|
||||
# Empty array
|
||||
_DoTestArray(vbtest, ())
|
||||
# Empty child array
|
||||
_DoTestArray(vbtest, ((), ()))
|
||||
# ints
|
||||
_DoTestArray(vbtest, tuple(range(1, 100)))
|
||||
# Floats
|
||||
_DoTestArray(vbtest, (1.0, 2.0, 3.0))
|
||||
# Strings.
|
||||
_DoTestArray(vbtest, tuple("Hello from Python".split()))
|
||||
# Date and Time?
|
||||
# COM objects.
|
||||
_DoTestArray(vbtest, (vbtest, vbtest))
|
||||
# Mixed
|
||||
_DoTestArray(vbtest, (1, 2.0, "3"))
|
||||
# Array alements containing other arrays
|
||||
_DoTestArray(vbtest, (1, (vbtest, vbtest), ("3", "4")))
|
||||
# Multi-dimensional
|
||||
_DoTestArray(vbtest, (((1, 2, 3), (4, 5, 6))))
|
||||
_DoTestArray(vbtest, (((vbtest, vbtest, vbtest), (vbtest, vbtest, vbtest))))
|
||||
# Another dimension!
|
||||
arrayData = (((1, 2), (3, 4), (5, 6)), ((7, 8), (9, 10), (11, 12)))
|
||||
arrayData = (
|
||||
((vbtest, vbtest), (vbtest, vbtest), (vbtest, vbtest)),
|
||||
((vbtest, vbtest), (vbtest, vbtest), (vbtest, vbtest)),
|
||||
)
|
||||
_DoTestArray(vbtest, arrayData)
|
||||
|
||||
# Check that when a '__getitem__ that fails' object is the first item
|
||||
# in the structure, we don't mistake it for a sequence.
|
||||
_DoTestArray(vbtest, (vbtest, 2.0, "3"))
|
||||
_DoTestArray(vbtest, (1, 2.0, vbtest))
|
||||
|
||||
# Pass arbitrarily sized arrays - these used to fail, but thanks to
|
||||
# Stefan Schukat, they now work!
|
||||
expected_exception = None
|
||||
arrayData = (((1, 2, 1), (3, 4), (5, 6)), ((7, 8), (9, 10), (11, 12)))
|
||||
_DoTestArray(vbtest, arrayData, expected_exception)
|
||||
arrayData = (((vbtest, vbtest),), ((vbtest,),))
|
||||
_DoTestArray(vbtest, arrayData, expected_exception)
|
||||
# Pass bad data - last item wrong size
|
||||
arrayData = (((1, 2), (3, 4), (5, 6, 8)), ((7, 8), (9, 10), (11, 12)))
|
||||
_DoTestArray(vbtest, arrayData, expected_exception)
|
||||
|
||||
# byref safearray results with incorrect size.
|
||||
callback_ob = wrap(TestObject(), useDispatcher=useDispatcher)
|
||||
print("** Expecting a 'ValueError' exception to be printed next:")
|
||||
try:
|
||||
vbtest.DoCallbackSafeArraySizeFail(callback_ob)
|
||||
except pythoncom.com_error as exc:
|
||||
assert exc.excepinfo[1] == "Python COM Server Internal Error", (
|
||||
f"Didn't get the correct exception - '{exc}'"
|
||||
)
|
||||
|
||||
if bUseGenerated:
|
||||
# This one is a bit strange! The array param is "ByRef", as VB insists.
|
||||
# The function itself also _returns_ the arram param.
|
||||
# Therefore, Python sees _2_ result values - one for the result,
|
||||
# and one for the byref.
|
||||
testData = "Mark was here".split()
|
||||
resultData, byRefParam = vbtest.PassSAFEARRAY(testData)
|
||||
assert testData == list(resultData), (
|
||||
f"The safe array data was not what we expected - got {resultData}"
|
||||
)
|
||||
assert testData == list(byRefParam), (
|
||||
f"The safe array data was not what we expected - got {byRefParam}"
|
||||
)
|
||||
testData = [1.0, 2.0, 3.0]
|
||||
resultData, byRefParam = vbtest.PassSAFEARRAYVariant(testData)
|
||||
assert testData == list(byRefParam)
|
||||
assert testData == list(resultData)
|
||||
testData = ["hi", "from", "Python"]
|
||||
resultData, byRefParam = vbtest.PassSAFEARRAYVariant(testData)
|
||||
assert testData == list(byRefParam), "Expected '{}', got '{}'".format(
|
||||
testData,
|
||||
list(byRefParam),
|
||||
)
|
||||
assert testData == list(resultData), "Expected '{}', got '{}'".format(
|
||||
testData,
|
||||
list(resultData),
|
||||
)
|
||||
# This time, we just pass Unicode, so the result should compare equal
|
||||
testData = [1, 2.0, "3"]
|
||||
resultData, byRefParam = vbtest.PassSAFEARRAYVariant(testData)
|
||||
assert testData == list(byRefParam)
|
||||
assert testData == list(resultData)
|
||||
print("Array tests passed")
|
||||
|
||||
|
||||
def TestStructs(vbtest):
|
||||
try:
|
||||
vbtest.IntProperty = "One"
|
||||
raise AssertionError("Should have failed by now")
|
||||
except pythoncom.com_error as exc:
|
||||
assert exc.hresult == winerror.DISP_E_TYPEMISMATCH, (
|
||||
"Expected DISP_E_TYPEMISMATCH"
|
||||
)
|
||||
|
||||
s = vbtest.StructProperty
|
||||
assert s.int_val == 99 and str(s.str_val) == "hello", (
|
||||
"The struct value was not correct"
|
||||
)
|
||||
s.str_val = "Hi from Python"
|
||||
s.int_val = 11
|
||||
assert s.int_val == 11 and str(s.str_val) == "Hi from Python", (
|
||||
"The struct value didn't persist!"
|
||||
)
|
||||
assert s.sub_val.int_val == 66 and str(s.sub_val.str_val) == "sub hello", (
|
||||
"The sub-struct value was not correct"
|
||||
)
|
||||
sub = s.sub_val
|
||||
sub.int_val = 22
|
||||
assert sub.int_val == 22, (
|
||||
f"The sub-struct value didn't persist!",
|
||||
str(sub.int_val),
|
||||
)
|
||||
assert s.sub_val.int_val == 22, (
|
||||
"The sub-struct value (re-fetched) didn't persist!",
|
||||
str(s.sub_val.int_val),
|
||||
)
|
||||
assert (
|
||||
s.sub_val.array_val[0].int_val == 0
|
||||
and str(s.sub_val.array_val[0].str_val) == "zero"
|
||||
), ("The array element wasn't correct", str(s.sub_val.array_val[0].int_val))
|
||||
s.sub_val.array_val[0].int_val = 99
|
||||
s.sub_val.array_val[1].int_val = 66
|
||||
assert (
|
||||
s.sub_val.array_val[0].int_val == 99 and s.sub_val.array_val[1].int_val == 66
|
||||
), (
|
||||
"The array elements didn't persist.",
|
||||
str(s.sub_val.array_val[0].int_val),
|
||||
str(s.sub_val.array_val[1].int_val),
|
||||
)
|
||||
# Now pass the struct back to VB
|
||||
vbtest.StructProperty = s
|
||||
# And get it back again
|
||||
s = vbtest.StructProperty
|
||||
assert s.int_val == 11 and str(s.str_val) == "Hi from Python", (
|
||||
"After sending to VB, the struct value didn't persist!"
|
||||
)
|
||||
assert s.sub_val.array_val[0].int_val == 99, (
|
||||
"After sending to VB, the struct array value didn't persist!"
|
||||
)
|
||||
|
||||
# Now do some object equality tests.
|
||||
assert s == s
|
||||
assert s is not None
|
||||
try:
|
||||
s < None
|
||||
raise AssertionError("Expected type error")
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
None < s
|
||||
raise AssertionError("Expected type error")
|
||||
except TypeError:
|
||||
pass
|
||||
assert s != s.sub_val
|
||||
import copy
|
||||
|
||||
s2 = copy.copy(s)
|
||||
assert s is not s2
|
||||
assert s == s2
|
||||
s2.int_val = 123
|
||||
assert s != s2
|
||||
# Make sure everything works with functions
|
||||
s2 = vbtest.GetStructFunc()
|
||||
assert s == s2
|
||||
vbtest.SetStructSub(s2)
|
||||
|
||||
# Create a new structure, and set its elements.
|
||||
s = win32com.client.Record("VBStruct", vbtest)
|
||||
assert s.int_val == 0, "new struct inst initialized correctly!"
|
||||
s.int_val = -1
|
||||
vbtest.SetStructSub(s)
|
||||
assert vbtest.GetStructFunc().int_val == -1, (
|
||||
"new struct didn't make the round trip!"
|
||||
)
|
||||
# Finally, test stand-alone structure arrays.
|
||||
s_array = vbtest.StructArrayProperty
|
||||
assert s_array is None, "Expected None from the uninitialized VB array"
|
||||
vbtest.MakeStructArrayProperty(3)
|
||||
s_array = vbtest.StructArrayProperty
|
||||
assert len(s_array) == 3
|
||||
for i in range(len(s_array)):
|
||||
assert s_array[i].int_val == i
|
||||
assert s_array[i].sub_val.int_val == i
|
||||
assert s_array[i].sub_val.array_val[0].int_val == i
|
||||
assert s_array[i].sub_val.array_val[1].int_val == i + 1
|
||||
assert s_array[i].sub_val.array_val[2].int_val == i + 2
|
||||
|
||||
# Some error type checks.
|
||||
try:
|
||||
s.bad_attribute
|
||||
raise AssertionError("Could get a bad attribute")
|
||||
except AttributeError:
|
||||
pass
|
||||
m = s.__members__
|
||||
assert (
|
||||
m[0] == "int_val"
|
||||
and m[1] == "str_val"
|
||||
and m[2] == "ob_val"
|
||||
and m[3] == "sub_val"
|
||||
), m
|
||||
|
||||
# Test attribute errors.
|
||||
try:
|
||||
s.foo
|
||||
raise AssertionError("Expected attribute error")
|
||||
except AttributeError as exc:
|
||||
assert "foo" in str(exc), exc
|
||||
|
||||
# test repr - it uses repr() of the sub-objects, so check it matches.
|
||||
expected = (
|
||||
"com_struct(int_val={!r}, str_val={!r}, ob_val={!r}, sub_val={!r})".format(
|
||||
s.int_val,
|
||||
s.str_val,
|
||||
s.ob_val,
|
||||
s.sub_val,
|
||||
)
|
||||
)
|
||||
repr_s = repr(s)
|
||||
if repr_s != expected:
|
||||
print("Expected repr:", expected)
|
||||
print("Actual repr :", repr_s)
|
||||
raise AssertionError("repr() of record object failed")
|
||||
|
||||
print("Struct/Record tests passed")
|
||||
|
||||
|
||||
def TestVBInterface(ob):
|
||||
t = ob.GetInterfaceTester(2)
|
||||
assert t.getn() == 2, "Initial value wrong"
|
||||
t.setn(3)
|
||||
assert t.getn() == 3, "New value wrong"
|
||||
|
||||
|
||||
def TestObjectSemantics(ob):
|
||||
# a convenient place to test some of our equality semantics
|
||||
assert ob == ob._oleobj_
|
||||
assert not ob != ob._oleobj_
|
||||
# same test again, but lhs and rhs reversed.
|
||||
assert ob._oleobj_ == ob
|
||||
assert not ob._oleobj_ != ob
|
||||
# same tests but against different pointers. COM identity rules should
|
||||
# still ensure all works
|
||||
assert ob._oleobj_ == ob._oleobj_.QueryInterface(pythoncom.IID_IUnknown)
|
||||
assert not ob._oleobj_ != ob._oleobj_.QueryInterface(pythoncom.IID_IUnknown)
|
||||
|
||||
assert ob._oleobj_ is not None
|
||||
assert None != ob._oleobj_
|
||||
assert ob is not None
|
||||
assert None != ob
|
||||
try:
|
||||
ob < None
|
||||
raise AssertionError("Expected type error")
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
None < ob
|
||||
raise AssertionError("Expected type error")
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
assert ob._oleobj_.QueryInterface(pythoncom.IID_IUnknown) == ob._oleobj_
|
||||
assert not ob._oleobj_.QueryInterface(pythoncom.IID_IUnknown) != ob._oleobj_
|
||||
|
||||
assert ob._oleobj_ == ob._oleobj_.QueryInterface(pythoncom.IID_IDispatch)
|
||||
assert not ob._oleobj_ != ob._oleobj_.QueryInterface(pythoncom.IID_IDispatch)
|
||||
|
||||
assert ob._oleobj_.QueryInterface(pythoncom.IID_IDispatch) == ob._oleobj_
|
||||
assert not ob._oleobj_.QueryInterface(pythoncom.IID_IDispatch) != ob._oleobj_
|
||||
|
||||
print("Object semantic tests passed")
|
||||
|
||||
|
||||
def DoTestAll():
|
||||
o = win32com.client.Dispatch("PyCOMVBTest.Tester")
|
||||
TestObjectSemantics(o)
|
||||
TestVB(o, 1)
|
||||
|
||||
o = win32com.client.dynamic.DumbDispatch("PyCOMVBTest.Tester")
|
||||
TestObjectSemantics(o)
|
||||
TestVB(o, 0)
|
||||
|
||||
|
||||
def TestAll():
|
||||
# Import the type library for the test module. Let the 'invalid clsid'
|
||||
# exception filter up, where the test runner will treat it as 'skipped'
|
||||
win32com.client.gencache.EnsureDispatch("PyCOMVBTest.Tester")
|
||||
|
||||
if not __debug__:
|
||||
raise RuntimeError("This must be run in debug mode - we use assert!")
|
||||
try:
|
||||
DoTestAll()
|
||||
print("All tests appear to have worked!")
|
||||
except:
|
||||
# ?????
|
||||
print("TestAll() failed!!")
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
|
||||
# Make this test run under our test suite to leak tests etc work
|
||||
def suite():
|
||||
import unittest
|
||||
|
||||
test = util.CapturingFunctionTestCase(TestAll, description="VB tests")
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(test)
|
||||
return suite
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
util.testmain()
|
||||
@@ -0,0 +1,40 @@
|
||||
import unittest
|
||||
|
||||
import win32com.test.util
|
||||
from win32com.client.dynamic import DumbDispatch
|
||||
from win32com.client.gencache import EnsureDispatch
|
||||
|
||||
|
||||
class RegexTest(win32com.test.util.TestCase):
|
||||
def _CheckMatches(self, match, expected):
|
||||
found = []
|
||||
for imatch in match:
|
||||
found.append(imatch.FirstIndex)
|
||||
self.assertEqual(list(found), list(expected))
|
||||
|
||||
def _TestVBScriptRegex(self, re):
|
||||
StringToSearch = r"Python python pYthon Python"
|
||||
re.Pattern = r"Python"
|
||||
re.Global = True
|
||||
|
||||
re.IgnoreCase = True
|
||||
match = re.Execute(StringToSearch)
|
||||
expected = 0, 7, 14, 21
|
||||
self._CheckMatches(match, expected)
|
||||
|
||||
re.IgnoreCase = False
|
||||
match = re.Execute(StringToSearch)
|
||||
expected = 0, 21
|
||||
self._CheckMatches(match, expected)
|
||||
|
||||
def testDynamic(self):
|
||||
re = DumbDispatch("VBScript.Regexp")
|
||||
self._TestVBScriptRegex(re)
|
||||
|
||||
def testGenerated(self):
|
||||
re = EnsureDispatch("VBScript.Regexp")
|
||||
self._TestVBScriptRegex(re)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,12 @@
|
||||
//Args: input-file style-file output-file
|
||||
var xml = WScript.CreateObject("Microsoft.XMLDOM"); //input
|
||||
xml.validateOnParse=false;
|
||||
xml.load(WScript.Arguments(0));
|
||||
var xsl = WScript.CreateObject("Microsoft.XMLDOM"); //style
|
||||
xsl.validateOnParse=false;
|
||||
xsl.load(WScript.Arguments(1));
|
||||
var out = WScript.CreateObject("Scripting.FileSystemObject"); //output
|
||||
var replace = true; var unicode = false; //output file properties
|
||||
var hdl = out.CreateTextFile( WScript.Arguments(2), replace, unicode )
|
||||
hdl.write( xml.transformNode( xsl.documentElement ));
|
||||
//eof
|
||||
@@ -0,0 +1,34 @@
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import win32com.test.util
|
||||
|
||||
expected_output = "The jscript test worked.\nThe Python test worked"
|
||||
|
||||
|
||||
class XSLT(win32com.test.util.TestCase):
|
||||
def testAll(self):
|
||||
output_name = tempfile.mktemp("-pycom-test")
|
||||
cmd = (
|
||||
"cscript //nologo testxslt.js doesnt_matter.xml testxslt.xsl " + output_name
|
||||
)
|
||||
win32com.test.util.ExecuteShellCommand(cmd, self)
|
||||
try:
|
||||
f = open(output_name)
|
||||
try:
|
||||
got = f.read()
|
||||
if got != expected_output:
|
||||
print(f"ERROR: XSLT expected output of {expected_output!r}")
|
||||
print(f"but got {got!r}")
|
||||
finally:
|
||||
f.close()
|
||||
finally:
|
||||
try:
|
||||
os.unlink(output_name)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,54 @@
|
||||
<!-- include in the xsl:stylesheet element:
|
||||
(a) the version attribute as usual
|
||||
(b) the XSLT namespace declaration as usual
|
||||
(c) the MSXSL namespace declaration
|
||||
(d) a namespace declaration to identify your functions
|
||||
(e) the 'extension-element-prefixes' attribute to give the
|
||||
namespace prefixes that indicate extension elements
|
||||
(i.e. 'msxsl')
|
||||
(f) the 'exclude-result-prefixes' attribute to indicate the
|
||||
namespaces that aren't supposed to be part of the result
|
||||
tree (i.e. 'foo') -->
|
||||
<xsl:stylesheet version="1.0"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
|
||||
xmlns:foo="http://www.pythoncom-test.com/foo"
|
||||
xmlns:bar="http://www.pythoncom-test.com/bar"
|
||||
extension-element-prefixes="msxsl"
|
||||
exclude-result-prefixes="foo bar">
|
||||
|
||||
<!-- do whatever output you want - you can use full XSLT functionality
|
||||
-->
|
||||
<xsl:output method="html" />
|
||||
|
||||
<!-- define the Javascript functions that you want to include within
|
||||
a msxsl:script element.
|
||||
- language indicates the scripting language
|
||||
- implements-prefix gives the namespace prefix that you declared
|
||||
for your function (i.e. foo) -->
|
||||
<msxsl:script language="javascript"
|
||||
implements-prefix="foo">
|
||||
function worked() {
|
||||
return "The jscript test worked";
|
||||
}
|
||||
</msxsl:script>
|
||||
|
||||
<!-- ditto for Python, using the 'bar' namespace
|
||||
-->
|
||||
<msxsl:script language="python"
|
||||
implements-prefix="bar">
|
||||
def worked():
|
||||
return "The Python test worked"
|
||||
</msxsl:script>
|
||||
|
||||
<xsl:template match="/">
|
||||
<!-- The output template. Keep whitespace down as our test matches text exactly -->
|
||||
<!-- call your functions using the prefix that you've used (i.e.
|
||||
foo) anywhere you can normally use an XPath function, but
|
||||
make sure it's returning the right kind of object -->
|
||||
<xsl:value-of select="foo:worked()" />.
|
||||
<xsl:value-of select="bar:worked()" />
|
||||
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
@@ -0,0 +1,262 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import winreg
|
||||
|
||||
import pythoncom
|
||||
import pywin32_testutil
|
||||
import pywintypes
|
||||
import win32api
|
||||
import win32com
|
||||
import winerror
|
||||
from pythoncom import _GetGatewayCount, _GetInterfaceCount
|
||||
|
||||
|
||||
def CheckClean():
|
||||
# Ensure no lingering exceptions - Python should have zero outstanding
|
||||
# COM objects
|
||||
c = _GetInterfaceCount()
|
||||
if c:
|
||||
print("Warning - %d com interface objects still alive" % c)
|
||||
c = _GetGatewayCount()
|
||||
if c:
|
||||
print("Warning - %d com gateway objects still alive" % c)
|
||||
|
||||
|
||||
def RegisterPythonServer(filename, progids=None, verbose=0):
|
||||
if progids:
|
||||
if isinstance(progids, str):
|
||||
progids = [progids]
|
||||
# we know the CLSIDs we need, but we might not be an admin user
|
||||
# and otherwise unable to register them. So as long as the progids
|
||||
# exist and the DLL points at our version, assume it already is.
|
||||
why_not = None
|
||||
for progid in progids:
|
||||
try:
|
||||
clsid = pywintypes.IID(progid)
|
||||
except pythoncom.com_error:
|
||||
# not registered.
|
||||
break
|
||||
try:
|
||||
HKCR = winreg.HKEY_CLASSES_ROOT
|
||||
hk = winreg.OpenKey(HKCR, "CLSID\\%s" % clsid)
|
||||
dll = winreg.QueryValue(hk, "InprocServer32")
|
||||
except OSError:
|
||||
# no CLSID or InProcServer32 - not registered
|
||||
break
|
||||
ok_files = [
|
||||
os.path.basename(pythoncom.__file__),
|
||||
"pythoncomloader%d%d.dll"
|
||||
% (sys.version_info.major, sys.version_info.minor),
|
||||
]
|
||||
if os.path.basename(dll) not in ok_files:
|
||||
why_not = (
|
||||
"{!r} is registered against a different Python version ({})".format(
|
||||
progid,
|
||||
dll,
|
||||
)
|
||||
)
|
||||
break
|
||||
else:
|
||||
# print(f"Skipping registration of '{filename}' - already registered")
|
||||
return
|
||||
# needs registration - see if it's likely!
|
||||
try:
|
||||
from win32com.shell.shell import IsUserAnAdmin
|
||||
except ImportError:
|
||||
print("Can't import win32com.shell - no idea if you are an admin or not?")
|
||||
is_admin = False
|
||||
else:
|
||||
try:
|
||||
is_admin = IsUserAnAdmin()
|
||||
except pythoncom.com_error:
|
||||
# old, less-secure OS - assume *is* admin.
|
||||
is_admin = True
|
||||
if not is_admin:
|
||||
msg = (
|
||||
"%r isn't registered, but I'm not an administrator who can register it."
|
||||
% progids[0]
|
||||
)
|
||||
if why_not:
|
||||
msg += "\n(registration check failed as %s)" % why_not
|
||||
# throw a normal "class not registered" exception - we don't report
|
||||
# them the same way as "real" errors.
|
||||
raise pythoncom.com_error(winerror.CO_E_CLASSSTRING, msg, None, -1)
|
||||
# so theoretically we are able to register it.
|
||||
cmd = f'{win32api.GetModuleFileName(0)} "{filename}" --unattended > nul 2>&1'
|
||||
if verbose:
|
||||
print("Registering engine", filename)
|
||||
# print(cmd)
|
||||
rc = os.system(cmd)
|
||||
if rc:
|
||||
print("Registration command was:")
|
||||
print(cmd)
|
||||
raise RuntimeError("Registration of engine '%s' failed" % filename)
|
||||
|
||||
|
||||
def ExecuteShellCommand(
|
||||
cmd,
|
||||
testcase,
|
||||
expected_output=None, # Set to '' to check for nothing
|
||||
tracebacks_ok=0, # OK if the output contains a t/b?
|
||||
):
|
||||
output_name = tempfile.mktemp("win32com_test")
|
||||
cmd += ' > "%s" 2>&1' % output_name
|
||||
rc = os.system(cmd)
|
||||
output = open(output_name, "r").read().strip()
|
||||
os.remove(output_name)
|
||||
|
||||
class Failed(Exception):
|
||||
pass
|
||||
|
||||
try:
|
||||
if rc:
|
||||
raise Failed("exit code was " + str(rc))
|
||||
if expected_output is not None and output != expected_output:
|
||||
raise Failed(f"Expected output {expected_output!r} (got {output!r})")
|
||||
if not tracebacks_ok and output.find("Traceback (most recent call last)") >= 0:
|
||||
raise Failed("traceback in program output")
|
||||
return output
|
||||
except Failed as why:
|
||||
print("Failed to exec command '%r'" % cmd)
|
||||
print("Failed as", why)
|
||||
print("** start of program output **")
|
||||
print(output)
|
||||
print("** end of program output **")
|
||||
testcase.fail(f"Executing '{cmd}' failed as {why}")
|
||||
|
||||
|
||||
def assertRaisesCOM_HRESULT(testcase, hresult, func, *args, **kw):
|
||||
try:
|
||||
func(*args, **kw)
|
||||
except pythoncom.com_error as details:
|
||||
if details.hresult == hresult:
|
||||
return
|
||||
testcase.fail("Excepected COM exception with HRESULT 0x%x" % hresult)
|
||||
|
||||
|
||||
class CaptureWriter:
|
||||
def __init__(self):
|
||||
self.old_err = self.old_out = None
|
||||
self.clear()
|
||||
|
||||
def capture(self):
|
||||
self.clear()
|
||||
self.old_out = sys.stdout
|
||||
self.old_err = sys.stderr
|
||||
sys.stdout = sys.stderr = self
|
||||
|
||||
def release(self):
|
||||
if self.old_out:
|
||||
sys.stdout = self.old_out
|
||||
self.old_out = None
|
||||
if self.old_err:
|
||||
sys.stderr = self.old_err
|
||||
self.old_err = None
|
||||
|
||||
def clear(self):
|
||||
self.captured = []
|
||||
|
||||
def write(self, msg):
|
||||
self.captured.append(msg)
|
||||
|
||||
def get_captured(self):
|
||||
return "".join(self.captured)
|
||||
|
||||
def get_num_lines_captured(self):
|
||||
return len("".join(self.captured).split("\n"))
|
||||
|
||||
|
||||
# Utilities to set the win32com logger to something what just captures
|
||||
# records written and doesn't print them.
|
||||
class LogHandler(logging.Handler):
|
||||
def __init__(self):
|
||||
self.emitted = []
|
||||
logging.Handler.__init__(self)
|
||||
|
||||
def emit(self, record):
|
||||
self.emitted.append(record)
|
||||
|
||||
|
||||
_win32com_logger = None
|
||||
|
||||
|
||||
def setup_test_logger():
|
||||
old_log = getattr(win32com, "logger", None)
|
||||
global _win32com_logger
|
||||
if _win32com_logger is None:
|
||||
_win32com_logger = logging.Logger("test")
|
||||
handler = LogHandler()
|
||||
_win32com_logger.addHandler(handler)
|
||||
|
||||
win32com.logger = _win32com_logger
|
||||
handler = _win32com_logger.handlers[0]
|
||||
handler.emitted = []
|
||||
return handler.emitted, old_log
|
||||
|
||||
|
||||
def restore_test_logger(prev_logger):
|
||||
assert prev_logger is None, "who needs this?"
|
||||
if prev_logger is None:
|
||||
del win32com.logger
|
||||
else:
|
||||
win32com.logger = prev_logger
|
||||
|
||||
|
||||
# We used to override some of this (and may later!)
|
||||
TestCase = unittest.TestCase
|
||||
|
||||
|
||||
def CapturingFunctionTestCase(*args, **kw):
|
||||
real_test = _CapturingFunctionTestCase(*args, **kw)
|
||||
return pywin32_testutil.LeakTestCase(real_test)
|
||||
|
||||
|
||||
class _CapturingFunctionTestCase(unittest.FunctionTestCase): # , TestCaseMixin):
|
||||
def __call__(self, result=None):
|
||||
if result is None:
|
||||
result = self.defaultTestResult()
|
||||
writer = CaptureWriter()
|
||||
# self._preTest()
|
||||
writer.capture()
|
||||
try:
|
||||
unittest.FunctionTestCase.__call__(self, result)
|
||||
if getattr(self, "do_leak_tests", 0) and hasattr(sys, "gettotalrefcount"):
|
||||
self.run_leak_tests(result)
|
||||
finally:
|
||||
writer.release()
|
||||
# self._postTest(result)
|
||||
output = writer.get_captured()
|
||||
self.checkOutput(output, result)
|
||||
if result.showAll:
|
||||
print(output)
|
||||
|
||||
def checkOutput(self, output, result):
|
||||
if output.find("Traceback") >= 0:
|
||||
msg = "Test output contained a traceback\n---\n%s\n---" % output
|
||||
result.errors.append((self, msg))
|
||||
|
||||
|
||||
class ShellTestCase(unittest.TestCase):
|
||||
def __init__(self, cmd, expected_output):
|
||||
self.__cmd = cmd
|
||||
self.__eo = expected_output
|
||||
unittest.TestCase.__init__(self)
|
||||
|
||||
def runTest(self):
|
||||
ExecuteShellCommand(self.__cmd, self, self.__eo)
|
||||
|
||||
def __str__(self):
|
||||
max = 30
|
||||
if len(self.__cmd) > max:
|
||||
cmd_repr = self.__cmd[:max] + "..."
|
||||
else:
|
||||
cmd_repr = self.__cmd
|
||||
return "exec: " + cmd_repr
|
||||
|
||||
|
||||
def testmain(*args, **kw):
|
||||
pywin32_testutil.testmain(*args, **kw)
|
||||
CheckClean()
|
||||
Reference in New Issue
Block a user