Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
hashmap_tests.h
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <[email protected]>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19#pragma once
20
21#include <stdio.h>
22#include <string.h>
23
24#include <serene/rt/context.h>
26
27#include "acutest.h"
28#include "base.h"
29
30#define HMAP_TESTS(X) \
31 X("hashmap::empty", test_hashmap_empty), \
32 X("hashmap::deterministic_hash_fn", test_hashmap_make_sure_hash_is_deterministic), \
33 X("hashmap::insert_new_kv", test_hashmap_insert_kv), \
34 X("hashmap::many_insert_and_lookup", test_hashmap_many_insert_and_lookup), \
35 X("hashmap::collision_lookup", test_hashmap_collision_node_lookup), \
36 X("hashmap::max_depth_divergence", test_hashmap_max_depth_divergence), \
37 X("hashmap::update", test_hashmap_update)
38
39#define HMAP_TEST_LOG(...) DBG("HMAP_TEST", __VA_ARGS__)
40
41typedef struct hashmap_dummy {
42 int foo;
43 int bar;
45
46static void test_hashmap_empty() {
47 MAKE_ENGINE(mm, engine);
48 MAKE_CONTEXT(engine, ctx);
49
50 auto s = hmap_empty(ctx);
51
52 TEST_CHECK(s.len == 0);
53 TEST_CHECK(s.root == nullptr);
54 // TEST_CHECK(s.maybe_error == nullptr);
55 RELEASE_CONTEXT(ctx);
56 SHUTDOWN_ENGINE(mm, engine);
57}
58
60 MAKE_ENGINE(mm, engine);
61 MAKE_CONTEXT(engine, ctx);
62
63 hashmap_dummy d = {.foo = 10, .bar = 20}; // NOLINT
64 hmap_t h = hmap_empty(ctx);
65
66 int key = 1236;
67 int key_missing = 342;
68
69 hmap_key_t k = {.data = &key, .len = sizeof(key)};
70 hmap_key_t k_missing = {.data = &key_missing, .len = sizeof(key_missing)};
71
72 hmap_t h1 = hmap_insert(&h, &k, &d);
73
74 void *dptr = hmap_lookup(&h1, &k, nullptr);
75 TEST_CHECK(dptr != nullptr);
76 TEST_CHECK(dptr == &d);
77
78 int default_v = 999;
79 void *dptr_missing = hmap_lookup(&h1, &k_missing, &default_v);
80 TEST_CHECK(dptr_missing != nullptr);
81 TEST_CHECK(*(int *)dptr_missing == 999);
82
83 (void)h1;
84
85 RELEASE_CONTEXT(ctx);
86 SHUTDOWN_ENGINE(mm, engine);
87}
88
90 MAKE_ENGINE(mm, engine);
91 MAKE_CONTEXT(engine, ctx);
92
93 int x = 10;
94
95 for (int i = 0; i < 100; i++) {
96 uint32_t h1 = hmap_hash(ctx, &x, sizeof(x));
97 uint32_t h2 = hmap_hash(ctx, &x, sizeof(x));
98 TEST_CHECK(h1 == h2);
99 }
100
101 RELEASE_CONTEXT(ctx);
102 SHUTDOWN_ENGINE(mm, engine);
103}
105 MAKE_ENGINE(mm, engine);
106 MAKE_CONTEXT(engine, ctx);
107
108 constexpr int N = 4096;
109
110 int *keys = ALLOCN(ctx, int, N);
111 hmap_key_t *ks = ALLOCN(ctx, hmap_key_t, N);
112 hashmap_dummy *vals = ALLOCN(ctx, hashmap_dummy, N);
113
114 hmap_t h = hmap_empty(ctx);
115
116 for (int i = 0; i < N; ++i) {
117 keys[i] = i;
118 ks[i].data = &keys[i];
119 ks[i].len = sizeof(keys[i]);
120
121 vals[i].foo = i;
122 vals[i].bar = i * 10;
123
124 h = hmap_insert(&h, &ks[i], &vals[i]);
125 }
126
127 TEST_CHECK(h.len == (size_t)N);
128 TEST_CHECK(h.root != nullptr);
129 // TEST_CHECK(h.maybe_error == nullptr);
130
131 // Lookup everything
132 for (int i = 0; i < N; ++i) {
133 void *p = hmap_lookup(&h, &ks[i], nullptr);
134 TEST_CHECK(p != nullptr);
135
137 TEST_ASSERT(d != nullptr);
138 TEST_CHECK(d->foo == i);
139 TEST_CHECK(d->bar == i * 10);
140 }
141
142 // update the keys (effectively inserting new keys)
143 for (int i = 0; i < N; ++i) {
144 vals[i].foo = i + i;
145 vals[i].bar = i * 10 + i;
146
147 h = hmap_insert(&h, &ks[i], &vals[i]);
148 }
149
150 TEST_CHECK(h.len == (size_t)N);
151 TEST_CHECK(h.root != nullptr);
152 // TEST_CHECK(h.maybe_error == nullptr);
153
154 // Lookup everything again!
155 for (int i = 0; i < N; ++i) {
156 void *p = hmap_lookup(&h, &ks[i], nullptr);
157 TEST_CHECK(p != nullptr);
158
160 TEST_CHECK(d->foo == i + i);
161 TEST_CHECK(d->bar == i * 10 + i);
162 }
163
164 // Missing key returns default value (since nullptr can be a legit value).
165 int missing_key = 0x7fffff;
166 hmap_key_t kmiss = {.data = &missing_key, .len = sizeof(missing_key)};
167
168 int default_v = 999;
169 void *p = hmap_lookup(&h, &kmiss, &default_v);
170 TEST_CHECK(p == &default_v);
171 TEST_CHECK(*(int *)p == 999);
172
173 RELEASE_CONTEXT(ctx);
174 SHUTDOWN_ENGINE(mm, engine);
175}
176
178 MAKE_ENGINE(mm, engine);
179 MAKE_CONTEXT(engine, ctx);
180
181 hmap_t h = hmap_empty(ctx);
182
183 int a = 111;
184 int b = 222;
185
186 hmap_key_t ka = {.data = &a, .len = sizeof(a)};
187 hmap_key_t kb = {.data = &b, .len = sizeof(b)};
188
189 hashmap_dummy va = {.foo = 1, .bar = 10};
190 hashmap_dummy vb = {.foo = 2, .bar = 20};
191
192 // Insert two distinct keys that will almost certainly share the same mocked
193 // hash.
194 hmap_t h1 = hmap_insert(&h, &ka, &va);
195 hmap_t h2 = hmap_insert(&h1, &kb, &vb);
196
197 TEST_CHECK(h2.len == 2);
198 TEST_CHECK(h2.root != nullptr);
199 // TEST_CHECK(h2.maybe_error == nullptr);
200
201 void *pa = hmap_lookup(&h2, &ka, nullptr);
202 void *pb = hmap_lookup(&h2, &kb, nullptr);
203
204 TEST_CHECK(pa == &va);
205 TEST_CHECK(pb == &vb);
206
207 RELEASE_CONTEXT(ctx);
208 SHUTDOWN_ENGINE(mm, engine);
209}
210
211/// Hash hook that reads the hash straight from the key's first four bytes,
212/// so a test can craft hashes with an exact bit pattern.
214 (void)hmap;
215 hmap_hash_t h = 0;
216 memcpy(&h, k->data, sizeof(h));
217 return h;
218}
219
220/// Two keys whose hashes agree on bits 0-29 (every full 5 bit fragment) and
221/// differ only in bits 30-31 diverge at the trie's maximum depth. They must
222/// end up in separate data slots, not in one collision node keyed by a single
223/// bit position. A third key with a fully identical hash must still land in a
224/// real collision node next to them.
226 MAKE_ENGINE(mm, engine);
227 MAKE_CONTEXT(engine, ctx);
228
231
232 // First four bytes are the crafted hash; the tag makes each key distinct.
233 struct crafted_key {
234 uint32_t hash;
235 uint32_t tag;
236 };
237
238 struct crafted_key ka_bytes = {.hash = 0x00000001U, .tag = 1};
239 struct crafted_key kb_bytes = {.hash = 0x00000001U | (1U << 30U), .tag = 2};
240 struct crafted_key kc_bytes = {.hash = 0x00000001U, .tag = 3};
241
242 hmap_key_t ka = {.data = &ka_bytes, .len = sizeof(ka_bytes)};
243 hmap_key_t kb = {.data = &kb_bytes, .len = sizeof(kb_bytes)};
244 hmap_key_t kc = {.data = &kc_bytes, .len = sizeof(kc_bytes)};
245
246 int va = 1;
247 int vb = 2;
248 int vc = 3;
249
250 hmap_t h0 = hmap_empty(ctx);
251 // Inserting kb forces the merge at maximum depth with divergent final
252 // fragments; inserting kc forces a genuine full collision with ka.
253 hmap_t h1 = hmap_insert_ctl(&ctl, &h0, &ka, &va);
254 hmap_t h2 = hmap_insert_ctl(&ctl, &h1, &kb, &vb);
255 hmap_t h3 = hmap_insert_ctl(&ctl, &h2, &kc, &vc);
256
257 TEST_CHECK(h3.len == 3);
258
259 TEST_CHECK(hmap_lookup_ctl(&ctl, &h3, &ka, nullptr) == &va);
260 TEST_CHECK(hmap_lookup_ctl(&ctl, &h3, &kb, nullptr) == &vb);
261 TEST_CHECK(hmap_lookup_ctl(&ctl, &h3, &kc, nullptr) == &vc);
262
263 RELEASE_CONTEXT(ctx);
264 SHUTDOWN_ENGINE(mm, engine);
265}
266
267static void test_hashmap_update() {
268 MAKE_ENGINE(mm, engine);
269 MAKE_CONTEXT(engine, ctx);
270
271 hmap_t h = hmap_empty(ctx);
272
273 int a = 111;
274 int b = 222;
275
276 hmap_key_t ka = {.data = &a, .len = sizeof(a)};
277 hmap_key_t kb = {.data = &b, .len = sizeof(b)};
278
279 hashmap_dummy va = {.foo = 1, .bar = 10};
280 hashmap_dummy vb = {.foo = 2, .bar = 20};
281 hashmap_dummy vc = {.foo = 200, .bar = 400};
282
283 // Insert two distinct keys that will almost certainly share the same mocked
284 // hash.
285 hmap_t h1 = hmap_insert(&h, &ka, &va);
286 hmap_t h2 = hmap_insert(&h1, &kb, &vb);
287 hmap_t h3 = hmap_insert(&h2, &ka, &vc);
288
289 TEST_CHECK(h3.len == 2);
290 TEST_CHECK(h3.root != nullptr);
291 // TEST_CHECK(h3.maybe_error == nullptr);
292
293 void *pa = hmap_lookup(&h3, &ka, nullptr);
294 void *pb = hmap_lookup(&h3, &kb, nullptr);
295
296 TEST_CHECK(pa == &vc);
297 TEST_CHECK(pb == &vb);
298
299 RELEASE_CONTEXT(ctx);
300 SHUTDOWN_ENGINE(mm, engine);
301}
#define TEST_CHECK(cond)
Definition acutest.h:95
#define TEST_ASSERT(cond)
Definition acutest.h:117
#define RELEASE_CONTEXT(x)
Definition base.h:46
#define SHUTDOWN_ENGINE(mm, engine)
Definition base.h:38
#define MAKE_ENGINE(mm, engine)
Definition base.h:32
#define MAKE_CONTEXT(engine, x)
Definition base.h:42
#define ALLOCN(ctx, T, N)
Definition context.h:85
hmap_t hmap_insert(const hmap_t *hmap, hmap_key_t *k, void *v)
Insert the given key k with the value v in the given hash hmap and return the new map.
Definition hashmap.c:661
hmap_hash_t hmap_hash(const srn_context_t *ctx, const void *data, size_t len)
This is a simple hack to mock the hash function during tests to force a high collision hashing functi...
Definition hashmap.c:77
hmap_t hmap_empty(const srn_context_t *ctx)
Create, initialize and return a new hashmap pinned to ctx.
Definition hashmap.c:655
void * hmap_lookup_ctl(const hmap_control_t *ctl, const hmap_t *hmap, const hmap_key_t *k, void *default_value)
Just like the hmap_lookup function but, it receives a hmap_control_t to customize the comparison and ...
Definition hashmap.c:638
hmap_t hmap_insert_ctl(const hmap_control_t *ctl, const hmap_t *hmap, hmap_key_t *k, void *v)
Just like the hmap_insert function but, it receives a hmap_control_t to customize the comparison and ...
Definition hashmap.c:594
void * hmap_lookup(const hmap_t *hmap, const hmap_key_t *k, void *default_value)
Lookup the given k in the given hmap and return the value if it's been found.
Definition hashmap.c:665
hmap_control_t hmap_default_control
Definition hashmap.c:690
This is an implementation of Compressed Hash-Array Mapped Prefix-tree, which is a bit-partitioned,...
HMAP_HASH_TYPE hmap_hash_t
Definition hashmap.h:61
static void test_hashmap_make_sure_hash_is_deterministic()
static void test_hashmap_empty()
static void test_hashmap_collision_node_lookup()
static void test_hashmap_many_insert_and_lookup()
static hmap_hash_t test_hash_from_key_bytes(const hmap_t *hmap, const hmap_key_t *k)
Hash hook that reads the hash straight from the key's first four bytes, so a test can craft hashes wi...
static void test_hashmap_max_depth_divergence()
Two keys whose hashes agree on bits 0-29 (every full 5 bit fragment) and differ only in bits 30-31 di...
void test_hashmap_insert_kv()
static void test_hashmap_update()
If we ever want to modify some of these behaviours for a new instance of hashmap, we should use this ...
Definition hashmap.h:109
hmap_hash_t(* hash)(const hmap_t *hmap, const hmap_key_t *k)
Definition hashmap.h:111
Note: For key equality we use the memcpy function.
Definition hashmap.h:66
void * data
len 0 -> data == nullptr
Definition hashmap.h:68
size_t len
Definition hashmap.h:69
hmap_node_t * root
Definition hashmap.h:98
size_t len
Number of key/value pairs in the map.
Definition hashmap.h:97