Skip to content

Commit

Permalink
Merge pull request #18 from borgbackup/stresstest
Browse files Browse the repository at this point in the history
Stresstest
  • Loading branch information
ThomasWaldmann authored Oct 27, 2024
2 parents 6b65d4c + 416502c commit ac4c5aa
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 20 deletions.
94 changes: 94 additions & 0 deletions tests/hashtable_stress_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import struct
import random

import pytest

from borghash import HashTable


def H(x, y):
"""
Create a 256bit key - x will determine the first 32 bits, y will determine the last 32bit.
As our HashTable computes the ht index from first 32 bits, same x will give same ht index (a collision).
"""
return struct.pack(">IIIIIIII", x, 0, 0, 0, 0, 0, 0, y) # BE is easier to read.


@pytest.fixture
def ht():
# 256bit keys, 32bit values
return HashTable(key_size=32, value_size=4)


def check(ht, pydict, destructive=False):
"""check if ht has same contents as pydict"""
assert len(ht) == len(pydict)
assert dict(ht.items()) == pydict
for key, value in pydict.items():
assert ht[key] == value
if destructive:
items = list(pydict.items())
random.shuffle(items)
for key, value in items:
del ht[key]
assert len(ht) == 0


def test_few_collisions_stress(ht):
pydict = {}
for h in range(10000):
key = H(h, h) # few collisions
value = key[-4:]
ht[key] = value
pydict[key] = value
check(ht, pydict, destructive=True)


def test_many_collisions_stress(ht):
pydict = {}
for h in range(10000):
key = H(0, h) # everything collides
value = key[-4:]
ht[key] = value
pydict[key] = value
check(ht, pydict, destructive=True)


@pytest.mark.parametrize("delete_threshold", [0, 10, 100, 1000, 10000])
def test_ht_vs_dict_stress(ht, delete_threshold):
SET, GET, DELETE = 0, 1, 2
UINT32_MAX = 2 ** 32 - 1

def random_operations(ht, pydict):
def new_random_keys(count):
keys = set()
while len(keys) < count:
x = random.randint(0, UINT32_MAX)
key = H(x, x) # few collisions
keys.add(key)
return keys

def some_existing_keys(count):
all_keys = list(pydict.keys())
return random.sample(all_keys, min(len(all_keys), count))

count = random.randint(1, 20)
operation = random.choice([SET, GET, DELETE] if len(pydict) > delete_threshold else [SET, GET])
if operation == SET:
for key in new_random_keys(count):
value = key[:4]
ht[key] = value
pydict[key] = value
else:
for key in some_existing_keys(count):
if operation == GET:
assert ht[key] == pydict[key]
elif operation == DELETE:
del ht[key]
del pydict[key]

pydict = {}
for _ in range(10000):
random_operations(ht, pydict)
check(ht, pydict, destructive=False)
check(ht, pydict, destructive=True)
20 changes: 0 additions & 20 deletions tests/hashtable_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,26 +109,6 @@ def test_clear(ht12):
ht12[key2]


def test_ht_stress(ht):
# this also triggers some hashtable resizing
keys = set()
for i in range(10000):
key = H2(i)
value = key[:4]
ht[key] = value
keys.add(key)
found_keys = set()
for key, value in ht.items():
found_keys.add(key)
assert value == key[:4]
assert keys == found_keys
for key in keys:
assert ht[key] == key[:4]
for key in keys:
del ht[key]
assert len(ht) == 0


def test_stats(ht):
assert ht.stats["get"] == 0
assert ht.stats["set"] == 0
Expand Down

0 comments on commit ac4c5aa

Please sign in to comment.