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 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 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 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 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 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 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 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