fix timestamp hash computation on 32-bit platform. Patch extracted from PR #22 and #23, see: https://github.com/zopefoundation/persistent/issues/21 https://github.com/zopefoundation/persistent/pull/22 https://github.com/zopefoundation/persistent/pull/23 --- persistent/tests/test_timestamp.py | 64 +++++++++++++++++++++++++++++++++---- persistent/timestamp.py | 4 +- 2 files changed, 60 insertions(+), 8 deletions(-) Index: persistent-4.0.9/persistent/tests/test_timestamp.py =================================================================== --- persistent-4.0.9.orig/persistent/tests/test_timestamp.py +++ persistent-4.0.9/persistent/tests/test_timestamp.py @@ -253,9 +253,45 @@ class PyAndCComparisonTests(unittest.Tes c, py = self._make_C_and_Py(*self.now_ts_args) self.assertEqual(hash(c), hash(py)) + def test_py_hash_32_64_bit(self): + # We happen to know that on a 32-bit platform, the hashcode + # of the c version should be exactly + # -1419374591 + # and the 64-bit should be exactly: + # -3850693964765720575 + # Fake out the python version to think it's on a 32-bit + # platform and test the same; also verify 64 bit + bit_32_hash = -1419374591 + bit_64_hash = -3850693964765720575 + import persistent.timestamp + import ctypes + orig_c_long = persistent.timestamp.c_long + try: + persistent.timestamp.c_long = ctypes.c_int32 + py = self._makePy(*self.now_ts_args) + self.assertEqual(hash(py), bit_32_hash) + + persistent.timestamp.c_long = ctypes.c_int64 + # call __hash__ directly to avoid interpreter truncation + # in hash() on 32-bit platforms + self.assertEqual(py.__hash__(), bit_64_hash) + finally: + persistent.timestamp.c_long = orig_c_long + + if orig_c_long is ctypes.c_int32: + self.assertEqual(py.__hash__(), bit_32_hash) + elif orig_c_long is ctypes.c_int64: + self.assertEqual(py.__hash__(), bit_64_hash) + else: + self.fail("Unknown bitness") + def test_hash_equal_constants(self): # The simple constants make it easier to diagnose # a difference in algorithms + import persistent.timestamp + import ctypes + is_32_bit = persistent.timestamp.c_long == ctypes.c_int32 + c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x00\x00\x00') self.assertEqual(hash(c), 8) self.assertEqual(hash(c), hash(py)) @@ -268,25 +304,41 @@ class PyAndCComparisonTests(unittest.Tes self.assertEqual(hash(c), 1000011) self.assertEqual(hash(c), hash(py)) + # overflow kicks in here on 32-bit platforms c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x01\x00\x00') - self.assertEqual(hash(c), 1000006000001) + if is_32_bit: + self.assertEqual(hash(c), -721379967) + else: + self.assertEqual(hash(c), 1000006000001) self.assertEqual(hash(c), hash(py)) c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x01\x00\x00\x00') - self.assertEqual(hash(c), 1000009000027000019) + if is_32_bit: + self.assertEqual(hash(c), 583896275) + else: + self.assertEqual(hash(c), 1000009000027000019) self.assertEqual(hash(c), hash(py)) - # Overflow kicks in at this point + # 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') - self.assertEqual(hash(c), -4442925868394654887) + if is_32_bit: + self.assertEqual(hash(c), 1525764953) + else: + self.assertEqual(hash(c), -4442925868394654887) self.assertEqual(hash(c), hash(py)) c, py = self._make_C_and_Py(b'\x00\x00\x01\x00\x00\x00\x00\x00') - self.assertEqual(hash(c), -3993531167153147845) + if is_32_bit: + self.assertEqual(hash(c), -429739973) + else: + self.assertEqual(hash(c), -3993531167153147845) self.assertEqual(hash(c), hash(py)) c, py = self._make_C_and_Py(b'\x01\x00\x00\x00\x00\x00\x00\x00') - self.assertEqual(hash(c), -3099646879006235965) + if is_32_bit: + self.assertEqual(hash(c), 263152323) + else: + self.assertEqual(hash(c), -3099646879006235965) self.assertEqual(hash(c), hash(py)) def test_ordering(self): Index: persistent-4.0.9/persistent/timestamp.py =================================================================== --- persistent-4.0.9.orig/persistent/timestamp.py +++ persistent-4.0.9/persistent/timestamp.py @@ -13,7 +13,7 @@ ############################################################################## __all__ = ('TimeStamp',) -from ctypes import c_int64 +from ctypes import c_long import datetime import math import struct @@ -158,7 +158,7 @@ class pyTimeStamp(object): # Make sure to overflow and wraparound just # like the C code does. - x = c_int64(x).value + x = c_long(x).value if x == -1: #pragma: no cover # The C version has this condition, but it's not clear # why; it's also not immediately obvious what bytestring