The Fossil Test public API is built around a collection of high-level macros that simplify the creation, organization, and execution of unit tests in C and C++ projects. These macros provide a consistent and expressive interface for defining test cases, test suites, setup and teardown routines, assertions, and execution workflows without requiring developers to interact directly with the underlying runtime engine. By abstracting implementation details, the API allows test code to remain clean, readable, and maintainable while preserving the performance and low-level control expected from native systems programming environments.
At the core of the API are the test definition and organization macros, including FOSSIL_TEST, FOSSIL_SUITE, FOSSIL_ADD_TEST, and FOSSIL_ADD_SUITE. These constructs enable developers to create individual test cases, group related functionality into suites, and register those suites with the test engine for execution. Additional metadata macros such as FOSSIL_TEST_SET_TAGS, FOSSIL_TEST_SET_CRITERIA, FOSSIL_TEST_SET_SKIP, FOSSIL_TEST_SET_BEFORE, and FOSSIL_TEST_SET_AFTER allow each test case to be enriched with filtering information, execution conditions, lifecycle callbacks, and descriptive context. Together, these capabilities make it possible to build highly structured test collections that can be selectively executed, categorized, and analyzed.
The API also provides comprehensive support for test execution and behavioral validation. Engine control macros such as FOSSIL_TEST_START, FOSSIL_RUN_SUITE, FOSSIL_RUN_ALL, FOSSIL_SUMMARY, and FOSSIL_END manage the complete testing lifecycle from initialization to reporting. Validation macros including FOSSIL_TEST_ASSUME and FOSSIL_TEST_ASSERT provide mechanisms for expressing expectations and enforcing correctness during execution. For behavior-driven development, the GIVEN, WHEN, THEN, and AND macros introduce a natural language structure that improves readability and intent. Furthermore, FOSSIL_SUBCASE enables fine-grained edge case scoping within a test, allowing complex scenarios to be broken into isolated contexts while maintaining clear and traceable test results.
PUBLIC INTERFACE #
// *****************************************************************************
// Public API Macros
// *****************************************************************************
/** @brief Macro to define a test case.
*
* This macro is used to define a test case, which is a single unit of testing
* that verifies a specific functionality or behavior. The test case can be
* executed independently or as part of a test suite.
*
* @param test_name The name of the test case to define.
*/
#define FOSSIL_TEST(test_name) \
_FOSSIL_TEST(test_name)
/** @brief Macro to set a test case's tags.
*
* This macro is used to specify tags for a test case. Tags can be used to
* categorize or filter test cases based on specific criteria.
*
* @param test_name The name of the test case.
* @param tags The tags to assign to the test case.
*/
#define FOSSIL_TEST_SET_TAGS(test_name, tags) \
_FOSSIL_TEST_SET_TAGS(test_name, tags)
/** @brief Macro to set a test case's skip message.
*
* This macro is used to specify a skip message for a test case. The skip
* message indicates that the test case is intentionally skipped and provides
* a reason for the skip.
*
* @param test_name The name of the test case.
* @param skip The skip message to assign to the test case.
*/
#define FOSSIL_TEST_SET_SKIP(test_name, skip) \
_FOSSIL_TEST_SET_SKIP(test_name, skip)
/** @brief Macro to set a test case's criteria.
*
* This macro is used to specify criteria for a test case. The criteria can be
* used to filter or categorize test cases based on specific conditions or
* requirements.
*
* @param test_name The name of the test case.
* @param criteria The criteria to assign to the test case.
*/
#define FOSSIL_TEST_SET_CRITERIA(test_name, criteria) \
_FOSSIL_TEST_SET_CRITERIA(test_name, criteria)
/** @brief Macro to set a test case's setup function.
*
* This macro is used to specify a setup function for a test case. The setup
* function will be called before the test case is executed, allowing for any
* necessary initialization or preparation.
*
* @param test_name The name of the test case.
* @param before The name of the setup function to call before the test case.
*/
#define FOSSIL_TEST_SET_BEFORE(test_name, before) \
_FOSSIL_TEST_SET_BEFORE(test_name, before)
/** @brief Macro to set a test case's setup function.
*
* This macro is used to specify a setup function for a test case. The setup
* function will be called before the test case is executed, allowing for any
* necessary initialization or preparation.
*
* @param test_name The name of the test case.
* @param before The name of the setup function to call before the test case.
*/
#define FOSSIL_TEST_SET_AFTER(test_name, after) \
_FOSSIL_TEST_SET_AFTER(test_name, after)
/** @brief Macro to define a test suite.
*
* This macro is used to define a test suite, which is a collection of test cases
* that are related to each other. The test suite can be executed as a whole to
* verify the correctness of a group of functionalities.
*
* @param name The name of the suite to define.
*/
#define FOSSIL_SUITE(suite) \
_FOSSIL_SUITE(suite)
/** @brief Macro to define a test setup function.
*
* This macro is used to define a setup function for a test case or suite. The
* setup function will be called before the test case or suite is executed, and
* can be used to initialize resources or set up the test environment.
*
* @param test_setup The name of the setup function to define.
*/
#define FOSSIL_SETUP(test_setup) \
_FOSSIL_SETUP(test_setup)
/** @brief Macro to define a test teardown function.
*
* This macro is used to define a teardown function for a test case or suite. The
* teardown function will be called after the test case or suite has been executed, and
* can be used to clean up resources or reset the test environment.
*
* @param test_teardown The name of the teardown function to define.
*/
#define FOSSIL_TEARDOWN(test_teardown) \
_FOSSIL_TEARDOWN(test_teardown)
/** @brief Macro to define a test setup function.
*
* This macro is used to define a setup function for a test case or suite. The
* setup function will be called before the test case or suite is executed, and
* can be used to initialize resources or set up the test environment.
*
* @param test_setup The name of the setup function to define.
*/
#define FOSSIL_BEFORE(test_setup) \
_FOSSIL_BEFORE(test_setup)
/** @brief Macro to define a test teardown function.
*
* This macro is used to define a teardown function for a test case or suite. The
* teardown function will be called after the test case or suite has been executed, and
* can be used to clean up resources or reset the test environment.
*
* @param test_teardown The name of the teardown function to define.
*/
#define FOSSIL_AFTER(test_teardown) \
_FOSSIL_AFTER(test_teardown)
/** @brief Macro to add a test case to a specific suite.
*
* This macro is used to add a test case to a specific test suite that has been
* defined in the test engine. It will link the test case to the suite and
* ensure it is executed when the suite is run.
*
* @param suite The name of the suite to which the test case will be added.
* @param test_case The name of the test case to add.
*/
#define FOSSIL_ADD_TEST(suite, test_case) \
_FOSSIL_ADD_TEST(suite, test_case)
/** @brief Macro to run a specific test suite.
*
* This macro is used to run a specific test suite that has been defined in the
* test engine. It will execute all test cases within the specified suite and
* print the results.
*
* @param suite The name of the suite to run.
*/
#define FOSSIL_RUN_SUITE(suite) \
_FOSSIL_RUN_SUITE(suite)
/** @brief Macro to start the test engine.
*
* This macro is used to initialize and start the test engine. It should be called
* at the beginning of the test execution to set up the environment for running
* tests.
*
* @param argc The number of command line arguments.
* @param argv The command line arguments.
*/
#define FOSSIL_TEST_START(argc, argv) \
_FOSSIL_TEST_START(argc, argv)
/** @brief Macro to print a summary of the test results.
*
* This macro is used to print a summary of the test results after all tests have
* been run. It provides an overview of the number of tests passed, failed, and
* skipped.
*
* @param engine The engine instance containing the test results.
*/
#define FOSSIL_SUMMARY() \
_FOSSIL_SUMMARY()
/** @brief Macro to run all test suites in the engine.
*
* This macro is used to run all test suites that have been added to the test
* engine. It will execute each suite and its associated test cases, collecting
* results and printing a summary at the end.
*
* @param engine The engine instance containing the test suites.
*/
#define FOSSIL_RUN_ALL() \
_FOSSIL_RUN_ALL()
/** @brief Macro to end the test engine.
*
* This macro is used to clean up and end the test engine after all tests have
* been run. It should be called at the end of the test execution.
*
* @param engine The engine instance to end.
*/
#define FOSSIL_END() \
_FOSSIL_END()
/**
* @brief Macro to register a test suite with the engine.
*
* This macro is used to register a test suite with the test engine. It will
* ensure that the suite is added to the engine and can be executed when the
* test runner is run.
*
* @param suite The name of the suite to register.
*/
#define FOSSIL_ADD_SUITE(suite) \
_FOSSIL_ADD_SUITE(suite)
/**
* @brief Macro to define a test group.
*
* This macro is used to define a test group, which is a collection of test cases
* that are related to each other. The test group can be executed as a whole to
* verify the correctness of a group of functionalities.
*
* @param name The name of the test group.
*/
#define FOSSIL_TEST_GROUP(name) \
_FOSSIL_TEST_GROUP(name)
/**
* @brief Macro to export a test group.
*
* This macro is used to export a test group from a test file. The test group
* will be available to other test files that import it.
*
* @param name The name of the test group to export.
*/
#define FOSSIL_TEST_EXPORT(name) \
_FOSSIL_TEST_EXPORT(name)
/**
* @brief Macro to import a test group.
*
* This macro is used to import a test group into the test runner. The test group
* will be executed when the test runner is run.
*
* @param name The name of the test group to import.
*/
#define FOSSIL_TEST_IMPORT(name) \
_FOSSIL_TEST_IMPORT(name)
/**
* @brief Macro to assume a condition in a test runner.
* This macro is used to assert that a specific condition is true within a test
* runner. If the condition is false, the test runner will output the specified
* message and may abort the execution of the test case or test suite.
*/
#define FOSSIL_TEST_ASSUME(condition, message) \
_FOSSIL_TEST_ASSUME(condition, message)
/**
* @brief Macro to assert a condition in a test runner.
* This macro is used to assert that a specific condition is true within a test
* runner. If the condition is false, the test runner will output the specified
* message and may abort the execution of the test case or test suite.
*/
#define FOSSIL_TEST_ASSERT(condition, message) \
_FOSSIL_TEST_ASSERT(condition, message)
/**
* @brief Macro for defining a Given step in a behavior-driven development test.
*
* This macro is used to define a Given step in a behavior-driven development test.
* The Given step is used to specify the initial context of a test case.
*
* @param description The description of the Given step.
*/
#define GIVEN(description) \
_GIVEN(description)
/**
* @brief Macro for defining a When step in a behavior-driven development test.
*
* This macro is used to define a When step in a behavior-driven development test.
* The When step is used to specify the action that is being tested.
*
* @param description The description of the When step.
*/
#define WHEN(description) \
_WHEN(description)
/**
* @brief Macro for defining a Then step in a behavior-driven development test.
*
* This macro is used to define a Then step in a behavior-driven development test.
* The Then step is used to specify the expected outcome of a test case.
*
* @param description The description of the Then step.
*/
#define THEN(description) \
_THEN(description)
/**
* @brief Macro for defining an And step in a behavior-driven development test.
*
* This macro is used to define an And step in a behavior-driven development test.
* The And step is used to add an additional condition or continuation to a
* previously defined Given, When, or Then step.
*
* @param description The description of the And step.
*/
#define AND(description) \
_AND(description)
/**
* @brief Macro for defining a Subcases for scoping edge cases within test cases.
*
* This macro is used to define a Subcase for scoping edge cases within test cases.
* The Subcase is used to specify the context of a test case.
*
* @param description The description of the Subcase.
*/
#define FOSSIL_SUBCASE(description) \
_FOSSIL_SUBCASE(description)
C SAMPLE #
// Test data structure for a sample test
FOSSIL_MOCK_STRUCT(CSampleTestData) {
int input;
int expected_output;
} CSampleTestData;
// Setup function for the test suite
FOSSIL_SETUP(sample_suite) {
// Setup code here
}
// Teardown function for the test suite
FOSSIL_TEARDOWN(sample_suite) {
// Teardown code here
}
// Define the test suite and add test cases
FOSSIL_SUITE(sample_suite);
// A simple test case to check if input + 1 equals expected_output
FOSSIL_TEST(test_input_increment) {
CSampleTestData data = { .input = 5, .expected_output = 6 };
int actual_output = data.input + 1;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Increment test failed");
}
// A simple test case to check if input - 1 equals expected_output
FOSSIL_TEST(test_input_decrement) {
CSampleTestData data = { .input = 5, .expected_output = 4 };
int actual_output = data.input - 1;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Decrement test failed");
}
// A simple test case to check if input * 2 equals expected_output
FOSSIL_TEST(test_input_double) {
CSampleTestData data = { .input = 5, .expected_output = 10 };
int actual_output = data.input * 2;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Double test failed");
}
// A simple test case to check if input / 2 equals expected_output
FOSSIL_TEST(test_input_half) {
CSampleTestData data = { .input = 10, .expected_output = 5 };
int actual_output = data.input / 2;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Half test failed");
}
// A simple test case to check if input % 2 equals expected_output
FOSSIL_TEST(test_input_modulo) {
CSampleTestData data = { .input = 5, .expected_output = 1 };
int actual_output = data.input % 2;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Modulo test failed");
}
// A simple test case to check if input squared equals expected_output
FOSSIL_TEST(test_input_square) {
CSampleTestData data = { .input = 3, .expected_output = 9 };
int actual_output = data.input * data.input;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Square test failed");
}
// A simple test case to check if input is equal to expected_output
FOSSIL_TEST(test_input_equal) {
CSampleTestData data = { .input = 7, .expected_output = 7 };
int actual_output = data.input;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Equality test failed");
}
// FOSSIL_TEST(test_empty_case) {
// // test the absence of an assumption .
// }
FOSSIL_TEST_GROUP(c_sample_test_cases) {
FOSSIL_ADD_TEST(sample_suite, test_input_increment);
FOSSIL_ADD_TEST(sample_suite, test_input_decrement);
FOSSIL_ADD_TEST(sample_suite, test_input_double);
FOSSIL_ADD_TEST(sample_suite, test_input_half);
FOSSIL_ADD_TEST(sample_suite, test_input_modulo);
FOSSIL_ADD_TEST(sample_suite, test_input_square);
FOSSIL_ADD_TEST(sample_suite, test_input_equal);
//FOSSIL_ADD_TEST(sample_suite, test_empty_case);
FOSSIL_ADD_SUITE(sample_suite);
}C++ SAMPLE #
// Test data structure for a sample test
FOSSIL_MOCK_STRUCT(CppSampleTestData) {
int input;
int expected_output;
};
// Setup function for the test suite
FOSSIL_SETUP(cpp_sample_suite) {
// Setup code here
}
// Teardown function for the test suite
FOSSIL_TEARDOWN(cpp_sample_suite) {
// Teardown code here
}
// Define the test suite and add test cases
FOSSIL_SUITE(cpp_sample_suite);
// A simple test case to check if input + 1 equals expected_output
FOSSIL_TEST(cpp_test_input_increment) {
CppSampleTestData data = {5, 6}; // Simplified initialization
int actual_output = data.input + 1;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Increment test failed");
}
// A simple test case to check if input - 1 equals expected_output
FOSSIL_TEST(cpp_test_input_decrement) {
CppSampleTestData data = {5, 4}; // Simplified initialization
int actual_output = data.input - 1;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Decrement test failed");
}
// A simple test case to check if input * 2 equals expected_output
FOSSIL_TEST(cpp_test_input_double) {
CppSampleTestData data = {5, 10}; // Simplified initialization
int actual_output = data.input * 2;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Double test failed");
}
// A simple test case to check if input / 2 equals expected_output
FOSSIL_TEST(cpp_test_input_half) {
CppSampleTestData data = {10, 5}; // Simplified initialization
int actual_output = data.input / 2;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Half test failed");
}
// A simple test case to check if input % 2 equals expected_output
FOSSIL_TEST(cpp_test_input_modulo) {
CppSampleTestData data = { 5, 1 };
int actual_output = data.input % 2;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Modulo test failed");
}
// A simple test case to check if input squared equals expected_output
FOSSIL_TEST(cpp_test_input_square) {
CppSampleTestData data = { 3, 9 };
int actual_output = data.input * data.input;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Square test failed");
}
// A simple test case to check if input is equal to expected_output
FOSSIL_TEST(cpp_test_input_equal) {
CppSampleTestData data = { 7, 7 };
int actual_output = data.input;
FOSSIL_TEST_ASSUME(actual_output == data.expected_output, "Equality test failed");
}
FOSSIL_TEST_GROUP(cpp_sample_test_cases) {
FOSSIL_ADD_TEST(cpp_sample_suite, cpp_test_input_increment);
FOSSIL_ADD_TEST(cpp_sample_suite, cpp_test_input_decrement);
FOSSIL_ADD_TEST(cpp_sample_suite, cpp_test_input_double);
FOSSIL_ADD_TEST(cpp_sample_suite, cpp_test_input_half);
FOSSIL_ADD_TEST(cpp_sample_suite, cpp_test_input_modulo);
FOSSIL_ADD_TEST(cpp_sample_suite, cpp_test_input_square);
FOSSIL_ADD_TEST(cpp_sample_suite, cpp_test_input_equal);
FOSSIL_ADD_SUITE(cpp_sample_suite);
}