tests/test_hash_map.c

Sun, 15 Dec 2024 14:32:51 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 15 Dec 2024 14:32:51 +0100
changeset 1015
a0922b925d2a
parent 994
3603bdf4a78b
permissions
-rw-r--r--

increase test coverage

relates to #431

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2023 Mike Becker, Olaf Wintermann All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "cx/test.h"
#include "util_allocator.h"

#include "cx/hash_map.h"

CX_TEST(test_hash_map_create) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *allocator = &talloc.base;
    CX_TEST_DO {
        CxMap *map = cxHashMapCreate(allocator, 1, 0);
        struct cx_hash_map_s *hmap = (struct cx_hash_map_s *) map;
        CX_TEST_ASSERT(hmap->bucket_count > 0);
        for(size_t i = 0 ; i < hmap->bucket_count ; i++) {
            CX_TEST_ASSERT(hmap->buckets[i] == NULL);
        }
        CX_TEST_ASSERT(map->collection.elem_size == 1);
        CX_TEST_ASSERT(map->collection.size == 0);
        CX_TEST_ASSERT(map->collection.allocator == allocator);
        CX_TEST_ASSERT(!map->collection.store_pointer);
        CX_TEST_ASSERT(map->collection.cmpfunc == NULL);
        CX_TEST_ASSERT(map->collection.simple_destructor == NULL);
        CX_TEST_ASSERT(map->collection.advanced_destructor == NULL);
        CX_TEST_ASSERT(map->collection.destructor_data == NULL);
        cxMapStorePointers(map);
        CX_TEST_ASSERT(map->collection.store_pointer);
        CX_TEST_ASSERT(map->collection.elem_size == sizeof(void *));
        cxMapStoreObjects(map);
        CX_TEST_ASSERT(!map->collection.store_pointer);

        cxMapFree(map);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_hash_map_create_store_pointers) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *allocator = &talloc.base;
    CX_TEST_DO {
        CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 0);
        struct cx_hash_map_s *hmap = (struct cx_hash_map_s *) map;
        CX_TEST_ASSERT(hmap->bucket_count > 0);
        for (size_t i = 0; i < hmap->bucket_count; i++) {
            CX_TEST_ASSERT(hmap->buckets[i] == NULL);
        }
        CX_TEST_ASSERT(map->collection.size == 0);
        CX_TEST_ASSERT(map->collection.allocator == allocator);
        CX_TEST_ASSERT(map->collection.store_pointer);
        CX_TEST_ASSERT(map->collection.elem_size == sizeof(void *));

        cxMapFree(map);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_hash_map_rehash) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *allocator = &talloc.base;
    CX_TEST_DO {
        CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 7);

        cxMapPut(map, "key 1", (void *) "val 1");
        cxMapPut(map, "key 2", (void *) "val 2");
        cxMapPut(map, "key 3", (void *) "val 3");
        cxMapPut(map, "foo 4", (void *) "val 4");
        cxMapPut(map, "key 5", (void *) "val 5");
        cxMapPut(map, "key 6", (void *) "val 6");
        cxMapPut(map, "bar 7", (void *) "val 7");
        cxMapPut(map, "key 8", (void *) "val 8");
        cxMapPut(map, "key 9", (void *) "val 9");
        cxMapPut(map, "key 10", (void *) "val 10");

        CX_TEST_ASSERT(((struct cx_hash_map_s *)map)->bucket_count == 7);
        int result = cxMapRehash(map);
        CX_TEST_ASSERT(result == 0);
        CX_TEST_ASSERT(((struct cx_hash_map_s *)map)->bucket_count == 25);
        CX_TEST_ASSERT(map->collection.size == 10);

        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "key 1"), "val 1"));
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "key 2"), "val 2"));
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "key 3"), "val 3"));
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "foo 4"), "val 4"));
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "key 5"), "val 5"));
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "key 6"), "val 6"));
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "bar 7"), "val 7"));
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "key 8"), "val 8"));
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "key 9"), "val 9"));
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "key 10"), "val 10"));

        cxMapFree(map);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_hash_map_rehash_not_required) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *allocator = &talloc.base;
    CX_TEST_DO {
        CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 8);

        cxMapPut(map, "key 1", (void *) "val 1");
        cxMapPut(map, "key 2", (void *) "val 2");
        cxMapPut(map, "key 3", (void *) "val 3");
        cxMapPut(map, "key 4", (void *) "val 4");
        cxMapPut(map, "key 5", (void *) "val 5");
        cxMapPut(map, "key 6", (void *) "val 6");

        // 6/8 does not exceed 0.75, therefore the function should not rehash
        int result = cxMapRehash(map);
        CX_TEST_ASSERT(result == 0);
        CX_TEST_ASSERT(((struct cx_hash_map_s *)map)->bucket_count == 8);

        cxMapFree(map);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_hash_map_clear) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *allocator = &talloc.base;
    CX_TEST_DO {
        CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 0);

        cxMapPut(map, "key 1", (void *) "val 1");
        cxMapPut(map, "key 2", (void *) "val 2");
        cxMapPut(map, "key 3", (void *) "val 3");

        CX_TEST_ASSERT(map->collection.size == 3);

        cxMapClear(map);

        CX_TEST_ASSERT(map->collection.size == 0);
        CX_TEST_ASSERT(cxMapGet(map, "key 1") == NULL);
        CX_TEST_ASSERT(cxMapGet(map, "key 2") == NULL);
        CX_TEST_ASSERT(cxMapGet(map, "key 3") == NULL);

        cxMapFree(map);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_hash_map_store_ucx_strings) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *allocator = &talloc.base;
    CX_TEST_DO {
        // create the map
        CxMap *map = cxHashMapCreate(allocator, sizeof(cxstring), 8);

        // define some strings
        cxstring s1 = CX_STR("this");
        cxstring s2 = CX_STR("is");
        cxstring s3 = CX_STR("a");
        cxstring s4 = CX_STR("test");
        cxstring s5 = CX_STR("setup");

        // put them into the map
        cxMapPut(map, "s1", &s1);
        cxMapPut(map, "s2", &s2);
        cxMapPut(map, "s3", &s3);
        cxMapPut(map, "s4", &s4);

        // overwrite a value
        cxMapPut(map, "s1", &s5);

        // look up a string
        cxstring * s3p = cxMapGet(map, "s3");
        CX_TEST_ASSERT(s3p->length == s3.length);
        CX_TEST_ASSERT(s3p->ptr == s3.ptr);
        CX_TEST_ASSERT(s3p !=  &s3);

        // remove a string
        cxstring ret = {0};
        CX_TEST_ASSERT(0 == cxMapRemoveAndGet(map, "s2", &ret));
        CX_TEST_ASSERT(map->collection.size == 3);
        CX_TEST_ASSERT(0 == cx_strcmp(ret, cx_str("is")));

        // iterate
        bool s3found = false, s4found = false, s5found = false;
        CxIterator iter = cxMapIteratorValues(map);
        cx_foreach(cxstring*, s, iter) {
            s3found |= s3.ptr == s->ptr;
            s4found |= s4.ptr == s->ptr;
            s5found |= s5.ptr == s->ptr;
        }
        CX_TEST_ASSERT(s3found && s4found && s5found);

        cxMapFree(map);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_hash_map_remove_via_iterator) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *allocator = &talloc.base;
    CX_TEST_DO {
        CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 4);

        cxMapPut(map, "key 1", (void *) "val 1");
        cxMapPut(map, "key 2", (void *) "val 2");
        cxMapPut(map, "key 3", (void *) "val 3");
        cxMapPut(map, "key 4", (void *) "val 4");
        cxMapPut(map, "key 5", (void *) "val 5");
        cxMapPut(map, "key 6", (void *) "val 6");

        CxIterator iter = cxMapMutIterator(map);
        cx_foreach(CxMapEntry*, entry, iter) {
            if (((const char *)entry->key->data)[4] % 2 == 1) cxIteratorFlagRemoval(iter);
        }
        CX_TEST_ASSERT(map->collection.size == 3);
        CX_TEST_ASSERT(iter.index == map->collection.size);

        CX_TEST_ASSERT(cxMapGet(map, "key 1") == NULL);
        CX_TEST_ASSERT(cxMapGet(map, "key 2") != NULL);
        CX_TEST_ASSERT(cxMapGet(map, "key 3") == NULL);
        CX_TEST_ASSERT(cxMapGet(map, "key 4") != NULL);
        CX_TEST_ASSERT(cxMapGet(map, "key 5") == NULL);
        CX_TEST_ASSERT(cxMapGet(map, "key 6") != NULL);

        cxMapFree(map);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

