tests/test_map.cpp

Sun, 21 May 2023 14:40:05 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 21 May 2023 14:40:05 +0200
changeset 707
87eb4bdb2d0e
parent 706
8c6edaccaef1
permissions
-rw-r--r--

fix rehash not valid for non-hash-maps

     1 /*
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
     5  *
     6  * Redistribution and use in source and binary forms, with or without
     7  * modification, are permitted provided that the following conditions are met:
     8  *
     9  *   1. Redistributions of source code must retain the above copyright
    10  *      notice, this list of conditions and the following disclaimer.
    11  *
    12  *   2. Redistributions in binary form must reproduce the above copyright
    13  *      notice, this list of conditions and the following disclaimer in the
    14  *      documentation and/or other materials provided with the distribution.
    15  *
    16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    26  * POSSIBILITY OF SUCH DAMAGE.
    27  */
    29 #include "cx/hash_map.h"
    30 #include "cx/utils.h"
    31 #include "cx/string.h"
    32 #include "util_allocator.h"
    33 #include "test_map_generics.h"
    35 #include <gtest/gtest.h>
    36 #include <unordered_map>
    37 #include <unordered_set>
    39 struct map_operation {
    40     enum {
    41         put, rm
    42     } op;
    43     char const *key;
    44     char const *value;
    45 };
    47 auto generate_map_operations() -> std::vector<map_operation> {
    48     return {
    49             {map_operation::put, "key 1",          "test"},
    50             {map_operation::put, "key 2",          "blub"},
    51             {map_operation::put, "key 3",          "hallo"},
    52             {map_operation::put, "key 2",          "foobar"},
    53             {map_operation::put, "key 4",          "value 4"},
    54             {map_operation::put, "key 5",          "value 5"},
    55             {map_operation::put, "key 6",          "value 6"},
    56             {map_operation::rm,  "key 4",          nullptr},
    57             {map_operation::put, "key 7",          "value 7"},
    58             {map_operation::put, "key 8",          "value 8"},
    59             {map_operation::rm,  "does not exist", nullptr},
    60             {map_operation::put, "key 9",          "value 9"},
    61             {map_operation::put, "key 6",          "other value"},
    62             {map_operation::put, "key 7",          "something else"},
    63             {map_operation::rm,  "key 8",          nullptr},
    64             {map_operation::rm,  "key 2",          nullptr},
    65             {map_operation::put, "key 8",          "new value"},
    66     };
    67 }
    69 static void verify_map_contents(
    70         CxMap *map,
    71         std::unordered_map<std::string, std::string> const &refmap
    72 ) {
    73     // verify key iterator
    74     {
    75         auto keyiter = cxMapIteratorKeys(map);
    76         std::unordered_set<std::string> keys;
    77         cx_foreach(CxHashKey*, elem, keyiter) {
    78             keys.insert(std::string(reinterpret_cast<char const *>(elem->data), elem->len));
    79         }
    80         EXPECT_EQ(keyiter.index, map->size);
    81         ASSERT_EQ(keys.size(), map->size);
    82         for (auto &&k: keys) {
    83             EXPECT_NE(refmap.find(k), refmap.end());
    84         }
    85     }
    87     // verify value iterator
    88     {
    89         auto valiter = cxMapIteratorValues(map);
    90         std::unordered_set<std::string> values; // we use that the values in our test data are unique strings
    91         cx_foreach(char const*, elem, valiter) {
    92             values.insert(std::string(elem));
    93         }
    94         EXPECT_EQ(valiter.index, map->size);
    95         ASSERT_EQ(values.size(), map->size);
    96         for (auto &&v: values) {
    97             EXPECT_NE(std::find_if(refmap.begin(), refmap.end(),
    98                                    [v](auto const &e) { return e.second == v; }), refmap.end());
    99         }
   100     }
   102     // verify pair iterator
   103     {
   104         auto pairiter = cxMapIterator(map);
   105         std::unordered_map<std::string, std::string> pairs;
   106         cx_foreach(CxMapEntry*, entry, pairiter) {
   107             pairs[std::string(reinterpret_cast<char const *>(entry->key->data), entry->key->len)] = std::string(
   108                     (char *) entry->value);
   109         }
   110         EXPECT_EQ(pairiter.index, map->size);
   111         ASSERT_EQ(pairs.size(), refmap.size());
   112         for (auto &&p: pairs) {
   113             ASSERT_EQ(p.second, refmap.at(p.first));
   114         }
   115     }
   116 }
   118 TEST(CxHashMap, Create) {
   119     CxTestingAllocator allocator;
   120     auto map = cxHashMapCreate(&allocator, 1, 0);
   121     auto hmap = reinterpret_cast<struct cx_hash_map_s *>(map);
   122     EXPECT_GT(hmap->bucket_count, 0);
   123     cx_for_n(i, hmap->bucket_count) {
   124         EXPECT_EQ(hmap->buckets[i], nullptr);
   125     }
   126     EXPECT_EQ(map->item_size, 1);
   127     EXPECT_EQ(map->size, 0);
   128     EXPECT_EQ(map->allocator, &allocator);
   129     EXPECT_FALSE(map->store_pointer);
   130     EXPECT_EQ(map->cmpfunc, nullptr);
   131     EXPECT_EQ(map->simple_destructor, nullptr);
   132     EXPECT_EQ(map->advanced_destructor, nullptr);
   133     EXPECT_EQ(map->destructor_data, nullptr);
   134     cxMapStorePointers(map);
   135     EXPECT_TRUE(map->store_pointer);
   136     EXPECT_EQ(map->item_size, sizeof(void *));
   137     cxMapStoreObjects(map);
   138     EXPECT_FALSE(map->store_pointer);
   140     cxMapDestroy(map);
   141     EXPECT_TRUE(allocator.verify());
   142 }
   144 TEST(CxHashMap, CreateForStoringPointers) {
   145     CxTestingAllocator allocator;
   146     auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 0);
   147     auto hmap = reinterpret_cast<struct cx_hash_map_s *>(map);
   148     EXPECT_GT(hmap->bucket_count, 0);
   149     cx_for_n(i, hmap->bucket_count) {
   150         EXPECT_EQ(hmap->buckets[i], nullptr);
   151     }
   152     EXPECT_EQ(map->size, 0);
   153     EXPECT_EQ(map->allocator, &allocator);
   154     EXPECT_TRUE(map->store_pointer);
   155     EXPECT_EQ(map->item_size, sizeof(void *));
   157     cxMapDestroy(map);
   158     EXPECT_TRUE(allocator.verify());
   159 }
   161 TEST(CxHashMap, BasicOperations) {
   162     // create the map
   163     CxTestingAllocator allocator;
   164     auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 8);
   166     // create a reference map
   167     std::unordered_map<std::string, std::string> refmap;
   169     // generate operations
   170     auto ops = generate_map_operations();
   172     // verify iterators for empty map
   173     verify_map_contents(map, refmap);
   175     // execute operations and verify results
   176     for (auto &&op: ops) {
   177         CxHashKey key = cx_hash_key_str(op.key);
   178         key.hash = 0; // force the hash map to compute the hash
   179         if (op.op == map_operation::put) {
   180             // execute a put operation and verify that the exact value can be read back
   181             refmap[std::string(op.key)] = std::string(op.value);
   182             int result = cxMapPut(map, key, (void *) op.value);
   183             EXPECT_EQ(result, 0);
   184             auto added = cxMapGet(map, key);
   185             EXPECT_EQ(memcmp(op.value, added, strlen(op.value)), 0);
   186         } else {
   187             // execute a remove and verify that the removed element was returned (or nullptr)
   188             auto found = refmap.find(op.key);
   189             auto removed = cxMapRemoveAndGet(map, key);
   190             if (found == refmap.end()) {
   191                 EXPECT_EQ(removed, nullptr);
   192             } else {
   193                 EXPECT_EQ(std::string((char *) removed), found->second);
   194                 refmap.erase(found);
   195             }
   196         }
   197         // compare the current map state with the reference map
   198         verify_map_contents(map, refmap);
   199     }
   201     // destroy the map and verify the memory (de)allocations
   202     cxMapDestroy(map);
   203     EXPECT_TRUE(allocator.verify());
   204 }
   206 TEST(CxHashMap, RemoveViaIterator) {
   207     CxTestingAllocator allocator;
   208     auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 4);
   210     cxMapPut(map, "key 1", (void *) "val 1");
   211     cxMapPut(map, "key 2", (void *) "val 2");
   212     cxMapPut(map, "key 3", (void *) "val 3");
   213     cxMapPut(map, "key 4", (void *) "val 4");
   214     cxMapPut(map, "key 5", (void *) "val 5");
   215     cxMapPut(map, "key 6", (void *) "val 6");
   217     auto iter = cxMapMutIterator(map);
   218     cx_foreach(CxMapEntry*, entry, iter) {
   219         if (reinterpret_cast<char const *>(entry->key->data)[4] % 2 == 1) cxIteratorFlagRemoval(iter);
   220     }
   221     EXPECT_EQ(map->size, 3);
   222     EXPECT_EQ(iter.index, map->size);
   224     EXPECT_EQ(cxMapGet(map, "key 1"), nullptr);
   225     EXPECT_NE(cxMapGet(map, "key 2"), nullptr);
   226     EXPECT_EQ(cxMapGet(map, "key 3"), nullptr);
   227     EXPECT_NE(cxMapGet(map, "key 4"), nullptr);
   228     EXPECT_EQ(cxMapGet(map, "key 5"), nullptr);
   229     EXPECT_NE(cxMapGet(map, "key 6"), nullptr);
   231     cxMapDestroy(map);
   232     EXPECT_TRUE(allocator.verify());
   233 }
   235 TEST(CxHashMap, RehashNotRequired) {
   236     CxTestingAllocator allocator;
   237     auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 8);
   239     cxMapPut(map, "key 1", (void *) "val 1");
   240     cxMapPut(map, "key 2", (void *) "val 2");
   241     cxMapPut(map, "key 3", (void *) "val 3");
   242     cxMapPut(map, "key 4", (void *) "val 4");
   243     cxMapPut(map, "key 5", (void *) "val 5");
   244     cxMapPut(map, "key 6", (void *) "val 6");
   246     // 6/8 does not exceed 0.75, therefore the function should not rehash
   247     int result = cxMapRehash(map);
   248     EXPECT_EQ(result, 0);
   249     EXPECT_EQ(reinterpret_cast<struct cx_hash_map_s *>(map)->bucket_count, 8);
   251     cxMapDestroy(map);
   252     EXPECT_TRUE(allocator.verify());
   253 }
   255 TEST(CxHashMap, Rehash) {
   256     CxTestingAllocator allocator;
   257     auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 7);
   259     cxMapPut(map, "key 1", (void *) "val 1");
   260     cxMapPut(map, "key 2", (void *) "val 2");
   261     cxMapPut(map, "key 3", (void *) "val 3");
   262     cxMapPut(map, "foo 4", (void *) "val 4");
   263     cxMapPut(map, "key 5", (void *) "val 5");
   264     cxMapPut(map, "key 6", (void *) "val 6");
   265     cxMapPut(map, "bar 7", (void *) "val 7");
   266     cxMapPut(map, "key 8", (void *) "val 8");
   267     cxMapPut(map, "key 9", (void *) "val 9");
   268     cxMapPut(map, "key 10", (void *) "val 10");
   270     int result = cxMapRehash(map);
   271     EXPECT_EQ(result, 0);
   272     EXPECT_EQ(reinterpret_cast<struct cx_hash_map_s *>(map)->bucket_count, 25);
   273     EXPECT_EQ(map->size, 10);
   275     EXPECT_STREQ((char *) cxMapGet(map, "key 1"), "val 1");
   276     EXPECT_STREQ((char *) cxMapGet(map, "key 2"), "val 2");
   277     EXPECT_STREQ((char *) cxMapGet(map, "key 3"), "val 3");
   278     EXPECT_STREQ((char *) cxMapGet(map, "foo 4"), "val 4");
   279     EXPECT_STREQ((char *) cxMapGet(map, "key 5"), "val 5");
   280     EXPECT_STREQ((char *) cxMapGet(map, "key 6"), "val 6");
   281     EXPECT_STREQ((char *) cxMapGet(map, "bar 7"), "val 7");
   282     EXPECT_STREQ((char *) cxMapGet(map, "key 8"), "val 8");
   283     EXPECT_STREQ((char *) cxMapGet(map, "key 9"), "val 9");
   284     EXPECT_STREQ((char *) cxMapGet(map, "key 10"), "val 10");
   286     cxMapDestroy(map);
   287     EXPECT_TRUE(allocator.verify());
   288 }
   290 TEST(CxHashMap, Clear) {
   291     CxTestingAllocator allocator;
   292     auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 0);
   294     cxMapPut(map, "key 1", (void *) "val 1");
   295     cxMapPut(map, "key 2", (void *) "val 2");
   296     cxMapPut(map, "key 3", (void *) "val 3");
   298     EXPECT_EQ(map->size, 3);
   300     cxMapClear(map);
   302     EXPECT_EQ(map->size, 0);
   303     EXPECT_EQ(cxMapGet(map, "key 1"), nullptr);
   304     EXPECT_EQ(cxMapGet(map, "key 2"), nullptr);
   305     EXPECT_EQ(cxMapGet(map, "key 3"), nullptr);
   307     cxMapDestroy(map);
   308     EXPECT_TRUE(allocator.verify());
   309 }
   311 TEST(CxHashMap, StoreUcxStrings) {
   312     // create the map
   313     CxTestingAllocator allocator;
   314     auto map = cxHashMapCreate(&allocator, sizeof(cxstring), 8);
   316     // define some strings
   317     auto s1 = CX_STR("this");
   318     auto s2 = CX_STR("is");
   319     auto s3 = CX_STR("a");
   320     auto s4 = CX_STR("test");
   321     auto s5 = CX_STR("setup");
   323     // put them into the map
   324     cxMapPut(map, "s1", &s1);
   325     cxMapPut(map, "s2", &s2);
   326     cxMapPut(map, "s3", &s3);
   327     cxMapPut(map, "s4", &s4);
   329     // overwrite a value
   330     cxMapPut(map, "s1", &s5);
   332     // look up a string
   333     auto s3p = reinterpret_cast<cxstring *>(cxMapGet(map, "s3"));
   334     EXPECT_EQ(s3p->length, s3.length);
   335     EXPECT_EQ(s3p->ptr, s3.ptr);
   336     EXPECT_NE(s3p, &s3);
   338     // remove a string
   339     cxMapRemove(map, "s2");
   341     // iterate
   342     auto ref = std::vector{s5.ptr, s3.ptr, s4.ptr};
   343     auto iter = cxMapIteratorValues(map);
   344     cx_foreach(cxstring*, s, iter) {
   345         auto found = std::find(ref.begin(), ref.end(), s->ptr);
   346         ASSERT_NE(found, ref.end());
   347         ref.erase(found);
   348     }
   349     EXPECT_EQ(ref.size(), 0);
   351     cxMapDestroy(map);
   352     EXPECT_TRUE(allocator.verify());
   353 }
   355 static void test_simple_destructor(void *data) {
   356     strcpy((char *) data, "OK");
   357 }
   359 static void test_advanced_destructor(
   360         [[maybe_unused]] void *unused,
   361         void *data
   362 ) {
   363     strcpy((char *) data, "OK");
   364 }
   366 static void verify_any_destructor(CxMap *map) {
   367     auto k1 = cx_hash_key_str("key 1");
   368     auto k2 = cx_hash_key_str("key 2");
   369     auto k3 = cx_hash_key_str("key 3");
   370     auto k4 = cx_hash_key_str("key 4");
   371     auto k5 = cx_hash_key_str("key 5");
   373     char v1[] = "val 1";
   374     char v2[] = "val 2";
   375     char v3[] = "val 3";
   376     char v4[] = "val 4";
   377     char v5[] = "val 5";
   379     cxMapPut(map, k1, (void *) v1);
   380     cxMapPut(map, k2, (void *) v2);
   381     cxMapPut(map, k3, (void *) v3);
   382     cxMapPut(map, k4, (void *) v4);
   384     cxMapRemove(map, k2);
   385     auto r = cxMapRemoveAndGet(map, k3);
   386     cxMapDetach(map, k1);
   388     EXPECT_STREQ(v1, "val 1");
   389     EXPECT_STREQ(v2, "OK");
   390     EXPECT_STREQ(v3, "val 3");
   391     EXPECT_STREQ(v4, "val 4");
   392     EXPECT_STREQ(v5, "val 5");
   393     EXPECT_EQ(r, v3);
   395     cxMapClear(map);
   397     EXPECT_STREQ(v1, "val 1");
   398     EXPECT_STREQ(v2, "OK");
   399     EXPECT_STREQ(v3, "val 3");
   400     EXPECT_STREQ(v4, "OK");
   401     EXPECT_STREQ(v5, "val 5");
   403     cxMapPut(map, k1, (void *) v1);
   404     cxMapPut(map, k3, (void *) v3);
   405     cxMapPut(map, k5, (void *) v5);
   407     {
   408         auto iter = cxMapMutIteratorKeys(map);
   409         cx_foreach(CxHashKey*, key, iter) {
   410             if (reinterpret_cast<char const *>(key->data)[4] == '1') cxIteratorFlagRemoval(iter);
   411         }
   412     }
   413     {
   414         auto iter = cxMapMutIteratorValues(map);
   415         cx_foreach(char*, v, iter) {
   416             if (v[4] == '5') cxIteratorFlagRemoval(iter);
   417         }
   418     }
   420     EXPECT_STREQ(v1, "OK");
   421     EXPECT_STREQ(v2, "OK");
   422     EXPECT_STREQ(v3, "val 3");
   423     EXPECT_STREQ(v4, "OK");
   424     EXPECT_STREQ(v5, "OK");
   426     v1[0] = v2[0] = v4[0] = v5[0] = 'c';
   428     cxMapDestroy(map);
   430     EXPECT_STREQ(v1, "cK");
   431     EXPECT_STREQ(v2, "cK");
   432     EXPECT_STREQ(v3, "OK");
   433     EXPECT_STREQ(v4, "cK");
   434     EXPECT_STREQ(v5, "cK");
   435 }
   437 TEST(CxHashMap, SimpleDestructor) {
   438     CxTestingAllocator allocator;
   439     auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 0);
   440     map->simple_destructor = test_simple_destructor;
   441     verify_any_destructor(map);
   442     EXPECT_TRUE(allocator.verify());
   443 }
   445 TEST(CxHashMap, AdvancedDestructor) {
   446     CxTestingAllocator allocator;
   447     auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 0);
   448     map->advanced_destructor = test_advanced_destructor;
   449     verify_any_destructor(map);
   450     EXPECT_TRUE(allocator.verify());
   451 }
   453 TEST(CxHashMap, Generics) {
   454     CxTestingAllocator allocator;
   455     auto map = test_map_generics_step_1(&allocator);
   457     EXPECT_EQ(map->size, 3);
   458     EXPECT_STREQ((char *) cxMapGet(map, "test"), "test");
   459     EXPECT_STREQ((char *) cxMapGet(map, "foo"), "bar");
   460     EXPECT_STREQ((char *) cxMapGet(map, "hallo"), "welt");
   462     test_map_generics_step_2(map);
   464     EXPECT_EQ(map->size, 2);
   465     EXPECT_STREQ((char *) cxMapGet(map, "key"), "value");
   466     EXPECT_STREQ((char *) cxMapGet(map, "foo"), "bar");
   468     test_map_generics_step_3(map);
   470     EXPECT_EQ(map->size, 0);
   472     cxMapDestroy(map);
   473     EXPECT_TRUE(allocator.verify());
   474 }
   476 TEST(EmptyMap, Size) {
   477     auto map = cxEmptyMap;
   479     EXPECT_EQ(map->size, 0);
   480 }
   482 TEST(EmptyMap, Iterator) {
   483     auto map = cxEmptyMap;
   485     auto it1 = cxMapIterator(map);
   486     auto it2 = cxMapIteratorValues(map);
   487     auto it3 = cxMapIteratorKeys(map);
   488     auto it4 = cxMapMutIterator(map);
   489     auto it5 = cxMapMutIteratorValues(map);
   490     auto it6 = cxMapMutIteratorKeys(map);
   492     EXPECT_FALSE(cxIteratorValid(it1));
   493     EXPECT_FALSE(cxIteratorValid(it2));
   494     EXPECT_FALSE(cxIteratorValid(it3));
   495     EXPECT_FALSE(cxIteratorValid(it4));
   496     EXPECT_FALSE(cxIteratorValid(it5));
   497     EXPECT_FALSE(cxIteratorValid(it6));
   499     int c = 0;
   500     cx_foreach(void*, data, it1) c++;
   501     cx_foreach(void*, data, it2) c++;
   502     cx_foreach(void*, data, it3) c++;
   503     cx_foreach(void*, data, it4) c++;
   504     cx_foreach(void*, data, it5) c++;
   505     cx_foreach(void*, data, it6) c++;
   506     EXPECT_EQ(c, 0);
   507 }
   509 TEST(EmptyMap, NoOps) {
   510     auto map = cxEmptyMap;
   512     ASSERT_NO_FATAL_FAILURE(cxMapClear(map));
   513     ASSERT_NO_FATAL_FAILURE(cxMapDestroy(map));
   514 }
   516 TEST(EmptyMap, Get) {
   517     auto map = cxEmptyMap;
   519     CxHashKey key = cx_hash_key_str("test");
   520     EXPECT_EQ(cxMapGet(map, key), nullptr);
   521 }

mercurial