All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH bpf-next] bpf/selftests: add granular subtest output for prog_test
@ 2022-04-25  4:56 Mykola Lysenko
  2022-04-25  5:56 ` Mykola Lysenko
  0 siblings, 1 reply; 2+ messages in thread
From: Mykola Lysenko @ 2022-04-25  4:56 UTC (permalink / raw)
  To: bpf, ast, andrii, daniel; +Cc: kernel-team, Mykola Lysenko

Implement per subtest log collection for both parallel
and sequential test execution. This allows granular
per-subtest error output in the 'All error logs' section.
Add subtest log transfer into the protocol during the
parallel test execution.

Move all test log printing logic into dump_test_log
function. One exception is the output of test names when
verbose printing is enabled. Move test name/result
printing into separate functions to avoid repetition.

Print all successful subtest results in the log. Print
only failed test logs when test does not have subtests.
Or only failed subtests' logs when test has subtests.

Disable 'All error logs' output when verbose mode is
enabled. This functionality was already broken and is
causing confusion.

Signed-off-by: Mykola Lysenko <mykolal@fb.com>
---
 tools/testing/selftests/bpf/test_progs.c | 639 +++++++++++++++++------
 tools/testing/selftests/bpf/test_progs.h |  35 +-
 2 files changed, 498 insertions(+), 176 deletions(-)

diff --git a/tools/testing/selftests/bpf/test_progs.c b/tools/testing/selftests/bpf/test_progs.c
index c536d1d29d57..375e10576336 100644
--- a/tools/testing/selftests/bpf/test_progs.c
+++ b/tools/testing/selftests/bpf/test_progs.c
@@ -18,6 +18,90 @@
 #include <sys/socket.h>
 #include <sys/un.h>
 
+static bool verbose(void)
+{
+	return env.verbosity > VERBOSE_NONE;
+}
+
+static void stdio_hijack_init(char **log_buf, size_t *log_cnt)
+{
+#ifdef __GLIBC__
+	if (verbose() && env.worker_id == -1) {
+		/* nothing to do, output to stdout by default */
+		return;
+	}
+
+	fflush(stdout);
+	fflush(stderr);
+
+	stdout = open_memstream(log_buf, log_cnt);
+	if (!stdout) {
+		stdout = env.stdout;
+		perror("open_memstream");
+		return;
+	}
+
+	if (env.subtest_state)
+		env.subtest_state->stdout = stdout;
+	else
+		env.test_state->stdout = stdout;
+
+	stderr = stdout;
+#endif
+}
+
+static void stdio_hijack(char **log_buf, size_t *log_cnt)
+{
+#ifdef __GLIBC__
+	if (verbose() && env.worker_id == -1) {
+		/* nothing to do, output to stdout by default */
+		return;
+	}
+
+	env.stdout = stdout;
+	env.stderr = stderr;
+
+	stdio_hijack_init(log_buf, log_cnt);
+#endif
+}
+
+static void stdio_restore_cleanup(void)
+{
+#ifdef __GLIBC__
+	if (verbose() && env.worker_id == -1) {
+		/* nothing to do, output to stdout by default */
+		return;
+	}
+
+	fflush(stdout);
+
+	if (env.subtest_state) {
+		fclose(env.subtest_state->stdout);
+		stdout = env.test_state->stdout;
+	} else {
+		fclose(env.test_state->stdout);
+	}
+#endif
+}
+
+static void stdio_restore(void)
+{
+#ifdef __GLIBC__
+	if (verbose() && env.worker_id == -1) {
+		/* nothing to do, output to stdout by default */
+		return;
+	}
+
+	if (stdout == env.stdout)
+		return;
+
+	stdio_restore_cleanup();
+
+	stdout = env.stdout;
+	stderr = env.stderr;
+#endif
+}
+
 /* Adapted from perf/util/string.c */
 static bool glob_match(const char *str, const char *pat)
 {
@@ -130,30 +214,96 @@ static bool should_run_subtest(struct test_selector *sel,
 	return subtest_num < subtest_sel->num_set_len && subtest_sel->num_set[subtest_num];
 }
 
+static char *test_result(bool failed, bool skipped)
+{
+	return failed ? "FAIL" : (skipped ? "SKIP" : "OK");
+}
+
+static void print_test_log(char *log_buf, size_t log_cnt)
+{
+	log_buf[log_cnt] = '\0';
+	fprintf(env.stdout, "%s", log_buf);
+	if (log_buf[log_cnt - 1] != '\n')
+		fprintf(env.stdout, "\n");
+}
+
+static void print_test_name(int test_num, const char *test_name, char *result)
+{
+	fprintf(env.stdout, "#%-9d %s", test_num, test_name);
+
+	if (result)
+		fprintf(env.stdout, ":%s", result);
+
+	fprintf(env.stdout, "\n");
+}
+
+static void print_subtest_name(int test_num, int subtest_num,
+			       const char *test_name, char *subtest_name,
+			       char *result)
+{
+	fprintf(env.stdout, "#%-3d/%-5d %s/%s",
+		test_num, subtest_num,
+		test_name, subtest_name);
+
+	if (result)
+		fprintf(env.stdout, ":%s", result);
+
+	fprintf(env.stdout, "\n");
+}
+
 static void dump_test_log(const struct prog_test_def *test,
 			  const struct test_state *test_state,
-			  bool force_failed)
+			  bool skip_ok_subtests,
+			  bool par_exec_result)
 {
-	bool failed = test_state->error_cnt > 0 || force_failed;
+	bool test_failed = test_state->error_cnt > 0;
+	bool force_log = test_state->force_log;
+	bool print_test = verbose() || force_log || test_failed;
+	int i;
+	struct subtest_state *subtest_state;
+	bool subtest_failed;
+	bool print_subtest;
 
-	/* worker always holds log */
+	/* we do not print anything in the worker thread */
 	if (env.worker_id != -1)
 		return;
 
-	fflush(stdout); /* exports test_state->log_buf & test_state->log_cnt */
+	/* there is nothing to print when verbose log is used and execution
+	 * is not in parallel mode
+	 */
+	if (verbose() && !par_exec_result)
+		return;
+
+	if (test_state->subtest_num || print_test)
+		print_test_name(test->test_num, test->test_name, NULL);
+
+	if (test_state->log_cnt && print_test)
+		print_test_log(test_state->log_buf, test_state->log_cnt);
 
-	fprintf(env.stdout, "#%-3d %s:%s\n",
-		test->test_num, test->test_name,
-		failed ? "FAIL" : (test_state->skip_cnt ? "SKIP" : "OK"));
+	for (i = 0; i < test_state->subtest_num; i++) {
+		subtest_state = &test_state->subtest_states[i];
+		subtest_failed = subtest_state->error_cnt;
+		print_subtest = verbose() || force_log || subtest_failed;
 
-	if (env.verbosity > VERBOSE_NONE || test_state->force_log || failed) {
-		if (test_state->log_cnt) {
-			test_state->log_buf[test_state->log_cnt] = '\0';
-			fprintf(env.stdout, "%s", test_state->log_buf);
-			if (test_state->log_buf[test_state->log_cnt - 1] != '\n')
-				fprintf(env.stdout, "\n");
+		if (skip_ok_subtests && !subtest_failed)
+			continue;
+
+		if (subtest_state->log_cnt && print_subtest) {
+			print_subtest_name(test->test_num, i + 1,
+					   test->test_name, subtest_state->name,
+					   NULL);
+			print_test_log(subtest_state->log_buf,
+				       subtest_state->log_cnt);
 		}
+
+		print_subtest_name(test->test_num, i + 1,
+				   test->test_name, subtest_state->name,
+				   test_result(subtest_state->error_cnt,
+					       subtest_state->skipped));
 	}
+
+	print_test_name(test->test_num, test->test_name,
+			test_result(test_failed, test_state->skip_cnt));
 }
 
 static void stdio_restore(void);
@@ -205,35 +355,50 @@ static void restore_netns(void)
 void test__end_subtest(void)
 {
 	struct prog_test_def *test = env.test;
-	struct test_state *state = env.test_state;
-	int sub_error_cnt = state->error_cnt - state->old_error_cnt;
-
-	fprintf(stdout, "#%d/%d %s/%s:%s\n",
-	       test->test_num, state->subtest_num, test->test_name, state->subtest_name,
-	       sub_error_cnt ? "FAIL" : (state->subtest_skip_cnt ? "SKIP" : "OK"));
-
-	if (sub_error_cnt == 0) {
-		if (state->subtest_skip_cnt == 0) {
-			state->sub_succ_cnt++;
-		} else {
-			state->subtest_skip_cnt = 0;
-			state->skip_cnt++;
-		}
+	struct test_state *test_state = env.test_state;
+	struct subtest_state *subtest_state = env.subtest_state;
+
+	if (subtest_state->error_cnt) {
+		test_state->error_cnt++;
+	} else {
+		if (!subtest_state->skipped)
+			test_state->sub_succ_cnt++;
+		else
+			test_state->skip_cnt++;
 	}
 
-	free(state->subtest_name);
-	state->subtest_name = NULL;
+	if (verbose() && !env.workers)
+		print_subtest_name(test->test_num, test_state->subtest_num,
+				   test->test_name, subtest_state->name,
+				   test_result(subtest_state->error_cnt,
+					       subtest_state->skipped));
+
+	stdio_restore_cleanup();
+	env.subtest_state = NULL;
 }
 
 bool test__start_subtest(const char *subtest_name)
 {
 	struct prog_test_def *test = env.test;
 	struct test_state *state = env.test_state;
+	struct subtest_state *subtest_state;
+	size_t sub_state_size = sizeof(*subtest_state);
 
-	if (state->subtest_name)
+	if (env.subtest_state)
 		test__end_subtest();
 
 	state->subtest_num++;
+	state->subtest_states =
+		realloc(state->subtest_states,
+			state->subtest_num * sub_state_size);
+	if (!state->subtest_states) {
+		fprintf(stderr, "Not enough memory to allocate subtest result\n");
+		return false;
+	}
+
+	subtest_state = &state->subtest_states[state->subtest_num - 1];
+
+	memset(subtest_state, 0, sub_state_size);
 
 	if (!subtest_name || !subtest_name[0]) {
 		fprintf(env.stderr,
@@ -242,21 +407,30 @@ bool test__start_subtest(const char *subtest_name)
 		return false;
 	}
 
+	subtest_state->name = strdup(subtest_name);
+	if (!subtest_state->name) {
+		fprintf(env.stderr,
+			"Subtest #%d: failed to copy subtest name!\n",
+			state->subtest_num);
+		return false;
+	}
+
 	if (!should_run_subtest(&env.test_selector,
 				&env.subtest_selector,
 				state->subtest_num,
 				test->test_name,
-				subtest_name))
-		return false;
-
-	state->subtest_name = strdup(subtest_name);
-	if (!state->subtest_name) {
-		fprintf(env.stderr,
-			"Subtest #%d: failed to copy subtest name!\n",
-			state->subtest_num);
+				subtest_name)) {
+		subtest_state->skipped = true;
 		return false;
 	}
-	state->old_error_cnt = state->error_cnt;
+
+	env.subtest_state = subtest_state;
+	stdio_hijack_init(&subtest_state->log_buf, &subtest_state->log_cnt);
+
+	if (verbose() && !env.workers)
+		print_subtest_name(test->test_num, state->subtest_num,
+				   test->test_name, subtest_state->name,
+				   NULL);
 
 	return true;
 }
@@ -268,15 +442,18 @@ void test__force_log(void)
 
 void test__skip(void)
 {
-	if (env.test_state->subtest_name)
-		env.test_state->subtest_skip_cnt++;
+	if (env.subtest_state)
+		env.subtest_state->skipped = true;
 	else
 		env.test_state->skip_cnt++;
 }
 
 void test__fail(void)
 {
-	env.test_state->error_cnt++;
+	if (env.subtest_state)
+		env.subtest_state->error_cnt++;
+	else
+		env.test_state->error_cnt++;
 }
 
 int test__join_cgroup(const char *path)
@@ -455,14 +632,14 @@ static void unload_bpf_testmod(void)
 		fprintf(env.stderr, "Failed to trigger kernel-side RCU sync!\n");
 	if (delete_module("bpf_testmod", 0)) {
 		if (errno == ENOENT) {
-			if (env.verbosity > VERBOSE_NONE)
+			if (verbose())
 				fprintf(stdout, "bpf_testmod.ko is already unloaded.\n");
 			return;
 		}
 		fprintf(env.stderr, "Failed to unload bpf_testmod.ko from kernel: %d\n", -errno);
 		return;
 	}
-	if (env.verbosity > VERBOSE_NONE)
+	if (verbose())
 		fprintf(stdout, "Successfully unloaded bpf_testmod.ko.\n");
 }
 
@@ -473,7 +650,7 @@ static int load_bpf_testmod(void)
 	/* ensure previous instance of the module is unloaded */
 	unload_bpf_testmod();
 
-	if (env.verbosity > VERBOSE_NONE)
+	if (verbose())
 		fprintf(stdout, "Loading bpf_testmod.ko...\n");
 
 	fd = open("bpf_testmod.ko", O_RDONLY);
@@ -488,7 +665,7 @@ static int load_bpf_testmod(void)
 	}
 	close(fd);
 
-	if (env.verbosity > VERBOSE_NONE)
+	if (verbose())
 		fprintf(stdout, "Successfully loaded bpf_testmod.ko.\n");
 	return 0;
 }
@@ -655,7 +832,7 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
 			}
 		}
 
-		if (env->verbosity > VERBOSE_NONE) {
+		if (verbose()) {
 			if (setenv("SELFTESTS_VERBOSE", "1", 1) == -1) {
 				fprintf(stderr,
 					"Unable to setenv SELFTESTS_VERBOSE=1 (errno=%d)",
@@ -696,44 +873,6 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
 	return 0;
 }
 
-static void stdio_hijack(char **log_buf, size_t *log_cnt)
-{
-#ifdef __GLIBC__
-	env.stdout = stdout;
-	env.stderr = stderr;
-
-	if (env.verbosity > VERBOSE_NONE && env.worker_id == -1) {
-		/* nothing to do, output to stdout by default */
-		return;
-	}
-
-	/* stdout and stderr -> buffer */
-	fflush(stdout);
-
-	stdout = open_memstream(log_buf, log_cnt);
-	if (!stdout) {
-		stdout = env.stdout;
-		perror("open_memstream");
-		return;
-	}
-
-	stderr = stdout;
-#endif
-}
-
-static void stdio_restore(void)
-{
-#ifdef __GLIBC__
-	if (stdout == env.stdout)
-		return;
-
-	fclose(stdout);
-
-	stdout = env.stdout;
-	stderr = env.stderr;
-#endif
-}
-
 /*
  * Determine if test_progs is running as a "flavored" test runner and switch
  * into corresponding sub-directory to load correct BPF objects.
@@ -759,7 +898,7 @@ int cd_flavor_subdir(const char *exec_name)
 	if (!flavor)
 		return 0;
 	flavor++;
-	if (env.verbosity > VERBOSE_NONE)
+	if (verbose())
 		fprintf(stdout,	"Switching to flavor '%s' subdirectory...\n", flavor);
 
 	return chdir(flavor);
@@ -812,8 +951,10 @@ void crash_handler(int signum)
 
 	sz = backtrace(bt, ARRAY_SIZE(bt));
 
-	if (env.test)
-		dump_test_log(env.test, env.test_state, true);
+	if (env.test) {
+		env.test_state->error_cnt++;
+		dump_test_log(env.test, env.test_state, true, false);
+	}
 	if (env.stdout)
 		stdio_restore();
 	if (env.worker_id != -1)
@@ -839,13 +980,18 @@ static inline const char *str_msg(const struct msg *msg, char *buf)
 {
 	switch (msg->type) {
 	case MSG_DO_TEST:
-		sprintf(buf, "MSG_DO_TEST %d", msg->do_test.test_num);
+		sprintf(buf, "MSG_DO_TEST %d", msg->do_test.num);
 		break;
 	case MSG_TEST_DONE:
 		sprintf(buf, "MSG_TEST_DONE %d (log: %d)",
-			msg->test_done.test_num,
+			msg->test_done.num,
 			msg->test_done.have_log);
 		break;
+	case MSG_SUBTEST_DONE:
+		sprintf(buf, "MSG_SUBTEST_DONE %d (log: %d)",
+			msg->subtest_done.num,
+			msg->subtest_done.have_log);
+		break;
 	case MSG_TEST_LOG:
 		sprintf(buf, "MSG_TEST_LOG (cnt: %ld, last: %d)",
 			strlen(msg->test_log.log_buf),
@@ -895,18 +1041,23 @@ static void run_one_test(int test_num)
 
 	stdio_hijack(&state->log_buf, &state->log_cnt);
 
+	if (verbose() && env.worker_id == -1)
+		print_test_name(test_num + 1, test->test_name, NULL);
+
 	if (test->run_test)
 		test->run_test();
 	else if (test->run_serial_test)
 		test->run_serial_test();
 
 	/* ensure last sub-test is finalized properly */
-	if (state->subtest_name)
+	if (env.subtest_state)
 		test__end_subtest();
 
 	state->tested = true;
 
-	dump_test_log(test, state, false);
+	if (verbose() && env.worker_id == -1)
+		print_test_name(test_num + 1, test->test_name,
+				test_result(state->error_cnt, state->skip_cnt));
 
 	reset_affinity();
 	restore_netns();
@@ -914,6 +1065,8 @@ static void run_one_test(int test_num)
 		cleanup_cgroup_environment();
 
 	stdio_restore();
+
+	dump_test_log(test, state, false, false);
 }
 
 struct dispatch_data {
@@ -921,6 +1074,73 @@ struct dispatch_data {
 	int sock_fd;
 };
 
+static int read_prog_test_msg(int sock_fd, struct msg *msg, enum msg_type type)
+{
+	if (recv_message(sock_fd, msg) < 0)
+		return 1;
+
+	if (msg->type != type) {
+		printf("%s: unexpected message type %d. expected %d\n", __func__, msg->type, type);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int dispatch_thread_read_log(int sock_fd, char **log_buf, size_t *log_cnt)
+{
+	FILE *log_fp = NULL;
+
+	log_fp = open_memstream(log_buf, log_cnt);
+	if (!log_fp)
+		return 1;
+
+	while (true) {
+		struct msg msg;
+
+		if (read_prog_test_msg(sock_fd, &msg, MSG_TEST_LOG))
+			return 1;
+
+		fprintf(log_fp, "%s", msg.test_log.log_buf);
+		if (msg.test_log.is_last)
+			break;
+	}
+	fclose(log_fp);
+	log_fp = NULL;
+	return 0;
+}
+
+static int dispatch_thread_send_subtests(int sock_fd, struct test_state *state)
+{
+	struct msg msg;
+	struct subtest_state *subtest_state;
+	int subtest_num = state->subtest_num;
+
+	state->subtest_states = malloc(subtest_num * sizeof(*subtest_state));
+
+	for (int i = 0; i < subtest_num; i++) {
+		subtest_state = &state->subtest_states[i];
+
+		memset(subtest_state, 0, sizeof(*subtest_state));
+
+		if (read_prog_test_msg(sock_fd, &msg, MSG_SUBTEST_DONE))
+			return 1;
+
+		subtest_state->name = strdup(msg.subtest_done.name);
+		subtest_state->error_cnt = msg.subtest_done.error_cnt;
+		subtest_state->skipped = msg.subtest_done.skipped;
+
+		/* collect all logs */
+		if (msg.subtest_done.have_log)
+			if (dispatch_thread_read_log(sock_fd,
+						     &subtest_state->log_buf,
+						     &subtest_state->log_cnt))
+				return 1;
+	}
+
+	return 0;
+}
+
 static void *dispatch_thread(void *ctx)
 {
 	struct dispatch_data *data = ctx;
@@ -957,8 +1177,9 @@ static void *dispatch_thread(void *ctx)
 		{
 			struct msg msg_do_test;
 
+			memset(&msg_do_test, 0, sizeof(msg_do_test));
 			msg_do_test.type = MSG_DO_TEST;
-			msg_do_test.do_test.test_num = test_to_run;
+			msg_do_test.do_test.num = test_to_run;
 			if (send_message(sock_fd, &msg_do_test) < 0) {
 				perror("Fail to send command");
 				goto done;
@@ -967,49 +1188,39 @@ static void *dispatch_thread(void *ctx)
 		}
 
 		/* wait for test done */
-		{
-			int err;
-			struct msg msg_test_done;
+		do {
+			struct msg msg;
 
-			err = recv_message(sock_fd, &msg_test_done);
-			if (err < 0)
+			if (read_prog_test_msg(sock_fd, &msg, MSG_TEST_DONE))
 				goto error;
-			if (msg_test_done.type != MSG_TEST_DONE)
-				goto error;
-			if (test_to_run != msg_test_done.test_done.test_num)
+			if (test_to_run != msg.test_done.num)
 				goto error;
 
 			state = &test_states[test_to_run];
 			state->tested = true;
-			state->error_cnt = msg_test_done.test_done.error_cnt;
-			state->skip_cnt = msg_test_done.test_done.skip_cnt;
-			state->sub_succ_cnt = msg_test_done.test_done.sub_succ_cnt;
+			state->error_cnt = msg.test_done.error_cnt;
+			state->skip_cnt = msg.test_done.skip_cnt;
+			state->sub_succ_cnt = msg.test_done.sub_succ_cnt;
+			state->subtest_num = msg.test_done.subtest_num;
 
 			/* collect all logs */
-			if (msg_test_done.test_done.have_log) {
-				log_fp = open_memstream(&state->log_buf, &state->log_cnt);
-				if (!log_fp)
+			if (msg.test_done.have_log) {
+				if (dispatch_thread_read_log(sock_fd,
+							     &state->log_buf,
+							     &state->log_cnt))
 					goto error;
+			}
 
-				while (true) {
-					struct msg msg_log;
-
-					if (recv_message(sock_fd, &msg_log) < 0)
-						goto error;
-					if (msg_log.type != MSG_TEST_LOG)
-						goto error;
+			/* collect all subtests and subtest logs */
+			if (!state->subtest_num)
+				break;
 
-					fprintf(log_fp, "%s", msg_log.test_log.log_buf);
-					if (msg_log.test_log.is_last)
-						break;
-				}
-				fclose(log_fp);
-				log_fp = NULL;
-			}
-		} /* wait for test done */
+			if (dispatch_thread_send_subtests(sock_fd, state))
+				goto error;
+		} while (false);
 
 		pthread_mutex_lock(&stdout_output_lock);
-		dump_test_log(test, state, false);
+		dump_test_log(test, state, false, true);
 		pthread_mutex_unlock(&stdout_output_lock);
 	} /* while (true) */
 error:
@@ -1052,18 +1263,24 @@ static void calculate_summary_and_print_errors(struct test_env *env)
 			succ_cnt++;
 	}
 
-	if (fail_cnt)
+	/*
+	 * We only print error logs summary when there are failed tests and
+	 * verbose mode is not enabled. Otherwise, results may be incosistent.
+	 *
+	 */
+	if (!verbose() && fail_cnt) {
 		printf("\nAll error logs:\n");
 
-	/* print error logs again */
-	for (i = 0; i < prog_test_cnt; i++) {
-		struct prog_test_def *test = &prog_test_defs[i];
-		struct test_state *state = &test_states[i];
+		/* print error logs again */
+		for (i = 0; i < prog_test_cnt; i++) {
+			struct prog_test_def *test = &prog_test_defs[i];
+			struct test_state *state = &test_states[i];
 
-		if (!state->tested || !state->error_cnt)
-			continue;
+			if (!state->tested || !state->error_cnt)
+				continue;
 
-		dump_test_log(test, state, true);
+			dump_test_log(test, state, true, true);
+		}
 	}
 
 	printf("Summary: %d/%d PASSED, %d SKIPPED, %d FAILED\n",
@@ -1154,6 +1371,90 @@ static void server_main(void)
 	}
 }
 
+static void worker_main_send_log(int sock, char *log_buf, size_t log_cnt)
+{
+	char *src;
+	size_t slen;
+
+	src = log_buf;
+	slen = log_cnt;
+	while (slen) {
+		struct msg msg_log;
+		char *dest;
+		size_t len;
+
+		memset(&msg_log, 0, sizeof(msg_log));
+		msg_log.type = MSG_TEST_LOG;
+		dest = msg_log.test_log.log_buf;
+		len = slen >= MAX_LOG_TRUNK_SIZE ? MAX_LOG_TRUNK_SIZE : slen;
+		memcpy(dest, src, len);
+
+		src += len;
+		slen -= len;
+		if (!slen)
+			msg_log.test_log.is_last = true;
+
+		assert(send_message(sock, &msg_log) >= 0);
+	}
+}
+
+static void free_subtest_state(struct subtest_state *state)
+{
+	if (state->log_buf) {
+		free(state->log_buf);
+		state->log_buf = NULL;
+		state->log_cnt = 0;
+	}
+	free(state->name);
+	state->name = NULL;
+}
+
+static int worker_main_send_subtests(int sock, struct test_state *state)
+{
+	int i, result = 0;
+	struct msg msg;
+	struct subtest_state *subtest_state;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.type = MSG_SUBTEST_DONE;
+
+	for (i = 0; i < state->subtest_num; i++) {
+		subtest_state = &state->subtest_states[i];
+
+		msg.subtest_done.num = i;
+
+		strncpy(msg.subtest_done.name, subtest_state->name, MAX_SUBTEST_NAME);
+
+		msg.subtest_done.error_cnt = subtest_state->error_cnt;
+		msg.subtest_done.skipped = subtest_state->skipped;
+		msg.subtest_done.have_log = false;
+
+		if (verbose() || state->force_log || subtest_state->error_cnt) {
+			if (subtest_state->log_cnt)
+				msg.subtest_done.have_log = true;
+		}
+
+		if (send_message(sock, &msg) < 0) {
+			perror("Fail to send message done");
+			result = 1;
+			goto out;
+		}
+
+		/* send logs */
+		if (msg.subtest_done.have_log)
+			worker_main_send_log(sock, subtest_state->log_buf, subtest_state->log_cnt);
+
+		free_subtest_state(subtest_state);
+		free(subtest_state->name);
+	}
+
+out:
+	for (; i < state->subtest_num; i++)
+		free_subtest_state(&state->subtest_states[i]);
+	free(state->subtest_states);
+	return result;
+}
+
 static int worker_main(int sock)
 {
 	save_netns();
@@ -1172,10 +1473,10 @@ static int worker_main(int sock)
 					env.worker_id);
 			goto out;
 		case MSG_DO_TEST: {
-			int test_to_run = msg.do_test.test_num;
+			int test_to_run = msg.do_test.num;
 			struct prog_test_def *test = &prog_test_defs[test_to_run];
 			struct test_state *state = &test_states[test_to_run];
-			struct msg msg_done;
+			struct msg msg;
 
 			if (env.debug)
 				fprintf(stderr, "[%d]: #%d:%s running.\n",
@@ -1185,54 +1486,38 @@ static int worker_main(int sock)
 
 			run_one_test(test_to_run);
 
-			memset(&msg_done, 0, sizeof(msg_done));
-			msg_done.type = MSG_TEST_DONE;
-			msg_done.test_done.test_num = test_to_run;
-			msg_done.test_done.error_cnt = state->error_cnt;
-			msg_done.test_done.skip_cnt = state->skip_cnt;
-			msg_done.test_done.sub_succ_cnt = state->sub_succ_cnt;
-			msg_done.test_done.have_log = false;
+			memset(&msg, 0, sizeof(msg));
+			msg.type = MSG_TEST_DONE;
+			msg.test_done.num = test_to_run;
+			msg.test_done.error_cnt = state->error_cnt;
+			msg.test_done.skip_cnt = state->skip_cnt;
+			msg.test_done.sub_succ_cnt = state->sub_succ_cnt;
+			msg.test_done.subtest_num = state->subtest_num;
+			msg.test_done.have_log = false;
 
-			if (env.verbosity > VERBOSE_NONE || state->force_log || state->error_cnt) {
+			if (verbose() || state->force_log || state->error_cnt) {
 				if (state->log_cnt)
-					msg_done.test_done.have_log = true;
+					msg.test_done.have_log = true;
 			}
-			if (send_message(sock, &msg_done) < 0) {
+			if (send_message(sock, &msg) < 0) {
 				perror("Fail to send message done");
 				goto out;
 			}
 
 			/* send logs */
-			if (msg_done.test_done.have_log) {
-				char *src;
-				size_t slen;
-
-				src = state->log_buf;
-				slen = state->log_cnt;
-				while (slen) {
-					struct msg msg_log;
-					char *dest;
-					size_t len;
-
-					memset(&msg_log, 0, sizeof(msg_log));
-					msg_log.type = MSG_TEST_LOG;
-					dest = msg_log.test_log.log_buf;
-					len = slen >= MAX_LOG_TRUNK_SIZE ? MAX_LOG_TRUNK_SIZE : slen;
-					memcpy(dest, src, len);
-
-					src += len;
-					slen -= len;
-					if (!slen)
-						msg_log.test_log.is_last = true;
-
-					assert(send_message(sock, &msg_log) >= 0);
-				}
-			}
+			if (msg.test_done.have_log)
+				worker_main_send_log(sock, state->log_buf, state->log_cnt);
+
 			if (state->log_buf) {
 				free(state->log_buf);
 				state->log_buf = NULL;
 				state->log_cnt = 0;
 			}
+
+			if (state->subtest_num)
+				if (worker_main_send_subtests(sock, state))
+					goto out;
+
 			if (env.debug)
 				fprintf(stderr, "[%d]: #%d:%s done.\n",
 					env.worker_id,
@@ -1250,6 +1535,23 @@ static int worker_main(int sock)
 	return 0;
 }
 
+static void free_test_states(void)
+{
+	int i, j;
+
+	for (i = 0; i < ARRAY_SIZE(prog_test_defs); i++) {
+		struct test_state *test_state = &test_states[i];
+
+		for (j = 0; j < test_state->subtest_num; j++)
+			free_subtest_state(&test_state->subtest_states[j]);
+
+		free(test_state->subtest_states);
+		free(test_state->log_buf);
+		test_state->subtest_states = NULL;
+		test_state->log_buf = NULL;
+	}
+}
+
 int main(int argc, char **argv)
 {
 	static const struct argp argp = {
@@ -1396,6 +1698,7 @@ int main(int argc, char **argv)
 		unload_bpf_testmod();
 
 	free_test_selector(&env.test_selector);
+	free_test_states();
 
 	if (env.succ_cnt + env.fail_cnt + env.skip_cnt == 0)
 		return EXIT_NO_TEST;
diff --git a/tools/testing/selftests/bpf/test_progs.h b/tools/testing/selftests/bpf/test_progs.h
index 0a102ce460d6..18262077fdeb 100644
--- a/tools/testing/selftests/bpf/test_progs.h
+++ b/tools/testing/selftests/bpf/test_progs.h
@@ -64,23 +64,31 @@ struct test_selector {
 	int num_set_len;
 };
 
+struct subtest_state {
+	char *name;
+	size_t log_cnt;
+	char *log_buf;
+	int error_cnt;
+	bool skipped;
+
+	FILE *stdout;
+};
+
 struct test_state {
 	bool tested;
 	bool force_log;
 
 	int error_cnt;
 	int skip_cnt;
-	int subtest_skip_cnt;
 	int sub_succ_cnt;
 
-	char *subtest_name;
+	struct subtest_state *subtest_states;
 	int subtest_num;
 
-	/* store counts before subtest started */
-	int old_error_cnt;
-
 	size_t log_cnt;
 	char *log_buf;
+
+	FILE *stdout;
 };
 
 struct test_env {
@@ -96,7 +104,8 @@ struct test_env {
 	bool list_test_names;
 
 	struct prog_test_def *test; /* current running test */
-	struct test_state *test_state; /* current running test result */
+	struct test_state *test_state; /* current running test state */
+	struct subtest_state *subtest_state; /* current running subtest state */
 
 	FILE *stdout;
 	FILE *stderr;
@@ -116,29 +125,39 @@ struct test_env {
 };
 
 #define MAX_LOG_TRUNK_SIZE 8192
+#define MAX_SUBTEST_NAME 1024
 enum msg_type {
 	MSG_DO_TEST = 0,
 	MSG_TEST_DONE = 1,
 	MSG_TEST_LOG = 2,
+	MSG_SUBTEST_DONE = 3,
 	MSG_EXIT = 255,
 };
 struct msg {
 	enum msg_type type;
 	union {
 		struct {
-			int test_num;
+			int num;
 		} do_test;
 		struct {
-			int test_num;
+			int num;
 			int sub_succ_cnt;
 			int error_cnt;
 			int skip_cnt;
 			bool have_log;
+			int subtest_num;
 		} test_done;
 		struct {
 			char log_buf[MAX_LOG_TRUNK_SIZE + 1];
 			bool is_last;
 		} test_log;
+		struct {
+			int num;
+			char name[MAX_SUBTEST_NAME + 1];
+			int error_cnt;
+			bool skipped;
+			bool have_log;
+		} subtest_done;
 	};
 };
 
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH bpf-next] bpf/selftests: add granular subtest output for prog_test
  2022-04-25  4:56 [PATCH bpf-next] bpf/selftests: add granular subtest output for prog_test Mykola Lysenko
@ 2022-04-25  5:56 ` Mykola Lysenko
  0 siblings, 0 replies; 2+ messages in thread
