# Test code for a VB Program. # # This requires the PythonCOM VB Test Harness. # import sys import winerror import pythoncom, win32com.client, win32com.client.dynamic, win32com.client.gencache from win32com.server.util import NewCollection, wrap from win32com.test import util from pywin32_testutil import str2memory import traceback # for debugging useDispatcher = None ## import win32com.server.dispatcher ## useDispatcher = win32com.server.dispatcher.DefaultDebugDispatcher error = RuntimeError # 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 if vbtest.LongProperty != -1: raise error("Could not set the long property correctly.") vbtest.IntProperty = 10 if vbtest.IntProperty != 10: raise error("Could not set the integer property correctly.") vbtest.VariantProperty = 10 if vbtest.VariantProperty != 10: raise error("Could not set the variant integer property correctly.") vbtest.VariantProperty = str2memory('raw\0data') if vbtest.VariantProperty != str2memory('raw\0data'): raise error("Could not set the variant buffer property correctly.") vbtest.StringProperty = "Hello from Python" if vbtest.StringProperty != "Hello from Python": raise error("Could not set the string property correctly.") vbtest.VariantProperty = "Hello from Python" if vbtest.VariantProperty != "Hello from Python": raise error("Could not set the variant string property correctly.") vbtest.VariantProperty = (1.0, 2.0, 3.0) if vbtest.VariantProperty != (1.0, 2.0, 3.0): raise error("Could not set the variant property to an array of floats correctly - '%s'." % (vbtest.VariantProperty,)) TestArrays(vbtest, bUseGenerated) TestStructs(vbtest) TestCollections(vbtest) assert vbtest.TakeByValObject(vbtest)==vbtest # Python doesnt 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 if vbtest.VariantPutref._oleobj_!= vbtest._oleobj_: raise error("Could not set the VariantPutref property correctly.") # Cant 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")) # if vbtest.CollectionProperty != (1,2,"3", "Four"): # raise error("Could not set the Collection property correctly - got back " + str(vbtest.CollectionProperty)) # These are sub's that have a single byref param # Result should be just the byref. if vbtest.IncrementIntegerParam(1) != 2: raise error("Could not pass an integer byref") # Sigh - we cant 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. # if vbtest.IncrementIntegerParam() != 1: # raise error("Could not pass an omitted integer byref") if vbtest.IncrementVariantParam(1) != 2: raise error("Could not pass an int VARIANT byref:"+str(vbtest.IncrementVariantParam(1))) if vbtest.IncrementVariantParam(1.5) != 2.5: raise error("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) if ret != 2: raise error("Could not increment the integer - "+str(ret)) TestVBInterface(vbtest) # Python doesnt 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) if ret != (1,2): raise error("Could not increment the integer - "+str(ret)) # Check you can leave a byref arg blank. # see above # ret = vbtest.PassIntByRef() # if ret != (0,1): # raise error("Could not increment the integer with default arg- "+str(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 type(r)!=type(0): return r() return r c = getattr(vbtest, col_name) check = [] for item in c: check.append(item) if check != list(expected): raise error("Collection %s didn't have %r (had %r)" % (col_name, expected, check)) # Just looping over the collection again works (ie, is restartable) check = [] for item in c: check.append(item) if check != list(expected): raise error("Collection 2nd time around %s didn't have %r (had %r)" % (col_name, expected, check)) # Check we can get it via iter() i = iter(getattr(vbtest, col_name)) check = [] for item in i: check.append(item) if check != list(expected): raise error("Collection iterator %s didn't have %r 2nd time around (had %r)" % (col_name, expected, check)) # but an iterator is not restartable check = [] for item in i: check.append(item) if check != []: raise error("2nd time around Collection iterator %s wasn't empty (had %r)" % (col_name, check)) # Check len()==Count() c = getattr(vbtest, col_name) if len(c) != _getcount(c): raise error("Collection %s __len__(%r) wasn't==Count(%r)" % (col_name, len(c), _getcount(c))) # 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]) if check != list(expected): raise error("Collection %s didn't have %r (had %r)" % (col_name, expected, check)) # 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]) if check != list(expected): raise error("Collection %s didn't have %r (had %r)" % (col_name, expected, check)) def TestCollections(vbtest): _DoTestCollection(vbtest, "CollectionProperty", [1,"Two", "3"]) # zero based indexing works for simple VB collections. if vbtest.CollectionProperty[0] != 1: raise error("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 if expected_exception is not None: raise error("Expected '%s'" % expected_exception) except expected_exception: return got = vbtest.ArrayProperty if got != data: raise error( "Could not set the array data correctly - got %r, expected %r" % (got, data)) def TestArrays(vbtest, bUseGenerated): # Try and use a safe array (note that the VB code has this declared as a VARIANT # and I cant 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", "Didnt get the correct exception - '%s'" % (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) if testData != list(resultData): raise error("The safe array data was not what we expected - got " + str(resultData)) if testData != list(byRefParam): raise error("The safe array data was not what we expected - got " + str(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 '%s', got '%s'" % (testData, list(byRefParam)) assert testData == list(resultData), "Expected '%s', got '%s'" % (testData, list(resultData)) # This time, instead of an explicit str() for 1.5, 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 error("Should have failed by now") except pythoncom.com_error as exc: if exc.hresult != winerror.DISP_E_TYPEMISMATCH: raise error("Expected DISP_E_TYPEMISMATCH") s = vbtest.StructProperty if s.int_val != 99 or str(s.str_val) != "hello": raise error("The struct value was not correct") s.str_val = "Hi from Python" s.int_val = 11 if s.int_val != 11 or str(s.str_val) != "Hi from Python": raise error("The struct value didnt persist!") if s.sub_val.int_val != 66 or str(s.sub_val.str_val) != "sub hello": raise error("The sub-struct value was not correct") sub = s.sub_val sub.int_val = 22 if sub.int_val != 22: print(sub.int_val) raise error("The sub-struct value didnt persist!") if s.sub_val.int_val != 22: print(s.sub_val.int_val) raise error("The sub-struct value (re-fetched) didnt persist!") if s.sub_val.array_val[0].int_val != 0 or str(s.sub_val.array_val[0].str_val) != "zero": print(s.sub_val.array_val[0].int_val) raise error("The array element wasnt correct") s.sub_val.array_val[0].int_val = 99 s.sub_val.array_val[1].int_val = 66 if s.sub_val.array_val[0].int_val != 99 or \ s.sub_val.array_val[1].int_val != 66: print(s.sub_val.array_val[0].int_val) raise error("The array element didnt persist.") # Now pass the struct back to VB vbtest.StructProperty = s # And get it back again s = vbtest.StructProperty if s.int_val != 11 or str(s.str_val) != "Hi from Python": raise error("After sending to VB, the struct value didnt persist!") if s.sub_val.array_val[0].int_val != 99: raise error("After sending to VB, the struct array value didnt persist!") # Now do some object equality tests. assert s==s assert s!=None if sys.version_info > (3,0): try: s < None raise error("Expected type error") except TypeError: pass try: None < s raise error("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 didnt 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 RuntimeError("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 RuntimeError("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)" % (s.int_val, s.str_val, s.ob_val, s.sub_val) if repr(s) != expected: print("Expected repr:", expected) print("Actual repr :", repr(s)) raise RuntimeError("repr() of record object failed") print("Struct/Record tests passed") def TestVBInterface(ob): t = ob.GetInterfaceTester(2) if t.getn() != 2: raise error("Initial value wrong") t.setn(3) if t.getn() != 3: raise error("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_!=None assert None!=ob._oleobj_ assert ob!=None assert None!=ob if sys.version_info > (3,0): try: ob < None raise error("Expected type error") except TypeError: pass try: None < ob raise error("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()