Skip to content

Commit

Permalink
Migrate dict.c unit tests to new framework (#946)
Browse files Browse the repository at this point in the history
This PR migrates the tests related to dict into new test framework as
part of #428.

Signed-off-by: haoqixu <[email protected]>
Signed-off-by: Binbin <[email protected]>
Co-authored-by: Binbin <[email protected]>
  • Loading branch information
haoqixu and enjoy-binbin authored Sep 9, 2024
1 parent 14016d2 commit 20d583f
Show file tree
Hide file tree
Showing 5 changed files with 343 additions and 271 deletions.
266 changes: 0 additions & 266 deletions src/dict.c
Original file line number Diff line number Diff line change
Expand Up @@ -1828,269 +1828,3 @@ void dictGetStats(char *buf, size_t bufsize, dict *d, int full) {
/* Make sure there is a NULL term at the end. */
orig_buf[orig_bufsize - 1] = '\0';
}

/* ------------------------------- Benchmark ---------------------------------*/

#ifdef SERVER_TEST
#include "testhelp.h"

#define TEST(name) printf("test — %s\n", name);

uint64_t hashCallback(const void *key) {
return dictGenHashFunction((unsigned char *)key, strlen((char *)key));
}

int compareCallback(dict *d, const void *key1, const void *key2) {
int l1, l2;
UNUSED(d);

l1 = strlen((char *)key1);
l2 = strlen((char *)key2);
if (l1 != l2) return 0;
return memcmp(key1, key2, l1) == 0;
}

void freeCallback(dict *d, void *val) {
UNUSED(d);

zfree(val);
}

char *stringFromLongLong(long long value) {
char buf[32];
int len;
char *s;

len = snprintf(buf, sizeof(buf), "%lld", value);
s = zmalloc(len + 1);
memcpy(s, buf, len);
s[len] = '\0';
return s;
}

dictType BenchmarkDictType = {hashCallback, NULL, compareCallback, freeCallback, NULL, NULL};

#define start_benchmark() start = timeInMilliseconds()
#define end_benchmark(msg) \
do { \
elapsed = timeInMilliseconds() - start; \
printf(msg ": %ld items in %lld ms\n", count, elapsed); \
} while (0)

/* ./valkey-server test dict [<count> | --accurate] */
int dictTest(int argc, char **argv, int flags) {
long j;
long long start, elapsed;
int retval;
dict *dict = dictCreate(&BenchmarkDictType);
long count = 0;
unsigned long new_dict_size, current_dict_used, remain_keys;
int accurate = (flags & TEST_ACCURATE);

if (argc == 4) {
if (accurate) {
count = 5000000;
} else {
count = strtol(argv[3], NULL, 10);
}
} else {
count = 5000;
}

TEST("Add 16 keys and verify dict resize is ok") {
dictSetResizeEnabled(DICT_RESIZE_ENABLE);
for (j = 0; j < 16; j++) {
retval = dictAdd(dict, stringFromLongLong(j), (void *)j);
assert(retval == DICT_OK);
}
while (dictIsRehashing(dict)) dictRehashMicroseconds(dict, 1000);
assert(dictSize(dict) == 16);
assert(dictBuckets(dict) == 16);
}

TEST("Use DICT_RESIZE_AVOID to disable the dict resize and pad to (dict_force_resize_ratio * 16)") {
/* Use DICT_RESIZE_AVOID to disable the dict resize, and pad
* the number of keys to (dict_force_resize_ratio * 16), so we can satisfy
* dict_force_resize_ratio in next test. */
dictSetResizeEnabled(DICT_RESIZE_AVOID);
for (j = 16; j < (long)dict_force_resize_ratio * 16; j++) {
retval = dictAdd(dict, stringFromLongLong(j), (void *)j);
assert(retval == DICT_OK);
}
current_dict_used = dict_force_resize_ratio * 16;
assert(dictSize(dict) == current_dict_used);
assert(dictBuckets(dict) == 16);
}

TEST("Add one more key, trigger the dict resize") {
retval = dictAdd(dict, stringFromLongLong(current_dict_used), (void *)(current_dict_used));
assert(retval == DICT_OK);
current_dict_used++;
new_dict_size = 1UL << _dictNextExp(current_dict_used);
assert(dictSize(dict) == current_dict_used);
assert(DICTHT_SIZE(dict->ht_size_exp[0]) == 16);
assert(DICTHT_SIZE(dict->ht_size_exp[1]) == new_dict_size);

/* Wait for rehashing. */
dictSetResizeEnabled(DICT_RESIZE_ENABLE);
while (dictIsRehashing(dict)) dictRehashMicroseconds(dict, 1000);
assert(dictSize(dict) == current_dict_used);
assert(DICTHT_SIZE(dict->ht_size_exp[0]) == new_dict_size);
assert(DICTHT_SIZE(dict->ht_size_exp[1]) == 0);
}

TEST("Delete keys until we can trigger shrink in next test") {
/* Delete keys until we can satisfy (1 / HASHTABLE_MIN_FILL) in the next test. */
for (j = new_dict_size / HASHTABLE_MIN_FILL + 1; j < (long)current_dict_used; j++) {
char *key = stringFromLongLong(j);
retval = dictDelete(dict, key);
zfree(key);
assert(retval == DICT_OK);
}
current_dict_used = new_dict_size / HASHTABLE_MIN_FILL + 1;
assert(dictSize(dict) == current_dict_used);
assert(DICTHT_SIZE(dict->ht_size_exp[0]) == new_dict_size);
assert(DICTHT_SIZE(dict->ht_size_exp[1]) == 0);
}

TEST("Delete one more key, trigger the dict resize") {
current_dict_used--;
char *key = stringFromLongLong(current_dict_used);
retval = dictDelete(dict, key);
zfree(key);
unsigned long oldDictSize = new_dict_size;
new_dict_size = 1UL << _dictNextExp(current_dict_used);
assert(retval == DICT_OK);
assert(dictSize(dict) == current_dict_used);
assert(DICTHT_SIZE(dict->ht_size_exp[0]) == oldDictSize);
assert(DICTHT_SIZE(dict->ht_size_exp[1]) == new_dict_size);

/* Wait for rehashing. */
while (dictIsRehashing(dict)) dictRehashMicroseconds(dict, 1000);
assert(dictSize(dict) == current_dict_used);
assert(DICTHT_SIZE(dict->ht_size_exp[0]) == new_dict_size);
assert(DICTHT_SIZE(dict->ht_size_exp[1]) == 0);
}

TEST("Empty the dictionary and add 128 keys") {
dictEmpty(dict, NULL);
for (j = 0; j < 128; j++) {
retval = dictAdd(dict, stringFromLongLong(j), (void *)j);
assert(retval == DICT_OK);
}
while (dictIsRehashing(dict)) dictRehashMicroseconds(dict, 1000);
assert(dictSize(dict) == 128);
assert(dictBuckets(dict) == 128);
}

TEST("Use DICT_RESIZE_AVOID to disable the dict resize and reduce to 3") {
/* Use DICT_RESIZE_AVOID to disable the dict reset, and reduce
* the number of keys until we can trigger shrinking in next test. */
dictSetResizeEnabled(DICT_RESIZE_AVOID);
remain_keys = DICTHT_SIZE(dict->ht_size_exp[0]) / (HASHTABLE_MIN_FILL * dict_force_resize_ratio) + 1;
for (j = remain_keys; j < 128; j++) {
char *key = stringFromLongLong(j);
retval = dictDelete(dict, key);
zfree(key);
assert(retval == DICT_OK);
}
current_dict_used = remain_keys;
assert(dictSize(dict) == remain_keys);
assert(dictBuckets(dict) == 128);
}

TEST("Delete one more key, trigger the dict resize") {
current_dict_used--;
char *key = stringFromLongLong(current_dict_used);
retval = dictDelete(dict, key);
zfree(key);
new_dict_size = 1UL << _dictNextExp(current_dict_used);
assert(retval == DICT_OK);
assert(dictSize(dict) == current_dict_used);
assert(DICTHT_SIZE(dict->ht_size_exp[0]) == 128);
assert(DICTHT_SIZE(dict->ht_size_exp[1]) == new_dict_size);

/* Wait for rehashing. */
dictSetResizeEnabled(DICT_RESIZE_ENABLE);
while (dictIsRehashing(dict)) dictRehashMicroseconds(dict, 1000);
assert(dictSize(dict) == current_dict_used);
assert(DICTHT_SIZE(dict->ht_size_exp[0]) == new_dict_size);
assert(DICTHT_SIZE(dict->ht_size_exp[1]) == 0);
}

TEST("Restore to original state") {
dictEmpty(dict, NULL);
dictSetResizeEnabled(DICT_RESIZE_ENABLE);
}

start_benchmark();
for (j = 0; j < count; j++) {
retval = dictAdd(dict, stringFromLongLong(j), (void *)j);
assert(retval == DICT_OK);
}
end_benchmark("Inserting");
assert((long)dictSize(dict) == count);

/* Wait for rehashing. */
while (dictIsRehashing(dict)) {
dictRehashMicroseconds(dict, 100 * 1000);
}

start_benchmark();
for (j = 0; j < count; j++) {
char *key = stringFromLongLong(j);
dictEntry *de = dictFind(dict, key);
assert(de != NULL);
zfree(key);
}
end_benchmark("Linear access of existing elements");

start_benchmark();
for (j = 0; j < count; j++) {
char *key = stringFromLongLong(j);
dictEntry *de = dictFind(dict, key);
assert(de != NULL);
zfree(key);
}
end_benchmark("Linear access of existing elements (2nd round)");

start_benchmark();
for (j = 0; j < count; j++) {
char *key = stringFromLongLong(rand() % count);
dictEntry *de = dictFind(dict, key);
assert(de != NULL);
zfree(key);
}
end_benchmark("Random access of existing elements");

start_benchmark();
for (j = 0; j < count; j++) {
dictEntry *de = dictGetRandomKey(dict);
assert(de != NULL);
}
end_benchmark("Accessing random keys");

start_benchmark();
for (j = 0; j < count; j++) {
char *key = stringFromLongLong(rand() % count);
key[0] = 'X';
dictEntry *de = dictFind(dict, key);
assert(de == NULL);
zfree(key);
}
end_benchmark("Accessing missing");

start_benchmark();
for (j = 0; j < count; j++) {
char *key = stringFromLongLong(j);
retval = dictDelete(dict, key);
assert(retval == DICT_OK);
key[0] += 17; /* Change first number to letter. */
retval = dictAdd(dict, key, (void *)j);
assert(retval == DICT_OK);
}
end_benchmark("Removing and adding");
dictRelease(dict);
return 0;
}
#endif
4 changes: 0 additions & 4 deletions src/dict.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,4 @@ dictStats *dictGetStatsHt(dict *d, int htidx, int full);
void dictCombineStats(dictStats *from, dictStats *into);
void dictFreeStats(dictStats *stats);

#ifdef SERVER_TEST
int dictTest(int argc, char *argv[], int flags);
#endif

#endif /* __DICT_H */
1 change: 0 additions & 1 deletion src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -6698,7 +6698,6 @@ struct serverTest {
int failed;
} serverTests[] = {
{"quicklist", quicklistTest},
{"dict", dictTest},
};
serverTestProc *getTestProcByName(const char *name) {
int numtests = sizeof(serverTests) / sizeof(struct serverTest);
Expand Down
Loading

0 comments on commit 20d583f

Please sign in to comment.