You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
291 lines
9.1 KiB
291 lines
9.1 KiB
5 years ago
|
from ctypes import *
|
||
|
import unittest
|
||
|
import struct
|
||
|
|
||
|
def valid_ranges(*types):
|
||
|
# given a sequence of numeric types, collect their _type_
|
||
|
# attribute, which is a single format character compatible with
|
||
|
# the struct module, use the struct module to calculate the
|
||
|
# minimum and maximum value allowed for this format.
|
||
|
# Returns a list of (min, max) values.
|
||
|
result = []
|
||
|
for t in types:
|
||
|
fmt = t._type_
|
||
|
size = struct.calcsize(fmt)
|
||
|
a = struct.unpack(fmt, (b"\x00"*32)[:size])[0]
|
||
|
b = struct.unpack(fmt, (b"\xFF"*32)[:size])[0]
|
||
|
c = struct.unpack(fmt, (b"\x7F"+b"\x00"*32)[:size])[0]
|
||
|
d = struct.unpack(fmt, (b"\x80"+b"\xFF"*32)[:size])[0]
|
||
|
result.append((min(a, b, c, d), max(a, b, c, d)))
|
||
|
return result
|
||
|
|
||
|
ArgType = type(byref(c_int(0)))
|
||
|
|
||
|
unsigned_types = [c_ubyte, c_ushort, c_uint, c_ulong]
|
||
|
signed_types = [c_byte, c_short, c_int, c_long, c_longlong]
|
||
|
|
||
|
bool_types = []
|
||
|
|
||
|
float_types = [c_double, c_float]
|
||
|
|
||
|
try:
|
||
|
c_ulonglong
|
||
|
c_longlong
|
||
|
except NameError:
|
||
|
pass
|
||
|
else:
|
||
|
unsigned_types.append(c_ulonglong)
|
||
|
signed_types.append(c_longlong)
|
||
|
|
||
|
try:
|
||
|
c_bool
|
||
|
except NameError:
|
||
|
pass
|
||
|
else:
|
||
|
bool_types.append(c_bool)
|
||
|
|
||
|
unsigned_ranges = valid_ranges(*unsigned_types)
|
||
|
signed_ranges = valid_ranges(*signed_types)
|
||
|
bool_values = [True, False, 0, 1, -1, 5000, 'test', [], [1]]
|
||
|
|
||
|
################################################################
|
||
|
|
||
|
class NumberTestCase(unittest.TestCase):
|
||
|
|
||
|
def test_default_init(self):
|
||
|
# default values are set to zero
|
||
|
for t in signed_types + unsigned_types + float_types:
|
||
|
self.assertEqual(t().value, 0)
|
||
|
|
||
|
def test_unsigned_values(self):
|
||
|
# the value given to the constructor is available
|
||
|
# as the 'value' attribute
|
||
|
for t, (l, h) in zip(unsigned_types, unsigned_ranges):
|
||
|
self.assertEqual(t(l).value, l)
|
||
|
self.assertEqual(t(h).value, h)
|
||
|
|
||
|
def test_signed_values(self):
|
||
|
# see above
|
||
|
for t, (l, h) in zip(signed_types, signed_ranges):
|
||
|
self.assertEqual(t(l).value, l)
|
||
|
self.assertEqual(t(h).value, h)
|
||
|
|
||
|
def test_bool_values(self):
|
||
|
from operator import truth
|
||
|
for t, v in zip(bool_types, bool_values):
|
||
|
self.assertEqual(t(v).value, truth(v))
|
||
|
|
||
|
def test_typeerror(self):
|
||
|
# Only numbers are allowed in the constructor,
|
||
|
# otherwise TypeError is raised
|
||
|
for t in signed_types + unsigned_types + float_types:
|
||
|
self.assertRaises(TypeError, t, "")
|
||
|
self.assertRaises(TypeError, t, None)
|
||
|
|
||
|
@unittest.skip('test disabled')
|
||
|
def test_valid_ranges(self):
|
||
|
# invalid values of the correct type
|
||
|
# raise ValueError (not OverflowError)
|
||
|
for t, (l, h) in zip(unsigned_types, unsigned_ranges):
|
||
|
self.assertRaises(ValueError, t, l-1)
|
||
|
self.assertRaises(ValueError, t, h+1)
|
||
|
|
||
|
def test_from_param(self):
|
||
|
# the from_param class method attribute always
|
||
|
# returns PyCArgObject instances
|
||
|
for t in signed_types + unsigned_types + float_types:
|
||
|
self.assertEqual(ArgType, type(t.from_param(0)))
|
||
|
|
||
|
def test_byref(self):
|
||
|
# calling byref returns also a PyCArgObject instance
|
||
|
for t in signed_types + unsigned_types + float_types + bool_types:
|
||
|
parm = byref(t())
|
||
|
self.assertEqual(ArgType, type(parm))
|
||
|
|
||
|
|
||
|
def test_floats(self):
|
||
|
# c_float and c_double can be created from
|
||
|
# Python int and float
|
||
|
class FloatLike(object):
|
||
|
def __float__(self):
|
||
|
return 2.0
|
||
|
f = FloatLike()
|
||
|
for t in float_types:
|
||
|
self.assertEqual(t(2.0).value, 2.0)
|
||
|
self.assertEqual(t(2).value, 2.0)
|
||
|
self.assertEqual(t(2).value, 2.0)
|
||
|
self.assertEqual(t(f).value, 2.0)
|
||
|
|
||
|
def test_integers(self):
|
||
|
class FloatLike(object):
|
||
|
def __float__(self):
|
||
|
return 2.0
|
||
|
f = FloatLike()
|
||
|
class IntLike(object):
|
||
|
def __int__(self):
|
||
|
return 2
|
||
|
i = IntLike()
|
||
|
# integers cannot be constructed from floats,
|
||
|
# but from integer-like objects
|
||
|
for t in signed_types + unsigned_types:
|
||
|
self.assertRaises(TypeError, t, 3.14)
|
||
|
self.assertRaises(TypeError, t, f)
|
||
|
self.assertEqual(t(i).value, 2)
|
||
|
|
||
|
def test_sizes(self):
|
||
|
for t in signed_types + unsigned_types + float_types + bool_types:
|
||
|
try:
|
||
|
size = struct.calcsize(t._type_)
|
||
|
except struct.error:
|
||
|
continue
|
||
|
# sizeof of the type...
|
||
|
self.assertEqual(sizeof(t), size)
|
||
|
# and sizeof of an instance
|
||
|
self.assertEqual(sizeof(t()), size)
|
||
|
|
||
|
def test_alignments(self):
|
||
|
for t in signed_types + unsigned_types + float_types:
|
||
|
code = t._type_ # the typecode
|
||
|
align = struct.calcsize("c%c" % code) - struct.calcsize(code)
|
||
|
|
||
|
# alignment of the type...
|
||
|
self.assertEqual((code, alignment(t)),
|
||
|
(code, align))
|
||
|
# and alignment of an instance
|
||
|
self.assertEqual((code, alignment(t())),
|
||
|
(code, align))
|
||
|
|
||
|
def test_int_from_address(self):
|
||
|
from array import array
|
||
|
for t in signed_types + unsigned_types:
|
||
|
# the array module doesn't support all format codes
|
||
|
# (no 'q' or 'Q')
|
||
|
try:
|
||
|
array(t._type_)
|
||
|
except ValueError:
|
||
|
continue
|
||
|
a = array(t._type_, [100])
|
||
|
|
||
|
# v now is an integer at an 'external' memory location
|
||
|
v = t.from_address(a.buffer_info()[0])
|
||
|
self.assertEqual(v.value, a[0])
|
||
|
self.assertEqual(type(v), t)
|
||
|
|
||
|
# changing the value at the memory location changes v's value also
|
||
|
a[0] = 42
|
||
|
self.assertEqual(v.value, a[0])
|
||
|
|
||
|
|
||
|
def test_float_from_address(self):
|
||
|
from array import array
|
||
|
for t in float_types:
|
||
|
a = array(t._type_, [3.14])
|
||
|
v = t.from_address(a.buffer_info()[0])
|
||
|
self.assertEqual(v.value, a[0])
|
||
|
self.assertIs(type(v), t)
|
||
|
a[0] = 2.3456e17
|
||
|
self.assertEqual(v.value, a[0])
|
||
|
self.assertIs(type(v), t)
|
||
|
|
||
|
def test_char_from_address(self):
|
||
|
from ctypes import c_char
|
||
|
from array import array
|
||
|
|
||
|
a = array('b', [0])
|
||
|
a[0] = ord('x')
|
||
|
v = c_char.from_address(a.buffer_info()[0])
|
||
|
self.assertEqual(v.value, b'x')
|
||
|
self.assertIs(type(v), c_char)
|
||
|
|
||
|
a[0] = ord('?')
|
||
|
self.assertEqual(v.value, b'?')
|
||
|
|
||
|
# array does not support c_bool / 't'
|
||
|
@unittest.skip('test disabled')
|
||
|
def test_bool_from_address(self):
|
||
|
from ctypes import c_bool
|
||
|
from array import array
|
||
|
a = array(c_bool._type_, [True])
|
||
|
v = t.from_address(a.buffer_info()[0])
|
||
|
self.assertEqual(v.value, a[0])
|
||
|
self.assertEqual(type(v) is t)
|
||
|
a[0] = False
|
||
|
self.assertEqual(v.value, a[0])
|
||
|
self.assertEqual(type(v) is t)
|
||
|
|
||
|
def test_init(self):
|
||
|
# c_int() can be initialized from Python's int, and c_int.
|
||
|
# Not from c_long or so, which seems strange, abc should
|
||
|
# probably be changed:
|
||
|
self.assertRaises(TypeError, c_int, c_long(42))
|
||
|
|
||
|
def test_float_overflow(self):
|
||
|
import sys
|
||
|
big_int = int(sys.float_info.max) * 2
|
||
|
for t in float_types + [c_longdouble]:
|
||
|
self.assertRaises(OverflowError, t, big_int)
|
||
|
if (hasattr(t, "__ctype_be__")):
|
||
|
self.assertRaises(OverflowError, t.__ctype_be__, big_int)
|
||
|
if (hasattr(t, "__ctype_le__")):
|
||
|
self.assertRaises(OverflowError, t.__ctype_le__, big_int)
|
||
|
|
||
|
@unittest.skip('test disabled')
|
||
|
def test_perf(self):
|
||
|
check_perf()
|
||
|
|
||
|
from ctypes import _SimpleCData
|
||
|
class c_int_S(_SimpleCData):
|
||
|
_type_ = "i"
|
||
|
__slots__ = []
|
||
|
|
||
|
def run_test(rep, msg, func, arg=None):
|
||
|
## items = [None] * rep
|
||
|
items = range(rep)
|
||
|
from time import perf_counter as clock
|
||
|
if arg is not None:
|
||
|
start = clock()
|
||
|
for i in items:
|
||
|
func(arg); func(arg); func(arg); func(arg); func(arg)
|
||
|
stop = clock()
|
||
|
else:
|
||
|
start = clock()
|
||
|
for i in items:
|
||
|
func(); func(); func(); func(); func()
|
||
|
stop = clock()
|
||
|
print("%15s: %.2f us" % (msg, ((stop-start)*1e6/5/rep)))
|
||
|
|
||
|
def check_perf():
|
||
|
# Construct 5 objects
|
||
|
from ctypes import c_int
|
||
|
|
||
|
REP = 200000
|
||
|
|
||
|
run_test(REP, "int()", int)
|
||
|
run_test(REP, "int(999)", int)
|
||
|
run_test(REP, "c_int()", c_int)
|
||
|
run_test(REP, "c_int(999)", c_int)
|
||
|
run_test(REP, "c_int_S()", c_int_S)
|
||
|
run_test(REP, "c_int_S(999)", c_int_S)
|
||
|
|
||
|
# Python 2.3 -OO, win2k, P4 700 MHz:
|
||
|
#
|
||
|
# int(): 0.87 us
|
||
|
# int(999): 0.87 us
|
||
|
# c_int(): 3.35 us
|
||
|
# c_int(999): 3.34 us
|
||
|
# c_int_S(): 3.23 us
|
||
|
# c_int_S(999): 3.24 us
|
||
|
|
||
|
# Python 2.2 -OO, win2k, P4 700 MHz:
|
||
|
#
|
||
|
# int(): 0.89 us
|
||
|
# int(999): 0.89 us
|
||
|
# c_int(): 9.99 us
|
||
|
# c_int(999): 10.02 us
|
||
|
# c_int_S(): 9.87 us
|
||
|
# c_int_S(999): 9.85 us
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
## check_perf()
|
||
|
unittest.main()
|