|
|
|
@@ -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 <ctype.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#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 */
|