[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