[PATCH 2/6] unit-tests: implement the unit-testing framework

Bartosz Golaszewski bartekgola at gmail.com
Tue Apr 15 23:06:35 UTC 2014


Signed-off-by: Bartosz Golaszewski <bartekgola at gmail.com>
---
 Config.in        |   8 +++
 include/bbunit.h | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 libbb/bbunit.c   | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 315 insertions(+)
 create mode 100644 include/bbunit.h
 create mode 100644 libbb/bbunit.c

diff --git a/Config.in b/Config.in
index b6eaea5..b83beb5 100644
--- a/Config.in
+++ b/Config.in
@@ -675,6 +675,14 @@ config DEBUG_PESSIMIZE
 	  in a much bigger executable that more closely matches the source
 	  code.
 
+config UNIT_TEST
+	bool "Build unit tests"
+	default n
+	help
+	  Say Y here if you want to build unit tests (both the framework and
+	  test cases) as a Busybox applet. This results in bigger code, so you
+	  probably don't want this option in production builds.
+
 config WERROR
 	bool "Abort compilation on any warning"
 	default n
diff --git a/include/bbunit.h b/include/bbunit.h
new file mode 100644
index 0000000..5861ec2
--- /dev/null
+++ b/include/bbunit.h
@@ -0,0 +1,153 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bbunit: Simple unit-testing framework for Busybox.
+ *
+ * Copyright (C) 2014 by Bartosz Golaszewski <bartekgola at gmail.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+#ifndef BBUNIT_H
+#define BBUNIT_H
+
+#include "libbb.h"
+
+void bbunit_print(const char* fmt, ...) PRINTF_FUNC(1, 2);
+void bbunit_printerr(const char* fmt, ...) PRINTF_FUNC(1, 2);
+
+typedef void (*bbunit_testfunc)(void);
+
+struct bbunit_listelem
+{
+	struct bbunit_listelem* next;
+	const char* name;
+	bbunit_testfunc testfunc;
+};
+
+void __bbunit_registertest(struct bbunit_listelem* test);
+void __bbunit_settestfailed(void);
+
+#define BBUNIT_DEFINE_TEST(NAME) \
+	static void __##NAME##_test(void); \
+	static struct bbunit_listelem __##NAME##_elem = { \
+		.name = #NAME, \
+		.testfunc = __##NAME##_test, \
+	}; \
+	static void INIT_LAST __##NAME##_register(void) \
+	{ \
+		__bbunit_registertest(&__##NAME##_elem); \
+	} \
+	static void __##NAME##_test(void)
+
+/*
+ * Both 'goto __finally' and 'break' are here only to get rid
+ * of compiler warnings.
+ */
+#define BBUNIT_ENDTEST \
+	do { \
+		goto __finally; \
+		__finally: \
+			break; \
+	} while (0)
+
+#define BBUNIT_PRINTASSERTFAIL \
+	do { \
+		bbunit_printerr( \
+			"Assertion failed in file %s, line %d!", \
+			__FILE__, __LINE__); \
+	} while (0)
+
+#define BBUNIT_ASSERTION_FAILED \
+	do { \
+		__bbunit_settestfailed(); \
+		goto __finally; \
+	} while (0)
+
+/*
+ * Assertions.
+ *
+ * For now we only offer assertions which cause tests to fail
+ * immediately. In the future 'expects' might be added too -
+ * similar to those offered by the gtest framework.
+ */
+
+#define BBUNIT_ASSERT_EQ(EXPECTED, ACTUAL) \
+	do { \
+		if ((EXPECTED) != (ACTUAL)) { \
+			BBUNIT_PRINTASSERTFAIL; \
+			bbunit_printerr("'%s' isn't equal to '%s'", \
+						#EXPECTED, #ACTUAL); \
+			BBUNIT_ASSERTION_FAILED; \
+		} \
+	} while (0)
+
+#define BBUNIT_ASSERT_NOTEQ(EXPECTED, ACTUAL) \
+	do { \
+		if ((EXPECTED) == (ACTUAL)) { \
+			BBUNIT_PRINTASSERTFAIL; \
+			bbunit_printerr("'%s' is equal to '%s'", \
+						#EXPECTED, #ACTUAL); \
+			BBUNIT_ASSERTION_FAILED; \
+		} \
+	} while (0)
+
+#define BBUNIT_ASSERT_NOTNULL(PTR) \
+	do { \
+		if ((PTR) == NULL) { \
+			BBUNIT_PRINTASSERTFAIL; \
+			bbunit_printerr("'%s' is NULL!", #PTR); \
+			BBUNIT_ASSERTION_FAILED; \
+		} \
+	} while (0)
+
+#define BBUNIT_ASSERT_NULL(PTR) \
+	do { \
+		if ((PTR) != NULL) { \
+			BBUNIT_PRINTASSERTFAIL; \
+			bbunit_printerr("'%s' is not NULL!", #PTR); \
+			BBUNIT_ASSERTION_FAILED; \
+		} \
+	} while (0)
+
+#define BBUNIT_ASSERT_FALSE(STATEMENT) \
+	do { \
+		if ((STATEMENT)) { \
+			BBUNIT_PRINTASSERTFAIL; \
+			bbunit_printerr("Statement '%s' evaluated to true!", \
+								#STATEMENT); \
+			BBUNIT_ASSERTION_FAILED; \
+		} \
+	} while (0)
+
+#define BBUNIT_ASSERT_TRUE(STATEMENT) \
+	do { \
+		if (!(STATEMENT)) { \
+			BBUNIT_PRINTASSERTFAIL; \
+			bbunit_printerr("Statement '%s' evaluated to false!", \
+					#STATEMENT); \
+			BBUNIT_ASSERTION_FAILED; \
+		} \
+	} while (0)
+
+#define BBUNIT_ASSERT_STREQ(STR1, STR2) \
+	do { \
+		if (strcmp(STR1, STR2) != 0) { \
+			BBUNIT_PRINTASSERTFAIL; \
+			bbunit_printerr("Strings '%s' and '%s' " \
+					"are not the same", STR1, STR2); \
+			BBUNIT_ASSERTION_FAILED; \
+		} \
+	} while (0)
+
+#define BBUNIT_ASSERT_STRNOTEQ(STR1, STR2) \
+	do { \
+		if (strcmp(STR1, STR2) == 0) { \
+			BBUNIT_PRINTASSERTFAIL; \
+			bbunit_printerr("Strings '%s' and '%s' " \
+					"are the same, but were " \
+					"expected to differ", STR1, STR2); \
+			BBUNIT_ASSERTION_FAILED; \
+		} \
+	} while (0)
+
+#endif /* BBUNIT_H */
diff --git a/libbb/bbunit.c b/libbb/bbunit.c
new file mode 100644
index 0000000..368a475
--- /dev/null
+++ b/libbb/bbunit.c
@@ -0,0 +1,154 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bbunit: Simple unit-testing framework for Busybox.
+ *
+ * Copyright (C) 2014 by Bartosz Golaszewski <bartekgola at gmail.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+//kbuild:lib-$(CONFIG_UNIT_TEST) += bbunit.o
+//applet:IF_UNIT_TEST(APPLET(unit, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//usage:#define unit_trivial_usage
+//usage:       ""
+//usage:#define unit_full_usage "\n\n"
+//usage:       "Run the unit-test suite"
+
+#include "libbb.h"
+#include "bbunit.h"
+
+struct testlist
+{
+	struct bbunit_listelem* head;
+	struct bbunit_listelem* tail;
+};
+
+static struct testlist tests;
+static unsigned tests_registered = 0;
+static int test_retval;
+
+static INIT_FIRST void testlist_init(void)
+{
+	tests.head = tests.tail = NULL;
+}
+
+void __bbunit_registertest(struct bbunit_listelem* test)
+{
+	if (tests.tail == NULL) {
+		tests.head = tests.tail = test;
+		test->next = NULL;
+	} else {
+		tests.tail->next = test;
+		test->next = NULL;
+		tests.tail = test;
+	}
+	++tests_registered;
+}
+
+void __bbunit_settestfailed(void)
+{
+	test_retval = -1;
+}
+
+#define PRINT_FROM_VA(STREAM, HDR, FMT) \
+	do { \
+		va_list va; \
+		va_start(va, FMT); \
+		fprintf(STREAM, HDR"\t"); \
+		vfprintf(STREAM, FMT, va); \
+		fprintf(STREAM, "\n"); \
+		va_end(va); \
+	} while (0)
+
+void bbunit_print(const char* fmt, ...)
+{
+	PRINT_FROM_VA(stdout, "[INFO]", fmt);
+}
+
+void bbunit_printerr(const char* fmt, ...)
+{
+	PRINT_FROM_VA(stderr, "[ERROR]\t", fmt);
+}
+
+static void timeval_diff(struct timeval* res,
+				const struct timeval* x,
+				const struct timeval* y)
+{
+	struct timeval tmp;
+
+	tmp.tv_sec = y->tv_sec;
+	tmp.tv_usec = y->tv_usec;
+
+	if (x->tv_usec < tmp.tv_usec) {
+		int nsec = (tmp.tv_usec - x->tv_usec) / 1000000 + 1;
+		tmp.tv_usec -= 1000000 * nsec;
+		tmp.tv_sec += nsec;
+	}
+
+	if (x->tv_usec - tmp.tv_usec > 1000000) {
+		int nsec = (x->tv_usec - tmp.tv_usec) / 1000000;
+		tmp.tv_usec += 1000000 * nsec;
+		tmp.tv_sec -= nsec;
+	}
+
+	res->tv_sec = x->tv_sec - tmp.tv_sec;
+	res->tv_usec = x->tv_usec - tmp.tv_usec;
+}
+
+#define TEST_NUMBER_STR(VAR) ((VAR) == 1 ? "test" : "tests")
+
+int unit_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) MAIN_EXTERNALLY_VISIBLE;
+int unit_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+	struct bbunit_listelem* el;
+	int r;
+	unsigned tests_run = 0;
+	unsigned tests_failed = 0;
+	struct timeval begin;
+	struct timeval end;
+	struct timeval time_spent;
+
+	memset(&begin, 0, sizeof(struct timeval));
+	memset(&end, 0, sizeof(struct timeval));
+	memset(&time_spent, 0, sizeof(struct timeval));
+
+	bbunit_print("##############################");
+	bbunit_print("####> Busybox unit-tests <####");
+	bbunit_print("##############################");
+	bbunit_print("%u %s registered.", tests_registered,
+				TEST_NUMBER_STR(tests_registered));
+	bbunit_print("Running tests...");
+	el = tests.head;
+	gettimeofday(&begin, NULL);
+	while (el != NULL) {
+		bbunit_print("Case:\t[%s]", el->name);
+		test_retval = 0;
+		el->testfunc();
+		if (test_retval < 0) {
+			bbunit_printerr("[%s]: TEST FAILED", el->name);
+			tests_failed++;
+		}
+		++tests_run;
+		el = el->next;
+	}
+	gettimeofday(&end, NULL);
+	timeval_diff(&time_spent, &end, &begin);
+	bbunit_print("All done!");
+	bbunit_print("SUMMARY:");
+	bbunit_print(" %u %s run in %d.%06d seconds.", tests_run,
+			TEST_NUMBER_STR(tests_run), (int)time_spent.tv_sec,
+			(int)time_spent.tv_usec);
+	if (tests_failed > 0) {
+		bbunit_printerr(" %u %s FAILED", tests_failed,
+					TEST_NUMBER_STR(tests_failed));
+		bbunit_printerr(" Failure rate: %.2f%%",
+				((double)tests_failed/tests_run)*100);
+		r = EXIT_FAILURE;
+	} else {
+		bbunit_print(" All tests PASSED!");
+		r = EXIT_SUCCESS;
+	}
+
+	return r;
+}
-- 
1.8.4.5



More information about the busybox mailing list