From 63769c9c0b62e5c58a44ec62668f71f28084bdb0 Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Wed, 19 Aug 2015 17:15:46 -0400 Subject: [PATCH] Convert unit test framework to C I commented out most of the file and directory modification code while I was at it. I'll re-add these as I convert more files to C. Signed-off-by: Anna Schumaker --- Sconstruct | 1 + include/tests/test.h | 109 +++++++++++++++++------- tests/Sconscript | 77 ++++------------- tests/sanity.c | 105 +++++++++++++++++++++++ tests/sanity.cpp | 52 ------------ tests/test.c | 196 +++++++++++++++++++++++++++++++++++++++++++ tests/test.cpp | 137 ------------------------------ 7 files changed, 398 insertions(+), 279 deletions(-) create mode 100644 tests/sanity.c delete mode 100644 tests/sanity.cpp create mode 100644 tests/test.c delete mode 100644 tests/test.cpp diff --git a/Sconstruct b/Sconstruct index 9d464c34..cd7f6b82 100644 --- a/Sconstruct +++ b/Sconstruct @@ -15,6 +15,7 @@ class OEnvironment(Environment): Environment.__init__(self, CCFLAGS = CCFLAGS) self.Append(CPPPATH = os.path.abspath("include")) self.Append(CXXCOMSTR = "C++ $TARGET") + self.Append(CCCOMSTR = "CC $TARGET") self.Append(LINKCOMSTR = "Linking $TARGET") self.Debug = CONFIG_DEBUG self.Version = CONFIG_VERSION diff --git a/include/tests/test.h b/include/tests/test.h index 2bec7e36..abef0ec6 100644 --- a/include/tests/test.h +++ b/include/tests/test.h @@ -4,46 +4,93 @@ #ifndef OCARINA_TESTS_TEST_H #define OCARINA_TESTS_TEST_H -#include +#include +#include +#include +#include +#include +#include -namespace test -{ - extern unsigned int failed; - void run(const std::string &, void (*)()); +#ifndef __cplusplus +static inline gchar *stos(const char *s) { return g_strdup(s); } +static inline gchar *utos(unsigned int u) { return g_strdup_printf("%u", u); } +static inline gchar *itos(int i) { return g_strdup_printf("%i", i); } +static inline gchar *btos(bool b) { return g_strdup(b ? "true" : "false"); } +static inline gchar *ptos(void *p) { return g_strdup_printf("%p", p); } - void equal(const int, const int, unsigned int); - void equal(const std::string &, const std::string &, unsigned int); - void equal(const void *, const void *, unsigned int); - void not_equal(const int, const int, unsigned int); - void not_equal(const std::string &, const std::string &, unsigned int); - void not_equal(const void *, const void *, unsigned int); +#define tostring(x) (_Generic((x), \ + char *: stos, \ + const char *: stos, \ + bool: btos, \ + unsigned int: utos, \ + int: itos, \ + void *:ptos \ + ) (x)) +#endif /* __cplusplus */ - void for_each(unsigned int, unsigned int, unsigned int, - unsigned int (*)(unsigned int), unsigned int); - std::string data_dir(); - std::string data_file(const std::string &name); - bool data_dir_exists(); - bool data_file_exists(const std::string &name); - void rm_data_dir(); - void reset_data_dir(); - void cp_data_dir(); - void gen_library(); - void rm_library_dirs(); -} +struct UnitTest { + const char *t_name; + void (*t_func)(); +}; -#define test_equal(lhs, rhs) \ - test :: equal(lhs, rhs, __LINE__) +extern struct UnitTest unit_tests[]; +extern unsigned int unit_tests_size;; -#define test_not_equal(lhs, rhs) \ - test :: not_equal(lhs, rhs, __LINE__) -#define test_for_each(init, max, inc, func) \ - test :: for_each(init, max, inc, func, __LINE__) +#define UNIT_TEST(name, func) \ + [__COUNTER__] = { \ + .t_name = name, \ + .t_func = func, \ + } -#define LOOP_PASSED 0 -#define LOOP_FAILED __LINE__ + +#define DECLARE_UNIT_TESTS(...) \ + struct UnitTest unit_tests[] = {\ + __VA_ARGS__ \ + }; \ + unsigned int unit_tests_size = __COUNTER__ + + +extern unsigned int tests_failed; +void test_strings_equal(gchar *, gchar *, unsigned int); +void test_strings_not_equal(gchar *, gchar *, unsigned int); +void loop_strings_equal(gchar *, gchar *, unsigned int, unsigned int); +void loop_strings_not_equal(gchar *, gchar *, unsigned int, unsigned int); + + +#define test_equal(lhs, rhs) \ + test_strings_equal(tostring(lhs), tostring(rhs), __LINE__) + +#define test_not_equal(lhs, rhs) \ + test_strings_not_equal(tostring(lhs), tostring(rhs), __LINE__) + +#define test_loop_equal(lhs, rhs, i) \ + if (1) { \ + loop_strings_equal(tostring(lhs), tostring(rhs), i, __LINE__); \ + if (tests_failed > 0) \ + break; \ + } + +#define test_loop_not_equal(lhs, rhs, i) \ + if (1) { \ + loop_strings_not_equal(tostring(lhs), tostring(rhs), i, __LINE__); \ + if (tests_failed > 0) \ + break; \ + } + +#define test_loop_passed() \ + if (tests_failed == 0) \ + test_equal(tests_failed, 0) + + +void test_cp_data_dir(); +void test_rm_data_dir(); +gchar *test_data_file(const gchar *); +bool test_data_file_exists(const gchar *); +void test_generate_library(); +void test_rm_library_dirs(); #endif /* OCARINA_TESTS_TEST_H */ diff --git a/tests/Sconscript b/tests/Sconscript index 8032836f..57274e83 100644 --- a/tests/Sconscript +++ b/tests/Sconscript @@ -3,75 +3,34 @@ import sys, os Import("env") tests = [] - +check_sanity = False for arg in sys.argv[1:]: if arg.find("tests") == 0: tests += [ arg ] + if arg in set(["tests", "tests/", "tests/sanity"]): + check_sanity = True if len(tests) == 0: Return() -env.Append(CCFLAGS = "-DCONFIG_TEST") +env.Append(CCFLAGS = "-DCONFIG_TESTING") env.UsePackage("glib-2.0") +test_o = env.Object("test", "test.c") -class UnitTest: - def __init__(self, test, sources): - name = os.path.basename(test) - make = env.Program(name, sources) - run = Command("%s.fake" % name, [], "tests/%s" % test) - Depends(run, make) - Alias("tests/%s" % test, run) - -res = UnitTest("sanity", [ "sanity.cpp", "test.cpp" ]) - -Return("res") - - - +def UnitTest(test, sources): + name = os.path.basename(test) + make = env.Program(name, sources + [ test_o ]) + run = Command("%s.fake" % name, [], "tests/%s" % test) + Depends(run, make) + Alias("tests/%s" % test, run) + return run +Export("UnitTest") res = [] -def all_tests_enabled(dir): - for arg in sys.argv[1:]: - arg = os.path.normpath(arg) - name = os.path.basename(arg) - if (arg.find("tests") == 0) and (name == "tests"): - return True - if (arg.find("tests/%s" % dir) == 0) and (name == dir): - return True - return False - -def add_test(test, dir): - global res - if (all_tests_enabled(dir) == True) and (len(res) > 0): - Depends(test, res[-1]) - res += [ test ] - -def get_test_obj(name, dir): - src = "../../%s/%s.cpp" % (dir, name) - if os.path.exists(src): - return test_env.Object("%s.cpp-%s" % (name, dir), src) - return None - -def generic_test(name, dir, objs, extra): - global test_obj; - obj = get_test_obj(name, dir) - test_objs = extra - if obj != None: - objs += [ obj ] - test_objs = extra + objs - - exe = test_env.Program(name, [ "%s.cpp" % name, test_lib ] + test_objs) - test = Command("%s.fake" % name, [], "tests/%s/%s" % (dir, name)) - - Alias("tests/%s/%s" % (dir, name), test) - Depends(test, exe) - add_test(test, dir) - return objs - - -Export("get_test_obj", "generic_test") - - -#SConscript("core/Sconscript") +#res = SConscript("core/Sconscript") +if check_sanity == True: + res = [ UnitTest("sanity", [ "sanity.c" ]) ] + res + for t in res[1:]: + Depends(t, res[0]) Return("res") diff --git a/tests/sanity.c b/tests/sanity.c new file mode 100644 index 00000000..e7b8b38e --- /dev/null +++ b/tests/sanity.c @@ -0,0 +1,105 @@ +/* + * Copyright 2015 (c) Anna Schumaker. + */ +#include +#include + +#ifdef CONFIG_TESTING +static const char *str = "Enabled"; +#else /* CONFIG_TESTING */ +static const char *str = "Disabled"; +#endif /* CONFIG_TESTING */ + +static unsigned int counter = 0; +static unsigned int inc_counter() { return counter++; } + +static void test_passing_tests() +{ + unsigned int i; + + test_equal(0, 0); + test_equal("2", "2"); + test_equal((void *)4, (void *)4); + test_equal(str, "Enabled"); + test_equal(inc_counter(), 0); + test_equal((bool)true, (bool)true); + + for (i = 0; i < 10; i++) + test_loop_equal(i, i, i); + test_loop_passed(); + + test_not_equal(0, 1); + test_not_equal("2", "3"); + test_not_equal((void *)4, (void *)5); + test_not_equal(str, "Disabled"); + test_not_equal(inc_counter(), 2); + test_not_equal((bool)true, (bool)false); + + for (i = 0; i < 10; i++) + test_loop_not_equal(i, i + 1, i); + test_loop_passed(); +} + +static void test_failing_tests() +{ + unsigned int i; + + test_equal(0, 1); + test_equal("2", "3"); + test_equal((void *)4, (void *)5); + test_equal(str, "Disabled"); + test_equal(inc_counter(), 3); + test_equal((bool)true, (bool)false); + + for (i = 0; i < 10; i++) + test_loop_equal(i, i + 1, i); + test_loop_passed(); + + test_not_equal(0, 0); + test_not_equal("2", "2"); + test_not_equal((void *)4, (void *)4); + test_not_equal(str, "Enabled"); + test_not_equal(inc_counter(), 3); + test_not_equal((bool)true, (bool)true); + + for (i = 0; i < 10; i++) + test_loop_not_equal(i, i, i); + test_loop_passed(); + tests_failed = 0; +} + +static void test_utilities() +{ + gchar *dir = test_data_file(NULL); + gchar *file = test_data_file("sanity"); + const gchar *data = g_get_user_data_dir(); + gchar *ddata = g_strjoin("/", data, "ocarina-test", NULL); + gchar *fdata = g_strjoin("/", data, "ocarina-test", "sanity", NULL); + + test_equal(dir, ddata); + test_equal(file, fdata); + test_equal(test_data_file_exists(NULL), (bool)false); + test_equal(test_data_file_exists("sanity"), (bool)false); + + test_cp_data_dir(); + test_equal(test_data_file_exists(NULL), (bool)true); + test_equal(test_data_file_exists("artist.db"), (bool)true); + test_equal(test_data_file_exists("cur_track"), (bool)true); + test_equal(test_data_file_exists("library.db"), (bool)true); + test_equal(test_data_file_exists("genre.db"), (bool)true); + test_equal(test_data_file_exists("playlist.db"), (bool)true); + test_equal(test_data_file_exists("track.db"), (bool)true); + test_equal(test_data_file_exists("album.db"), (bool)true); + test_equal(test_data_file_exists("deck"), (bool)true); + + g_free(dir); + g_free(file); + g_free(ddata); + g_free(fdata); +} + +DECLARE_UNIT_TESTS( + UNIT_TEST("Sanity Check", test_passing_tests), + UNIT_TEST("Sanity Check (All should fail)", test_failing_tests), + UNIT_TEST("Sanity Check Utility Functions", test_utilities), +); diff --git a/tests/sanity.cpp b/tests/sanity.cpp deleted file mode 100644 index 0de695d1..00000000 --- a/tests/sanity.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2015 (c) Anna Schumaker. - */ -#include -#include - -static unsigned int expected_failures = 0; - -#define should_fail(code) \ - code; \ - expected_failures++ - -static unsigned int test_for_each_body(unsigned int i) -{ - if (i > 10) - return LOOP_FAILED; - return LOOP_PASSED; -} - -static void test_tests() -{ - test_equal(0, 0); - test_equal((std::string)"2", (std::string)"2"); - test_equal((void *)4, (void *)4); - - should_fail(test_equal(0, 1)); - should_fail(test_equal((std::string)"2", "3")); - should_fail(test_equal((void *)4, (void *)5)); - - - test_not_equal(0, 1); - test_not_equal((std::string)"2", "3"); - test_not_equal((void *)4, (void *)5); - - should_fail(test_not_equal(0, 0)); - should_fail(test_not_equal((std::string)"2", "2")); - should_fail(test_not_equal((void *)4, (void *)4)); - - - test_for_each(0, 10, 1, test_for_each_body); - should_fail(test_for_each(0, 15, 1, test_for_each_body)); - - - test_equal(test :: failed, expected_failures); - test :: failed -= expected_failures; -} - -int main(int argc, char **argv) -{ - test :: run("Testing Sanity Check", test_tests); - return 0; -} diff --git a/tests/test.c b/tests/test.c new file mode 100644 index 00000000..fb8263c4 --- /dev/null +++ b/tests/test.c @@ -0,0 +1,196 @@ +/* + * Copyright 2015 (c) Anna Schumaker. + */ +#include +#include +#include +#include + + +static unsigned int test_num; +unsigned int tests_failed = 0; + + +static void test_print_result(bool passed, unsigned int line) +{ + printf("\t%u: ", test_num++); + if (passed) + printf("Success!\n"); + else { + printf("Failed at line %u.\n", line); + tests_failed++; + } +} + +static void test_loop_print_result(bool passed, unsigned int i, + unsigned int line) +{ + if (!passed) { + printf("\t%u: ", test_num++); + printf("Failed loop at line: %u (i = %u)\n", line, i); + tests_failed++; + } +} + +/* + * lhs and rhs should be newly allocated strings so we can + * free them at the end of this function. + */ +void test_strings_equal(gchar *lhs, gchar *rhs, unsigned int line) +{ + bool passed = strcmp(lhs, rhs) == 0; + + test_print_result(passed, line); + if (!passed) { + printf("\t\t Actual: %s\n", lhs); + printf("\t\tExpected: %s\n", rhs); + } + g_free(lhs); + g_free(rhs); +} + +void test_strings_not_equal(gchar *lhs, gchar *rhs, unsigned int line) +{ + bool passed = strcmp(lhs, rhs) != 0; + + test_print_result(passed, line); + if (!passed) + printf("\t\tUnexpected: %s\n", lhs); + g_free(lhs); + g_free(rhs); +} + +void loop_strings_equal(gchar *lhs, gchar *rhs, unsigned int i, + unsigned int line) +{ + bool passed = strcmp(lhs, rhs) == 0; + + test_loop_print_result(passed, i, line); + if (!passed) { + printf("\t\t Actual: %s\n", lhs); + printf("\t\tExpected: %s\n", rhs); + } + + g_free(lhs); + g_free(rhs); +} + +void loop_strings_not_equal(gchar *lhs, gchar *rhs, unsigned int i, + unsigned int line) +{ + bool passed = strcmp(lhs, rhs) != 0; + + test_loop_print_result(passed, i, line); + if (!passed) + printf("\t\tUnexpected: %s\n", lhs); + + g_free(lhs); + g_free(rhs); +} + +static void run_test(struct UnitTest *test) +{ + test_num = 0; + + printf("Testing %s\n", test->t_name); + test->t_func(); + printf("\n"); + + if (tests_failed > 0) + printf("\n%u tests failed =(\n\n", tests_failed); +} + +gchar *test_data_file(const gchar *name) +{ + return g_strjoin("/", g_get_user_data_dir(), "ocarina-test", name, NULL); +} + +bool test_data_file_exists(const gchar *name) +{ + GFileTest test = G_FILE_TEST_EXISTS; + gchar *dir; + bool ret; + + if (!name) + test = G_FILE_TEST_IS_DIR; + + dir = g_strjoin("/", g_get_user_data_dir(), "ocarina-test", name, NULL); + ret = g_file_test(dir, test); + g_free(dir); + return ret; +} + +void test_cp_data_dir() +{ + const gchar *file = NULL; + gchar *dst_dir = test_data_file(NULL); + GDir *dir = g_dir_open("tests/Data", 0, NULL); + + if (!dir) + goto out; + g_mkdir_with_parents(dst_dir, 0755); + + /* TODO: This could be a good place to use vfs_copy_file_range() */ + while((file = g_dir_read_name(dir)) != NULL) { + gchar *orig = g_strjoin("/", "tests/Data", file, NULL); + gchar *dst = g_strjoin("/", dst_dir, file, NULL); + gchar *cmd = g_strjoin(" ", "cp", orig, dst, NULL); + + system(cmd); + + g_free(orig); + g_free(dst); + g_free(cmd); + } + + g_dir_close(dir); +out: + g_free(dst_dir); +} + +void test_rm_data_dir() +{ + const gchar *file = NULL; + gchar *path = test_data_file(NULL); + GDir *dir = g_dir_open(path, 0, NULL); + + if (!dir) + goto out; + + while ((file = g_dir_read_name(dir)) != NULL) { + gchar *fullpath = g_strjoin("/", path, file, NULL); + g_remove(fullpath); + g_free(fullpath); + } + + g_dir_close(dir); + g_rmdir(path); +out: + g_free(path); +} + +void test_generate_library() +{ + system("tests/gen_library.sh"); +} + +void test_rm_library_dirs() +{ + system("rm -r /tmp/ocarina/dir2 /tmp/ocarina/dir4"); +} + +int main(int argc, char **argv) +{ + unsigned int i; + + if (test_data_file_exists(NULL)) + test_rm_data_dir(); + + for (i = 0; i < unit_tests_size; i++) { + run_test(&unit_tests[i]); + if (tests_failed != 0) + break; + } + printf("\n"); + return tests_failed; +} diff --git a/tests/test.cpp b/tests/test.cpp deleted file mode 100644 index 3dca4b2b..00000000 --- a/tests/test.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2015 (c) Anna Schumaker. - */ -#include - -#include -#include -#include - -static unsigned int test_num; -unsigned int test :: failed; - - -template -static void generic_equal(const T &a, const T &b, const unsigned int line) -{ - std::cout << " " << test_num++ << ": "; - if (a == b) - std::cout << "Success!" << std::endl; - else { - std::cout << "Failed at line " << line << ":" << std::endl; - std::cout << " Actual: " << a << std::endl; - std::cout << " Expected: " << b << std::endl; - test :: failed++; - } -} - -template -static void generic_not_equal(const T &a, const T &b, const unsigned int line) -{ - std::cout << " " << test_num++ << ": "; - if (a != b) - std::cout << "Success!" << std::endl; - else { - std::cout << "Failed at line " << line << ":" << std::endl; - std::cout << " Unexpected: " << a << std::endl; - test :: failed++; - } -} - - -void test :: run(const std::string &name, void (*func)()) -{ - failed = 0; - test_num = 0; - std::cout << name << std::endl; - - func(); - - std::cout << std::endl; - if (failed > 0) { - std::cout << failed << " tests failed =(" << std::endl << std::endl; - exit(failed); - } -} - -void test :: for_each(unsigned int init, unsigned int max, unsigned int inc, - unsigned int (*func)(unsigned int), unsigned int line) -{ - std::cout << " " << test_num++ << ": "; - for (unsigned int i = init; i < max; i += inc) { - int ret = func(i); - if (ret != 0) { - std::cout << "Failed loop at line: " << ret; - std::cout << " (i = " << i << ")" << std::endl; - std::cout << " Called from line: " << line << std::endl; - test :: failed++; - return; - } - } - std::cout << "Success!" << std::endl; -} - -#define DEFINE_COMPARE_FUNC(name, type) \ - void test :: name(const type a, const type b, unsigned int line) \ - { generic_##name(a, b, line); } - -#define DEFINE_COMPARE(type) \ - DEFINE_COMPARE_FUNC(equal, type) \ - DEFINE_COMPARE_FUNC(not_equal, type) - -DEFINE_COMPARE(int) -DEFINE_COMPARE(std::string &) -DEFINE_COMPARE(void *) - -std::string test :: data_dir() -{ - std::string res = g_get_user_data_dir(); - res += "/ocarina-test"; - return res; -} - -std::string test :: data_file(const std::string &name) -{ - return data_dir() + "/" + name; -} - -bool test :: data_dir_exists() -{ - return g_file_test(data_dir().c_str(), G_FILE_TEST_IS_DIR); -} - -bool test :: data_file_exists(const std::string &name) -{ - return g_file_test(data_file(name).c_str(), G_FILE_TEST_EXISTS); -} - -void test :: rm_data_dir() -{ - std::string cmd = "rm -r " + data_dir() + " 2>/dev/null"; - if (data_dir_exists()) - system(cmd.c_str()); -} - -void test :: reset_data_dir() -{ - std::string cmd = "mkdir -p " + data_dir(); - rm_data_dir(); - system(cmd.c_str()); -} - -void test :: cp_data_dir() -{ - reset_data_dir(); - std::string cmd = "cp -r tests/Data/* " + data_dir(); - system(cmd.c_str()); -} - -void test :: gen_library() -{ - system("tests/gen_library.sh"); -} - -void test :: rm_library_dirs() -{ - system("rm -r /tmp/ocarina/dir2 /tmp/ocarina/dir4"); -}