static void test_simple_destructor(void *data) {
    strcpy(data, "OK");
}

static void test_advanced_destructor(
        cx_attr_unused void *unused,
        void *data
) {
    strcpy(data, "OK");
}

static CX_TEST_SUBROUTINE(verify_any_destructor, CxMap *map) {
    CxHashKey k1 = cx_hash_key_str("key 1");
    CxHashKey k2 = cx_hash_key_str("key 2");
    CxHashKey k3 = cx_hash_key_str("key 3");
    CxHashKey k4 = cx_hash_key_str("key 4");
    CxHashKey k5 = cx_hash_key_str("key 5");

    char v1[] = "val 1";
    char v2[] = "val 2";
    char v3[] = "val 3";
    char v4[] = "val 4";
    char v5[] = "val 5";

    cxMapPut(map, k1, v1);
    cxMapPut(map, k2, v2);
    cxMapPut(map, k3, v3);
    cxMapPut(map, k4, v4);

    CX_TEST_ASSERT(0 == cxMapRemove(map, k2));
    char *r;
    CX_TEST_ASSERT(0 == cxMapRemoveAndGet(map, k3, &r));

    CX_TEST_ASSERT(0 == strcmp(v1, "val 1"));
    CX_TEST_ASSERT(0 == strcmp(v2, "OK"));
    CX_TEST_ASSERT(0 == strcmp(v3, "val 3"));
    CX_TEST_ASSERT(0 == strcmp(v4, "val 4"));
    CX_TEST_ASSERT(0 == strcmp(v5, "val 5"));
    CX_TEST_ASSERT(r == v3);

    cxMapClear(map);

    CX_TEST_ASSERT(0 == strcmp(v1, "val 1"));
    CX_TEST_ASSERT(0 == strcmp(v2, "OK"));
    CX_TEST_ASSERT(0 == strcmp(v3, "val 3"));
    CX_TEST_ASSERT(0 == strcmp(v4, "OK"));
    CX_TEST_ASSERT(0 == strcmp(v5, "val 5"));

    cxMapPut(map, k1, (void *) v1);
    cxMapPut(map, k3, (void *) v3);
    cxMapPut(map, k5, (void *) v5);

    {
        CxIterator iter = cxMapMutIteratorKeys(map);
        cx_foreach(CxHashKey*, key, iter) {
            if (((char*)key->data)[4] == '1') cxIteratorFlagRemoval(iter);
        }
    }
    {
        CxIterator iter = cxMapMutIteratorValues(map);
        cx_foreach(char*, v, iter) {
            if (v[4] == '5') cxIteratorFlagRemoval(iter);
        }
    }

    CX_TEST_ASSERT(0 == strcmp(v1, "OK"));
    CX_TEST_ASSERT(0 == strcmp(v2, "OK"));
    CX_TEST_ASSERT(0 == strcmp(v3, "val 3"));
    CX_TEST_ASSERT(0 == strcmp(v4, "OK"));
    CX_TEST_ASSERT(0 == strcmp(v5, "OK"));

    v1[0] = v2[0] = v4[0] = v5[0] = 'c';

    cxMapFree(map);

    CX_TEST_ASSERT(0 == strcmp(v1, "cK"));
    CX_TEST_ASSERT(0 == strcmp(v2, "cK"));
    CX_TEST_ASSERT(0 == strcmp(v3, "OK"));
    CX_TEST_ASSERT(0 == strcmp(v4, "cK"));
    CX_TEST_ASSERT(0 == strcmp(v5, "cK"));
}

