• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from ctypes import *
2import unittest
3import struct
4
5def valid_ranges(*types):
6    # given a sequence of numeric types, collect their _type_
7    # attribute, which is a single format character compatible with
8    # the struct module, use the struct module to calculate the
9    # minimum and maximum value allowed for this format.
10    # Returns a list of (min, max) values.
11    result = []
12    for t in types:
13        fmt = t._type_
14        size = struct.calcsize(fmt)
15        a = struct.unpack(fmt, (b"\x00"*32)[:size])[0]
16        b = struct.unpack(fmt, (b"\xFF"*32)[:size])[0]
17        c = struct.unpack(fmt, (b"\x7F"+b"\x00"*32)[:size])[0]
18        d = struct.unpack(fmt, (b"\x80"+b"\xFF"*32)[:size])[0]
19        result.append((min(a, b, c, d), max(a, b, c, d)))
20    return result
21
22ArgType = type(byref(c_int(0)))
23
24unsigned_types = [c_ubyte, c_ushort, c_uint, c_ulong]
25signed_types = [c_byte, c_short, c_int, c_long, c_longlong]
26
27bool_types = []
28
29float_types = [c_double, c_float]
30
31try:
32    c_ulonglong
33    c_longlong
34except NameError:
35    pass
36else:
37    unsigned_types.append(c_ulonglong)
38    signed_types.append(c_longlong)
39
40try:
41    c_bool
42except NameError:
43    pass
44else:
45    bool_types.append(c_bool)
46
47unsigned_ranges = valid_ranges(*unsigned_types)
48signed_ranges = valid_ranges(*signed_types)
49bool_values = [True, False, 0, 1, -1, 5000, 'test', [], [1]]
50
51################################################################
52
53class NumberTestCase(unittest.TestCase):
54
55    def test_default_init(self):
56        # default values are set to zero
57        for t in signed_types + unsigned_types + float_types:
58            self.assertEqual(t().value, 0)
59
60    def test_unsigned_values(self):
61        # the value given to the constructor is available
62        # as the 'value' attribute
63        for t, (l, h) in zip(unsigned_types, unsigned_ranges):
64            self.assertEqual(t(l).value, l)
65            self.assertEqual(t(h).value, h)
66
67    def test_signed_values(self):
68        # see above
69        for t, (l, h) in zip(signed_types, signed_ranges):
70            self.assertEqual(t(l).value, l)
71            self.assertEqual(t(h).value, h)
72
73    def test_bool_values(self):
74        from operator import truth
75        for t, v in zip(bool_types, bool_values):
76            self.assertEqual(t(v).value, truth(v))
77
78    def test_typeerror(self):
79        # Only numbers are allowed in the constructor,
80        # otherwise TypeError is raised
81        for t in signed_types + unsigned_types + float_types:
82            self.assertRaises(TypeError, t, "")
83            self.assertRaises(TypeError, t, None)
84
85    @unittest.skip('test disabled')
86    def test_valid_ranges(self):
87        # invalid values of the correct type
88        # raise ValueError (not OverflowError)
89        for t, (l, h) in zip(unsigned_types, unsigned_ranges):
90            self.assertRaises(ValueError, t, l-1)
91            self.assertRaises(ValueError, t, h+1)
92
93    def test_from_param(self):
94        # the from_param class method attribute always
95        # returns PyCArgObject instances
96        for t in signed_types + unsigned_types + float_types:
97            self.assertEqual(ArgType, type(t.from_param(0)))
98
99    def test_byref(self):
100        # calling byref returns also a PyCArgObject instance
101        for t in signed_types + unsigned_types + float_types + bool_types:
102            parm = byref(t())
103            self.assertEqual(ArgType, type(parm))
104
105
106    def test_floats(self):
107        # c_float and c_double can be created from
108        # Python int and float
109        class FloatLike(object):
110            def __float__(self):
111                return 2.0
112        f = FloatLike()
113        for t in float_types:
114            self.assertEqual(t(2.0).value, 2.0)
115            self.assertEqual(t(2).value, 2.0)
116            self.assertEqual(t(2).value, 2.0)
117            self.assertEqual(t(f).value, 2.0)
118
119    def test_integers(self):
120        class FloatLike(object):
121            def __float__(self):
122                return 2.0
123        f = FloatLike()
124        class IntLike(object):
125            def __int__(self):
126                return 2
127        d = IntLike()
128        class IndexLike(object):
129            def __index__(self):
130                return 2
131        i = IndexLike()
132        # integers cannot be constructed from floats,
133        # but from integer-like objects
134        for t in signed_types + unsigned_types:
135            self.assertRaises(TypeError, t, 3.14)
136            self.assertRaises(TypeError, t, f)
137            self.assertRaises(TypeError, t, d)
138            self.assertEqual(t(i).value, 2)
139
140    def test_sizes(self):
141        for t in signed_types + unsigned_types + float_types + bool_types:
142            try:
143                size = struct.calcsize(t._type_)
144            except struct.error:
145                continue
146            # sizeof of the type...
147            self.assertEqual(sizeof(t), size)
148            # and sizeof of an instance
149            self.assertEqual(sizeof(t()), size)
150
151    def test_alignments(self):
152        for t in signed_types + unsigned_types + float_types:
153            code = t._type_ # the typecode
154            align = struct.calcsize("c%c" % code) - struct.calcsize(code)
155
156            # alignment of the type...
157            self.assertEqual((code, alignment(t)),
158                                 (code, align))
159            # and alignment of an instance
160            self.assertEqual((code, alignment(t())),
161                                 (code, align))
162
163    def test_int_from_address(self):
164        from array import array
165        for t in signed_types + unsigned_types:
166            # the array module doesn't support all format codes
167            # (no 'q' or 'Q')
168            try:
169                array(t._type_)
170            except ValueError:
171                continue
172            a = array(t._type_, [100])
173
174            # v now is an integer at an 'external' memory location
175            v = t.from_address(a.buffer_info()[0])
176            self.assertEqual(v.value, a[0])
177            self.assertEqual(type(v), t)
178
179            # changing the value at the memory location changes v's value also
180            a[0] = 42
181            self.assertEqual(v.value, a[0])
182
183
184    def test_float_from_address(self):
185        from array import array
186        for t in float_types:
187            a = array(t._type_, [3.14])
188            v = t.from_address(a.buffer_info()[0])
189            self.assertEqual(v.value, a[0])
190            self.assertIs(type(v), t)
191            a[0] = 2.3456e17
192            self.assertEqual(v.value, a[0])
193            self.assertIs(type(v), t)
194
195    def test_char_from_address(self):
196        from ctypes import c_char
197        from array import array
198
199        a = array('b', [0])
200        a[0] = ord('x')
201        v = c_char.from_address(a.buffer_info()[0])
202        self.assertEqual(v.value, b'x')
203        self.assertIs(type(v), c_char)
204
205        a[0] = ord('?')
206        self.assertEqual(v.value, b'?')
207
208    # array does not support c_bool / 't'
209    @unittest.skip('test disabled')
210    def test_bool_from_address(self):
211        from ctypes import c_bool
212        from array import array
213        a = array(c_bool._type_, [True])
214        v = t.from_address(a.buffer_info()[0])
215        self.assertEqual(v.value, a[0])
216        self.assertEqual(type(v) is t)
217        a[0] = False
218        self.assertEqual(v.value, a[0])
219        self.assertEqual(type(v) is t)
220
221    def test_init(self):
222        # c_int() can be initialized from Python's int, and c_int.
223        # Not from c_long or so, which seems strange, abc should
224        # probably be changed:
225        self.assertRaises(TypeError, c_int, c_long(42))
226
227    def test_float_overflow(self):
228        import sys
229        big_int = int(sys.float_info.max) * 2
230        for t in float_types + [c_longdouble]:
231            self.assertRaises(OverflowError, t, big_int)
232            if (hasattr(t, "__ctype_be__")):
233                self.assertRaises(OverflowError, t.__ctype_be__, big_int)
234            if (hasattr(t, "__ctype_le__")):
235                self.assertRaises(OverflowError, t.__ctype_le__, big_int)
236
237    @unittest.skip('test disabled')
238    def test_perf(self):
239        check_perf()
240
241from ctypes import _SimpleCData
242class c_int_S(_SimpleCData):
243    _type_ = "i"
244    __slots__ = []
245
246def run_test(rep, msg, func, arg=None):
247##    items = [None] * rep
248    items = range(rep)
249    from time import perf_counter as clock
250    if arg is not None:
251        start = clock()
252        for i in items:
253            func(arg); func(arg); func(arg); func(arg); func(arg)
254        stop = clock()
255    else:
256        start = clock()
257        for i in items:
258            func(); func(); func(); func(); func()
259        stop = clock()
260    print("%15s: %.2f us" % (msg, ((stop-start)*1e6/5/rep)))
261
262def check_perf():
263    # Construct 5 objects
264    from ctypes import c_int
265
266    REP = 200000
267
268    run_test(REP, "int()", int)
269    run_test(REP, "int(999)", int)
270    run_test(REP, "c_int()", c_int)
271    run_test(REP, "c_int(999)", c_int)
272    run_test(REP, "c_int_S()", c_int_S)
273    run_test(REP, "c_int_S(999)", c_int_S)
274
275# Python 2.3 -OO, win2k, P4 700 MHz:
276#
277#          int(): 0.87 us
278#       int(999): 0.87 us
279#        c_int(): 3.35 us
280#     c_int(999): 3.34 us
281#      c_int_S(): 3.23 us
282#   c_int_S(999): 3.24 us
283
284# Python 2.2 -OO, win2k, P4 700 MHz:
285#
286#          int(): 0.89 us
287#       int(999): 0.89 us
288#        c_int(): 9.99 us
289#     c_int(999): 10.02 us
290#      c_int_S(): 9.87 us
291#   c_int_S(999): 9.85 us
292
293if __name__ == '__main__':
294##    check_perf()
295    unittest.main()
296