From: Mykola Lysenko @ 2022-04-25  5:56 UTC (permalink / raw)
  To: bpf, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann
  Cc: Mykola Lysenko, Kernel Team

Skip for now. Going to debug what is going on with CI running this patch with noalu32 on z15

> On Apr 24, 2022, at 9:56 PM, Mykola Lysenko <mykolal@fb.com> wrote:
> 
> Implement per subtest log collection for both parallel
> and sequential test execution. This allows granular
> per-subtest error output in the 'All error logs' section.
> Add subtest log transfer into the protocol during the
> parallel test execution.
> 
> Move all test log printing logic into dump_test_log
> function. One exception is the output of test names when
> verbose printing is enabled. Move test name/result
> printing into separate functions to avoid repetition.
> 
> Print all successful subtest results in the log. Print
> only failed test logs when test does not have subtests.
> Or only failed subtests' logs when test has subtests.
> 
> Disable 'All error logs' output when verbose mode is
> enabled. This functionality was already broken and is
> causing confusion.
> 
> Signed-off-by: Mykola Lysenko <mykolal@fb.com>
> ---
> tools/testing/selftests/bpf/test_progs.c | 639 +++++++++++++++++------
> tools/testing/selftests/bpf/test_progs.h |  35 +-
> 2 files changed, 498 insertions(+), 176 deletions(-)
> 
> diff --git a/tools/testing/selftests/bpf/test_progs.c b/tools/testing/selftests/bpf/test_progs.c
> index c536d1d29d57..375e10576336 100644
> --- a/tools/testing/selftests/bpf/test_progs.c
> +++ b/tools/testing/selftests/bpf/test_progs.c
> @@ -18,6 +18,90 @@
> #include <sys/socket.h>
> #include <sys/un.h>
> 
> +static bool verbose(void)
> +{
> +	return env.verbosity > VERBOSE_NONE;
> +}
> +
> +static void stdio_hijack_init(char **log_buf, size_t *log_cnt)
> +{
> +#ifdef __GLIBC__
> +	if (verbose() && env.worker_id == -1) {
> +		/* nothing to do, output to stdout by default */
> +		return;
> +	}
> +
> +	fflush(stdout);
> +	fflush(stderr);
> +
> +	stdout = open_memstream(log_buf, log_cnt);
> +	if (!stdout) {
> +		stdout = env.stdout;
> +		perror("open_memstream");
> +		return;
> +	}
> +
> +	if (env.subtest_state)
> +		env.subtest_state->stdout = stdout;
> +	else
> +		env.test_state->stdout = stdout;
> +
> +	stderr = stdout;
> +#endif
> +}
> +
> +static void stdio_hijack(char **log_buf, size_t *log_cnt)
> +{
> +#ifdef __GLIBC__
> +	if (verbose() && env.worker_id == -1) {
> +		/* nothing to do, output to stdout by default */
> +		return;
> +	}
> +
> +	env.stdout = stdout;
> +	env.stderr = stderr;
> +
> +	stdio_hijack_init(log_buf, log_cnt);
> +#endif
> +}
> +
> +static void stdio_restore_cleanup(void)
> +{
> +#ifdef __GLIBC__
> +	if (verbose() && env.worker_id == -1) {
> +		/* nothing to do, output to stdout by default */
> +		return;
> +	}
> +
> +	fflush(stdout);
> +
> +	if (env.subtest_state) {
> +		fclose(env.subtest_state->stdout);
> +		stdout = env.test_state->stdout;
> +	} else {
> +		fclose(env.test_state->stdout);
> +	}
> +#endif
> +}
> +
> +static void stdio_restore(void)
> +{
> +#ifdef __GLIBC__
> +	if (verbose() && env.worker_id == -1) {
> +		/* nothing to do, output to stdout by default */
> +		return;
> +	}
> +
> +	if (stdout == env.stdout)
> +		return;
> +
> +	stdio_restore_cleanup();
> +
> +	stdout = env.stdout;
> +	stderr = env.stderr;
> +#endif
> +}
> +
> /* Adapted from perf/util/string.c */
> static bool glob_match(const char *str, const char *pat)
> {
> @@ -130,30 +214,96 @@ static bool should_run_subtest(struct test_selector *sel,
> 	return subtest_num < subtest_sel->num_set_len && subtest_sel->num_set[subtest_num];
> }
> 
> +static char *test_result(bool failed, bool skipped)
> +{
> +	return failed ? "FAIL" : (skipped ? "SKIP" : "OK");
> +}
> +
> +static void print_test_log(char *log_buf, size_t log_cnt)
> +{
> +	log_buf[log_cnt] = '\0';
> +	fprintf(env.stdout, "%s", log_buf);
> +	if (log_buf[log_cnt - 1] != '\n')
> +		fprintf(env.stdout, "\n");
> +}
> +
> +static void print_test_name(int test_num, const char *test_name, char *result)
> +{
> +	fprintf(env.stdout, "#%-9d %s", test_num, test_name);
> +
> +	if (result)
> +		fprintf(env.stdout, ":%s", result);
> +
> +	fprintf(env.stdout, "\n");
> +}
> +
> +static void print_subtest_name(int test_num, int subtest_num,
> +			       const char *test_name, char *subtest_name,
> +			       char *result)
> +{
> +	fprintf(env.stdout, "#%-3d/%-5d %s/%s",
> +		test_num, subtest_num,
> +		test_name, subtest_name);
> +
> +	if (result)
> +		fprintf(env.stdout, ":%s", result);
> +
> +	fprintf(env.stdout, "\n");
> +}
> +
> static void dump_test_log(const struct prog_test_def *test,
> 			  const struct test_state *test_state,
> -			  bool force_failed)
> +			  bool skip_ok_subtests,
> +			  bool par_exec_result)
> {
> -	bool failed = test_state->error_cnt > 0 || force_failed;
> +	bool test_failed = test_state->error_cnt > 0;
> +	bool force_log = test_state->force_log;
> +	bool print_test = verbose() || force_log || test_failed;
> +	int i;
> +	struct subtest_state *subtest_state;
> +	bool subtest_failed;
> +	bool print_subtest;
> 
> -	/* worker always holds log */
> +	/* we do not print anything in the worker thread */
> 	if (env.worker_id != -1)
> 		return;
> 
> -	fflush(stdout); /* exports test_state->log_buf & test_state->log_cnt */
> +	/* there is nothing to print when verbose log is used and execution
> +	 * is not in parallel mode
> +	 */
> +	if (verbose() && !par_exec_result)
> +		return;
> +
> +	if (test_state->subtest_num || print_test)
> +		print_test_name(test->test_num, test->test_name, NULL);
> +
> +	if (test_state->log_cnt && print_test)
> +		print_test_log(test_state->log_buf, test_state->log_cnt);
> 
> -	fprintf(env.stdout, "#%-3d %s:%s\n",
> -		test->test_num, test->test_name,
> -		failed ? "FAIL" : (test_state->skip_cnt ? "SKIP" : "OK"));
> +	for (i = 0; i < test_state->subtest_num; i++) {
> +		subtest_state = &test_state->subtest_states[i];
> +		subtest_failed = subtest_state->error_cnt;
> +		print_subtest = verbose() || force_log || subtest_failed;
> 
> -	if (env.verbosity > VERBOSE_NONE || test_state->force_log || failed) {
> -		if (test_state->log_cnt) {
> -			test_state->log_buf[test_state->log_cnt] = '\0';
> -			fprintf(env.stdout, "%s", test_state->log_buf);
> -			if (test_state->log_buf[test_state->log_cnt - 1] != '\n')
> -				fprintf(env.stdout, "\n");
> +		if (skip_ok_subtests && !subtest_failed)
> +			continue;
> +
> +		if (subtest_state->log_cnt && print_subtest) {
> +			print_subtest_name(test->test_num, i + 1,
> +					   test->test_name, subtest_state->name,
> +					   NULL);
> +			print_test_log(subtest_state->log_buf,
> +				       subtest_state->log_cnt);
> 		}
> +
> +		print_subtest_name(test->test_num, i + 1,
> +				   test->test_name, subtest_state->name,
> +				   test_result(subtest_state->error_cnt,
> +					       subtest_state->skipped));
> 	}
> +
> +	print_test_name(test->test_num, test->test_name,
> +			test_result(test_failed, test_state->skip_cnt));
> }
> 
> static void stdio_restore(void);
> @@ -205,35 +355,50 @@ static void restore_netns(void)
> void test__end_subtest(void)
> {
> 	struct prog_test_def *test = env.test;
> -	struct test_state *state = env.test_state;
> -	int sub_error_cnt = state->error_cnt - state->old_error_cnt;
> -
> -	fprintf(stdout, "#%d/%d %s/%s:%s\n",
> -	       test->test_num, state->subtest_num, test->test_name, state->subtest_name,
> -	       sub_error_cnt ? "FAIL" : (state->subtest_skip_cnt ? "SKIP" : "OK"));
> -
> -	if (sub_error_cnt == 0) {
> -		if (state->subtest_skip_cnt == 0) {
> -			state->sub_succ_cnt++;
> -		} else {
> -			state->subtest_skip_cnt = 0;
> -			state->skip_cnt++;
> -		}
> +	struct test_state *test_state = env.test_state;
> +	struct subtest_state *subtest_state = env.subtest_state;
> +
> +	if (subtest_state->error_cnt) {
> +		test_state->error_cnt++;
> +	} else {
> +		if (!subtest_state->skipped)
> +			test_state->sub_succ_cnt++;
> +		else
> +			test_state->skip_cnt++;
> 	}
> 
> -	free(state->subtest_name);
> -	state->subtest_name = NULL;
> +	if (verbose() && !env.workers)
> +		print_subtest_name(test->test_num, test_state->subtest_num,
> +				   test->test_name, subtest_state->name,
> +				   test_result(subtest_state->error_cnt,
> +					       subtest_state->skipped));
> +
> +	stdio_restore_cleanup();
> +	env.subtest_state = NULL;
> }
> 
> bool test__start_subtest(const char *subtest_name)
> {
> 	struct prog_test_def *test = env.test;
> 	struct test_state *state = env.test_state;
> +	struct subtest_state *subtest_state;
> +	size_t sub_state_size = sizeof(*subtest_state);
> 
> -	if (state->subtest_name)
> +	if (env.subtest_state)
> 		test__end_subtest();
> 
> 	state->subtest_num++;
> +	state->subtest_states =
> +		realloc(state->subtest_states,
> +			state->subtest_num * sub_state_size);
> +	if (!state->subtest_states) {
> +		fprintf(stderr, "Not enough memory to allocate subtest result\n");
> +		return false;
> +	}
> +
> +	subtest_state = &state->subtest_states[state->subtest_num - 1];
> +
> +	memset(subtest_state, 0, sub_state_size);
> 
> 	if (!subtest_name || !subtest_name[0]) {
> 		fprintf(env.stderr,
> @@ -242,21 +407,30 @@ bool test__start_subtest(const char *subtest_name)
> 		return false;
> 	}
> 
> +	subtest_state->name = strdup(subtest_name);
> +	if (!subtest_state->name) {
> +		fprintf(env.stderr,
> +			"Subtest #%d: failed to copy subtest name!\n",
> +			state->subtest_num);
> +		return false;
> +	}
> +
> 	if (!should_run_subtest(&env.test_selector,
> 				&env.subtest_selector,
> 				state->subtest_num,
> 				test->test_name,
> -				subtest_name))
> -		return false;
> -
> -	state->subtest_name = strdup(subtest_name);
> -	if (!state->subtest_name) {
> -		fprintf(env.stderr,
> -			"Subtest #%d: failed to copy subtest name!\n",
> -			state->subtest_num);
> +				subtest_name)) {
> +		subtest_state->skipped = true;
> 		return false;
> 	}
> -	state->old_error_cnt = state->error_cnt;
> +
> +	env.subtest_state = subtest_state;
> +	stdio_hijack_init(&subtest_state->log_buf, &subtest_state->log_cnt);
> +
> +	if (verbose() && !env.workers)
> +		print_subtest_name(test->test_num, state->subtest_num,
> +				   test->test_name, subtest_state->name,
> +				   NULL);
> 
> 	return true;
> }
> @@ -268,15 +442,18 @@ void test__force_log(void)
> 
> void test__skip(void)
> {
> -	if (env.test_state->subtest_name)
> -		env.test_state->subtest_skip_cnt++;
> +	if (env.subtest_state)
> +		env.subtest_state->skipped = true;
> 	else
> 		env.test_state->skip_cnt++;
> }
> 
> void test__fail(void)
> {
> -	env.test_state->error_cnt++;
> +	if (env.subtest_state)
> +		env.subtest_state->error_cnt++;
> +	else
> +		env.test_state->error_cnt++;
> }
> 
> int test__join_cgroup(const char *path)
> @@ -455,14 +632,14 @@ static void unload_bpf_testmod(void)
> 		fprintf(env.stderr, "Failed to trigger kernel-side RCU sync!\n");
> 	if (delete_module("bpf_testmod", 0)) {
> 		if (errno == ENOENT) {
> -			if (env.verbosity > VERBOSE_NONE)
> +			if (verbose())
> 				fprintf(stdout, "bpf_testmod.ko is already unloaded.\n");
> 			return;
> 		}
> 		fprintf(env.stderr, "Failed to unload bpf_testmod.ko from kernel: %d\n", -errno);
> 		return;
> 	}
> -	if (env.verbosity > VERBOSE_NONE)
> +	if (verbose())
> 		fprintf(stdout, "Successfully unloaded bpf_testmod.ko.\n");
> }
> 
> @@ -473,7 +650,7 @@ static int load_bpf_testmod(void)
> 	/* ensure previous instance of the module is unloaded */
> 	unload_bpf_testmod();
> 
> -	if (env.verbosity > VERBOSE_NONE)
> +	if (verbose())
> 		fprintf(stdout, "Loading bpf_testmod.ko...\n");
> 
> 	fd = open("bpf_testmod.ko", O_RDONLY);
> @@ -488,7 +665,7 @@ static int load_bpf_testmod(void)
> 	}
> 	close(fd);
> 
> -	if (env.verbosity > VERBOSE_NONE)
> +	if (verbose())
> 		fprintf(stdout, "Successfully loaded bpf_testmod.ko.\n");
> 	return 0;
> }
> @@ -655,7 +832,7 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
> 			}
> 		}
> 
> -		if (env->verbosity > VERBOSE_NONE) {
> +		if (verbose()) {
> 			if (setenv("SELFTESTS_VERBOSE", "1", 1) == -1) {
> 				fprintf(stderr,
> 					"Unable to setenv SELFTESTS_VERBOSE=1 (errno=%d)",
> @@ -696,44 +873,6 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
> 	return 0;
> }
> 
> -static void stdio_hijack(char **log_buf, size_t *log_cnt)
> -{
> -#ifdef __GLIBC__
> -	env.stdout = stdout;
> -	env.stderr = stderr;
> -
> -	if (env.verbosity > VERBOSE_NONE && env.worker_id == -1) {
> -		/* nothing to do, output to stdout by default */
> -		return;
> -	}
> -
> -	/* stdout and stderr -> buffer */
> -	fflush(stdout);
> -
> -	stdout = open_memstream(log_buf, log_cnt);
> -	if (!stdout) {
> -		stdout = env.stdout;
> -		perror("open_memstream");
> -		return;
> -	}
> -
> -	stderr = stdout;
> -#endif
> -}
> -
> -static void stdio_restore(void)
> -{
> -#ifdef __GLIBC__
> -	if (stdout == env.stdout)
> -		return;
> -
> -	fclose(stdout);
> -
> -	stdout = env.stdout;
> -	stderr = env.stderr;
> -#endif
> -}
> -
> /*
>  * Determine if test_progs is running as a "flavored" test runner and switch
>  * into corresponding sub-directory to load correct BPF objects.
> @@ -759,7 +898,7 @@ int cd_flavor_subdir(const char *exec_name)
> 	if (!flavor)
> 		return 0;
> 	flavor++;
> -	if (env.verbosity > VERBOSE_NONE)
> +	if (verbose())
> 		fprintf(stdout,	"Switching to flavor '%s' subdirectory...\n", flavor);
> 
> 	return chdir(flavor);
> @@ -812,8 +951,10 @@ void crash_handler(int signum)
> 
> 	sz = backtrace(bt, ARRAY_SIZE(bt));
> 
> -	if (env.test)
> -		dump_test_log(env.test, env.test_state, true);
> +	if (env.test) {
> +		env.test_state->error_cnt++;
> +		dump_test_log(env.test, env.test_state, true, false);
> +	}
> 	if (env.stdout)
> 		stdio_restore();
> 	if (env.worker_id != -1)
> @@ -839,13 +980,18 @@ static inline const char *str_msg(const struct msg *msg, char *buf)
> {
> 	switch (msg->type) {
> 	case MSG_DO_TEST:
> -		sprintf(buf, "MSG_DO_TEST %d", msg->do_test.test_num);
> +		sprintf(buf, "MSG_DO_TEST %d", msg->do_test.num);
> 		break;
> 	case MSG_TEST_DONE:
> 		sprintf(buf, "MSG_TEST_DONE %d (log: %d)",
> -			msg->test_done.test_num,
> +			msg->test_done.num,
> 			msg->test_done.have_log);
> 		break;
> +	case MSG_SUBTEST_DONE:
> +		sprintf(buf, "MSG_SUBTEST_DONE %d (log: %d)",
> +			msg->subtest_done.num,
> +			msg->subtest_done.have_log);
> +		break;
> 	case MSG_TEST_LOG:
> 		sprintf(buf, "MSG_TEST_LOG (cnt: %ld, last: %d)",
> 			strlen(msg->test_log.log_buf),
> @@ -895,18 +1041,23 @@ static void run_one_test(int test_num)
> 
> 	stdio_hijack(&state->log_buf, &state->log_cnt);
> 
> +	if (verbose() && env.worker_id == -1)
> +		print_test_name(test_num + 1, test->test_name, NULL);
> +
> 	if (test->run_test)
> 		test->run_test();
> 	else if (test->run_serial_test)
> 		test->run_serial_test();
> 
> 	/* ensure last sub-test is finalized properly */
> -	if (state->subtest_name)
> +	if (env.subtest_state)
> 		test__end_subtest();
> 
> 	state->tested = true;
> 
> -	dump_test_log(test, state, false);
> +	if (verbose() && env.worker_id == -1)
> +		print_test_name(test_num + 1, test->test_name,
> +				test_result(state->error_cnt, state->skip_cnt));
> 
> 	reset_affinity();
> 	restore_netns();
> @@ -914,6 +1065,8 @@ static void run_one_test(int test_num)
> 		cleanup_cgroup_environment();
> 
> 	stdio_restore();
> +
> +	dump_test_log(test, state, false, false);
> }
> 
> struct dispatch_data {
> @@ -921,6 +1074,73 @@ struct dispatch_data {
> 	int sock_fd;
> };
> 
> +static int read_prog_test_msg(int sock_fd, struct msg *msg, enum msg_type type)
> +{
> +	if (recv_message(sock_fd, msg) < 0)
> +		return 1;
> +
> +	if (msg->type != type) {
> +		printf("%s: unexpected message type %d. expected %d\n", __func__, msg->type, type);
> +		return 1;
> +	}
> +
> +	return 0;
> +}
> +
> +static int dispatch_thread_read_log(int sock_fd, char **log_buf, size_t *log_cnt)
> +{
> +	FILE *log_fp = NULL;
> +
> +	log_fp = open_memstream(log_buf, log_cnt);
> +	if (!log_fp)
> +		return 1;
> +
> +	while (true) {
> +		struct msg msg;
> +
> +		if (read_prog_test_msg(sock_fd, &msg, MSG_TEST_LOG))
> +			return 1;
> +
> +		fprintf(log_fp, "%s", msg.test_log.log_buf);
> +		if (msg.test_log.is_last)
> +			break;
> +	}
> +	fclose(log_fp);
> +	log_fp = NULL;
> +	return 0;
> +}
> +
> +static int dispatch_thread_send_subtests(int sock_fd, struct test_state *state)
> +{
> +	struct msg msg;
> +	struct subtest_state *subtest_state;
> +	int subtest_num = state->subtest_num;
> +
> +	state->subtest_states = malloc(subtest_num * sizeof(*subtest_state));
> +
> +	for (int i = 0; i < subtest_num; i++) {
> +		subtest_state = &state->subtest_states[i];
> +
> +		memset(subtest_state, 0, sizeof(*subtest_state));
> +
> +		if (read_prog_test_msg(sock_fd, &msg, MSG_SUBTEST_DONE))
> +			return 1;
> +
> +		subtest_state->name = strdup(msg.subtest_done.name);
> +		subtest_state->error_cnt = msg.subtest_done.error_cnt;
> +		subtest_state->skipped = msg.subtest_done.skipped;
> +
> +		/* collect all logs */
> +		if (msg.subtest_done.have_log)
> +			if (dispatch_thread_read_log(sock_fd,
> +						     &subtest_state->log_buf,
> +						     &subtest_state->log_cnt))
> +				return 1;
> +	}
> +
> +	return 0;
> +}
> +
> static void *dispatch_thread(void *ctx)
> {
> 	struct dispatch_data *data = ctx;
> @@ -957,8 +1177,9 @@ static void *dispatch_thread(void *ctx)
> 		{
> 			struct msg msg_do_test;
> 
> +			memset(&msg_do_test, 0, sizeof(msg_do_test));
> 			msg_do_test.type = MSG_DO_TEST;
> -			msg_do_test.do_test.test_num = test_to_run;
> +			msg_do_test.do_test.num = test_to_run;
> 			if (send_message(sock_fd, &msg_do_test) < 0) {
> 				perror("Fail to send command");
> 				goto done;
> @@ -967,49 +1188,39 @@ static void *dispatch_thread(void *ctx)
> 		}
> 
> 		/* wait for test done */
> -		{
> -			int err;
> -			struct msg msg_test_done;
> +		do {
> +			struct msg msg;
> 
> -			err = recv_message(sock_fd, &msg_test_done);
> -			if (err < 0)
> +			if (read_prog_test_msg(sock_fd, &msg, MSG_TEST_DONE))
> 				goto error;
> -			if (msg_test_done.type != MSG_TEST_DONE)
> -				goto error;
> -			if (test_to_run != msg_test_done.test_done.test_num)
> +			if (test_to_run != msg.test_done.num)
> 				goto error;
> 
> 			state = &test_states[test_to_run];
> 			state->tested = true;
> -			state->error_cnt = msg_test_done.test_done.error_cnt;
> -			state->skip_cnt = msg_test_done.test_done.skip_cnt;
> -			state->sub_succ_cnt = msg_test_done.test_done.sub_succ_cnt;
> +			state->error_cnt = msg.test_done.error_cnt;
> +			state->skip_cnt = msg.test_done.skip_cnt;
> +			state->sub_succ_cnt = msg.test_done.sub_succ_cnt;
> +			state->subtest_num = msg.test_done.subtest_num;
> 
> 			/* collect all logs */
> -			if (msg_test_done.test_done.have_log) {
> -				log_fp = open_memstream(&state->log_buf, &state->log_cnt);
> -				if (!log_fp)
> +			if (msg.test_done.have_log) {
> +				if (dispatch_thread_read_log(sock_fd,
> +							     &state->log_buf,
> +							     &state->log_cnt))
> 					goto error;
> +			}
> 
> -				while (true) {
> -					struct msg msg_log;
> -
> -					if (recv_message(sock_fd, &msg_log) < 0)
> -						goto error;
> -					if (msg_log.type != MSG_TEST_LOG)
> -						goto error;
> +			/* collect all subtests and subtest logs */
> +			if (!state->subtest_num)
> +				break;
> 
> -					fprintf(log_fp, "%s", msg_log.test_log.log_buf);
> -					if (msg_log.test_log.is_last)
> -						break;
> -				}
> -				fclose(log_fp);
> -				log_fp = NULL;
> -			}
> -		} /* wait for test done */
> +			if (dispatch_thread_send_subtests(sock_fd, state))
> +				goto error;
> +		} while (false);
> 
> 		pthread_mutex_lock(&stdout_output_lock);
> -		dump_test_log(test, state, false);
> +		dump_test_log(test, state, false, true);
> 		pthread_mutex_unlock(&stdout_output_lock);
> 	} /* while (true) */
> error:
> @@ -1052,18 +1263,24 @@ static void calculate_summary_and_print_errors(struct test_env *env)
> 			succ_cnt++;
> 	}
> 
> -	if (fail_cnt)
> +	/*
> +	 * We only print error logs summary when there are failed tests and
> +	 * verbose mode is not enabled. Otherwise, results may be incosistent.
> +	 *
> +	 */
> +	if (!verbose() && fail_cnt) {
> 		printf("\nAll error logs:\n");
> 
> -	/* print error logs again */
> -	for (i = 0; i < prog_test_cnt; i++) {
> -		struct prog_test_def *test = &prog_test_defs[i];
> -		struct test_state *state = &test_states[i];
> +		/* print error logs again */
> +		for (i = 0; i < prog_test_cnt; i++) {
> +			struct prog_test_def *test = &prog_test_defs[i];
> +			struct test_state *state = &test_states[i];
> 
> -		if (!state->tested || !state->error_cnt)
> -			continue;
> +			if (!state->tested || !state->error_cnt)
> +				continue;
> 
> -		dump_test_log(test, state, true);
> +			dump_test_log(test, state, true, true);
> +		}
> 	}
> 
> 	printf("Summary: %d/%d PASSED, %d SKIPPED, %d FAILED\n",
> @@ -1154,6 +1371,90 @@ static void server_main(void)
> 	}
> }
> 
> +static void worker_main_send_log(int sock, char *log_buf, size_t log_cnt)
> +{
> +	char *src;
> +	size_t slen;
> +
> +	src = log_buf;
> +	slen = log_cnt;
> +	while (slen) {
> +		struct msg msg_log;
> +		char *dest;
> +		size_t len;
> +
> +		memset(&msg_log, 0, sizeof(msg_log));
> +		msg_log.type = MSG_TEST_LOG;
> +		dest = msg_log.test_log.log_buf;
> +		len = slen >= MAX_LOG_TRUNK_SIZE ? MAX_LOG_TRUNK_SIZE : slen;
> +		memcpy(dest, src, len);
> +
> +		src += len;
> +		slen -= len;
> +		if (!slen)
> +			msg_log.test_log.is_last = true;
> +
> +		assert(send_message(sock, &msg_log) >= 0);
> +	}
> +}
> +
> +static void free_subtest_state(struct subtest_state *state)
> +{
> +	if (state->log_buf) {
> +		free(state->log_buf);
> +		state->log_buf = NULL;
> +		state->log_cnt = 0;
> +	}
> +	free(state->name);
> +	state->name = NULL;
> +}
> +
> +static int worker_main_send_subtests(int sock, struct test_state *state)
> +{
> +	int i, result = 0;
> +	struct msg msg;
> +	struct subtest_state *subtest_state;
> +
> +	memset(&msg, 0, sizeof(msg));
> +	msg.type = MSG_SUBTEST_DONE;
> +
> +	for (i = 0; i < state->subtest_num; i++) {
> +		subtest_state = &state->subtest_states[i];
> +
> +		msg.subtest_done.num = i;
> +
> +		strncpy(msg.subtest_done.name, subtest_state->name, MAX_SUBTEST_NAME);
> +
> +		msg.subtest_done.error_cnt = subtest_state->error_cnt;
> +		msg.subtest_done.skipped = subtest_state->skipped;
> +		msg.subtest_done.have_log = false;
> +
> +		if (verbose() || state->force_log || subtest_state->error_cnt) {
> +			if (subtest_state->log_cnt)
> +				msg.subtest_done.have_log = true;
> +		}
> +
> +		if (send_message(sock, &msg) < 0) {
> +			perror("Fail to send message done");
> +			result = 1;
> +			goto out;
> +		}
> +
> +		/* send logs */
> +		if (msg.subtest_done.have_log)
> +			worker_main_send_log(sock, subtest_state->log_buf, subtest_state->log_cnt);
> +
> +		free_subtest_state(subtest_state);
> +		free(subtest_state->name);
> +	}
> +
> +out:
> +	for (; i < state->subtest_num; i++)
> +		free_subtest_state(&state->subtest_states[i]);
> +	free(state->subtest_states);
> +	return result;
> +}
> +
> static int worker_main(int sock)
> {
> 	save_netns();
> @@ -1172,10 +1473,10 @@ static int worker_main(int sock)
> 					env.worker_id);
> 			goto out;
> 		case MSG_DO_TEST: {
> -			int test_to_run = msg.do_test.test_num;
> +			int test_to_run = msg.do_test.num;
> 			struct prog_test_def *test = &prog_test_defs[test_to_run];
> 			struct test_state *state = &test_states[test_to_run];
> -			struct msg msg_done;
> +			struct msg msg;
> 
> 			if (env.debug)
> 				fprintf(stderr, "[%d]: #%d:%s running.\n",
> @@ -1185,54 +1486,38 @@ static int worker_main(int sock)
> 
> 			run_one_test(test_to_run);
> 
> -			memset(&msg_done, 0, sizeof(msg_done));
> -			msg_done.type = MSG_TEST_DONE;
> -			msg_done.test_done.test_num = test_to_run;
> -			msg_done.test_done.error_cnt = state->error_cnt;
> -			msg_done.test_done.skip_cnt = state->skip_cnt;
> -			msg_done.test_done.sub_succ_cnt = state->sub_succ_cnt;
> -			msg_done.test_done.have_log = false;
> +			memset(&msg, 0, sizeof(msg));
> +			msg.type = MSG_TEST_DONE;
> +			msg.test_done.num = test_to_run;
> +			msg.test_done.error_cnt = state->error_cnt;
> +			msg.test_done.skip_cnt = state->skip_cnt;
> +			msg.test_done.sub_succ_cnt = state->sub_succ_cnt;
> +			msg.test_done.subtest_num = state->subtest_num;
> +			msg.test_done.have_log = false;
> 
> -			if (env.verbosity > VERBOSE_NONE || state->force_log || state->error_cnt) {
> +			if (verbose() || state->force_log || state->error_cnt) {
> 				if (state->log_cnt)
> -					msg_done.test_done.have_log = true;
> +					msg.test_done.have_log = true;
> 			}
> -			if (send_message(sock, &msg_done) < 0) {
> +			if (send_message(sock, &msg) < 0) {
> 				perror("Fail to send message done");
> 				goto out;
> 			}
> 
> 			/* send logs */
> -			if (msg_done.test_done.have_log) {
> -				char *src;
> -				size_t slen;
> -
> -				src = state->log_buf;
> -				slen = state->log_cnt;
> -				while (slen) {
> -					struct msg msg_log;
> -					char *dest;
> -					size_t len;
> -
> -					memset(&msg_log, 0, sizeof(msg_log));
> -					msg_log.type = MSG_TEST_LOG;
> -					dest = msg_log.test_log.log_buf;
> -					len = slen >= MAX_LOG_TRUNK_SIZE ? MAX_LOG_TRUNK_SIZE : slen;
> -					memcpy(dest, src, len);
> -
> -					src += len;
> -					slen -= len;
> -					if (!slen)
> -						msg_log.test_log.is_last = true;
> -
> -					assert(send_message(sock, &msg_log) >= 0);
> -				}
> -			}
> +			if (msg.test_done.have_log)
> +				worker_main_send_log(sock, state->log_buf, state->log_cnt);
> +
> 			if (state->log_buf) {
> 				free(state->log_buf);
> 				state->log_buf = NULL;
> 				state->log_cnt = 0;
> 			}
> +
> +			if (state->subtest_num)
> +				if (worker_main_send_subtests(sock, state))
> +					goto out;
> +
> 			if (env.debug)
> 				fprintf(stderr, "[%d]: #%d:%s done.\n",
> 					env.worker_id,
> @@ -1250,6 +1535,23 @@ static int worker_main(int sock)
> 	return 0;
> }
> 
> +static void free_test_states(void)
> +{
> +	int i, j;
> +
> +	for (i = 0; i < ARRAY_SIZE(prog_test_defs); i++) {
> +		struct test_state *test_state = &test_states[i];
> +
> +		for (j = 0; j < test_state->subtest_num; j++)
> +			free_subtest_state(&test_state->subtest_states[j]);
> +
> +		free(test_state->subtest_states);
> +		free(test_state->log_buf);
> +		test_state->subtest_states = NULL;
> +		test_state->log_buf = NULL;
> +	}
> +}
> +
> int main(int argc, char **argv)
> {
> 	static const struct argp argp = {
> @@ -1396,6 +1698,7 @@ int main(int argc, char **argv)
> 		unload_bpf_testmod();
> 
> 	free_test_selector(&env.test_selector);
> +	free_test_states();
> 
> 	if (env.succ_cnt + env.fail_cnt + env.skip_cnt == 0)
> 		return EXIT_NO_TEST;
> diff --git a/tools/testing/selftests/bpf/test_progs.h b/tools/testing/selftests/bpf/test_progs.h
> index 0a102ce460d6..18262077fdeb 100644
> --- a/tools/testing/selftests/bpf/test_progs.h
> +++ b/tools/testing/selftests/bpf/test_progs.h
> @@ -64,23 +64,31 @@ struct test_selector {
> 	int num_set_len;
> };
> 
> +struct subtest_state {
> +	char *name;
> +	size_t log_cnt;
> +	char *log_buf;
> +	int error_cnt;
> +	bool skipped;
> +
> +	FILE *stdout;
> +};
> +
> struct test_state {
> 	bool tested;
> 	bool force_log;
> 
> 	int error_cnt;
> 	int skip_cnt;
> -	int subtest_skip_cnt;
> 	int sub_succ_cnt;
> 
> -	char *subtest_name;
> +	struct subtest_state *subtest_states;
> 	int subtest_num;
> 
> -	/* store counts before subtest started */
> -	int old_error_cnt;
> -
> 	size_t log_cnt;
> 	char *log_buf;
> +
> +	FILE *stdout;
> };
> 
> struct test_env {
> @@ -96,7 +104,8 @@ struct test_env {
> 	bool list_test_names;
> 
> 	struct prog_test_def *test; /* current running test */
> -	struct test_state *test_state; /* current running test result */
> +	struct test_state *test_state; /* current running test state */
> +	struct subtest_state *subtest_state; /* current running subtest state */
> 
> 	FILE *stdout;
> 	FILE *stderr;
> @@ -116,29 +125,39 @@ struct test_env {
> };
> 
> #define MAX_LOG_TRUNK_SIZE 8192
> +#define MAX_SUBTEST_NAME 1024
> enum msg_type {
> 	MSG_DO_TEST = 0,
> 	MSG_TEST_DONE = 1,
> 	MSG_TEST_LOG = 2,
> +	MSG_SUBTEST_DONE = 3,
> 	MSG_EXIT = 255,
> };
> struct msg {
> 	enum msg_type type;
> 	union {
> 		struct {
> -			int test_num;
> +			int num;
> 		} do_test;
> 		struct {
> -			int test_num;
> +			int num;
> 			int sub_succ_cnt;
> 			int error_cnt;
> 			int skip_cnt;
> 			bool have_log;
> +			int subtest_num;
> 		} test_done;
> 		struct {
> 			char log_buf[MAX_LOG_TRUNK_SIZE + 1];
> 			bool is_last;
> 		} test_log;
> +		struct {
> +			int num;
> +			char name[MAX_SUBTEST_NAME + 1];
> +			int error_cnt;
> +			bool skipped;
> +			bool have_log;
> +		} subtest_done;
> 	};
> };
> 
> -- 
> 2.30.2
> 


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2022-04-25  5:56 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-04-25  4:56 [PATCH bpf-next] bpf/selftests: add granular subtest output for prog_test Mykola Lysenko
2022-04-25  5:56 ` Mykola Lysenko

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.