diff --git a/tests/Makefile.am b/tests/Makefile.am index 5bcb987..a80c016 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -8,7 +8,8 @@ EXTRA_DIST = package.m4 testsuite.at $(TESTSUITE) $(TESTS_) -TESTS_ = lib_hash.at lib_prng.at lib_watcher.at lib_sandbox.at +TESTS_ = lib_hash.at lib_prng.at lib_watcher.at lib_sandbox.at \ + lib_hashmap.at # Helper programs for testing @@ -20,7 +21,8 @@ if BOOKMARKFS_UTIL check_util_lib_CPPFLAGS = -I$(top_srcdir)/src check_util_lib_LDADD = $(top_builddir)/src/libbookmarkfs_util.la - check_util_lib_SOURCES = check_lib.c check_watcher.c check_sandbox.c + check_util_lib_SOURCES = check_lib.c check_watcher.c check_sandbox.c \ + check_hashmap.c endif # BOOKMARKFS_UTIL # Autotest setup diff --git a/tests/check_hashmap.c b/tests/check_hashmap.c new file mode 100644 index 0000000..614928c --- /dev/null +++ b/tests/check_hashmap.c @@ -0,0 +1,213 @@ +/** + * bookmarkfs/tests/check_hashmap.c + * ---- + * + * Copyright (C) 2025 CismonX + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include +#include + +#include "check_lib.h" +#include "frontend_util.h" +#include "hashmap.h" +#include "prng.h" + +struct check_item { + unsigned long id; + unsigned long val; +}; + +// Forward declaration start +static int check_one_round (struct hashmap *, struct check_item *, size_t); +static int do_check_hashmap (int, int); +static int item_comp_func (union hashmap_key, void const *); +static unsigned long + item_hash_func (void const *); +static void item_walk_func (void *, void *); +// Forward declaration end + +static int +check_one_round ( + struct hashmap *map, + struct check_item *items, + size_t items_cnt +) { + for (size_t i = 0; i < items_cnt; ++i) { + struct check_item *item = items + (prng_rand() % items_cnt); + unsigned long id = item->id; + unsigned long val = item->val; + + union hashmap_key key = { .u64 = id }; + unsigned long entry_id; + void *found = hashmap_search(map, key, val, &entry_id); + + if (id & 1) { + if (found != item) { + log_puts("unexpected item in hashmap"); + return -1; + } + hashmap_delete(map, item, entry_id); + } else { + if (found != NULL) { + log_puts("unexpected item in hashmap"); + return -1; + } + hashmap_insert(map, val, item); + } + item->id = id ^ 1; + } + return 0; +} + +int +do_check_hashmap ( + int n, + int rounds +) { + size_t items_cnt = 1u << n; + size_t buf_size = sizeof(struct check_item) * items_cnt; + struct check_item *items = mmap(NULL, buf_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (items == MAP_FAILED) { + return -1; + } + struct hashmap *map = hashmap_create(item_comp_func, item_hash_func); + + for (size_t i = 0; i < items_cnt; ++i) { + items[i] = (struct check_item) { + .id = i << 1, + .val = prng_rand(), + }; + } + + int status = -1; + for (int i = 0; i < rounds; ++i) { + if (0 != check_one_round(map, items, items_cnt)) { + goto end; + } + } + + size_t cnt = 0; + hashmap_foreach(map, item_walk_func, &cnt); + + for (size_t i = 0; i < items_cnt; ++i) { + struct check_item *item = items + i; + unsigned long id = item->id; + + if (id & 1) { + --cnt; + hashmap_delete(map, item, -1); + } + } + if (cnt != 0) { + log_printf("bad item cnt %zu, expected 0", cnt); + goto end; + } + + hashmap_foreach(map, item_walk_func, &cnt); + if (cnt != 0) { + log_printf("bad item cnt %zu, expected 0", cnt); + goto end; + } + status = 0; + + end: + munmap(items, buf_size); + hashmap_destroy(map); + return status; +} + +static int +item_comp_func ( + union hashmap_key key, + void const *entry +) { + struct check_item const *item = entry; + + return (key.u64 >> 1) - (item->id >> 1); +} + +static unsigned long +item_hash_func ( + void const *entry +) { + struct check_item const *item = entry; + + return item->val; +} + +static void +item_walk_func ( + void *user_data, + void *UNUSED_VAR(entry) +) { + size_t *cnt_ptr = user_data; + + ++(*cnt_ptr); +} + +int +check_hashmap ( + int argc, + char *argv[] +) { + static uint64_t buf[4]; + uint64_t *seed = NULL; + + int n = -1; + int r = -1; + + getopt_foreach(argc, argv, ":s:n:r:") { + case 's': + if (0 != prng_seed_from_hex(buf, optarg)) { + return -1; + } + seed = buf; + break; + + case 'n': + n = atoi(optarg); + break; + + case 'r': + r = atoi(optarg); + break; + + default: + log_printf("bad option '-%c'", optopt); + return -1; + } + if (n < 10 || n > 30) { + log_printf("bad size %d", n); + return -1; + } + if (r < 0) { + log_printf("bad rounds cnt %d", r); + return -1; + } + + if (0 != prng_seed(seed)) { + return -1; + } + return do_check_hashmap(n, r); +} diff --git a/tests/check_lib.c b/tests/check_lib.c index 7a30e3f..a8fa08f 100644 --- a/tests/check_lib.c +++ b/tests/check_lib.c @@ -61,6 +61,8 @@ dispatch_subcmds ( status = check_watcher(argc, argv); } else if (0 == strcmp("sandbox", cmd)) { status = check_sandbox(argc, argv); + } else if (0 == strcmp("hashmap", cmd)) { + status = check_hashmap(argc, argv); } else { log_printf("bad subcmd '%s'", cmd); } diff --git a/tests/check_lib.h b/tests/check_lib.h index 2fa2b47..f35fb88 100644 --- a/tests/check_lib.h +++ b/tests/check_lib.h @@ -35,6 +35,12 @@ action_if_false \ } while (0) +int +check_hashmap ( + int argc, + char *argv[] +); + int check_sandbox ( int argc, diff --git a/tests/lib_hashmap.at b/tests/lib_hashmap.at new file mode 100644 index 0000000..2518d7d --- /dev/null +++ b/tests/lib_hashmap.at @@ -0,0 +1,35 @@ +dnl +dnl Copyright (C) 2025 CismonX +dnl +dnl Copying and distribution of this file, with or without modification, +dnl are permitted in any medium without royalty, +dnl provided the copyright notice and this notice are preserved. +dnl This file is offered as-is, without any warranty. +dnl + +AT_SETUP([util lib: hashmap]) +AT_KEYWORDS([lib hashmap]) + +ATX_CHECK_LIB([ + seed="${CHECK_HASHMAP_PRNG_SEED}" + if test -z "$seed"; then + seed=$(ath_fn_prng_seed) + fi + + size="${CHECK_HASHMAP_DATA_SIZE}" + if test -z "$size"; then + # Requires at least 128 MiB of memory on a 64-bit platform. + size=22 + fi + + rounds="${CHECK_HASHMAP_ROUNDS}" + if test -z "$rounds"; then + rounds=5 + fi + + ATX_RUN([ + check-util-lib hashmap -s "$seed" -n "$size" -r "$rounds" + ]) +]) + +AT_CLEANUP diff --git a/tests/testsuite.at b/tests/testsuite.at index 60a95cd..0c327d7 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -107,3 +107,4 @@ m4_include([lib_hash.at]) m4_include([lib_prng.at]) m4_include([lib_watcher.at]) m4_include([lib_sandbox.at]) +m4_include([lib_hashmap.at])