[PATCH bpf-next v5 2/2] selftests/bpf: add tests for selective module BTF loading

From: Fuyu Zhao

Date: Wed Aug 26 2026 - 09:16:09 EST


Add selftests covering selective kernel module BTF loading through
bpf_object_open_opts.

The tests verify that:
- the existing behavior is preserved when btf_module_names is not
specified, and loading succeeds when the required module BTF is
specified;
- a required module BTF is skipped when the module is not specified in
btf_module_names;
- an empty btf_module_names list skips loading all module BTFs;
- invalid module BTF name lists are rejected.

Signed-off-by: Fuyu Zhao <zhaofuyu@xxxxxxxx>
---
.../bpf/prog_tests/btf_module_names.c | 139 ++++++++++++++++++
.../selftests/bpf/progs/btf_module_names.c | 13 ++
2 files changed, 152 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_module_names.c
create mode 100644 tools/testing/selftests/bpf/progs/btf_module_names.c

diff --git a/tools/testing/selftests/bpf/prog_tests/btf_module_names.c b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
new file mode 100644
index 000000000000..3dc72bef014c
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include "btf_module_names.skel.h"
+
+static void btf_module_names_load(void)
+{
+ struct btf_module_names *skel = NULL;
+ int ret;
+ static const char *mod_names[] = { "bpf_testmod" };
+
+ LIBBPF_OPTS(bpf_object_open_opts, opts,
+ .btf_module_names = mod_names,
+ .nr_btf_module_names = 1,
+ );
+
+ /* Verify backward compatibility. */
+ skel = btf_module_names__open_opts(NULL);
+ if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts default"))
+ goto out;
+
+ ret = btf_module_names__load(skel);
+ ASSERT_OK(ret, "btf_module_names__load default");
+
+ btf_module_names__destroy(skel);
+ skel = NULL;
+
+ skel = btf_module_names__open_opts(&opts);
+ if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts"))
+ goto out;
+
+ ret = btf_module_names__load(skel);
+ ASSERT_OK(ret, "btf_module_names__load");
+out:
+ btf_module_names__destroy(skel);
+}
+
+/*
+ * Verify that an unrequested module BTF is skipped. The BPF program
+ * requires the BTF of bpf_testmod, but bpf_testmod is not specified in
+ * btf_module_names, so its BTF is skipped and the BPF program fails to load.
+ */
+static void btf_module_names_skip(void)
+{
+ struct btf_module_names *skel = NULL;
+ int ret;
+ static const char *mod_names[] = { "module_nonexist" };
+
+ LIBBPF_OPTS(bpf_object_open_opts, opts,
+ .btf_module_names = mod_names,
+ .nr_btf_module_names = 1,
+ );
+
+ skel = btf_module_names__open_opts(&opts);
+ if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts"))
+ goto out;
+
+ ret = btf_module_names__load(skel);
+ ASSERT_EQ(ret, -ESRCH, "btf_module_names__load");
+
+out:
+ btf_module_names__destroy(skel);
+}
+
+/*
+ * Verify that an empty filter skips loading all module BTFs. The BPF
+ * program requires bpf_testmod BTF, so it fails to load.
+ */
+static void btf_module_names_empty(void)
+{
+ struct btf_module_names *skel = NULL;
+ int ret;
+ static const char *mod_names[] = { "" };
+
+ LIBBPF_OPTS(bpf_object_open_opts, opts,
+ .btf_module_names = mod_names,
+ );
+
+ skel = btf_module_names__open_opts(&opts);
+ if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts empty"))
+ goto out;
+
+ ret = btf_module_names__load(skel);
+ ASSERT_EQ(ret, -ESRCH, "btf_module_names__load empty");
+
+out:
+ btf_module_names__destroy(skel);
+}
+
+static void btf_module_names_invalid(void)
+{
+ struct btf_module_names *skel = NULL;
+ const char *names[] = { NULL };
+ const char *empty_names[] = { "" };
+ const char *duplicate_names[] = {
+ "bpf_testmod", "bpf_testmod",
+ };
+ LIBBPF_OPTS(bpf_object_open_opts, opts,
+ .btf_module_names = names,
+ .nr_btf_module_names = 1,
+ );
+
+ skel = btf_module_names__open_opts(&opts);
+ ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
+ "btf_module_names__open_opts null");
+ btf_module_names__destroy(skel);
+
+ opts.btf_module_names = empty_names;
+ skel = btf_module_names__open_opts(&opts);
+ ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
+ "btf_module_names__open_opts empty");
+ btf_module_names__destroy(skel);
+
+ opts.btf_module_names = duplicate_names;
+ opts.nr_btf_module_names = 2;
+ skel = btf_module_names__open_opts(&opts);
+ ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
+ "btf_module_names__open_opts duplicate name");
+ btf_module_names__destroy(skel);
+}
+
+void test_btf_module_names(void)
+{
+ if (!env.has_testmod) {
+ test__skip();
+ return;
+ }
+
+ if (test__start_subtest("btf_module_names_load"))
+ btf_module_names_load();
+
+ if (test__start_subtest("btf_module_names_skip"))
+ btf_module_names_skip();
+
+ if (test__start_subtest("btf_module_names_empty"))
+ btf_module_names_empty();
+
+ if (test__start_subtest("btf_module_names_invalid"))
+ btf_module_names_invalid();
+}
diff --git a/tools/testing/selftests/bpf/progs/btf_module_names.c b/tools/testing/selftests/bpf/progs/btf_module_names.c
new file mode 100644
index 000000000000..232adc0f33f6
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/btf_module_names.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+SEC("fentry/bpf_testmod_loop_test")
+int BPF_PROG(test_btf_module_names)
+{
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.34.1