xref: /linux/tools/testing/selftests/bpf/prog_tests/libarena.c (revision 5a8cd539ac19f7a68e68e1d25ef9ca2ff55b8500)
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