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