The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
hash_tests.c
Go to the documentation of this file.
1/*
2 * This library is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2.1 of the License, or (at your option) any later version.
6 *
7 * This library is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with this library; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/** Tests for the hash table
18 *
19 * @file src/lib/util/test/hash_tests.c
20 *
21 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
22 */
23#include "acutest.h"
24#include "acutest_helpers.h"
25
26#include <freeradius-devel/util/hash.h>
27#include <freeradius-devel/util/rand.h>
28
29typedef struct {
31 char name[32];
33
34static uint32_t hash_test_hash(void const *data)
35{
36 hash_test_node_t const *n = data;
37
38 return fr_hash(&n->num, sizeof(n->num));
39}
40
41static int8_t hash_test_cmp(void const *one, void const *two)
42{
43 hash_test_node_t const *a = one, *b = two;
44
45 return CMP(a->num, b->num);
46}
47
48/*
49 * Test basic hash function operations.
50 */
51static void test_hash_functions(void)
52{
53 uint32_t h1, h2, h3;
54
55 TEST_CASE("fr_hash produces consistent results");
56 h1 = fr_hash("hello", 5);
57 h2 = fr_hash("hello", 5);
58 TEST_CHECK(h1 == h2);
59
60 TEST_CASE("Different inputs produce different hashes");
61 h3 = fr_hash("world", 5);
62 TEST_CHECK(h1 != h3);
63
64 TEST_CASE("fr_hash_string works");
65 h1 = fr_hash_string("test");
66 h2 = fr_hash_string("test");
67 TEST_CHECK(h1 == h2);
68
69 h3 = fr_hash_string("other");
70 TEST_CHECK(h1 != h3);
71
72 TEST_CASE("fr_hash_case_string is case insensitive");
73 h1 = fr_hash_case_string("Hello");
74 h2 = fr_hash_case_string("hello");
75 TEST_CHECK(h1 == h2);
76
77 h3 = fr_hash_case_string("HELLO");
78 TEST_CHECK(h1 == h3);
79
80 TEST_CASE("fr_hash_update for incremental hashing");
81 h1 = fr_hash("helloworld", 10);
82 h2 = fr_hash("hello", 5);
83 h2 = fr_hash_update("world", 5, h2);
84 TEST_CHECK(h1 == h2);
85}
86
87/*
88 * Test hash64 functions.
89 */
90static void test_hash64_functions(void)
91{
92 uint64_t h1, h2, h3;
93
94 TEST_CASE("fr_hash64 produces consistent results");
95 h1 = fr_hash64("hello", 5);
96 h2 = fr_hash64("hello", 5);
97 TEST_CHECK(h1 == h2);
98
99 TEST_CASE("Different inputs produce different 64-bit hashes");
100 h3 = fr_hash64("world", 5);
101 TEST_CHECK(h1 != h3);
102
103 TEST_CASE("fr_hash64_update for incremental hashing");
104 h1 = fr_hash64("helloworld", 10);
105 h2 = fr_hash64("hello", 5);
106 h2 = fr_hash64_update("world", 5, h2);
107 TEST_CHECK(h1 == h2);
108}
109
110/*
111 * Test hash table creation and basic insert/find.
112 */
113static void test_hash_table_basic(void)
114{
115 fr_hash_table_t *ht;
116 hash_test_node_t node, *found;
117
119 TEST_ASSERT(ht != NULL);
120
121 TEST_CASE("Empty table has 0 elements");
123
124 TEST_CASE("Insert an element");
125 node.num = 42;
126 snprintf(node.name, sizeof(node.name), "node-%u", node.num);
128
130
131 TEST_CASE("Find the element");
132 found = fr_hash_table_find(ht, &node);
133 TEST_CHECK(found != NULL);
134 if (found) {
135 TEST_CHECK(found->num == 42);
136 }
137
138 TEST_CASE("Find non-existent element returns NULL");
139 found = fr_hash_table_find(ht, &(hash_test_node_t) { .num = 999 });
140 TEST_CHECK(found == NULL);
141
142 talloc_free(ht);
143}
144
145#define HASH_TEST_SIZE 1024
146
147/*
148 * Test insert, find, and remove with many elements.
149 */
150static void test_hash_table_many(void)
151{
152 fr_hash_table_t *ht;
153 hash_test_node_t *nodes;
154 int i;
155
157 TEST_ASSERT(ht != NULL);
158
159 nodes = calloc(HASH_TEST_SIZE, sizeof(hash_test_node_t));
160
161 TEST_CASE("Insert many elements");
162 for (i = 0; i < HASH_TEST_SIZE; i++) {
163 nodes[i].num = i;
164 snprintf(nodes[i].name, sizeof(nodes[i].name), "node-%d", i);
165 TEST_CHECK(fr_hash_table_insert(ht, &nodes[i]));
166 TEST_MSG("insert %d failed", i);
167 }
169
170 TEST_CASE("Find all elements");
171 for (i = 0; i < HASH_TEST_SIZE; i++) {
172 hash_test_node_t *found;
173
174 found = fr_hash_table_find(ht, &(hash_test_node_t) { .num = i });
175 TEST_CHECK(found != NULL);
176 TEST_MSG("find %d failed", i);
177
178 if (found) {
179 TEST_CHECK(found->num == (uint32_t) i);
180 }
181 }
182
183 TEST_CASE("Remove half the elements");
184 for (i = 0; i < HASH_TEST_SIZE; i += 2) {
185 void *removed;
186
187 removed = fr_hash_table_remove(ht, &(hash_test_node_t) { .num = i });
188 TEST_CHECK(removed != NULL);
189 TEST_MSG("remove %d failed", i);
190 }
192
193 TEST_CASE("Verify removed elements are gone and remaining are present");
194 for (i = 0; i < HASH_TEST_SIZE; i++) {
195 hash_test_node_t *found;
196
197 found = fr_hash_table_find(ht, &(hash_test_node_t) { .num = i });
198 if (i % 2 == 0) {
199 TEST_CHECK(found == NULL);
200 TEST_MSG("element %d should have been removed", i);
201 } else {
202 TEST_CHECK(found != NULL);
203 TEST_MSG("element %d should still be present", i);
204 }
205 }
206
207 talloc_free(ht);
208 free(nodes);
209}
210
211/*
212 * Test duplicate insertion fails.
213 */
215{
216 fr_hash_table_t *ht;
217 hash_test_node_t node;
218
220 TEST_ASSERT(ht != NULL);
221
222 node.num = 1;
223 TEST_CASE("First insert succeeds");
225
226 TEST_CASE("Duplicate insert fails");
227 TEST_CHECK(!fr_hash_table_insert(ht, &node));
228
230
231 talloc_free(ht);
232}
233
234/*
235 * Test replace operation.
236 */
237static void test_hash_table_replace(void)
238{
239 fr_hash_table_t *ht;
240 hash_test_node_t node1, node2;
241 hash_test_node_t *found;
242 void *old = NULL;
243 int ret;
244
246 TEST_ASSERT(ht != NULL);
247
248 node1.num = 1;
249 snprintf(node1.name, sizeof(node1.name), "first");
250 node2.num = 1;
251 snprintf(node2.name, sizeof(node2.name), "second");
252
253 TEST_CASE("Replace on empty table inserts");
254 ret = fr_hash_table_replace(&old, ht, &node1);
255 TEST_CHECK(ret == 1);
256 TEST_CHECK(old == NULL);
258
259 TEST_CASE("Replace existing element");
260 ret = fr_hash_table_replace(&old, ht, &node2);
261 TEST_CHECK(ret == 0);
262 TEST_CHECK(old == &node1);
264
265 TEST_CASE("Find returns the replacement");
266 found = fr_hash_table_find(ht, &node2);
267 TEST_CHECK(found == &node2);
268
269 talloc_free(ht);
270}
271
272/*
273 * Test delete operation (with free callback).
274 */
275static void test_hash_table_delete(void)
276{
277 fr_hash_table_t *ht;
278 hash_test_node_t *node;
279
281 TEST_ASSERT(ht != NULL);
282
283 node = talloc(NULL, hash_test_node_t);
284 node->num = 42;
285
288
289 TEST_CASE("Delete removes and frees element");
292
293 TEST_CASE("Delete non-existent element returns false");
294 {
295 hash_test_node_t missing = { .num = 999 };
296 TEST_CHECK(!fr_hash_table_delete(ht, &missing));
297 }
298
299 talloc_free(ht);
300}
301
302/*
303 * Test iteration over the hash table.
304 */
305static void test_hash_table_iter(void)
306{
307 fr_hash_table_t *ht;
308 hash_test_node_t nodes[16];
309 fr_hash_iter_t iter;
311 unsigned int count = 0;
312 uint32_t seen = 0;
313 int i;
314
316 TEST_ASSERT(ht != NULL);
317
318 for (i = 0; i < 16; i++) {
319 nodes[i].num = i;
320 fr_hash_table_insert(ht, &nodes[i]);
321 }
322
323 TEST_CASE("Iterate over all elements");
324 for (p = fr_hash_table_iter_init(ht, &iter);
325 p;
326 p = fr_hash_table_iter_next(ht, &iter)) {
327 TEST_CHECK(p->num < 16);
328 TEST_CHECK((seen & (1U << p->num)) == 0);
329 TEST_MSG("element %u seen twice", p->num);
330 seen |= (1U << p->num);
331 count++;
332 }
333
334 TEST_CHECK(count == 16);
335 TEST_CHECK(seen == 0xffff);
336
337 talloc_free(ht);
338}
339
340/*
341 * Test the flatten operation.
342 */
343static void test_hash_table_flatten(void)
344{
345 fr_hash_table_t *ht;
346 hash_test_node_t nodes[8];
347 hash_test_node_t **flat = NULL;
348 int ret, i;
349
351 TEST_ASSERT(ht != NULL);
352
353 for (i = 0; i < 8; i++) {
354 nodes[i].num = i;
355 fr_hash_table_insert(ht, &nodes[i]);
356 }
357
358 TEST_CASE("Flatten table into array");
359 ret = fr_hash_table_flatten(NULL, (void ***)&flat, ht);
360 TEST_CHECK(ret == 0);
361 TEST_CHECK(flat != NULL);
362
363 if (flat) {
364 uint32_t seen = 0;
365
366 for (i = 0; i < 8; i++) {
367 TEST_CHECK(flat[i] != NULL);
368 if (flat[i]) {
369 TEST_CHECK(flat[i]->num < 8);
370 seen |= (1U << flat[i]->num);
371 }
372 }
373 TEST_CHECK(seen == 0xff);
374
375 talloc_free(flat);
376 }
377
378 talloc_free(ht);
379}
380
382 { "hash_functions", test_hash_functions },
383 { "hash64_functions", test_hash64_functions },
384 { "hash_table_basic", test_hash_table_basic },
385 { "hash_table_many", test_hash_table_many },
386 { "hash_table_duplicate", test_hash_table_duplicate },
387 { "hash_table_replace", test_hash_table_replace },
388 { "hash_table_delete", test_hash_table_delete },
389 { "hash_table_iter", test_hash_table_iter },
390 { "hash_table_flatten", test_hash_table_flatten },
392};
#define TEST_CHECK(cond)
Definition acutest.h:87
int n
Definition acutest.h:579
#define TEST_CASE(name)
Definition acutest.h:186
#define TEST_ASSERT(cond)
Definition acutest.h:110
#define TEST_TERMINATOR
Definition acutest.h:64
#define TEST_MSG(...)
Definition acutest.h:217
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
Definition build.h:112
void * fr_hash_table_iter_next(fr_hash_table_t *ht, fr_hash_iter_t *iter)
Iterate over entries in a hash table.
Definition hash.c:649
void * fr_hash_table_find(fr_hash_table_t *ht, void const *data)
Find data in a hash table.
Definition hash.c:450
void * fr_hash_table_iter_init(fr_hash_table_t *ht, fr_hash_iter_t *iter)
Initialise an iterator.
Definition hash.c:704
uint32_t fr_hash_case_string(char const *p)
Hash a C string, converting all chars to lowercase.
Definition hash.c:916
uint32_t fr_hash_update(void const *data, size_t size, uint32_t hash)
Definition hash.c:881
uint32_t fr_hash(void const *data, size_t size)
Definition hash.c:847
uint64_t fr_hash64(void const *data, size_t size)
Definition hash.c:940
bool fr_hash_table_insert(fr_hash_table_t *ht, void const *data)
Insert data into a hash table.
Definition hash.c:489
void * fr_hash_table_remove(fr_hash_table_t *ht, void const *data)
Remove an entry from the hash table, without freeing the data.
Definition hash.c:582
int fr_hash_table_flatten(TALLOC_CTX *ctx, void **out[], fr_hash_table_t *ht)
Copy all entries out of a hash table into an array.
Definition hash.c:721
uint32_t fr_hash_string(char const *p)
Definition hash.c:900
bool fr_hash_table_delete(fr_hash_table_t *ht, void const *data)
Remove and free data (if a free function was specified)
Definition hash.c:617
uint64_t fr_hash64_update(void const *data, size_t size, uint64_t hash)
Definition hash.c:968
int fr_hash_table_replace(void **old, fr_hash_table_t *ht, void const *data)
Replace old data with new data, OR insert if there is no old.
Definition hash.c:551
uint32_t fr_hash_table_num_elements(fr_hash_table_t *ht)
Definition hash.c:633
#define fr_hash_table_alloc(_ctx, _hash_node, _cmp_node, _free_node)
Definition hash.h:61
Stores the state of the current iteration operation.
Definition hash.h:41
static void test_hash_table_iter(void)
Definition hash_tests.c:305
static void test_hash_functions(void)
Definition hash_tests.c:51
static void test_hash_table_replace(void)
Definition hash_tests.c:237
TEST_LIST
Definition hash_tests.c:381
static int8_t hash_test_cmp(void const *one, void const *two)
Definition hash_tests.c:41
static void test_hash_table_duplicate(void)
Definition hash_tests.c:214
static void test_hash_table_basic(void)
Definition hash_tests.c:113
static void test_hash_table_delete(void)
Definition hash_tests.c:275
static uint32_t hash_test_hash(void const *data)
Definition hash_tests.c:34
static void test_hash_table_many(void)
Definition hash_tests.c:150
static void test_hash64_functions(void)
Definition hash_tests.c:90
static void test_hash_table_flatten(void)
Definition hash_tests.c:343
#define HASH_TEST_SIZE
Definition hash_tests.c:145
free(array)
talloc_free(hp)
unsigned int uint32_t
static char const * name
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition snprintf.c:689
return count
Definition module.c:155
void talloc_free_data(void *data)
A wrapper that can be passed to tree or hash alloc functions that take a fr_free_t.
Definition talloc.c:37
static fr_slen_t data
Definition value.h:1340