61 lines
1.9 KiB
Python
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()
|