diff --git a/src/Makefile.am b/src/Makefile.am index b21ea96..6d04ba2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -51,3 +51,8 @@ TESTS += resizable.test check_PROGRAMS += resizable.test resizable_test_SOURCES = resizable_test.c test.h resizable.h resizable_test_LDADD = libapfl.a + +TESTS += hashmap.test +check_PROGRAMS += hashmap.test +hashmap_test_SOURCES = hashmap_test.c test.h hashmap.h +hashmap_test_LDADD = libapfl.a diff --git a/src/hashmap_foo.c b/src/hashmap_foo.c deleted file mode 100644 index 8f2e06b..0000000 --- a/src/hashmap_foo.c +++ /dev/null @@ -1,142 +0,0 @@ -#include -#include -#include -#include - -#include "hashmap.h" - -bool keys_eq_impl(void *opaque, const void *_a, const void *_b) -{ - (void)opaque; - - const char * const *a = _a; - const char * const *b = _b; - return strcmp(*a, *b) == 0; -} - -apfl_hash calc_hash_impl(void *opaque, const void *_key) -{ - (void)opaque; - - const char * const *key = _key; - - return apfl_hash_fnv1a(*key, strlen(*key)); -} - -void destroy_kv_impl(void *opaque, void *_data) -{ - (void)opaque; - - char **data = _data; - free(*data); -} - -bool copy_kv_impl(void *opaque, void *_dest, const void *_src) -{ - (void)opaque; - - char **dest = _dest; - const char * const *src = _src; - - *dest = malloc(strlen(*src) + 1); - if (*dest == NULL) { - return false; - } - - strcpy(*dest, *src); - return true; -} - -#define BUFSIZE 10000 - -int -main(int argc, char **argv) -{ - (void)argc; - (void)argv; - - struct apfl_hashmap_callbacks callbacks = { - .opaque = NULL, - .keys_eq = keys_eq_impl, - .calc_hash = calc_hash_impl, - .destroy_key = destroy_kv_impl, - .destroy_value = destroy_kv_impl, - .copy_key = copy_kv_impl, - .copy_value = copy_kv_impl, - }; - - apfl_hashmap map = apfl_hashmap_new(callbacks, sizeof(char *), sizeof(char *)); - if (map == NULL) { - return 1; - } - - char line[BUFSIZE]; - - char key[BUFSIZE]; - char *keyp = &key[0]; - char value[BUFSIZE]; - char *valuep = &value[0]; - - char *retreived = NULL; - - for (;;) { - if (fgets(line, BUFSIZE, stdin) == NULL) { - break; - } - - char *tok = strtok(line, " "); - if (tok == NULL) { - continue; - } - - if (strcmp(tok, "set") == 0) { - tok = strtok(NULL, " \n"); - strcpy(key, tok); - - tok = strtok(NULL, " \n"); - strcpy(value, tok); - - if (!apfl_hashmap_set(map, &keyp, &valuep)) { - return 2; - } - } else if (strcmp(tok, "get") == 0 ) { - tok = strtok(NULL, " \n"); - strcpy(key, tok); - - if (apfl_hashmap_get(map, &keyp, &retreived)) { - printf("%s => %s\n", key, retreived); - free(retreived); - continue; - } - } else if (strcmp(tok, "del") == 0 ) { - tok = strtok(NULL, " \n"); - strcpy(key, tok); - - apfl_hashmap_delete(map, &keyp); - } else if (strcmp(tok, "list") == 0) { - apfl_hashmap_cursor cur = apfl_hashmap_get_cursor(map); - if (cur == NULL) { - return 3; - } - for (; !apfl_hashmap_cursor_is_end(cur); apfl_hashmap_cursor_next(cur)) { - char *k = NULL; - char *v = NULL; - - if ( - apfl_hashmap_cursor_get_key(cur, &k) - && apfl_hashmap_cursor_get_value(cur, &v) - ) { - printf("%s => %s\n", k, v); - } - free(k); - free(v); - } - - apfl_hashmap_cursor_destroy(cur); - } - } - - apfl_hashmap_destroy(map); - - return 0; -} diff --git a/src/hashmap_test.c b/src/hashmap_test.c new file mode 100644 index 0000000..7c89424 --- /dev/null +++ b/src/hashmap_test.c @@ -0,0 +1,621 @@ +#include + +#include "test.h" +#include "hashmap.h" + +static struct apfl_hashmap +init_int2int(testctx t) +{ + struct apfl_hashmap map; + if (!apfl_hashmap_init( + &map, + (struct apfl_hashmap_callbacks) { .opaque = NULL }, + sizeof(int), + sizeof(int) + )) { + test_fatalf(t, "Failed initializing int2int map!"); + } + return map; +} + +static void +assert_len(testctx t, struct apfl_hashmap map, size_t want, const char *label) +{ + size_t have = apfl_hashmap_count(map); + if (have != want) { + test_failf(t, "%s: expected count to be %d, got %d instead", label, want, have); + } +} + +static void +check_get_int2int(testctx t, struct apfl_hashmap *map, int k, int v) +{ + int have; + if (apfl_hashmap_get(map, &k, &have)) { + if (have != v) { + test_failf(t, "Got %d instead of %d for key %d", have, v, k); + } + } else { + test_failf(t, "Failed getting %d (expected %d)", k, v); + } +} + +static void +check_set_int2int(testctx t, struct apfl_hashmap *map, int k, int v) +{ + if (!apfl_hashmap_set(map, &k, &v)) { + test_fatalf(t, "Failed setting %d -> %d", k, v); + } + + check_get_int2int(t, map, k, v); +} + +static void +check_not_exists_int2int(testctx t, struct apfl_hashmap *map, int k) +{ + int v; + if (apfl_hashmap_get(map, &k, &v)) { + test_failf(t, "Expected key %d to not be set, but got value %d", k, v); + } +} + +static void +check_get_str2str(testctx t, struct apfl_hashmap *map, const char *k, const char *v) +{ + char *have; + if (apfl_hashmap_get(map, &k, &have)) { + if (strcmp(v, have) != 0) { + test_failf(t, "Got %s instead of %s for key %s", have, v, k); + } + } else { + test_failf(t, "Failed getting %s (expected %s)", k, v); + } + + free(have); +} + +static void +check_set_str2str(testctx t, struct apfl_hashmap *map, const char *k, const char *v) +{ + if (!apfl_hashmap_set(map, &k, &v)) { + test_fatalf(t, "Failed setting %s -> %s", k, v); + } + + check_get_str2str(t, map, k, v); +} + +static void +check_not_exists_str2str(testctx t, struct apfl_hashmap *map, const char *k) +{ + char *v; + if (apfl_hashmap_get(map, &k, &v)) { + test_failf(t, "Expected key %s to not be set, but got value %s", k, v); + free(v); + } +} + +static bool +str2str_keys_eq(void *_opaque, const void *_a, const void *_b) +{ + testctx t = _opaque; + const char * const *a = _a; + const char * const *b = _b; + + if (a == b) { + test_fatalf(t, "keys_eq callback: a == b, this should not happen!"); + } + if (*a == *b) { + test_fatalf(t, "keys_eq callback: *a == *b, this should not happen!"); + } + + return strcmp(*a, *b) == 0; +} + +static apfl_hash +str2str_calc_hash(void *opaque, const void *_key) +{ + (void)opaque; + const char * const *key = _key; + + return apfl_hash_fnv1a(*key, strlen(*key)); +} + +static void +destroy_str_kv(char **strptr) +{ + free(*strptr); + *strptr = NULL; +} + +static void +str2str_destroy_key(void *opaque, void *_key) +{ + (void)opaque; + destroy_str_kv(_key); +} + +static void +str2str_destroy_value(void *opaque, void *_value) +{ + (void)opaque; + destroy_str_kv(_value); +} + +static void +copy_str_kv(testctx t, char **dest, char **src) +{ + size_t len = strlen(*src); + *dest = must_alloc(t, len + 1); + memcpy(*dest, *src, len + 1); +} + +static void +str2str_copy_key(void *_opaque, void *_dest, void *_src) +{ + testctx t = _opaque; + copy_str_kv(t, _dest, _src); +} + +static void +str2str_copy_value(void *_opaque, void *_dest, void *_src) +{ + testctx t = _opaque; + copy_str_kv(t, _dest, _src); +} + +static struct apfl_hashmap +init_str2str(testctx t) +{ + struct apfl_hashmap map; + if (!apfl_hashmap_init( + &map, + (struct apfl_hashmap_callbacks) { + .opaque = t, + .keys_eq = str2str_keys_eq, + .calc_hash = str2str_calc_hash, + .destroy_key = str2str_destroy_key, + .destroy_value = str2str_destroy_value, + .copy_key = str2str_copy_key, + .copy_value = str2str_copy_value, + }, + sizeof(char *), + sizeof(char *) + )) { + test_fatalf(t, "Failed initializing str2str map!"); + } + return map; +} + +TEST(int2int_set_get_delete, t) { + struct apfl_hashmap map = init_int2int(t); + assert_len(t, map, 0, "after init"); + + int k = 1337; + check_set_int2int(t, &map, k, 666); + assert_len(t, map, 1, "after setting"); + + apfl_hashmap_delete(&map, &k); + + check_not_exists_int2int(t, &map, k); + + assert_len(t, map, 0, "after deleting"); + + apfl_hashmap_deinit(&map); +} + +TEST(int2int_set_overwrite, t) { + struct apfl_hashmap map = init_int2int(t); + assert_len(t, map, 0, "after init"); + + check_set_int2int(t, &map, 42, 1337); + assert_len(t, map, 1, "after setting"); + + check_set_int2int(t, &map, 42, 666); + assert_len(t, map, 1, "after setting again"); + + apfl_hashmap_deinit(&map); +} + +TEST(int2int_set_many, t) { + struct apfl_hashmap map = init_int2int(t); + assert_len(t, map, 0, "after init"); + + check_set_int2int(t, &map, 42, 1337); + assert_len(t, map, 1, "after setting 42"); + + check_set_int2int(t, &map, 123, 456); + assert_len(t, map, 2, "after setting 123"); + + check_set_int2int(t, &map, 42, 666); + assert_len(t, map, 2, "after setting 42 again"); + + check_set_int2int(t, &map, 1, 2); + assert_len(t, map, 3, "after setting 1"); + + check_set_int2int(t, &map, 0, 0); + assert_len(t, map, 4, "after setting 0"); + + // Let's check, if they are still in the map + check_get_int2int(t, &map, 42, 666); + check_get_int2int(t, &map, 123, 456); + check_get_int2int(t, &map, 1, 2); + check_get_int2int(t, &map, 0, 0); + + apfl_hashmap_deinit(&map); +} + +#define ITERATING_SWITCH_CASE(t, key, seen, value_want, value_have) \ + case key: \ + if (seen) { \ + test_failf( \ + t, \ + "already seen key %d, got it again (with value=%d)", \ + key, \ + value_have \ + ); \ + } else { \ + seen = true; \ + if (value_have != value_want) { \ + test_failf( \ + t, \ + "got key %d but with wrong value: want=%d, have=%d", \ + key, \ + value_want, \ + value_have \ + ); \ + } \ + } \ + break; + +TEST(int2int_iterating, t) { + struct apfl_hashmap map = init_int2int(t); + + check_set_int2int(t, &map, 123, 456); + check_set_int2int(t, &map, 42, 666); + check_set_int2int(t, &map, 1, 2); + check_set_int2int(t, &map, 0, 0); + + bool seen_123 = false; + bool seen_42 = false; + bool seen_1 = false; + bool seen_0 = false; + + for ( + struct apfl_hashmap_cursor it = apfl_hashmap_get_cursor(&map); + !apfl_hashmap_cursor_is_end(it); + apfl_hashmap_cursor_next(&it) + ) { + int k, v; + + apfl_hashmap_cursor_get_key(it, &k); + apfl_hashmap_cursor_get_value(it, &v); + + switch (k) { + ITERATING_SWITCH_CASE(t, 123, seen_123, 456, v) + ITERATING_SWITCH_CASE(t, 42, seen_42, 666, v) + ITERATING_SWITCH_CASE(t, 1, seen_1, 2, v) + ITERATING_SWITCH_CASE(t, 0, seen_0, 0, v) + default: + test_failf(t, "Seen unexpected key %d with value %d", k, v); + break; + } + } + + if (!seen_123) { + test_failf(t, "Did not see key 123"); + } + if (!seen_42) { + test_failf(t, "Did not see key 42"); + } + if (!seen_1) { + test_failf(t, "Did not see key 1"); + } + if (!seen_0) { + test_failf(t, "Did not see key 0"); + } + + apfl_hashmap_deinit(&map); +} + +TEST(int2int_moving, t) { + struct apfl_hashmap orig = init_int2int(t); + + check_set_int2int(t, &orig, 011, 899); + check_set_int2int(t, &orig, 988, 199); + check_set_int2int(t, &orig, 911, 972); + check_set_int2int(t, &orig, 5, 3); + + struct apfl_hashmap new = apfl_hashmap_move(&orig); + + assert_len(t, new, 4, "after moving"); + check_get_int2int(t, &new, 011, 899); + check_get_int2int(t, &new, 988, 199); + check_get_int2int(t, &new, 911, 972); + check_get_int2int(t, &new, 5, 3); + + apfl_hashmap_deinit(&new); + apfl_hashmap_deinit(&orig); // not neccessary, but should still work without crashing +} + +TEST(int2int_copying, t) { + struct apfl_hashmap orig = init_int2int(t); + + check_set_int2int(t, &orig, 1, 111); + check_set_int2int(t, &orig, 2, 222); + check_set_int2int(t, &orig, 3, 333); + check_set_int2int(t, &orig, 4, 444); + + struct apfl_hashmap copy; + if (!apfl_hashmap_copy(©, orig)) { + test_fatalf(t, "Copying failed"); + } + + assert_len(t, orig, 4, "orig after moving"); + check_get_int2int(t, &orig, 1, 111); + check_get_int2int(t, &orig, 2, 222); + check_get_int2int(t, &orig, 3, 333); + check_get_int2int(t, &orig, 4, 444); + + assert_len(t, copy, 4, "copy after moving"); + check_get_int2int(t, ©, 1, 111); + check_get_int2int(t, ©, 2, 222); + check_get_int2int(t, ©, 3, 333); + check_get_int2int(t, ©, 4, 444); + + // Setting something in orig should not influence the copy + check_set_int2int(t, &orig, 5, 555); + check_not_exists_int2int(t, ©, 5); + + // And the other way around too + check_set_int2int(t, ©, 6, 666); + check_not_exists_int2int(t, &orig, 6); + + // Overwriting something in orig should not influence the copy + check_set_int2int(t, &orig, 1, 1337); + check_get_int2int(t, ©, 1, 111); + + // And the other way around too + check_set_int2int(t, ©, 2, 1337); + check_get_int2int(t, &orig, 2, 222); + + int k; + // Deleting something in orig should not influence the copy + k = 3; + apfl_hashmap_delete(&orig, &k); + check_get_int2int(t, ©, 3, 333); + + // And the other way around too + k = 4; + apfl_hashmap_delete(©, &k); + check_get_int2int(t, &orig, 4, 444); + + apfl_hashmap_deinit(©); + apfl_hashmap_deinit(&orig); +} + +TEST(str2str_set_get_delete, t) { + struct apfl_hashmap map = init_str2str(t); + assert_len(t, map, 0, "after init"); + + const char *k = "foo"; + check_set_str2str(t, &map, k, "bar"); + assert_len(t, map, 1, "after setting"); + + apfl_hashmap_delete(&map, &k); + + check_not_exists_str2str(t, &map, k); + + assert_len(t, map, 0, "after deleting"); + + apfl_hashmap_deinit(&map); +} + +TEST(str2str_set_overwrite, t) { + struct apfl_hashmap map = init_str2str(t); + assert_len(t, map, 0, "after init"); + + check_set_str2str(t, &map, "foo", "bar"); + assert_len(t, map, 1, "after setting"); + + check_set_str2str(t, &map, "foo", "baz"); + assert_len(t, map, 1, "after setting again"); + + apfl_hashmap_deinit(&map); +} + +TEST(str2str_set_many, t) { + struct apfl_hashmap map = init_str2str(t); + assert_len(t, map, 0, "after init"); + + check_set_str2str(t, &map, "foo", "abc"); + assert_len(t, map, 1, "after setting foo"); + + check_set_str2str(t, &map, "bar", "def"); + assert_len(t, map, 2, "after setting bar"); + + check_set_str2str(t, &map, "foo", "ghi"); + assert_len(t, map, 2, "after setting foo again"); + + check_set_str2str(t, &map, "baz", "jkl"); + assert_len(t, map, 3, "after setting 1"); + + check_set_str2str(t, &map, "", ""); + assert_len(t, map, 4, "after setting \"\""); + + // Let's check, if they are still in the map + check_get_str2str(t, &map, "foo", "ghi"); + check_get_str2str(t, &map, "bar", "def"); + check_get_str2str(t, &map, "baz", "jkl"); + check_get_str2str(t, &map, "", ""); + + apfl_hashmap_deinit(&map); +} + +#define ITERATING_SEEN_CHECK(t, key, seen, value_want, value_have) \ + if (seen) { \ + test_failf( \ + t, \ + "already seen key %s, got it again (with value=%s)", \ + key, \ + value_have \ + ); \ + } else { \ + seen = true; \ + if (strcmp(value_have, value_want) != 0) { \ + test_failf( \ + t, \ + "got key %s but with wrong value: want=%s, have=%s", \ + key, \ + value_want, \ + value_have \ + ); \ + } \ + } \ + +TEST(str2str_iterating, t) { + struct apfl_hashmap map = init_str2str(t); + + check_set_str2str(t, &map, "foo", "abc"); + check_set_str2str(t, &map, "bar", "def"); + check_set_str2str(t, &map, "baz", "ghi"); + check_set_str2str(t, &map, "", ""); + + bool seen_foo = false; + bool seen_bar = false; + bool seen_baz = false; + bool seen_blank = false; + + for ( + struct apfl_hashmap_cursor it = apfl_hashmap_get_cursor(&map); + !apfl_hashmap_cursor_is_end(it); + apfl_hashmap_cursor_next(&it) + ) { + char *k; + char *v; + + apfl_hashmap_cursor_get_key(it, &k); + apfl_hashmap_cursor_get_value(it, &v); + + if (strcmp(k, "foo") == 0) { + ITERATING_SEEN_CHECK(t, "foo", seen_foo, "abc", v) + } else if (strcmp(k, "bar") == 0) { + ITERATING_SEEN_CHECK(t, "bar", seen_bar, "def", v) + } else if (strcmp(k, "baz") == 0) { + ITERATING_SEEN_CHECK(t, "baz", seen_baz, "ghi", v) + } else if (strcmp(k, "") == 0) { + ITERATING_SEEN_CHECK(t, "", seen_blank, "", v) + } else { + test_failf(t, "Seen unexpected key %s with value %s", k, v); + } + + free(k); + free(v); + } + + if (!seen_foo) { + test_failf(t, "Did not see key foo"); + } + if (!seen_bar) { + test_failf(t, "Did not see key bar"); + } + if (!seen_baz) { + test_failf(t, "Did not see key baz"); + } + if (!seen_blank) { + test_failf(t, "Did not see key \"\""); + } + + apfl_hashmap_deinit(&map); +} + +TEST(str2str_moving, t) { + struct apfl_hashmap orig = init_str2str(t); + + check_set_str2str(t, &orig, "foo", "abc"); + check_set_str2str(t, &orig, "bar", "def"); + check_set_str2str(t, &orig, "baz", "ghi"); + check_set_str2str(t, &orig, "quux", "jkl"); + + struct apfl_hashmap new = apfl_hashmap_move(&orig); + + assert_len(t, new, 4, "after moving"); + check_get_str2str(t, &new, "foo", "abc"); + check_get_str2str(t, &new, "bar", "def"); + check_get_str2str(t, &new, "baz", "ghi"); + check_get_str2str(t, &new, "quux", "jkl"); + + apfl_hashmap_deinit(&new); + apfl_hashmap_deinit(&orig); // not neccessary, but should still work without crashing +} + +TEST(str2str_copying, t) { + struct apfl_hashmap orig = init_str2str(t); + + check_set_str2str(t, &orig, "foo", "abc"); + check_set_str2str(t, &orig, "bar", "def"); + check_set_str2str(t, &orig, "baz", "ghi"); + check_set_str2str(t, &orig, "quux", "jkl"); + + struct apfl_hashmap copy; + if (!apfl_hashmap_copy(©, orig)) { + test_fatalf(t, "Copying failed"); + } + + assert_len(t, orig, 4, "orig after moving"); + check_get_str2str(t, &orig, "foo", "abc"); + check_get_str2str(t, &orig, "bar", "def"); + check_get_str2str(t, &orig, "baz", "ghi"); + check_get_str2str(t, &orig, "quux", "jkl"); + + assert_len(t, copy, 4, "copy after moving"); + check_get_str2str(t, ©, "foo", "abc"); + check_get_str2str(t, ©, "bar", "def"); + check_get_str2str(t, ©, "baz", "ghi"); + check_get_str2str(t, ©, "quux", "jkl"); + + // Setting something in orig should not influence the copy + check_set_str2str(t, &orig, "X", "xxx"); + check_not_exists_str2str(t, ©, "X"); + + // And the other way around too + check_set_str2str(t, ©, "Y", "yyy"); + check_not_exists_str2str(t, &orig, "Y"); + + // Overwriting something in orig should not influence the copy + check_set_str2str(t, &orig, "foo", "newfoo"); + check_get_str2str(t, ©, "foo", "abc"); + + // And the other way around too + check_set_str2str(t, ©, "bar", "newbar"); + check_get_str2str(t, &orig, "bar", "def"); + + const char *k; + // Deleting something in orig should not influence the copy + k = "baz"; + apfl_hashmap_delete(&orig, &k); + check_get_str2str(t, ©, k, "ghi"); + + // And the other way around too + k = "quux"; + apfl_hashmap_delete(©, &k); + check_get_str2str(t, &orig, k, "jkl"); + + apfl_hashmap_deinit(©); + apfl_hashmap_deinit(&orig); +} + +TESTS_BEGIN + ADDTEST(int2int_set_get_delete), + ADDTEST(int2int_set_overwrite), + ADDTEST(int2int_set_many), + ADDTEST(int2int_iterating), + ADDTEST(int2int_moving), + ADDTEST(int2int_copying), + + ADDTEST(str2str_set_get_delete), + ADDTEST(str2str_set_overwrite), + ADDTEST(str2str_set_many), + ADDTEST(str2str_iterating), + ADDTEST(str2str_moving), + ADDTEST(str2str_copying), +TESTS_END