Fri, 23 May 2025 12:44:24 +0200
make test-compile depend on both static and shared
the shared lib is not needed for the tests,
but when run with coverage, gcov will be confused
when outdated line information is available from
a previous shared build
/* * 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); 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; CxMapIterator 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"); CxMapIterator 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); } struct test_destr_struct { char *str; }; static void test_simple_destructor(void *d) { struct test_destr_struct *data = d; strcpy(data->str, "OK"); } static void test_advanced_destructor( cx_attr_unused void *unused, void *d ) { test_simple_destructor(d); } 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"; char v6[] = "val 6"; char v7[] = "val 7"; struct test_destr_struct d1 = {v1}; struct test_destr_struct d2 = {v2}; struct test_destr_struct d3 = {v3}; struct test_destr_struct d4 = {v4}; struct test_destr_struct d5 = {v5}; struct test_destr_struct d6 = {v6}; struct test_destr_struct d7 = {v7}; void *v1data = &d1; void *v2data = &d2; void *v3data = &d3; void *v4data = &d4; void *v5data = &d5; void *v6data = &d6; void *v7data = &d7; cxMapPut(map, k1, v1data); cxMapPut(map, k2, v2data); cxMapPut(map, k3, v3data); cxMapPut(map, k4, v4data); CX_TEST_ASSERT(0 == cxMapRemove(map, k2)); if (map->collection.store_pointer) { struct test_destr_struct *r; CX_TEST_ASSERT(0 == cxMapRemoveAndGet(map, k3, &r)); CX_TEST_ASSERT(r == &d3); } else { struct test_destr_struct r; CX_TEST_ASSERT(0 == cxMapRemoveAndGet(map, k3, &r)); CX_TEST_ASSERT(r.str == v3); } 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")); // put k5, overwrite k1, put new k3 cxMapPut(map, k5, v5data); cxMapPut(map, k1, v6data); cxMapPut(map, k3, v7data); // destructor the value behind k1 was called, but for k3 not CX_TEST_ASSERT(0 == strcmp(v1, "OK")); CX_TEST_ASSERT(0 == strcmp(v3, "val 3")); // now remove k1 via key iterator and k5 (val 5) via value iterator v1[0] = 'y'; // mark v1 and check that destr is not called for previous val { CxMapIterator iter = cxMapMutIteratorKeys(map); cx_foreach(CxHashKey*, key, iter) { if (((char*)key->data)[4] == '1') cxIteratorFlagRemoval(iter); } } { CxMapIterator iter = cxMapMutIteratorValues(map); cx_foreach(struct test_destr_struct*, v, iter) { if (v->str[4] == '5') cxIteratorFlagRemoval(iter); } } CX_TEST_ASSERT(0 == strcmp(v1, "yK")); 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, "OK")); CX_TEST_ASSERT(0 == strcmp(v6, "OK")); CX_TEST_ASSERT(0 == strcmp(v7, "val 7")); // mark the already destroyed items // and check that they are not destroyed again 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, "val 3")); CX_TEST_ASSERT(0 == strcmp(v4, "OK")); CX_TEST_ASSERT(0 == strcmp(v5, "cK")); CX_TEST_ASSERT(0 == strcmp(v6, "OK")); CX_TEST_ASSERT(0 == strcmp(v7, "OK")); } CX_TEST(test_hash_map_simple_destructor_objects) { CxTestingAllocator talloc; cx_testing_allocator_init(&talloc); CxAllocator *allocator = &talloc.base; CX_TEST_DO { CxMap *map = cxHashMapCreate(allocator, sizeof(struct test_destr_struct), 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_objects) { CxTestingAllocator talloc; cx_testing_allocator_init(&talloc); CxAllocator *allocator = &talloc.base; CX_TEST_DO { CxMap *map = cxHashMapCreate(allocator, sizeof(struct test_destr_struct), 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_hash_map_simple_destructor_pointers) { 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_pointers) { 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; CxMapIterator it1 = cxMapIterator(map); CxMapIterator it2 = cxMapIteratorValues(map); CxMapIterator it3 = cxMapIteratorKeys(map); CxMapIterator it4 = cxMapMutIterator(map); CxMapIterator it5 = cxMapMutIteratorValues(map); CxMapIterator 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 CxMapIterator 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 CxMapIterator 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 { CxMapIterator 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 cx_strdup'ed cxFreeDefault((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_hash_map_simple_destructor_objects); cx_test_register(suite, test_hash_map_advanced_destructor_objects); cx_test_register(suite, test_hash_map_simple_destructor_pointers); cx_test_register(suite, test_hash_map_advanced_destructor_pointers); 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; }