1import functools 2import unittest 3from test import support 4 5from ctypes import * 6from ctypes.test import need_symbol 7from _ctypes import CTYPES_MAX_ARGCOUNT 8import _ctypes_test 9 10class Callbacks(unittest.TestCase): 11 functype = CFUNCTYPE 12 13## def tearDown(self): 14## import gc 15## gc.collect() 16 17 def callback(self, *args): 18 self.got_args = args 19 return args[-1] 20 21 def check_type(self, typ, arg): 22 PROTO = self.functype.__func__(typ, typ) 23 result = PROTO(self.callback)(arg) 24 if typ == c_float: 25 self.assertAlmostEqual(result, arg, places=5) 26 else: 27 self.assertEqual(self.got_args, (arg,)) 28 self.assertEqual(result, arg) 29 30 PROTO = self.functype.__func__(typ, c_byte, typ) 31 result = PROTO(self.callback)(-3, arg) 32 if typ == c_float: 33 self.assertAlmostEqual(result, arg, places=5) 34 else: 35 self.assertEqual(self.got_args, (-3, arg)) 36 self.assertEqual(result, arg) 37 38 ################ 39 40 def test_byte(self): 41 self.check_type(c_byte, 42) 42 self.check_type(c_byte, -42) 43 44 def test_ubyte(self): 45 self.check_type(c_ubyte, 42) 46 47 def test_short(self): 48 self.check_type(c_short, 42) 49 self.check_type(c_short, -42) 50 51 def test_ushort(self): 52 self.check_type(c_ushort, 42) 53 54 def test_int(self): 55 self.check_type(c_int, 42) 56 self.check_type(c_int, -42) 57 58 def test_uint(self): 59 self.check_type(c_uint, 42) 60 61 def test_long(self): 62 self.check_type(c_long, 42) 63 self.check_type(c_long, -42) 64 65 def test_ulong(self): 66 self.check_type(c_ulong, 42) 67 68 @need_symbol('c_longlong') 69 def test_longlong(self): 70 self.check_type(c_longlong, 42) 71 self.check_type(c_longlong, -42) 72 73 @need_symbol('c_ulonglong') 74 def test_ulonglong(self): 75 self.check_type(c_ulonglong, 42) 76 77 def test_float(self): 78 # only almost equal: double -> float -> double 79 import math 80 self.check_type(c_float, math.e) 81 self.check_type(c_float, -math.e) 82 83 def test_double(self): 84 self.check_type(c_double, 3.14) 85 self.check_type(c_double, -3.14) 86 87 @need_symbol('c_longdouble') 88 def test_longdouble(self): 89 self.check_type(c_longdouble, 3.14) 90 self.check_type(c_longdouble, -3.14) 91 92 def test_char(self): 93 self.check_type(c_char, b"x") 94 self.check_type(c_char, b"a") 95 96 # disabled: would now (correctly) raise a RuntimeWarning about 97 # a memory leak. A callback function cannot return a non-integral 98 # C type without causing a memory leak. 99 @unittest.skip('test disabled') 100 def test_char_p(self): 101 self.check_type(c_char_p, "abc") 102 self.check_type(c_char_p, "def") 103 104 def test_pyobject(self): 105 o = () 106 from sys import getrefcount as grc 107 for o in (), [], object(): 108 initial = grc(o) 109 # This call leaks a reference to 'o'... 110 self.check_type(py_object, o) 111 before = grc(o) 112 # ...but this call doesn't leak any more. Where is the refcount? 113 self.check_type(py_object, o) 114 after = grc(o) 115 self.assertEqual((after, o), (before, o)) 116 117 def test_unsupported_restype_1(self): 118 # Only "fundamental" result types are supported for callback 119 # functions, the type must have a non-NULL stgdict->setfunc. 120 # POINTER(c_double), for example, is not supported. 121 122 prototype = self.functype.__func__(POINTER(c_double)) 123 # The type is checked when the prototype is called 124 self.assertRaises(TypeError, prototype, lambda: None) 125 126 def test_unsupported_restype_2(self): 127 prototype = self.functype.__func__(object) 128 self.assertRaises(TypeError, prototype, lambda: None) 129 130 def test_issue_7959(self): 131 proto = self.functype.__func__(None) 132 133 class X(object): 134 def func(self): pass 135 def __init__(self): 136 self.v = proto(self.func) 137 138 import gc 139 for i in range(32): 140 X() 141 gc.collect() 142 live = [x for x in gc.get_objects() 143 if isinstance(x, X)] 144 self.assertEqual(len(live), 0) 145 146 def test_issue12483(self): 147 import gc 148 class Nasty: 149 def __del__(self): 150 gc.collect() 151 CFUNCTYPE(None)(lambda x=Nasty(): None) 152 153 154@need_symbol('WINFUNCTYPE') 155class StdcallCallbacks(Callbacks): 156 try: 157 functype = WINFUNCTYPE 158 except NameError: 159 pass 160 161################################################################ 162 163class SampleCallbacksTestCase(unittest.TestCase): 164 165 def test_integrate(self): 166 # Derived from some then non-working code, posted by David Foster 167 dll = CDLL(_ctypes_test.__file__) 168 169 # The function prototype called by 'integrate': double func(double); 170 CALLBACK = CFUNCTYPE(c_double, c_double) 171 172 # The integrate function itself, exposed from the _ctypes_test dll 173 integrate = dll.integrate 174 integrate.argtypes = (c_double, c_double, CALLBACK, c_long) 175 integrate.restype = c_double 176 177 def func(x): 178 return x**2 179 180 result = integrate(0.0, 1.0, CALLBACK(func), 10) 181 diff = abs(result - 1./3.) 182 183 self.assertLess(diff, 0.01, "%s not less than 0.01" % diff) 184 185 def test_issue_8959_a(self): 186 from ctypes.util import find_library 187 libc_path = find_library("c") 188 if not libc_path: 189 self.skipTest('could not find libc') 190 libc = CDLL(libc_path) 191 192 @CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int)) 193 def cmp_func(a, b): 194 return a[0] - b[0] 195 196 array = (c_int * 5)(5, 1, 99, 7, 33) 197 198 libc.qsort(array, len(array), sizeof(c_int), cmp_func) 199 self.assertEqual(array[:], [1, 5, 7, 33, 99]) 200 201 @need_symbol('WINFUNCTYPE') 202 def test_issue_8959_b(self): 203 from ctypes.wintypes import BOOL, HWND, LPARAM 204 global windowCount 205 windowCount = 0 206 207 @WINFUNCTYPE(BOOL, HWND, LPARAM) 208 def EnumWindowsCallbackFunc(hwnd, lParam): 209 global windowCount 210 windowCount += 1 211 return True #Allow windows to keep enumerating 212 213 windll.user32.EnumWindows(EnumWindowsCallbackFunc, 0) 214 215 def test_callback_register_int(self): 216 # Issue #8275: buggy handling of callback args under Win64 217 # NOTE: should be run on release builds as well 218 dll = CDLL(_ctypes_test.__file__) 219 CALLBACK = CFUNCTYPE(c_int, c_int, c_int, c_int, c_int, c_int) 220 # All this function does is call the callback with its args squared 221 func = dll._testfunc_cbk_reg_int 222 func.argtypes = (c_int, c_int, c_int, c_int, c_int, CALLBACK) 223 func.restype = c_int 224 225 def callback(a, b, c, d, e): 226 return a + b + c + d + e 227 228 result = func(2, 3, 4, 5, 6, CALLBACK(callback)) 229 self.assertEqual(result, callback(2*2, 3*3, 4*4, 5*5, 6*6)) 230 231 def test_callback_register_double(self): 232 # Issue #8275: buggy handling of callback args under Win64 233 # NOTE: should be run on release builds as well 234 dll = CDLL(_ctypes_test.__file__) 235 CALLBACK = CFUNCTYPE(c_double, c_double, c_double, c_double, 236 c_double, c_double) 237 # All this function does is call the callback with its args squared 238 func = dll._testfunc_cbk_reg_double 239 func.argtypes = (c_double, c_double, c_double, 240 c_double, c_double, CALLBACK) 241 func.restype = c_double 242 243 def callback(a, b, c, d, e): 244 return a + b + c + d + e 245 246 result = func(1.1, 2.2, 3.3, 4.4, 5.5, CALLBACK(callback)) 247 self.assertEqual(result, 248 callback(1.1*1.1, 2.2*2.2, 3.3*3.3, 4.4*4.4, 5.5*5.5)) 249 250 def test_callback_large_struct(self): 251 class Check: pass 252 253 # This should mirror the structure in Modules/_ctypes/_ctypes_test.c 254 class X(Structure): 255 _fields_ = [ 256 ('first', c_ulong), 257 ('second', c_ulong), 258 ('third', c_ulong), 259 ] 260 261 def callback(check, s): 262 check.first = s.first 263 check.second = s.second 264 check.third = s.third 265 # See issue #29565. 266 # The structure should be passed by value, so 267 # any changes to it should not be reflected in 268 # the value passed 269 s.first = s.second = s.third = 0x0badf00d 270 271 check = Check() 272 s = X() 273 s.first = 0xdeadbeef 274 s.second = 0xcafebabe 275 s.third = 0x0bad1dea 276 277 CALLBACK = CFUNCTYPE(None, X) 278 dll = CDLL(_ctypes_test.__file__) 279 func = dll._testfunc_cbk_large_struct 280 func.argtypes = (X, CALLBACK) 281 func.restype = None 282 # the function just calls the callback with the passed structure 283 func(s, CALLBACK(functools.partial(callback, check))) 284 self.assertEqual(check.first, s.first) 285 self.assertEqual(check.second, s.second) 286 self.assertEqual(check.third, s.third) 287 self.assertEqual(check.first, 0xdeadbeef) 288 self.assertEqual(check.second, 0xcafebabe) 289 self.assertEqual(check.third, 0x0bad1dea) 290 # See issue #29565. 291 # Ensure that the original struct is unchanged. 292 self.assertEqual(s.first, check.first) 293 self.assertEqual(s.second, check.second) 294 self.assertEqual(s.third, check.third) 295 296 def test_callback_too_many_args(self): 297 def func(*args): 298 return len(args) 299 300 # valid call with nargs <= CTYPES_MAX_ARGCOUNT 301 proto = CFUNCTYPE(c_int, *(c_int,) * CTYPES_MAX_ARGCOUNT) 302 cb = proto(func) 303 args1 = (1,) * CTYPES_MAX_ARGCOUNT 304 self.assertEqual(cb(*args1), CTYPES_MAX_ARGCOUNT) 305 306 # invalid call with nargs > CTYPES_MAX_ARGCOUNT 307 args2 = (1,) * (CTYPES_MAX_ARGCOUNT + 1) 308 with self.assertRaises(ArgumentError): 309 cb(*args2) 310 311 # error when creating the type with too many arguments 312 with self.assertRaises(ArgumentError): 313 CFUNCTYPE(c_int, *(c_int,) * (CTYPES_MAX_ARGCOUNT + 1)) 314 315 def test_convert_result_error(self): 316 def func(): 317 return ("tuple",) 318 319 proto = CFUNCTYPE(c_int) 320 ctypes_func = proto(func) 321 with support.catch_unraisable_exception() as cm: 322 # don't test the result since it is an uninitialized value 323 result = ctypes_func() 324 325 self.assertIsInstance(cm.unraisable.exc_value, TypeError) 326 self.assertEqual(cm.unraisable.err_msg, 327 "Exception ignored on converting result " 328 "of ctypes callback function") 329 self.assertIs(cm.unraisable.object, func) 330 331 332if __name__ == '__main__': 333 unittest.main() 334