CX_TEST(test_hash_map_simple_destructor) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *allocator = &talloc.base;
    CX_TEST_DO {
        CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 0);
        map->collection.simple_destructor = test_simple_destructor;
        CX_TEST_CALL_SUBROUTINE(verify_any_destructor, map);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_hash_map_advanced_destructor) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *allocator = &talloc.base;
    CX_TEST_DO {
        CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 0);
        map->collection.advanced_destructor = test_advanced_destructor;
        CX_TEST_CALL_SUBROUTINE(verify_any_destructor, map);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_empty_map_size) {
    CX_TEST_DO {
        CX_TEST_ASSERT(cxEmptyMap->collection.size == 0);
        CX_TEST_ASSERT(cxMapSize(cxEmptyMap) == 0);
    }
}

CX_TEST(test_empty_map_iterator) {
    CxMap *map = cxEmptyMap;

    CxIterator it1 = cxMapIterator(map);
    CxIterator it2 = cxMapIteratorValues(map);
    CxIterator it3 = cxMapIteratorKeys(map);
    CxIterator it4 = cxMapMutIterator(map);
    CxIterator it5 = cxMapMutIteratorValues(map);
    CxIterator it6 = cxMapMutIteratorKeys(map);

    CX_TEST_DO {
        CX_TEST_ASSERT(!cxIteratorValid(it1));
        CX_TEST_ASSERT(!cxIteratorValid(it2));
        CX_TEST_ASSERT(!cxIteratorValid(it3));
        CX_TEST_ASSERT(!cxIteratorValid(it4));
        CX_TEST_ASSERT(!cxIteratorValid(it5));
        CX_TEST_ASSERT(!cxIteratorValid(it6));

        int c = 0;
        cx_foreach(void*, data, it1) c++;
        cx_foreach(void*, data, it2) c++;
        cx_foreach(void*, data, it3) c++;
        cx_foreach(void*, data, it4) c++;
        cx_foreach(void*, data, it5) c++;
        cx_foreach(void*, data, it6) c++;
        CX_TEST_ASSERT(c == 0);
    }
}

