1442 lines
46 KiB
Python
1442 lines
46 KiB
Python
"""
|
|
Helper code for implementing unittests.
|
|
|
|
This module is unsupported and is primairily used in the PyObjC
|
|
testsuite.
|
|
"""
|
|
|
|
import contextlib
|
|
import gc as _gc
|
|
import os as _os
|
|
import re as _re
|
|
import struct as _struct
|
|
import sys as _sys
|
|
import unittest as _unittest
|
|
import subprocess as _subprocess
|
|
import pickle as _pickle
|
|
from sysconfig import get_config_var as _get_config_var
|
|
|
|
import objc
|
|
|
|
|
|
# Ensure that methods in this module get filtered in the tracebacks
|
|
# from unittest
|
|
__unittest = False
|
|
|
|
# Have a way to disable the autorelease pool behaviour
|
|
_usepool = not _os.environ.get("PYOBJC_NO_AUTORELEASE")
|
|
|
|
# XXX: Python 2 Compatibility for the PyObjC Test Suite
|
|
try:
|
|
unicode
|
|
except NameError:
|
|
unicode = str
|
|
|
|
try:
|
|
long
|
|
except NameError:
|
|
long = int
|
|
|
|
try:
|
|
basestring
|
|
except NameError:
|
|
basestring = str
|
|
|
|
try:
|
|
unichr
|
|
except NameError:
|
|
unichr = chr
|
|
|
|
|
|
def _typemap(tp):
|
|
if tp is None:
|
|
return None
|
|
return (
|
|
tp.replace(b"_NSRect", b"CGRect")
|
|
.replace(b"_NSPoint", b"CGPoint")
|
|
.replace(b"_NSSize", b"CGSize")
|
|
)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def pyobjc_options(**kwds):
|
|
orig = {}
|
|
try:
|
|
for k in kwds:
|
|
orig[k] = getattr(objc.options, k)
|
|
setattr(objc.options, k, kwds[k])
|
|
|
|
yield
|
|
|
|
finally:
|
|
for k in orig:
|
|
setattr(objc.options, k, orig[k])
|
|
|
|
|
|
def sdkForPython(_cache=[]): # noqa: B006, M511
|
|
"""
|
|
Return the SDK version used to compile Python itself,
|
|
or None if no framework was used
|
|
"""
|
|
if not _cache:
|
|
cflags = _get_config_var("CFLAGS")
|
|
m = _re.search(r"-isysroot\s+([^ ]*)(\s|$)", cflags)
|
|
if m is None:
|
|
_cache.append(None)
|
|
return None
|
|
|
|
path = m.group(1)
|
|
if path == "/":
|
|
result = tuple(map(int, os_release().split(".")))
|
|
_cache.append(result)
|
|
return result
|
|
|
|
bn = _os.path.basename(path)
|
|
version = bn[6:-4]
|
|
if version.endswith("u"):
|
|
version = version[:-1]
|
|
|
|
result = tuple(map(int, version.split(".")))
|
|
_cache.append(result)
|
|
return result
|
|
|
|
return _cache[0]
|
|
|
|
|
|
def fourcc(v):
|
|
"""
|
|
Decode four-character-code integer definition
|
|
|
|
(e.g. 'abcd')
|
|
"""
|
|
return _struct.unpack(">i", v)[0]
|
|
|
|
|
|
def cast_int(value):
|
|
"""
|
|
Cast value to 32bit integer
|
|
|
|
Usage:
|
|
cast_int(1 << 31) == -1
|
|
|
|
(where as: 1 << 31 == 2147483648)
|
|
"""
|
|
value = value & 0xFFFFFFFF
|
|
if value & 0x80000000:
|
|
value = ~value + 1 & 0xFFFFFFFF
|
|
return -value
|
|
else:
|
|
return value
|
|
|
|
|
|
def cast_longlong(value):
|
|
"""
|
|
Cast value to 64bit integer
|
|
|
|
Usage:
|
|
cast_longlong(1 << 63) == -1
|
|
"""
|
|
value = value & 0xFFFFFFFFFFFFFFFF
|
|
if value & 0x8000000000000000:
|
|
value = ~value + 1 & 0xFFFFFFFFFFFFFFFF
|
|
return -value
|
|
else:
|
|
return value
|
|
|
|
|
|
def cast_uint(value):
|
|
"""
|
|
Cast value to 32bit integer
|
|
|
|
Usage:
|
|
cast_int(1 << 31) == 2147483648
|
|
|
|
"""
|
|
value = value & 0xFFFFFFFF
|
|
return value
|
|
|
|
|
|
def cast_ulonglong(value):
|
|
"""
|
|
Cast value to 64bit integer
|
|
"""
|
|
value = value & 0xFFFFFFFFFFFFFFFF
|
|
return value
|
|
|
|
|
|
_os_release = None
|
|
|
|
|
|
def os_release():
|
|
"""
|
|
Returns the release of macOS (for example 10.5.1).
|
|
"""
|
|
global _os_release
|
|
if _os_release is not None:
|
|
return _os_release
|
|
|
|
_os_release = (
|
|
_subprocess.check_output(["sw_vers", "-productVersion"]).decode().strip()
|
|
)
|
|
|
|
return _os_release
|
|
|
|
|
|
def arch_only(arch):
|
|
"""
|
|
Usage::
|
|
class Tests (unittest.TestCase):
|
|
|
|
@arch_only("arm64")
|
|
def testArm64(self):
|
|
pass
|
|
|
|
The test runs only when the specified architecture matches
|
|
"""
|
|
|
|
def decorator(function):
|
|
return _unittest.skipUnless(objc.arch == arch, f"{arch} only")(function)
|
|
|
|
return decorator
|
|
|
|
|
|
def min_python_release(version):
|
|
"""
|
|
Usage::
|
|
|
|
class Tests (unittest.TestCase):
|
|
|
|
@min_python_release('3.2')
|
|
def test_python_3_2(self):
|
|
pass
|
|
"""
|
|
parts = tuple(map(int, version.split(".")))
|
|
return _unittest.skipUnless(
|
|
_sys.version_info[:2] >= parts, f"Requires Python {version} or later"
|
|
)
|
|
|
|
|
|
def _sort_key(version):
|
|
parts = version.split(".")
|
|
if len(parts) == 2:
|
|
parts.append("0")
|
|
|
|
if len(parts) != 3:
|
|
raise ValueError(f"Invalid version: {version!r}")
|
|
|
|
return tuple(int(x) for x in parts)
|
|
|
|
|
|
def os_level_key(release):
|
|
"""
|
|
Return an object that can be used to compare two releases.
|
|
"""
|
|
return _sort_key(release)
|
|
|
|
|
|
def min_sdk_level(release):
|
|
"""
|
|
Usage::
|
|
|
|
class Tests (unittest.TestCase):
|
|
@min_sdk_level('10.6')
|
|
def testSnowLeopardSDK(self):
|
|
pass
|
|
"""
|
|
v = (objc.PyObjC_BUILD_RELEASE // 100, objc.PyObjC_BUILD_RELEASE % 100, 0)
|
|
return _unittest.skipUnless(
|
|
v >= os_level_key(release), f"Requires build with SDK {release} or later"
|
|
)
|
|
|
|
|
|
def max_sdk_level(release):
|
|
"""
|
|
Usage::
|
|
|
|
class Tests (unittest.TestCase):
|
|
@max_sdk_level('10.5')
|
|
def testUntilLeopardSDK(self):
|
|
pass
|
|
"""
|
|
v = (objc.PyObjC_BUILD_RELEASE // 100, objc.PyObjC_BUILD_RELEASE % 100, 0)
|
|
return _unittest.skipUnless(
|
|
v <= os_level_key(release), f"Requires build with SDK {release} or later"
|
|
)
|
|
|
|
|
|
def min_os_level(release):
|
|
"""
|
|
Usage::
|
|
|
|
class Tests (unittest.TestCase):
|
|
|
|
@min_os_level('10.6')
|
|
def testSnowLeopardCode(self):
|
|
pass
|
|
"""
|
|
return _unittest.skipUnless(
|
|
os_level_key(os_release()) >= os_level_key(release),
|
|
f"Requires macOS {release} or later",
|
|
)
|
|
|
|
|
|
def max_os_level(release):
|
|
"""
|
|
Usage::
|
|
|
|
class Tests (unittest.TestCase):
|
|
|
|
@max_os_level('10.5')
|
|
def testUntilLeopard(self):
|
|
pass
|
|
"""
|
|
return _unittest.skipUnless(
|
|
os_level_key(os_release()) <= os_level_key(release),
|
|
f"Requires macOS up to {release}",
|
|
)
|
|
|
|
|
|
def os_level_between(min_release, max_release):
|
|
"""
|
|
Usage::
|
|
|
|
class Tests (unittest.TestCase):
|
|
|
|
@os_level_between('10.5', '10.8')
|
|
def testUntilLeopard(self):
|
|
pass
|
|
"""
|
|
return _unittest.skipUnless(
|
|
os_level_key(min_release)
|
|
<= os_level_key(os_release())
|
|
<= os_level_key(max_release),
|
|
f"Requires macOS {min_release} up to {max_release}",
|
|
)
|
|
|
|
|
|
_poolclass = objc.lookUpClass("NSAutoreleasePool")
|
|
|
|
# NOTE: On at least macOS 10.8 there are multiple proxy classes for CFTypeRef...
|
|
_nscftype = tuple(cls for cls in objc.getClassList(1) if "NSCFType" in cls.__name__)
|
|
|
|
_typealias = {}
|
|
|
|
_typealias[objc._C_LNG_LNG] = objc._C_LNG
|
|
_typealias[objc._C_ULNG_LNG] = objc._C_ULNG
|
|
|
|
_idlike_cache = set()
|
|
|
|
|
|
class TestCase(_unittest.TestCase):
|
|
"""
|
|
A version of TestCase that wraps every test into its own
|
|
autorelease pool.
|
|
|
|
This also adds a number of useful assertion methods
|
|
"""
|
|
|
|
# New API for testing function/method signatures, with one assert for
|
|
# the callable and one assert each for every return value and argument.
|
|
#
|
|
# Primary reason for the new API is to ensure that all metadata overrides
|
|
# are explicitly tested.
|
|
|
|
def assertManualBinding(self, func):
|
|
if hasattr(func, "__metadata__"):
|
|
self.fail(f"{func} has automatic bindings")
|
|
|
|
def assertIsCFType(self, tp, message=None):
|
|
if not isinstance(tp, objc.objc_class):
|
|
self.fail(message or f"{tp!r} is not a CFTypeRef type")
|
|
|
|
if any(x is tp for x in _nscftype):
|
|
self.fail(message or f"{tp!r} is not a unique CFTypeRef type")
|
|
|
|
for cls in tp.__bases__:
|
|
if "NSCFType" in cls.__name__:
|
|
return
|
|
|
|
self.fail(message or f"{tp!r} is not a CFTypeRef type")
|
|
|
|
# NOTE: Don't test if this is a subclass of one of the known
|
|
# CF roots, this tests is mostly used to ensure that the
|
|
# type is distinct from one of those roots.
|
|
# NOTE: With the next two lines enabled there are spurious test
|
|
# failures when a CF type is toll-free bridged to an
|
|
# (undocumented) Cocoa class. It might be worthwhile to
|
|
# look for these, but not in the test suite.
|
|
# if not issubclass(tp, _nscftype):
|
|
# self.fail(message or "%r is not a CFTypeRef subclass"%(tp,))
|
|
|
|
def assertIsEnumType(self, tp):
|
|
if not hasattr(tp, "__supertype__"):
|
|
# Ducktyping for compatibility with Python 3.7
|
|
# or earlier.
|
|
self.fail(f"{tp!r} is not a typing.NewType")
|
|
|
|
if tp.__supertype__ != int:
|
|
self.fail(f"{tp!r} is not a typing.NewType based on 'int'")
|
|
|
|
def assertIsTypedEnum(self, tp, base):
|
|
if not hasattr(tp, "__supertype__"):
|
|
# Ducktyping for compatibility with Python 3.7
|
|
# or earlier.
|
|
self.fail(f"{tp!r} is not a typing.NewType")
|
|
|
|
if tp.__supertype__ != base:
|
|
self.fail(f"{tp!r} is not a typing.NewType based on {base.__name__!r}")
|
|
|
|
def assertIsOpaquePointer(self, tp, message=None):
|
|
if not hasattr(tp, "__pointer__"):
|
|
self.fail(message or f"{tp!r} is not an opaque-pointer")
|
|
|
|
if not hasattr(tp, "__typestr__"):
|
|
self.fail(message or f"{tp!r} is not an opaque-pointer")
|
|
|
|
def assertResultIsNullTerminated(self, method, message=None):
|
|
info = method.__metadata__()
|
|
if not info.get("retval", {}).get("c_array_delimited_by_null"):
|
|
self.fail(message or f"result of {method!r} is not a null-terminated array")
|
|
|
|
def assertIsNullTerminated(self, method, message=None):
|
|
info = method.__metadata__()
|
|
if not info.get("c_array_delimited_by_null") or not info.get("variadic"):
|
|
self.fail(
|
|
message
|
|
or "%s is not a variadic function with a "
|
|
"null-terminated list of arguments" % (method,)
|
|
)
|
|
|
|
def assertArgIsIDLike(self, method, argno, message=None):
|
|
global _idlike_cache
|
|
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
tp = info["arguments"][argno + offset].get("type")
|
|
|
|
if tp in {b"@", b"^@", b"n^@", b"N^@", b"o^@"}:
|
|
return
|
|
|
|
if tp in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"^") and tp[1:] in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"o^") and tp[2:] in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"n^") and tp[2:] in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"N^") and tp[2:] in _idlike_cache:
|
|
return
|
|
|
|
# Assume that tests are supposed to pass,
|
|
# our cache may be out of date
|
|
tmp = set(objc._idSignatures())
|
|
_idlike_cache = set(tmp)
|
|
|
|
if tp in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"^") and tp[1:] in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"o^") and tp[2:] in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"n^") and tp[2:] in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"N^") and tp[2:] in _idlike_cache:
|
|
return
|
|
|
|
self.fail(
|
|
message or "argument %d of %r is not IDLike (%r)" % (argno, method, tp)
|
|
)
|
|
|
|
def assertResultIsIDLike(self, method, message=None):
|
|
global _idlike_cache
|
|
|
|
info = method.__metadata__()
|
|
tp = info["retval"].get("type")
|
|
|
|
if tp in {b"@", b"^@", b"n^@", b"N^@", b"o^@"}:
|
|
return
|
|
|
|
if tp in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"^") and tp[1:] in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"o^") and tp[2:] in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"n^") and tp[2:] in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"N^") and tp[2:] in _idlike_cache:
|
|
return
|
|
|
|
# Assume that tests are supposed to pass,
|
|
# our cache may be out of date
|
|
tmp = set(objc._idSignatures())
|
|
_idlike_cache = set(tmp)
|
|
|
|
if tp in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"^") and tp[1:] in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"o^") and tp[2:] in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"n^") and tp[2:] in _idlike_cache:
|
|
return
|
|
elif tp.startswith(b"N^") and tp[2:] in _idlike_cache:
|
|
return
|
|
|
|
self.fail(message or f"result of {method!r} is not IDLike ({tp!r})")
|
|
|
|
def assertArgIsNullTerminated(self, method, argno, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
try:
|
|
if not info["arguments"][argno + offset].get("c_array_delimited_by_null"):
|
|
self.fail(
|
|
message
|
|
or "argument %d of %r is not a null-terminated array"
|
|
% (argno, method)
|
|
)
|
|
except (KeyError, IndexError):
|
|
self.fail(
|
|
message
|
|
or "argument %d of %r is not a null-terminated array" % (argno, method)
|
|
)
|
|
|
|
def assertArgIsVariableSize(self, method, argno, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
try:
|
|
if not info["arguments"][argno + offset].get("c_array_of_variable_length"):
|
|
self.fail(
|
|
message
|
|
or "argument %d of %r is not a variable sized array"
|
|
% (argno, method)
|
|
)
|
|
except (KeyError, IndexError):
|
|
self.fail(
|
|
message
|
|
or "argument %d of %r is not a variable sized array" % (argno, method)
|
|
)
|
|
|
|
def assertResultIsVariableSize(self, method, message=None):
|
|
info = method.__metadata__()
|
|
if not info.get("retval", {}).get("c_array_of_variable_length", False):
|
|
self.fail(message or f"result of {method!r} is not a variable sized array")
|
|
|
|
def assertArgSizeInResult(self, method, argno, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
try:
|
|
if not info["arguments"][argno + offset].get("c_array_length_in_result"):
|
|
self.fail(
|
|
message
|
|
or "argument %d of %r does not have size in result"
|
|
% (argno, method)
|
|
)
|
|
except (KeyError, IndexError):
|
|
self.fail(
|
|
message
|
|
or "argument %d of %r does not have size in result" % (argno, method)
|
|
)
|
|
|
|
def assertArgIsPrintf(self, method, argno, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
if not info.get("variadic"):
|
|
self.fail(message or f"{method!r} is not a variadic function")
|
|
|
|
try:
|
|
if not info["arguments"][argno + offset].get("printf_format"):
|
|
self.fail(
|
|
message
|
|
or "%r argument %d is not a printf format string" % (method, argno)
|
|
)
|
|
except (KeyError, IndexError):
|
|
self.fail(
|
|
message
|
|
or "%r argument %d is not a printf format string" % (method, argno)
|
|
)
|
|
|
|
def assertArgIsCFRetained(self, method, argno, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
|
|
try:
|
|
if not info["arguments"][argno + offset]["already_cfretained"]:
|
|
self.fail(
|
|
message or f"Argument {argno} of {method!r} is not cfretained"
|
|
)
|
|
except (KeyError, IndexError):
|
|
self.fail(message or f"Argument {argno} of {method!r} is not cfretained")
|
|
|
|
def assertArgIsNotCFRetained(self, method, argno, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
try:
|
|
if info["arguments"][argno + offset]["already_cfretained"]:
|
|
self.fail(message or f"Argument {argno} of {method!r} is cfretained")
|
|
except (KeyError, IndexError):
|
|
pass
|
|
|
|
def assertResultIsCFRetained(self, method, message=None):
|
|
info = method.__metadata__()
|
|
|
|
if not info.get("retval", {}).get("already_cfretained", False):
|
|
self.fail(message or f"{method!r} is not cfretained")
|
|
|
|
def assertResultIsNotCFRetained(self, method, message=None):
|
|
info = method.__metadata__()
|
|
if info.get("retval", {}).get("already_cfretained", False):
|
|
self.fail(message or f"{method!r} is cfretained")
|
|
|
|
def assertArgIsRetained(self, method, argno, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
|
|
try:
|
|
if not info["arguments"][argno + offset]["already_retained"]:
|
|
self.fail(message or f"Argument {argno} of {method!r} is not retained")
|
|
except (KeyError, IndexError):
|
|
self.fail(message or f"Argument {argno} of {method!r} is not retained")
|
|
|
|
def assertArgIsNotRetained(self, method, argno, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
try:
|
|
if info["arguments"][argno + offset]["already_retained"]:
|
|
self.fail(message or f"Argument {argno} of {method!r} is retained")
|
|
except (KeyError, IndexError):
|
|
pass
|
|
|
|
def assertResultIsRetained(self, method, message=None):
|
|
info = method.__metadata__()
|
|
if not info.get("retval", {}).get("already_retained", False):
|
|
self.fail(message or f"Result of {method!r} is not retained")
|
|
|
|
def assertResultIsNotRetained(self, method, message=None):
|
|
info = method.__metadata__()
|
|
if info.get("retval", {}).get("already_retained", False):
|
|
self.fail(message or f"Result of {method!r} is retained")
|
|
|
|
def assertResultHasType(self, method, tp, message=None):
|
|
info = method.__metadata__()
|
|
typestr = info.get("retval").get("type", b"v")
|
|
if (
|
|
typestr != tp
|
|
and _typemap(typestr) != _typemap(tp)
|
|
and _typealias.get(typestr, typestr) != _typealias.get(tp, tp)
|
|
):
|
|
self.fail(
|
|
message
|
|
or f"result of {method!r} is not of type {tp!r}, but {typestr!r}"
|
|
)
|
|
|
|
def assertArgHasType(self, method, argno, tp, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
try:
|
|
i = info["arguments"][argno + offset]
|
|
|
|
except (KeyError, IndexError):
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s has no metadata (or doesn't exist)" % (argno, method)
|
|
)
|
|
|
|
else:
|
|
typestr = i.get("type", b"@")
|
|
|
|
if (
|
|
typestr != tp
|
|
and _typemap(typestr) != _typemap(tp)
|
|
and _typealias.get(typestr, typestr) != _typealias.get(tp, tp)
|
|
):
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is not of type %r, but %r"
|
|
% (argno, method, tp, typestr)
|
|
)
|
|
|
|
def assertArgIsFunction(self, method, argno, sel_type, retained, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
|
|
try:
|
|
i = info["arguments"][argno + offset]
|
|
except (KeyError, IndexError):
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s has no metadata (or doesn't exist)" % (argno, method)
|
|
)
|
|
|
|
else:
|
|
typestr = i.get("type", b"@")
|
|
|
|
if typestr != b"^?":
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is not of type function_pointer" % (argno, method)
|
|
)
|
|
|
|
st = i.get("callable")
|
|
if st is None:
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is not of type function_pointer" % (argno, method)
|
|
)
|
|
|
|
try:
|
|
iface = st["retval"]["type"]
|
|
for a in st["arguments"]:
|
|
iface += a["type"]
|
|
except KeyError:
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is a function pointer with incomplete type information"
|
|
% (argno, method)
|
|
)
|
|
|
|
if iface != sel_type:
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is not a function_pointer with type %r, but %r"
|
|
% (argno, method, sel_type, iface)
|
|
)
|
|
|
|
st = info["arguments"][argno + offset].get("callable_retained", False)
|
|
if bool(st) != bool(retained):
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s; retained: %r, expected: %r"
|
|
% (argno, method, st, retained)
|
|
)
|
|
|
|
def assertResultIsFunction(self, method, sel_type, message=None):
|
|
info = method.__metadata__()
|
|
|
|
try:
|
|
i = info["retval"]
|
|
except (KeyError, IndexError):
|
|
self.fail(
|
|
message or f"result of {method} has no metadata (or doesn't exist)"
|
|
)
|
|
|
|
else:
|
|
typestr = i.get("type", b"@")
|
|
|
|
if typestr != b"^?":
|
|
self.fail(message or f"result of {method} is not of type function_pointer")
|
|
|
|
st = i.get("callable")
|
|
if st is None:
|
|
self.fail(message or f"result of {method} is not of type function_pointer")
|
|
|
|
try:
|
|
iface = st["retval"]["type"]
|
|
for a in st["arguments"]:
|
|
iface += a["type"]
|
|
except KeyError:
|
|
self.fail(
|
|
message
|
|
or "result of %s is a function pointer with incomplete type information"
|
|
% (method,)
|
|
)
|
|
|
|
if iface != sel_type:
|
|
self.fail(
|
|
message
|
|
or "result of %s is not a function_pointer with type %r, but %r"
|
|
% (method, sel_type, iface)
|
|
)
|
|
|
|
def assertArgIsBlock(self, method, argno, sel_type, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
try:
|
|
typestr = info["arguments"][argno + offset]["type"]
|
|
except (IndexError, KeyError):
|
|
self.fail("arg %d of %s does not exist" % (argno, method))
|
|
|
|
if typestr != b"@?":
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is not of type block: %s" % (argno, method, typestr)
|
|
)
|
|
|
|
st = info["arguments"][argno + offset].get("callable")
|
|
if st is None:
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is not of type block: no callable" % (argno, method)
|
|
)
|
|
|
|
try:
|
|
iface = st["retval"]["type"]
|
|
if st["arguments"][0]["type"] != b"^v":
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s has an invalid block signature %r for argument 0"
|
|
% (argno, method, st["arguments"][0]["type"])
|
|
)
|
|
for a in st["arguments"][1:]:
|
|
iface += a["type"]
|
|
except KeyError:
|
|
self.fail(
|
|
message
|
|
or "result of %s is a block pointer with incomplete type information"
|
|
% (method,)
|
|
)
|
|
|
|
if iface != sel_type:
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is not a block with type %r, but %r"
|
|
% (argno, method, sel_type, iface)
|
|
)
|
|
|
|
def assertResultIsBlock(self, method, sel_type, message=None):
|
|
info = method.__metadata__()
|
|
|
|
try:
|
|
typestr = info["retval"]["type"]
|
|
if typestr != b"@?":
|
|
self.fail(
|
|
message or f"result of {method} is not of type block: {typestr}"
|
|
)
|
|
except KeyError:
|
|
self.fail(
|
|
message or "result of {} is not of type block: {}".format(method, b"v")
|
|
)
|
|
|
|
st = info["retval"].get("callable")
|
|
if st is None:
|
|
self.fail(
|
|
message
|
|
or "result of %s is not of type block: no callable specified" % (method)
|
|
)
|
|
|
|
try:
|
|
iface = st["retval"]["type"]
|
|
if st["arguments"][0]["type"] != b"^v":
|
|
self.fail(
|
|
message
|
|
or "result %s has an invalid block signature %r for argument 0"
|
|
% (method, st["arguments"][0]["type"])
|
|
)
|
|
for a in st["arguments"][1:]:
|
|
iface += a["type"]
|
|
except KeyError:
|
|
self.fail(
|
|
message
|
|
or "result of %s is a block pointer with incomplete type information"
|
|
% (method,)
|
|
)
|
|
|
|
if iface != sel_type:
|
|
self.fail(
|
|
message
|
|
or "result of %s is not a block with type %r, but %r"
|
|
% (method, sel_type, iface)
|
|
)
|
|
|
|
def assertResultIsSEL(self, method, sel_type, message=None):
|
|
info = method.__metadata__()
|
|
try:
|
|
i = info["retval"]
|
|
except (KeyError, IndexError):
|
|
self.fail(
|
|
message or f"result of {method} has no metadata (or doesn't exist)"
|
|
)
|
|
|
|
typestr = i.get("type", b"@")
|
|
if typestr != objc._C_SEL:
|
|
self.fail(message or f"result of {method} is not of type SEL")
|
|
|
|
st = i.get("sel_of_type")
|
|
if st != sel_type and _typemap(st) != _typemap(sel_type):
|
|
self.fail(
|
|
message
|
|
or "result of %s doesn't have sel_type %r but %r"
|
|
% (method, sel_type, st)
|
|
)
|
|
|
|
def assertArgIsSEL(self, method, argno, sel_type, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
try:
|
|
i = info["arguments"][argno + offset]
|
|
except (KeyError, IndexError):
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s has no metadata (or doesn't exist)" % (argno, method)
|
|
)
|
|
|
|
typestr = i.get("type", b"@")
|
|
if typestr != objc._C_SEL:
|
|
self.fail(message or "arg %d of %s is not of type SEL" % (argno, method))
|
|
|
|
st = i.get("sel_of_type")
|
|
if st != sel_type and _typemap(st) != _typemap(sel_type):
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s doesn't have sel_type %r but %r"
|
|
% (argno, method, sel_type, st)
|
|
)
|
|
|
|
def assertResultIsBOOL(self, method, message=None):
|
|
info = method.__metadata__()
|
|
typestr = info["retval"]["type"]
|
|
if typestr not in (objc._C_NSBOOL, objc._C_BOOL):
|
|
self.fail(
|
|
message or f"result of {method} is not of type BOOL, but {typestr!r}"
|
|
)
|
|
|
|
def assertArgIsBOOL(self, method, argno, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
typestr = info["arguments"][argno + offset]["type"]
|
|
if typestr not in (objc._C_NSBOOL, objc._C_BOOL):
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is not of type BOOL, but %r" % (argno, method, typestr)
|
|
)
|
|
|
|
def assertArgIsFixedSize(self, method, argno, count, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
try:
|
|
cnt = info["arguments"][argno + offset]["c_array_of_fixed_length"]
|
|
if cnt != count:
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is not a C-array of length %d"
|
|
% (argno, method, count)
|
|
)
|
|
except (KeyError, IndexError):
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is not a C-array of length %d" % (argno, method, count)
|
|
)
|
|
|
|
def assertResultIsFixedSize(self, method, count, message=None):
|
|
info = method.__metadata__()
|
|
try:
|
|
cnt = info["retval"]["c_array_of_fixed_length"]
|
|
if cnt != count:
|
|
self.fail(
|
|
message
|
|
or "result of %s is not a C-array of length %d" % (method, count)
|
|
)
|
|
except (KeyError, IndexError):
|
|
self.fail(
|
|
message
|
|
or "result of %s is not a C-array of length %d" % (method, count)
|
|
)
|
|
|
|
def assertArgSizeInArg(self, method, argno, count, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
try:
|
|
cnt = info["arguments"][argno + offset]["c_array_length_in_arg"]
|
|
except (KeyError, IndexError):
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is not a C-array of with length in arg %s"
|
|
% (argno, method, count)
|
|
)
|
|
|
|
if isinstance(count, (list, tuple)):
|
|
count2 = tuple(x + offset for x in count)
|
|
else:
|
|
count2 = count + offset
|
|
if cnt != count2:
|
|
self.fail(
|
|
message
|
|
or "arg %d of %s is not a C-array of with length in arg %s"
|
|
% (argno, method, count)
|
|
)
|
|
|
|
def assertResultSizeInArg(self, method, count, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
cnt = info["retval"]["c_array_length_in_arg"]
|
|
if cnt != count + offset:
|
|
self.fail(
|
|
message
|
|
or "result %s is not a C-array of with length in arg %d"
|
|
% (method, count)
|
|
)
|
|
|
|
def assertArgIsOut(self, method, argno, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
typestr = info["arguments"][argno + offset]["type"]
|
|
if not typestr.startswith(b"o^") and not typestr.startswith(b"o*"):
|
|
self.fail(
|
|
message or "arg %d of %s is not an 'out' argument" % (argno, method)
|
|
)
|
|
|
|
def assertArgIsInOut(self, method, argno, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
typestr = info["arguments"][argno + offset]["type"]
|
|
if not typestr.startswith(b"N^") and not typestr.startswith(b"N*"):
|
|
self.fail(
|
|
message or "arg %d of %s is not an 'inout' argument" % (argno, method)
|
|
)
|
|
|
|
def assertArgIsIn(self, method, argno, message=None):
|
|
if isinstance(method, objc.selector):
|
|
offset = 2
|
|
else:
|
|
offset = 0
|
|
info = method.__metadata__()
|
|
typestr = info["arguments"][argno + offset]["type"]
|
|
if not typestr.startswith(b"n^") and not typestr.startswith(b"n*"):
|
|
self.fail(
|
|
message or "arg %d of %s is not an 'in' argument" % (argno, method)
|
|
)
|
|
|
|
def assertStartswith(self, value, test, message=None):
|
|
if not value.startswith(test):
|
|
self.fail(message or f"{value!r} does not start with {test!r}")
|
|
|
|
def assertHasAttr(self, value, key, message=None):
|
|
if not hasattr(value, key):
|
|
self.fail(message or f"{key} is not an attribute of {value!r}")
|
|
|
|
def assertNotHasAttr(self, value, key, message=None):
|
|
if hasattr(value, key):
|
|
self.fail(message or f"{key} is an attribute of {value!r}")
|
|
|
|
def assertIsSubclass(self, value, types, message=None):
|
|
if not issubclass(value, types):
|
|
self.fail(message or f"{value} is not a subclass of {types!r}")
|
|
|
|
def assertIsNotSubclass(self, value, types, message=None):
|
|
if issubclass(value, types):
|
|
self.fail(message or f"{value} is a subclass of {types!r}")
|
|
|
|
def assertClassIsFinal(self, cls):
|
|
if not isinstance(cls, objc.objc_class):
|
|
self.fail(f"{cls} is not an Objective-C class")
|
|
elif not cls.__objc_final__:
|
|
self.fail(f"{cls} is not a final class")
|
|
|
|
def assertProtocolExists(self, name):
|
|
ok = True
|
|
try:
|
|
proto = objc.protocolNamed(name)
|
|
|
|
except objc.ProtocolError:
|
|
ok = False
|
|
|
|
if not ok:
|
|
self.fail(f"Protocol {name!r} does not exist")
|
|
|
|
if not isinstance(proto, objc.formal_protocol):
|
|
# Should never happen
|
|
self.fail(f"Protocol {name!r} is not a protocol, but {type(proto)}")
|
|
|
|
def assertPickleRoundTrips(self, value):
|
|
try:
|
|
buf = _pickle.dumps(value)
|
|
clone = _pickle.loads(buf)
|
|
except Exception:
|
|
self.fail(f"{value} cannot be pickled")
|
|
|
|
self.assertEqual(clone, value)
|
|
self.assertIsInstance(clone, type(value))
|
|
|
|
def _validateCallableMetadata(
|
|
self, value, class_name=None, skip_simple_charptr_check=False
|
|
):
|
|
if False and isinstance(value, objc.selector):
|
|
# Check if the signature might contain types that are interesting
|
|
# for this method. This avoids creating a metadata dict for 'simple'
|
|
# methods.
|
|
# XXX: Disabled this shortcut due to adding already_retained tests
|
|
signature = value.signature
|
|
if objc._C_PTR not in signature and objc._C_CHARPTR not in signature:
|
|
return
|
|
|
|
callable_meta = value.__metadata__()
|
|
argcount = len(callable_meta["arguments"])
|
|
|
|
for idx, meta in [("retval", callable_meta["retval"])] + list(
|
|
enumerate(callable_meta["arguments"])
|
|
):
|
|
if meta.get("already_retained", False) and meta.get(
|
|
"already_cfretained", False
|
|
):
|
|
self.fail(
|
|
f"{value}: {idx}: both already_retained and already_cfretained"
|
|
)
|
|
|
|
if meta["type"].endswith(objc._C_PTR + objc._C_CHR) or meta[
|
|
"type"
|
|
].endswith(objc._C_CHARPTR):
|
|
if meta.get("c_array_delimited_by_null", False):
|
|
self.fail(
|
|
f"{value}: {idx}: null-delimited 'char*', use _C_CHAR_AS_TEXT instead {class_name or ''}"
|
|
)
|
|
if not skip_simple_charptr_check:
|
|
self.fail(f"{value}: {idx}: 'char*' {class_name or ''}")
|
|
|
|
v = meta.get("c_array_size_in_arg", None)
|
|
if isinstance(v, int):
|
|
if not (0 <= v < argcount):
|
|
self.fail(
|
|
f"{value}: {idx}: c_array_size_in_arg out of range {v} {class_name or ''}"
|
|
)
|
|
elif isinstance(v, tuple):
|
|
b, e = v
|
|
if not (0 <= b < argcount):
|
|
self.fail(
|
|
f"{value}: {idx}: c_array_size_in_arg out of range {b} {class_name or ''}"
|
|
)
|
|
if not (0 <= e < argcount):
|
|
self.fail(
|
|
f"{value}: {idx}: c_array_size_in_arg out of range {e} {class_name or ''}"
|
|
)
|
|
|
|
tp = meta["type"]
|
|
if any(
|
|
tp.startswith(pfx) for pfx in (objc._C_IN, objc._C_OUT, objc._C_INOUT)
|
|
):
|
|
rest = tp[1:]
|
|
if not rest.startswith(objc._C_PTR) and not rest.startswith(
|
|
objc._C_CHARPTR
|
|
):
|
|
self.fail(
|
|
f"{value}: {idx}: byref specifier on non-pointer: {tp} {class_name or ''}"
|
|
)
|
|
|
|
rest = rest[1:]
|
|
|
|
if rest.startswith(objc._C_STRUCT_B):
|
|
name, fields = objc.splitStructSignature(rest)
|
|
if not fields:
|
|
self.fail(
|
|
f"{value}: {idx}: byref to empty struct (handle/CFType?): {tp} {class_name or ''}"
|
|
)
|
|
|
|
if not isinstance(value, objc.selector):
|
|
# This gives too many false positives for selectors (sadly)
|
|
if (
|
|
tp.startswith(objc._C_PTR)
|
|
and tp not in (b"^v", b"^?")
|
|
and tp != b"^{AudioBufferList=I[1{AudioBuffer=II^v}]}"
|
|
and tp != b"^{_CFArrayCallBacks=q^?^?^?^?}"
|
|
):
|
|
if tp[1:].startswith(objc._C_STRUCT_B):
|
|
name, fields = objc.splitStructSignature(tp[1:])
|
|
if not fields:
|
|
continue
|
|
|
|
if idx == "retval":
|
|
if any(
|
|
x in meta
|
|
for x in {
|
|
"deref_result_pointer",
|
|
"c_array_delimited_by_null",
|
|
"c_array_of_variable_length",
|
|
"c_array_length_in_arg",
|
|
"c_array_size_in_arg",
|
|
}
|
|
):
|
|
continue
|
|
self.fail(
|
|
f"{value}: {idx}: pointer argument, but no by-ref annotation:{tp!r} {class_name or ''}"
|
|
)
|
|
|
|
def assertCallableMetadataIsSane(
|
|
self, module, *, exclude_cocoa=True, exclude_attrs=()
|
|
):
|
|
# Do some sanity checking on module metadata for
|
|
# callables.
|
|
#
|
|
# This test is *very* expensive, made slightly
|
|
# better by excluding CoreFoundation/Foundation/AppKit
|
|
# by default
|
|
#
|
|
# XXX: exclude_cocoa may exclude too much depending on
|
|
# import order.
|
|
|
|
if hasattr(module, "__bundle__"):
|
|
with self.subTest("validate framework identifier"):
|
|
self.assertHasAttr(module, "__framework_identifier__")
|
|
self.assertEqual(
|
|
module.__bundle__.bundleIdentifier(),
|
|
module.__framework_identifier__,
|
|
)
|
|
|
|
if exclude_cocoa:
|
|
import Cocoa
|
|
|
|
exclude_names = set(dir(Cocoa))
|
|
|
|
# Don't exclude NSObject' because a number
|
|
# of frameworks define categories on this class.
|
|
exclude_names -= {"NSObject"}
|
|
else:
|
|
exclude_names = set()
|
|
|
|
exclude_method_names = {
|
|
"copyRenderedTextureForCGLContext_pixelFormat_bounds_isFlipped_",
|
|
"newTaggedNSStringWithASCIIBytes__length__",
|
|
"utf8ValueSafe",
|
|
"utf8ValueSafe_",
|
|
"isKeyExcludedFromWebScript_",
|
|
}
|
|
|
|
exclude_attrs = set(exclude_attrs)
|
|
exclude_attrs.add("FBSMutableSceneClientSettings")
|
|
exclude_attrs.add("FBSSceneClientSettings")
|
|
exclude_attrs.add(("NSColor", "scn_C3DColorIgnoringColorSpace_success_"))
|
|
exclude_attrs.add(
|
|
("AVKitPlatformColorClass", "scn_C3DColorIgnoringColorSpace_success_")
|
|
)
|
|
exclude_attrs.add(
|
|
("PDFKitPlatformColor", "scn_C3DColorIgnoringColorSpace_success_")
|
|
)
|
|
exclude_attrs.add(("SCNColor", "scn_C3DColorIgnoringColorSpace_success_"))
|
|
exclude_attrs.add(("SKColor", "scn_C3DColorIgnoringColorSpace_success_"))
|
|
exclude_attrs.add(
|
|
(
|
|
"NSObject",
|
|
"copyRenderedTextureForCGLContext_pixelFormat_bounds_isFlipped_",
|
|
)
|
|
)
|
|
exclude_attrs.add(
|
|
(
|
|
"NSObject",
|
|
"newTaggedNSStringWithASCIIBytes__length__",
|
|
)
|
|
)
|
|
exclude_attrs.add(
|
|
(
|
|
"NSObject",
|
|
"utf8ValueSafe",
|
|
)
|
|
)
|
|
exclude_attrs.add(
|
|
(
|
|
"NSObject",
|
|
"utf8ValueSafe_",
|
|
)
|
|
)
|
|
exclude_attrs.add(
|
|
(
|
|
"NSObject",
|
|
"isKeyExcludedFromWebScript_",
|
|
)
|
|
)
|
|
|
|
# Two (private) classes that end up being found in
|
|
# test runs on macOS 10.12 and 10.13
|
|
exclude_attrs.add("ABCDContact_ABCDContact_")
|
|
exclude_attrs.add("ABCDGroup_ABCDGroup_")
|
|
|
|
# Some bindings in CoreAudio with false positives
|
|
exclude_attrs.add("AudioHardwareClaimAudioDeviceID")
|
|
exclude_attrs.add("AudioHardwareClaimAudioStreamID")
|
|
exclude_attrs.add("AudioHardwareDevicePropertyChanged")
|
|
exclude_attrs.add("AudioHardwareDevicesCreated")
|
|
exclude_attrs.add("AudioHardwareDevicesDied")
|
|
exclude_attrs.add("AudioHardwareStreamPropertyChanged")
|
|
exclude_attrs.add("AudioHardwareStreamsCreated")
|
|
exclude_attrs.add("AudioHardwareStreamsDied")
|
|
exclude_attrs.add("AudioObjectCreate")
|
|
exclude_attrs.add("AudioObjectPropertiesChanged")
|
|
exclude_attrs.add("AudioObjectsPublishedAndDied")
|
|
|
|
# Calculate all (interesting) names in the module. This pokes into
|
|
# the implementation details of objc.ObjCLazyModule to avoid loading
|
|
# all attributes (which is expensive for larger bindings).
|
|
if isinstance(module, objc.ObjCLazyModule) and False:
|
|
module_names = []
|
|
module_names.extend(
|
|
cls.__name__
|
|
for cls in objc.getClassList()
|
|
if (not cls.__name__.startswith("_")) and ("." not in cls.__name__)
|
|
)
|
|
module_names.extend(module._ObjCLazyModule__funcmap or [])
|
|
module_names.extend(module.__dict__.keys())
|
|
todo = list(module._ObjCLazyModule__parents or [])
|
|
while todo:
|
|
parent = todo.pop()
|
|
if isinstance(parent, objc.ObjCLazyModule):
|
|
module_names.extend(parent._ObjCLazyModule__funcmap or ())
|
|
todo.extend(parent._ObjCLazyModule__parents or ())
|
|
module_names.extend(parent.__dict__.keys())
|
|
else:
|
|
module_names.extend(dir(module))
|
|
|
|
# The module_names list might contain duplicates
|
|
module_names = sorted(set(module_names))
|
|
else:
|
|
module_names = sorted(set(dir(module)))
|
|
|
|
for _idx, nm in enumerate(module_names):
|
|
# print(f"{_idx}/{len(module_names)} {nm}")
|
|
if nm in exclude_names:
|
|
continue
|
|
if nm in exclude_attrs:
|
|
continue
|
|
|
|
try:
|
|
value = getattr(module, nm)
|
|
except AttributeError:
|
|
continue
|
|
if isinstance(value, objc.objc_class):
|
|
if value.__name__ == "Object":
|
|
# Root class, does not conform to the NSObject
|
|
# protocol and useless to test.
|
|
continue
|
|
for attr_name, attr in value.pyobjc_instanceMethods.__dict__.items():
|
|
if attr_name in exclude_method_names:
|
|
continue
|
|
if (nm, attr_name) in exclude_attrs:
|
|
continue
|
|
if attr_name.startswith("_"):
|
|
# Skip private names
|
|
continue
|
|
|
|
with self.subTest(classname=nm, instance_method=attr_name):
|
|
if isinstance(attr, objc.selector): # pragma: no branch
|
|
self._validateCallableMetadata(
|
|
attr, nm, skip_simple_charptr_check=not exclude_cocoa
|
|
)
|
|
|
|
for attr_name, attr in value.pyobjc_classMethods.__dict__.items():
|
|
if attr_name in exclude_method_names:
|
|
continue
|
|
if (nm, attr_name) in exclude_attrs:
|
|
continue
|
|
if attr_name.startswith("_"):
|
|
# Skip private names
|
|
continue
|
|
|
|
with self.subTest(classname=nm, instance_method=attr_name):
|
|
attr = getattr(value.pyobjc_classMethods, attr_name, None)
|
|
if isinstance(attr, objc.selector): # pragma: no branch
|
|
self._validateCallableMetadata(
|
|
attr, nm, skip_simple_charptr_check=not exclude_cocoa
|
|
)
|
|
elif isinstance(value, objc.function):
|
|
with self.subTest(function=nm):
|
|
self._validateCallableMetadata(value)
|
|
|
|
else:
|
|
continue
|
|
|
|
def __init__(self, methodName="runTest"):
|
|
super().__init__(methodName)
|
|
|
|
testMethod = getattr(self, methodName)
|
|
|
|
if getattr(testMethod, "_no_autorelease_pool", False):
|
|
self._skip_usepool = True
|
|
else:
|
|
self._skip_usepool = False
|
|
|
|
def run(self, *args, **kwds):
|
|
"""
|
|
Run the test, same as unittest.TestCase.run, but every test is
|
|
run with a fresh autorelease pool.
|
|
"""
|
|
try:
|
|
cls = objc.lookUpClass("NSApplication")
|
|
except objc.error:
|
|
pass
|
|
else:
|
|
cls.sharedApplication()
|
|
|
|
if _usepool and not self._skip_usepool:
|
|
p = _poolclass.alloc().init()
|
|
else:
|
|
p = 1
|
|
|
|
try:
|
|
_unittest.TestCase.run(self, *args, **kwds)
|
|
finally:
|
|
_gc.collect()
|
|
del p
|
|
_gc.collect()
|
|
|
|
|
|
main = _unittest.main
|
|
expectedFailure = _unittest.expectedFailure
|
|
skipUnless = _unittest.skipUnless
|
|
SkipTest = _unittest.SkipTest
|
|
|
|
|
|
def expectedFailureIf(condition):
|
|
if condition:
|
|
return expectedFailure
|
|
else:
|
|
return lambda func: func
|
|
|
|
|
|
def no_autorelease_pool(func):
|
|
func._no_autorelease_pool = True
|
|
return func
|