xref: /third_party/python/Lib/test/test_hash.py (revision 7db96d56)
17db96d56Sopenharmony_ci# test the invariant that
27db96d56Sopenharmony_ci#   iff a==b then hash(a)==hash(b)
37db96d56Sopenharmony_ci#
47db96d56Sopenharmony_ci# Also test that hash implementations are inherited as expected
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_ciimport datetime
77db96d56Sopenharmony_ciimport os
87db96d56Sopenharmony_ciimport sys
97db96d56Sopenharmony_ciimport unittest
107db96d56Sopenharmony_cifrom test.support.script_helper import assert_python_ok
117db96d56Sopenharmony_cifrom collections.abc import Hashable
127db96d56Sopenharmony_ci
137db96d56Sopenharmony_ciIS_64BIT = sys.maxsize > 2**32
147db96d56Sopenharmony_ci
157db96d56Sopenharmony_cidef lcg(x, length=16):
167db96d56Sopenharmony_ci    """Linear congruential generator"""
177db96d56Sopenharmony_ci    if x == 0:
187db96d56Sopenharmony_ci        return bytes(length)
197db96d56Sopenharmony_ci    out = bytearray(length)
207db96d56Sopenharmony_ci    for i in range(length):
217db96d56Sopenharmony_ci        x = (214013 * x + 2531011) & 0x7fffffff
227db96d56Sopenharmony_ci        out[i] = (x >> 16) & 0xff
237db96d56Sopenharmony_ci    return bytes(out)
247db96d56Sopenharmony_ci
257db96d56Sopenharmony_cidef pysiphash(uint64):
267db96d56Sopenharmony_ci    """Convert SipHash24 output to Py_hash_t
277db96d56Sopenharmony_ci    """
287db96d56Sopenharmony_ci    assert 0 <= uint64 < (1 << 64)
297db96d56Sopenharmony_ci    # simple unsigned to signed int64
307db96d56Sopenharmony_ci    if uint64 > (1 << 63) - 1:
317db96d56Sopenharmony_ci        int64 = uint64 - (1 << 64)
327db96d56Sopenharmony_ci    else:
337db96d56Sopenharmony_ci        int64 = uint64
347db96d56Sopenharmony_ci    # mangle uint64 to uint32
357db96d56Sopenharmony_ci    uint32 = (uint64 ^ uint64 >> 32) & 0xffffffff
367db96d56Sopenharmony_ci    # simple unsigned to signed int32
377db96d56Sopenharmony_ci    if uint32 > (1 << 31) - 1:
387db96d56Sopenharmony_ci        int32 = uint32 - (1 << 32)
397db96d56Sopenharmony_ci    else:
407db96d56Sopenharmony_ci        int32 = uint32
417db96d56Sopenharmony_ci    return int32, int64
427db96d56Sopenharmony_ci
437db96d56Sopenharmony_cidef skip_unless_internalhash(test):
447db96d56Sopenharmony_ci    """Skip decorator for tests that depend on SipHash24 or FNV"""
457db96d56Sopenharmony_ci    ok = sys.hash_info.algorithm in {"fnv", "siphash13", "siphash24"}
467db96d56Sopenharmony_ci    msg = "Requires SipHash13, SipHash24 or FNV"
477db96d56Sopenharmony_ci    return test if ok else unittest.skip(msg)(test)
487db96d56Sopenharmony_ci
497db96d56Sopenharmony_ci
507db96d56Sopenharmony_ciclass HashEqualityTestCase(unittest.TestCase):
517db96d56Sopenharmony_ci
527db96d56Sopenharmony_ci    def same_hash(self, *objlist):
537db96d56Sopenharmony_ci        # Hash each object given and fail if
547db96d56Sopenharmony_ci        # the hash values are not all the same.
557db96d56Sopenharmony_ci        hashed = list(map(hash, objlist))
567db96d56Sopenharmony_ci        for h in hashed[1:]:
577db96d56Sopenharmony_ci            if h != hashed[0]:
587db96d56Sopenharmony_ci                self.fail("hashed values differ: %r" % (objlist,))
597db96d56Sopenharmony_ci
607db96d56Sopenharmony_ci    def test_numeric_literals(self):
617db96d56Sopenharmony_ci        self.same_hash(1, 1, 1.0, 1.0+0.0j)
627db96d56Sopenharmony_ci        self.same_hash(0, 0.0, 0.0+0.0j)
637db96d56Sopenharmony_ci        self.same_hash(-1, -1.0, -1.0+0.0j)
647db96d56Sopenharmony_ci        self.same_hash(-2, -2.0, -2.0+0.0j)
657db96d56Sopenharmony_ci
667db96d56Sopenharmony_ci    def test_coerced_integers(self):
677db96d56Sopenharmony_ci        self.same_hash(int(1), int(1), float(1), complex(1),
687db96d56Sopenharmony_ci                       int('1'), float('1.0'))
697db96d56Sopenharmony_ci        self.same_hash(int(-2**31), float(-2**31))
707db96d56Sopenharmony_ci        self.same_hash(int(1-2**31), float(1-2**31))
717db96d56Sopenharmony_ci        self.same_hash(int(2**31-1), float(2**31-1))
727db96d56Sopenharmony_ci        # for 64-bit platforms
737db96d56Sopenharmony_ci        self.same_hash(int(2**31), float(2**31))
747db96d56Sopenharmony_ci        self.same_hash(int(-2**63), float(-2**63))
757db96d56Sopenharmony_ci        self.same_hash(int(2**63), float(2**63))
767db96d56Sopenharmony_ci
777db96d56Sopenharmony_ci    def test_coerced_floats(self):
787db96d56Sopenharmony_ci        self.same_hash(int(1.23e300), float(1.23e300))
797db96d56Sopenharmony_ci        self.same_hash(float(0.5), complex(0.5, 0.0))
807db96d56Sopenharmony_ci
817db96d56Sopenharmony_ci    def test_unaligned_buffers(self):
827db96d56Sopenharmony_ci        # The hash function for bytes-like objects shouldn't have
837db96d56Sopenharmony_ci        # alignment-dependent results (example in issue #16427).
847db96d56Sopenharmony_ci        b = b"123456789abcdefghijklmnopqrstuvwxyz" * 128
857db96d56Sopenharmony_ci        for i in range(16):
867db96d56Sopenharmony_ci            for j in range(16):
877db96d56Sopenharmony_ci                aligned = b[i:128+j]
887db96d56Sopenharmony_ci                unaligned = memoryview(b)[i:128+j]
897db96d56Sopenharmony_ci                self.assertEqual(hash(aligned), hash(unaligned))
907db96d56Sopenharmony_ci
917db96d56Sopenharmony_ci
927db96d56Sopenharmony_ci_default_hash = object.__hash__
937db96d56Sopenharmony_ciclass DefaultHash(object): pass
947db96d56Sopenharmony_ci
957db96d56Sopenharmony_ci_FIXED_HASH_VALUE = 42
967db96d56Sopenharmony_ciclass FixedHash(object):
977db96d56Sopenharmony_ci    def __hash__(self):
987db96d56Sopenharmony_ci        return _FIXED_HASH_VALUE
997db96d56Sopenharmony_ci
1007db96d56Sopenharmony_ciclass OnlyEquality(object):
1017db96d56Sopenharmony_ci    def __eq__(self, other):
1027db96d56Sopenharmony_ci        return self is other
1037db96d56Sopenharmony_ci
1047db96d56Sopenharmony_ciclass OnlyInequality(object):
1057db96d56Sopenharmony_ci    def __ne__(self, other):
1067db96d56Sopenharmony_ci        return self is not other
1077db96d56Sopenharmony_ci
1087db96d56Sopenharmony_ciclass InheritedHashWithEquality(FixedHash, OnlyEquality): pass
1097db96d56Sopenharmony_ciclass InheritedHashWithInequality(FixedHash, OnlyInequality): pass
1107db96d56Sopenharmony_ci
1117db96d56Sopenharmony_ciclass NoHash(object):
1127db96d56Sopenharmony_ci    __hash__ = None
1137db96d56Sopenharmony_ci
1147db96d56Sopenharmony_ciclass HashInheritanceTestCase(unittest.TestCase):
1157db96d56Sopenharmony_ci    default_expected = [object(),
1167db96d56Sopenharmony_ci                        DefaultHash(),
1177db96d56Sopenharmony_ci                        OnlyInequality(),
1187db96d56Sopenharmony_ci                       ]
1197db96d56Sopenharmony_ci    fixed_expected = [FixedHash(),
1207db96d56Sopenharmony_ci                      InheritedHashWithEquality(),
1217db96d56Sopenharmony_ci                      InheritedHashWithInequality(),
1227db96d56Sopenharmony_ci                      ]
1237db96d56Sopenharmony_ci    error_expected = [NoHash(),
1247db96d56Sopenharmony_ci                      OnlyEquality(),
1257db96d56Sopenharmony_ci                      ]
1267db96d56Sopenharmony_ci
1277db96d56Sopenharmony_ci    def test_default_hash(self):
1287db96d56Sopenharmony_ci        for obj in self.default_expected:
1297db96d56Sopenharmony_ci            self.assertEqual(hash(obj), _default_hash(obj))
1307db96d56Sopenharmony_ci
1317db96d56Sopenharmony_ci    def test_fixed_hash(self):
1327db96d56Sopenharmony_ci        for obj in self.fixed_expected:
1337db96d56Sopenharmony_ci            self.assertEqual(hash(obj), _FIXED_HASH_VALUE)
1347db96d56Sopenharmony_ci
1357db96d56Sopenharmony_ci    def test_error_hash(self):
1367db96d56Sopenharmony_ci        for obj in self.error_expected:
1377db96d56Sopenharmony_ci            self.assertRaises(TypeError, hash, obj)
1387db96d56Sopenharmony_ci
1397db96d56Sopenharmony_ci    def test_hashable(self):
1407db96d56Sopenharmony_ci        objects = (self.default_expected +
1417db96d56Sopenharmony_ci                   self.fixed_expected)
1427db96d56Sopenharmony_ci        for obj in objects:
1437db96d56Sopenharmony_ci            self.assertIsInstance(obj, Hashable)
1447db96d56Sopenharmony_ci
1457db96d56Sopenharmony_ci    def test_not_hashable(self):
1467db96d56Sopenharmony_ci        for obj in self.error_expected:
1477db96d56Sopenharmony_ci            self.assertNotIsInstance(obj, Hashable)
1487db96d56Sopenharmony_ci
1497db96d56Sopenharmony_ci
1507db96d56Sopenharmony_ci# Issue #4701: Check that some builtin types are correctly hashable
1517db96d56Sopenharmony_ciclass DefaultIterSeq(object):
1527db96d56Sopenharmony_ci    seq = range(10)
1537db96d56Sopenharmony_ci    def __len__(self):
1547db96d56Sopenharmony_ci        return len(self.seq)
1557db96d56Sopenharmony_ci    def __getitem__(self, index):
1567db96d56Sopenharmony_ci        return self.seq[index]
1577db96d56Sopenharmony_ci
1587db96d56Sopenharmony_ciclass HashBuiltinsTestCase(unittest.TestCase):
1597db96d56Sopenharmony_ci    hashes_to_check = [enumerate(range(10)),
1607db96d56Sopenharmony_ci                       iter(DefaultIterSeq()),
1617db96d56Sopenharmony_ci                       iter(lambda: 0, 0),
1627db96d56Sopenharmony_ci                      ]
1637db96d56Sopenharmony_ci
1647db96d56Sopenharmony_ci    def test_hashes(self):
1657db96d56Sopenharmony_ci        _default_hash = object.__hash__
1667db96d56Sopenharmony_ci        for obj in self.hashes_to_check:
1677db96d56Sopenharmony_ci            self.assertEqual(hash(obj), _default_hash(obj))
1687db96d56Sopenharmony_ci
1697db96d56Sopenharmony_ciclass HashRandomizationTests:
1707db96d56Sopenharmony_ci
1717db96d56Sopenharmony_ci    # Each subclass should define a field "repr_", containing the repr() of
1727db96d56Sopenharmony_ci    # an object to be tested
1737db96d56Sopenharmony_ci
1747db96d56Sopenharmony_ci    def get_hash_command(self, repr_):
1757db96d56Sopenharmony_ci        return 'print(hash(eval(%a)))' % repr_
1767db96d56Sopenharmony_ci
1777db96d56Sopenharmony_ci    def get_hash(self, repr_, seed=None):
1787db96d56Sopenharmony_ci        env = os.environ.copy()
1797db96d56Sopenharmony_ci        env['__cleanenv'] = True  # signal to assert_python not to do a copy
1807db96d56Sopenharmony_ci                                  # of os.environ on its own
1817db96d56Sopenharmony_ci        if seed is not None:
1827db96d56Sopenharmony_ci            env['PYTHONHASHSEED'] = str(seed)
1837db96d56Sopenharmony_ci        else:
1847db96d56Sopenharmony_ci            env.pop('PYTHONHASHSEED', None)
1857db96d56Sopenharmony_ci        out = assert_python_ok(
1867db96d56Sopenharmony_ci            '-c', self.get_hash_command(repr_),
1877db96d56Sopenharmony_ci            **env)
1887db96d56Sopenharmony_ci        stdout = out[1].strip()
1897db96d56Sopenharmony_ci        return int(stdout)
1907db96d56Sopenharmony_ci
1917db96d56Sopenharmony_ci    def test_randomized_hash(self):
1927db96d56Sopenharmony_ci        # two runs should return different hashes
1937db96d56Sopenharmony_ci        run1 = self.get_hash(self.repr_, seed='random')
1947db96d56Sopenharmony_ci        run2 = self.get_hash(self.repr_, seed='random')
1957db96d56Sopenharmony_ci        self.assertNotEqual(run1, run2)
1967db96d56Sopenharmony_ci
1977db96d56Sopenharmony_ciclass StringlikeHashRandomizationTests(HashRandomizationTests):
1987db96d56Sopenharmony_ci    repr_ = None
1997db96d56Sopenharmony_ci    repr_long = None
2007db96d56Sopenharmony_ci
2017db96d56Sopenharmony_ci    # 32bit little, 64bit little, 32bit big, 64bit big
2027db96d56Sopenharmony_ci    known_hashes = {
2037db96d56Sopenharmony_ci        'djba33x': [ # only used for small strings
2047db96d56Sopenharmony_ci            # seed 0, 'abc'
2057db96d56Sopenharmony_ci            [193485960, 193485960,  193485960, 193485960],
2067db96d56Sopenharmony_ci            # seed 42, 'abc'
2077db96d56Sopenharmony_ci            [-678966196, 573763426263223372, -820489388, -4282905804826039665],
2087db96d56Sopenharmony_ci            ],
2097db96d56Sopenharmony_ci        'siphash13': [
2107db96d56Sopenharmony_ci            # NOTE: PyUCS2 layout depends on endianness
2117db96d56Sopenharmony_ci            # seed 0, 'abc'
2127db96d56Sopenharmony_ci            [69611762, -4594863902769663758, 69611762, -4594863902769663758],
2137db96d56Sopenharmony_ci            # seed 42, 'abc'
2147db96d56Sopenharmony_ci            [-975800855, 3869580338025362921, -975800855, 3869580338025362921],
2157db96d56Sopenharmony_ci            # seed 42, 'abcdefghijk'
2167db96d56Sopenharmony_ci            [-595844228, 7764564197781545852, -595844228, 7764564197781545852],
2177db96d56Sopenharmony_ci            # seed 0, 'äú∑ℇ'
2187db96d56Sopenharmony_ci            [-1093288643, -2810468059467891395, -1041341092, 4925090034378237276],
2197db96d56Sopenharmony_ci            # seed 42, 'äú∑ℇ'
2207db96d56Sopenharmony_ci            [-585999602, -2845126246016066802, -817336969, -2219421378907968137],
2217db96d56Sopenharmony_ci        ],
2227db96d56Sopenharmony_ci        'siphash24': [
2237db96d56Sopenharmony_ci            # NOTE: PyUCS2 layout depends on endianness
2247db96d56Sopenharmony_ci            # seed 0, 'abc'
2257db96d56Sopenharmony_ci            [1198583518, 4596069200710135518, 1198583518, 4596069200710135518],
2267db96d56Sopenharmony_ci            # seed 42, 'abc'
2277db96d56Sopenharmony_ci            [273876886, -4501618152524544106, 273876886, -4501618152524544106],
2287db96d56Sopenharmony_ci            # seed 42, 'abcdefghijk'
2297db96d56Sopenharmony_ci            [-1745215313, 4436719588892876975, -1745215313, 4436719588892876975],
2307db96d56Sopenharmony_ci            # seed 0, 'äú∑ℇ'
2317db96d56Sopenharmony_ci            [493570806, 5749986484189612790, -1006381564, -5915111450199468540],
2327db96d56Sopenharmony_ci            # seed 42, 'äú∑ℇ'
2337db96d56Sopenharmony_ci            [-1677110816, -2947981342227738144, -1860207793, -4296699217652516017],
2347db96d56Sopenharmony_ci        ],
2357db96d56Sopenharmony_ci        'fnv': [
2367db96d56Sopenharmony_ci            # seed 0, 'abc'
2377db96d56Sopenharmony_ci            [-1600925533, 1453079729188098211, -1600925533,
2387db96d56Sopenharmony_ci             1453079729188098211],
2397db96d56Sopenharmony_ci            # seed 42, 'abc'
2407db96d56Sopenharmony_ci            [-206076799, -4410911502303878509, -1024014457,
2417db96d56Sopenharmony_ci             -3570150969479994130],
2427db96d56Sopenharmony_ci            # seed 42, 'abcdefghijk'
2437db96d56Sopenharmony_ci            [811136751, -5046230049376118746, -77208053 ,
2447db96d56Sopenharmony_ci             -4779029615281019666],
2457db96d56Sopenharmony_ci            # seed 0, 'äú∑ℇ'
2467db96d56Sopenharmony_ci            [44402817, 8998297579845987431, -1956240331,
2477db96d56Sopenharmony_ci             -782697888614047887],
2487db96d56Sopenharmony_ci            # seed 42, 'äú∑ℇ'
2497db96d56Sopenharmony_ci            [-283066365, -4576729883824601543, -271871407,
2507db96d56Sopenharmony_ci             -3927695501187247084],
2517db96d56Sopenharmony_ci        ]
2527db96d56Sopenharmony_ci    }
2537db96d56Sopenharmony_ci
2547db96d56Sopenharmony_ci    def get_expected_hash(self, position, length):
2557db96d56Sopenharmony_ci        if length < sys.hash_info.cutoff:
2567db96d56Sopenharmony_ci            algorithm = "djba33x"
2577db96d56Sopenharmony_ci        else:
2587db96d56Sopenharmony_ci            algorithm = sys.hash_info.algorithm
2597db96d56Sopenharmony_ci        if sys.byteorder == 'little':
2607db96d56Sopenharmony_ci            platform = 1 if IS_64BIT else 0
2617db96d56Sopenharmony_ci        else:
2627db96d56Sopenharmony_ci            assert(sys.byteorder == 'big')
2637db96d56Sopenharmony_ci            platform = 3 if IS_64BIT else 2
2647db96d56Sopenharmony_ci        return self.known_hashes[algorithm][position][platform]
2657db96d56Sopenharmony_ci
2667db96d56Sopenharmony_ci    def test_null_hash(self):
2677db96d56Sopenharmony_ci        # PYTHONHASHSEED=0 disables the randomized hash
2687db96d56Sopenharmony_ci        known_hash_of_obj = self.get_expected_hash(0, 3)
2697db96d56Sopenharmony_ci
2707db96d56Sopenharmony_ci        # Randomization is enabled by default:
2717db96d56Sopenharmony_ci        self.assertNotEqual(self.get_hash(self.repr_), known_hash_of_obj)
2727db96d56Sopenharmony_ci
2737db96d56Sopenharmony_ci        # It can also be disabled by setting the seed to 0:
2747db96d56Sopenharmony_ci        self.assertEqual(self.get_hash(self.repr_, seed=0), known_hash_of_obj)
2757db96d56Sopenharmony_ci
2767db96d56Sopenharmony_ci    @skip_unless_internalhash
2777db96d56Sopenharmony_ci    def test_fixed_hash(self):
2787db96d56Sopenharmony_ci        # test a fixed seed for the randomized hash
2797db96d56Sopenharmony_ci        # Note that all types share the same values:
2807db96d56Sopenharmony_ci        h = self.get_expected_hash(1, 3)
2817db96d56Sopenharmony_ci        self.assertEqual(self.get_hash(self.repr_, seed=42), h)
2827db96d56Sopenharmony_ci
2837db96d56Sopenharmony_ci    @skip_unless_internalhash
2847db96d56Sopenharmony_ci    def test_long_fixed_hash(self):
2857db96d56Sopenharmony_ci        if self.repr_long is None:
2867db96d56Sopenharmony_ci            return
2877db96d56Sopenharmony_ci        h = self.get_expected_hash(2, 11)
2887db96d56Sopenharmony_ci        self.assertEqual(self.get_hash(self.repr_long, seed=42), h)
2897db96d56Sopenharmony_ci
2907db96d56Sopenharmony_ci
2917db96d56Sopenharmony_ciclass StrHashRandomizationTests(StringlikeHashRandomizationTests,
2927db96d56Sopenharmony_ci                                unittest.TestCase):
2937db96d56Sopenharmony_ci    repr_ = repr('abc')
2947db96d56Sopenharmony_ci    repr_long = repr('abcdefghijk')
2957db96d56Sopenharmony_ci    repr_ucs2 = repr('äú∑ℇ')
2967db96d56Sopenharmony_ci
2977db96d56Sopenharmony_ci    @skip_unless_internalhash
2987db96d56Sopenharmony_ci    def test_empty_string(self):
2997db96d56Sopenharmony_ci        self.assertEqual(hash(""), 0)
3007db96d56Sopenharmony_ci
3017db96d56Sopenharmony_ci    @skip_unless_internalhash
3027db96d56Sopenharmony_ci    def test_ucs2_string(self):
3037db96d56Sopenharmony_ci        h = self.get_expected_hash(3, 6)
3047db96d56Sopenharmony_ci        self.assertEqual(self.get_hash(self.repr_ucs2, seed=0), h)
3057db96d56Sopenharmony_ci        h = self.get_expected_hash(4, 6)
3067db96d56Sopenharmony_ci        self.assertEqual(self.get_hash(self.repr_ucs2, seed=42), h)
3077db96d56Sopenharmony_ci
3087db96d56Sopenharmony_ciclass BytesHashRandomizationTests(StringlikeHashRandomizationTests,
3097db96d56Sopenharmony_ci                                  unittest.TestCase):
3107db96d56Sopenharmony_ci    repr_ = repr(b'abc')
3117db96d56Sopenharmony_ci    repr_long = repr(b'abcdefghijk')
3127db96d56Sopenharmony_ci
3137db96d56Sopenharmony_ci    @skip_unless_internalhash
3147db96d56Sopenharmony_ci    def test_empty_string(self):
3157db96d56Sopenharmony_ci        self.assertEqual(hash(b""), 0)
3167db96d56Sopenharmony_ci
3177db96d56Sopenharmony_ciclass MemoryviewHashRandomizationTests(StringlikeHashRandomizationTests,
3187db96d56Sopenharmony_ci                                       unittest.TestCase):
3197db96d56Sopenharmony_ci    repr_ = "memoryview(b'abc')"
3207db96d56Sopenharmony_ci    repr_long = "memoryview(b'abcdefghijk')"
3217db96d56Sopenharmony_ci
3227db96d56Sopenharmony_ci    @skip_unless_internalhash
3237db96d56Sopenharmony_ci    def test_empty_string(self):
3247db96d56Sopenharmony_ci        self.assertEqual(hash(memoryview(b"")), 0)
3257db96d56Sopenharmony_ci
3267db96d56Sopenharmony_ciclass DatetimeTests(HashRandomizationTests):
3277db96d56Sopenharmony_ci    def get_hash_command(self, repr_):
3287db96d56Sopenharmony_ci        return 'import datetime; print(hash(%s))' % repr_
3297db96d56Sopenharmony_ci
3307db96d56Sopenharmony_ciclass DatetimeDateTests(DatetimeTests, unittest.TestCase):
3317db96d56Sopenharmony_ci    repr_ = repr(datetime.date(1066, 10, 14))
3327db96d56Sopenharmony_ci
3337db96d56Sopenharmony_ciclass DatetimeDatetimeTests(DatetimeTests, unittest.TestCase):
3347db96d56Sopenharmony_ci    repr_ = repr(datetime.datetime(1, 2, 3, 4, 5, 6, 7))
3357db96d56Sopenharmony_ci
3367db96d56Sopenharmony_ciclass DatetimeTimeTests(DatetimeTests, unittest.TestCase):
3377db96d56Sopenharmony_ci    repr_ = repr(datetime.time(0))
3387db96d56Sopenharmony_ci
3397db96d56Sopenharmony_ci
3407db96d56Sopenharmony_ciclass HashDistributionTestCase(unittest.TestCase):
3417db96d56Sopenharmony_ci
3427db96d56Sopenharmony_ci    def test_hash_distribution(self):
3437db96d56Sopenharmony_ci        # check for hash collision
3447db96d56Sopenharmony_ci        base = "abcdefghabcdefg"
3457db96d56Sopenharmony_ci        for i in range(1, len(base)):
3467db96d56Sopenharmony_ci            prefix = base[:i]
3477db96d56Sopenharmony_ci            with self.subTest(prefix=prefix):
3487db96d56Sopenharmony_ci                s15 = set()
3497db96d56Sopenharmony_ci                s255 = set()
3507db96d56Sopenharmony_ci                for c in range(256):
3517db96d56Sopenharmony_ci                    h = hash(prefix + chr(c))
3527db96d56Sopenharmony_ci                    s15.add(h & 0xf)
3537db96d56Sopenharmony_ci                    s255.add(h & 0xff)
3547db96d56Sopenharmony_ci                # SipHash24 distribution depends on key, usually > 60%
3557db96d56Sopenharmony_ci                self.assertGreater(len(s15), 8, prefix)
3567db96d56Sopenharmony_ci                self.assertGreater(len(s255), 128, prefix)
3577db96d56Sopenharmony_ci
3587db96d56Sopenharmony_ciif __name__ == "__main__":
3597db96d56Sopenharmony_ci    unittest.main()
360