Commit 475e31f8 authored by Alexei Starovoitov's avatar Alexei Starovoitov

Merge branch 'revamp-test_progs'

Andrii Nakryiko says:

====================
This patch set makes a number of changes to test_progs selftest, which is
a collection of many other tests (and sometimes sub-tests as well), to provide
better testing experience and allow to start convering many individual test
programs under selftests/bpf into a single and convenient test runner.

Patch #1 fixes issue with Makefile, which makes prog_tests/test.h compiled as
a C code. This fix allows to change how test.h is generated, providing ability
to have more control on what and how tests are run.

Patch #2 changes how test.h is auto-generated, which allows to have test
definitions, instead of just running test functions. This gives ability to do
more complicated test run policies.

Patch #3 adds `-t <test-name>` and `-n <test-num>` selectors to run only
subset of tests.

Patch #4 changes libbpf_set_print() to return previously set print callback,
allowing to temporarily replace current print callback and then set it back.
This is necessary for some tests that want more control over libbpf logging.

Patch #5 sets up and takes over libbpf logging from individual tests to
test_prog runner, adding -vv verbosity to capture debug output from libbpf.
This is useful when debugging failing tests.

Patch #6 furthers test output management and buffers it by default, emitting
log output only if test fails. This give succinct and clean default test
output. It's possible to bypass this behavior with -v flag, which will turn
off test output buffering.

Patch #7 adds support for sub-tests. It also enhances -t and -n selectors to
both support ability to specify sub-test selectors, as well as enhancing
number selector to accept sets of test, instead of just individual test
number.

Patch #8 converts bpf_verif_scale.c test to use sub-test APIs.

Patch #9 converts send_signal.c tests to use sub-test APIs.

v2->v3:
  - fix buffered output rare unitialized value bug (Alexei);
  - fix buffered output va_list reuse bug (Alexei);
  - fix buffered output truncation due to interleaving zero terminators;

v1->v2:
  - drop libbpf_swap_print, instead return previous function from
    libbpf_set_print (Stanislav);
