Skip to content

Commit 5d04ec6

Browse files
anakryikoAlexei Starovoitov
authored andcommitted
selftests/bpf: add tests for libbpf's hashmap
Test all APIs for internal hashmap implementation. Signed-off-by: Andrii Nakryiko <andriin@fb.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org>
1 parent e3b9242 commit 5d04ec6

File tree

3 files changed

+384
-1
lines changed

3 files changed

+384
-1
lines changed

tools/testing/selftests/bpf/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ test_sysctl
3535
alu32
3636
libbpf.pc
3737
libbpf.so.*
38+
test_hashmap

tools/testing/selftests/bpf/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test
2323
test_align test_verifier_log test_dev_cgroup test_tcpbpf_user \
2424
test_sock test_btf test_sockmap test_lirc_mode2_user get_cgroup_id_user \
2525
test_socket_cookie test_cgroup_storage test_select_reuseport test_section_names \
26-
test_netcnt test_tcpnotify_user test_sock_fields test_sysctl
26+
test_netcnt test_tcpnotify_user test_sock_fields test_sysctl test_hashmap
2727

2828
BPF_OBJ_FILES = $(patsubst %.c,%.o, $(notdir $(wildcard progs/*.c)))
2929
TEST_GEN_FILES = $(BPF_OBJ_FILES)
Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2+
3+
/*
4+
* Tests for libbpf's hashmap.
5+
*
6+
* Copyright (c) 2019 Facebook
7+
*/
8+
#include <stdio.h>
9+
#include <errno.h>
10+
#include <linux/err.h>
11+
#include "hashmap.h"
12+
13+
#define CHECK(condition, format...) ({ \
14+
int __ret = !!(condition); \
15+
if (__ret) { \
16+
fprintf(stderr, "%s:%d:FAIL ", __func__, __LINE__); \
17+
fprintf(stderr, format); \
18+
} \
19+
__ret; \
20+
})
21+
22+
size_t hash_fn(const void *k, void *ctx)
23+
{
24+
return (long)k;
25+
}
26+
27+
bool equal_fn(const void *a, const void *b, void *ctx)
28+
{
29+
return (long)a == (long)b;
30+
}
31+
32+
static inline size_t next_pow_2(size_t n)
33+
{
34+
size_t r = 1;
35+
36+
while (r < n)
37+
r <<= 1;
38+
return r;
39+
}
40+
41+
static inline size_t exp_cap(size_t sz)
42+
{
43+
size_t r = next_pow_2(sz);
44+
45+
if (sz * 4 / 3 > r)
46+
r <<= 1;
47+
return r;
48+
}
49+
50+
#define ELEM_CNT 62
51+
52+
int test_hashmap_generic(void)
53+
{
54+
struct hashmap_entry *entry, *tmp;
55+
int err, bkt, found_cnt, i;
56+
long long found_msk;
57+
struct hashmap *map;
58+
59+
fprintf(stderr, "%s: ", __func__);
60+
61+
map = hashmap__new(hash_fn, equal_fn, NULL);
62+
if (CHECK(IS_ERR(map), "failed to create map: %ld\n", PTR_ERR(map)))
63+
return 1;
64+
65+
for (i = 0; i < ELEM_CNT; i++) {
66+
const void *oldk, *k = (const void *)(long)i;
67+
void *oldv, *v = (void *)(long)(1024 + i);
68+
69+
err = hashmap__update(map, k, v, &oldk, &oldv);
70+
if (CHECK(err != -ENOENT, "unexpected result: %d\n", err))
71+
return 1;
72+
73+
if (i % 2) {
74+
err = hashmap__add(map, k, v);
75+
} else {
76+
err = hashmap__set(map, k, v, &oldk, &oldv);
77+
if (CHECK(oldk != NULL || oldv != NULL,
78+
"unexpected k/v: %p=%p\n", oldk, oldv))
79+
return 1;
80+
}
81+
82+
if (CHECK(err, "failed to add k/v %ld = %ld: %d\n",
83+
(long)k, (long)v, err))
84+
return 1;
85+
86+
if (CHECK(!hashmap__find(map, k, &oldv),
87+
"failed to find key %ld\n", (long)k))
88+
return 1;
89+
if (CHECK(oldv != v, "found value is wrong: %ld\n", (long)oldv))
90+
return 1;
91+
}
92+
93+
if (CHECK(hashmap__size(map) != ELEM_CNT,
94+
"invalid map size: %zu\n", hashmap__size(map)))
95+
return 1;
96+
if (CHECK(hashmap__capacity(map) != exp_cap(hashmap__size(map)),
97+
"unexpected map capacity: %zu\n", hashmap__capacity(map)))
98+
return 1;
99+
100+
found_msk = 0;
101+
hashmap__for_each_entry(map, entry, bkt) {
102+
long k = (long)entry->key;
103+
long v = (long)entry->value;
104+
105+
found_msk |= 1ULL << k;
106+
if (CHECK(v - k != 1024, "invalid k/v pair: %ld = %ld\n", k, v))
107+
return 1;
108+
}
109+
if (CHECK(found_msk != (1ULL << ELEM_CNT) - 1,
110+
"not all keys iterated: %llx\n", found_msk))
111+
return 1;
112+
113+
for (i = 0; i < ELEM_CNT; i++) {
114+
const void *oldk, *k = (const void *)(long)i;
115+
void *oldv, *v = (void *)(long)(256 + i);
116+
117+
err = hashmap__add(map, k, v);
118+
if (CHECK(err != -EEXIST, "unexpected add result: %d\n", err))
119+
return 1;
120+
121+
if (i % 2)
122+
err = hashmap__update(map, k, v, &oldk, &oldv);
123+
else
124+
err = hashmap__set(map, k, v, &oldk, &oldv);
125+
126+
if (CHECK(err, "failed to update k/v %ld = %ld: %d\n",
127+
(long)k, (long)v, err))
128+
return 1;
129+
if (CHECK(!hashmap__find(map, k, &oldv),
130+
"failed to find key %ld\n", (long)k))
131+
return 1;
132+
if (CHECK(oldv != v, "found value is wrong: %ld\n", (long)oldv))
133+
return 1;
134+
}
135+
136+
if (CHECK(hashmap__size(map) != ELEM_CNT,
137+
"invalid updated map size: %zu\n", hashmap__size(map)))
138+
return 1;
139+
if (CHECK(hashmap__capacity(map) != exp_cap(hashmap__size(map)),
140+
"unexpected map capacity: %zu\n", hashmap__capacity(map)))
141+
return 1;
142+
143+
found_msk = 0;
144+
hashmap__for_each_entry_safe(map, entry, tmp, bkt) {
145+
long k = (long)entry->key;
146+
long v = (long)entry->value;
147+
148+
found_msk |= 1ULL << k;
149+
if (CHECK(v - k != 256,
150+
"invalid updated k/v pair: %ld = %ld\n", k, v))
151+
return 1;
152+
}
153+
if (CHECK(found_msk != (1ULL << ELEM_CNT) - 1,
154+
"not all keys iterated after update: %llx\n", found_msk))
155+
return 1;
156+
157+
found_cnt = 0;
158+
hashmap__for_each_key_entry(map, entry, (void *)0) {
159+
found_cnt++;
160+
}
161+
if (CHECK(!found_cnt, "didn't find any entries for key 0\n"))
162+
return 1;
163+
164+
found_msk = 0;
165+
found_cnt = 0;
166+
hashmap__for_each_key_entry_safe(map, entry, tmp, (void *)0) {
167+
const void *oldk, *k;
168+
void *oldv, *v;
169+
170+
k = entry->key;
171+
v = entry->value;
172+
173+
found_cnt++;
174+
found_msk |= 1ULL << (long)k;
175+
176+
if (CHECK(!hashmap__delete(map, k, &oldk, &oldv),
177+
"failed to delete k/v %ld = %ld\n",
178+
(long)k, (long)v))
179+
return 1;
180+
if (CHECK(oldk != k || oldv != v,
181+
"invalid deleted k/v: expected %ld = %ld, got %ld = %ld\n",
182+
(long)k, (long)v, (long)oldk, (long)oldv))
183+
return 1;
184+
if (CHECK(hashmap__delete(map, k, &oldk, &oldv),
185+
"unexpectedly deleted k/v %ld = %ld\n",
186+
(long)oldk, (long)oldv))
187+
return 1;
188+
}
189+
190+
if (CHECK(!found_cnt || !found_msk,
191+
"didn't delete any key entries\n"))
192+
return 1;
193+
if (CHECK(hashmap__size(map) != ELEM_CNT - found_cnt,
194+
"invalid updated map size (already deleted: %d): %zu\n",
195+
found_cnt, hashmap__size(map)))
196+
return 1;
197+
if (CHECK(hashmap__capacity(map) != exp_cap(hashmap__size(map)),
198+
"unexpected map capacity: %zu\n", hashmap__capacity(map)))
199+
return 1;
200+
201+
hashmap__for_each_entry_safe(map, entry, tmp, bkt) {
202+
const void *oldk, *k;
203+
void *oldv, *v;
204+
205+
k = entry->key;
206+
v = entry->value;
207+
208+
found_cnt++;
209+
found_msk |= 1ULL << (long)k;
210+
211+
if (CHECK(!hashmap__delete(map, k, &oldk, &oldv),
212+
"failed to delete k/v %ld = %ld\n",
213+
(long)k, (long)v))
214+
return 1;
215+
if (CHECK(oldk != k || oldv != v,
216+
"invalid old k/v: expect %ld = %ld, got %ld = %ld\n",
217+
(long)k, (long)v, (long)oldk, (long)oldv))
218+
return 1;
219+
if (CHECK(hashmap__delete(map, k, &oldk, &oldv),
220+
"unexpectedly deleted k/v %ld = %ld\n",
221+
(long)k, (long)v))
222+
return 1;
223+
}
224+
225+
if (CHECK(found_cnt != ELEM_CNT || found_msk != (1ULL << ELEM_CNT) - 1,
226+
"not all keys were deleted: found_cnt:%d, found_msk:%llx\n",
227+
found_cnt, found_msk))
228+
return 1;
229+
if (CHECK(hashmap__size(map) != 0,
230+
"invalid updated map size (already deleted: %d): %zu\n",
231+
found_cnt, hashmap__size(map)))
232+
return 1;
233+
234+
found_cnt = 0;
235+
hashmap__for_each_entry(map, entry, bkt) {
236+
CHECK(false, "unexpected map entries left: %ld = %ld\n",
237+
(long)entry->key, (long)entry->value);
238+
return 1;
239+
}
240+
241+
hashmap__free(map);
242+
hashmap__for_each_entry(map, entry, bkt) {
243+
CHECK(false, "unexpected map entries left: %ld = %ld\n",
244+
(long)entry->key, (long)entry->value);
245+
return 1;
246+
}
247+
248+
fprintf(stderr, "OK\n");
249+
return 0;
250+
}
251+
252+
size_t collision_hash_fn(const void *k, void *ctx)
253+
{
254+
return 0;
255+
}
256+
257+
int test_hashmap_multimap(void)
258+
{
259+
void *k1 = (void *)0, *k2 = (void *)1;
260+
struct hashmap_entry *entry;
261+
struct hashmap *map;
262+
long found_msk;
263+
int err, bkt;
264+
265+
fprintf(stderr, "%s: ", __func__);
266+
267+
/* force collisions */
268+
map = hashmap__new(collision_hash_fn, equal_fn, NULL);
269+
if (CHECK(IS_ERR(map), "failed to create map: %ld\n", PTR_ERR(map)))
270+
return 1;
271+
272+
273+
/* set up multimap:
274+
* [0] -> 1, 2, 4;
275+
* [1] -> 8, 16, 32;
276+
*/
277+
err = hashmap__append(map, k1, (void *)1);
278+
if (CHECK(err, "failed to add k/v: %d\n", err))
279+
return 1;
280+
err = hashmap__append(map, k1, (void *)2);
281+
if (CHECK(err, "failed to add k/v: %d\n", err))
282+
return 1;
283+
err = hashmap__append(map, k1, (void *)4);
284+
if (CHECK(err, "failed to add k/v: %d\n", err))
285+
return 1;
286+
287+
err = hashmap__append(map, k2, (void *)8);
288+
if (CHECK(err, "failed to add k/v: %d\n", err))
289+
return 1;
290+
err = hashmap__append(map, k2, (void *)16);
291+
if (CHECK(err, "failed to add k/v: %d\n", err))
292+
return 1;
293+
err = hashmap__append(map, k2, (void *)32);
294+
if (CHECK(err, "failed to add k/v: %d\n", err))
295+
return 1;
296+
297+
if (CHECK(hashmap__size(map) != 6,
298+
"invalid map size: %zu\n", hashmap__size(map)))
299+
return 1;
300+
301+
/* verify global iteration still works and sees all values */
302+
found_msk = 0;
303+
hashmap__for_each_entry(map, entry, bkt) {
304+
found_msk |= (long)entry->value;
305+
}
306+
if (CHECK(found_msk != (1 << 6) - 1,
307+
"not all keys iterated: %lx\n", found_msk))
308+
return 1;
309+
310+
/* iterate values for key 1 */
311+
found_msk = 0;
312+
hashmap__for_each_key_entry(map, entry, k1) {
313+
found_msk |= (long)entry->value;
314+
}
315+
if (CHECK(found_msk != (1 | 2 | 4),
316+
"invalid k1 values: %lx\n", found_msk))
317+
return 1;
318+
319+
/* iterate values for key 2 */
320+
found_msk = 0;
321+
hashmap__for_each_key_entry(map, entry, k2) {
322+
found_msk |= (long)entry->value;
323+
}
324+
if (CHECK(found_msk != (8 | 16 | 32),
325+
"invalid k2 values: %lx\n", found_msk))
326+
return 1;
327+
328+
fprintf(stderr, "OK\n");
329+
return 0;
330+
}
331+
332+
int test_hashmap_empty()
333+
{
334+
struct hashmap_entry *entry;
335+
int bkt;
336+
struct hashmap *map;
337+
void *k = (void *)0;
338+
339+
fprintf(stderr, "%s: ", __func__);
340+
341+
/* force collisions */
342+
map = hashmap__new(hash_fn, equal_fn, NULL);
343+
if (CHECK(IS_ERR(map), "failed to create map: %ld\n", PTR_ERR(map)))
344+
return 1;
345+
346+
if (CHECK(hashmap__size(map) != 0,
347+
"invalid map size: %zu\n", hashmap__size(map)))
348+
return 1;
349+
if (CHECK(hashmap__capacity(map) != 0,
350+
"invalid map capacity: %zu\n", hashmap__capacity(map)))
351+
return 1;
352+
if (CHECK(hashmap__find(map, k, NULL), "unexpected find\n"))
353+
return 1;
354+
if (CHECK(hashmap__delete(map, k, NULL, NULL), "unexpected delete\n"))
355+
return 1;
356+
357+
hashmap__for_each_entry(map, entry, bkt) {
358+
CHECK(false, "unexpected iterated entry\n");
359+
return 1;
360+
}
361+
hashmap__for_each_key_entry(map, entry, k) {
362+
CHECK(false, "unexpected key entry\n");
363+
return 1;
364+
}
365+
366+
fprintf(stderr, "OK\n");
367+
return 0;
368+
}
369+
370+
int main(int argc, char **argv)
371+
{
372+
bool failed = false;
373+
374+
if (test_hashmap_generic())
375+
failed = true;
376+
if (test_hashmap_multimap())
377+
failed = true;
378+
if (test_hashmap_empty())
379+
failed = true;
380+
381+
return failed;
382+
}

0 commit comments

Comments
 (0)