Commit e52a8b96 authored by Andrii Nakryiko's avatar Andrii Nakryiko

Merge branch 'selftests/bpf: Add parallelism to test_progs'

Yucong Sun says:

====================

This patch series adds "-j" parelell execution to test_progs, with "--debug" to
display server/worker communications. Also, some Tests that often fails in
parallel are marked as serial test, and it will run in sequence after parallel
execution is done.

This patch series also adds a error summary after all tests execution finished.

V6 -> V5:
  * adding error summary logic for non parallel mode too.
  * changed how serial tests are implemented, use main process instead of worker 0.
  * fixed a dozen broken test when running in parallel.

V5 -> V4:
  * change to SOCK_SEQPACKET for close notification.
  * move all debug output to "--debug" mode
  * output log as test finish, and all error logs again after summary line.
  * variable naming / style changes
  * adds serial_test_name() to replace serial test lists.
====================
Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
parents a1852ce0 d3f7b166
......@@ -11,6 +11,7 @@
#include <fcntl.h>
#include <unistd.h>
#include <ftw.h>
#include <unistd.h>
#include "cgroup_helpers.h"
......@@ -33,10 +34,9 @@
#define CGROUP_MOUNT_DFLT "/sys/fs/cgroup"
#define NETCLS_MOUNT_PATH CGROUP_MOUNT_DFLT "/net_cls"
#define CGROUP_WORK_DIR "/cgroup-test-work-dir"
#define format_cgroup_path(buf, path) \
snprintf(buf, sizeof(buf), "%s%s%s", CGROUP_MOUNT_PATH, \
CGROUP_WORK_DIR, path)
snprintf(buf, sizeof(buf), "%s%s%d%s", CGROUP_MOUNT_PATH, \
CGROUP_WORK_DIR, getpid(), path)
#define format_classid_path(buf) \
snprintf(buf, sizeof(buf), "%s%s", NETCLS_MOUNT_PATH, \
......
......@@ -26,4 +26,4 @@ int join_classid(void);
int setup_classid_environment(void);
void cleanup_classid_environment(void);
#endif /* __CGROUP_HELPERS_H */
#endif /* __CGROUP_HELPERS_H */
\ No newline at end of file
......@@ -225,6 +225,7 @@ void test_atomics(void)
test__skip();
goto cleanup;
}
skel->bss->pid = getpid();
if (test__start_subtest("add"))
test_add(skel);
......
......@@ -179,7 +179,7 @@ static void do_bpf_iter_setsockopt(struct bpf_iter_setsockopt *iter_skel,
free_fds(est_fds, nr_est);
}
void test_bpf_iter_setsockopt(void)
void serial_test_bpf_iter_setsockopt(void)
{
struct bpf_iter_setsockopt *iter_skel = NULL;
struct bpf_cubic *cubic_skel = NULL;
......
......@@ -3,7 +3,7 @@
#define nr_iters 2
void test_bpf_obj_id(void)
void serial_test_bpf_obj_id(void)
{
const __u64 array_magic_value = 0xfaceb00c;
const __u32 array_key = 0;
......
......@@ -363,7 +363,7 @@ static void test_shared(int parent_cgroup_fd, int child_cgroup_fd)
cg_storage_multi_shared__destroy(obj);
}
void test_cg_storage_multi(void)
void serial_test_cg_storage_multi(void)
{
int parent_cgroup_fd = -1, child_cgroup_fd = -1;
......
......@@ -21,7 +21,7 @@ static int prog_load(void)
bpf_log_buf, BPF_LOG_BUF_SIZE);
}
void test_cgroup_attach_autodetach(void)
void serial_test_cgroup_attach_autodetach(void)
{
__u32 duration = 0, prog_cnt = 4, attach_flags;
int allow_prog[2] = {-1};
......
......@@ -74,7 +74,7 @@ static int prog_load_cnt(int verdict, int val)
return ret;
}
void test_cgroup_attach_multi(void)
void serial_test_cgroup_attach_multi(void)
{
__u32 prog_ids[4], prog_cnt = 0, attach_flags, saved_prog_id;
int cg1 = 0, cg2 = 0, cg3 = 0, cg4 = 0, cg5 = 0, key = 0;
......
......@@ -23,7 +23,7 @@ static int prog_load(int verdict)
bpf_log_buf, BPF_LOG_BUF_SIZE);
}
void test_cgroup_attach_override(void)
void serial_test_cgroup_attach_override(void)
{
int drop_prog = -1, allow_prog = -1, foo = -1, bar = -1;
__u32 duration = 0;
......
......@@ -24,7 +24,7 @@ int ping_and_check(int exp_calls, int exp_alt_calls)
return 0;
}
void test_cgroup_link(void)
void serial_test_cgroup_link(void)
{
struct {
const char *path;
......
......@@ -46,7 +46,7 @@ void test_cgroup_v1v2(void)
{
struct network_helper_opts opts = {};
int server_fd, client_fd, cgroup_fd;
static const int port = 60123;
static const int port = 60120;
/* Step 1: Check base connectivity works without any BPF. */
server_fd = start_server(AF_INET, SOCK_STREAM, NULL, port, 0);
......
......@@ -195,7 +195,7 @@ static void test_check_mtu_tc(__u32 mtu, __u32 ifindex)
test_check_mtu__destroy(skel);
}
void test_check_mtu(void)
void serial_test_check_mtu(void)
{
__u32 mtu_lo;
......
......@@ -380,7 +380,8 @@ static void test_func_map_prog_compatibility(void)
"./test_attach_probe.o");
}
void test_fexit_bpf2bpf(void)
/* NOTE: affect other tests, must run in serial mode */
void serial_test_fexit_bpf2bpf(void)
{
if (test__start_subtest("target_no_callees"))
test_target_no_callees();
......
......@@ -2,7 +2,7 @@
#include <test_progs.h>
#include <network_helpers.h>
void test_flow_dissector_load_bytes(void)
void serial_test_flow_dissector_load_bytes(void)
{
struct bpf_flow_keys flow_keys;
__u32 duration = 0, retval, size;
......
......@@ -628,7 +628,7 @@ static void run_tests(int netns)
}
}
void test_flow_dissector_reattach(void)
void serial_test_flow_dissector_reattach(void)
{
int err, new_net, saved_net;
......
......@@ -73,7 +73,7 @@ static void close_perf_events(void)
free(pfd_array);
}
void test_get_branch_snapshot(void)
void serial_test_get_branch_snapshot(void)
{
struct get_branch_snapshot *skel = NULL;
int err;
......
......@@ -48,7 +48,8 @@ static void on_sample(void *ctx, int cpu, void *data, __u32 size)
*(bool *)ctx = true;
}
void test_kfree_skb(void)
/* TODO: fix kernel panic caused by this test in parallel mode */
void serial_test_kfree_skb(void)
{
struct __sk_buff skb = {};
struct bpf_prog_test_run_attr tattr = {
......
......@@ -541,7 +541,7 @@ static void run_test(struct migrate_reuseport_test_case *test_case,
}
}
void test_migrate_reuseport(void)
void serial_test_migrate_reuseport(void)
{
struct test_migrate_reuseport *skel;
int i;
......
......@@ -53,7 +53,8 @@ static void run_test(__u32 input_retval, __u16 want_side_effect, __s16 want_ret)
modify_return__destroy(skel);
}
void test_modify_return(void)
/* TODO: conflict with get_func_ip_test */
void serial_test_modify_return(void)
{
run_test(0 /* input_retval */,
1 /* want_side_effect */,
......
......@@ -78,7 +78,8 @@ static void test_ns_current_pid_tgid_new_ns(void)
return;
}
void test_ns_current_pid_tgid(void)
/* TODO: use a different tracepoint */
void serial_test_ns_current_pid_tgid(void)
{
if (test__start_subtest("ns_current_pid_tgid_root_ns"))
test_current_pid_tgid(NULL);
......
......@@ -43,7 +43,7 @@ int trigger_on_cpu(int cpu)
return 0;
}
void test_perf_buffer(void)
void serial_test_perf_buffer(void)
{
int err, on_len, nr_on_cpus = 0, nr_cpus, i;
struct perf_buffer_opts pb_opts = {};
......
......@@ -23,7 +23,8 @@ static void burn_cpu(void)
++j;
}
void test_perf_link(void)
/* TODO: often fails in concurrent mode */
void serial_test_perf_link(void)
{
struct test_perf_link *skel = NULL;
struct perf_event_attr attr;
......
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
void test_probe_user(void)
/* TODO: corrupts other tests uses connect() */
void serial_test_probe_user(void)
{
const char *prog_name = "handle_sys_connect";
const char *obj_file = "./test_probe_user.o";
......
......@@ -3,7 +3,8 @@
#include <test_progs.h>
#include <linux/nbd.h>
void test_raw_tp_writable_test_run(void)
/* NOTE: conflict with other tests. */
void serial_test_raw_tp_writable_test_run(void)
{
__u32 duration = 0;
char error[4096];
......
......@@ -858,7 +858,7 @@ void test_map_type(enum bpf_map_type mt)
cleanup();
}
void test_select_reuseport(void)
void serial_test_select_reuseport(void)
{
saved_tcp_fo = read_int_sysctl(TCP_FO_SYSCTL);
if (saved_tcp_fo < 0)
......
......@@ -25,7 +25,8 @@ static void *worker(void *p)
return NULL;
}
void test_send_signal_sched_switch(void)
/* NOTE: cause events loss */
void serial_test_send_signal_sched_switch(void)
{
struct test_send_signal_kern *skel;
pthread_t threads[THREAD_COUNT];
......
......@@ -105,7 +105,7 @@ static void do_test(void)
close(listen_fd);
}
void test_sk_storage_tracing(void)
void serial_test_sk_storage_tracing(void)
{
struct test_sk_storage_trace_itself *skel_itself;
int err;
......
......@@ -6,7 +6,7 @@
/* Demonstrate that bpf_snprintf_btf succeeds and that various data types
* are formatted correctly.
*/
void test_snprintf_btf(void)
void serial_test_snprintf_btf(void)
{
struct netif_receive_skb *skel;
struct netif_receive_skb__bss *bss;
......
......@@ -329,7 +329,7 @@ static void test(void)
close(listen_fd);
}
void test_sock_fields(void)
void serial_test_sock_fields(void)
{
struct bpf_link *egress_link = NULL, *ingress_link = NULL;
int parent_cg_fd = -1, child_cg_fd = -1;
......
......@@ -2037,7 +2037,7 @@ static void run_tests(struct test_sockmap_listen *skel, struct bpf_map *map,
test_udp_unix_redir(skel, map, family);
}
void test_sockmap_listen(void)
void serial_test_sockmap_listen(void)
{
struct test_sockmap_listen *skel;
......
......@@ -39,7 +39,8 @@ static int timer(struct timer *timer_skel)
return 0;
}
void test_timer(void)
/* TODO: use pid filtering */
void serial_test_timer(void)
{
struct timer *timer_skel = NULL;
int err;
......
......@@ -52,7 +52,7 @@ static int timer_mim(struct timer_mim *timer_skel)
return 0;
}
void test_timer_mim(void)
void serial_test_timer_mim(void)
{
struct timer_mim_reject *timer_reject_skel = NULL;
libbpf_print_fn_t old_print_fn = NULL;
......
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
void test_tp_attach_query(void)
void serial_test_tp_attach_query(void)
{
const int num_progs = 3;
int i, j, bytes, efd, err, prog_fd[num_progs], pmu_fd[num_progs];
......
......@@ -8,7 +8,7 @@
#define TRACEBUF "/sys/kernel/debug/tracing/trace_pipe"
#define SEARCHMSG "testing,testing"
void test_trace_printk(void)
void serial_test_trace_printk(void)
{
int err = 0, iter = 0, found = 0;
struct trace_printk__bss *bss;
......
......@@ -8,7 +8,7 @@
#define TRACEBUF "/sys/kernel/debug/tracing/trace_pipe"
#define SEARCHMSG "1,2,3,4,5,6,7,8,9,10"
void test_trace_vprintk(void)
void serial_test_trace_vprintk(void)
{
int err = 0, iter = 0, found = 0;
struct trace_vprintk__bss *bss;
......
......@@ -41,7 +41,8 @@ static struct bpf_link *load(struct bpf_object *obj, const char *name)
return bpf_program__attach_trace(prog);
}
void test_trampoline_count(void)
/* TODO: use different target function to run in concurrent mode */
void serial_test_trampoline_count(void)
{
const char *fentry_name = "fentry/__set_task_comm";
const char *fexit_name = "fexit/__set_task_comm";
......
......@@ -4,7 +4,7 @@
#define IFINDEX_LO 1
#define XDP_FLAGS_REPLACE (1U << 4)
void test_xdp_attach(void)
void serial_test_xdp_attach(void)
{
__u32 duration = 0, id1, id2, id0 = 0, len;
struct bpf_object *obj1, *obj2, *obj3;
......
......@@ -519,7 +519,7 @@ static struct bond_test_case bond_test_cases[] = {
{ "xdp_bonding_xor_layer34", BOND_MODE_XOR, BOND_XMIT_POLICY_LAYER34, },
};
void test_xdp_bonding(void)
void serial_test_xdp_bonding(void)
{
libbpf_print_fn_t old_print_fn;
struct skeletons skeletons = {};
......
......@@ -7,7 +7,7 @@
#define IFINDEX_LO 1
void test_xdp_cpumap_attach(void)
void serial_test_xdp_cpumap_attach(void)
{
struct test_xdp_with_cpumap_helpers *skel;
struct bpf_prog_info info = {};
......
......@@ -72,7 +72,7 @@ void test_neg_xdp_devmap_helpers(void)
}
void test_xdp_devmap_attach(void)
void serial_test_xdp_devmap_attach(void)
{
if (test__start_subtest("DEVMAP with programs in entries"))
test_xdp_with_devmap_helpers();
......
......@@ -4,7 +4,7 @@
#define IFINDEX_LO 1
void test_xdp_info(void)
void serial_test_xdp_info(void)
{
__u32 len = sizeof(struct bpf_prog_info), duration = 0, prog_id;
const char *file = "./xdp_dummy.o";
......
......@@ -6,7 +6,7 @@
#define IFINDEX_LO 1
void test_xdp_link(void)
void serial_test_xdp_link(void)
{
__u32 duration = 0, id1, id2, id0 = 0, prog_fd1, prog_fd2, err;
DECLARE_LIBBPF_OPTS(bpf_xdp_set_link_opts, opts, .old_fd = -1);
......
......@@ -10,6 +10,8 @@ bool skip_tests __attribute((__section__(".data"))) = false;
bool skip_tests = true;
#endif
__u32 pid = 0;
__u64 add64_value = 1;
__u64 add64_result = 0;
__u32 add32_value = 1;
......@@ -21,6 +23,8 @@ __u64 add_noreturn_value = 1;
SEC("fentry/bpf_fentry_test1")
int BPF_PROG(add, int a)
{
if (pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
#ifdef ENABLE_ATOMICS_TESTS
__u64 add_stack_value = 1;
......@@ -45,6 +49,8 @@ __s64 sub_noreturn_value = 1;
SEC("fentry/bpf_fentry_test1")
int BPF_PROG(sub, int a)
{
if (pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
#ifdef ENABLE_ATOMICS_TESTS
__u64 sub_stack_value = 1;
......@@ -67,6 +73,8 @@ __u64 and_noreturn_value = (0x110ull << 32);
SEC("fentry/bpf_fentry_test1")
int BPF_PROG(and, int a)
{
if (pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
#ifdef ENABLE_ATOMICS_TESTS
and64_result = __sync_fetch_and_and(&and64_value, 0x011ull << 32);
......@@ -86,6 +94,8 @@ __u64 or_noreturn_value = (0x110ull << 32);
SEC("fentry/bpf_fentry_test1")
int BPF_PROG(or, int a)
{
if (pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
#ifdef ENABLE_ATOMICS_TESTS
or64_result = __sync_fetch_and_or(&or64_value, 0x011ull << 32);
or32_result = __sync_fetch_and_or(&or32_value, 0x011);
......@@ -104,6 +114,8 @@ __u64 xor_noreturn_value = (0x110ull << 32);
SEC("fentry/bpf_fentry_test1")
int BPF_PROG(xor, int a)
{
if (pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
#ifdef ENABLE_ATOMICS_TESTS
xor64_result = __sync_fetch_and_xor(&xor64_value, 0x011ull << 32);
xor32_result = __sync_fetch_and_xor(&xor32_value, 0x011);
......@@ -123,6 +135,8 @@ __u32 cmpxchg32_result_succeed = 0;
SEC("fentry/bpf_fentry_test1")
int BPF_PROG(cmpxchg, int a)
{
if (pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
#ifdef ENABLE_ATOMICS_TESTS
cmpxchg64_result_fail = __sync_val_compare_and_swap(&cmpxchg64_value, 0, 3);
cmpxchg64_result_succeed = __sync_val_compare_and_swap(&cmpxchg64_value, 1, 2);
......@@ -142,6 +156,8 @@ __u32 xchg32_result = 0;
SEC("fentry/bpf_fentry_test1")
int BPF_PROG(xchg, int a)
{
if (pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
#ifdef ENABLE_ATOMICS_TESTS
__u64 val64 = 2;
__u32 val32 = 2;
......
......@@ -18,7 +18,7 @@ int connect_v4_dropper(struct bpf_sock_addr *ctx)
{
if (ctx->type != SOCK_STREAM)
return VERDICT_PROCEED;
if (ctx->user_port == bpf_htons(60123))
if (ctx->user_port == bpf_htons(60120))
return VERDICT_REJECT;
return VERDICT_PROCEED;
}
......
......@@ -13,7 +13,7 @@ int fexit_cnt = 0;
SEC("fentry/__x64_sys_nanosleep")
int BPF_PROG(nanosleep_fentry, const struct pt_regs *regs)
{
if ((int)bpf_get_current_pid_tgid() != pid)
if (bpf_get_current_pid_tgid() >> 32 != pid)
return 0;
fentry_cnt++;
......@@ -23,7 +23,7 @@ int BPF_PROG(nanosleep_fentry, const struct pt_regs *regs)
SEC("fexit/__x64_sys_nanosleep")
int BPF_PROG(nanosleep_fexit, const struct pt_regs *regs, int ret)
{
if ((int)bpf_get_current_pid_tgid() != pid)
if (bpf_get_current_pid_tgid() >> 32 != pid)
return 0;
fexit_cnt++;
......
......@@ -13,6 +13,6 @@ __u64 count = 0;
SEC("raw_tracepoint/sys_enter")
int test_enable_stats(void *ctx)
{
count += 1;
__sync_fetch_and_add(&count, 1);
return 0;
}
......@@ -12,6 +12,11 @@
#include <string.h>
#include <execinfo.h> /* backtrace */
#include <linux/membarrier.h>
#include <sys/sysinfo.h> /* get_nprocs */
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
/* Adapted from perf/util/string.c */
static bool glob_match(const char *str, const char *pat)
......@@ -45,9 +50,12 @@ struct prog_test_def {
const char *test_name;
int test_num;
void (*run_test)(void);
void (*run_serial_test)(void);
bool force_log;
int error_cnt;
int skip_cnt;
int sub_succ_cnt;
bool should_run;
bool tested;
bool need_cgroup_cleanup;
......@@ -97,6 +105,10 @@ static void dump_test_log(const struct prog_test_def *test, bool failed)
if (stdout == env.stdout)
return;
/* worker always holds log */
if (env.worker_id != -1)
return;
fflush(stdout); /* exports env.log_buf & env.log_cnt */
if (env.verbosity > VERBOSE_NONE || test->force_log || failed) {
......@@ -107,8 +119,6 @@ static void dump_test_log(const struct prog_test_def *test, bool failed)
fprintf(env.stdout, "\n");
}
}
fseeko(stdout, 0, SEEK_SET); /* rewind */
}
static void skip_account(void)
......@@ -124,7 +134,8 @@ static void stdio_restore(void);
/* A bunch of tests set custom affinity per-thread and/or per-process. Reset
* it after each test/sub-test.
*/
static void reset_affinity() {
static void reset_affinity(void)
{
cpu_set_t cpuset;
int i, err;
......@@ -165,21 +176,21 @@ static void restore_netns(void)
}
}
void test__end_subtest()
void test__end_subtest(void)
{
struct prog_test_def *test = env.test;
int sub_error_cnt = test->error_cnt - test->old_error_cnt;
dump_test_log(test, sub_error_cnt);
fprintf(env.stdout, "#%d/%d %s/%s:%s\n",
fprintf(stdout, "#%d/%d %s/%s:%s\n",
test->test_num, test->subtest_num, test->test_name, test->subtest_name,
sub_error_cnt ? "FAIL" : (test->skip_cnt ? "SKIP" : "OK"));
if (sub_error_cnt)
env.fail_cnt++;
test->error_cnt++;
else if (test->skip_cnt == 0)
env.sub_succ_cnt++;
test->sub_succ_cnt++;
skip_account();
free(test->subtest_name);
......@@ -217,7 +228,8 @@ bool test__start_subtest(const char *name)
return true;
}
void test__force_log() {
void test__force_log(void)
{
env.test->force_log = true;
}
......@@ -446,14 +458,17 @@ static int load_bpf_testmod(void)
}
/* extern declarations for test funcs */
#define DEFINE_TEST(name) extern void test_##name(void);
#define DEFINE_TEST(name) \
extern void test_##name(void) __weak; \
extern void serial_test_##name(void) __weak;
#include <prog_tests/tests.h>
#undef DEFINE_TEST
static struct prog_test_def prog_test_defs[] = {
#define DEFINE_TEST(name) { \
.test_name = #name, \
.run_test = &test_##name, \
#define DEFINE_TEST(name) { \
.test_name = #name, \
.run_test = &test_##name, \
.run_serial_test = &serial_test_##name, \
},
#include <prog_tests/tests.h>
#undef DEFINE_TEST
......@@ -474,6 +489,8 @@ enum ARG_KEYS {
ARG_LIST_TEST_NAMES = 'l',
ARG_TEST_NAME_GLOB_ALLOWLIST = 'a',
ARG_TEST_NAME_GLOB_DENYLIST = 'd',
ARG_NUM_WORKERS = 'j',
ARG_DEBUG = -1,
};
static const struct argp_option opts[] = {
......@@ -495,6 +512,10 @@ static const struct argp_option opts[] = {
"Run tests with name matching the pattern (supports '*' wildcard)." },
{ "deny", ARG_TEST_NAME_GLOB_DENYLIST, "NAMES", 0,
"Don't run tests with name matching the pattern (supports '*' wildcard)." },
{ "workers", ARG_NUM_WORKERS, "WORKERS", OPTION_ARG_OPTIONAL,
"Number of workers to run in parallel, default to number of cpus." },
{ "debug", ARG_DEBUG, NULL, 0,
"print extra debug information for test_progs." },
{},
};
......@@ -650,7 +671,7 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
fprintf(stderr,
"Unable to setenv SELFTESTS_VERBOSE=1 (errno=%d)",
errno);
return -1;
return -EINVAL;
}
}
......@@ -661,6 +682,20 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
case ARG_LIST_TEST_NAMES:
env->list_test_names = true;
break;
case ARG_NUM_WORKERS:
if (arg) {
env->workers = atoi(arg);
if (!env->workers) {
fprintf(stderr, "Invalid number of worker: %s.", arg);
return -EINVAL;
}
} else {
env->workers = get_nprocs();
}
break;
case ARG_DEBUG:
env->debug = true;
break;
case ARGP_KEY_ARG:
argp_usage(state);
break;
......@@ -678,7 +713,7 @@ static void stdio_hijack(void)
env.stdout = stdout;
env.stderr = stderr;
if (env.verbosity > VERBOSE_NONE) {
if (env.verbosity > VERBOSE_NONE && env.worker_id == -1) {
/* nothing to do, output to stdout by default */
return;
}
......@@ -704,10 +739,6 @@ static void stdio_restore(void)
return;
fclose(stdout);
free(env.log_buf);
env.log_buf = NULL;
env.log_cnt = 0;
stdout = env.stdout;
stderr = env.stderr;
......@@ -794,11 +825,498 @@ void crash_handler(int signum)
dump_test_log(env.test, true);
if (env.stdout)
stdio_restore();
if (env.worker_id != -1)
fprintf(stderr, "[%d]: ", env.worker_id);
fprintf(stderr, "Caught signal #%d!\nStack trace:\n", signum);
backtrace_symbols_fd(bt, sz, STDERR_FILENO);
}
static void sigint_handler(int signum)
{
int i;
for (i = 0; i < env.workers; i++)
if (env.worker_socks[i] > 0)
close(env.worker_socks[i]);
}
static int current_test_idx;
static pthread_mutex_t current_test_lock;
static pthread_mutex_t stdout_output_lock;
struct test_result {
int error_cnt;
int skip_cnt;
int sub_succ_cnt;
size_t log_cnt;
char *log_buf;
};
static struct test_result test_results[ARRAY_SIZE(prog_test_defs)];
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);
break;
case MSG_TEST_DONE:
sprintf(buf, "MSG_TEST_DONE %d (log: %d)",
msg->test_done.test_num,
msg->test_done.have_log);
break;
case MSG_TEST_LOG:
sprintf(buf, "MSG_TEST_LOG (cnt: %ld, last: %d)",
strlen(msg->test_log.log_buf),
msg->test_log.is_last);
break;
case MSG_EXIT:
sprintf(buf, "MSG_EXIT");
break;
default:
sprintf(buf, "UNKNOWN");
break;
}
return buf;
}
static int send_message(int sock, const struct msg *msg)
{
char buf[256];
if (env.debug)
fprintf(stderr, "Sending msg: %s\n", str_msg(msg, buf));
return send(sock, msg, sizeof(*msg), 0);
}
static int recv_message(int sock, struct msg *msg)
{
int ret;
char buf[256];
memset(msg, 0, sizeof(*msg));
ret = recv(sock, msg, sizeof(*msg), 0);
if (ret >= 0) {
if (env.debug)
fprintf(stderr, "Received msg: %s\n", str_msg(msg, buf));
}
return ret;
}
static void run_one_test(int test_num)
{
struct prog_test_def *test = &prog_test_defs[test_num];
env.test = test;
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 (test->subtest_name)
test__end_subtest();
test->tested = true;
dump_test_log(test, test->error_cnt);
reset_affinity();
restore_netns();
if (test->need_cgroup_cleanup)
cleanup_cgroup_environment();
}
struct dispatch_data {
int worker_id;
int sock_fd;
};
static void *dispatch_thread(void *ctx)
{
struct dispatch_data *data = ctx;
int sock_fd;
FILE *log_fd = NULL;
sock_fd = data->sock_fd;
while (true) {
int test_to_run = -1;
struct prog_test_def *test;
struct test_result *result;
/* grab a test */
{
pthread_mutex_lock(&current_test_lock);
if (current_test_idx >= prog_test_cnt) {
pthread_mutex_unlock(&current_test_lock);
goto done;
}
test = &prog_test_defs[current_test_idx];
test_to_run = current_test_idx;
current_test_idx++;
pthread_mutex_unlock(&current_test_lock);
}
if (!test->should_run || test->run_serial_test)
continue;
/* run test through worker */
{
struct msg msg_do_test;
msg_do_test.type = MSG_DO_TEST;
msg_do_test.do_test.test_num = test_to_run;
if (send_message(sock_fd, &msg_do_test) < 0) {
perror("Fail to send command");
goto done;
}
env.worker_current_test[data->worker_id] = test_to_run;
}
/* wait for test done */
{
int err;
struct msg msg_test_done;
err = recv_message(sock_fd, &msg_test_done);
if (err < 0)
goto error;
if (msg_test_done.type != MSG_TEST_DONE)
goto error;
if (test_to_run != msg_test_done.test_done.test_num)
goto error;
test->tested = true;
result = &test_results[test_to_run];
result->error_cnt = msg_test_done.test_done.error_cnt;
result->skip_cnt = msg_test_done.test_done.skip_cnt;
result->sub_succ_cnt = msg_test_done.test_done.sub_succ_cnt;
/* collect all logs */
if (msg_test_done.test_done.have_log) {
log_fd = open_memstream(&result->log_buf, &result->log_cnt);
if (!log_fd)
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;
fprintf(log_fd, "%s", msg_log.test_log.log_buf);
if (msg_log.test_log.is_last)
break;
}
fclose(log_fd);
log_fd = NULL;
}
/* output log */
{
pthread_mutex_lock(&stdout_output_lock);
if (result->log_cnt) {
result->log_buf[result->log_cnt] = '\0';
fprintf(stdout, "%s", result->log_buf);
if (result->log_buf[result->log_cnt - 1] != '\n')
fprintf(stdout, "\n");
}
fprintf(stdout, "#%d %s:%s\n",
test->test_num, test->test_name,
result->error_cnt ? "FAIL" : (result->skip_cnt ? "SKIP" : "OK"));
pthread_mutex_unlock(&stdout_output_lock);
}
} /* wait for test done */
} /* while (true) */
error:
if (env.debug)
fprintf(stderr, "[%d]: Protocol/IO error: %s.\n", data->worker_id, strerror(errno));
if (log_fd)
fclose(log_fd);
done:
{
struct msg msg_exit;
msg_exit.type = MSG_EXIT;
if (send_message(sock_fd, &msg_exit) < 0) {
if (env.debug)
fprintf(stderr, "[%d]: send_message msg_exit: %s.\n",
data->worker_id, strerror(errno));
}
}
return NULL;
}
static void print_all_error_logs(void)
{
int i;
if (env.fail_cnt)
fprintf(stdout, "\nAll error logs:\n");
/* print error logs again */
for (i = 0; i < prog_test_cnt; i++) {
struct prog_test_def *test;
struct test_result *result;
test = &prog_test_defs[i];
result = &test_results[i];
if (!test->tested || !result->error_cnt)
continue;
fprintf(stdout, "\n#%d %s:%s\n",
test->test_num, test->test_name,
result->error_cnt ? "FAIL" : (result->skip_cnt ? "SKIP" : "OK"));
if (result->log_cnt) {
result->log_buf[result->log_cnt] = '\0';
fprintf(stdout, "%s", result->log_buf);
if (result->log_buf[result->log_cnt - 1] != '\n')
fprintf(stdout, "\n");
}
}
}
static int server_main(void)
{
pthread_t *dispatcher_threads;
struct dispatch_data *data;
struct sigaction sigact_int = {
.sa_handler = sigint_handler,
.sa_flags = SA_RESETHAND,
};
int i;
sigaction(SIGINT, &sigact_int, NULL);
dispatcher_threads = calloc(sizeof(pthread_t), env.workers);
data = calloc(sizeof(struct dispatch_data), env.workers);
env.worker_current_test = calloc(sizeof(int), env.workers);
for (i = 0; i < env.workers; i++) {
int rc;
data[i].worker_id = i;
data[i].sock_fd = env.worker_socks[i];
rc = pthread_create(&dispatcher_threads[i], NULL, dispatch_thread, &data[i]);
if (rc < 0) {
perror("Failed to launch dispatcher thread");
exit(EXIT_ERR_SETUP_INFRA);
}
}
/* wait for all dispatcher to finish */
for (i = 0; i < env.workers; i++) {
while (true) {
int ret = pthread_tryjoin_np(dispatcher_threads[i], NULL);
if (!ret) {
break;
} else if (ret == EBUSY) {
if (env.debug)
fprintf(stderr, "Still waiting for thread %d (test %d).\n",
i, env.worker_current_test[i] + 1);
usleep(1000 * 1000);
continue;
} else {
fprintf(stderr, "Unexpected error joining dispatcher thread: %d", ret);
break;
}
}
}
free(dispatcher_threads);
free(env.worker_current_test);
free(data);
/* run serial tests */
save_netns();
for (int i = 0; i < prog_test_cnt; i++) {
struct prog_test_def *test = &prog_test_defs[i];
struct test_result *result = &test_results[i];
if (!test->should_run || !test->run_serial_test)
continue;
stdio_hijack();
run_one_test(i);
stdio_restore();
if (env.log_buf) {
result->log_cnt = env.log_cnt;
result->log_buf = strdup(env.log_buf);
free(env.log_buf);
env.log_buf = NULL;
env.log_cnt = 0;
}
restore_netns();
fprintf(stdout, "#%d %s:%s\n",
test->test_num, test->test_name,
test->error_cnt ? "FAIL" : (test->skip_cnt ? "SKIP" : "OK"));
result->error_cnt = test->error_cnt;
result->skip_cnt = test->skip_cnt;
result->sub_succ_cnt = test->sub_succ_cnt;
}
/* generate summary */
fflush(stderr);
fflush(stdout);
for (i = 0; i < prog_test_cnt; i++) {
struct prog_test_def *current_test;
struct test_result *result;
current_test = &prog_test_defs[i];
result = &test_results[i];
if (!current_test->tested)
continue;
env.succ_cnt += result->error_cnt ? 0 : 1;
env.skip_cnt += result->skip_cnt;
if (result->error_cnt)
env.fail_cnt++;
env.sub_succ_cnt += result->sub_succ_cnt;
}
fprintf(stdout, "Summary: %d/%d PASSED, %d SKIPPED, %d FAILED\n",
env.succ_cnt, env.sub_succ_cnt, env.skip_cnt, env.fail_cnt);
print_all_error_logs();
/* reap all workers */
for (i = 0; i < env.workers; i++) {
int wstatus, pid;
pid = waitpid(env.worker_pids[i], &wstatus, 0);
if (pid != env.worker_pids[i])
perror("Unable to reap worker");
}
return 0;
}
static int worker_main(int sock)
{
save_netns();
while (true) {
/* receive command */
struct msg msg;
if (recv_message(sock, &msg) < 0)
goto out;
switch (msg.type) {
case MSG_EXIT:
if (env.debug)
fprintf(stderr, "[%d]: worker exit.\n",
env.worker_id);
goto out;
case MSG_DO_TEST: {
int test_to_run;
struct prog_test_def *test;
struct msg msg_done;
test_to_run = msg.do_test.test_num;
test = &prog_test_defs[test_to_run];
if (env.debug)
fprintf(stderr, "[%d]: #%d:%s running.\n",
env.worker_id,
test_to_run + 1,
test->test_name);
stdio_hijack();
run_one_test(test_to_run);
stdio_restore();
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 = test->error_cnt;
msg_done.test_done.skip_cnt = test->skip_cnt;
msg_done.test_done.sub_succ_cnt = test->sub_succ_cnt;
msg_done.test_done.have_log = false;
if (env.verbosity > VERBOSE_NONE || test->force_log || test->error_cnt) {
if (env.log_cnt)
msg_done.test_done.have_log = true;
}
if (send_message(sock, &msg_done) < 0) {
perror("Fail to send message done");
goto out;
}
/* send logs */
if (msg_done.test_done.have_log) {
char *src;
size_t slen;
src = env.log_buf;
slen = env.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 (env.log_buf) {
free(env.log_buf);
env.log_buf = NULL;
env.log_cnt = 0;
}
if (env.debug)
fprintf(stderr, "[%d]: #%d:%s done.\n",
env.worker_id,
test_to_run + 1,
test->test_name);
break;
} /* case MSG_DO_TEST */
default:
if (env.debug)
fprintf(stderr, "[%d]: unknown message.\n", env.worker_id);
return -1;
}
}
out:
return 0;
}
int main(int argc, char **argv)
{
static const struct argp argp = {
......@@ -809,7 +1327,7 @@ int main(int argc, char **argv)
struct sigaction sigact = {
.sa_handler = crash_handler,
.sa_flags = SA_RESETHAND,
};
};
int err, i;
sigaction(SIGSEGV, &sigact, NULL);
......@@ -837,21 +1355,84 @@ int main(int argc, char **argv)
return -1;
}
save_netns();
stdio_hijack();
env.stdout = stdout;
env.stderr = stderr;
env.has_testmod = true;
if (!env.list_test_names && load_bpf_testmod()) {
fprintf(env.stderr, "WARNING! Selftests relying on bpf_testmod.ko will be skipped.\n");
env.has_testmod = false;
}
/* initializing tests */
for (i = 0; i < prog_test_cnt; i++) {
struct prog_test_def *test = &prog_test_defs[i];
env.test = test;
test->test_num = i + 1;
if (!should_run(&env.test_selector,
if (should_run(&env.test_selector,
test->test_num, test->test_name))
test->should_run = true;
else
test->should_run = false;
if ((test->run_test == NULL && test->run_serial_test == NULL) ||
(test->run_test != NULL && test->run_serial_test != NULL)) {
fprintf(stderr, "Test %d:%s must have either test_%s() or serial_test_%sl() defined.\n",
test->test_num, test->test_name, test->test_name, test->test_name);
exit(EXIT_ERR_SETUP_INFRA);
}
}
/* ignore workers if we are just listing */
if (env.get_test_cnt || env.list_test_names)
env.workers = 0;
/* launch workers if requested */
env.worker_id = -1; /* main process */
if (env.workers) {
env.worker_pids = calloc(sizeof(__pid_t), env.workers);
env.worker_socks = calloc(sizeof(int), env.workers);
if (env.debug)
fprintf(stdout, "Launching %d workers.\n", env.workers);
for (i = 0; i < env.workers; i++) {
int sv[2];
pid_t pid;
if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sv) < 0) {
perror("Fail to create worker socket");
return -1;
}
pid = fork();
if (pid < 0) {
perror("Failed to fork worker");
return -1;
} else if (pid != 0) { /* main process */
close(sv[1]);
env.worker_pids[i] = pid;
env.worker_socks[i] = sv[0];
} else { /* inside each worker process */
close(sv[0]);
env.worker_id = i;
return worker_main(sv[1]);
}
}
if (env.worker_id == -1) {
server_main();
goto out;
}
}
/* The rest of the main process */
/* on single mode */
save_netns();
for (i = 0; i < prog_test_cnt; i++) {
struct prog_test_def *test = &prog_test_defs[i];
struct test_result *result;
if (!test->should_run)
continue;
if (env.get_test_cnt) {
......@@ -865,33 +1446,35 @@ int main(int argc, char **argv)
continue;
}
test->run_test();
/* ensure last sub-test is finalized properly */
if (test->subtest_name)
test__end_subtest();
stdio_hijack();
test->tested = true;
run_one_test(i);
dump_test_log(test, test->error_cnt);
stdio_restore();
fprintf(env.stdout, "#%d %s:%s\n",
test->test_num, test->test_name,
test->error_cnt ? "FAIL" : (test->skip_cnt ? "SKIP" : "OK"));
result = &test_results[i];
result->error_cnt = test->error_cnt;
if (env.log_buf) {
result->log_buf = strdup(env.log_buf);
result->log_cnt = env.log_cnt;
free(env.log_buf);
env.log_buf = NULL;
env.log_cnt = 0;
}
if (test->error_cnt)
env.fail_cnt++;
else
env.succ_cnt++;
skip_account();
reset_affinity();
restore_netns();
if (test->need_cgroup_cleanup)
cleanup_cgroup_environment();
skip_account();
env.sub_succ_cnt += test->sub_succ_cnt;
}
if (!env.list_test_names && env.has_testmod)
unload_bpf_testmod();
stdio_restore();
if (env.get_test_cnt) {
printf("%d\n", env.succ_cnt);
......@@ -904,14 +1487,18 @@ int main(int argc, char **argv)
fprintf(stdout, "Summary: %d/%d PASSED, %d SKIPPED, %d FAILED\n",
env.succ_cnt, env.sub_succ_cnt, env.skip_cnt, env.fail_cnt);
print_all_error_logs();
close(env.saved_netns_fd);
out:
if (!env.list_test_names && env.has_testmod)
unload_bpf_testmod();
free_str_set(&env.test_selector.blacklist);
free_str_set(&env.test_selector.whitelist);
free(env.test_selector.num_set);
free_str_set(&env.subtest_selector.blacklist);
free_str_set(&env.subtest_selector.whitelist);
free(env.subtest_selector.num_set);
close(env.saved_netns_fd);
if (env.succ_cnt + env.fail_cnt + env.skip_cnt == 0)
return EXIT_NO_TEST;
......
......@@ -62,6 +62,7 @@ struct test_env {
struct test_selector test_selector;
struct test_selector subtest_selector;
bool verifier_stats;
bool debug;
enum verbosity verbosity;
bool jit_enabled;
......@@ -69,7 +70,8 @@ struct test_env {
bool get_test_cnt;
bool list_test_names;
struct prog_test_def *test;
struct prog_test_def *test; /* current running tests */
FILE *stdout;
FILE *stderr;
char *log_buf;
......@@ -82,6 +84,38 @@ struct test_env {
int skip_cnt; /* skipped tests */
int saved_netns_fd;
int workers; /* number of worker process */
int worker_id; /* id number of current worker, main process is -1 */
pid_t *worker_pids; /* array of worker pids */
int *worker_socks; /* array of worker socks */
int *worker_current_test; /* array of current running test for each worker */
};
#define MAX_LOG_TRUNK_SIZE 8192
enum msg_type {
MSG_DO_TEST = 0,
MSG_TEST_DONE = 1,
MSG_TEST_LOG = 2,
MSG_EXIT = 255,
};
struct msg {
enum msg_type type;
union {
struct {
int test_num;
} do_test;
struct {
int test_num;
int sub_succ_cnt;
int error_cnt;
int skip_cnt;
bool have_log;
} test_done;
struct {
char log_buf[MAX_LOG_TRUNK_SIZE + 1];
bool is_last;
} test_log;
};
};
extern struct test_env env;
......
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