From b8537c9e136c150e8fbf7593fed5a2c3bdb163f0 Mon Sep 17 00:00:00 2001 From: SoraKatadzuma Date: Fri, 21 Aug 2026 06:00:10 -0500 Subject: [PATCH] Initial Commit --- .editorconfig | 9 + .gitignore | 3 + chinookfile | 28 ++ include/malunal/microtest.h | 577 ++++++++++++++++++++++++++++++++++++ tests/entry.c | 2 + tests/general.c | 139 +++++++++ 6 files changed, 758 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 chinookfile create mode 100644 include/malunal/microtest.h create mode 100644 tests/entry.c create mode 100644 tests/general.c diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6c8b36 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2744b01 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.chinook/ +.environ/ +.vscode/ diff --git a/chinookfile b/chinookfile new file mode 100644 index 0000000..be227ec --- /dev/null +++ b/chinookfile @@ -0,0 +1,28 @@ +name: microtest +gpid: malunal +semv: 1.0.0 + +requires: +- remote: git@git.erasit.com:malunal/config + branch: v1.0.2 +- remote: git@git.erasit.com:malunal/types + branch: v1.0.0 + +targets: +- name: malunal.microtest + type: interface + deps: + - malunal.config + - malunal.types + +tests: +- name: malunal.microtest.allinone + type: program + deps: + - malunal.microtest + srcs: + - ./tests/general.c + - ./tests/entry.c + +exports: +- malunal.microtest diff --git a/include/malunal/microtest.h b/include/malunal/microtest.h new file mode 100644 index 0000000..371561d --- /dev/null +++ b/include/malunal/microtest.h @@ -0,0 +1,577 @@ +/** + * @file microtest.h + * @brief Contains the macros, variables, and functions needed to provide a + * minimal testing framework for C99. + * @author John Christman (sorakatadzuma@gmail.com) + * @copyright Malunal Studios, LLC. + * @remarks This library is heavily inspired and written after the Tau testing + * framework. Support the original project. + * @sa https://github.com/jasmcaus/tau + */ +#include +#include +#include +#include +#include "malunal/config.h" +#include "malunal/types/bool.h" +#include "malunal/types/integer.h" +#include "malunal/types/string.h" + +#ifndef MALUNAL_MICROTEST_HEADER +#define MALUNAL_MICROTEST_HEADER + + +/** + * @brief A structure to define a single test suite. + * @details Tests suites are how microtest tracks what tests need execution. + * That is, they are not ignored. It tracks the actual function that + * will be executed and the name of that test as defined by the user. + */ +typedef struct { + /** + * @brief A pointer to a function which runs the test suite. + * @details This should be populated by microtest to be the function which the + * user defines for a given test suite. When the tests are run, this + * is what microtest will actually call. + */ + malunal_void_t (*execute)(); + + /** + * @brief The name of the test suite. + * @details This is also set by microtest based on the suite name and test + * name that the user defines for it. + */ + malunal_str_t name; + + /** + * @brief Whether the test should be ignored. + * @details This is set by microtest when using the @c IGNORE_MICROTEST macro + * to create the test instead of the regular @c MICROTEST macro. + * Test suites that are marked as ignored will not be executed and + * will be indicated in the output log of the runner. + */ + malunal_bool_t ignored; +} microtest_suite_t; + +/** + * @brief Type defines a pointer to a mutable microtest test suite. + * @details This is provided to make function declarations simpler and align the + * naming conventions of other types within our libraries. + */ +typedef microtest_suite_t* microtest_suite_mptr_t; + +/** + * @brief Type defines a pointer to an immutable microtest test suite. + * @details This is provided to make function declarations simpler and align the + * naming conventions of other types within our libraries. + */ +typedef const microtest_suite_t* microtest_suite_iptr_t; + +/** + * @brief A structure to define the microtest state object. + * @details The state object is used to easily track what tests suites are + * contained and need execution. + */ +typedef struct { + /** + * @brief The list of tests to execute. + * @details This will be automatically resized at startup by the registration + * functions, to make sure that microtest finds them all. + */ + microtest_suite_mptr_t tests; + + /** + * @brief The number of tests in the @c tests list. + * @details This will be automatically set at startup by the registration + * functions, to make sure that microtest knows how many there are. + */ + malunal_size_t count; +} microtest_state_t; + +/** + * @brief Type defines a pointer to a mutable microtest state. + * @details This is provided to make function declarations simpler and align the + * naming conventions of other types within our libraries. + */ +typedef microtest_state_t* microtest_state_mptr_t; + +/** + * @brief Type defines a pointer to an immutable microtest state. + * @details This is provided to make function declarations simpler and align the + * naming conventions of other types within our libraries. + */ +typedef const microtest_state_t* microtest_state_iptr_t; + + +static malunal_size_t microtest_ran_count = 0; +static malunal_size_t microtest_failed_count = 0; +static malunal_size_t microtest_skipped_count = 0; +static malunal_size_t* microtest_failed_suites = null; +static malunal_size_t microtest_failed_suites_count = 0; +static malunal_size_t microtest_total_suites_count = 0; + +extern microtest_state_t microtest_context; +extern malunal_size_t microtest_warnings_count; +extern volatile malunal_bool_t microtest_inside_test_suite; +extern volatile malunal_bool_t microtest_current_test_failed; +extern volatile malunal_bool_t microtest_current_test_ignored; +extern volatile malunal_bool_t microtest_should_fail_test; +extern volatile malunal_bool_t microtest_should_abort_test; + + +/** + * @brief Tells microtest that the current test should fail without aborting. + * @details This will cause the test to fail and increment the failure count but + * will not force microtest to abort all the other tests in the suite. + */ +static +malunal_void_t +microtest_fail_inside_test() { + if (!microtest_inside_test_suite) + return; + microtest_current_test_failed = true; + microtest_should_fail_test = true; +} + +/** + * @brief Tells microtest that the current test should fail and abort. + * @details This will cause the test to fail and increment the failure count as + * well as force microtest to abort all the other tests in the suite. + */ +static +malunal_void_t +microtest_abort_inside_test() { + if (!microtest_inside_test_suite) + return; + microtest_current_test_failed = true; + microtest_should_abort_test = true; +} + +/** + * @brief Checks if we should decompose a macro. + * @details This is for when we are inside of macro + */ +static +malunal_bool_t +microtest_should_decompose_macro( + malunal_cstr_t const actual, + malunal_cstr_t const expected, + malunal_bool_t strcmp +) { + malunal_int32_t dots_count = 0; + malunal_int32_t actual_digits_count = 0; + malunal_int32_t expected_digits_count = 0; + + if (!strcmp) { + malunal_size_t index; + for (index = 0; index < strlen(actual); index++) { + if (isdigit(actual[index])) { + actual_digits_count++; + } else if (actual[index] == '.') { + if (++dots_count > 1) + return true; + } else { + return true; + } + } + + dots_count = 0; + for(index = 0; index < strlen(expected); index++) { + if(isdigit(expected[index])) { + expected_digits_count++; + } else if(expected[index] == '.') { + if(++dots_count > 1) + return true; + } else { + return true; + } + } + } + + return + strchr(actual, '(') != null || + strchr(expected, '(') != null || + actual [0] != '"' || + expected[0] != '"'; +} + + +#define MICROTEST_FAIL_INSIDE_TEST microtest_fail_inside_test() +#define MICROTEST_ABORT_INSIDE_TEST microtest_abort_inside_test() + + +// This is a little hack that allows a form of "polymorphism" to a macro - it allows a user to optionally pass +// an extra argument to a macro in {EXPECT|ASSERT}. +// The first argument is always the condition to expect/assert. If this condition fails, by default a `FAILED` +// message is sent to STDOUT. If a second argument is passed to the macro (a string), this will be outputted +// instead. +// +// The MACRO_CHOOSER uses the list of arguments twice, first to form the name of the helper macro, and then to +// pass the arguments to that helper macro. It uses a standard trick to count the number of arguments to a macro. +// +// Neat hack from: +// https://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros, and +// https://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments +// https://stackoverflow.com/questions/2124339/c-preprocessor-va-args-number-of-arguments +#define GET_64TH_ARG( \ + a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, \ + a11,a12,a13,a14,a15,a16,a17,a18,a19,a20, \ + a21,a22,a23,a24,a25,a26,a27,a28,a29,a30, \ + a31,a32,a33,a34,a35,a36,a37,a38,a39,a40, \ + a41,a42,a43,a44,a45,a46,a47,a48,a49,a50, \ + a51,a52,a53,a54,a55,a56,a57,a58,a59,a60, \ + a61,a62,a63,a64,... \ + ) a64 + +#define MACRO_CHOOSER(F1, F2, F3, FN, ...) GET_64TH_ARG( __VA_ARGS__, \ + FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, \ + FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, \ + FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, FN, \ + FN, F3, F2, F1, ) + +#define FIXED1_F1(NAME, cond) NAME##_(cond, "FAILED") +#define FIXED1_FN(NAME, cond, ...) NAME##_(cond, __VA_ARGS__) +#define FIXED1_CHOOSER(...) MACRO_CHOOSER(FIXED1_F1, FIXED1_FN, FIXED1_FN, FIXED1_FN, __VA_ARGS__) + +#define FIXED2_F1(NAME, act) NAME##_(act) /* will fail */ +#define FIXED2_F2(NAME, act, exp) NAME##_(act, exp, "FAILED") +#define FIXED2_FN(NAME, act, exp, ...) NAME##_(act, exp, __VA_ARGS__) +#define FIXED2_CHOOSER(...) MACRO_CHOOSER(FIXED2_F1, FIXED2_F2, FIXED2_FN, FIXED2_FN, __VA_ARGS__) + +#define FIXED3_F1(NAME, act) NAME##_(act) /* will fail */ +#define FIXED3_F2(NAME, act, exp) NAME##_(act, exp) /* will fail */ +#define FIXED3_F3(NAME, act, exp, n) NAME##_(act, exp, n, "FAILED") +#define FIXED3_FN(NAME, act, exp, n, ...) NAME##_(act, exp, n, __VA_ARGS__) +#define FIXED3_CHOOSER(...) MACRO_CHOOSER(FIXED3_F1, FIXED3_F2, FIXED3_F3, FIXED3_FN, __VA_ARGS__) + + +#define MICROTEST_COMPARE(actual, expected, condition, macro, handler, ...) \ + do { \ + if(!((actual) condition (expected))) { \ + printf("%s:%u: ", __FILE__, __LINE__); \ + printf(__VA_ARGS__); \ + printf("\n"); \ + if(microtest_should_decompose_macro(#actual, #expected, 0)) { \ + printf(" In macro : "); \ + printf("%s( %s, %s )\n", #macro, #actual, #expected); \ + } \ + printf(" Expected : %s", #actual); \ + printf(" %s ", #condition); \ + printf(#expected); \ + printf("\n"); \ + printf(" Actual : %s", #actual); \ + printf(" == "); \ + printf(#actual); \ + printf("\n"); \ + handler; \ + if (microtest_should_abort_test) \ + return; \ + } \ + } \ + while(0) + +#define MICROTEST_EXPECT_ASSERT(cond, handler, macro, ...) \ + do { \ + if(!(cond)) { \ + printf("%s:%u: ", __FILE__, __LINE__); \ + printf(__VA_ARGS__); \ + printf("\n"); \ + printf("The following assertion failed: \n"); \ + printf(" %s( %s )\n", #macro, #cond); \ + handler; \ + if(microtest_should_abort_test) \ + return; \ + } \ + } \ + while(0) + +#define MICROTEST_EXPECT_(cond, ...) MICROTEST_EXPECT_ASSERT(cond, MICROTEST_FAIL_INSIDE_TEST, MICROTEST_EXPECT, __VA_ARGS__) +#define MICROTEST_EXPECT_EQ_(actual, expected, ...) MICROTEST_COMPARE(actual, expected, ==, MICROTEST_EXPECT_EQ, MICROTEST_FAIL_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_EXPECT_NE_(actual, expected, ...) MICROTEST_COMPARE(actual, expected, !=, MICROTEST_EXPECT_EQ, MICROTEST_FAIL_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_EXPECT_LT_(actual, expected, ...) MICROTEST_COMPARE(actual, expected, <, MICROTEST_EXPECT_EQ, MICROTEST_FAIL_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_EXPECT_LE_(actual, expected, ...) MICROTEST_COMPARE(actual, expected, <=, MICROTEST_EXPECT_EQ, MICROTEST_FAIL_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_EXPECT_GT_(actual, expected, ...) MICROTEST_COMPARE(actual, expected, >, MICROTEST_EXPECT_EQ, MICROTEST_FAIL_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_EXPECT_GE_(actual, expected, ...) MICROTEST_COMPARE(actual, expected, >=, MICROTEST_EXPECT_EQ, MICROTEST_FAIL_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_EXPECT_TRUE_(actual, ...) MICROTEST_COMPARE(actual, true, ==, MICROTEST_EXPECT_EQ, MICROTEST_FAIL_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_EXPECT_FALSE_(actual, ...) MICROTEST_COMPARE(actual, false, ==, MICROTEST_EXPECT_EQ, MICROTEST_FAIL_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_EXPECT_NULL_(actual, ...) MICROTEST_EXPECT_(actual == null, __VA_ARGS__) +#define MICROTEST_EXPECT_NOT_NULL_(actual, ...) MICROTEST_EXPECT_(actual != null, __VA_ARGS__) + +#define MICROTEST_ASSERT_(cond, ...) MICROTEST_EXPECT_ASSERT(cond, MICROTEST_ABORT_INSIDE_TEST, MICROTEST_ASSERT, __VA_ARGS__) +#define MICROTEST_ASSERT_EQ_(actual, expected, ...) MICROTEST_COMPARE(actual, expected, ==, MICROTEST_ASSERT_EQ, MICROTEST_ABORT_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_ASSERT_NE_(actual, expected, ...) MICROTEST_COMPARE(actual, expected, !=, MICROTEST_ASSERT_EQ, MICROTEST_ABORT_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_ASSERT_LT_(actual, expected, ...) MICROTEST_COMPARE(actual, expected, <, MICROTEST_ASSERT_EQ, MICROTEST_ABORT_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_ASSERT_LE_(actual, expected, ...) MICROTEST_COMPARE(actual, expected, <=, MICROTEST_ASSERT_EQ, MICROTEST_ABORT_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_ASSERT_GT_(actual, expected, ...) MICROTEST_COMPARE(actual, expected, >, MICROTEST_ASSERT_EQ, MICROTEST_ABORT_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_ASSERT_GE_(actual, expected, ...) MICROTEST_COMPARE(actual, expected, >=, MICROTEST_ASSERT_EQ, MICROTEST_ABORT_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_ASSERT_TRUE_(actual, ...) MICROTEST_COMPARE(actual, true, ==, MICROTEST_ASSERT_EQ, MICROTEST_ABORT_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_ASSERT_FALSE_(actual, ...) MICROTEST_COMPARE(actual, false, ==, MICROTEST_ASSERT_EQ, MICROTEST_ABORT_INSIDE_TEST, __VA_ARGS__) +#define MICROTEST_ASSERT_NULL_(actual, ...) MICROTEST_ASSERT_(actual == null, __VA_ARGS__) +#define MICROTEST_ASSERT_NOT_NULL_(actual, ...) MICROTEST_ASSERT_(actual != null, __VA_ARGS__) + + +#define MICROTEST_EXPECT(...) FIXED1_CHOOSER(__VA_ARGS__)(MICROTEST_EXPECT, __VA_ARGS__) +#define MICROTEST_EXPECT_EQ(...) FIXED2_CHOOSER(__VA_ARGS__)(MICROTEST_EXPECT_EQ, __VA_ARGS__) +#define MICROTEST_EXPECT_NE(...) FIXED2_CHOOSER(__VA_ARGS__)(MICROTEST_EXPECT_NE, __VA_ARGS__) +#define MICROTEST_EXPECT_LT(...) FIXED2_CHOOSER(__VA_ARGS__)(MICROTEST_EXPECT_LT, __VA_ARGS__) +#define MICROTEST_EXPECT_LE(...) FIXED2_CHOOSER(__VA_ARGS__)(MICROTEST_EXPECT_LE, __VA_ARGS__) +#define MICROTEST_EXPECT_GT(...) FIXED2_CHOOSER(__VA_ARGS__)(MICROTEST_EXPECT_GT, __VA_ARGS__) +#define MICROTEST_EXPECT_GE(...) FIXED2_CHOOSER(__VA_ARGS__)(MICROTEST_EXPECT_GE, __VA_ARGS__) +#define MICROTEST_EXPECT_TRUE(...) FIXED1_CHOOSER(__VA_ARGS__)(MICROTEST_EXPECT_TRUE, __VA_ARGS__) +#define MICROTEST_EXPECT_FALSE(...) FIXED1_CHOOSER(__VA_ARGS__)(MICROTEST_EXPECT_FALSE, __VA_ARGS__) +#define MICROTEST_EXPECT_NULL(...) FIXED1_CHOOSER(__VA_ARGS__)(MICROTEST_EXPECT_NULL, __VA_ARGS__) +#define MICROTEST_EXPECT_NOT_NULL(...) FIXED1_CHOOSER(__VA_ARGS__)(MICROTEST_EXPECT_NOT_NULL, __VA_ARGS__) + +#define MICROTEST_ASSERT(...) FIXED1_CHOOSER(__VA_ARGS__)(MICROTEST_ASSERT, __VA_ARGS__) +#define MICROTEST_ASSERT_EQ(...) FIXED2_CHOOSER(__VA_ARGS__)(MICROTEST_ASSERT_EQ, __VA_ARGS__) +#define MICROTEST_ASSERT_NE(...) FIXED2_CHOOSER(__VA_ARGS__)(MICROTEST_ASSERT_NE, __VA_ARGS__) +#define MICROTEST_ASSERT_LT(...) FIXED2_CHOOSER(__VA_ARGS__)(MICROTEST_ASSERT_LT, __VA_ARGS__) +#define MICROTEST_ASSERT_LE(...) FIXED2_CHOOSER(__VA_ARGS__)(MICROTEST_ASSERT_LE, __VA_ARGS__) +#define MICROTEST_ASSERT_GT(...) FIXED2_CHOOSER(__VA_ARGS__)(MICROTEST_ASSERT_GT, __VA_ARGS__) +#define MICROTEST_ASSERT_GE(...) FIXED2_CHOOSER(__VA_ARGS__)(MICROTEST_ASSERT_GE, __VA_ARGS__) +#define MICROTEST_ASSERT_TRUE(...) FIXED1_CHOOSER(__VA_ARGS__)(MICROTEST_ASSERT_TRUE, __VA_ARGS__) +#define MICROTEST_ASSERT_FALSE(...) FIXED1_CHOOSER(__VA_ARGS__)(MICROTEST_ASSERT_FALSE, __VA_ARGS__) +#define MICROTEST_ASSERT_NULL(...) FIXED1_CHOOSER(__VA_ARGS__)(MICROTEST_ASSERT_NULL, __VA_ARGS__) +#define MICROTEST_ASSERT_NOT_NULL(...) FIXED1_CHOOSER(__VA_ARGS__)(MICROTEST_ASSERT_NOT_NULL, __VA_ARGS__) + + +/** + * @def MICROTEST_INITIALIZER + * @brief Responsible for registering a function with the c-runtime. + * @details By registering the function in this manner, we are able to make sure + * they are executed at the start up of the application such that + * microtest understands what test suites it will need to run. + * @param func The name of the function which will be registered. + */ +#if MALUNAL_COMPILER_MSVC +#if defined(_WIN64) +# define PREFIX +#else +# define PREFIX "_" +#endif // _WIN64 + +#pragma section(".CRT$XCU", read) +#define MICROTEST_INITIALIZER(func) \ + static void __cdecl f(void); \ + __declspec(allocate(".CRT$XCU")) \ + __pragma(comment(linker, "/include:" PREFIX #func "_")) \ + malunal_void_t(__cdecl * func##_)(malunal_void_t) = func; \ + static void __cdecl func(void) + +#undef PREFIX +#else /* Compiler is not MSVC */ +#define MICROTEST_INITIALIZER(func) \ + static void func(malunal_void_t) __attribute__((constructor)); \ + static void func(malunal_void_t) +#endif /* Compiler check for microtest registry */ + + +/** + * @def MICROTEST + * @brief Defines a test under a test suite and registers it with the global + * microtest context. + * @details This is responsible for registering the test in the c-runtime global + * constructor section, using either the GCC built in attribute or a + * pragma trick on MSVC. + * @param suite_name The name of the test suite; what the test belongs under. + * @param test_name The name of the test itself. + */ +#define MICROTEST(suite_name, test_name) \ + extern microtest_state_t microtest_context; \ + static malunal_void_t MICROTEST_##suite_name##_##test_name(malunal_void_t); \ + MICROTEST_INITIALIZER(microtest_register_##suite_name##_##test_name) { \ + malunal_size_t index = microtest_context.count++; \ + malunal_size_t bytes = microtest_context.count * sizeof(microtest_suite_t); \ + \ + malunal_cstr_t part = #suite_name "." #test_name; \ + malunal_size_t size = strlen(part) + 1; \ + malunal_str_t name = malloc(size); \ + snprintf(name, size, "%s", part); \ + \ + microtest_context.tests = (microtest_suite_mptr_t) \ + realloc(microtest_context.tests, bytes); \ + microtest_context.tests[index].execute = &MICROTEST_##suite_name##_##test_name; \ + microtest_context.tests[index].name = name; \ + microtest_context.tests[index].ignored = false; \ + } \ + \ + malunal_void_t MICROTEST_##suite_name##_##test_name(malunal_void_t) + + +/** + * @def IGNORE_MICROTEST + * @brief Defines a test under a test suite and registers it with the global + * microtest context, but marking it as ignored. + * @details This is responsible for registering the test in the c-runtime global + * constructor section, using either the GCC built in attribute or a + * pragma trick on MSVC. + * @param suite_name The name of the test suite; what the test belongs under. + * @param test_name The name of the test itself. + */ +#define IGNORE_MICROTEST(suite_name, test_name) \ + extern microtest_state_t microtest_context; \ + static malunal_void_t MICROTEST_##suite_name##_##test_name(malunal_void_t); \ + MICROTEST_INITIALIZER(microtest_register_##suite_name##_##test_name) { \ + malunal_size_t index = microtest_context.count++; \ + malunal_size_t bytes = microtest_context.count * sizeof(microtest_suite_t); \ + \ + malunal_cstr_t part = #suite_name "." #test_name; \ + malunal_size_t size = strlen(part) + 1; \ + malunal_str_t name = malloc(size); \ + snprintf(name, size, "%s", part); \ + \ + microtest_context.tests = (microtest_suite_mptr_t) \ + realloc(microtest_context.tests, bytes); \ + microtest_context.tests[index].execute = &MICROTEST_##suite_name##_##test_name; \ + microtest_context.tests[index].name = name; \ + microtest_context.tests[index].ignored = true; \ + } \ + \ + malunal_void_t MICROTEST_##suite_name##_##test_name(malunal_void_t) + + +/** + * @brief Responsible for shutting down the microtest framework. + * @details This will assure that all allocated resources are freed and that the + * framework returns cleanly. + */ +static +malunal_int32_t +microtest_cleanup() { + malunal_size_t index; + for (index = 0; index < microtest_context.count; index++) + free(microtest_context.tests[index].name); + free(microtest_failed_suites); + free(microtest_context.tests); + return microtest_failed_count; +} + +/** + * @brief Runs the tests that are registered with microtest. + * @details This will execute all tests and track which ones failed and which + * ones passed, printing the results as it processes them. + */ +static +malunal_void_t +microtest_run_tests() { + malunal_size_t index; + for (index = 0; index < microtest_context.count; index++) { + microtest_inside_test_suite = true; + microtest_current_test_failed = false; + microtest_current_test_ignored = microtest_context.tests[index].ignored; + // TODO: add test filtering? + if (microtest_current_test_ignored) { + // TODO: add logging library. + printf("[ IGNORED ] "); + printf("%s\n", microtest_context.tests[index].name); + microtest_skipped_count++; + continue; + } + + // TODO: add file output? + // TODO: track execution time. + printf("[ RUN ] "); + printf("%s\n", microtest_context.tests[index].name); + microtest_context.tests[index].execute(); + if (microtest_current_test_failed) { + malunal_size_t failed = microtest_failed_suites_count++; + malunal_size_t bytes = microtest_failed_suites_count * sizeof(malunal_size_t); + + microtest_failed_suites = (malunal_size_t*) + realloc(microtest_failed_suites, bytes); + microtest_failed_suites[failed] = index; + microtest_failed_count++; + + printf("[ FAILED ]"); + printf("%s (", microtest_context.tests[index].name); + printf(")\n"); + } else { + printf("[ OK ]"); + printf("%s (", microtest_context.tests[index].name); + printf(")\n"); + } + } + + printf("[==========] "); + printf("%zu test suites ran\n", microtest_ran_count); +} + +/** + * @brief Responsible for handling any command line options and running all + * tests that are registered with microtest. + * @param argc The number of command line arguments. + * @param argv The array of command line arguments. + * @returns The result of running microtest. + */ +static +malunal_int32_t +microtest_main( + malunal_int32_t argc, + malunal_str_t* argv +) { + microtest_total_suites_count = microtest_context.count; + microtest_ran_count = microtest_total_suites_count; + printf("[==========] "); + printf("Running %zu test suites.\n", microtest_ran_count); + microtest_run_tests(); + + malunal_size_t failed = microtest_failed_count; + malunal_size_t passed = microtest_ran_count - failed; + printf("[ PASSED ] %zu %s\n", passed, passed == 1 ? "suite" : "suites"); + printf("[ FAILED ] %zu %s\n", failed, failed == 1 ? "suite" : "suites"); + + printf("\nSummary:\n"); + printf(" Total test suites: %zu\n", microtest_total_suites_count); + printf(" Total suites run: %zu\n", microtest_ran_count); + printf(" Total warnings generated: %zu\n", microtest_warnings_count); + printf(" Total suites skipped: %zu\n", microtest_skipped_count); + printf(" Total suites failed: %zu\n", failed); + return microtest_cleanup(); +} + +/** + * @def MICROTEST_GLOBALS + * @brief This defines the globals that microtest needs to function. + * @details This isn't designed to be used by anyone unless they are manually + * specifying the main function, but we recommend that you simply use + * @c MICROTEST_MAIN + */ +#define MICROTEST_GLOBALS() \ + microtest_state_t microtest_context = { null }; \ + malunal_size_t microtest_warnings_count = 0; \ + \ + volatile malunal_bool_t microtest_inside_test_suite; \ + volatile malunal_bool_t microtest_current_test_failed; \ + volatile malunal_bool_t microtest_current_test_ignored; \ + volatile malunal_bool_t microtest_should_fail_test; \ + volatile malunal_bool_t microtest_should_abort_test; + +/** + * @def MICROTEST_NO_MAIN() + * @brief Sets up microtest without specifying the main function. + * @details Use this if you plan to write your own main function or to modify + * the manner in which microtest executes its main function. + */ +#define MICROTEST_NO_MAIN() \ + MICROTEST_GLOBALS() + +/** + * @def MICROTEST_MAIN + * @brief This defines the microtest globals and the main function. + * @details This is provided to make defining the main test easier. It will + * automatically define all needed globals, and the main function which + * will call the actual @c microtest_main. + */ +#define MICROTEST_MAIN() \ + MICROTEST_GLOBALS() \ + \ + malunal_int32_t \ + main( \ + malunal_int32_t argc, \ + malunal_str_t* argv \ + ) { \ + return microtest_main(argc, argv); \ + } + + +#endif /* MALUNAL_MICROTEST_HEADER */ diff --git a/tests/entry.c b/tests/entry.c new file mode 100644 index 0000000..134b53d --- /dev/null +++ b/tests/entry.c @@ -0,0 +1,2 @@ +#include "malunal/microtest.h" +MICROTEST_MAIN() diff --git a/tests/general.c b/tests/general.c new file mode 100644 index 0000000..c1e7051 --- /dev/null +++ b/tests/general.c @@ -0,0 +1,139 @@ +#include "malunal/microtest.h" + +MICROTEST(c, MICROTEST_EXPECT_TF) { + MICROTEST_EXPECT_FALSE(0); + MICROTEST_EXPECT_TRUE(1); +} + +MICROTEST(c, MICROTEST_ASSERT_TF) { + MICROTEST_ASSERT_FALSE(0); + MICROTEST_ASSERT_TRUE(1); +} + +MICROTEST(c, MemAlloc) { + malunal_mptr_t mem; + + mem = malloc(5); + MICROTEST_EXPECT(mem != NULL, "Mem ideally should not be NULL"); + + mem = realloc(mem, 10); + MICROTEST_EXPECT(mem != NULL, "Mem ideally should not be NULL"); +} + +malunal_int32_t retu() { + return 10000000; +} + +MICROTEST(c, ForLoop) { + malunal_int32_t count = 0; + malunal_int32_t index = 0; + for(index; index < 10000000; index++) + count++; + MICROTEST_ASSERT_EQ(count, retu()); +} + +MICROTEST(c, MICROTEST_EXPECT) { + MICROTEST_EXPECT(1 < 2, "1 < 2"); +} + +MICROTEST(c, MICROTEST_ASSERT_LT) { + MICROTEST_ASSERT_LT(1, 2); +} + +MICROTEST(c, MICROTEST_ASSERT_LE) { + MICROTEST_ASSERT_LE(1, 1); + MICROTEST_ASSERT_LE(1, 2); +} + +MICROTEST(c, MICROTEST_ASSERT_GT) { + MICROTEST_ASSERT_GT(2, 1); +} + +MICROTEST(c, MICROTEST_ASSERT_GE) { + MICROTEST_ASSERT_GE(1, 1); + MICROTEST_ASSERT_GE(2, 1); +} + +MICROTEST(c, MICROTEST_EXPECT_EQ1) { + MICROTEST_EXPECT_EQ(345, 345); +} + +MICROTEST(c, MICROTEST_EXPECT_NE) { + MICROTEST_EXPECT_NE(1, 2); +} + +MICROTEST(c, MICROTEST_EXPECT_LT) { + MICROTEST_EXPECT_LT(1, 2); +} + +MICROTEST(c, MICROTEST_EXPECT_LE) { + MICROTEST_EXPECT_LE(1, 1); + MICROTEST_EXPECT_LE(1, 2); +} + +MICROTEST(c, MICROTEST_EXPECT_GT) { + MICROTEST_EXPECT_GT(2, 1); +} + +MICROTEST(c, MICROTEST_EXPECT_GE) { + MICROTEST_EXPECT_GE(1, 1); + MICROTEST_EXPECT_GE(2, 1); +} + +MICROTEST(c11, MICROTEST_ASSERT_EQ) { + MICROTEST_ASSERT_EQ(1, 1); +} + +MICROTEST(c11, MICROTEST_ASSERT_NE) { + MICROTEST_ASSERT_NE(1, 2); +} + +MICROTEST(c11, MICROTEST_ASSERT_LT) { + MICROTEST_ASSERT_LT(1, 2); +} + +MICROTEST(c11, MICROTEST_ASSERT_LE) { + MICROTEST_ASSERT_LE(1, 1); + MICROTEST_ASSERT_LE(1, 2); +} + +MICROTEST(c11, MICROTEST_ASSERT_GT) { + MICROTEST_ASSERT_GT(2, 1); +} + +MICROTEST(c11, MICROTEST_ASSERT_GE) { + MICROTEST_ASSERT_GE(1, 1); + MICROTEST_ASSERT_GE(2, 1); +} + +MICROTEST(c11, MICROTEST_EXPECT_EQ2) { + MICROTEST_EXPECT_EQ(1, 1); +} + +MICROTEST(c11, MICROTEST_EXPECT_NE) { + MICROTEST_EXPECT_NE(1, 2); +} + +MICROTEST(c11, MICROTEST_EXPECT_LT) { + MICROTEST_EXPECT_LT(1, 2); +} + +MICROTEST(c11, MICROTEST_EXPECT_LE) { + MICROTEST_EXPECT_LE(1, 1); + MICROTEST_EXPECT_LE(1, 2); +} + +MICROTEST(c11, MICROTEST_EXPECT_GT) { + MICROTEST_EXPECT_GT(3, 1); +} + +MICROTEST(c11, MICROTEST_EXPECT_GE) { + MICROTEST_EXPECT_GE(1, 1); + MICROTEST_EXPECT_GE(2, 1); +} + +MICROTEST(c11, no_double_eval) { + malunal_int32_t i = 0; + MICROTEST_ASSERT_EQ(i++, 0); + MICROTEST_ASSERT_EQ(i, 1); +}