• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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