1 // SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause
2 /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
3 #include <test_progs.h>
4 #include <unistd.h>
5
6 #include <libarena/common.h>
7 #include <libarena/asan.h>
8 #include <libarena/buddy.h>
9 #include <libarena/userspace.h>
10
11 #include "libarena/libarena.skel.h"
12
run_libarena_test(struct libarena * skel,struct bpf_program * prog,const char * name)13 static void run_libarena_test(struct libarena *skel, struct bpf_program *prog,
14 const char *name)
15 {
16 int ret;
17
18 if (strstr(name, "test_buddy")) {
19 /* Buddy tests initialize the allocator directly. */
20 ret = libarena_run_prog(bpf_program__fd(skel->progs.arena_buddy_destroy));
21 if (!ASSERT_OK(ret, "arena_buddy_destroy"))
22 return;
23 } else {
24 ret = libarena_run_prog(bpf_program__fd(skel->progs.arena_buddy_reset));
25 if (!ASSERT_OK(ret, "arena_buddy_reset"))
26 return;
27 }
28
29 ret = libarena_run_prog(bpf_program__fd(prog));
30
31 ASSERT_OK(ret, name);
32 }
33
run_libarena_parallel_prog(void * arg)34 static void *run_libarena_parallel_prog(void *arg)
35 {
36 struct bpf_program *prog = arg;
37
38 return (void *)(long)libarena_run_prog(bpf_program__fd(prog));
39 }
40
41 /* Max suffix is ceil((lg 2^32) / (lg 10)) + sizeof("__") = 10 + 2 = 12. */
42 #define MAX_PARTEST_SUFFIX (12)
43 #define MAX_PARTEST_NAME (1024)
44 #define MAX_PARTEST_PREFIX (MAX_PARTEST_NAME - MAX_PARTEST_SUFFIX)
45
run_libarena_parallel_fini(struct libarena * skel,const char * name,size_t prefixlen)46 static int run_libarena_parallel_fini(struct libarena *skel, const char *name,
47 size_t prefixlen)
48 {
49 char tdname[MAX_PARTEST_NAME];
50 struct bpf_program *fini_prog;
51 int ret;
52
53 ret = snprintf(tdname, sizeof(tdname), "%.*s__fini", (int)prefixlen, name);
54 if (!ASSERT_LT(ret, sizeof(tdname), "partest fini name"))
55 return -ENAMETOOLONG;
56
57 fini_prog = bpf_object__find_program_by_name(skel->obj, tdname);
58 if (!ASSERT_TRUE(fini_prog, "partest fini prog"))
59 return -ENOENT;
60
61 ret = libarena_run_prog(bpf_program__fd(fini_prog));
62 ASSERT_OK(ret, tdname);
63
64 return ret;
65 }
66
run_libarena_parallel_test_workers(struct libarena * skel,const char * name,size_t prefixlen)67 static int run_libarena_parallel_test_workers(struct libarena *skel,
68 const char *name, size_t prefixlen)
69 {
70 pthread_t *threads = NULL, *tmp_threads;
71 char tdname[MAX_PARTEST_NAME];
72 struct bpf_program *tdprog;
73 uint32_t nthreads;
74 void *thread_ret;
75 int ret, err = 0;
76 int worker_err;
77 int i;
78
79 for (nthreads = 0; nthreads < UINT_MAX; nthreads++) {
80 ret = snprintf(tdname, sizeof(tdname), "%.*s__%u", (int)prefixlen,
81 name, nthreads);
82 if (!ASSERT_LT(ret, sizeof(tdname), "test worker name")) {
83 err = -ENAMETOOLONG;
84 break;
85 }
86
87 /*
88 * We enumerate the worker threads for a given test with __0, __1,
89 * and so on. The suffixes always start from 0 and are contiguous,
90 * so if we don't find a program with the requested name we have
91 * discovered all available worker programs.
92 */
93 tdprog = bpf_object__find_program_by_name(skel->obj, tdname);
94 if (!tdprog)
95 break;
96
97 /* Bump the alloc array to accommodate the new thread. */
98 tmp_threads = realloc(threads, (nthreads + 1) * sizeof(*threads));
99 if (!ASSERT_TRUE(tmp_threads, "realloc")) {
100 err = -ENOMEM;
101 break;
102 }
103 threads = tmp_threads;
104
105 ret = pthread_create(&threads[nthreads], NULL,
106 run_libarena_parallel_prog,
107 tdprog);
108 if (!ASSERT_OK(ret, "pthread_create")) {
109 err = ret;
110 break;
111 }
112 }
113
114
115 for (i = 0; i < nthreads; i++) {
116 ret = pthread_join(threads[i], &thread_ret);
117 if (!ASSERT_OK(ret, "pthread_join")) {
118 err = err ?: ret;
119 continue;
120 }
121
122 worker_err = (long)thread_ret;
123
124 /*
125 * A worker that bails out because another one already gave up
126 * reports -EINTR. It is collateral damage that carries no
127 * information, so skip it entirely: never let it become the
128 * reported error, and don't log it either.
129 */
130 if (!worker_err || worker_err == -EINTR)
131 continue;
132
133 if (!err)
134 err = worker_err;
135
136 fprintf(stdout, "%.*s__%d returned %d\n", (int)prefixlen, name,
137 i, worker_err);
138 }
139
140 free(threads);
141
142 return err;
143 }
144
libarena_parallel_test_enabled(struct libarena * skel,const char * prefix,size_t prefixlen)145 static bool libarena_parallel_test_enabled(struct libarena *skel,
146 const char *prefix,
147 size_t prefixlen)
148 {
149 struct bpf_program *prog;
150 char progname[MAX_PARTEST_NAME];
151 int ret;
152
153 ret = snprintf(progname, sizeof(progname), "%.*s__enabled", (int)prefixlen,
154 prefix);
155 if (!ASSERT_LT(ret, sizeof(progname), "partest enabled name"))
156 return false;
157
158 prog = bpf_object__find_program_by_name(skel->obj, progname);
159 if (!prog)
160 return true;
161
162 ret = libarena_run_prog(bpf_program__fd(prog));
163 if (ret == -EOPNOTSUPP)
164 return false;
165 if (!ASSERT_OK(ret, progname))
166 return false;
167 return true;
168 }
169
run_libarena_parallel_test(struct libarena * skel,struct bpf_program * prog,const char * name)170 static void run_libarena_parallel_test(struct libarena *skel, struct bpf_program *prog,
171 const char *name)
172 {
173 char testname[MAX_PARTEST_NAME];
174 size_t prefixlen;
175 const char *pos;
176 int ret;
177
178 /*
179 * We annotate the initialization prog with __init. If the current prog does
180 * not match, it is one of the parallel threads instead and is ignored.
181 *
182 * We assume the test writer knows what they are doing and do not add __init
183 * randomly in the middle of a test name.
184 */
185 pos = strstr(name, "__init");
186 if (!pos)
187 return;
188
189 prefixlen = pos - name;
190 if (!ASSERT_LT(prefixlen, MAX_PARTEST_PREFIX, "partest prefix too long"))
191 return;
192
193 /* The name of the test without the __init suffix. Looks nicer in the test log. */
194 ret = snprintf(testname, sizeof(testname), "%.*s", (int)prefixlen, name);
195 if (!ASSERT_LT(ret, sizeof(testname), "partest test name"))
196 return;
197
198 if (!test__start_subtest(testname))
199 return;
200
201 if (!libarena_parallel_test_enabled(skel, testname, prefixlen)) {
202 test__skip();
203 return;
204 }
205
206 ret = libarena_run_prog(bpf_program__fd(skel->progs.arena_buddy_reset));
207 if (!ASSERT_OK(ret, "arena_buddy_reset"))
208 return;
209
210 ret = libarena_run_prog(bpf_program__fd(prog));
211 if (!ASSERT_OK(ret, testname))
212 return;
213
214 ret = run_libarena_parallel_test_workers(skel, name, prefixlen);
215
216 ASSERT_OK(ret, testname);
217
218 run_libarena_parallel_fini(skel, name, prefixlen);
219 }
220
serial_test_libarena(void)221 void serial_test_libarena(void)
222 {
223 struct arena_alloc_reserve_args args;
224 struct libarena *skel;
225 struct bpf_program *prog;
226 int ret;
227
228 skel = libarena__open_and_load();
229 if (!ASSERT_OK_PTR(skel, "open_and_load"))
230 return;
231
232 ret = libarena__attach(skel);
233 if (!ASSERT_OK(ret, "attach"))
234 goto out;
235
236 args.nr_pages = ARENA_RESERVE_PAGES_DFL;
237
238 ret = libarena_run_prog_args(bpf_program__fd(skel->progs.arena_alloc_reserve),
239 &args, sizeof(args));
240 if (!ASSERT_OK(ret, "arena_alloc_reserve"))
241 goto out;
242
243 bpf_object__for_each_program(prog, skel->obj) {
244 const char *name = bpf_program__name(prog);
245
246 /*
247 * Handle parallel test progs separately. For those
248 * progs it's not a matter of test/skip, because each
249 * parallel test prog includes an initialization prog
250 * and a set of progs to be run in parallel. For the
251 * latter we do not record them as skipped or run,
252 * because we run them all at once when we come across
253 * the initialization prog. For more details on how we
254 * discover the progs see the comment on
255 * run_libarena_parallel_test.
256 */
257 if (libarena_is_parallel_test_prog(name)) {
258 run_libarena_parallel_test(skel, prog, name);
259 continue;
260 }
261
262 if (!libarena_is_test_prog(name))
263 continue;
264
265 if (!test__start_subtest(name))
266 continue;
267
268 run_libarena_test(skel, prog, name);
269 }
270
271 out:
272 libarena__destroy(skel);
273 }
274