1""" 2Helper code for implementing unittests. 3 4This module is unsupported and is primairily used in the PyObjC 5testsuite. 6""" 7import plistlib as _pl 8import unittest as _unittest 9import objc 10import os as _os 11import gc as _gc 12import subprocess as _subprocess 13import sys as _sys 14import struct as _struct 15from distutils.sysconfig import get_config_var as _get_config_var 16import re as _re 17 18# Have a way to disable the autorelease pool behaviour 19_usepool = not _os.environ.get('PYOBJC_NO_AUTORELEASE') 20_useleaks = bool(_os.environ.get('PyOBJC_USE_LEAKS')) 21_leaksVerbose = True 22 23def sdkForPython(_cache=[]): 24 """ 25 Return the SDK version used to compile Python itself, 26 or None if no framework was used 27 """ 28 if not _cache: 29 30 cflags = _get_config_var('CFLAGS') 31 m = _re.search('-isysroot ([^ ]*) ', cflags) 32 if m is None: 33 return None 34 35 path = m.group(1) 36 bn = _os.path.basename(path) 37 version = bn[6:-4] 38 if version.endswith('u'): 39 version = version[:-1] 40 41 return map(int, version.split('.')) 42 43 return _cache[0] 44 45def fourcc(v): 46 """ 47 Decode four-character-code integer definition 48 49 (e.g. 'abcd') 50 """ 51 return _struct.unpack('>i', v)[0] 52 53def cast_int(value): 54 """ 55 Cast value to 32bit integer 56 57 Usage: 58 cast_int(1 << 31) == -1 59 60 (where as: 1 << 31 == 2147483648) 61 """ 62 value = value & 0xffffffff 63 if value & 0x80000000: 64 value = ~value + 1 & 0xffffffff 65 return -value 66 else: 67 return value 68 69_os_release = None 70def os_release(): 71 """ 72 Returns '10.5' on all releases of Leopard, simularly for other 73 major releases. 74 """ 75 global _os_release 76 if _os_release is not None: 77 return _os_release 78 79 pl = _pl.readPlist('/System/Library/CoreServices/SystemVersion.plist') 80 v = pl['ProductVersion'] 81 return tuple(map(int, v.split('.')[:2])) 82 83def onlyOn32Bit(function): 84 """ 85 Usage:: 86 87 class Tests (unittest.TestCase): 88 89 @onlyOn32Bit 90 def test32BitOnly(self): 91 pass 92 93 The test runs only on 32-bit systems 94 """ 95 if _sys.maxint > 2 ** 32: 96 return None 97 else: 98 return function 99 100 101def min_os_level(release): 102 """ 103 Usage:: 104 105 class Tests (unittest.TestCase): 106 107 @min_os_level('10.6') 108 def testSnowLeopardCode(self): 109 pass 110 """ 111 if os_release() >= tuple(map(int, release.split('.'))): 112 def decorator(function): 113 return function 114 115 else: 116 def decorator(function): 117 return None 118 119 return decorator 120 121 122 123def _leaks(): 124 data = _subprocess.Popen( 125 ['/usr/bin/leaks', str(_os.getpid())], stdout=_subprocess.PIPE 126 ).communicate()[0] 127 return data.splitlines() 128 129 130_poolclass = objc.lookUpClass('NSAutoreleasePool') 131_nscftype = objc.lookUpClass('NSCFType') 132 133class TestCase (_unittest.TestCase): 134 """ 135 A version of TestCase that wraps every test into its own 136 autorelease pool. 137 138 This also adds a number of useful assertion methods 139 """ 140 def failUnlessIsCFType(self, tp, message = None): 141 if not isinstance(tp, objc.objc_class): 142 self.fail(message or "%r is not a CFTypeRef type"%(tp,)) 143 144 if tp is _nscftype: 145 self.fail(message or "%r is not a unique CFTypeRef type"%(tp,)) 146 147 def failUnlessIsOpaquePointer(self, tp, message = None): 148 if not hasattr(tp, "__pointer__"): 149 self.fail(message or "%r is not an opaque-pointer"%(tp,)) 150 151 if not hasattr(tp, "__typestr__"): 152 self.fail(message or "%r is not an opaque-pointer"%(tp,)) 153 154 155 def failUnlessIsNone(self, value, message = None): 156 if value is not None: 157 sel.fail(message or "%r is not %r"%(value, test)) 158 159 def failIfIsNone(self, value, message = None): 160 if value is None: 161 sel.fail(message, "%r is not %r"%(value, test)) 162 163 def failUnlessResultIsNullTerminated(self, method, message = None): 164 info = method.__metadata__() 165 if not info['retval'].get('c_array_delimited_by_null'): 166 self.fail(message or "argument %d of %r is not a nul-terminated array"%(argno, method)) 167 168 def failUnlessIsNullTerminated(self, method, message = None): 169 info = method.__metadata__() 170 if not info.get('c_array_delimited_by_null') or not info.get('variadic'): 171 self.fail(message or "%s is not a variadic function with a null-terminated list of arguments"%(method,)) 172 173 def failUnlessArgIsNullTerminated(self, method, argno, message = None): 174 if isinstance(method, objc.selector): 175 offset = 2 176 else: 177 offset = 0 178 info = method.__metadata__() 179 if not info['arguments'][argno+offset].get('c_array_delimited_by_null'): 180 self.fail(message or "argument %d of %r is not a nul-terminated array"%(argno, method)) 181 182 def failUnlessArgIsVariableSize(self, method, argno, message = None): 183 if isinstance(method, objc.selector): 184 offset = 2 185 else: 186 offset = 0 187 info = method.__metadata__() 188 if not info['arguments'][argno+offset].get('c_array_of_variable_length'): 189 self.fail(message or "argument %d of %r is not a variable sized array"%(argno, method)) 190 191 def failUnlessResultIsVariableSize(self, method, message = None): 192 info = method.__metadata__() 193 if not info['retval'].get('c_array_of_variable_length'): 194 self.fail(message or "result of %r is not a variable sized array"%(argno, method)) 195 196 def failUnlessArgSizeInResult(self, method, argno, message = None): 197 if isinstance(method, objc.selector): 198 offset = 2 199 else: 200 offset = 0 201 info = method.__metadata__() 202 if not info['arguments'][argno+offset].get('c_array_length_in_result'): 203 self.fail(message or "argument %d of %r does not have size in result"%(argno, method)) 204 205 def failUnlessArgIsPrintf(self, method, argno, message = None): 206 if isinstance(method, objc.selector): 207 offset = 2 208 else: 209 offset = 0 210 info = method.__metadata__() 211 if not info.get('variadic'): 212 self.fail(message or "%r is not a variadic function"%(method,)) 213 214 if not info['arguments'][argno+offset].get('printf_format'): 215 self.fail(message or "%r argument %d is not a printf format string"%(method, argno)) 216 217 def failUnlessArgIsCFRetained(self, method, argno, message = None): 218 if isinstance(method, objc.selector): 219 offset = 2 220 else: 221 offset = 0 222 info = method.__metadata__() 223 if not info['arguments'][argno+offset]['already_cfretained']: 224 self.fail(message or "%r is not cfretained"%(method,)) 225 226 def failIfArgIsCFRetained(self, method, argno, message = None): 227 if isinstance(method, objc.selector): 228 offset = 2 229 else: 230 offset = 0 231 info = method.__metadata__() 232 if info['arguments'][argno+offset]['already_cfretained']: 233 self.fail(message or "%r is cfretained"%(method,)) 234 235 def failUnlessResultIsCFRetained(self, method, message = None): 236 info = method.__metadata__() 237 if not info['retval']['already_cfretained']: 238 self.fail(message or "%r is not cfretained"%(method,)) 239 240 def failIfResultIsCFRetained(self, method, message = None): 241 info = method.__metadata__() 242 if info['retval']['already_cfretained']: 243 self.fail(message or "%r is cfretained"%(method,)) 244 245 def failUnlessResultIsRetained(self, method, message = None): 246 info = method.__metadata__() 247 if not info['retval']['already_retained']: 248 self.fail(message or "%r is not retained"%(method,)) 249 250 def failIfResultIsRetained(self, method, message = None): 251 info = method.__metadata__() 252 if info['retval']['already_retained']: 253 self.fail(message or "%r is retained"%(method,)) 254 255 def failUnlessResultHasType(self, method, tp, message=None): 256 info = method.__metadata__() 257 type = info['retval']['type'] 258 if type != tp: 259 self.fail(message or "result of %r is not of type %r, but %r"%( 260 method, tp, type)) 261 262 def failUnlessArgHasType(self, method, argno, tp, message=None): 263 if isinstance(method, objc.selector): 264 offset = 2 265 else: 266 offset = 0 267 info = method.__metadata__() 268 type = info['arguments'][argno+offset]['type'] 269 if type != tp: 270 self.fail(message or "arg %d of %s is not of type %r, but %r"%( 271 argno, method, tp, type)) 272 273 def failUnlessArgIsFunction(self, method, argno, sel_type, retained, message=None): 274 if isinstance(method, objc.selector): 275 offset = 2 276 else: 277 offset = 0 278 info = method.__metadata__() 279 type = info['arguments'][argno+offset]['type'] 280 if type != '^?': 281 self.fail(message or "arg %d of %s is not of type function_pointer"%( 282 argno, method)) 283 284 st = info['arguments'][argno+offset].get('callable') 285 if st is None: 286 self.fail(message or "arg %d of %s is not of type function_pointer"%( 287 argno, method)) 288 289 iface = st['retval']['type'] 290 for a in st['arguments']: 291 iface += a['type'] 292 293 if iface != sel_type: 294 self.fail(message or "arg %d of %s is not a function_pointer with type %r, but %r"%(argno, method, sel_type, iface)) 295 296 297 st = info['arguments'][argno+offset]['callable_retained'] 298 if bool(st) != bool(retained): 299 self.fail(message or "arg %d of %s; retained: %r, expected: %r"%( 300 argno, method, st, retained)) 301 302 303 def failUnlessArgIsSEL(self, method, argno, sel_type, message=None): 304 if isinstance(method, objc.selector): 305 offset = 2 306 else: 307 offset = 0 308 info = method.__metadata__() 309 type = info['arguments'][argno+offset]['type'] 310 if type != objc._C_SEL: 311 self.fail(message or "arg %d of %s is not of type SEL"%( 312 argno, method)) 313 314 st = info['arguments'][argno+offset].get('sel_of_type') 315 if st != sel_type: 316 self.fail(message or "arg %d of %s doesn't have sel_type %r but %r"%( 317 argno, method, sel_type, st)) 318 319 def failUnlessResultIsBOOL(self, method, message=None): 320 info = method.__metadata__() 321 type = info['retval']['type'] 322 if type != objc._C_NSBOOL: 323 self.fail(message or "result of %s is not of type BOOL"%( 324 method)) 325 326 def failUnlessArgIsBOOL(self, method, argno, message=None): 327 if isinstance(method, objc.selector): 328 offset = 2 329 else: 330 offset = 0 331 info = method.__metadata__() 332 type = info['arguments'][argno+offset]['type'] 333 if type != objc._C_NSBOOL: 334 self.fail(message or "arg %d of %s is not of type BOOL"%( 335 argno, method)) 336 337 def failUnlessArgIsFixedSize(self, method, argno, count, message=None): 338 if isinstance(method, objc.selector): 339 offset = 2 340 else: 341 offset = 0 342 info = method.__metadata__() 343 cnt = info['arguments'][argno+offset]['c_array_of_fixed_length'] 344 if cnt != count: 345 self.fail(message or "arg %d of %s is not a C-array of length %d"%( 346 argno, method, count)) 347 348 def failUnlessArgSizeInArg(self, method, argno, count, message=None): 349 if isinstance(method, objc.selector): 350 offset = 2 351 else: 352 offset = 0 353 info = method.__metadata__() 354 cnt = info['arguments'][argno+offset]['c_array_length_in_arg'] 355 if isinstance(count, (list, tuple)): 356 count2 = tuple(x + offset for x in count) 357 else: 358 count2 = count + offset 359 if cnt != count2: 360 self.fail(message or "arg %d of %s is not a C-array of with length in arg %d"%( 361 argno, method, count)) 362 363 def failUnlessResultSizeInArg(self, method, count, message=None): 364 if isinstance(method, objc.selector): 365 offset = 2 366 else: 367 offset = 0 368 info = method.__metadata__() 369 cnt = info['retval']['c_array_length_in_arg'] 370 if cnt != count + offset: 371 self.fail(message or "result %s is not a C-array of with length in arg %d"%( 372 argno, method, count)) 373 374 375 def failUnlessArgIsOut(self, method, argno, message=None): 376 if isinstance(method, objc.selector): 377 offset = 2 378 else: 379 offset = 0 380 info = method.__metadata__() 381 type = info['arguments'][argno+offset]['type'] 382 if not type.startswith('o^'): 383 self.fail(message or "arg %d of %s is not an 'out' argument"%( 384 argno, method)) 385 386 def failUnlessArgIsInOut(self, method, argno, message=None): 387 if isinstance(method, objc.selector): 388 offset = 2 389 else: 390 offset = 0 391 info = method.__metadata__() 392 type = info['arguments'][argno+offset]['type'] 393 if not type.startswith('N^'): 394 self.fail(message or "arg %d of %s is not an 'inout' argument"%( 395 argno, method)) 396 397 def failUnlessArgIsIn(self, method, argno, message=None): 398 if isinstance(method, objc.selector): 399 offset = 2 400 else: 401 offset = 0 402 info = method.__metadata__() 403 type = info['arguments'][argno+offset]['type'] 404 if not type.startswith('n^'): 405 self.fail(message or "arg %d of %s is not an 'in' argument"%( 406 argno, method)) 407 408 409 def failUnlessStartswith(self, value, check, message=None): 410 if not value.startswith(check): 411 self.fail(message or "not %r.startswith(%r)"%(value, check)) 412 413 def failUnlessIsInstance(self, value, types, message=None): 414 if not isinstance(value, types): 415 self.fail(message or "%s is not an instance of %r"%(value, types)) 416 417 def failIfIsInstance(self, value, types, message=None): 418 if isinstance(value, types): 419 self.fail(message or "%s is an instance of %r"%(value, types)) 420 421 assertIsInstance = failUnlessIsInstance 422 423 if not hasattr(_unittest.TestCase, "assertAlmostEquals"): 424 def assertAlmostEquals(self, val1, val2, message=None): 425 self.failUnless(abs (val1 - val2) < 0.00001, message) 426 427 428 429 def run(self, *args): 430 if _useleaks: 431 leaksBefore = _leaks() 432 if _usepool: 433 p = _poolclass.alloc().init() 434 else: 435 p = 1 436 437 try: 438 _unittest.TestCase.run(self, *args) 439 finally: 440 _gc.collect() 441 del p 442 _gc.collect() 443 444 if _useleaks: 445 leaksAfter = _leaks() 446 if len(leaksBefore) != len(leaksAfter): 447 print "\ntest %s is leaking [%d lines]"%(self, len(leaksAfter) - len(leaksBefore)) 448 if _leaksVerbose: 449 # XXX: add a smartish filter the surpresses the leaks 450 # in leaksBefore. 451 for ln in leaksAfter: 452 print ln 453 454main = _unittest.main 455 456if hasattr(_unittest, 'expectedFailure'): 457 expectedFailure = _unittest.expectedFailure 458else: 459 def expectedFailure(func): 460 def test(self): 461 try: 462 func(self) 463 464 except AssertionError: 465 return 466 467 self.fail("test unexpectedly passed") 468 test.__name__ == func.__name__ 469 470 return test 471 472