====================
Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents 943e398d b207edfe
...@@ -74,9 +74,12 @@ static int __base_pr(enum libbpf_print_level level, const char *format, ...@@ -74,9 +74,12 @@ static int __base_pr(enum libbpf_print_level level, const char *format,
static libbpf_print_fn_t __libbpf_pr = __base_pr; static libbpf_print_fn_t __libbpf_pr = __base_pr;
void libbpf_set_print(libbpf_print_fn_t fn) libbpf_print_fn_t libbpf_set_print(libbpf_print_fn_t fn)
{ {
libbpf_print_fn_t old_print_fn = __libbpf_pr;
__libbpf_pr = fn; __libbpf_pr = fn;
return old_print_fn;
} }
__printf(2, 3) __printf(2, 3)
......
...@@ -57,7 +57,7 @@ enum libbpf_print_level { ...@@ -57,7 +57,7 @@ enum libbpf_print_level {
typedef int (*libbpf_print_fn_t)(enum libbpf_print_level level, typedef int (*libbpf_print_fn_t)(enum libbpf_print_level level,
const char *, va_list ap); const char *, va_list ap);
LIBBPF_API void libbpf_set_print(libbpf_print_fn_t fn); LIBBPF_API libbpf_print_fn_t libbpf_set_print(libbpf_print_fn_t fn);
/* Hide internal to user */ /* Hide internal to user */
struct bpf_object; struct bpf_object;
......
...@@ -235,18 +235,12 @@ PROG_TESTS_H := $(PROG_TESTS_DIR)/tests.h ...@@ -235,18 +235,12 @@ PROG_TESTS_H := $(PROG_TESTS_DIR)/tests.h
PROG_TESTS_FILES := $(wildcard prog_tests/*.c) PROG_TESTS_FILES := $(wildcard prog_tests/*.c)
test_progs.c: $(PROG_TESTS_H) test_progs.c: $(PROG_TESTS_H)
$(OUTPUT)/test_progs: CFLAGS += $(TEST_PROGS_CFLAGS) $(OUTPUT)/test_progs: CFLAGS += $(TEST_PROGS_CFLAGS)
$(OUTPUT)/test_progs: test_progs.c $(PROG_TESTS_H) $(PROG_TESTS_FILES) $(OUTPUT)/test_progs: test_progs.c $(PROG_TESTS_FILES) | $(PROG_TESTS_H)
$(PROG_TESTS_H): $(PROG_TESTS_FILES) | $(PROG_TESTS_DIR) $(PROG_TESTS_H): $(PROG_TESTS_FILES) | $(PROG_TESTS_DIR)
$(shell ( cd prog_tests/; \ $(shell ( cd prog_tests/; \
echo '/* Generated header, do not edit */'; \ echo '/* Generated header, do not edit */'; \
echo '#ifdef DECLARE'; \
ls *.c 2> /dev/null | \ ls *.c 2> /dev/null | \
sed -e 's@\([^\.]*\)\.c@extern void test_\1(void);@'; \ sed -e 's@\([^\.]*\)\.c@DEFINE_TEST(\1)@'; \
echo '#endif'; \
echo '#ifdef CALL'; \
ls *.c 2> /dev/null | \
sed -e 's@\([^\.]*\)\.c@test_\1();@'; \
echo '#endif' \
) > $(PROG_TESTS_H)) ) > $(PROG_TESTS_H))
MAP_TESTS_DIR = $(OUTPUT)/map_tests MAP_TESTS_DIR = $(OUTPUT)/map_tests
...@@ -256,7 +250,7 @@ MAP_TESTS_H := $(MAP_TESTS_DIR)/tests.h ...@@ -256,7 +250,7 @@ MAP_TESTS_H := $(MAP_TESTS_DIR)/tests.h
MAP_TESTS_FILES := $(wildcard map_tests/*.c) MAP_TESTS_FILES := $(wildcard map_tests/*.c)
test_maps.c: $(MAP_TESTS_H) test_maps.c: $(MAP_TESTS_H)
$(OUTPUT)/test_maps: CFLAGS += $(TEST_MAPS_CFLAGS) $(OUTPUT)/test_maps: CFLAGS += $(TEST_MAPS_CFLAGS)
$(OUTPUT)/test_maps: test_maps.c $(MAP_TESTS_H) $(MAP_TESTS_FILES) $(OUTPUT)/test_maps: test_maps.c $(MAP_TESTS_FILES) | $(MAP_TESTS_H)
$(MAP_TESTS_H): $(MAP_TESTS_FILES) | $(MAP_TESTS_DIR) $(MAP_TESTS_H): $(MAP_TESTS_FILES) | $(MAP_TESTS_DIR)
$(shell ( cd map_tests/; \ $(shell ( cd map_tests/; \
echo '/* Generated header, do not edit */'; \ echo '/* Generated header, do not edit */'; \
...@@ -277,7 +271,7 @@ VERIFIER_TESTS_H := $(VERIFIER_TESTS_DIR)/tests.h ...@@ -277,7 +271,7 @@ VERIFIER_TESTS_H := $(VERIFIER_TESTS_DIR)/tests.h
VERIFIER_TEST_FILES := $(wildcard verifier/*.c) VERIFIER_TEST_FILES := $(wildcard verifier/*.c)
test_verifier.c: $(VERIFIER_TESTS_H) test_verifier.c: $(VERIFIER_TESTS_H)
$(OUTPUT)/test_verifier: CFLAGS += $(TEST_VERIFIER_CFLAGS) $(OUTPUT)/test_verifier: CFLAGS += $(TEST_VERIFIER_CFLAGS)
$(OUTPUT)/test_verifier: test_verifier.c $(VERIFIER_TESTS_H) $(OUTPUT)/test_verifier: test_verifier.c | $(VERIFIER_TEST_FILES) $(VERIFIER_TESTS_H)
$(VERIFIER_TESTS_H): $(VERIFIER_TEST_FILES) | $(VERIFIER_TESTS_DIR) $(VERIFIER_TESTS_H): $(VERIFIER_TEST_FILES) | $(VERIFIER_TESTS_DIR)
$(shell ( cd verifier/; \ $(shell ( cd verifier/; \
echo '/* Generated header, do not edit */'; \ echo '/* Generated header, do not edit */'; \
......
...@@ -106,8 +106,8 @@ void test_bpf_obj_id(void) ...@@ -106,8 +106,8 @@ void test_bpf_obj_id(void)
if (CHECK(err || if (CHECK(err ||
prog_infos[i].type != BPF_PROG_TYPE_SOCKET_FILTER || prog_infos[i].type != BPF_PROG_TYPE_SOCKET_FILTER ||
info_len != sizeof(struct bpf_prog_info) || info_len != sizeof(struct bpf_prog_info) ||
(jit_enabled && !prog_infos[i].jited_prog_len) || (env.jit_enabled && !prog_infos[i].jited_prog_len) ||
(jit_enabled && (env.jit_enabled &&
!memcmp(jited_insns, zeros, sizeof(zeros))) || !memcmp(jited_insns, zeros, sizeof(zeros))) ||
!prog_infos[i].xlated_prog_len || !prog_infos[i].xlated_prog_len ||
!memcmp(xlated_insns, zeros, sizeof(zeros)) || !memcmp(xlated_insns, zeros, sizeof(zeros)) ||
...@@ -121,7 +121,7 @@ void test_bpf_obj_id(void) ...@@ -121,7 +121,7 @@ void test_bpf_obj_id(void)
err, errno, i, err, errno, i,
prog_infos[i].type, BPF_PROG_TYPE_SOCKET_FILTER, prog_infos[i].type, BPF_PROG_TYPE_SOCKET_FILTER,
info_len, sizeof(struct bpf_prog_info), info_len, sizeof(struct bpf_prog_info),
jit_enabled, env.jit_enabled,
prog_infos[i].jited_prog_len, prog_infos[i].jited_prog_len,
prog_infos[i].xlated_prog_len, prog_infos[i].xlated_prog_len,
!!memcmp(jited_insns, zeros, sizeof(zeros)), !!memcmp(jited_insns, zeros, sizeof(zeros)),
......
...@@ -4,12 +4,15 @@ ...@@ -4,12 +4,15 @@
static int libbpf_debug_print(enum libbpf_print_level level, static int libbpf_debug_print(enum libbpf_print_level level,
const char *format, va_list args) const char *format, va_list args)
{ {
if (level != LIBBPF_DEBUG) if (level != LIBBPF_DEBUG) {
return vfprintf(stderr, format, args); test__vprintf(format, args);
return 0;
}
if (!strstr(format, "verifier log")) if (!strstr(format, "verifier log"))
return 0; return 0;
return vfprintf(stderr, "%s", args); test__vprintf("%s", args);
return 0;
} }
static int check_load(const char *file, enum bpf_prog_type type) static int check_load(const char *file, enum bpf_prog_type type)
...@@ -30,14 +33,25 @@ static int check_load(const char *file, enum bpf_prog_type type) ...@@ -30,14 +33,25 @@ static int check_load(const char *file, enum bpf_prog_type type)
return err; return err;
} }
struct scale_test_def {
const char *file;
enum bpf_prog_type attach_type;
bool fails;
};
void test_bpf_verif_scale(void) void test_bpf_verif_scale(void)
{ {
const char *sched_cls[] = { struct scale_test_def tests[] = {
"./test_verif_scale1.o", "./test_verif_scale2.o", "./test_verif_scale3.o", { "loop3.o", BPF_PROG_TYPE_RAW_TRACEPOINT, true /* fails */ },
};
const char *raw_tp[] = { { "test_verif_scale1.o", BPF_PROG_TYPE_SCHED_CLS },
{ "test_verif_scale2.o", BPF_PROG_TYPE_SCHED_CLS },
{ "test_verif_scale3.o", BPF_PROG_TYPE_SCHED_CLS },
/* full unroll by llvm */ /* full unroll by llvm */
"./pyperf50.o", "./pyperf100.o", "./pyperf180.o", { "pyperf50.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
{ "pyperf100.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
{ "pyperf180.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
/* partial unroll. llvm will unroll loop ~150 times. /* partial unroll. llvm will unroll loop ~150 times.
* C loop count -> 600. * C loop count -> 600.
...@@ -45,7 +59,7 @@ void test_bpf_verif_scale(void) ...@@ -45,7 +59,7 @@ void test_bpf_verif_scale(void)
* 16k insns in loop body. * 16k insns in loop body.
* Total of 5 such loops. Total program size ~82k insns. * Total of 5 such loops. Total program size ~82k insns.
*/ */
"./pyperf600.o", { "pyperf600.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
/* no unroll at all. /* no unroll at all.
* C loop count -> 600. * C loop count -> 600.
...@@ -53,48 +67,50 @@ void test_bpf_verif_scale(void) ...@@ -53,48 +67,50 @@ void test_bpf_verif_scale(void)
* ~110 insns in loop body. * ~110 insns in loop body.
* Total of 5 such loops. Total program size ~1500 insns. * Total of 5 such loops. Total program size ~1500 insns.
*/ */
"./pyperf600_nounroll.o", { "pyperf600_nounroll.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
"./loop1.o", "./loop2.o", { "loop1.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
{ "loop2.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
/* partial unroll. 19k insn in a loop. /* partial unroll. 19k insn in a loop.
* Total program size 20.8k insn. * Total program size 20.8k insn.
* ~350k processed_insns * ~350k processed_insns
*/ */
"./strobemeta.o", { "strobemeta.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
/* no unroll, tiny loops */ /* no unroll, tiny loops */
"./strobemeta_nounroll1.o", { "strobemeta_nounroll1.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
"./strobemeta_nounroll2.o", { "strobemeta_nounroll2.o", BPF_PROG_TYPE_RAW_TRACEPOINT },
};
const char *cg_sysctl[] = {
"./test_sysctl_loop1.o", "./test_sysctl_loop2.o",
};
int err, i;
if (verifier_stats) { "test_sysctl_loop1.o", BPF_PROG_TYPE_CGROUP_SYSCTL },
libbpf_set_print(libbpf_debug_print); { "test_sysctl_loop2.o", BPF_PROG_TYPE_CGROUP_SYSCTL },
err = check_load("./loop3.o", BPF_PROG_TYPE_RAW_TRACEPOINT); { "test_xdp_loop.o", BPF_PROG_TYPE_XDP },
printf("test_scale:loop3:%s\n", err ? (error_cnt--, "OK") : "FAIL"); { "test_seg6_loop.o", BPF_PROG_TYPE_LWT_SEG6LOCAL },
};
libbpf_print_fn_t old_print_fn = NULL;
int err, i;
for (i = 0; i < ARRAY_SIZE(sched_cls); i++) { if (env.verifier_stats) {
err = check_load(sched_cls[i], BPF_PROG_TYPE_SCHED_CLS); test__force_log();
printf("test_scale:%s:%s\n", sched_cls[i], err ? "FAIL" : "OK"); old_print_fn = libbpf_set_print(libbpf_debug_print);
} }
for (i = 0; i < ARRAY_SIZE(raw_tp); i++) { for (i = 0; i < ARRAY_SIZE(tests); i++) {
err = check_load(raw_tp[i], BPF_PROG_TYPE_RAW_TRACEPOINT); const struct scale_test_def *test = &tests[i];
printf("test_scale:%s:%s\n", raw_tp[i], err ? "FAIL" : "OK");
} if (!test__start_subtest(test->file))
continue;
for (i = 0; i < ARRAY_SIZE(cg_sysctl); i++) { err = check_load(test->file, test->attach_type);
err = check_load(cg_sysctl[i], BPF_PROG_TYPE_CGROUP_SYSCTL); if (test->fails) { /* expected to fail */
printf("test_scale:%s:%s\n", cg_sysctl[i], err ? "FAIL" : "OK"); if (err)
error_cnt--;
else
error_cnt++;
}
} }
err = check_load("./test_xdp_loop.o", BPF_PROG_TYPE_XDP);
printf("test_scale:test_xdp_loop:%s\n", err ? "FAIL" : "OK");
err = check_load("./test_seg6_loop.o", BPF_PROG_TYPE_LWT_SEG6LOCAL); if (env.verifier_stats)
printf("test_scale:test_seg6_loop:%s\n", err ? "FAIL" : "OK"); libbpf_set_print(old_print_fn);
} }
...@@ -41,7 +41,7 @@ static void get_stack_print_output(void *ctx, int cpu, void *data, __u32 size) ...@@ -41,7 +41,7 @@ static void get_stack_print_output(void *ctx, int cpu, void *data, __u32 size)
* just assume it is good if the stack is not empty. * just assume it is good if the stack is not empty.
* This could be improved in the future. * This could be improved in the future.
*/ */
if (jit_enabled) { if (env.jit_enabled) {
found = num_stack > 0; found = num_stack > 0;
} else { } else {
for (i = 0; i < num_stack; i++) { for (i = 0; i < num_stack; i++) {
...@@ -58,7 +58,7 @@ static void get_stack_print_output(void *ctx, int cpu, void *data, __u32 size) ...@@ -58,7 +58,7 @@ static void get_stack_print_output(void *ctx, int cpu, void *data, __u32 size)
} }
} else { } else {
num_stack = e->kern_stack_size / sizeof(__u64); num_stack = e->kern_stack_size / sizeof(__u64);
if (jit_enabled) { if (env.jit_enabled) {
good_kern_stack = num_stack > 0; good_kern_stack = num_stack > 0;
} else { } else {
for (i = 0; i < num_stack; i++) { for (i = 0; i < num_stack; i++) {
......
...@@ -74,7 +74,7 @@ static void test_l4lb(const char *file) ...@@ -74,7 +74,7 @@ static void test_l4lb(const char *file)
} }
if (bytes != MAGIC_BYTES * NUM_ITER * 2 || pkts != NUM_ITER * 2) { if (bytes != MAGIC_BYTES * NUM_ITER * 2 || pkts != NUM_ITER * 2) {
error_cnt++; error_cnt++;
printf("test_l4lb:FAIL:stats %lld %lld\n", bytes, pkts); test__printf("test_l4lb:FAIL:stats %lld %lld\n", bytes, pkts);
} }
out: out:
bpf_object__close(obj); bpf_object__close(obj);
......
...@@ -9,12 +9,12 @@ static void *parallel_map_access(void *arg) ...@@ -9,12 +9,12 @@ static void *parallel_map_access(void *arg)
for (i = 0; i < 10000; i++) { for (i = 0; i < 10000; i++) {
err = bpf_map_lookup_elem_flags(map_fd, &key, vars, BPF_F_LOCK); err = bpf_map_lookup_elem_flags(map_fd, &key, vars, BPF_F_LOCK);
if (err) { if (err) {
printf("lookup failed\n"); test__printf("lookup failed\n");
error_cnt++; error_cnt++;
goto out; goto out;
} }
if (vars[0] != 0) { if (vars[0] != 0) {
printf("lookup #%d var[0]=%d\n", i, vars[0]); test__printf("lookup #%d var[0]=%d\n", i, vars[0]);
error_cnt++; error_cnt++;
goto out; goto out;
} }
...@@ -22,8 +22,8 @@ static void *parallel_map_access(void *arg) ...@@ -22,8 +22,8 @@ static void *parallel_map_access(void *arg)
for (j = 2; j < 17; j++) { for (j = 2; j < 17; j++) {
if (vars[j] == rnd) if (vars[j] == rnd)
continue; continue;
printf("lookup #%d var[1]=%d var[%d]=%d\n", test__printf("lookup #%d var[1]=%d var[%d]=%d\n",
i, rnd, j, vars[j]); i, rnd, j, vars[j]);
error_cnt++; error_cnt++;
goto out; goto out;
} }
...@@ -43,7 +43,7 @@ void test_map_lock(void) ...@@ -43,7 +43,7 @@ void test_map_lock(void)
err = bpf_prog_load(file, BPF_PROG_TYPE_CGROUP_SKB, &obj, &prog_fd); err = bpf_prog_load(file, BPF_PROG_TYPE_CGROUP_SKB, &obj, &prog_fd);
if (err) { if (err) {
printf("test_map_lock:bpf_prog_load errno %d\n", errno); test__printf("test_map_lock:bpf_prog_load errno %d\n", errno);
goto close_prog; goto close_prog;
} }
map_fd[0] = bpf_find_map(__func__, obj, "hash_map"); map_fd[0] = bpf_find_map(__func__, obj, "hash_map");
......
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#include <test_progs.h> #include <test_progs.h>
static int libbpf_debug_print(enum libbpf_print_level level,
const char *format, va_list args)
{
if (level == LIBBPF_DEBUG)
return 0;
return vfprintf(stderr, format, args);
}
void test_reference_tracking(void) void test_reference_tracking(void)
{ {
const char *file = "./test_sk_lookup_kern.o"; const char *file = "./test_sk_lookup_kern.o";
...@@ -36,9 +27,11 @@ void test_reference_tracking(void) ...@@ -36,9 +27,11 @@ void test_reference_tracking(void)
/* Expect verifier failure if test name has 'fail' */ /* Expect verifier failure if test name has 'fail' */
if (strstr(title, "fail") != NULL) { if (strstr(title, "fail") != NULL) {
libbpf_set_print(NULL); libbpf_print_fn_t old_print_fn;
old_print_fn = libbpf_set_print(NULL);
err = !bpf_program__load(prog, "GPL", 0); err = !bpf_program__load(prog, "GPL", 0);
libbpf_set_print(libbpf_debug_print); libbpf_set_print(old_print_fn);
} else { } else {
err = bpf_program__load(prog, "GPL", 0); err = bpf_program__load(prog, "GPL", 0);
} }
......
...@@ -202,8 +202,8 @@ static int test_send_signal_nmi(void) ...@@ -202,8 +202,8 @@ static int test_send_signal_nmi(void)
-1 /* cpu */, -1 /* group_fd */, 0 /* flags */); -1 /* cpu */, -1 /* group_fd */, 0 /* flags */);
if (pmu_fd == -1) { if (pmu_fd == -1) {
if (errno == ENOENT) { if (errno == ENOENT) {
printf("%s:SKIP:no PERF_COUNT_HW_CPU_CYCLES\n", test__printf("%s:SKIP:no PERF_COUNT_HW_CPU_CYCLES\n",
__func__); __func__);
return 0; return 0;
} }
/* Let the test fail with a more informative message */ /* Let the test fail with a more informative message */
...@@ -219,11 +219,10 @@ void test_send_signal(void) ...@@ -219,11 +219,10 @@ void test_send_signal(void)
{ {
int ret = 0; int ret = 0;
ret |= test_send_signal_tracepoint(); if (test__start_subtest("send_signal_tracepoint"))
ret |= test_send_signal_perf(); ret |= test_send_signal_tracepoint();
ret |= test_send_signal_nmi(); if (test__start_subtest("send_signal_perf"))
if (!ret) ret |= test_send_signal_perf();
printf("test_send_signal:OK\n"); if (test__start_subtest("send_signal_nmi"))
else ret |= test_send_signal_nmi();
printf("test_send_signal:FAIL\n");
} }
...@@ -12,7 +12,7 @@ void test_spinlock(void) ...@@ -12,7 +12,7 @@ void test_spinlock(void)
err = bpf_prog_load(file, BPF_PROG_TYPE_CGROUP_SKB, &obj, &prog_fd); err = bpf_prog_load(file, BPF_PROG_TYPE_CGROUP_SKB, &obj, &prog_fd);
if (err) { if (err) {
printf("test_spin_lock:bpf_prog_load errno %d\n", errno); test__printf("test_spin_lock:bpf_prog_load errno %d\n", errno);
goto close_prog; goto close_prog;
} }
for (i = 0; i < 4; i++) for (i = 0; i < 4; i++)
......
...@@ -109,8 +109,8 @@ void test_stacktrace_build_id(void) ...@@ -109,8 +109,8 @@ void test_stacktrace_build_id(void)
if (build_id_matches < 1 && retry--) { if (build_id_matches < 1 && retry--) {
bpf_link__destroy(link); bpf_link__destroy(link);
bpf_object__close(obj); bpf_object__close(obj);
printf("%s:WARN:Didn't find expected build ID from the map, retrying\n", test__printf("%s:WARN:Didn't find expected build ID from the map, retrying\n",
__func__); __func__);
goto retry; goto retry;
} }
......
...@@ -140,8 +140,8 @@ void test_stacktrace_build_id_nmi(void) ...@@ -140,8 +140,8 @@ void test_stacktrace_build_id_nmi(void)
if (build_id_matches < 1 && retry--) { if (build_id_matches < 1 && retry--) {
bpf_link__destroy(link); bpf_link__destroy(link);
bpf_object__close(obj); bpf_object__close(obj);
printf("%s:WARN:Didn't find expected build ID from the map, retrying\n", test__printf("%s:WARN:Didn't find expected build ID from the map, retrying\n",
__func__); __func__);
goto retry; goto retry;
} }
......
...@@ -75,7 +75,8 @@ void test_xdp_noinline(void) ...@@ -75,7 +75,8 @@ void test_xdp_noinline(void)
} }
if (bytes != MAGIC_BYTES * NUM_ITER * 2 || pkts != NUM_ITER * 2) { if (bytes != MAGIC_BYTES * NUM_ITER * 2 || pkts != NUM_ITER * 2) {
error_cnt++; error_cnt++;
printf("test_xdp_noinline:FAIL:stats %lld %lld\n", bytes, pkts); test__printf("test_xdp_noinline:FAIL:stats %lld %lld\n",
bytes, pkts);
} }
out: out:
bpf_object__close(obj); bpf_object__close(obj);
......
...@@ -3,10 +3,160 @@ ...@@ -3,10 +3,160 @@
*/ */
#include "test_progs.h" #include "test_progs.h"
#include "bpf_rlimit.h" #include "bpf_rlimit.h"
#include <argp.h>
#include <string.h>
/* defined in test_progs.h */
struct test_env env;
int error_cnt, pass_cnt; int error_cnt, pass_cnt;
bool jit_enabled;
bool verifier_stats = false; struct prog_test_def {
const char *test_name;
int test_num;
void (*run_test)(void);
bool force_log;
int pass_cnt;
int error_cnt;
bool tested;
const char *subtest_name;
int subtest_num;
/* store counts before subtest started */
int old_pass_cnt;
int old_error_cnt;
};
static bool should_run(struct test_selector *sel, int num, const char *name)
{
if (sel->name && sel->name[0] && !strstr(name, sel->name))
return false;
if (!sel->num_set)
return true;
return num < sel->num_set_len && sel->num_set[num];
}
static void dump_test_log(const struct prog_test_def *test, bool failed)
{
if (env.verbose || test->force_log || failed) {
if (env.log_cnt) {
fprintf(stdout, "%s", env.log_buf);
if (env.log_buf[env.log_cnt - 1] != '\n')
fprintf(stdout, "\n");
}
env.log_cnt = 0;
}
}
void test__end_subtest()
{
struct prog_test_def *test = env.test;
int sub_error_cnt = error_cnt - test->old_error_cnt;
if (sub_error_cnt)
env.fail_cnt++;
else
env.sub_succ_cnt++;
dump_test_log(test, sub_error_cnt);
printf("#%d/%d %s:%s\n",
test->test_num, test->subtest_num,
test->subtest_name, sub_error_cnt ? "FAIL" : "OK");
}
bool test__start_subtest(const char *name)
{
struct prog_test_def *test = env.test;
if (test->subtest_name) {
test__end_subtest();
test->subtest_name = NULL;
}
test->subtest_num++;
if (!name || !name[0]) {
fprintf(stderr, "Subtest #%d didn't provide sub-test name!\n",
test->subtest_num);
return false;
}
if (!should_run(&env.subtest_selector, test->subtest_num, name))
return false;
test->subtest_name = name;
env.test->old_pass_cnt = pass_cnt;
env.test->old_error_cnt = error_cnt;
return true;
}
void test__force_log() {
env.test->force_log = true;
}
void test__vprintf(const char *fmt, va_list args)
{
size_t rem_sz;
int ret = 0;
if (env.verbose || (env.test && env.test->force_log)) {
vfprintf(stderr, fmt, args);
return;
}
try_again:
rem_sz = env.log_cap - env.log_cnt;
if (rem_sz) {
va_list ap;
va_copy(ap, args);
/* we reserved extra byte for \0 at the end */
ret = vsnprintf(env.log_buf + env.log_cnt, rem_sz + 1, fmt, ap);
va_end(ap);
if (ret < 0) {
env.log_buf[env.log_cnt] = '\0';
fprintf(stderr, "failed to log w/ fmt '%s'\n", fmt);
return;
}
}
if (!rem_sz || ret > rem_sz) {
size_t new_sz = env.log_cap * 3 / 2;
char *new_buf;
if (new_sz < 4096)
new_sz = 4096;
if (new_sz < ret + env.log_cnt)
new_sz = ret + env.log_cnt;
/* +1 for guaranteed space for terminating \0 */
new_buf = realloc(env.log_buf, new_sz + 1);
if (!new_buf) {
fprintf(stderr, "failed to realloc log buffer: %d\n",
errno);
return;
}
env.log_buf = new_buf;
env.log_cap = new_sz;
goto try_again;
}
env.log_cnt += ret;
}
void test__printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
test__vprintf(fmt, args);
va_end(args);
}
struct ipv4_packet pkt_v4 = { struct ipv4_packet pkt_v4 = {
.eth.h_proto = __bpf_constant_htons(ETH_P_IP), .eth.h_proto = __bpf_constant_htons(ETH_P_IP),
...@@ -156,23 +306,232 @@ void *spin_lock_thread(void *arg) ...@@ -156,23 +306,232 @@ void *spin_lock_thread(void *arg)
pthread_exit(arg); pthread_exit(arg);
} }
#define DECLARE /* extern declarations for test funcs */
#define DEFINE_TEST(name) extern void test_##name();
#include <prog_tests/tests.h> #include <prog_tests/tests.h>
#undef DECLARE #undef DEFINE_TEST
static struct prog_test_def prog_test_defs[] = {
#define DEFINE_TEST(name) { \
.test_name = #name, \
.run_test = &test_##name, \
},
#include <prog_tests/tests.h>
#undef DEFINE_TEST
};
const int prog_test_cnt = ARRAY_SIZE(prog_test_defs);
int main(int ac, char **av) const char *argp_program_version = "test_progs 0.1";
const char *argp_program_bug_address = "<bpf@vger.kernel.org>";
const char argp_program_doc[] = "BPF selftests test runner";
enum ARG_KEYS {
ARG_TEST_NUM = 'n',
ARG_TEST_NAME = 't',
ARG_VERIFIER_STATS = 's',
ARG_VERBOSE = 'v',
};
static const struct argp_option opts[] = {
{ "num", ARG_TEST_NUM, "NUM", 0,
"Run test number NUM only " },
{ "name", ARG_TEST_NAME, "NAME", 0,
"Run tests with names containing NAME" },
{ "verifier-stats", ARG_VERIFIER_STATS, NULL, 0,
"Output verifier statistics", },
{ "verbose", ARG_VERBOSE, "LEVEL", OPTION_ARG_OPTIONAL,
"Verbose output (use -vv for extra verbose output)" },
{},
};
static int libbpf_print_fn(enum libbpf_print_level level,
const char *format, va_list args)
{
if (!env.very_verbose && level == LIBBPF_DEBUG)
return 0;
test__vprintf(format, args);
return 0;
}
int parse_num_list(const char *s, struct test_selector *sel)
{
int i, set_len = 0, num, start = 0, end = -1;
bool *set = NULL, *tmp, parsing_end = false;
char *next;
while (s[0]) {
errno = 0;
num = strtol(s, &next, 10);
if (errno)
return -errno;
if (parsing_end)
end = num;
else
start = num;
if (!parsing_end && *next == '-') {
s = next + 1;
parsing_end = true;
continue;
} else if (*next == ',') {
parsing_end = false;
s = next + 1;
end = num;
} else if (*next == '\0') {
parsing_end = false;
s = next;
end = num;
} else {
return -EINVAL;
}
if (start > end)
return -EINVAL;
if (end + 1 > set_len) {
set_len = end + 1;
tmp = realloc(set, set_len);
if (!tmp) {
free(set);
return -ENOMEM;
}
set = tmp;
}
for (i = start; i <= end; i++) {
set[i] = true;
}
}
if (!set)
return -EINVAL;
sel->num_set = set;
sel->num_set_len = set_len;
return 0;
}
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{ {
struct test_env *env = state->input;
switch (key) {
case ARG_TEST_NUM: {
char *subtest_str = strchr(arg, '/');
if (subtest_str) {
*subtest_str = '\0';
if (parse_num_list(subtest_str + 1,
&env->subtest_selector)) {
fprintf(stderr,
"Failed to parse subtest numbers.\n");
return -EINVAL;
}
}
if (parse_num_list(arg, &env->test_selector)) {
fprintf(stderr, "Failed to parse test numbers.\n");
return -EINVAL;
}
break;
}
case ARG_TEST_NAME: {
char *subtest_str = strchr(arg, '/');
if (subtest_str) {
*subtest_str = '\0';
env->subtest_selector.name = strdup(subtest_str + 1);
if (!env->subtest_selector.name)
return -ENOMEM;
}
env->test_selector.name = strdup(arg);
if (!env->test_selector.name)
return -ENOMEM;
break;
}
case ARG_VERIFIER_STATS:
env->verifier_stats = true;
break;
case ARG_VERBOSE:
if (arg) {
if (strcmp(arg, "v") == 0) {
env->very_verbose = true;
} else {
fprintf(stderr,
"Unrecognized verbosity setting ('%s'), only -v and -vv are supported\n",
arg);
return -EINVAL;
}
}
env->verbose = true;
break;
case ARGP_KEY_ARG:
argp_usage(state);
break;
case ARGP_KEY_END:
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
int main(int argc, char **argv)
{
static const struct argp argp = {
.options = opts,
.parser = parse_arg,
.doc = argp_program_doc,
};
int err, i;
err = argp_parse(&argp, argc, argv, 0, NULL, &env);
if (err)
return err;
libbpf_set_print(libbpf_print_fn);
srand(time(NULL)); srand(time(NULL));
jit_enabled = is_jit_enabled(); env.jit_enabled = is_jit_enabled();
if (ac == 2 && strcmp(av[1], "-s") == 0) for (i = 0; i < prog_test_cnt; i++) {
verifier_stats = true; struct prog_test_def *test = &prog_test_defs[i];
int old_pass_cnt = pass_cnt;
int old_error_cnt = error_cnt;
#define CALL env.test = test;
#include <prog_tests/tests.h> test->test_num = i + 1;
#undef CALL
if (!should_run(&env.test_selector,
test->test_num, test->test_name))
continue;
test->run_test();
/* ensure last sub-test is finalized properly */
if (test->subtest_name)
test__end_subtest();
test->tested = true;
test->pass_cnt = pass_cnt - old_pass_cnt;
test->error_cnt = error_cnt - old_error_cnt;
if (test->error_cnt)
env.fail_cnt++;
else
env.succ_cnt++;
dump_test_log(test, test->error_cnt);
printf("#%d %s:%s\n", test->test_num, test->test_name,
test->error_cnt ? "FAIL" : "OK");
}
printf("Summary: %d/%d PASSED, %d FAILED\n",
env.succ_cnt, env.sub_succ_cnt, env.fail_cnt);
free(env.log_buf);
free(env.test_selector.num_set);
free(env.subtest_selector.num_set);
printf("Summary: %d PASSED, %d FAILED\n", pass_cnt, error_cnt);
return error_cnt ? EXIT_FAILURE : EXIT_SUCCESS; return error_cnt ? EXIT_FAILURE : EXIT_SUCCESS;
} }
...@@ -38,9 +38,41 @@ typedef __u16 __sum16; ...@@ -38,9 +38,41 @@ typedef __u16 __sum16;
#include "trace_helpers.h" #include "trace_helpers.h"
#include "flow_dissector_load.h" #include "flow_dissector_load.h"
extern int error_cnt, pass_cnt; struct prog_test_def;
extern bool jit_enabled;
extern bool verifier_stats; struct test_selector {
const char *name;
bool *num_set;
int num_set_len;
};
struct test_env {
struct test_selector test_selector;
struct test_selector subtest_selector;
bool verifier_stats;
bool verbose;
bool very_verbose;
bool jit_enabled;
struct prog_test_def *test;
char *log_buf;
size_t log_cnt;
size_t log_cap;
int succ_cnt; /* successful tests */
int sub_succ_cnt; /* successful sub-tests */
int fail_cnt; /* total failed tests + sub-tests */
};
extern int error_cnt;
extern int pass_cnt;
extern struct test_env env;
extern void test__printf(const char *fmt, ...);
extern void test__vprintf(const char *fmt, va_list args);
extern void test__force_log();
extern bool test__start_subtest(const char *name);
#define MAGIC_BYTES 123 #define MAGIC_BYTES 123
...@@ -64,11 +96,12 @@ extern struct ipv6_packet pkt_v6; ...@@ -64,11 +96,12 @@ extern struct ipv6_packet pkt_v6;
int __ret = !!(condition); \ int __ret = !!(condition); \
if (__ret) { \ if (__ret) { \
error_cnt++; \ error_cnt++; \
printf("%s:FAIL:%s ", __func__, tag); \ test__printf("%s:FAIL:%s ", __func__, tag); \
printf(format); \ test__printf(format); \
} else { \ } else { \
pass_cnt++; \ pass_cnt++; \
printf("%s:PASS:%s %d nsec\n", __func__, tag, duration);\ test__printf("%s:PASS:%s %d nsec\n", \
__func__, tag, duration); \
} \ } \
__ret; \ __ret; \
}) })
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment