diff mbox series

[BlueZ,1/3] shared/tester: Create ell-based version of tester code

Message ID 20210206013140.390219-2-inga.stotland@intel.com (mailing list archive)
State New, archived
Headers show
Series Framework for non-interactive mesh test | expand

Commit Message

Stotland, Inga Feb. 6, 2021, 1:31 a.m. UTC
Create a version of tester that uses ell primitives instead of glib:
tester-ell.c. This source is included to generate lishared-ell library.
The original tester.c is built as part of libshared-glib library.
---
 Makefile.am             |   8 +-
 src/shared/tester-ell.c | 887 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 892 insertions(+), 3 deletions(-)
 create mode 100644 src/shared/tester-ell.c

Comments

bluez.test.bot@gmail.com Feb. 6, 2021, 3:35 a.m. UTC | #1
This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=429145

---Test result---

##############################
Test: CheckPatch - FAIL
Output:
shared/tester: Create ell-based version of tester code
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#166: FILE: src/shared/tester-ell.c:118:
+} __attribute__((packed));

WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#171: FILE: src/shared/tester-ell.c:123:
+} __attribute__((packed));

ERROR:INITIALISED_STATIC: do not initialise statics to false
#816: FILE: src/shared/tester-ell.c:768:
+	static bool terminated = false;

- total: 1 errors, 2 warnings, 913 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

"[PATCH] shared/tester: Create ell-based version of tester code" has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.

