testvb.py 19.8 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
# 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, 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, u"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, 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, 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()