Commit 92e6b354 authored by Tres Seaver's avatar Tres Seaver

Merge pull request #31 from zerodb/master.

Get rid of ctypes dependency to make persistent more pure-python compatible.

Update PR's branch to fix tests under Py3k / pypy3.
parents ed211197 3a5bf839
...@@ -11,13 +11,11 @@ ...@@ -11,13 +11,11 @@
# FOR A PARTICULAR PURPOSE. # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
import operator
import unittest import unittest
import platform
py_impl = getattr(platform, 'python_implementation', lambda: None)
_is_jython = py_impl() == 'Jython'
MAX_32_BITS = 2 ** 31 - 1
MAX_64_BITS = 2 ** 63 - 1
class Test__UTC(unittest.TestCase): class Test__UTC(unittest.TestCase):
...@@ -160,12 +158,12 @@ class pyTimeStampTests(unittest.TestCase): ...@@ -160,12 +158,12 @@ class pyTimeStampTests(unittest.TestCase):
self.assertEqual(repr(ts), repr(SERIAL)) self.assertEqual(repr(ts), repr(SERIAL))
def test_comparisons_to_non_timestamps(self): def test_comparisons_to_non_timestamps(self):
import operator
from persistent._compat import PYTHON2 from persistent._compat import PYTHON2
# Check the corner cases when comparing non-comparable types # Check the corner cases when comparing non-comparable types
ts = self._makeOne(2011, 2, 16, 14, 37, 22.0) ts = self._makeOne(2011, 2, 16, 14, 37, 22.0)
if PYTHON2: def check_py2(op, passes):
def check(op, passes):
if passes == 'neither': if passes == 'neither':
self.assertFalse(op(ts, None)) self.assertFalse(op(ts, None))
self.assertFalse(op(None, ts)) self.assertFalse(op(None, ts))
...@@ -178,8 +176,8 @@ class pyTimeStampTests(unittest.TestCase): ...@@ -178,8 +176,8 @@ class pyTimeStampTests(unittest.TestCase):
else: else:
self.assertFalse(op(ts, None)) self.assertFalse(op(ts, None))
self.assertTrue(op(None, ts)) self.assertTrue(op(None, ts))
else:
def check(op, passes): def check_py3(op, passes):
if passes == 'neither': if passes == 'neither':
self.assertFalse(op(ts, None)) self.assertFalse(op(ts, None))
self.assertFalse(op(None, ts)) self.assertFalse(op(None, ts))
...@@ -190,6 +188,8 @@ class pyTimeStampTests(unittest.TestCase): ...@@ -190,6 +188,8 @@ class pyTimeStampTests(unittest.TestCase):
self.assertRaises(TypeError, op, ts, None) self.assertRaises(TypeError, op, ts, None)
self.assertRaises(TypeError, op, None, ts) self.assertRaises(TypeError, op, None, ts)
check = check_py2 if PYTHON2 else check_py3
for op_name, passes in (('lt', 'second'), for op_name, passes in (('lt', 'second'),
('gt', 'first'), ('gt', 'first'),
('le', 'second'), ('le', 'second'),
...@@ -233,6 +233,12 @@ class PyAndCComparisonTests(unittest.TestCase): ...@@ -233,6 +233,12 @@ class PyAndCComparisonTests(unittest.TestCase):
from persistent.timestamp import pyTimeStamp from persistent.timestamp import pyTimeStamp
return pyTimeStamp(*args, **kwargs) return pyTimeStamp(*args, **kwargs)
@property
def _is_jython(self):
import platform
py_impl = getattr(platform, 'python_implementation', lambda: None)
return py_impl() == 'Jython'
def _make_C_and_Py(self, *args, **kwargs): def _make_C_and_Py(self, *args, **kwargs):
return self._makeC(*args, **kwargs), self._makePy(*args, **kwargs) return self._makeC(*args, **kwargs), self._makePy(*args, **kwargs)
...@@ -267,21 +273,19 @@ class PyAndCComparisonTests(unittest.TestCase): ...@@ -267,21 +273,19 @@ class PyAndCComparisonTests(unittest.TestCase):
# -3850693964765720575 # -3850693964765720575
# Fake out the python version to think it's on a 32-bit # Fake out the python version to think it's on a 32-bit
# platform and test the same; also verify 64 bit # platform and test the same; also verify 64 bit
from persistent import timestamp as MUT
bit_32_hash = -1419374591 bit_32_hash = -1419374591
bit_64_hash = -3850693964765720575 bit_64_hash = -3850693964765720575
import persistent.timestamp orig_maxint = MUT._MAXINT
import ctypes
orig_c_long = persistent.timestamp.c_long
try: try:
persistent.timestamp.c_long = ctypes.c_int32 MUT._MAXINT = MAX_32_BITS
py = self._makePy(*self.now_ts_args) py = self._makePy(*self.now_ts_args)
self.assertEqual(hash(py), bit_32_hash) self.assertEqual(hash(py), bit_32_hash)
MUT._MAXINT = int(2 ** 63 - 1)
persistent.timestamp.c_long = ctypes.c_int64
# call __hash__ directly to avoid interpreter truncation # call __hash__ directly to avoid interpreter truncation
# in hash() on 32-bit platforms # in hash() on 32-bit platforms
if not _is_jython: if not self._is_jython:
self.assertEqual(py.__hash__(), bit_64_hash) self.assertEqual(py.__hash__(), bit_64_hash)
else: else:
# Jython 2.7's ctypes module doesn't properly # Jython 2.7's ctypes module doesn't properly
...@@ -290,72 +294,73 @@ class PyAndCComparisonTests(unittest.TestCase): ...@@ -290,72 +294,73 @@ class PyAndCComparisonTests(unittest.TestCase):
# Therefore we get back the full python long. The actual # Therefore we get back the full python long. The actual
# hash() calls are correct, though, because the JVM uses # hash() calls are correct, though, because the JVM uses
# 32-bit ints for its hashCode methods. # 32-bit ints for its hashCode methods.
self.assertEqual(py.__hash__(), 384009219096809580920179179233996861765753210540033) self.assertEqual(
py.__hash__(),
384009219096809580920179179233996861765753210540033)
finally: finally:
persistent.timestamp.c_long = orig_c_long MUT._MAXINT = orig_maxint
# These are *usually* aliases, but aren't required # These are *usually* aliases, but aren't required
# to be (and aren't under Jython 2.7). # to be (and aren't under Jython 2.7).
if orig_c_long is ctypes.c_int32: if orig_maxint == MAX_32_BITS:
self.assertEqual(py.__hash__(), bit_32_hash) self.assertEqual(py.__hash__(), bit_32_hash)
elif orig_c_long is ctypes.c_int64: elif orig_maxint == MAX_64_BITS:
self.assertEqual(py.__hash__(), bit_64_hash) self.assertEqual(py.__hash__(), bit_64_hash)
def test_hash_equal_constants(self): def test_hash_equal_constants(self):
# The simple constants make it easier to diagnose # The simple constants make it easier to diagnose
# a difference in algorithms # a difference in algorithms
import persistent.timestamp import persistent.timestamp as MUT
import ctypes
# We get 32-bit hash values of 32-bit platforms, or on the JVM # We get 32-bit hash values of 32-bit platforms, or on the JVM
is_32_bit = persistent.timestamp.c_long == ctypes.c_int32 or _is_jython is_32_bit = MUT._MAXINT == (2**31 - 1) or self._is_jython
c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x00\x00\x00') c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(hash(c), 8) self.assertEqual(c.__hash__(), 8)
self.assertEqual(hash(c), hash(py)) self.assertEqual(hash(c), hash(py))
c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x00\x00\x01') c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x00\x00\x01')
self.assertEqual(hash(c), 9) self.assertEqual(c.__hash__(), 9)
self.assertEqual(hash(c), hash(py)) self.assertEqual(hash(c), hash(py))
c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x00\x01\x00') c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x00\x01\x00')
self.assertEqual(hash(c), 1000011) self.assertEqual(c.__hash__(), 1000011)
self.assertEqual(hash(c), hash(py)) self.assertEqual(hash(c), hash(py))
# overflow kicks in here on 32-bit platforms # overflow kicks in here on 32-bit platforms
c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x01\x00\x00') c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x01\x00\x00')
if is_32_bit: if is_32_bit:
self.assertEqual(hash(c), -721379967) self.assertEqual(c.__hash__(), -721379967)
else: else:
self.assertEqual(hash(c), 1000006000001) self.assertEqual(c.__hash__(), 1000006000001)
self.assertEqual(hash(c), hash(py)) self.assertEqual(hash(c), hash(py))
c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x01\x00\x00\x00') c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x01\x00\x00\x00')
if is_32_bit: if is_32_bit:
self.assertEqual(hash(c), 583896275) self.assertEqual(c.__hash__(), 583896275)
else: else:
self.assertEqual(hash(c), 1000009000027000019) self.assertEqual(c.__hash__(), 1000009000027000019)
self.assertEqual(hash(c), hash(py)) self.assertEqual(hash(c), hash(py))
# Overflow kicks in at this point on 64-bit platforms # Overflow kicks in at this point on 64-bit platforms
c, py = self._make_C_and_Py(b'\x00\x00\x00\x01\x00\x00\x00\x00') c, py = self._make_C_and_Py(b'\x00\x00\x00\x01\x00\x00\x00\x00')
if is_32_bit: if is_32_bit:
self.assertEqual(hash(c), 1525764953) self.assertEqual(c.__hash__(), 1525764953)
else: else:
self.assertEqual(hash(c), -4442925868394654887) self.assertEqual(c.__hash__(), -4442925868394654887)
self.assertEqual(hash(c), hash(py)) self.assertEqual(hash(c), hash(py))
c, py = self._make_C_and_Py(b'\x00\x00\x01\x00\x00\x00\x00\x00') c, py = self._make_C_and_Py(b'\x00\x00\x01\x00\x00\x00\x00\x00')
if is_32_bit: if is_32_bit:
self.assertEqual(hash(c), -429739973) self.assertEqual(c.__hash__(), -429739973)
else: else:
self.assertEqual(hash(c), -3993531167153147845) self.assertEqual(c.__hash__(), -3993531167153147845)
self.assertEqual(hash(c), hash(py)) self.assertEqual(hash(c), hash(py))
c, py = self._make_C_and_Py(b'\x01\x00\x00\x00\x00\x00\x00\x00') c, py = self._make_C_and_Py(b'\x01\x00\x00\x00\x00\x00\x00\x00')
if is_32_bit: if is_32_bit:
self.assertEqual(hash(c), 263152323) self.assertEqual(c.__hash__(), 263152323)
else: else:
self.assertEqual(hash(c), -3099646879006235965) self.assertEqual(c.__hash__(), -3099646879006235965)
self.assertEqual(hash(c), hash(py)) self.assertEqual(hash(c), hash(py))
def test_ordering(self): def test_ordering(self):
......
...@@ -13,22 +13,29 @@ ...@@ -13,22 +13,29 @@
############################################################################## ##############################################################################
__all__ = ('TimeStamp',) __all__ = ('TimeStamp',)
from ctypes import c_long
import datetime import datetime
import math import math
import struct import struct
import sys import sys
_RAWTYPE = bytes _RAWTYPE = bytes
_MAXINT = sys.maxsize
def _makeOctets(s): def _makeOctets(s):
if sys.version_info < (3,): if sys.version_info < (3,):
return bytes(s) return bytes(s)
return bytes(s, 'ascii') #pragma NO COVERAGE return bytes(s, 'ascii') #pragma NO COVERAGE
_ZERO = _makeOctets('\x00' * 8) _ZERO = _makeOctets('\x00' * 8)
def _wraparound(x):
# Make sure to overflow and wraparound just
# like the C code does.
return int(((x + (_MAXINT + 1)) & ((_MAXINT << 1) + 1)) - (_MAXINT + 1))
class _UTC(datetime.tzinfo): class _UTC(datetime.tzinfo):
def tzname(self): def tzname(self):
return 'UTC' return 'UTC'
...@@ -156,9 +163,8 @@ class pyTimeStamp(object): ...@@ -156,9 +163,8 @@ class pyTimeStamp(object):
x = (1000003 * x) ^ i x = (1000003 * x) ^ i
x ^= 8 x ^= 8
# Make sure to overflow and wraparound just x = _wraparound(x)
# like the C code does.
x = c_long(x).value
if x == -1: #pragma: no cover if x == -1: #pragma: no cover
# The C version has this condition, but it's not clear # The C version has this condition, but it's not clear
# why; it's also not immediately obvious what bytestring # why; it's also not immediately obvious what bytestring
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment