Skip to content

Commit 2742eb6

Browse files
chantraKernel Patches Daemon
authored and
Kernel Patches Daemon
committed
selftests/bpf: add --json-summary option to test_progs
Currently, test_progs outputs all stdout/stderr as it runs, and when it is done, prints a summary. It is non-trivial for tooling to parse that output and extract meaningful information from it. This change adds a new option, `--json-summary`/`-J` that let the caller specify a file where `test_progs{,-no_alu32}` can write a summary of the run in a json format that can later be parsed by tooling. Currently, it creates a summary section with successes/skipped/failures followed by a list of failed tests and subtests. A test contains the following fields: - name: the name of the test - number: the number of the test - message: the log message that was printed by the test. - failed: A boolean indicating whether the test failed or not. Currently we only output failed tests, but in the future, successful tests could be added. - subtests: A list of subtests associated with this test. A subtest contains the following fields: - name: same as above - number: sanme as above - message: the log message that was printed by the subtest. - failed: same as above but for the subtest An example run and json content below: ``` $ sudo ./test_progs -a $(grep -v '^#' ./DENYLIST.aarch64 | awk '{print $1","}' | tr -d '\n') -j -J /tmp/test_progs.json $ jq < /tmp/test_progs.json | head -n 30 { "success": 29, "success_subtest": 23, "skipped": 3, "failed": 28, "results": [ { "name": "bpf_cookie", "number": 10, "message": "test_bpf_cookie:PASS:skel_open 0 nsec\n", "failed": true, "subtests": [ { "name": "multi_kprobe_link_api", "number": 2, "message": "kprobe_multi_link_api_subtest:PASS:load_kallsyms 0 nsec\nlibbpf: extern 'bpf_testmod_fentry_test1' (strong): not resolved\nlibbpf: failed to load object 'kprobe_multi'\nlibbpf: failed to load BPF skeleton 'kprobe_multi': -3\nkprobe_multi_link_api_subtest:FAIL:fentry_raw_skel_load unexpected error: -3\n", "failed": true }, { "name": "multi_kprobe_attach_api", "number": 3, "message": "libbpf: extern 'bpf_testmod_fentry_test1' (strong): not resolved\nlibbpf: failed to load object 'kprobe_multi'\nlibbpf: failed to load BPF skeleton 'kprobe_multi': -3\nkprobe_multi_attach_api_subtest:FAIL:fentry_raw_skel_load unexpected error: -3\n", "failed": true }, { "name": "lsm", "number": 8, "message": "lsm_subtest:PASS:lsm.link_create 0 nsec\nlsm_subtest:FAIL:stack_mprotect unexpected stack_mprotect: actual 0 != expected -1\n", "failed": true } ``` The file can then be used to print a summary of the test run and list of failing tests/subtests: ``` $ jq -r < /tmp/test_progs.json '"Success: \(.success)/\(.success_subtest), Skipped: \(.skipped), Failed: \(.failed)"' Success: 29/23, Skipped: 3, Failed: 28 $ jq -r < /tmp/test_progs.json '.results | map([ if .failed then "#\(.number) \(.name)" else empty end, ( . as {name: $tname, number: $tnum} | .subtests | map( if .failed then "#\($tnum)/\(.number) \($tname)/\(.name)" else empty end ) ) ]) | flatten | .[]' | head -n 20 #10 bpf_cookie #10/2 bpf_cookie/multi_kprobe_link_api #10/3 bpf_cookie/multi_kprobe_attach_api #10/8 bpf_cookie/lsm #15 bpf_mod_race #15/1 bpf_mod_race/ksym (used_btfs UAF) #15/2 bpf_mod_race/kfunc (kfunc_btf_tab UAF) #36 cgroup_hierarchical_stats #61 deny_namespace #61/1 deny_namespace/unpriv_userns_create_no_bpf #73 fexit_stress #83 get_func_ip_test #99 kfunc_dynptr_param #99/1 kfunc_dynptr_param/dynptr_data_null #99/4 kfunc_dynptr_param/dynptr_data_null #100 kprobe_multi_bench_attach #100/1 kprobe_multi_bench_attach/kernel #100/2 kprobe_multi_bench_attach/modules #101 kprobe_multi_test #101/1 kprobe_multi_test/skel_api ``` Signed-off-by: Manu Bretelle <[email protected]>
1 parent a76ed8b commit 2742eb6

File tree

5 files changed

+87
-6
lines changed

5 files changed

+87
-6
lines changed

tools/testing/selftests/bpf/Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(BPFOBJ)
234234
CGROUP_HELPERS := $(OUTPUT)/cgroup_helpers.o
235235
TESTING_HELPERS := $(OUTPUT)/testing_helpers.o
236236
TRACE_HELPERS := $(OUTPUT)/trace_helpers.o
237+
JSON_WRITER := $(OUTPUT)/json_writer.o
237238
CAP_HELPERS := $(OUTPUT)/cap_helpers.o
238239

239240
$(OUTPUT)/test_dev_cgroup: $(CGROUP_HELPERS) $(TESTING_HELPERS)
@@ -559,7 +560,8 @@ TRUNNER_BPF_PROGS_DIR := progs
559560
TRUNNER_EXTRA_SOURCES := test_progs.c cgroup_helpers.c trace_helpers.c \
560561
network_helpers.c testing_helpers.c \
561562
btf_helpers.c flow_dissector_load.h \
562-
cap_helpers.c test_loader.c xsk.c disasm.c
563+
cap_helpers.c test_loader.c xsk.c disasm.c \
564+
json_writer.c
563565
TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read $(OUTPUT)/bpf_testmod.ko \
564566
$(OUTPUT)/liburandom_read.so \
565567
$(OUTPUT)/xdp_synproxy \
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../bpf/bpftool/json_writer.c
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../bpf/bpftool/json_writer.h

tools/testing/selftests/bpf/test_progs.c

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <sys/socket.h>
1919
#include <sys/un.h>
2020
#include <bpf/btf.h>
21+
#include "json_writer.h"
2122

2223
static bool verbose(void)
2324
{
@@ -269,10 +270,23 @@ static void print_subtest_name(int test_num, int subtest_num,
269270
fprintf(env.stdout, "\n");
270271
}
271272

273+
static void jsonw_write_log_message(json_writer_t *w, char *log_buf, size_t log_cnt)
274+
{
275+
/* open_memstream (from stdio_hijack_init) ensures that log_bug is terminated by a
276+
* null byte. Yet in parallel mode, log_buf will be NULL if there is no message.
277+
*/
278+
if (log_cnt) {
279+
jsonw_string_field(w, "message", log_buf);
280+
} else {
281+
jsonw_string_field(w, "message", "");
282+
}
283+
}
284+
272285
static void dump_test_log(const struct prog_test_def *test,
273286
const struct test_state *test_state,
274287
bool skip_ok_subtests,
275-
bool par_exec_result)
288+
bool par_exec_result,
289+
json_writer_t *w)
276290
{
277291
bool test_failed = test_state->error_cnt > 0;
278292
bool force_log = test_state->force_log;
@@ -296,6 +310,16 @@ static void dump_test_log(const struct prog_test_def *test,
296310
if (test_state->log_cnt && print_test)
297311
print_test_log(test_state->log_buf, test_state->log_cnt);
298312

313+
if (w && print_test) {
314+
jsonw_start_object(w);
315+
jsonw_string_field(w, "name", test->test_name);
316+
jsonw_uint_field(w, "number", test->test_num);
317+
jsonw_write_log_message(w, test_state->log_buf, test_state->log_cnt);
318+
jsonw_bool_field(w, "failed", test_failed);
319+
jsonw_name(w, "subtests");
320+
jsonw_start_array(w);
321+
}
322+
299323
for (i = 0; i < test_state->subtest_num; i++) {
300324
subtest_state = &test_state->subtest_states[i];
301325
subtest_failed = subtest_state->error_cnt;
@@ -314,8 +338,24 @@ static void dump_test_log(const struct prog_test_def *test,
314338
test->test_name, subtest_state->name,
315339
test_result(subtest_state->error_cnt,
316340
subtest_state->skipped));
341+
342+
if (w && print_subtest) {
343+
jsonw_start_object(w);
344+
jsonw_string_field(w, "name", subtest_state->name);
345+
jsonw_uint_field(w, "number", i+1);
346+
jsonw_write_log_message(w, subtest_state->log_buf, subtest_state->log_cnt);
347+
jsonw_bool_field(w, "failed", subtest_failed);
348+
jsonw_end_object(w);
349+
}
350+
351+
}
352+
353+
if (w && print_test) {
354+
jsonw_end_array(w);
355+
jsonw_end_object(w);
317356
}
318357

358+
319359
print_test_result(test, test_state);
320360
}
321361

@@ -715,6 +755,7 @@ enum ARG_KEYS {
715755
ARG_TEST_NAME_GLOB_DENYLIST = 'd',
716756
ARG_NUM_WORKERS = 'j',
717757
ARG_DEBUG = -1,
758+
ARG_JSON_SUMMARY = 'J'
718759
};
719760

720761
static const struct argp_option opts[] = {
@@ -740,6 +781,7 @@ static const struct argp_option opts[] = {
740781
"Number of workers to run in parallel, default to number of cpus." },
741782
{ "debug", ARG_DEBUG, NULL, 0,
742783
"print extra debug information for test_progs." },
784+
{ "json-summary", ARG_JSON_SUMMARY, "FILE", 0, "Write report in json format to this file."},
743785
{},
744786
};
745787

@@ -870,6 +912,13 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
870912
case ARG_DEBUG:
871913
env->debug = true;
872914
break;
915+
case ARG_JSON_SUMMARY:
916+
env->json = fopen(arg, "w");
917+
if (env->json == NULL) {
918+
perror("Failed to open json summary file");
919+
return -errno;
920+
}
921+
break;
873922
case ARGP_KEY_ARG:
874923
argp_usage(state);
875924
break;
@@ -1017,7 +1066,7 @@ void crash_handler(int signum)
10171066
stdio_restore();
10181067
if (env.test) {
10191068
env.test_state->error_cnt++;
1020-
dump_test_log(env.test, env.test_state, true, false);
1069+
dump_test_log(env.test, env.test_state, true, false, NULL);
10211070
}
10221071
if (env.worker_id != -1)
10231072
fprintf(stderr, "[%d]: ", env.worker_id);
@@ -1124,7 +1173,7 @@ static void run_one_test(int test_num)
11241173

11251174
stdio_restore();
11261175

1127-
dump_test_log(test, state, false, false);
1176+
dump_test_log(test, state, false, false, NULL);
11281177
}
11291178