CX_TEST(test_empty_map_no_ops) {
    CX_TEST_DO {
        // assertion not possible
        // test that no segfault happens and valgrind is happy
        cxMapClear(cxEmptyMap);
        cxMapFree(cxEmptyMap);
        CX_TEST_ASSERT(true);
    }
}

CX_TEST(test_empty_map_get) {
    CX_TEST_DO {
        CxHashKey key = cx_hash_key_str("test");
        CX_TEST_ASSERT(cxMapGet(cxEmptyMap, key) == NULL);
    }
}

CX_TEST(test_hash_map_generics) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *allocator = &talloc.base;
    CX_TEST_DO {
        CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 0);
        cxMapPut(map, "test", "test");
        cxMapPut(map, cx_mutstr("foo"), "bar");
        cxMapPut(map, cx_str("hallo"), "welt");

        CX_TEST_ASSERT(map->collection.size == 3);
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "test"), "test"));
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "foo"), "bar"));
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "hallo"), "welt"));

        CX_TEST_ASSERT(0 == cxMapRemove(map, cx_str("test")));
        const char *hallo = "hallo";
        CX_TEST_ASSERT(0 == cxMapRemove(map, hallo));
        cxMapPut(map, cx_hash_key_str("key"), "value");

        CX_TEST_ASSERT(map->collection.size == 2);
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "key"), "value"));
        CX_TEST_ASSERT(0 == strcmp(cxMapGet(map, "foo"), "bar"));

        const char *r1, *r2;
        CX_TEST_ASSERT(0 == cxMapRemoveAndGet(map, "key", &r1));
        CX_TEST_ASSERT(0 == strcmp(r1, "value"));
        CX_TEST_ASSERT(0 == cxMapRemoveAndGet(map, cx_str("foo"), &r2));
        CX_TEST_ASSERT(0 == strcmp(r2, "bar"));
        r2 = "nope";
        CX_TEST_ASSERT(0 != cxMapRemoveAndGet(map, cx_hash_key("notfound",9), &r2));
        CX_TEST_ASSERT(0 == strcmp(r2, "nope"));

        CX_TEST_ASSERT(map->collection.size == 0);

        cxMapFree(map);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

struct test_map_kv {
    const char *key;
    const char *value;
};

