apfl/src/hashmap_test.c
Laria Carolin Chabowski 0ab36ec37e hashmap: Add test cases
Also get rid of the silly hashmap_foo demo program.
2022-01-22 15:57:10 +01:00

621 lines
18 KiB
C

#include <string.h>
#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(&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, &copy, 1, 111);
check_get_int2int(t, &copy, 2, 222);
check_get_int2int(t, &copy, 3, 333);
check_get_int2int(t, &copy, 4, 444);
// Setting something in orig should not influence the copy
check_set_int2int(t, &orig, 5, 555);
check_not_exists_int2int(t, &copy, 5);
// And the other way around too
check_set_int2int(t, &copy, 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, &copy, 1, 111);
// And the other way around too
check_set_int2int(t, &copy, 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, &copy, 3, 333);
// And the other way around too
k = 4;
apfl_hashmap_delete(&copy, &k);
check_get_int2int(t, &orig, 4, 444);
apfl_hashmap_deinit(&copy);
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(&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, &copy, "foo", "abc");
check_get_str2str(t, &copy, "bar", "def");
check_get_str2str(t, &copy, "baz", "ghi");
check_get_str2str(t, &copy, "quux", "jkl");
// Setting something in orig should not influence the copy
check_set_str2str(t, &orig, "X", "xxx");
check_not_exists_str2str(t, &copy, "X");
// And the other way around too
check_set_str2str(t, &copy, "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, &copy, "foo", "abc");
// And the other way around too
check_set_str2str(t, &copy, "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, &copy, k, "ghi");
// And the other way around too
k = "quux";
apfl_hashmap_delete(&copy, &k);
check_get_str2str(t, &orig, k, "jkl");
apfl_hashmap_deinit(&copy);
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