* [PATCH bpf-next v2 1/6] selftests/bpf: Add compare_text_to_expected() helper
2026-08-28 21:52 [PATCH bpf-next v2 0/6] bpftool, selftests: Add tests for C dump and fix a dropped type Ihor Solodrai
@ 2026-08-28 21:52 ` Ihor Solodrai
2026-08-28 21:52 ` [PATCH bpf-next v2 2/6] selftests/bpf: Modernize btf_dump test scaffolding Ihor Solodrai
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Ihor Solodrai @ 2026-08-28 21:52 UTC (permalink / raw)
To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
Cc: bpf
Some selftests generate text (such as BTF dump) and check it against
an expectation committed nearby.
There is no shared way to do that. prog_tests/btf_dump.c assembles an
"awk ... | diff -u" pipeline and hands it to system(). Any other test
wanting the same behaviour needs to reproduce both the comparison and
the reporting of a mismatch.
test_progs captures per-subtest output by pointing the stdout and
stderr FILE * globals at a memstream. But a child process inherits
descriptors, not the globals. So the "| diff -u" goes to the console
instead of the subtest log and is absent from the failure report.
Add a helper that compares two strings and runs diff(1) on mismatch,
properly relaying the output to stdout. Add tests for the helper.
Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
.../bpf/prog_tests/prog_tests_framework.c | 23 ++++++++++
tools/testing/selftests/bpf/testing_helpers.c | 43 +++++++++++++++++++
tools/testing/selftests/bpf/testing_helpers.h | 3 ++
3 files changed, 69 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/prog_tests_framework.c b/tools/testing/selftests/bpf/prog_tests/prog_tests_framework.c
index 7607cfc2408c..d111fe105447 100644
--- a/tools/testing/selftests/bpf/prog_tests/prog_tests_framework.c
+++ b/tools/testing/selftests/bpf/prog_tests/prog_tests_framework.c
@@ -179,3 +179,26 @@ void test_prog_tests_framework_expected_msgs(void)
}
}
}
+
+void test_prog_tests_framework_compare_text(void)
+{
+ int err;
+
+ if (test__start_subtest("compare_text_match")) {
+ err = compare_text_to_expected("same\n", "same\n");
+ ASSERT_EQ(err, 0, "match_rc");
+ test__end_subtest();
+ }
+
+ if (test__start_subtest("compare_text_mismatch")) {
+ err = compare_text_to_expected("line two\n", "line one\n");
+ fflush(stdout);
+
+ ASSERT_EQ(err, -1, "mismatch_rc");
+ ASSERT_HAS_SUBSTR(env.subtest_state->log_buf, "-line one",
+ "diff_has_expected");
+ ASSERT_HAS_SUBSTR(env.subtest_state->log_buf, "+line two",
+ "diff_has_actual");
+ test__end_subtest();
+ }
+}
diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c
index c970e7793dfc..3f037949e978 100644
--- a/tools/testing/selftests/bpf/testing_helpers.c
+++ b/tools/testing/selftests/bpf/testing_helpers.c
@@ -534,3 +534,46 @@ int stack_mprotect(void)
PROT_READ | PROT_WRITE | PROT_EXEC);
return ret;
}
+
+int compare_text_to_expected(const char *actual, const char *expected)
+{
+ char exp_path[] = "/tmp/selftest_expected.XXXXXX";
+ char act_path[] = "/tmp/selftest_actual.XXXXXX";
+ char buf[512], cmd[128];
+ int exp_fd, act_fd;
+ FILE *p;
+
+ if (!strcmp(actual, expected))
+ return 0;
+
+ exp_fd = mkstemp(exp_path);
+ act_fd = mkstemp(act_path);
+ if (exp_fd < 0 || act_fd < 0) {
+ fprintf(stdout, "output differs, no temp file for a diff\n");
+ goto out;
+ }
+
+ dprintf(exp_fd, "%s", expected);
+ dprintf(act_fd, "%s", actual);
+
+ snprintf(cmd, sizeof(cmd), "diff -u '%s' '%s'", exp_path, act_path);
+ p = popen(cmd, "r");
+ if (!p) {
+ fprintf(stdout, "output differs, '%s' did not run\n", cmd);
+ goto out;
+ }
+ while (fgets(buf, sizeof(buf), p))
+ fputs(buf, stdout);
+ pclose(p);
+
+out:
+ if (exp_fd >= 0) {
+ close(exp_fd);
+ unlink(exp_path);
+ }
+ if (act_fd >= 0) {
+ close(act_fd);
+ unlink(act_path);
+ }
+ return -1;
+}
diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h
index 2edc6fb7fc52..1c58a2f08b64 100644
--- a/tools/testing/selftests/bpf/testing_helpers.h
+++ b/tools/testing/selftests/bpf/testing_helpers.h
@@ -61,4 +61,7 @@ int testing_prog_flags(void);
bool is_jit_enabled(void);
int stack_mprotect(void);
+/* Runs diff(1) on mismatch */
+int compare_text_to_expected(const char *actual, const char *expected);
+
#endif /* __TESTING_HELPERS_H */
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH bpf-next v2 2/6] selftests/bpf: Modernize btf_dump test scaffolding
2026-08-28 21:52 [PATCH bpf-next v2 0/6] bpftool, selftests: Add tests for C dump and fix a dropped type Ihor Solodrai
2026-08-28 21:52 ` [PATCH bpf-next v2 1/6] selftests/bpf: Add compare_text_to_expected() helper Ihor Solodrai
@ 2026-08-28 21:52 ` Ihor Solodrai
2026-08-28 21:52 ` [PATCH bpf-next v2 3/6] selftests/bpf: Compare btf_dump expected output in-process Ihor Solodrai
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Ihor Solodrai @ 2026-08-28 21:52 UTC (permalink / raw)
To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
Cc: bpf
Refactor test_btf_dump_case() in order to:
* use newer ASSERT_* macros instead of CHECK
* drop the file-scope "duration" variable CHECK required
Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
.../selftests/bpf/prog_tests/btf_dump.c | 19 ++++++++-----------
1 file changed, 8 insertions(+), 11 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dump.c b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
index 9f1b50e07a29..e9d2d4c26509 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
@@ -2,8 +2,6 @@
#include <test_progs.h>
#include <bpf/btf.h>
-static int duration = 0;
-
void btf_dump_printf(void *ctx, const char *fmt, va_list args)
{
vfprintf(ctx, fmt, args);
@@ -69,8 +67,9 @@ static int test_btf_dump_case(int n, struct btf_dump_test_case *t)
if (!t->known_ptr_sz) {
btf__set_pointer_size(btf, 8);
} else {
- CHECK(btf__pointer_size(btf) != 8, "ptr_sz", "exp %d, got %zu\n",
- 8, btf__pointer_size(btf));
+ size_t ptr_sz = btf__pointer_size(btf);
+
+ ASSERT_EQ(ptr_sz, (size_t)8, "ptr_sz");
}
snprintf(out_file, sizeof(out_file), "/tmp/%s.output.XXXXXX", t->file);
@@ -80,8 +79,7 @@ static int test_btf_dump_case(int n, struct btf_dump_test_case *t)
goto done;
}
f = fdopen(fd, "w");
- if (CHECK(f == NULL, "open_tmp", "failed to open file: %s(%d)\n",
- strerror(errno), errno)) {
+ if (!ASSERT_OK_PTR(f, "open_tmp")) {
close(fd);
goto done;
}
@@ -89,9 +87,8 @@ static int test_btf_dump_case(int n, struct btf_dump_test_case *t)
err = btf_dump_all_types(btf, f);
fclose(f);
close(fd);
- if (CHECK(err, "btf_dump", "failure during C dumping: %d\n", err)) {
+ if (!ASSERT_OK(err, "btf_dump"))
goto done;
- }
snprintf(test_file, sizeof(test_file), "progs/%s.c", t->file);
if (access(test_file, R_OK) == -1)
@@ -114,10 +111,10 @@ static int test_btf_dump_case(int n, struct btf_dump_test_case *t)
"out {sub(/^[ \\t]*\\*/, \"\"); print}' '%s' | diff -u - '%s'",
test_file, out_file);
err = system(diff_cmd);
- if (CHECK(err, "diff",
- "differing test output, output=%s, err=%d, diff cmd:\n%s\n",
- out_file, err, diff_cmd))
+ if (!ASSERT_OK(err, "diff")) {
+ fprintf(stdout, "output=%s, diff cmd:\n%s\n", out_file, diff_cmd);
goto done;
+ }
remove(out_file);
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH bpf-next v2 3/6] selftests/bpf: Compare btf_dump expected output in-process
2026-08-28 21:52 [PATCH bpf-next v2 0/6] bpftool, selftests: Add tests for C dump and fix a dropped type Ihor Solodrai
2026-08-28 21:52 ` [PATCH bpf-next v2 1/6] selftests/bpf: Add compare_text_to_expected() helper Ihor Solodrai
2026-08-28 21:52 ` [PATCH bpf-next v2 2/6] selftests/bpf: Modernize btf_dump test scaffolding Ihor Solodrai
@ 2026-08-28 21:52 ` Ihor Solodrai
2026-08-28 21:52 ` [PATCH bpf-next v2 4/6] selftests/bpf: NUL-terminate bpftool command output Ihor Solodrai
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Ihor Solodrai @ 2026-08-28 21:52 UTC (permalink / raw)
To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
Cc: bpf
test_btf_dump_case() writes the dumped types to a temporary file and
then runs a command against the expectation:
system("awk '...' 'progs/x.c' | diff -u - '/tmp/x.output.XXXXXX'");
This creates three processes per test case: a shell, awk and diff. The
awk cannot be deferred to the failure path, because its output is the
expected text.
In a VM with a 9p root (used on BPF CI) process creation dominates the
cost. A bare fork+exec measures around 110ms, so the btf_dump test
cases spend most of their runtime forking.
Replace the awk pipeline with a simple marker parser. Buffer the dump
in memory, and use compare_text_to_expected(), which only runs diff(1)
on mismatch.
Measured with "time ./test_progs -t btf_dump" in the VM:
real 2.751s -> 0.702s, sys 1.901s -> 0.193s
This also drops the temporary file setup.
Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
.../selftests/bpf/prog_tests/btf_dump.c | 112 +++++++++++++-----
1 file changed, 82 insertions(+), 30 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dump.c b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
index e9d2d4c26509..fe04a955d46c 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#include <bpf/btf.h>
+#include "testing_helpers.h"
void btf_dump_printf(void *ctx, const char *fmt, va_list args)
{
@@ -43,12 +44,75 @@ static int btf_dump_all_types(const struct btf *btf, void *ctx)
return err;
}
+/*
+ * Expected output is embedded in the test case source, between
+ * START-EXPECTED-OUTPUT and END-EXPECTED-OUTPUT markers. A region is either
+ * plain C, where a declaration doubles as its own expectation, or C wrapped in
+ * a block comment, where the rendered form differs from the source. All
+ * regions of a file concatenate into one expectation, compared against one
+ * whole-file dump.
+ *
+ * Returns a malloc'd buffer for the caller to free, or NULL on failure.
+ */
+static char *read_expected_output(const char *path)
+{
+ size_t out_sz = 0, line_cap = 0;
+ char *out = NULL, *line = NULL;
+ bool in_region = false;
+ FILE *f, *out_file;
+
+ f = fopen(path, "r");
+ if (!f)
+ return NULL;
+
+ out_file = open_memstream(&out, &out_sz);
+ if (!out_file) {
+ fclose(f);
+ return NULL;
+ }
+
+ while (getline(&line, &line_cap, f) > 0) {
+ const char *p;
+
+ if (strstr(line, "START-EXPECTED-OUTPUT")) {
+ in_region = true;
+ continue;
+ }
+ if (strstr(line, "END-EXPECTED-OUTPUT"))
+ in_region = false;
+ if (!in_region)
+ continue;
+
+ p = line + strspn(line, " \t");
+
+ /* opening or closing line of a commented out region */
+ if (!strncmp(p, "/*", 2) || !strncmp(p, "*/", 2))
+ continue;
+
+ /*
+ * Only a '*' directly after the indentation is a comment
+ * prefix. Without one the line is taken as it is, leading
+ * whitespace included.
+ */
+ p = *p == '*' ? p + 1 : line;
+
+ fputs(p, out_file);
+ }
+
+ free(line);
+ fclose(f);
+ fclose(out_file);
+ return out;
+}
+
static int test_btf_dump_case(int n, struct btf_dump_test_case *t)
{
- char test_file[256], out_file[256], diff_cmd[1024];
+ char *dump = NULL, *expected = NULL;
struct btf *btf = NULL;
- int err = 0, fd = -1;
- FILE *f = NULL;
+ char test_file[256];
+ size_t dump_sz = 0;
+ int err = 0;
+ FILE *f;
snprintf(test_file, sizeof(test_file), "%s.bpf.o", t->file);
@@ -72,21 +136,14 @@ static int test_btf_dump_case(int n, struct btf_dump_test_case *t)
ASSERT_EQ(ptr_sz, (size_t)8, "ptr_sz");
}
- snprintf(out_file, sizeof(out_file), "/tmp/%s.output.XXXXXX", t->file);
- fd = mkstemp(out_file);
- if (!ASSERT_GE(fd, 0, "create_tmp")) {
- err = fd;
- goto done;
- }
- f = fdopen(fd, "w");
- if (!ASSERT_OK_PTR(f, "open_tmp")) {
- close(fd);
+ f = open_memstream(&dump, &dump_sz);
+ if (!ASSERT_OK_PTR(f, "open_memstream")) {
+ err = -errno;
goto done;
}
err = btf_dump_all_types(btf, f);
fclose(f);
- close(fd);
if (!ASSERT_OK(err, "btf_dump"))
goto done;
@@ -97,28 +154,23 @@ static int test_btf_dump_case(int n, struct btf_dump_test_case *t)
* without preserving the directory structure.
*/
snprintf(test_file, sizeof(test_file), "%s.c", t->file);
- /*
- * Diff test output and expected test output, contained between
- * START-EXPECTED-OUTPUT and END-EXPECTED-OUTPUT lines in test case.
- * For expected output lines, everything before '*' is stripped out.
- * Also lines containing comment start and comment end markers are
- * ignored.
- */
- snprintf(diff_cmd, sizeof(diff_cmd),
- "awk '/START-EXPECTED-OUTPUT/{out=1;next} "
- "/END-EXPECTED-OUTPUT/{out=0} "
- "/\\/\\*|\\*\\//{next} " /* ignore comment start/end lines */
- "out {sub(/^[ \\t]*\\*/, \"\"); print}' '%s' | diff -u - '%s'",
- test_file, out_file);
- err = system(diff_cmd);
- if (!ASSERT_OK(err, "diff")) {
- fprintf(stdout, "output=%s, diff cmd:\n%s\n", out_file, diff_cmd);
+
+ expected = read_expected_output(test_file);
+ if (!ASSERT_OK_PTR(expected, "read_expected_output")) {
+ err = -errno;
goto done;
}
- remove(out_file);
+ /*
+ * The mismatch has already been reported, so this only has to
+ * register the failure. ASSERT_OK() would append a stale errno to it.
+ */
+ err = compare_text_to_expected(dump, expected);
+ ASSERT_EQ(err, 0, "compare_text_to_expected");
done:
+ free(expected);
+ free(dump);
btf__free(btf);
return err;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH bpf-next v2 4/6] selftests/bpf: NUL-terminate bpftool command output
2026-08-28 21:52 [PATCH bpf-next v2 0/6] bpftool, selftests: Add tests for C dump and fix a dropped type Ihor Solodrai
` (2 preceding siblings ...)
2026-08-28 21:52 ` [PATCH bpf-next v2 3/6] selftests/bpf: Compare btf_dump expected output in-process Ihor Solodrai
@ 2026-08-28 21:52 ` Ihor Solodrai
2026-08-28 21:52 ` [PATCH bpf-next v2 5/6] selftests/bpf: Add tests for bpftool btf dump format c Ihor Solodrai
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Ihor Solodrai @ 2026-08-28 21:52 UTC (permalink / raw)
To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
Cc: bpf
run_command() writes a bpftool command's output into a caller-supplied
buffer, and every caller treats that buffer as a C string.
However fread() reports a byte count and doesn't terminate the
string. The helper does not terminate either, so callers have to zero
the buffer first. prog_tests/bpftool_metadata.c does not, for example.
Read one byte less and terminate in the helper.
Fixes: f21fae577446 ("selftests/bpf: Add a few helpers for bpftool testing")
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
tools/testing/selftests/bpf/bpftool_helpers.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/bpf/bpftool_helpers.c b/tools/testing/selftests/bpf/bpftool_helpers.c
index 0a2a4f0a2794..c49fdd90eb03 100644
--- a/tools/testing/selftests/bpf/bpftool_helpers.c
+++ b/tools/testing/selftests/bpf/bpftool_helpers.c
@@ -53,6 +53,7 @@ static int run_command(char *args, char *output_buf, size_t output_max_len)
static char bpftool_path[PATH_MAX] = {};
bool suppress_output = !(output_buf && output_max_len);
char command[BPFTOOL_FULL_CMD_MAX_LEN];
+ size_t n;
FILE *f;
int ret;
@@ -68,8 +69,10 @@ static int run_command(char *args, char *output_buf, size_t output_max_len)
if (!f)
return 1;
- if (!suppress_output)
- fread(output_buf, 1, output_max_len, f);
+ if (!suppress_output) {
+ n = fread(output_buf, 1, output_max_len - 1, f);
+ output_buf[n] = '\0';
+ }
ret = pclose(f);
return ret;
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH bpf-next v2 5/6] selftests/bpf: Add tests for bpftool btf dump format c
2026-08-28 21:52 [PATCH bpf-next v2 0/6] bpftool, selftests: Add tests for C dump and fix a dropped type Ihor Solodrai
` (3 preceding siblings ...)
2026-08-28 21:52 ` [PATCH bpf-next v2 4/6] selftests/bpf: NUL-terminate bpftool command output Ihor Solodrai
@ 2026-08-28 21:52 ` Ihor Solodrai
2026-08-28 21:52 ` [PATCH bpf-next v2 6/6] bpftool: Don't drop a type in the sorted C dump Ihor Solodrai
2026-08-30 1:30 ` [PATCH bpf-next v2 0/6] bpftool, selftests: Add tests for C dump and fix a dropped type patchwork-bot+netdevbpf
6 siblings, 0 replies; 8+ messages in thread
From: Ihor Solodrai @ 2026-08-28 21:52 UTC (permalink / raw)
To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
Cc: bpf
"bpftool btf dump format c" generates the vmlinux.h that BPF programs
are built against, and it has no test coverage at all. The only
in-tree consumers are build systems.
Building against the header only catches what a compiler rejects,
which is insufficient.
Introduce a bpftool_btf_dump selftest. Dump a small hand-built BTF and
compare the output against committed expectations.
The BTF is assembled with btf__add_*() rather than compiled from BPF C
because the fixture needs a 4-byte "long int". That is what makes
bpftool render the hole in struct holey as a pair of "long: 32;"
bitfields. A BPF target is always 64-bit, so a compiled fixture could
not supply it.
Comparing the whole dump makes every change to a generated header show
up in a patch.
Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
tools/testing/selftests/bpf/Makefile | 8 +-
.../bpf/bpftool_btf_dump_sorted.expected | 44 +++++
.../bpf/bpftool_btf_dump_unsorted.expected | 48 +++++
.../bpf/prog_tests/bpftool_btf_dump.c | 178 ++++++++++++++++++
4 files changed, 276 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
create mode 100644 tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 5f1a3bfc0569..d99581257e54 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -119,7 +119,9 @@ TEST_GEN_PROGS += test_progs-cpuv4
TEST_INST_SUBDIRS += cpuv4
endif
-TEST_FILES = xsk_prereqs.sh $(wildcard progs/btf_dump_test_case_*.c)
+TEST_FILES = xsk_prereqs.sh $(wildcard progs/btf_dump_test_case_*.c) \
+ bpftool_btf_dump_sorted.expected \
+ bpftool_btf_dump_unsorted.expected
# Order correspond to 'make run_tests' order
TEST_PROGS := test_kmod.sh \
@@ -887,7 +889,9 @@ TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read \
ima_setup.sh \
$(VERIFY_SIG_SETUP) \
$(wildcard progs/btf_dump_test_case_*.c) \
- $(wildcard progs/*.bpf.o)
+ $(wildcard progs/*.bpf.o) \
+ bpftool_btf_dump_sorted.expected \
+ bpftool_btf_dump_unsorted.expected
TRUNNER_BPF_BUILD_RULE := CLANG_BPF_BUILD_RULE
TRUNNER_BPF_CFLAGS := $(BPF_CFLAGS) $(CLANG_CFLAGS) -DENABLE_ATOMICS_TESTS
$(eval $(call DEFINE_TEST_RUNNER,test_progs))
diff --git a/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected b/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
new file mode 100644
index 000000000000..a9c6b688ffd7
--- /dev/null
+++ b/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
@@ -0,0 +1,44 @@
+#ifndef __VMLINUX_H__
+#define __VMLINUX_H__
+
+#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
+#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)
+#endif
+
+#ifndef __ksym
+#define __ksym __attribute__((section(".ksyms")))
+#endif
+
+#ifndef __weak
+#define __weak __attribute__((weak))
+#endif
+
+#ifndef __bpf_fastcall
+#if __has_attribute(bpf_fastcall)
+#define __bpf_fastcall __attribute__((bpf_fastcall))
+#else
+#define __bpf_fastcall
+#endif
+#endif
+
+struct holey {
+ int c;
+ long: 32;
+ long: 32;
+ int tail;
+};
+
+struct s {
+ int f;
+};
+
+
+/* BPF kfuncs */
+#ifndef BPF_NO_KFUNC_PROTOTYPES
+#endif
+
+#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
+#pragma clang attribute pop
+#endif
+
+#endif /* __VMLINUX_H__ */
diff --git a/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected b/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
new file mode 100644
index 000000000000..4310adc2ed51
--- /dev/null
+++ b/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
@@ -0,0 +1,48 @@
+#ifndef __VMLINUX_H__
+#define __VMLINUX_H__
+
+#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
+#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)
+#endif
+
+#ifndef __ksym
+#define __ksym __attribute__((section(".ksyms")))
+#endif
+
+#ifndef __weak
+#define __weak __attribute__((weak))
+#endif
+
+#ifndef __bpf_fastcall
+#if __has_attribute(bpf_fastcall)
+#define __bpf_fastcall __attribute__((bpf_fastcall))
+#else
+#define __bpf_fastcall
+#endif
+#endif
+
+struct holey {
+ int c;
+ long: 32;
+ long: 32;
+ int tail;
+};
+
+enum {
+ E0 = 1,
+};
+
+struct s {
+ int f;
+};
+
+
+/* BPF kfuncs */
+#ifndef BPF_NO_KFUNC_PROTOTYPES
+#endif
+
+#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
+#pragma clang attribute pop
+#endif
+
+#endif /* __VMLINUX_H__ */
diff --git a/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c b/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
new file mode 100644
index 000000000000..d5b25302b0c8
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <bpftool_helpers.h>
+#include <bpf/btf.h>
+#include <limits.h>
+#include <unistd.h>
+#include "btf_helpers.h"
+#include "testing_helpers.h"
+
+#define DUMP_BUF_SZ (16 * 1024)
+
+#define EXPECTED_SORTED "bpftool_btf_dump_sorted.expected"
+#define EXPECTED_UNSORTED "bpftool_btf_dump_unsorted.expected"
+
+/*
+ * struct holey {
+ * int c;
+ * <64-bit hole>
+ * int tail;
+ * };
+ * enum { E0 = 1 };
+ * struct s { int f; };
+ */
+static struct btf *mk_btf(void)
+{
+ struct btf *btf;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "new_empty"))
+ return NULL;
+
+ btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+ btf__add_int(btf, "long int", 4, BTF_INT_SIGNED);
+
+ btf__add_struct(btf, "holey", 16);
+ btf__add_field(btf, "c", 1, 0, 0);
+ btf__add_field(btf, "tail", 1, 96, 0);
+
+ btf__add_enum(btf, NULL, 4);
+ btf__add_enum_value(btf, "E0", 1);
+
+ btf__add_struct(btf, "s", 4);
+ btf__add_field(btf, "f", 1, 0, 0);
+
+ VALIDATE_RAW_BTF(
+ btf,
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] INT 'long int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[3] STRUCT 'holey' size=16 vlen=2\n"
+ "\t'c' type_id=1 bits_offset=0\n"
+ "\t'tail' type_id=1 bits_offset=96",
+ "[4] ENUM '(anon)' encoding=UNSIGNED size=4 vlen=1\n"
+ "\t'E0' val=1",
+ "[5] STRUCT 's' size=4 vlen=1\n"
+ "\t'f' type_id=1 bits_offset=0");
+
+ return btf;
+}
+
+static int btf_to_tmpfile(const struct btf *btf, char *path)
+{
+ ssize_t written;
+ const void *raw;
+ __u32 sz;
+ int fd;
+
+ raw = btf__raw_data(btf, &sz);
+ if (!ASSERT_OK_PTR(raw, "raw_data"))
+ return -1;
+
+ snprintf(path, PATH_MAX, "/tmp/bpftool_btf_dump.XXXXXX");
+ fd = mkstemp(path);
+ if (!ASSERT_OK_FD(fd, "mkstemp_btf"))
+ return -1;
+
+ written = write(fd, raw, sz);
+ close(fd);
+ if (!ASSERT_EQ(written, sz, "write_btf")) {
+ unlink(path);
+ return -1;
+ }
+
+ return 0;
+}
+
+static char *dump_c(const char *btf_path, bool sorted)
+{
+ char args[MAX_BPFTOOL_CMD_LEN];
+ char *buf;
+ int err;
+
+ buf = malloc(DUMP_BUF_SZ);
+ if (!ASSERT_OK_PTR(buf, "alloc_dump"))
+ return NULL;
+
+ snprintf(args, sizeof(args), "btf dump file %s format c%s",
+ btf_path, sorted ? "" : " unsorted");
+
+ err = get_bpftool_command_output(args, buf, DUMP_BUF_SZ);
+ if (!ASSERT_OK(err, "btf_dump_format_c")) {
+ free(buf);
+ return NULL;
+ }
+
+ return buf;
+}
+
+static char *read_expected(const char *path)
+{
+ char *buf = NULL;
+ size_t cap = 0;
+ FILE *f;
+ int err;
+
+ f = fopen(path, "r");
+ if (!f) {
+ err = errno;
+ PRINT_FAIL("can't open expected output '%s': errno %d\n", path, err);
+ return NULL;
+ }
+
+ /* no NUL in a generated header, so this reads to the end */
+ if (getdelim(&buf, &cap, '\0', f) < 0) {
+ err = errno;
+ PRINT_FAIL("can't read expected output '%s': errno %d\n", path, err);
+ free(buf);
+ buf = NULL;
+ }
+
+ fclose(f);
+ return buf;
+}
+
+static void test_dump(const char *btf_path, bool sorted)
+{
+ const char *exp_path;
+ char *dump, *exp;
+ int err;
+
+ exp_path = sorted ? EXPECTED_SORTED : EXPECTED_UNSORTED;
+
+ dump = dump_c(btf_path, sorted);
+ if (!dump)
+ return;
+
+ exp = read_expected(exp_path);
+ if (!exp)
+ goto out_dump;
+
+ err = compare_text_to_expected(dump, exp);
+ ASSERT_OK(err, sorted ? "cmp_sorted" : "cmp_unsorted");
+
+ free(exp);
+out_dump:
+ free(dump);
+}
+
+void test_bpftool_btf_dump(void)
+{
+ char path[PATH_MAX];
+ struct btf *btf;
+
+ btf = mk_btf();
+ if (!btf)
+ return;
+
+ if (btf_to_tmpfile(btf, path))
+ goto out_btf;
+
+ if (test__start_subtest("c_sorted"))
+ test_dump(path, true);
+ if (test__start_subtest("c_unsorted"))
+ test_dump(path, false);
+
+ unlink(path);
+out_btf:
+ btf__free(btf);
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH bpf-next v2 6/6] bpftool: Don't drop a type in the sorted C dump
2026-08-28 21:52 [PATCH bpf-next v2 0/6] bpftool, selftests: Add tests for C dump and fix a dropped type Ihor Solodrai
` (4 preceding siblings ...)
2026-08-28 21:52 ` [PATCH bpf-next v2 5/6] selftests/bpf: Add tests for bpftool btf dump format c Ihor Solodrai
@ 2026-08-28 21:52 ` Ihor Solodrai
2026-08-30 1:30 ` [PATCH bpf-next v2 0/6] bpftool, selftests: Add tests for C dump and fix a dropped type patchwork-bot+netdevbpf
6 siblings, 0 replies; 8+ messages in thread
From: Ihor Solodrai @ 2026-08-28 21:52 UTC (permalink / raw)
To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
Cc: bpf
The C dump sorts types by default, so that generated headers are
diffable. The sorted dump emits one type fewer than the unsorted dump
of the same BTF.
dump_btf_c() starts its loop at index 1 to skip the void type at BTF
type ID 0. That holds for the unsorted dump, where the array index is
the type ID, but not after qsort(): position 0 is then the lowest
ranked type, and btf_type_rank() ranks an anonymous enum 0 while void
takes the default rank of 10. So the enum is skipped, and void is
emitted instead as a no-op.
Fixes: 94133cf24bb3 ("bpftool: Introduce btf c dump sorting")
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
tools/bpf/bpftool/btf.c | 6 +++++-
.../testing/selftests/bpf/bpftool_btf_dump_sorted.expected | 4 ++++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
index c9589026da8d..bca0a3982f09 100644
--- a/tools/bpf/bpftool/btf.c
+++ b/tools/bpf/bpftool/btf.c
@@ -805,9 +805,13 @@ static int dump_btf_c(const struct btf *btf,
if (sort_dump)
datums = sort_btf_c(btf);
- for (i = 1; i < cnt; i++) {
+ for (i = 0; i < cnt; i++) {
int idx = datums ? datums[i].index : i;
+ /* type ID 0 is void, skip it */
+ if (idx == 0)
+ continue;
+
err = btf_dump__dump_type(d, idx);
if (err)
goto done;
diff --git a/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected b/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
index a9c6b688ffd7..5470b2b43229 100644
--- a/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
+++ b/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
@@ -21,6 +21,10 @@
#endif
#endif
+enum {
+ E0 = 1,
+};
+
struct holey {
int c;
long: 32;
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH bpf-next v2 0/6] bpftool, selftests: Add tests for C dump and fix a dropped type
2026-08-28 21:52 [PATCH bpf-next v2 0/6] bpftool, selftests: Add tests for C dump and fix a dropped type Ihor Solodrai
` (5 preceding siblings ...)
2026-08-28 21:52 ` [PATCH bpf-next v2 6/6] bpftool: Don't drop a type in the sorted C dump Ihor Solodrai
@ 2026-08-30 1:30 ` patchwork-bot+netdevbpf
6 siblings, 0 replies; 8+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-08-30 1:30 UTC (permalink / raw)
To: Ihor Solodrai; +Cc: ast, andrii, daniel, eddyz87, memxor, qmo, bpf
Hello:
This series was applied to bpf/bpf-next.git (master)
by Alexei Starovoitov <ast@kernel.org>:
On Fri, 28 Aug 2026 14:52:01 -0700 you wrote:
> "bpftool btf dump format c" generates the vmlinux.h that BPF programs
> are built against, and it has no direct test coverage. Building
> against the header is not a substitute for testing it, nothing a
> compiler accepts would break the build.
>
> Add selftests targeting C dump in particular.
>
> [...]
Here is the summary with links:
- [bpf-next,v2,1/6] selftests/bpf: Add compare_text_to_expected() helper
https://git.kernel.org/bpf/bpf-next/c/bd71904697c6
- [bpf-next,v2,2/6] selftests/bpf: Modernize btf_dump test scaffolding
https://git.kernel.org/bpf/bpf-next/c/c0722dc3288a
- [bpf-next,v2,3/6] selftests/bpf: Compare btf_dump expected output in-process
https://git.kernel.org/bpf/bpf-next/c/122255fb1933
- [bpf-next,v2,4/6] selftests/bpf: NUL-terminate bpftool command output
https://git.kernel.org/bpf/bpf-next/c/4f8a4c76f670
- [bpf-next,v2,5/6] selftests/bpf: Add tests for bpftool btf dump format c
https://git.kernel.org/bpf/bpf-next/c/438147f7f601
- [bpf-next,v2,6/6] bpftool: Don't drop a type in the sorted C dump
https://git.kernel.org/bpf/bpf-next/c/ff01f88a9136
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 8+ messages in thread