Signed-off-by: Bartosz Golaszewski <bartekg...@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 <bartekg...@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 <bartekg...@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

_______________________________________________
busybox mailing list
busybox@busybox.net
http://lists.busybox.net/mailman/listinfo/busybox

Reply via email to