From b0c402bc695edeefe59ed93a5f30c3a81536dea6 Mon Sep 17 00:00:00 2001 From: Marcel Holtmann Date: Tue, 1 Jan 2013 02:20:29 -0800 Subject: [PATCH] shared: Add support for tester framework --- src/shared/tester.c | 684 ++++++++++++++++++++++++++++++++++++++++++++ src/shared/tester.h | 68 +++++ 2 files changed, 752 insertions(+) create mode 100644 src/shared/tester.c create mode 100644 src/shared/tester.h diff --git a/src/shared/tester.c b/src/shared/tester.c new file mode 100644 index 000000000..e4a1093ff --- /dev/null +++ b/src/shared/tester.c @@ -0,0 +1,684 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include "src/shared/tester.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...) \ + printf(color fmt COLOR_OFF "\n", ## args) + +#define print_summary(label, color, value) \ + printf("%-45s " color "%-10s" COLOR_OFF "\n", label, value) + +#define print_progress(name, color, fmt, args...) \ + printf(COLOR_HIGHLIGHT "%s" COLOR_OFF " - " \ + color fmt COLOR_OFF "\n", name, ##args) + +enum test_result { + TEST_RESULT_NOT_RUN, + TEST_RESULT_PASSED, + TEST_RESULT_FAILED, +}; + +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; + 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; +}; + +static GMainLoop *main_loop; + +static GList *test_list; +static GList *test_current; + +static void test_destroy(gpointer data) +{ + struct test_case *test = data; + + g_free(test->name); + g_free(test); +} + +void tester_print(const char *format, ...) +{ + va_list ap; + + if (tester_use_quiet()) + return; + + printf(" %s", COLOR_WHITE); + va_start(ap, format); + vprintf(format, ap); + va_end(ap); + printf("%s\n", COLOR_OFF); +} + +void tester_warn(const char *format, ...) +{ + va_list ap; + + printf(" %s", COLOR_WHITE); + va_start(ap, format); + vprintf(format, ap); + va_end(ap); + printf("%s\n", COLOR_OFF); +} + +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) +{ + struct test_case *test; + + if (!test_func) + return; + + test = g_new0(struct test_case, 1); + + test->name = g_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_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); +} + +static void tester_summarize(void) +{ + unsigned int not_run = 0, passed = 0, failed = 0; + GList *list; + + printf("\n"); + 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; + + 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"); + passed++; + break; + case TEST_RESULT_FAILED: + print_summary(test->name, COLOR_RED, "Failed"); + failed++; + break; + } + } + + printf("\nTotal: %d, " + COLOR_GREEN "Passed: %d (%.1f%%)" COLOR_OFF ", " + COLOR_RED "Failed: %d" COLOR_OFF ", " + COLOR_YELLOW "Not Run: %d" COLOR_OFF "\n", + not_run + passed + failed, passed, + (float) passed * 100 / (not_run + passed + failed), + failed, not_run); + +} + +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_main_loop_quit(main_loop); + return; + } + + test = test_current->data; + + printf("\n"); + print_progress(test->name, COLOR_BLACK, "init"); + + 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 teardown_callback(gpointer user_data) +{ + struct test_case *test = user_data; + + test->stage = TEST_STAGE_TEARDOWN; + + print_progress(test->name, COLOR_MAGENTA, "teardown"); + test->teardown_func(test->test_data); + + return FALSE; +} + +static gboolean done_callback(gpointer user_data) +{ + struct test_case *test = user_data; + + 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; + + test->stage = TEST_STAGE_SETUP; + + tester_setup_failed(); +} + +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; + + print_progress(test->name, COLOR_RED, "setup failed"); + + g_idle_add(done_callback, test); +} + +void tester_test_passed(void) +{ + struct test_case *test; + + if (!test_current) + return; + + test = test_current->data; + + if (test->stage != TEST_STAGE_RUN) + return; + + test->result = TEST_RESULT_PASSED; + print_progress(test->name, COLOR_GREEN, "test passed"); + + g_idle_add(teardown_callback, test); +} + +void tester_test_failed(void) +{ + struct test_case *test; + + if (!test_current) + return; + + test = test_current->data; + + if (test->stage != TEST_STAGE_RUN) + return; + + test->result = TEST_RESULT_FAILED; + print_progress(test->name, COLOR_RED, "test failed"); + + g_idle_add(teardown_callback, test); +} + +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) +{ + 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 sec left", + wait->seconds); + return TRUE; + } + + print_progress(test->name, COLOR_BLACK, "waiting done"); + + wait->func(wait->user_data); + + g_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; + + print_progress(test->name, COLOR_BLACK, "waiting %u sec", seconds); + + wait = g_new0(struct wait_data, 1); + + wait->seconds = seconds; + wait->test = test; + wait->func = func; + wait->user_data = user_data; + + g_timeout_add_seconds(1, wait_callback, wait); +} + +static gboolean signal_handler(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + static unsigned int __terminated = 0; + struct signalfd_siginfo si; + ssize_t result; + int fd; + + if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + g_main_loop_quit(main_loop); + return FALSE; + } + + fd = g_io_channel_unix_get_fd(channel); + + result = read(fd, &si, sizeof(si)); + if (result != sizeof(si)) + return FALSE; + + switch (si.ssi_signo) { + case SIGINT: + case SIGTERM: + if (__terminated == 0) + g_main_loop_quit(main_loop); + + __terminated = 1; + break; + } + + return TRUE; +} + +static guint setup_signalfd(void) +{ + GIOChannel *channel; + guint source; + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { + perror("Failed to set signal mask"); + return 0; + } + + fd = signalfd(-1, &mask, 0); + if (fd < 0) { + perror("Failed to create signal descriptor"); + return 0; + } + + channel = g_io_channel_unix_new(fd); + + g_io_channel_set_close_on_unref(channel, TRUE); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + source = g_io_add_watch(channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + signal_handler, NULL); + + g_io_channel_unref(channel); + + return source; +} + +static gboolean option_version = FALSE; +static gboolean option_quiet = FALSE; +static gboolean option_debug = FALSE; + +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" }, + { 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); + } + + main_loop = g_main_loop_new(NULL, FALSE); + + test_list = NULL; + test_current = NULL; +} + +int tester_run(void) +{ + guint signal; + + if (!main_loop) + return EXIT_FAILURE; + + signal = setup_signalfd(); + + g_idle_add(start_tester, NULL); + g_main_loop_run(main_loop); + + g_source_remove(signal); + + g_main_loop_unref(main_loop); + + tester_summarize(); + + g_list_free_full(test_list, test_destroy); + + return EXIT_SUCCESS; +} diff --git a/src/shared/tester.h b/src/shared/tester.h new file mode 100644 index 000000000..7c233406a --- /dev/null +++ b/src/shared/tester.h @@ -0,0 +1,68 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +void tester_init(int *argc, char ***argv); +int tester_run(void); + +bool tester_use_quiet(void); +bool tester_use_debug(void); + +void tester_print(const char *format, ...) + __attribute__((format(printf, 1, 2))); +void tester_warn(const char *format, ...) + __attribute__((format(printf, 1, 2))); + +typedef void (*tester_data_func_t)(const void *test_data); + +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); +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); + +void tester_pre_setup_complete(void); +void tester_pre_setup_failed(void); + +void tester_setup_complete(void); +void tester_setup_failed(void); + +void tester_test_passed(void); +void tester_test_failed(void); + +void tester_teardown_complete(void); +void tester_teardown_failed(void); + +void tester_post_teardown_complete(void); +void tester_post_teardown_failed(void); + +typedef void (*tester_wait_func_t)(void *user_data); + +void tester_wait(unsigned int seconds, tester_wait_func_t func, + void *user_data); -- 2.47.3