#include #include #include "test.h" #include "hashmap.h" static struct apfl_hashmap init_int2int(testctx t) { struct apfl_hashmap map; if (!apfl_hashmap_init( &map, test_allocator(t), (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, test_allocator(t), (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; HASHMAP_EACH(&map, it) { int k, v; assert(apfl_hashmap_cursor_get_key(it, &k)); assert(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; HASHMAP_EACH(&map, it) { char *k; char *v; assert(apfl_hashmap_cursor_get_key(it, &k)); assert(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