Files
troupe-standup/wizard-work/beam/test_jump_consistent_hash.py
T

61 lines
1.9 KiB
Python

import random
import unittest
from jump_consistent_hash import jump_hash, JumpConsistentHash
class TestJumpHash(unittest.TestCase):
def test_range(self):
for n in (1, 2, 5, 10, 100, 1000):
for i in range(200):
b = jump_hash(f"k{i}", n)
self.assertGreaterEqual(b, 0)
self.assertLess(b, n)
def test_single_bucket(self):
for i in range(50):
self.assertEqual(jump_hash(i, 1), 0)
def test_deterministic(self):
self.assertEqual(jump_hash("abc", 7), jump_hash("abc", 7))
def test_invalid(self):
with self.assertRaises(ValueError):
jump_hash("x", 0)
with self.assertRaises(ValueError):
jump_hash("x", -1)
with self.assertRaises(TypeError):
jump_hash("x", 1.5)
def test_minimal_movement(self):
# Moving from k to k+1 buckets: only ~1/(k+1) keys should move.
keys = [f"key-{i}" for i in range(10000)]
for k in (4, 8, 16, 32):
before = {key: jump_hash(key, k) for key in keys}
after = {key: jump_hash(key, k + 1) for key in keys}
moved = sum(1 for key in keys if before[key] != after[key])
# expected ~ 10000/(k+1); allow generous slack
expected = 10000 / (k + 1)
self.assertLess(moved, expected * 2.5, f"k={k} moved={moved}")
def test_uniformity(self):
n = 10
counts = [0] * n
for i in range(100000):
counts[jump_hash(i, n)] += 1
# each bucket within 5% of mean
mean = 100000 / n
for c in counts:
self.assertGreater(c, mean * 0.9)
self.assertLess(c, mean * 1.1)
def test_wrapper(self):
jch = JumpConsistentHash(5)
self.assertEqual(jch.bucket("a"), jump_hash("a", 5))
jch.resize(7)
self.assertEqual(jch.num_buckets, 7)
if __name__ == "__main__":
unittest.main()