| // SPDX-License-Identifier: LGPL-2.1-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. |
| * |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <errno.h> |
| #include <syslog.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <sys/signalfd.h> |
| #include <sys/socket.h> |
| |
| #include <glib.h> |
| |
| #include "bluetooth/bluetooth.h" |
| #include "bluetooth/hci.h" |
| |
| #ifdef HAVE_VALGRIND_MEMCHECK_H |
| #include <valgrind/memcheck.h> |
| #endif |
| |
| #include "src/shared/mainloop.h" |
| #include "src/shared/util.h" |
| #include "src/shared/io.h" |
| #include "src/shared/tester.h" |
| #include "src/shared/log.h" |
| #include "src/shared/timeout.h" |
| |
| #define COLOR_OFF "\x1B[0m" |
| #define COLOR_BLACK "\x1B[0;30m" |
| #define COLOR_RED "\x1B[0;31m" |
| #define COLOR_GREEN "\x1B[0;32m" |
| #define COLOR_YELLOW "\x1B[0;33m" |
| #define COLOR_BLUE "\x1B[0;34m" |
| #define COLOR_MAGENTA "\x1B[0;35m" |
| #define COLOR_CYAN "\x1B[0;36m" |
| #define COLOR_WHITE "\x1B[0;37m" |
| #define COLOR_HIGHLIGHT "\x1B[1;39m" |
| |
| #define print_text(color, fmt, args...) \ |
| tester_log(color fmt COLOR_OFF, ## args) |
| |
| #define print_summary(label, color, value, fmt, args...) \ |
| tester_log("%-52s " color "%-10s" COLOR_OFF fmt, \ |
| label, value, ## args) |
| |
| #define print_progress(name, color, fmt, args...) \ |
| tester_log(COLOR_HIGHLIGHT "%s" COLOR_OFF " - " \ |
| color fmt COLOR_OFF, name, ## args) |
| |
| enum test_result { |
| TEST_RESULT_NOT_RUN, |
| TEST_RESULT_PASSED, |
| TEST_RESULT_FAILED, |
| TEST_RESULT_TIMED_OUT, |
| }; |
| |
| enum test_stage { |
| TEST_STAGE_INVALID, |
| TEST_STAGE_PRE_SETUP, |
| TEST_STAGE_SETUP, |
| TEST_STAGE_RUN, |
| TEST_STAGE_TEARDOWN, |
| TEST_STAGE_POST_TEARDOWN, |
| }; |
| |
| struct test_case { |
| char *name; |
| enum test_result result; |
| enum test_stage stage; |
| const void *test_data; |
| const struct iovec *iov; |
| size_t iovcnt; |
| tester_data_func_t pre_setup_func; |
| tester_data_func_t setup_func; |
| tester_data_func_t test_func; |
| tester_data_func_t teardown_func; |
| tester_data_func_t post_teardown_func; |
| tester_data_func_t io_complete_func; |
| gdouble start_time; |
| gdouble end_time; |
| unsigned int timeout; |
| unsigned int timeout_id; |
| unsigned int teardown_id; |
| tester_destroy_func_t destroy; |
| void *user_data; |
| }; |
| |
| static char *tester_name; |
| |
| static GList *test_list; |
| static GList *test_current; |
| static GTimer *test_timer; |
| |
| static gboolean option_version = FALSE; |
| static gboolean option_quiet = FALSE; |
| static gboolean option_debug = FALSE; |
| static gboolean option_monitor = FALSE; |
| static gboolean option_list = FALSE; |
| static const char *option_prefix = NULL; |
| static const char *option_string = NULL; |
| |
| struct monitor_hdr { |
| uint16_t opcode; |
| uint16_t index; |
| uint16_t len; |
| uint8_t priority; |
| uint8_t ident_len; |
| } __attribute__((packed)); |
| |
| struct monitor_l2cap_hdr { |
| uint16_t cid; |
| uint16_t psm; |
| } __attribute__((packed)); |
| |
| static void test_destroy(gpointer data) |
| { |
| struct test_case *test = data; |
| |
| if (test->timeout_id > 0) |
| timeout_remove(test->timeout_id); |
| |
| if (test->teardown_id > 0) |
| g_source_remove(test->teardown_id); |
| |
| if (test->destroy) |
| test->destroy(test->user_data); |
| |
| free(test->name); |
| free(test); |
| } |
| |
| static void tester_vprintf(const char *format, va_list ap) |
| { |
| if (tester_use_quiet()) |
| return; |
| |
| printf(" %s", COLOR_WHITE); |
| vprintf(format, ap); |
| printf("%s\n", COLOR_OFF); |
| } |
| |
| static void tester_log(const char *format, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, format); |
| vprintf(format, ap); |
| printf("\n"); |
| va_end(ap); |
| |
| va_start(ap, format); |
| bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_INFO, format, ap); |
| va_end(ap); |
| } |
| |
| void tester_print(const char *format, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, format); |
| tester_vprintf(format, ap); |
| va_end(ap); |
| |
| va_start(ap, format); |
| bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_INFO, format, ap); |
| va_end(ap); |
| } |
| |
| void tester_debug(const char *format, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, format); |
| tester_vprintf(format, ap); |
| va_end(ap); |
| |
| va_start(ap, format); |
| bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_DEBUG, format, ap); |
| va_end(ap); |
| } |
| |
| void tester_warn(const char *format, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, format); |
| tester_vprintf(format, ap); |
| va_end(ap); |
| |
| va_start(ap, format); |
| bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_WARNING, format, ap); |
| va_end(ap); |
| } |
| |
| static void monitor_debug(const char *str, void *user_data) |
| { |
| const char *label = user_data; |
| |
| tester_debug("%s: %s", label, str); |
| } |
| |
| static void monitor_log(char dir, uint16_t cid, uint16_t psm, const void *data, |
| size_t len) |
| { |
| struct iovec iov[3]; |
| struct monitor_l2cap_hdr hdr; |
| uint8_t term = 0x00; |
| char label[16]; |
| |
| if (snprintf(label, sizeof(label), "%c %s", dir, tester_name) < 0) |
| return; |
| |
| hdr.cid = cpu_to_le16(cid); |
| hdr.psm = cpu_to_le16(psm); |
| |
| iov[0].iov_base = &hdr; |
| iov[0].iov_len = sizeof(hdr); |
| |
| iov[1].iov_base = (void *) data; |
| iov[1].iov_len = len; |
| |
| /* Kernel won't forward if data is no NULL terminated */ |
| iov[2].iov_base = &term; |
| iov[2].iov_len = sizeof(term); |
| |
| bt_log_sendmsg(HCI_DEV_NONE, label, LOG_INFO, iov, 3); |
| } |
| |
| void tester_monitor(char dir, uint16_t cid, uint16_t psm, const void *data, |
| size_t len) |
| { |
| monitor_log(dir, cid, psm, data, len); |
| |
| if (!tester_use_debug()) |
| return; |
| |
| util_hexdump(dir, data, len, monitor_debug, (void *) tester_name); |
| } |
| |
| static void default_pre_setup(const void *test_data) |
| { |
| tester_pre_setup_complete(); |
| } |
| |
| static void default_setup(const void *test_data) |
| { |
| tester_setup_complete(); |
| } |
| |
| static void default_teardown(const void *test_data) |
| { |
| tester_teardown_complete(); |
| } |
| |
| static void default_post_teardown(const void *test_data) |
| { |
| tester_post_teardown_complete(); |
| } |
| |
| void tester_add_full(const char *name, const void *test_data, |
| tester_data_func_t pre_setup_func, |
| tester_data_func_t setup_func, |
| tester_data_func_t test_func, |
| tester_data_func_t teardown_func, |
| tester_data_func_t post_teardown_func, |
| unsigned int timeout, |
| void *user_data, tester_destroy_func_t destroy) |
| { |
| struct test_case *test; |
| |
| if (!test_func) |
| return; |
| |
| if (option_prefix && !g_str_has_prefix(name, option_prefix)) { |
| if (destroy) |
| destroy(user_data); |
| return; |
| } |
| |
| if (option_string && !strstr(name, option_string)) { |
| if (destroy) |
| destroy(user_data); |
| return; |
| } |
| |
| if (option_list) { |
| tester_log("%s", name); |
| if (destroy) |
| destroy(user_data); |
| return; |
| } |
| |
| test = new0(struct test_case, 1); |
| test->name = strdup(name); |
| test->result = TEST_RESULT_NOT_RUN; |
| test->stage = TEST_STAGE_INVALID; |
| |
| test->test_data = test_data; |
| |
| if (pre_setup_func) |
| test->pre_setup_func = pre_setup_func; |
| else |
| test->pre_setup_func = default_pre_setup; |
| |
| if (setup_func) |
| test->setup_func = setup_func; |
| else |
| test->setup_func = default_setup; |
| |
| test->test_func = test_func; |
| |
| if (teardown_func) |
| test->teardown_func = teardown_func; |
| else |
| test->teardown_func = default_teardown; |
| |
| if (post_teardown_func) |
| test->post_teardown_func = post_teardown_func; |
| else |
| test->post_teardown_func = default_post_teardown; |
| |
| test->timeout = timeout; |
| |
| test->destroy = destroy; |
| test->user_data = user_data; |
| |
| test_list = g_list_append(test_list, test); |
| } |
| |
| void tester_add(const char *name, const void *test_data, |
| tester_data_func_t setup_func, |
| tester_data_func_t test_func, |
| tester_data_func_t teardown_func) |
| { |
| tester_add_full(name, test_data, NULL, setup_func, test_func, |
| teardown_func, NULL, 0, NULL, NULL); |
| } |
| |
| static struct test_case *tester_get_test(void) |
| { |
| if (!test_current) |
| return NULL; |
| |
| return test_current->data; |
| } |
| |
| void *tester_get_data(void) |
| { |
| struct test_case *test = tester_get_test(); |
| |
| if (!test) |
| return NULL; |
| |
| return test->user_data; |
| } |
| |
| static int tester_summarize(void) |
| { |
| unsigned int not_run = 0, passed = 0, failed = 0; |
| gdouble execution_time; |
| GList *list; |
| |
| tester_log(""); |
| print_text(COLOR_HIGHLIGHT, ""); |
| print_text(COLOR_HIGHLIGHT, "Test Summary"); |
| print_text(COLOR_HIGHLIGHT, "------------"); |
| |
| for (list = g_list_first(test_list); list; list = g_list_next(list)) { |
| struct test_case *test = list->data; |
| gdouble exec_time; |
| |
| exec_time = test->end_time - test->start_time; |
| |
| switch (test->result) { |
| case TEST_RESULT_NOT_RUN: |
| print_summary(test->name, COLOR_YELLOW, "Not Run", ""); |
| not_run++; |
| break; |
| case TEST_RESULT_PASSED: |
| print_summary(test->name, COLOR_GREEN, "Passed", |
| "%8.3f seconds", exec_time); |
| passed++; |
| break; |
| case TEST_RESULT_FAILED: |
| print_summary(test->name, COLOR_RED, "Failed", |
| "%8.3f seconds", exec_time); |
| failed++; |
| break; |
| case TEST_RESULT_TIMED_OUT: |
| print_summary(test->name, COLOR_RED, "Timed out", |
| "%8.3f seconds", exec_time); |
| failed++; |
| break; |
| } |
| } |
| |
| tester_log("Total: %d, " |
| COLOR_GREEN "Passed: %d (%.1f%%)" COLOR_OFF ", " |
| COLOR_RED "Failed: %d" COLOR_OFF ", " |
| COLOR_YELLOW "Not Run: %d" COLOR_OFF, |
| not_run + passed + failed, passed, |
| (not_run + passed + failed) ? |
| (float) passed * 100 / (not_run + passed + failed) : 0, |
| failed, not_run); |
| |
| execution_time = g_timer_elapsed(test_timer, NULL); |
| tester_log("Overall execution time: %.3g seconds", execution_time); |
| |
| return failed; |
| } |
| |
| static gboolean teardown_callback(gpointer user_data) |
| { |
| struct test_case *test = user_data; |
| |
| test->teardown_id = 0; |
| test->stage = TEST_STAGE_TEARDOWN; |
| |
| print_progress(test->name, COLOR_MAGENTA, "teardown"); |
| test->teardown_func(test->test_data); |
| |
| #ifdef HAVE_VALGRIND_MEMCHECK_H |
| VALGRIND_DO_ADDED_LEAK_CHECK; |
| #endif |
| |
| return FALSE; |
| } |
| |
| static bool test_timeout(gpointer user_data) |
| { |
| struct test_case *test = user_data; |
| |
| test->timeout_id = 0; |
| |
| if (!test_current) |
| return FALSE; |
| |
| test->result = TEST_RESULT_TIMED_OUT; |
| print_progress(test->name, COLOR_RED, "test timed out"); |
| |
| g_idle_add(teardown_callback, test); |
| |
| return FALSE; |
| } |
| |
| static void next_test_case(void) |
| { |
| struct test_case *test; |
| |
| if (test_current) |
| test_current = g_list_next(test_current); |
| else |
| test_current = test_list; |
| |
| if (!test_current) { |
| g_timer_stop(test_timer); |
| |
| mainloop_quit(); |
| return; |
| } |
| |
| test = test_current->data; |
| |
| tester_log(""); |
| print_progress(test->name, COLOR_BLACK, "init"); |
| |
| test->start_time = g_timer_elapsed(test_timer, NULL); |
| |
| if (test->timeout > 0) |
| test->timeout_id = timeout_add_seconds(test->timeout, |
| test_timeout, test, |
| NULL); |
| |
| test->stage = TEST_STAGE_PRE_SETUP; |
| |
| test->pre_setup_func(test->test_data); |
| } |
| |
| static gboolean setup_callback(gpointer user_data) |
| { |
| struct test_case *test = user_data; |
| |
| test->stage = TEST_STAGE_SETUP; |
| |
| print_progress(test->name, COLOR_BLUE, "setup"); |
| test->setup_func(test->test_data); |
| |
| return FALSE; |
| } |
| |
| static gboolean run_callback(gpointer user_data) |
| { |
| struct test_case *test = user_data; |
| |
| test->stage = TEST_STAGE_RUN; |
| |
| print_progress(test->name, COLOR_BLACK, "run"); |
| test->test_func(test->test_data); |
| |
| return FALSE; |
| } |
| |
| static gboolean done_callback(gpointer user_data) |
| { |
| struct test_case *test = user_data; |
| |
| test->end_time = g_timer_elapsed(test_timer, NULL); |
| |
| print_progress(test->name, COLOR_BLACK, "done"); |
| next_test_case(); |
| |
| return FALSE; |
| } |
| |
| void tester_pre_setup_complete(void) |
| { |
| struct test_case *test; |
| |
| if (!test_current) |
| return; |
| |
| test = test_current->data; |
| |
| if (test->stage != TEST_STAGE_PRE_SETUP) |
| return; |
| |
| g_idle_add(setup_callback, test); |
| } |
| |
| void tester_pre_setup_failed(void) |
| { |
| struct test_case *test; |
| |
| if (!test_current) |
| return; |
| |
| test = test_current->data; |
| |
| if (test->stage != TEST_STAGE_PRE_SETUP) |
| return; |
| |
| if (test->timeout_id > 0) { |
| timeout_remove(test->timeout_id); |
| test->timeout_id = 0; |
| } |
| |
| print_progress(test->name, COLOR_RED, "pre setup failed"); |
| |
| g_idle_add(done_callback, test); |
| } |
| |
| void tester_pre_setup_abort(void) |
| { |
| struct test_case *test; |
| |
| if (!test_current) |
| return; |
| |
| test = test_current->data; |
| |
| if (test->stage != TEST_STAGE_PRE_SETUP) |
| return; |
| |
| if (test->timeout_id > 0) { |
| timeout_remove(test->timeout_id); |
| test->timeout_id = 0; |
| } |
| |
| print_progress(test->name, COLOR_YELLOW, "not run"); |
| |
| g_idle_add(done_callback, test); |
| } |
| |
| bool tester_pre_setup_skip_by_default(void) |
| { |
| if (!option_prefix && !option_string) { |
| tester_pre_setup_abort(); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void tester_setup_complete(void) |
| { |
| struct test_case *test; |
| |
| if (!test_current) |
| return; |
| |
| test = test_current->data; |
| |
| if (test->stage != TEST_STAGE_SETUP) |
| return; |
| |
| print_progress(test->name, COLOR_BLUE, "setup complete"); |
| |
| g_idle_add(run_callback, test); |
| } |
| |
| void tester_setup_failed(void) |
| { |
| struct test_case *test; |
| |
| if (!test_current) |
| return; |
| |
| test = test_current->data; |
| |
| if (test->stage != TEST_STAGE_SETUP) |
| return; |
| |
| test->stage = TEST_STAGE_POST_TEARDOWN; |
| |
| if (test->timeout_id > 0) { |
| timeout_remove(test->timeout_id); |
| test->timeout_id = 0; |
| } |
| |
| print_progress(test->name, COLOR_RED, "setup failed"); |
| print_progress(test->name, COLOR_MAGENTA, "teardown"); |
| |
| test->post_teardown_func(test->test_data); |
| } |
| |
| static void test_result(enum test_result result) |
| { |
| struct test_case *test; |
| |
| if (!test_current) |
| return; |
| |
| test = test_current->data; |
| |
| if (test->stage != TEST_STAGE_RUN) |
| return; |
| |
| if (test->timeout_id > 0) { |
| timeout_remove(test->timeout_id); |
| test->timeout_id = 0; |
| } |
| |
| tester_shutdown_io(); |
| |
| if (test->result == TEST_RESULT_FAILED) |
| result = TEST_RESULT_FAILED; |
| |
| test->result = result; |
| switch (result) { |
| case TEST_RESULT_PASSED: |
| print_progress(test->name, COLOR_GREEN, "test passed"); |
| break; |
| case TEST_RESULT_FAILED: |
| print_progress(test->name, COLOR_RED, "test failed"); |
| break; |
| case TEST_RESULT_NOT_RUN: |
| print_progress(test->name, COLOR_YELLOW, "test not run"); |
| break; |
| case TEST_RESULT_TIMED_OUT: |
| print_progress(test->name, COLOR_RED, "test timed out"); |
| break; |
| } |
| |
| if (test->teardown_id > 0) |
| return; |
| |
| test->teardown_id = g_idle_add(teardown_callback, test); |
| } |
| |
| void tester_test_passed(void) |
| { |
| test_result(TEST_RESULT_PASSED); |
| } |
| |
| void tester_test_failed(void) |
| { |
| test_result(TEST_RESULT_FAILED); |
| } |
| |
| void tester_test_abort(void) |
| { |
| test_result(TEST_RESULT_NOT_RUN); |
| } |
| |
| void tester_teardown_complete(void) |
| { |
| struct test_case *test; |
| |
| if (!test_current) |
| return; |
| |
| test = test_current->data; |
| |
| if (test->stage != TEST_STAGE_TEARDOWN) |
| return; |
| |
| test->stage = TEST_STAGE_POST_TEARDOWN; |
| |
| test->post_teardown_func(test->test_data); |
| } |
| |
| void tester_teardown_failed(void) |
| { |
| struct test_case *test; |
| |
| if (!test_current) |
| return; |
| |
| test = test_current->data; |
| |
| if (test->stage != TEST_STAGE_TEARDOWN) |
| return; |
| |
| test->stage = TEST_STAGE_POST_TEARDOWN; |
| |
| tester_post_teardown_failed(); |
| } |
| |
| void tester_post_teardown_complete(void) |
| { |
| struct test_case *test; |
| |
| if (!test_current) |
| return; |
| |
| test = test_current->data; |
| |
| if (test->stage != TEST_STAGE_POST_TEARDOWN) |
| return; |
| |
| print_progress(test->name, COLOR_MAGENTA, "teardown complete"); |
| |
| g_idle_add(done_callback, test); |
| } |
| |
| void tester_post_teardown_failed(void) |
| { |
| struct test_case *test; |
| |
| if (!test_current) |
| return; |
| |
| test = test_current->data; |
| |
| if (test->stage != TEST_STAGE_POST_TEARDOWN) |
| return; |
| |
| print_progress(test->name, COLOR_RED, "teardown failed"); |
| |
| g_idle_add(done_callback, test); |
| } |
| |
| static gboolean start_tester(gpointer user_data) |
| { |
| test_timer = g_timer_new(); |
| |
| next_test_case(); |
| |
| return FALSE; |
| } |
| |
| struct wait_data { |
| unsigned int seconds; |
| struct test_case *test; |
| tester_wait_func_t func; |
| void *user_data; |
| }; |
| |
| static gboolean wait_callback(gpointer user_data) |
| { |
| struct wait_data *wait = user_data; |
| struct test_case *test = wait->test; |
| |
| wait->seconds--; |
| |
| if (wait->seconds > 0) { |
| print_progress(test->name, COLOR_BLACK, "%u seconds left", |
| wait->seconds); |
| return TRUE; |
| } |
| |
| print_progress(test->name, COLOR_BLACK, "waiting done"); |
| |
| wait->func(wait->user_data); |
| |
| free(wait); |
| |
| return FALSE; |
| } |
| |
| void tester_wait(unsigned int seconds, tester_wait_func_t func, |
| void *user_data) |
| { |
| struct test_case *test; |
| struct wait_data *wait; |
| |
| if (!func || seconds < 1) |
| return; |
| |
| if (!test_current) |
| return; |
| |
| test = test_current->data; |
| |
| wait = new0(struct wait_data, 1); |
| wait->seconds = seconds; |
| wait->test = test; |
| wait->func = func; |
| wait->user_data = user_data; |
| |
| g_timeout_add(1000, wait_callback, wait); |
| |
| print_progress(test->name, COLOR_BLACK, "waiting %u seconds", seconds); |
| } |
| |
| static void signal_callback(int signum, void *user_data) |
| { |
| static bool terminated = false; |
| |
| switch (signum) { |
| case SIGINT: |
| case SIGTERM: |
| if (!terminated) |
| mainloop_quit(); |
| |
| terminated = true; |
| break; |
| } |
| } |
| |
| bool tester_use_quiet(void) |
| { |
| return option_quiet == TRUE ? true : false; |
| } |
| |
| bool tester_use_debug(void) |
| { |
| return option_debug == TRUE ? true : false; |
| } |
| |
| static GOptionEntry options[] = { |
| { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, |
| "Show version information and exit" }, |
| { "quiet", 'q', 0, G_OPTION_ARG_NONE, &option_quiet, |
| "Run tests without logging" }, |
| { "debug", 'd', 0, G_OPTION_ARG_NONE, &option_debug, |
| "Run tests with debug output" }, |
| { "monitor", 'm', 0, G_OPTION_ARG_NONE, &option_monitor, |
| "Enable monitor output" }, |
| { "list", 'l', 0, G_OPTION_ARG_NONE, &option_list, |
| "Only list the tests to be run" }, |
| { "prefix", 'p', 0, G_OPTION_ARG_STRING, &option_prefix, |
| "Run tests matching provided prefix" }, |
| { "string", 's', 0, G_OPTION_ARG_STRING, &option_string, |
| "Run tests matching provided string" }, |
| { NULL }, |
| }; |
| |
| void tester_init(int *argc, char ***argv) |
| { |
| GOptionContext *context; |
| GError *error = NULL; |
| |
| context = g_option_context_new(NULL); |
| g_option_context_add_main_entries(context, options, NULL); |
| |
| if (g_option_context_parse(context, argc, argv, &error) == FALSE) { |
| if (error != NULL) { |
| g_printerr("%s\n", error->message); |
| g_error_free(error); |
| } else |
| g_printerr("An unknown error occurred\n"); |
| exit(1); |
| } |
| |
| g_option_context_free(context); |
| |
| if (option_version == TRUE) { |
| g_print("%s\n", VERSION); |
| exit(EXIT_SUCCESS); |
| } |
| |
| mainloop_init(); |
| |
| tester_name = strrchr(*argv[0], '/'); |
| if (!tester_name) |
| tester_name = strdup(*argv[0]); |
| else |
| tester_name = strdup(++tester_name); |
| |
| test_list = NULL; |
| test_current = NULL; |
| } |
| |
| static struct io *ios[2]; |
| |
| static bool io_disconnected(struct io *io, void *user_data) |
| { |
| if (io == ios[0]) { |
| io_destroy(ios[0]); |
| ios[0] = NULL; |
| } else if (io == ios[1]) { |
| io_destroy(ios[1]); |
| ios[1] = NULL; |
| } |
| |
| return false; |
| } |
| |
| static const struct iovec *test_get_iov(struct test_case *test) |
| { |
| const struct iovec *iov; |
| |
| if (!test || !test->iov || !test->iovcnt) |
| return NULL; |
| |
| iov = test->iov; |
| |
| test->iov++; |
| test->iovcnt--; |
| |
| return iov; |
| } |
| |
| static bool test_io_send(struct io *io, void *user_data) |
| { |
| struct test_case *test = tester_get_test(); |
| const struct iovec *iov = test_get_iov(test); |
| ssize_t len; |
| |
| if (!iov) |
| return false; |
| |
| len = io_send(io, iov, 1); |
| |
| tester_monitor('<', 0x0004, 0x0000, iov->iov_base, len); |
| |
| g_assert_cmpint(len, ==, iov->iov_len); |
| |
| if (!test->iovcnt && test->io_complete_func) { |
| test->io_complete_func(test->test_data); |
| } else if (test->iovcnt && !test->iov->iov_base) { |
| test_get_iov(test); |
| return test_io_send(io, user_data); |
| } |
| |
| return false; |
| } |
| |
| static bool test_io_recv(struct io *io, void *user_data) |
| { |
| struct test_case *test = tester_get_test(); |
| const struct iovec *iov = test_get_iov(test); |
| unsigned char buf[512]; |
| int fd; |
| ssize_t len; |
| |
| fd = io_get_fd(io); |
| |
| len = read(fd, buf, sizeof(buf)); |
| |
| g_assert(len > 0); |
| |
| tester_monitor('>', 0x0004, 0x0000, buf, len); |
| |
| if (!iov) |
| return true; |
| |
| if (test->iovcnt && !iov->iov_base) |
| iov = test_get_iov(test); |
| |
| if ((size_t)len != iov->iov_len || memcmp(buf, iov->iov_base, len)) |
| tester_monitor('!', 0x0004, 0x0000, iov->iov_base, |
| iov->iov_len); |
| |
| g_assert_cmpint(len, ==, iov->iov_len); |
| |
| g_assert(memcmp(buf, iov->iov_base, len) == 0); |
| |
| if (test->iovcnt) |
| io_set_write_handler(io, test_io_send, NULL, NULL); |
| else if (test->io_complete_func) |
| test->io_complete_func(test->test_data); |
| |
| return true; |
| } |
| |
| static void setup_io(void) |
| { |
| int fd[2], err; |
| |
| io_destroy(ios[0]); |
| io_destroy(ios[1]); |
| |
| err = socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, fd); |
| if (err < 0) { |
| tester_warn("socketpair: %s (%d)", strerror(errno), errno); |
| return; |
| } |
| |
| ios[0] = io_new(fd[0]); |
| if (!ios[0]) { |
| tester_warn("io_new: %p", ios[0]); |
| return; |
| } |
| |
| io_set_close_on_destroy(ios[0], true); |
| io_set_disconnect_handler(ios[0], io_disconnected, NULL, NULL); |
| |
| ios[1] = io_new(fd[1]); |
| if (!ios[1]) { |
| tester_warn("io_new: %p", ios[1]); |
| return; |
| } |
| |
| io_set_close_on_destroy(ios[1], true); |
| io_set_disconnect_handler(ios[1], io_disconnected, NULL, NULL); |
| io_set_read_handler(ios[1], test_io_recv, NULL, NULL); |
| } |
| |
| struct io *tester_setup_io(const struct iovec *iov, int iovcnt) |
| { |
| struct test_case *test = tester_get_test(); |
| |
| if (!ios[0] || !ios[1]) { |
| setup_io(); |
| if (!ios[0] || !ios[1]) { |
| tester_warn("Unable to setup IO"); |
| return NULL; |
| } |
| } |
| |
| test->iov = iov; |
| test->iovcnt = iovcnt; |
| |
| return ios[0]; |
| } |
| |
| void tester_shutdown_io(void) |
| { |
| io_shutdown(ios[0]); |
| io_shutdown(ios[1]); |
| } |
| |
| void tester_io_send(void) |
| { |
| struct test_case *test = tester_get_test(); |
| |
| if (test->iovcnt) |
| io_set_write_handler(ios[1], test_io_send, NULL, NULL); |
| } |
| |
| void tester_io_set_complete_func(tester_data_func_t func) |
| { |
| struct test_case *test = tester_get_test(); |
| |
| test->io_complete_func = func; |
| } |
| |
| int tester_run(void) |
| { |
| int ret; |
| |
| if (option_list) { |
| mainloop_quit(); |
| return EXIT_SUCCESS; |
| } |
| |
| g_idle_add(start_tester, NULL); |
| |
| mainloop_run_with_signal(signal_callback, NULL); |
| |
| ret = tester_summarize(); |
| |
| g_list_free_full(test_list, test_destroy); |
| |
| if (option_monitor) |
| bt_log_close(); |
| |
| io_destroy(ios[0]); |
| io_destroy(ios[1]); |
| |
| return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; |
| } |