UNIXworkcode

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 */ 28 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" 34 35 #include <gtest/gtest.h> 36 #include <unordered_map> 37 #include <unordered_set> 38 39 struct map_operation { 40 enum { 41 put, rm 42 } op; 43 char const *key; 44 char const *value; 45 }; 46 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 } 68 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 } 86 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 } 101 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 } 117 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); 139 140 cxMapDestroy(map); 141 EXPECT_TRUE(allocator.verify()); 142 } 143 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 *)); 156 157 cxMapDestroy(map); 158 EXPECT_TRUE(allocator.verify()); 159 } 160 161 TEST(CxHashMap, BasicOperations) { 162 // create the map 163 CxTestingAllocator allocator; 164 auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 8); 165 166 // create a reference map 167 std::unordered_map<std::string, std::string> refmap; 168 169 // generate operations 170 auto ops = generate_map_operations(); 171 172 // verify iterators for empty map 173 verify_map_contents(map, refmap); 174 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 } 200 201 // destroy the map and verify the memory (de)allocations 202 cxMapDestroy(map); 203 EXPECT_TRUE(allocator.verify()); 204 } 205 206 TEST(CxHashMap, RemoveViaIterator) { 207 CxTestingAllocator allocator; 208 auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 4); 209 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"); 216 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); 223 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); 230 231 cxMapDestroy(map); 232 EXPECT_TRUE(allocator.verify()); 233 } 234 235 TEST(CxHashMap, RehashNotRequired) { 236 CxTestingAllocator allocator; 237 auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 8); 238 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"); 245 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); 250 251 cxMapDestroy(map); 252 EXPECT_TRUE(allocator.verify()); 253 } 254 255 TEST(CxHashMap, Rehash) { 256 CxTestingAllocator allocator; 257 auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 7); 258 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"); 269 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); 274 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"); 285 286 cxMapDestroy(map); 287 EXPECT_TRUE(allocator.verify()); 288 } 289 290 TEST(CxHashMap, Clear) { 291 CxTestingAllocator allocator; 292 auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 0); 293 294 cxMapPut(map, "key 1", (void *) "val 1"); 295 cxMapPut(map, "key 2", (void *) "val 2"); 296 cxMapPut(map, "key 3", (void *) "val 3"); 297 298 EXPECT_EQ(map->size, 3); 299 300 cxMapClear(map); 301 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); 306 307 cxMapDestroy(map); 308 EXPECT_TRUE(allocator.verify()); 309 } 310 311 TEST(CxHashMap, StoreUcxStrings) { 312 // create the map 313 CxTestingAllocator allocator; 314 auto map = cxHashMapCreate(&allocator, sizeof(cxstring), 8); 315 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"); 322 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); 328 329 // overwrite a value 330 cxMapPut(map, "s1", &s5); 331 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); 337 338 // remove a string 339 cxMapRemove(map, "s2"); 340 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); 350 351 cxMapDestroy(map); 352 EXPECT_TRUE(allocator.verify()); 353 } 354 355 static void test_simple_destructor(void *data) { 356 strcpy((char *) data, "OK"); 357 } 358 359 static void test_advanced_destructor( 360 [[maybe_unused]] void *unused, 361 void *data 362 ) { 363 strcpy((char *) data, "OK"); 364 } 365 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"); 372 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"; 378 379 cxMapPut(map, k1, (void *) v1); 380 cxMapPut(map, k2, (void *) v2); 381 cxMapPut(map, k3, (void *) v3); 382 cxMapPut(map, k4, (void *) v4); 383 384 cxMapRemove(map, k2); 385 auto r = cxMapRemoveAndGet(map, k3); 386 cxMapDetach(map, k1); 387 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); 394 395 cxMapClear(map); 396 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"); 402 403 cxMapPut(map, k1, (void *) v1); 404 cxMapPut(map, k3, (void *) v3); 405 cxMapPut(map, k5, (void *) v5); 406 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 } 419 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"); 425 426 v1[0] = v2[0] = v4[0] = v5[0] = 'c'; 427 428 cxMapDestroy(map); 429 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 } 436 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 } 444 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 } 452 453 TEST(CxHashMap, Generics) { 454 CxTestingAllocator allocator; 455 auto map = test_map_generics_step_1(&allocator); 456 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"); 461 462 test_map_generics_step_2(map); 463 464 EXPECT_EQ(map->size, 2); 465 EXPECT_STREQ((char *) cxMapGet(map, "key"), "value"); 466 EXPECT_STREQ((char *) cxMapGet(map, "foo"), "bar"); 467 468 test_map_generics_step_3(map); 469 470 EXPECT_EQ(map->size, 0); 471 472 cxMapDestroy(map); 473 EXPECT_TRUE(allocator.verify()); 474 } 475 476 TEST(EmptyMap, Size) { 477 auto map = cxEmptyMap; 478 479 EXPECT_EQ(map->size, 0); 480 } 481 482 TEST(EmptyMap, Iterator) { 483 auto map = cxEmptyMap; 484 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); 491 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)); 498 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 } 508 509 TEST(EmptyMap, NoOps) { 510 auto map = cxEmptyMap; 511 512 ASSERT_NO_FATAL_FAILURE(cxMapClear(map)); 513 ASSERT_NO_FATAL_FAILURE(cxMapDestroy(map)); 514 } 515 516 TEST(EmptyMap, Get) { 517 auto map = cxEmptyMap; 518 519 CxHashKey key = cx_hash_key_str("test"); 520 EXPECT_EQ(cxMapGet(map, key), nullptr); 521 } 522