Mon, 08 Aug 2022 17:12:00 +0200
#201 - remove dangerous allocator config
There is no plausible use case, except using the testing
allocator in the test case, and having the possibility to
specify any allocator (including another mempool) causes
more harm than good.
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 "util_allocator.h"
33 #include <gtest/gtest.h>
34 #include <unordered_map>
35 #include <unordered_set>
37 struct map_operation {
38 enum {
39 put, rm
40 } op;
41 char const *key;
42 char const *value;
43 };
45 auto generate_map_operations() -> std::vector<map_operation> {
46 return {
47 {map_operation::put, "key 1", "test"},
48 {map_operation::put, "key 2", "blub"},
49 {map_operation::put, "key 3", "hallo"},
50 {map_operation::put, "key 2", "foobar"},
51 {map_operation::put, "key 4", "value 4"},
52 {map_operation::put, "key 5", "value 5"},
53 {map_operation::put, "key 6", "value 6"},
54 {map_operation::rm, "key 4", nullptr},
55 {map_operation::put, "key 7", "value 7"},
56 {map_operation::put, "key 8", "value 8"},
57 {map_operation::rm, "does not exist", nullptr},
58 {map_operation::put, "key 9", "value 9"},
59 {map_operation::put, "key 6", "other value"},
60 {map_operation::put, "key 7", "something else"},
61 {map_operation::rm, "key 8", nullptr},
62 {map_operation::rm, "key 2", nullptr},
63 {map_operation::put, "key 8", "new value"},
64 };
65 }
67 static void verify_map_contents(
68 CxMap *map,
69 std::unordered_map<std::string, std::string> const &refmap
70 ) {
71 // verify key iterator
72 {
73 auto keyiter = cxMapIteratorKeys(map);
74 std::unordered_set<std::string> keys;
75 cx_foreach(CxHashKey*, elem, keyiter) {
76 // we use that our test keys contain NULL-terminated strings
77 keys.insert(std::string(elem->data.cstr));
78 }
79 EXPECT_EQ(keyiter.index, map->size);
80 ASSERT_EQ(keys.size(), map->size);
81 for (auto &&k: keys) {
82 EXPECT_NE(refmap.find(k), refmap.end());
83 }
84 }
86 // verify value iterator
87 {
88 auto valiter = cxMapIteratorValues(map);
89 std::unordered_set<std::string> values; // we use that the values in our test data are unique strings
90 cx_foreach(char const*, elem, valiter) {
91 values.insert(std::string(elem));
92 }
93 EXPECT_EQ(valiter.index, map->size);
94 ASSERT_EQ(values.size(), map->size);
95 for (auto &&v: values) {
96 EXPECT_NE(std::find_if(refmap.begin(), refmap.end(),
97 [v](auto const &e) { return e.second == v; }), refmap.end());
98 }
99 }
101 // verify pair iterator
102 {
103 auto pairiter = cxMapIterator(map);
104 std::unordered_map<std::string, std::string> pairs;
105 cx_foreach(CxMapEntry*, entry, pairiter) {
106 pairs[std::string(entry->key->data.cstr)] = std::string((char *) entry->value);
107 }
108 EXPECT_EQ(pairiter.index, map->size);
109 ASSERT_EQ(pairs.size(), refmap.size());
110 for (auto &&p: pairs) {
111 ASSERT_EQ(p.second, refmap.at(p.first));
112 }
113 }
114 }
116 TEST(CxHashMap, Create) {
117 CxTestingAllocator allocator;
118 auto map = cxHashMapCreate(&allocator, 0);
119 auto hmap = reinterpret_cast<struct cx_hash_map_s *>(map);
120 EXPECT_GT(hmap->bucket_count, 0);
121 cx_for_n(i, hmap->bucket_count) {
122 EXPECT_EQ(hmap->buckets[i], nullptr);
123 }
124 EXPECT_EQ(map->size, 0);
125 EXPECT_EQ(map->allocator, &allocator);
127 cxMapDestroy(map);
128 EXPECT_TRUE(allocator.verify());
129 }
131 TEST(CxHashMap, BasicOperations) {
132 // create the map
133 CxTestingAllocator allocator;
134 auto map = cxHashMapCreate(&allocator, 8);
136 // create a reference map
137 std::unordered_map<std::string, std::string> refmap;
139 // generate operations
140 auto ops = generate_map_operations();
142 // verify iterators for empty map
143 verify_map_contents(map, refmap);
145 // execute operations and verify results
146 for (auto &&op: ops) {
147 CxHashKey key = cx_hash_key_str(op.key);
148 key.hash = 0; // force the hash map to compute the hash
149 if (op.op == map_operation::put) {
150 // execute a put operation and verify that the exact value can be read back
151 refmap[std::string(op.key)] = std::string(op.value);
152 int result = cxMapPut(map, key, (void *) op.value);
153 EXPECT_EQ(result, 0);
154 auto added = cxMapGet(map, key);
155 EXPECT_EQ(memcmp(op.value, added, strlen(op.value)), 0);
156 } else {
157 // execute a remove and verify that the removed element was returned (or nullptr)
158 auto found = refmap.find(op.key);
159 auto removed = cxMapRemove(map, key);
160 if (found == refmap.end()) {
161 EXPECT_EQ(removed, nullptr);
162 } else {
163 EXPECT_EQ(std::string((char *) removed), found->second);
164 refmap.erase(found);
165 }
166 }
167 // compare the current map state with the reference map
168 verify_map_contents(map, refmap);
169 }
171 // destroy the map and verify the memory (de)allocations
172 cxMapDestroy(map);
173 EXPECT_TRUE(allocator.verify());
174 }
176 TEST(CxHashMap, RemoveViaIterator) {
177 CxTestingAllocator allocator;
178 auto map = cxHashMapCreate(&allocator, 4);
180 cxMapPut(map, cx_hash_key_str("key 1"), (void *) "val 1");
181 cxMapPut(map, cx_hash_key_str("key 2"), (void *) "val 2");
182 cxMapPut(map, cx_hash_key_str("key 3"), (void *) "val 3");
183 cxMapPut(map, cx_hash_key_str("key 4"), (void *) "val 4");
184 cxMapPut(map, cx_hash_key_str("key 5"), (void *) "val 5");
185 cxMapPut(map, cx_hash_key_str("key 6"), (void *) "val 6");
187 auto iter = cxMapIterator(map);
188 cx_foreach(CxMapEntry*, entry, iter) {
189 if (entry->key->data.cstr[4] % 2 == 1) iter.remove = true;
190 }
191 EXPECT_EQ(map->size, 3);
192 EXPECT_EQ(iter.index, map->size);
194 EXPECT_EQ(cxMapGet(map, cx_hash_key_str("key 1")), nullptr);
195 EXPECT_NE(cxMapGet(map, cx_hash_key_str("key 2")), nullptr);
196 EXPECT_EQ(cxMapGet(map, cx_hash_key_str("key 3")), nullptr);
197 EXPECT_NE(cxMapGet(map, cx_hash_key_str("key 4")), nullptr);
198 EXPECT_EQ(cxMapGet(map, cx_hash_key_str("key 5")), nullptr);
199 EXPECT_NE(cxMapGet(map, cx_hash_key_str("key 6")), nullptr);
201 cxMapDestroy(map);
202 EXPECT_TRUE(allocator.verify());
203 }
205 TEST(CxHashMap, RehashNotRequired) {
206 CxTestingAllocator allocator;
207 auto map = cxHashMapCreate(&allocator, 8);
209 cxMapPut(map, cx_hash_key_str("key 1"), (void *) "val 1");
210 cxMapPut(map, cx_hash_key_str("key 2"), (void *) "val 2");
211 cxMapPut(map, cx_hash_key_str("key 3"), (void *) "val 3");
212 cxMapPut(map, cx_hash_key_str("key 4"), (void *) "val 4");
213 cxMapPut(map, cx_hash_key_str("key 5"), (void *) "val 5");
214 cxMapPut(map, cx_hash_key_str("key 6"), (void *) "val 6");
216 // 6/8 does not exceed 0.75, therefore the function should not rehash
217 int result = cxMapRehash(map);
218 EXPECT_EQ(result, 0);
219 EXPECT_EQ(reinterpret_cast<struct cx_hash_map_s *>(map)->bucket_count, 8);
221 cxMapDestroy(map);
222 EXPECT_TRUE(allocator.verify());
223 }
225 TEST(CxHashMap, Rehash) {
226 CxTestingAllocator allocator;
227 auto map = cxHashMapCreate(&allocator, 8);
229 cxMapPut(map, cx_hash_key_str("key 1"), (void *) "val 1");
230 cxMapPut(map, cx_hash_key_str("key 2"), (void *) "val 2");
231 cxMapPut(map, cx_hash_key_str("key 3"), (void *) "val 3");
232 cxMapPut(map, cx_hash_key_str("key 4"), (void *) "val 4");
233 cxMapPut(map, cx_hash_key_str("key 5"), (void *) "val 5");
234 cxMapPut(map, cx_hash_key_str("key 6"), (void *) "val 6");
235 cxMapPut(map, cx_hash_key_str("key 7"), (void *) "val 7");
237 int result = cxMapRehash(map);
238 EXPECT_EQ(result, 0);
239 EXPECT_EQ(reinterpret_cast<struct cx_hash_map_s *>(map)->bucket_count, 17);
240 EXPECT_EQ(map->size, 7);
242 EXPECT_EQ(strcmp((char *) cxMapGet(map, cx_hash_key_str("key 1")), "val 1"), 0);
243 EXPECT_EQ(strcmp((char *) cxMapGet(map, cx_hash_key_str("key 2")), "val 2"), 0);
244 EXPECT_EQ(strcmp((char *) cxMapGet(map, cx_hash_key_str("key 3")), "val 3"), 0);
245 EXPECT_EQ(strcmp((char *) cxMapGet(map, cx_hash_key_str("key 4")), "val 4"), 0);
246 EXPECT_EQ(strcmp((char *) cxMapGet(map, cx_hash_key_str("key 5")), "val 5"), 0);
247 EXPECT_EQ(strcmp((char *) cxMapGet(map, cx_hash_key_str("key 6")), "val 6"), 0);
248 EXPECT_EQ(strcmp((char *) cxMapGet(map, cx_hash_key_str("key 7")), "val 7"), 0);
250 cxMapDestroy(map);
251 EXPECT_TRUE(allocator.verify());
252 }