11301179
struct dispatch_data {
@@ -1283,7 +1332,7 @@ static void *dispatch_thread(void *ctx)
12831332
} while (false);
12841333

12851334
pthread_mutex_lock(&stdout_output_lock);
1286-
dump_test_log(test, state, false, true);
1335+
dump_test_log(test, state, false, true, NULL);
12871336
pthread_mutex_unlock(&stdout_output_lock);
12881337
} /* while (true) */
12891338
error:
@@ -1308,6 +1357,7 @@ static void calculate_summary_and_print_errors(struct test_env *env)
13081357
{
13091358
int i;
13101359
int succ_cnt = 0, fail_cnt = 0, sub_succ_cnt = 0, skip_cnt = 0;
1360+
json_writer_t *w = NULL;
13111361

13121362
for (i = 0; i < prog_test_cnt; i++) {
13131363
struct test_state *state = &test_states[i];
@@ -1322,8 +1372,25 @@ static void calculate_summary_and_print_errors(struct test_env *env)
13221372
fail_cnt++;
13231373
else
13241374
succ_cnt++;
1375+
1376+
}
1377+
1378+
if (env->json) {
1379+
w = jsonw_new(env->json);
1380+
if (!w)
1381+
fprintf(env->stderr, "Failed to create new JSON stream.");
13251382
}
13261383

1384+
if (w) {
1385+
jsonw_start_object(w);
1386+
jsonw_uint_field(w, "success", succ_cnt);
1387+
jsonw_uint_field(w, "success_subtest", sub_succ_cnt);
1388+
jsonw_uint_field(w, "skipped", skip_cnt);
1389+
jsonw_uint_field(w, "failed", fail_cnt);
1390+
jsonw_name(w, "results");
1391+
jsonw_start_array(w);
1392+
1393+
}
13271394
/*
13281395
* We only print error logs summary when there are failed tests and
13291396
* verbose mode is not enabled. Otherwise, results may be incosistent.
@@ -1340,10 +1407,19 @@ static void calculate_summary_and_print_errors(struct test_env *env)
13401407
if (!state->tested || !state->error_cnt)
13411408
continue;
13421409

1343-
dump_test_log(test, state, true, true);
1410+
dump_test_log(test, state, true, true, w);
13441411
}
13451412
}
13461413

1414+
if (w) {
1415+
jsonw_end_array(w);
1416+
jsonw_end_object(w);
1417+
jsonw_destroy(&w);
1418+
}
1419+
1420+
if (env->json)
1421+
fclose(env->json);
1422+
13471423
printf("Summary: %d/%d PASSED, %d SKIPPED, %d FAILED\n",
13481424
succ_cnt, sub_succ_cnt, skip_cnt, fail_cnt);
13491425

tools/testing/selftests/bpf/test_progs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ struct test_env {
114114
FILE *stdout;
115115
FILE *stderr;
116116
int nr_cpus;
117+
FILE *json;
117118

118119
int succ_cnt; /* successful tests */
119120
int sub_succ_cnt; /* successful sub-tests */

0 commit comments

Comments
 (0)