/* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at https://mozilla.org/MPL/2.0/. * * See the COPYRIGHT file distributed with this work for additional * information regarding copyright ownership. */ #include #include /* IWYU pragma: keep */ #include #include #include #include #include #include #define UNIT_TESTING #include #include #include #include #include #include #include /* INCLUDE LAST */ #define mctx __mctx #include "hashmap.c" #undef mctx typedef struct test_node { uint32_t hashval; char key[64]; } test_node_t; static void test_hashmap_full(uint8_t init_bits, uintptr_t count) { isc_hashmap_t *hashmap = NULL; isc_result_t result; test_node_t *nodes, *long_nodes, *upper_nodes; nodes = isc_mem_get(mctx, count * sizeof(nodes[0])); long_nodes = isc_mem_get(mctx, count * sizeof(nodes[0])); upper_nodes = isc_mem_get(mctx, count * sizeof(nodes[0])); isc_hashmap_create(mctx, init_bits, ISC_HASHMAP_CASE_SENSITIVE, &hashmap); assert_non_null(hashmap); /* * Note: snprintf() is followed with strlcat() * to ensure we are always filling the 16 byte key. */ for (size_t i = 0; i < count; i++) { /* short keys */ snprintf(nodes[i].key, 16, "%u", (unsigned int)i); strlcat(nodes[i].key, " key of a raw hashmap!!", 16); /* long keys */ snprintf(long_nodes[i].key, sizeof(long_nodes[i].key), "%u", (unsigned int)i); strlcat(long_nodes[i].key, " key of a raw hashmap!!", sizeof(long_nodes[i].key)); /* (some) uppercase keys */ snprintf(upper_nodes[i].key, 16, "%u", (unsigned int)i); strlcat(upper_nodes[i].key, " KEY of a raw hashmap!!", 16); } /* insert short nodes */ for (size_t i = 0; i < count; i++) { nodes[i].hashval = isc_hashmap_hash(hashmap, nodes[i].key, 16); result = isc_hashmap_add(hashmap, &(nodes[i]).hashval, nodes[i].key, 16, &nodes[i]); assert_int_equal(result, ISC_R_SUCCESS); } /* check if the short nodes were insert */ for (size_t i = 0; i < count; i++) { void *f = NULL; result = isc_hashmap_find(hashmap, &(nodes[i]).hashval, nodes[i].key, 16, &f); assert_int_equal(result, ISC_R_SUCCESS); assert_ptr_equal(&nodes[i], f); } /* check for double inserts */ for (size_t i = 0; i < count; i++) { result = isc_hashmap_add(hashmap, NULL, nodes[i].key, 16, &nodes[i]); assert_int_equal(result, ISC_R_EXISTS); } for (size_t i = 0; i < count; i++) { result = isc_hashmap_add(hashmap, NULL, long_nodes[i].key, strlen((const char *)long_nodes[i].key), &long_nodes[i]); assert_int_equal(result, ISC_R_SUCCESS); } for (size_t i = 0; i < count; i++) { void *f = NULL; result = isc_hashmap_find(hashmap, NULL, upper_nodes[i].key, 16, &f); assert_int_equal(result, ISC_R_NOTFOUND); assert_null(f); } for (size_t i = 0; i < count; i++) { void *f = NULL; result = isc_hashmap_find( hashmap, NULL, long_nodes[i].key, strlen((const char *)long_nodes[i].key), &f); assert_int_equal(result, ISC_R_SUCCESS); assert_ptr_equal(f, &long_nodes[i]); } for (size_t i = 0; i < count; i++) { void *f = NULL; result = isc_hashmap_delete(hashmap, &nodes[i].hashval, nodes[i].key, 16); assert_int_equal(result, ISC_R_SUCCESS); result = isc_hashmap_find(hashmap, NULL, nodes[i].key, 16, &f); assert_int_equal(result, ISC_R_NOTFOUND); assert_null(f); } for (size_t i = 0; i < count; i++) { result = isc_hashmap_add(hashmap, NULL, upper_nodes[i].key, 16, &upper_nodes[i]); assert_int_equal(result, ISC_R_SUCCESS); } for (size_t i = 0; i < count; i++) { void *f = NULL; result = isc_hashmap_delete( hashmap, NULL, long_nodes[i].key, strlen((const char *)long_nodes[i].key)); assert_int_equal(result, ISC_R_SUCCESS); result = isc_hashmap_find( hashmap, NULL, long_nodes[i].key, strlen((const char *)long_nodes[i].key), &f); assert_int_equal(result, ISC_R_NOTFOUND); assert_null(f); } for (size_t i = 0; i < count; i++) { void *f = NULL; result = isc_hashmap_find(hashmap, NULL, upper_nodes[i].key, 16, &f); assert_int_equal(result, ISC_R_SUCCESS); assert_ptr_equal(f, &upper_nodes[i]); } for (size_t i = 0; i < count; i++) { void *f = NULL; result = isc_hashmap_find(hashmap, NULL, nodes[i].key, 16, &f); assert_int_equal(result, ISC_R_NOTFOUND); assert_null(f); } isc_hashmap_destroy(&hashmap); assert_null(hashmap); isc_mem_put(mctx, nodes, count * sizeof(nodes[0])); isc_mem_put(mctx, long_nodes, count * sizeof(nodes[0])); isc_mem_put(mctx, upper_nodes, count * sizeof(nodes[0])); } static void test_hashmap_iterator(void) { isc_hashmap_t *hashmap = NULL; isc_result_t result; isc_hashmap_iter_t *iter = NULL; size_t count = 7600; uint32_t walked; size_t tksize; test_node_t *nodes; nodes = isc_mem_get(mctx, count * sizeof(nodes[0])); isc_hashmap_create(mctx, HASHMAP_MIN_BITS, ISC_HASHMAP_CASE_SENSITIVE, &hashmap); assert_non_null(hashmap); for (size_t i = 0; i < count; i++) { /* short keys */ snprintf(nodes[i].key, 16, "%u", (unsigned int)i); strlcat(nodes[i].key, " key of a raw hashmap!!", 16); } for (size_t i = 0; i < count; i++) { result = isc_hashmap_add(hashmap, NULL, nodes[i].key, 16, &nodes[i]); assert_int_equal(result, ISC_R_SUCCESS); } /* We want to iterate while rehashing is in progress */ assert_true(rehashing_in_progress(hashmap)); walked = 0; isc_hashmap_iter_create(hashmap, &iter); for (result = isc_hashmap_iter_first(iter); result == ISC_R_SUCCESS; result = isc_hashmap_iter_next(iter)) { char key[16] = { 0 }; ptrdiff_t i; const uint8_t *tkey = NULL; test_node_t *v = NULL; isc_hashmap_iter_current(iter, (void *)&v); isc_hashmap_iter_currentkey(iter, &tkey, &tksize); assert_int_equal(tksize, 16); i = v - &nodes[0]; snprintf(key, 16, "%u", (unsigned int)i); strlcat(key, " key of a raw hashmap!!", 16); assert_memory_equal(key, tkey, 16); walked++; } assert_int_equal(walked, count); assert_int_equal(result, ISC_R_NOMORE); /* erase odd */ walked = 0; result = isc_hashmap_iter_first(iter); while (result == ISC_R_SUCCESS) { char key[16] = { 0 }; ptrdiff_t i; const uint8_t *tkey = NULL; test_node_t *v = NULL; isc_hashmap_iter_current(iter, (void *)&v); isc_hashmap_iter_currentkey(iter, &tkey, &tksize); assert_int_equal(tksize, 16); i = v - nodes; snprintf(key, 16, "%u", (unsigned int)i); strlcat(key, " key of a raw hashmap!!", 16); assert_memory_equal(key, tkey, 16); if (i % 2 == 0) { result = isc_hashmap_iter_delcurrent_next(iter); } else { result = isc_hashmap_iter_next(iter); } walked++; } assert_int_equal(result, ISC_R_NOMORE); assert_int_equal(walked, count); /* erase even */ walked = 0; result = isc_hashmap_iter_first(iter); while (result == ISC_R_SUCCESS) { char key[16] = { 0 }; ptrdiff_t i; const uint8_t *tkey = NULL; test_node_t *v = NULL; isc_hashmap_iter_current(iter, (void *)&v); isc_hashmap_iter_currentkey(iter, &tkey, &tksize); assert_int_equal(tksize, 16); i = v - nodes; snprintf(key, 16, "%u", (unsigned int)i); strlcat(key, " key of a raw hashmap!!", 16); assert_memory_equal(key, tkey, 16); if (i % 2 == 1) { result = isc_hashmap_iter_delcurrent_next(iter); } else { result = isc_hashmap_iter_next(iter); } walked++; } assert_int_equal(result, ISC_R_NOMORE); assert_int_equal(walked, count / 2); walked = 0; for (result = isc_hashmap_iter_first(iter); result == ISC_R_SUCCESS; result = isc_hashmap_iter_next(iter)) { walked++; } assert_int_equal(result, ISC_R_NOMORE); assert_int_equal(walked, 0); /* Iterator doesn't progress rehashing */ assert_true(rehashing_in_progress(hashmap)); isc_hashmap_iter_destroy(&iter); assert_null(iter); isc_hashmap_destroy(&hashmap); assert_null(hashmap); isc_mem_put(mctx, nodes, count * sizeof(nodes[0])); } /* 1 bit, 120 elements test, full rehashing */ ISC_RUN_TEST_IMPL(isc_hashmap_1_120) { test_hashmap_full(1, 120); return; } /* 6 bit, 1000 elements test, full rehashing */ ISC_RUN_TEST_IMPL(isc_hashmap_6_1000) { test_hashmap_full(6, 1000); return; } /* 24 bit, 200K elements test, no rehashing */ ISC_RUN_TEST_IMPL(isc_hashmap_24_200000) { test_hashmap_full(24, 200000); return; } /* 15 bit, 45K elements test, full rehashing */ ISC_RUN_TEST_IMPL(isc_hashmap_1_48000) { test_hashmap_full(1, 48000); return; } /* 8 bit, 20k elements test, partial rehashing */ ISC_RUN_TEST_IMPL(isc_hashmap_8_20000) { test_hashmap_full(8, 20000); return; } /* test hashmap iterator */ ISC_RUN_TEST_IMPL(isc_hashmap_iterator) { test_hashmap_iterator(); return; } ISC_RUN_TEST_IMPL(isc_hashmap_hash_zero_length) { isc_hashmap_t *hashmap = NULL; uint32_t hashval; bool again = false; again: isc_hashmap_create(mctx, 1, ISC_HASHMAP_CASE_SENSITIVE, &hashmap); hashval = isc_hashmap_hash(hashmap, "", 0); isc_hashmap_destroy(&hashmap); if (hashval == 0 && !again) { /* * We could be extremely unlock and the siphash could hash the * zero length string to 0, so try one more time. */ again = true; goto again; } assert_int_not_equal(hashval, 0); } ISC_RUN_TEST_IMPL(isc_hashmap_case) { isc_result_t result; isc_hashmap_t *hashmap = NULL; test_node_t lower = { .key = "isc_hashmap_case" }; test_node_t upper = { .key = "ISC_HASHMAP_CASE" }; test_node_t mixed = { .key = "IsC_hAsHmAp_CaSe" }; isc_hashmap_create(mctx, 1, ISC_HASHMAP_CASE_SENSITIVE, &hashmap); result = isc_hashmap_add(hashmap, NULL, lower.key, strlen(lower.key), &lower); assert_int_equal(result, ISC_R_SUCCESS); result = isc_hashmap_add(hashmap, NULL, lower.key, strlen(lower.key), &lower); assert_int_equal(result, ISC_R_EXISTS); result = isc_hashmap_add(hashmap, NULL, upper.key, strlen(upper.key), &upper); assert_int_equal(result, ISC_R_SUCCESS); result = isc_hashmap_find(hashmap, NULL, mixed.key, strlen(mixed.key), &(void *){ NULL }); assert_int_equal(result, ISC_R_NOTFOUND); isc_hashmap_destroy(&hashmap); isc_hashmap_create(mctx, 1, ISC_HASHMAP_CASE_INSENSITIVE, &hashmap); result = isc_hashmap_add(hashmap, NULL, lower.key, strlen(lower.key), &lower); assert_int_equal(result, ISC_R_SUCCESS); result = isc_hashmap_add(hashmap, NULL, lower.key, strlen(lower.key), &lower); assert_int_equal(result, ISC_R_EXISTS); result = isc_hashmap_add(hashmap, NULL, upper.key, strlen(upper.key), &upper); assert_int_equal(result, ISC_R_EXISTS); result = isc_hashmap_find(hashmap, NULL, mixed.key, strlen(mixed.key), &(void *){ NULL }); assert_int_equal(result, ISC_R_SUCCESS); isc_hashmap_destroy(&hashmap); } ISC_TEST_LIST_START ISC_TEST_ENTRY(isc_hashmap_hash_zero_length) ISC_TEST_ENTRY(isc_hashmap_case) ISC_TEST_ENTRY(isc_hashmap_1_120) ISC_TEST_ENTRY(isc_hashmap_6_1000) ISC_TEST_ENTRY(isc_hashmap_24_200000) ISC_TEST_ENTRY(isc_hashmap_1_48000) ISC_TEST_ENTRY(isc_hashmap_8_20000) ISC_TEST_ENTRY(isc_hashmap_iterator) ISC_TEST_LIST_END ISC_TEST_MAIN