test_hash.py 6.88 KB
Newer Older
1 2 3
# test the invariant that
#   iff a==b then hash(a)==hash(b)
#
4
# Also test that hash implementations are inherited as expected
5

6 7
import datetime
import os
8
import sys
9
import unittest
10
from test import support
11
from test.script_helper import assert_python_ok
12
from collections import Hashable
13

14
IS_64BIT = sys.maxsize > 2**32
15

16

17
class HashEqualityTestCase(unittest.TestCase):
18

19
    def same_hash(self, *objlist):
Fred Drake's avatar
Fred Drake committed
20 21
        # Hash each object given and fail if
        # the hash values are not all the same.
22
        hashed = list(map(hash, objlist))
23 24
        for h in hashed[1:]:
            if h != hashed[0]:
25
                self.fail("hashed values differ: %r" % (objlist,))
26

27
    def test_numeric_literals(self):
28
        self.same_hash(1, 1, 1.0, 1.0+0.0j)
29 30 31
        self.same_hash(0, 0.0, 0.0+0.0j)
        self.same_hash(-1, -1.0, -1.0+0.0j)
        self.same_hash(-2, -2.0, -2.0+0.0j)
32

33
    def test_coerced_integers(self):
34
        self.same_hash(int(1), int(1), float(1), complex(1),
35
                       int('1'), float('1.0'))
36 37 38 39 40 41
        self.same_hash(int(-2**31), float(-2**31))
        self.same_hash(int(1-2**31), float(1-2**31))
        self.same_hash(int(2**31-1), float(2**31-1))
        # for 64-bit platforms
        self.same_hash(int(2**31), float(2**31))
        self.same_hash(int(-2**63), float(-2**63))
Guilherme Polo's avatar
Guilherme Polo committed
42
        self.same_hash(int(2**63), float(2**63))
43

44
    def test_coerced_floats(self):
45
        self.same_hash(int(1.23e300), float(1.23e300))
46
        self.same_hash(float(0.5), complex(0.5, 0.0))
47

48

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
_default_hash = object.__hash__
class DefaultHash(object): pass

_FIXED_HASH_VALUE = 42
class FixedHash(object):
    def __hash__(self):
        return _FIXED_HASH_VALUE

class OnlyEquality(object):
    def __eq__(self, other):
        return self is other

class OnlyInequality(object):
    def __ne__(self, other):
        return self is not other

class InheritedHashWithEquality(FixedHash, OnlyEquality): pass
class InheritedHashWithInequality(FixedHash, OnlyInequality): pass

class NoHash(object):
    __hash__ = None

class HashInheritanceTestCase(unittest.TestCase):
    default_expected = [object(),
                        DefaultHash(),
                        OnlyInequality(),
                       ]
    fixed_expected = [FixedHash(),
                      InheritedHashWithEquality(),
                      InheritedHashWithInequality(),
                      ]
    error_expected = [NoHash(),
                      OnlyEquality(),
                      ]

    def test_default_hash(self):
        for obj in self.default_expected:
            self.assertEqual(hash(obj), _default_hash(obj))

    def test_fixed_hash(self):
        for obj in self.fixed_expected:
            self.assertEqual(hash(obj), _FIXED_HASH_VALUE)

    def test_error_hash(self):
        for obj in self.error_expected:
            self.assertRaises(TypeError, hash, obj)

    def test_hashable(self):
        objects = (self.default_expected +
                   self.fixed_expected)
        for obj in objects:
100
            self.assertIsInstance(obj, Hashable)
101 102 103

    def test_not_hashable(self):
        for obj in self.error_expected:
104
            self.assertNotIsInstance(obj, Hashable)
105 106


107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
# Issue #4701: Check that some builtin types are correctly hashable
class DefaultIterSeq(object):
    seq = range(10)
    def __len__(self):
        return len(self.seq)
    def __getitem__(self, index):
        return self.seq[index]

class HashBuiltinsTestCase(unittest.TestCase):
    hashes_to_check = [range(10),
                       enumerate(range(10)),
                       iter(DefaultIterSeq()),
                       iter(lambda: 0, 0),
                      ]

    def test_hashes(self):
        _default_hash = object.__hash__
        for obj in self.hashes_to_check:
            self.assertEqual(hash(obj), _default_hash(obj))

127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
class HashRandomizationTests(unittest.TestCase):

    # Each subclass should define a field "repr_", containing the repr() of
    # an object to be tested

    def get_hash_command(self, repr_):
        return 'print(hash(%s))' % repr_

    def get_hash(self, repr_, seed=None):
        env = os.environ.copy()
        env['__cleanenv'] = True  # signal to assert_python not to do a copy
                                  # of os.environ on its own
        if seed is not None:
            env['PYTHONHASHSEED'] = str(seed)
        else:
            env.pop('PYTHONHASHSEED', None)
        out = assert_python_ok(
            '-c', self.get_hash_command(repr_),
            **env)
        stdout = out[1].strip()
        return int(stdout)

    def test_randomized_hash(self):
        # two runs should return different hashes
        run1 = self.get_hash(self.repr_, seed='random')
        run2 = self.get_hash(self.repr_, seed='random')
        self.assertNotEqual(run1, run2)

class StringlikeHashRandomizationTests(HashRandomizationTests):
    def test_null_hash(self):
        # PYTHONHASHSEED=0 disables the randomized hash
        if IS_64BIT:
            known_hash_of_obj = 1453079729188098211
        else:
            known_hash_of_obj = -1600925533

        # Randomization is disabled by default:
        self.assertEqual(self.get_hash(self.repr_), known_hash_of_obj)

        # It can also be disabled by setting the seed to 0:
        self.assertEqual(self.get_hash(self.repr_, seed=0), known_hash_of_obj)

    def test_fixed_hash(self):
        # test a fixed seed for the randomized hash
        # Note that all types share the same values:
        if IS_64BIT:
173 174 175 176
            if sys.byteorder == 'little':
                h = -4410911502303878509
            else:
                h = -3570150969479994130
177
        else:
178 179 180 181
            if sys.byteorder == 'little':
                h = -206076799
            else:
                h = -1024014457
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
        self.assertEqual(self.get_hash(self.repr_, seed=42), h)

class StrHashRandomizationTests(StringlikeHashRandomizationTests):
    repr_ = repr('abc')

    def test_empty_string(self):
        self.assertEqual(hash(""), 0)

class BytesHashRandomizationTests(StringlikeHashRandomizationTests):
    repr_ = repr(b'abc')

    def test_empty_string(self):
        self.assertEqual(hash(b""), 0)

class DatetimeTests(HashRandomizationTests):
    def get_hash_command(self, repr_):
        return 'import datetime; print(hash(%s))' % repr_

class DatetimeDateTests(DatetimeTests):
    repr_ = repr(datetime.date(1066, 10, 14))

class DatetimeDatetimeTests(DatetimeTests):
    repr_ = repr(datetime.datetime(1, 2, 3, 4, 5, 6, 7))

class DatetimeTimeTests(DatetimeTests):
    repr_ = repr(datetime.time(0))


210
def test_main():
211
    support.run_unittest(HashEqualityTestCase,
212 213 214 215 216 217 218
                         HashInheritanceTestCase,
                         HashBuiltinsTestCase,
                         StrHashRandomizationTests,
                         BytesHashRandomizationTests,
                         DatetimeDateTests,
                         DatetimeDatetimeTests,
                         DatetimeTimeTests)
219 220 221 222


if __name__ == "__main__":
    test_main()