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, ("\x00"*32)[:size])[0]

        b = struct.unpack(fmt, ("\xFF"*32)[:size])[0]

        c = struct.unpack(fmt, ("\x7F"+"\x00"*32)[:size])[0]

        d = struct.unpack(fmt, ("\x80"+"\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.failUnlessEqual(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.failUnlessEqual(t(l).value, l)

            self.failUnlessEqual(t(h).value, h)



    def test_signed_values(self):

        # see above

        for t, (l, h) in zip(signed_types, signed_ranges):

            self.failUnlessEqual(t(l).value, l)

            self.failUnlessEqual(t(h).value, h)



    def test_bool_values(self):

        from operator import truth

        for t, v in zip(bool_types, bool_values):

            self.failUnlessEqual(t(v).value, truth(v))



    def test_typeerror(self):

        # Only numbers are allowed in the contructor,

        # otherwise TypeError is raised

        for t in signed_types + unsigned_types + float_types:

            self.assertRaises(TypeError, t, "")

            self.assertRaises(TypeError, t, None)



##    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.failUnlessEqual(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.failUnlessEqual(ArgType, type(parm))





    def test_floats(self):

        # c_float and c_double can be created from

        # Python int, long and float

        class FloatLike(object):

            def __float__(self):

                return 2.0

        f = FloatLike()

        for t in float_types:

            self.failUnlessEqual(t(2.0).value, 2.0)

            self.failUnlessEqual(t(2).value, 2.0)

            self.failUnlessEqual(t(2L).value, 2.0)

            self.failUnlessEqual(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.failUnlessEqual(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.failUnlessEqual(sizeof(t), size)

            # and sizeof of an instance

            self.failUnlessEqual(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.failUnlessEqual((code, alignment(t)),

                                 (code, align))

            # and alignment of an instance

            self.failUnlessEqual((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 suppport 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.failUnlessEqual(v.value, a[0])

            self.failUnlessEqual(type(v), t)



            # changing the value at the memory location changes v's value also

            a[0] = 42

            self.failUnlessEqual(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.failUnlessEqual(v.value, a[0])

            self.failUnless(type(v) is t)

            a[0] = 2.3456e17

            self.failUnlessEqual(v.value, a[0])

            self.failUnless(type(v) is t)



    def test_char_from_address(self):

        from ctypes import c_char

        from array import array



        a = array('c', 'x')

        v = c_char.from_address(a.buffer_info()[0])

        self.failUnlessEqual(v.value, a[0])

        self.failUnless(type(v) is c_char)



        a[0] = '?'

        self.failUnlessEqual(v.value, a[0])



    # array does not support c_bool / 't'

    # 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.failUnlessEqual(v.value, a[0])

    #     self.failUnlessEqual(type(v) is t)

    #     a[0] = False

    #     self.failUnlessEqual(v.value, a[0])

    #     self.failUnlessEqual(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, abd should

        # probably be changed:

        self.assertRaises(TypeError, c_int, c_long(42))



##    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 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()