static struct test_map_kv const test_map_operations[] = {
        {"key 1",          "test"},
        {"key 2",          "blub"},
        {"key 3",          "hallo"},
        {"key 2",          "foobar"},
        {"key 4",          "value 4"},
        {"key 5",          "value 5"},
        {"key 6",          "value 6"},
        {"key 4",          NULL},
        {"key 7",          "value 7"},
        {"key 8",          "value 8"},
        {"does not exist", NULL},
        {"key 9",          "value 9"},
        {"key 6",          "other value"},
        {"key 7",          "something else"},
        {"key 8",          NULL},
        {"key 2",          NULL},
        {"key 8",          "new value"},
};
static const size_t test_map_operations_len =
        sizeof(test_map_operations) / sizeof(struct test_map_kv);
static struct test_map_kv test_map_reference[] = {
        {"key 1", NULL},
        {"key 2", NULL},
        {"key 3", NULL},
        {"key 4", NULL},
        {"key 5", NULL},
        {"key 6", NULL},
        {"key 7", NULL},
        {"key 8", NULL},
        {"key 9", NULL},
};
static const size_t test_map_reference_len =
        sizeof(test_map_reference) / sizeof(struct test_map_kv);

static void test_map_reference_put(const char *key, const char *value) {
    for (size_t i = 0 ; i < test_map_reference_len ; i++) {
        if (0 == strcmp(key, test_map_reference[i].key)) {
            test_map_reference[i].value = value;
            return;
        }
    }
}

static const char *test_map_reference_get(const char *key) {
    for (size_t i = 0 ; i < test_map_reference_len ; i++) {
        if (0 == strcmp(key, test_map_reference[i].key)) {
            return test_map_reference[i].value;
        }
    }
    return NULL;
}

static const char *test_map_reference_remove(const char *key) {
    for (size_t i = 0 ; i < test_map_reference_len ; i++) {
        if (0 == strcmp(key, test_map_reference[i].key)) {
            const char *ret = test_map_reference[i].value;
            test_map_reference[i].value = NULL;
            return ret;
        }
    }
    return NULL;
}

static size_t test_map_reference_size(void) {
    size_t size = 0;
    for (size_t i = 0; i < test_map_reference_len; i++) {
        if (test_map_reference[i].value != NULL) {
            size++;
        }
    }
    return size;
}

static CX_TEST_SUBROUTINE(verify_map_contents, CxMap *map) {
    // verify that the reference map has same size (i.e. no other keys are mapped)
    CX_TEST_ASSERT(map->collection.size == test_map_reference_size());

    // verify key iterator
    {
        // collect the keys from the map iterator
        CxIterator keyiter = cxMapIteratorKeys(map);
        CX_TEST_ASSERT(keyiter.elem_size == sizeof(CxHashKey));
        CX_TEST_ASSERT(keyiter.elem_count == map->collection.size);
        CxHashKey *keys = calloc(map->collection.size, sizeof(CxHashKey));
        cx_foreach(CxHashKey*, elem, keyiter) {
            keys[keyiter.index] = *elem;
        }
        CX_TEST_ASSERT(keyiter.index == map->collection.size);
        // verify that all keys are mapped to values in reference map
        for (size_t i = 0 ; i < map->collection.size ; i++) {
            cxmutstr ksz = cx_strdup(cx_strn(keys[i].data, keys[i].len));
            CX_TEST_ASSERT(test_map_reference_get(ksz.ptr) != NULL);
            cx_strfree(&ksz);
        }
        free(keys);
    }

    // verify value iterator
    {
        // by using that the values in our test data are unique strings
        // we can re-use a similar approach as above
        CxIterator valiter = cxMapIteratorValues(map);
        CX_TEST_ASSERT(valiter.elem_size == map->collection.elem_size);
        CX_TEST_ASSERT(valiter.elem_count == map->collection.size);
        const char ** values = calloc(map->collection.size, sizeof(const char *));
        cx_foreach(const char *, elem, valiter) {
            values[valiter.index] = elem;
        }
        CX_TEST_ASSERT(valiter.index == map->collection.size);
        // verify that all values are present in the reference map
        for (size_t i = 0 ; i < map->collection.size ; i++) {
            bool found = false;
            for (size_t j = 0; j < test_map_reference_len ; j++) {
                if (test_map_reference[j].value == values[i]) {
                    found = true;
                    break;
                }
            }
            CX_TEST_ASSERTM(found, "A value was not found in the reference map");
        }
        free(values);
    }

    // verify pair iterator
    {
        CxIterator pairiter = cxMapIterator(map);
        CX_TEST_ASSERT(pairiter.elem_size == sizeof(CxMapEntry));
        CX_TEST_ASSERT(pairiter.elem_count == map->collection.size);
        struct test_map_kv *pairs = calloc(map->collection.size, sizeof(struct test_map_kv));
        cx_foreach(CxMapEntry*, entry, pairiter) {
            const CxHashKey *key = entry->key;
            pairs[pairiter.index].key = cx_strdup(cx_strn(key->data, key->len)).ptr;
            pairs[pairiter.index].value = entry->value;
        }
        CX_TEST_ASSERT(pairiter.index == map->collection.size);
        // verify that all pairs are present in the reference map
        for (size_t i = 0 ; i < map->collection.size ; i++) {
            CX_TEST_ASSERT(test_map_reference_get(pairs[i].key) == pairs[i].value);
            // this was strdup'ed
            free((void*)pairs[i].key);
        }
        free(pairs);
    }
}

