/* backtrace() and backtrace_symbols_fd() are glibc specific, * use header file when glibc is available and provide stub * implementations when another libc implementation is used.
*/ #ifdef __GLIBC__ #include <execinfo.h> /* backtrace */ #else
__weak int backtrace(void **buffer, int size)
{ return 0;
}
__weak void backtrace_symbols_fd(void *const *buffer, int size, int fd)
{
dprintf(fd, "\n");
} #endif/*__GLIBC__ */
/* Override C runtime library's usleep() implementation to ensure nanosleep() * is always called. Usleep is frequently used in selftests as a way to * trigger kprobe and tracepoints.
*/ int usleep(useconds_t usec)
{ struct timespec ts = {
.tv_sec = usec / 1000000,
.tv_nsec = (usec % 1000000) * 1000,
};
return syscall(__NR_nanosleep, &ts, NULL);
}
/* Watchdog timer is started by watchdog_start() and stopped by watchdog_stop(). * If timer is active for longer than env.secs_till_notify, * it prints the name of the current test to the stderr. * If timer is active for longer than env.secs_till_kill, * it kills the thread executing the test by sending a SIGSEGV signal to it.
*/ staticvoid watchdog_timer_func(union sigval sigval)
{ struct itimerspec timeout = {}; char test_name[256]; int err;
switch (env.watchdog_state) { case WD_NOTIFY:
fprintf(env.stderr_saved, "WATCHDOG: test case %s executes for %d seconds...\n",
test_name, env.secs_till_notify);
timeout.it_value.tv_sec = env.secs_till_kill - env.secs_till_notify;
env.watchdog_state = WD_KILL;
err = timer_settime(env.watchdog, 0, &timeout, NULL); if (err)
fprintf(env.stderr_saved, "Failed to arm watchdog timer\n"); break; case WD_KILL:
fprintf(env.stderr_saved, "WATCHDOG: test case %s executes for %d seconds, terminating with SIGSEGV\n",
test_name, env.secs_till_kill);
pthread_kill(env.main_thread, SIGSEGV); break;
}
}
staticvoid watchdog_start(void)
{ struct itimerspec timeout = {}; int err;
if (result)
fprintf(env.stdout_saved, ":%s", result);
fprintf(env.stdout_saved, "\n");
}
staticvoid jsonw_write_log_message(json_writer_t *w, char *log_buf, size_t log_cnt)
{ /* open_memstream (from stdio_hijack_init) ensures that log_bug is terminated by a * null byte. Yet in parallel mode, log_buf will be NULL if there is no message.
*/ if (log_cnt) {
jsonw_string_field(w, "message", log_buf);
} else {
jsonw_string_field(w, "message", "");
}
}
if (w && print_test) {
jsonw_end_array(w);
jsonw_end_object(w);
}
print_test_result(test, test_state);
}
/* A bunch of tests set custom affinity per-thread and/or per-process. Reset * it after each test/sub-test.
*/ staticvoid reset_affinity(void)
{
cpu_set_t cpuset; int i, err;
CPU_ZERO(&cpuset); for (i = 0; i < env.nr_cpus; i++)
CPU_SET(i, &cpuset);
err = sched_setaffinity(0, sizeof(cpuset), &cpuset); if (err < 0) {
fprintf(stderr, "Failed to reset process affinity: %d!\n", err); exit(EXIT_ERR_SETUP_INFRA);
}
err = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset); if (err < 0) {
fprintf(stderr, "Failed to reset thread affinity: %d!\n", err); exit(EXIT_ERR_SETUP_INFRA);
}
}
int compare_map_keys(int map1_fd, int map2_fd)
{
__u32 key, next_key; char val_buf[PERF_MAX_STACK_DEPTH * sizeof(struct bpf_stack_build_id)]; int err;
err = bpf_map_get_next_key(map1_fd, NULL, &key); if (err) return err;
err = bpf_map_lookup_elem(map2_fd, &key, val_buf); if (err) return err;
while (bpf_map_get_next_key(map1_fd, &key, &next_key) == 0) {
err = bpf_map_lookup_elem(map2_fd, &next_key, val_buf); if (err) return err;
key = next_key;
} if (errno != ENOENT) return -1;
return 0;
}
int compare_stack_ips(int smap_fd, int amap_fd, int stack_trace_len)
{
__u32 key, next_key, *cur_key_p, *next_key_p; char *val_buf1, *val_buf2; int i, err = 0;
val_buf1 = malloc(stack_trace_len);
val_buf2 = malloc(stack_trace_len);
cur_key_p = NULL;
next_key_p = &key; while (bpf_map_get_next_key(smap_fd, cur_key_p, next_key_p) == 0) {
err = bpf_map_lookup_elem(smap_fd, next_key_p, val_buf1); if (err) goto out;
err = bpf_map_lookup_elem(amap_fd, next_key_p, val_buf2); if (err) goto out; for (i = 0; i < stack_trace_len; i++) { if (val_buf1[i] != val_buf2[i]) {
err = -1; goto out;
}
}
key = *next_key_p;
cur_key_p = &key;
next_key_p = &next_key;
} if (errno != ENOENT)
err = -1;
/* Create a new network namespace with the given name. * * Create a new network namespace and set the network namespace of the * current process to the new network namespace if the argument "open" is * true. This function should be paired with netns_free() to release the * resource and delete the network namespace. * * It also implements the functionality of the option "-m" by starting * traffic monitor on the background to capture the packets in this network * namespace if the current test or subtest matching the pattern. * * nsname: the name of the network namespace to create. * open: open the network namespace if true. * * Return: the network namespace object on success, NULL on failure.
*/ struct netns_obj *netns_new(constchar *nsname, bool open)
{ struct netns_obj *netns_obj = malloc(sizeof(*netns_obj)); constchar *test_name, *subtest_name; int r;
if (!netns_obj) return NULL;
memset(netns_obj, 0, sizeof(*netns_obj));
netns_obj->nsname = strdup(nsname); if (!netns_obj->nsname) goto fail;
/* Create the network namespace */
r = make_netns(nsname); if (r) goto fail;
/* Delete the network namespace. * * This function should be paired with netns_new() to delete the namespace * created by netns_new().
*/ void netns_free(struct netns_obj *netns_obj)
{ if (!netns_obj) return;
traffic_monitor_stop(netns_obj->tmon);
close_netns(netns_obj->nstoken);
remove_netns(netns_obj->nsname);
free(netns_obj->nsname);
free(netns_obj);
}
constchar *argp_program_version = "test_progs 0.1"; constchar *argp_program_bug_address = ""; staticconstchar argp_program_doc[] = "BPF selftests test runner\v" "Options accepting the NAMES parameter take either a comma-separated list\n" "of test names, or a filename prefixed with @. The file contains one name\n" "(or wildcard pattern) per line, and comments beginning with # are ignored.\n" "\n" "These options can be passed repeatedly to read multiple files.\n";
staticconststruct argp_option opts[] = {
{ "num", ARG_TEST_NUM, "NUM", 0, "Run test number NUM only " },
{ "name", ARG_TEST_NAME, "NAMES", 0, "Run tests with names containing any string from NAMES list" },
{ "name-blacklist", ARG_TEST_NAME_BLACKLIST, "NAMES", 0, "Don't run tests with names containing any string from NAMES list" },
{ "verifier-stats", ARG_VERIFIER_STATS, NULL, 0, "Output verifier statistics", },
{ "verbose", ARG_VERBOSE, "LEVEL", OPTION_ARG_OPTIONAL, "Verbose output (use -vv or -vvv for progressively verbose output)" },
{ "count", ARG_GET_TEST_CNT, NULL, 0, "Get number of selected top-level tests " },
{ "list", ARG_LIST_TEST_NAMES, NULL, 0, "List test names that would run (without running them) " },
{ "allow", ARG_TEST_NAME_GLOB_ALLOWLIST, "NAMES", 0, "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." },
{ "json-summary", ARG_JSON_SUMMARY, "FILE", 0, "Write report in json format to this file."}, #ifdef TRAFFIC_MONITOR
{ "traffic-monitor", ARG_TRAFFIC_MONITOR, "NAMES", 0, "Monitor network traffic of tests with name matching the pattern (supports '*' wildcard)." }, #endif
{ "watchdog-timeout", ARG_WATCHDOG_TIMEOUT, "SECONDS", 0, "Kill the process if tests are not making progress for specified number of seconds." },
{},
};
/* Creates a global memstream capturing INFO and WARN level output * passed to libbpf_print_fn. * Returns 0 on success, negative value on failure. * On failure the description is printed using PRINT_FAIL and * current test case is marked as fail.
*/ int start_libbpf_log_capture(void)
{ if (libbpf_capture_stream) {
PRINT_FAIL("%s: libbpf_capture_stream != NULL\n", __func__); return -EINVAL;
}
/* Destroys global memstream created by start_libbpf_log_capture(). * Returns a pointer to captured data which has to be freed. * Returned buffer is null terminated.
*/ char *stop_libbpf_log_capture(void)
{ char *buf;
if (!libbpf_capture_stream) return NULL;
fputc(0, libbpf_capture_stream);
fclose(libbpf_capture_stream);
libbpf_capture_stream = NULL; /* get 'buf' after fclose(), see open_memstream() documentation */
buf = libbpf_output_capture.buf;
memset(&libbpf_output_capture, 0, sizeof(libbpf_output_capture)); return buf;
}
if (subtest_str) {
*subtest_str = '\0'; if (parse_num_list(subtest_str + 1,
&env->subtest_selector.num_set,
&env->subtest_selector.num_set_len)) {
fprintf(stderr, "Failed to parse subtest numbers.\n"); return -EINVAL;
}
} if (parse_num_list(arg, &env->test_selector.num_set,
&env->test_selector.num_set_len)) {
fprintf(stderr, "Failed to parse test numbers.\n"); return -EINVAL;
} break;
} case ARG_TEST_NAME_GLOB_ALLOWLIST: case ARG_TEST_NAME: { if (arg[0] == '@')
err = parse_test_list_file(arg + 1,
&env->test_selector.whitelist,
key == ARG_TEST_NAME_GLOB_ALLOWLIST); else
err = parse_test_list(arg,
&env->test_selector.whitelist,
key == ARG_TEST_NAME_GLOB_ALLOWLIST);
break;
} case ARG_TEST_NAME_GLOB_DENYLIST: case ARG_TEST_NAME_BLACKLIST: { if (arg[0] == '@')
err = parse_test_list_file(arg + 1,
&env->test_selector.blacklist,
key == ARG_TEST_NAME_GLOB_DENYLIST); else
err = parse_test_list(arg,
&env->test_selector.blacklist,
key == ARG_TEST_NAME_GLOB_DENYLIST);
break;
} case ARG_VERIFIER_STATS:
env->verifier_stats = true; break; case ARG_VERBOSE:
env->verbosity = VERBOSE_NORMAL; if (arg) { if (strcmp(arg, "v") == 0) {
env->verbosity = VERBOSE_VERY;
extra_prog_load_log_flags = 1;
} elseif (strcmp(arg, "vv") == 0) {
env->verbosity = VERBOSE_SUPER;
extra_prog_load_log_flags = 2;
} else {
fprintf(stderr, "Unrecognized verbosity setting ('%s'), only -v and -vv are supported\n",
arg); return -EINVAL;
}
}
env_verbosity = env->verbosity;
if (verbose()) { if (setenv("SELFTESTS_VERBOSE", "1", 1) == -1) {
fprintf(stderr, "Unable to setenv SELFTESTS_VERBOSE=1 (errno=%d)",
errno); return -EINVAL;
}
}
break; case ARG_GET_TEST_CNT:
env->get_test_cnt = true; break; 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 ARG_JSON_SUMMARY:
env->json = fopen(arg, "w"); if (env->json == NULL) {
perror("Failed to open json summary file"); return -errno;
} break; case ARGP_KEY_ARG:
argp_usage(state); break; case ARGP_KEY_END: break; #ifdef TRAFFIC_MONITOR case ARG_TRAFFIC_MONITOR: if (arg[0] == '@')
err = parse_test_list_file(arg + 1,
&env->tmon_selector.whitelist, true); else
err = parse_test_list(arg,
&env->tmon_selector.whitelist, true); break; #endif case ARG_WATCHDOG_TIMEOUT:
env->secs_till_kill = atoi(arg); if (env->secs_till_kill < 0) {
fprintf(stderr, "Invalid watchdog timeout: %s.\n", arg); return -EINVAL;
} if (env->secs_till_kill < env->secs_till_notify) {
env->secs_till_notify = 0;
} break; default: return ARGP_ERR_UNKNOWN;
} return err;
}
/* * Determine if test_progs is running as a "flavored" test runner and switch * into corresponding sub-directory to load correct BPF objects. * * This is done by looking at executable name. If it contains "-flavor" * suffix, then we are running as a flavored test runner.
*/ int cd_flavor_subdir(constchar *exec_name)
{ /* General form of argv[0] passed here is: * some/path/to/test_progs[-flavor], where -flavor part is optional. * First cut out "test_progs[-flavor]" part, then extract "flavor" * part, if it's there.
*/ constchar *flavor = strrchr(exec_name, '/');
if (!flavor)
flavor = exec_name; else
flavor++;
flavor = strrchr(flavor, '-'); if (!flavor) return 0;
flavor++; if (verbose())
fprintf(stdout, "Switching to flavor '%s' subdirectory...\n", flavor);
return chdir(flavor);
}
int trigger_module_test_read(int read_sz)
{ int fd, err;
/* 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;
}
/* * We only print error logs summary when there are failed tests and * verbose mode is not enabled. Otherwise, results may be inconsistent. *
*/ 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];
if (!state->tested || !state->error_cnt) continue;
dump_test_log(test, state, true, true, w);
}
}
if (w) {
jsonw_end_array(w);
jsonw_end_object(w);
jsonw_destroy(&w);
}
env.jit_enabled = is_jit_enabled();
env.nr_cpus = libbpf_num_possible_cpus(); if (env.nr_cpus < 0) {
fprintf(stderr, "Failed to get number of CPUs: %d!\n",
env.nr_cpus); return -1;
}
env.has_testmod = true; if (!env.list_test_names) { /* ensure previous instance of the module is unloaded */
unload_bpf_testmod(verbose());
if (load_bpf_testmod(verbose())) {
fprintf(env.stderr_saved, "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];
test->test_num = i + 1;
test->should_run = should_run(&env.test_selector,
test->test_num, test->test_name);
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);
} if (test->should_run)
test->should_tmon = should_tmon(&env.tmon_selector, test->test_name);
}
/* 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;
} elseif (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];
if (!test->should_run) continue;
if (env.get_test_cnt) {
env.succ_cnt++; continue;
}
if (env.list_test_names) {
fprintf(env.stdout_saved, "%s\n", test->test_name);
env.succ_cnt++; continue;
}
run_one_test(i);
}
if (env.get_test_cnt) {
printf("%d\n", env.succ_cnt); goto out;
}
if (env.list_test_names) goto out;
calculate_summary_and_print_errors(&env);
close(env.saved_netns_fd);
out: if (!env.list_test_names && env.has_testmod)
unload_bpf_testmod(verbose());
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.