teams-bots/write-message/venv/lib/python3.10/site-packages/PyObjCTools/TestSupport.py

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