CX_TEST(test_hash_map_basic_operations) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *allocator = &talloc.base;
    CX_TEST_DO {
        // create the map
        CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 8);

        // clear the reference map
        for (size_t i = 0 ; i < test_map_reference_len ; i++) {
            test_map_reference[i].value = NULL;
        }

        // verify iterators for empty map
        CX_TEST_CALL_SUBROUTINE(verify_map_contents, map);

        // execute operations and verify results
        for (size_t i = 0 ; i < test_map_operations_len ; i++) {
            struct test_map_kv kv = test_map_operations[i];
            CxHashKey key = cx_hash_key_str(kv.key);
            key.hash = 0; // force the hash map to compute the hash
            if (kv.value != NULL) {
                // execute a put operation and verify that the exact value can be read back
                test_map_reference_put(kv.key, kv.value);
                int result = cxMapPut(map, key, (void *) kv.value);
                CX_TEST_ASSERT(result == 0);
                void *added = cxMapGet(map, key);
                CX_TEST_ASSERT(0 == memcmp(kv.value, added, strlen(kv.value)));
            } else {
                // execute a remove and verify that the removed element was returned (or NULL)
                const char *found = test_map_reference_remove(kv.key);
                void *removed = (void*) 0x1337;
                int result = cxMapRemoveAndGet(map, key, &removed);
                if (found == NULL) {
                    CX_TEST_ASSERT(0 != result);
                    CX_TEST_ASSERT(removed == (void*) 0x1337);
                } else {
                    CX_TEST_ASSERT(0 == result);
                    CX_TEST_ASSERT(removed == found);
                }

            }
            // compare the current map state with the reference map
            CX_TEST_CALL_SUBROUTINE(verify_map_contents, map);
        }

        // destroy the map and verify the memory (de)allocations
        cxMapFree(map);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CxTestSuite *cx_test_suite_hash_map(void) {
    CxTestSuite *suite = cx_test_suite_new("map");

    cx_test_register(suite, test_hash_map_create);
    cx_test_register(suite, test_hash_map_create_store_pointers);
    cx_test_register(suite, test_hash_map_basic_operations);
    cx_test_register(suite, test_hash_map_rehash);
    cx_test_register(suite, test_hash_map_rehash_not_required);
    cx_test_register(suite, test_hash_map_clear);
    cx_test_register(suite, test_hash_map_store_ucx_strings);
    cx_test_register(suite, test_hash_map_remove_via_iterator);
    cx_test_register(suite, test_empty_map_no_ops);
    cx_test_register(suite, test_empty_map_size);
    cx_test_register(suite, test_empty_map_get);
    cx_test_register(suite, test_empty_map_iterator);
    cx_test_register(suite, test_hash_map_generics);

    return suite;
}

mercurial