mesh: Add unit test IO
WARNING:SPDX_LICENSE_TAG: Missing or malformed SPDX-License-Identifier tag in line 1
#126: FILE: mesh/mesh-io-unit.c:1:
+/*

WARNING:SPDX_LICENSE_TAG: Missing or malformed SPDX-License-Identifier tag in line 1
#674: FILE: mesh/mesh-io-unit.h:1:
+/*

- total: 0 errors, 2 warnings, 686 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

"[PATCH] mesh: Add unit test IO" has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.


##############################
Test: CheckGitLint - PASS

##############################
Test: CheckBuild - PASS

##############################
Test: MakeCheck - PASS



---
Regards,
Linux Bluetooth
diff mbox series

Patch

diff --git a/Makefile.am b/Makefile.am
index d0f979586..5fa9706c8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -189,7 +189,7 @@  shared_sources = src/shared/io.h src/shared/timeout.h \
 			src/shared/crypto.h src/shared/crypto.c \
 			src/shared/ecc.h src/shared/ecc.c \
 			src/shared/ringbuf.h src/shared/ringbuf.c \
-			src/shared/tester.h src/shared/tester.c \
+			src/shared/tester.h\
 			src/shared/hci.h src/shared/hci.c \
 			src/shared/hci-crypto.h src/shared/hci-crypto.c \
 			src/shared/hfp.h src/shared/hfp.c \
@@ -216,7 +216,8 @@  src_libshared_glib_la_SOURCES = $(shared_sources) \
 				src/shared/timeout-glib.c \
 				src/shared/mainloop-glib.c \
 				src/shared/mainloop-notify.h \
-				src/shared/mainloop-notify.c
+				src/shared/mainloop-notify.c \
+				src/shared/tester.c
 
 src_libshared_mainloop_la_SOURCES = $(shared_sources) \
 				src/shared/io-mainloop.c \
@@ -230,7 +231,8 @@  src_libshared_ell_la_SOURCES = $(shared_sources) \
 				src/shared/io-ell.c \
 				src/shared/timeout-ell.c \
 				src/shared/mainloop.h \
-				src/shared/mainloop-ell.c
+				src/shared/mainloop-ell.c \
+				src/shared/tester-ell.c
 endif
 
 attrib_sources = attrib/att.h attrib/att-database.h attrib/att.c \
diff --git a/src/shared/tester-ell.c b/src/shared/tester-ell.c
new file mode 100644
index 000000000..6b2afa689
--- /dev/null
+++ b/src/shared/tester-ell.c
@@ -0,0 +1,887 @@ 
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2012-2014, 2021  Intel Corporation. All rights reserved.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <getopt.h>
+#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 <sys/time.h>
+
+#include <ell/ell.h>
+
+#include "lib/bluetooth.h"
+#include "lib/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/tester.h"
+#include "src/shared/log.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;
+	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;
+	double start_time;
+	double end_time;
+	unsigned int timeout;
+	struct l_timeout *run_timer;
+	tester_destroy_func_t destroy;
+	void *user_data;
+	bool teardown;
+};
+
+static char *tester_name;
+
+static struct l_queue *test_list;
+static const struct l_queue_entry *test_entry;
+static struct timeval tester_start;
+
+static bool option_quiet;
+static bool option_debug;
+static bool option_monitor;
+static bool option_list;
+static const char *option_prefix;
+static const char *option_string;
+
+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(void *data)
+{
+	struct test_case *test = data;
+
+	l_timeout_remove(test->run_timer);
+
+	if (test->destroy)
+		test->destroy(test->user_data);
+
+	l_free(test->name);
+	l_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 && !l_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 = l_new(struct test_case, 1);
+	test->name = l_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;
+
+	l_queue_push_tail(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);
+}
+
+void *tester_get_data(void)
+{
+	struct test_case *test;
+
+	if (!test_entry)
+		return NULL;
+
+	test = test_entry->data;
+
+	return test->user_data;
+}
+
+static double get_elapsed_time(struct timeval *base)
+{
+	static struct timeval now, elapsed;
+
+	gettimeofday(&now, NULL);
+	timersub(&now, base, &elapsed);
+
+	return elapsed.tv_sec + ((double) elapsed.tv_usec) / 1000000;
+}
+
+static int tester_summarize(void)
+{
+	unsigned int not_run = 0, passed = 0, failed = 0;
+	double execution_time;
+	const struct l_queue_entry *entry;
+
+	tester_log("");
+	print_text(COLOR_HIGHLIGHT, "");
+	print_text(COLOR_HIGHLIGHT, "Test Summary");
+	print_text(COLOR_HIGHLIGHT, "------------");
+
+	entry = l_queue_get_entries(test_list);
+
+	for (; entry; entry = entry->next) {
+		struct test_case *test = entry->data;
+		double 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 = get_elapsed_time(&tester_start);
+	tester_log("Overall execution time: %.3g seconds", execution_time);
+
+	return failed;
+}
+
+static void teardown_callback(void *user_data)
+{
+	struct test_case *test = user_data;
+
+	test->stage = TEST_STAGE_TEARDOWN;
+	test->teardown = false;
+
+	print_progress(test->name, COLOR_MAGENTA, "teardown");
+	test->teardown_func(test->test_data);
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+	VALGRIND_DO_ADDED_LEAK_CHECK;
+#endif
+}
+
+static void test_timeout(struct l_timeout *timer, void *user_data)
+{
+	struct test_case *test = user_data;
+
+	l_timeout_remove(timer);
+	test->run_timer = NULL;
+
+	test->result = TEST_RESULT_TIMED_OUT;
+	print_progress(test->name, COLOR_RED, "test timed out");
+
+	l_idle_oneshot(teardown_callback, test, NULL);
+}
+
+static void next_test_case(void)
+{
+	struct test_case *test;
+
+	if (test_entry)
+		test_entry = test_entry->next;
+	else
+		test_entry = l_queue_get_entries(test_list);
+
+	if (!test_entry) {
+		mainloop_quit();
+		return;
+	}
+
+	test = test_entry->data;
+
+	tester_log("");
+	print_progress(test->name, COLOR_BLACK, "init");
+
+	test->start_time = get_elapsed_time(&tester_start);
+
+	if (test->timeout > 0)
+		test->run_timer = l_timeout_create(test->timeout, test_timeout,
+								test, NULL);
+
+	test->stage = TEST_STAGE_PRE_SETUP;
+
+	test->pre_setup_func(test->test_data);
+}
+
+static void setup_callback(void *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);
+}
+
+static void run_callback(void *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);
+}
+
+static void done_callback(void *user_data)
+{
+	struct test_case *test = user_data;
+
+	test->end_time = get_elapsed_time(&tester_start);
+
+	print_progress(test->name, COLOR_BLACK, "done");
+	next_test_case();
+}
+
+void tester_pre_setup_complete(void)
+{
+	struct test_case *test;
+
+	if (!test_entry)
+		return;
+
+	test = test_entry->data;
+
+	if (test->stage != TEST_STAGE_PRE_SETUP)
+		return;
+
+	l_idle_oneshot(setup_callback, test, NULL);
+}
+
+void tester_pre_setup_failed(void)
+{
+	struct test_case *test;
+
+	if (!test_entry)
+		return;
+
+	test = test_entry->data;
+
+	if (test->stage != TEST_STAGE_PRE_SETUP)
+		return;
+
+	print_progress(test->name, COLOR_RED, "pre setup failed");
+
+	l_idle_oneshot(done_callback, test, NULL);
+}
+
+void tester_setup_complete(void)
+{
+	struct test_case *test;
+
+	if (!test_entry)
+		return;
+
+	test = test_entry->data;
+
+	if (test->stage != TEST_STAGE_SETUP)
+		return;
+
+	print_progress(test->name, COLOR_BLUE, "setup complete");
+
+	l_idle_oneshot(run_callback, test, NULL);
+}
+
+void tester_setup_failed(void)
+{
+	struct test_case *test;
+
+	if (!test_entry)
+		return;
+
+	test = test_entry->data;
+
+	if (test->stage != TEST_STAGE_SETUP)
+		return;
+
+	test->stage = TEST_STAGE_POST_TEARDOWN;
+
+	l_timeout_remove(test->run_timer);
+	test->run_timer = NULL;
+
+	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_entry)
+		return;
+
+	test = test_entry->data;
+
+	if (test->stage != TEST_STAGE_RUN)
+		return;
+
+	l_timeout_remove(test->run_timer);
+	test->run_timer = NULL;
+
+	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)
+		return;
+
+	test->teardown = true;
+
+	l_idle_oneshot(teardown_callback, test, NULL);
+}
+
+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_entry)
+		return;
+
+	test = test_entry->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_entry)
+		return;
+
+	test = test_entry->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_entry)
+		return;
+
+	test = test_entry->data;
+
+	if (test->stage != TEST_STAGE_POST_TEARDOWN)
+		return;
+
+	print_progress(test->name, COLOR_MAGENTA, "teardown complete");
+
+	l_idle_oneshot(done_callback, test, NULL);
+}
+
+void tester_post_teardown_failed(void)
+{
+	struct test_case *test;
+
+	if (!test_entry)
+		return;
+
+	test = test_entry->data;
+
+	if (test->stage != TEST_STAGE_POST_TEARDOWN)
+		return;
+
+	print_progress(test->name, COLOR_RED, "teardown failed");
+
+	l_idle_oneshot(done_callback, test, NULL);
+}
+
+static void start_tester(void *user_data)
+{
+	gettimeofday(&tester_start, NULL);
+	next_test_case();
+}
+
+struct wait_data {
+	unsigned int seconds;
+	struct test_case *test;
+	tester_wait_func_t func;
+	void *user_data;
+};
+
+static void wait_callback(struct l_timeout *timer, void *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;
+	}
+
+	print_progress(test->name, COLOR_BLACK, "waiting done");
+
+	wait->func(wait->user_data);
+
+	free(wait);
+
+	l_timeout_remove(timer);
+}
+
+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_entry)
+		return;
+
+	test = test_entry->data;
+
+	wait = new0(struct wait_data, 1);
+	wait->seconds = seconds;
+	wait->test = test;
+	wait->func = func;
+	wait->user_data = user_data;
+
+	l_timeout_create(seconds, wait_callback, wait, NULL);
+
+	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;
+}
+
+bool tester_use_debug(void)
+{
+	return option_debug;
+}
+
+static const struct option options[] = {
+	{ "version",	no_argument,		NULL, 'v' },
+	{ "quiet",	no_argument,		NULL, 'q' },
+	{ "debug",	no_argument,		NULL, 'd' },
+	{ "monitor",	no_argument,		NULL, 'm' },
+	{ "list",	no_argument,		NULL, 'l' },
+	{ "prefix",	required_argument,	NULL, 'p' },
+	{ "string",	required_argument,	NULL, 's' },
+	{ }
+};
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage:\n"
+		"\%s [options]\n", tester_name);
+	fprintf(stderr,
+		"Options:\n"
+		"\t-v, --version	Show version information and exit\n"
+		"\t-q, --quiet	Run tests without logging\n"
+		"\t-d, --debug	Run tests with debug output\n"
+		"\t-m, --monitor	Enable monitor output\n"
+		"\t-l, --list	Only list the tests to be run\n"
+		"\t-p, --prefix	Run tests matching provided prefix\n"
+		"\t-s, --string	Run tests matching provided string\n");
+}
+
+void tester_init(int *argc, char ***argv)
+{
+	tester_name = strrchr(*argv[0], '/');
+	if (!tester_name)
+		tester_name = strdup(*argv[0]);
+	else
+		tester_name = strdup(++tester_name);
+
+	for (;;) {
+		int opt;
+
+		opt = getopt_long(*argc, *argv, "ps:vqdml", options, NULL);
+		if (opt < 0)
+			break;
+
+		switch (opt) {
+		case 'v':
+			printf("%s\n", VERSION);
+			exit(EXIT_SUCCESS);
+		case 'q':
+			option_quiet = true;
+			break;
+		case 'd':
+			option_debug = true;
+			break;
+		case 'm':
+			option_monitor = true;
+			break;
+		case 'l':
+			option_list = true;
+			break;
+		case 'p':
+			option_prefix = optarg;
+			break;
+		case 's':
+			option_string = optarg;
+			break;
+		default:
+			usage();
+			exit(EXIT_SUCCESS);
+		}
+	}
+
+	mainloop_init();
+
+	test_list = l_queue_new();
+}
+
+int tester_run(void)
+{
+	int ret;
+
+	if (option_list) {
+		mainloop_quit();
+		return EXIT_SUCCESS;
+	}
+
+	l_idle_oneshot(start_tester, NULL, NULL);
+
+	mainloop_run_with_signal(signal_callback, NULL);
+
+	ret = tester_summarize();
+
+	l_queue_destroy(test_list, test_destroy);
+
+	if (option_monitor)
+		bt_log_close();
+
+	return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}