On Thu, 3 Jul 2025 at 21:57, Emil Tsalapatis <emil@xxxxxxxxxxxxxxx> wrote: > > On Tue, Jul 1, 2025 at 11:17 PM Kumar Kartikeya Dwivedi > <memxor@xxxxxxxxx> wrote: > > > > Add selftests to stress test the various facets of the stream API, > > memory allocation pattern, and ensuring dumping support is tested and > > functional. > > > > Signed-off-by: Kumar Kartikeya Dwivedi <memxor@xxxxxxxxx> > > --- > > .../testing/selftests/bpf/prog_tests/stream.c | 141 ++++++++++++++++++ > > tools/testing/selftests/bpf/progs/stream.c | 81 ++++++++++ > > .../testing/selftests/bpf/progs/stream_fail.c | 17 +++ > > 3 files changed, 239 insertions(+) > > create mode 100644 tools/testing/selftests/bpf/prog_tests/stream.c > > create mode 100644 tools/testing/selftests/bpf/progs/stream.c > > create mode 100644 tools/testing/selftests/bpf/progs/stream_fail.c > > > > Reviewed-by: Emil Tsalapatis <emil@xxxxxxxxxxxxxxx> > > > diff --git a/tools/testing/selftests/bpf/prog_tests/stream.c b/tools/testing/selftests/bpf/prog_tests/stream.c > > new file mode 100644 > > index 000000000000..d9f0185dca61 > > --- /dev/null > > +++ b/tools/testing/selftests/bpf/prog_tests/stream.c > > @@ -0,0 +1,141 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ > > +#include <test_progs.h> > > +#include <sys/mman.h> > > +#include <regex.h> > > + > > +#include "stream.skel.h" > > +#include "stream_fail.skel.h" > > + > > +void test_stream_failure(void) > > +{ > > + RUN_TESTS(stream_fail); > > +} > > + > > +void test_stream_success(void) > > +{ > > + RUN_TESTS(stream); > > + return; > > +} > > + > > +struct { > > + int prog_off; > > + const char *errstr; > > +} stream_error_arr[] = { > > + { > > + offsetof(struct stream, progs.stream_cond_break), > > + "ERROR: Timeout detected for may_goto instruction\n" > > + "CPU: [0-9]+ UID: 0 PID: [0-9]+ Comm: .*\n" > > + "Call trace:\n" > > + "([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" > > + "|[ \t]+[^\n]+\n)*", > > + }, > > + { > > + offsetof(struct stream, progs.stream_deadlock), > > + "ERROR: AA or ABBA deadlock detected for bpf_res_spin_lock\n" > > + "Attempted lock = (0x[0-9a-fA-F]+)\n" > > + "Total held locks = 1\n" > > + "Held lock\\[ 0\\] = \\1\n" // Lock address must match > > + "CPU: [0-9]+ UID: 0 PID: [0-9]+ Comm: .*\n" > > + "Call trace:\n" > > + "([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" > > + "|[ \t]+[^\n]+\n)*", > > + }, > > +}; > > + > > +static int match_regex(const char *pattern, const char *string) > > +{ > > + int err, rc; > > + regex_t re; > > + > > + err = regcomp(&re, pattern, REG_EXTENDED | REG_NEWLINE); > > + if (err) > > + return -1; > > + rc = regexec(&re, string, 0, NULL, 0); > > + regfree(&re); > > + return rc == 0 ? 1 : 0; > > Nit: You can just return rc and do ASSERT_TRUE(ret > 0) for the result in > test_stream_errors. Ack. > > > +} > > + > > +void test_stream_errors(void) > > +{ > > + LIBBPF_OPTS(bpf_test_run_opts, opts); > > + LIBBPF_OPTS(bpf_prog_stream_read_opts, ropts); > > + struct stream *skel; > > + int ret, prog_fd; > > + char buf[1024]; > > + > > + skel = stream__open_and_load(); > > + if (!ASSERT_OK_PTR(skel, "stream__open_and_load")) > > + return; > > + > > + for (int i = 0; i < ARRAY_SIZE(stream_error_arr); i++) { > > + struct bpf_program **prog; > > + > > + prog = (struct bpf_program **)(((char *)skel) + stream_error_arr[i].prog_off); > > + prog_fd = bpf_program__fd(*prog); > > + ret = bpf_prog_test_run_opts(prog_fd, &opts); > > + ASSERT_OK(ret, "ret"); > > + ASSERT_OK(opts.retval, "retval"); > > + > > +#if !defined(__x86_64__) > > + ASSERT_TRUE(1, "Timed may_goto unsupported, skip."); > > + if (i == 0) { > > + ret = bpf_prog_stream_read(prog_fd, 2, buf, sizeof(buf), &ropts); > > + ASSERT_EQ(ret, 0, "stream read"); > > + continue; > > + } > > +#endif > > + > > + ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDERR, buf, sizeof(buf), &ropts); > > + ASSERT_GT(ret, 0, "stream read"); > > + ASSERT_LE(ret, 1023, "len for buf"); > > + buf[ret] = '\0'; > > + > > + ret = match_regex(stream_error_arr[i].errstr, buf); > > + if (!ASSERT_TRUE(ret == 1, "regex match")) > > + fprintf(stderr, "Output from stream:\n%s\n", buf); > > + } > > + > > + stream__destroy(skel); > > +} > > + > > +void test_stream_syscall(void) > > +{ > > + LIBBPF_OPTS(bpf_test_run_opts, opts); > > + LIBBPF_OPTS(bpf_prog_stream_read_opts, ropts); > > + struct stream *skel; > > + int ret, prog_fd; > > + char buf[64]; > > + > > + skel = stream__open_and_load(); > > + if (!ASSERT_OK_PTR(skel, "stream__open_and_load")) > > + return; > > + > > + prog_fd = bpf_program__fd(skel->progs.stream_syscall); > > + ret = bpf_prog_test_run_opts(prog_fd, &opts); > > + ASSERT_OK(ret, "ret"); > > + ASSERT_OK(opts.retval, "retval"); > > + > > + ASSERT_LT(bpf_prog_stream_read(0, BPF_STREAM_STDOUT, buf, sizeof(buf), &ropts), 0, "error"); > > + ret = -errno; > > + ASSERT_EQ(ret, -EINVAL, "bad prog_fd"); > > + > > + ASSERT_LT(bpf_prog_stream_read(prog_fd, 0, buf, sizeof(buf), &ropts), 0, "error"); > > + ret = -errno; > > + ASSERT_EQ(ret, -ENOENT, "bad stream id"); > > + > > + ASSERT_LT(bpf_prog_stream_read(prog_fd, BPF_STREAM_STDOUT, NULL, sizeof(buf), NULL), 0, "error"); > > + ret = -errno; > > + ASSERT_EQ(ret, -EFAULT, "bad stream buf"); > > + > > + ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDOUT, buf, 2, NULL); > > + ASSERT_EQ(ret, 2, "bytes"); > > + ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDOUT, buf, 2, NULL); > > + ASSERT_EQ(ret, 1, "bytes"); > > + ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDOUT, buf, 1, &ropts); > > + ASSERT_EQ(ret, 0, "no bytes stdout"); > > + ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDERR, buf, 1, &ropts); > > + ASSERT_EQ(ret, 0, "no bytes stderr"); > > + > > + stream__destroy(skel); > > +} > > diff --git a/tools/testing/selftests/bpf/progs/stream.c b/tools/testing/selftests/bpf/progs/stream.c > > new file mode 100644 > > index 000000000000..ae163a656082 > > --- /dev/null > > +++ b/tools/testing/selftests/bpf/progs/stream.c > > @@ -0,0 +1,81 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ > > +#include <vmlinux.h> > > +#include <bpf/bpf_tracing.h> > > +#include <bpf/bpf_helpers.h> > > +#include "bpf_misc.h" > > +#include "bpf_experimental.h" > > + > > +struct arr_elem { > > + struct bpf_res_spin_lock lock; > > +}; > > + > > +struct { > > + __uint(type, BPF_MAP_TYPE_ARRAY); > > + __uint(max_entries, 1); > > + __type(key, int); > > + __type(value, struct arr_elem); > > +} arrmap SEC(".maps"); > > + > > +#define ENOSPC 28 > > +#define _STR "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" > > + > > +#define STREAM_STR (u64)(_STR _STR _STR _STR) > > Is this unused? Yep, will drop. > > > + > > +int size; > > + > > +SEC("syscall") > > +__success __retval(0) > > +int stream_exhaust(void *ctx) > > +{ > > + /* Use global variable for loop convergence. */ > > + size = 0; > > + bpf_repeat(BPF_MAX_LOOPS) { > > + if (bpf_stream_printk(BPF_STDOUT, _STR) == -ENOSPC && size == 99954) > > + return 0; > > + size += sizeof(_STR) - 1; > > + } > > + return 1; > > +} > > + > > +SEC("syscall") > > +__success __retval(0) > > +int stream_cond_break(void *ctx) > > +{ > > + while (can_loop) > > + ; > > + return 0; > > +} > > + > > +SEC("syscall") > > +__success __retval(0) > > +int stream_deadlock(void *ctx) > > +{ > > + struct bpf_res_spin_lock *lock, *nlock; > > + > > + lock = bpf_map_lookup_elem(&arrmap, &(int){0}); > > + if (!lock) > > + return 0; > > Nit: Maybe change the unexpected failure paths to non-zero? > If they are followed then the test stderr output will fail the regex > in userspace, but still it would be nice to immediately be able > to see which step broke. Good catch, will fix. We should definitely not return 0. > > > + nlock = bpf_map_lookup_elem(&arrmap, &(int){0}); > > + if (!nlock) > > + return 0; > > + if (bpf_res_spin_lock(lock)) > > + return 0; > > + if (bpf_res_spin_lock(nlock)) { > > + bpf_res_spin_unlock(lock); > > + return 0; > > + } > > + bpf_res_spin_unlock(nlock); > > + bpf_res_spin_unlock(lock); > > + return 0; > > +} > > + > > +SEC("syscall") > > +__success __retval(0) > > +int stream_syscall(void *ctx) > > +{ > > + bpf_stream_printk(BPF_STDOUT, "foo"); > > + return 0; > > +} > > + > > +char _license[] SEC("license") = "GPL"; > > diff --git a/tools/testing/selftests/bpf/progs/stream_fail.c b/tools/testing/selftests/bpf/progs/stream_fail.c > > new file mode 100644 > > index 000000000000..12004d5092b7 > > --- /dev/null > > +++ b/tools/testing/selftests/bpf/progs/stream_fail.c > > @@ -0,0 +1,17 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ > > +#include <vmlinux.h> > > +#include <bpf/bpf_tracing.h> > > +#include <bpf/bpf_helpers.h> > > +#include <bpf/bpf_core_read.h> > > +#include "bpf_misc.h" > > + > > +SEC("syscall") > > +__failure __msg("Possibly NULL pointer passed") > > +int stream_vprintk_null_arg(void *ctx) > > +{ > > + bpf_stream_vprintk(BPF_STDOUT, "", NULL, 0, NULL); > > + return 0; > > +} > > + > > Possibly add a test passing a random scalar as the pointer? Though if the > test above succeeds, the verifier properly identifies the argument as a pointer > arg so it wouldn't give any extra signal. Yeah, I can add one more, and this is the only interface to streams visible to the program. > > > > +char _license[] SEC("license") = "GPL"; > > -- > > 2.47.1 > >