Closed christophercrouzet closed 4 years ago
For info, I managed to get a prototype working on gcc, clang, icc, and msvc. It's incomplete and needs much more work before being integrated into Rexo but it's looking promising.
#include <stdio.h>
#define RXP_TEST_CASE_PARAMETERS
typedef void (*RxPfnTestCase)(RXP_TEST_CASE_PARAMETERS);
struct RxpTestCaseDefinition {
const char *pName;
const char *pSuiteName;
RxPfnTestCase pfnRun;
};
#if defined(__GNUC__)
extern const struct RxpTestCaseDefinition *__start_rxcase;
extern const struct RxpTestCaseDefinition *__stop_rxcase;
#define RXP_ADD_TO_CASE_SECTION __attribute__((used, section("rxcase")))
#define RXP_CASE_SECTION_BEGIN __start_rxcase
#define RXP_CASE_SECTION_END __stop_rxcase
#elif defined(_MSC_VER)
#pragma section("rxcase$a", read)
#pragma section("rxcase$s", read)
#pragma section("rxcase$z", read)
__declspec(allocate("rxcase$a")) static const
struct RxpTestCaseDefinition *rxpCaseSectionBegin;
__declspec(allocate("rxcase$z")) static const
struct RxpTestCaseDefinition *rxpCaseSectionEnd;
#define RXP_ADD_TO_CASE_SECTION __declspec(allocate("rxcase$s"))
#define RXP_CASE_SECTION_BEGIN (*(&rxpCaseSectionBegin + 1))
#define RXP_CASE_SECTION_END rxpCaseSectionEnd
#endif
#define RX_TEST_CASE(suiteName, name) \
static void name(RXP_TEST_CASE_PARAMETERS); \
static const struct RxpTestCaseDefinition testCaseDefinition_##name \
= {#name, #suiteName, name}; \
RXP_ADD_TO_CASE_SECTION \
static const struct RxpTestCaseDefinition *pTestCaseDefinition_##name \
= &testCaseDefinition_##name; \
static void name(RXP_TEST_CASE_PARAMETERS)
RX_TEST_CASE(mySuite, foo)
{
printf("foo!\n");
}
RX_TEST_CASE(mySuite, bar)
{
printf("bar!\n");
}
int
main(void)
{
const struct RxpTestCaseDefinition **ppCursor = &RXP_CASE_SECTION_BEGIN;
for (; ppCursor < &RXP_CASE_SECTION_END; ++ppCursor) {
if (*ppCursor == NULL) {
continue;
}
(*ppCursor)->pfnRun();
}
return 0;
}
Quick heads up: it's 80% done and I'm pretty happy on the implementation so far.
The 20% remaining is about having the possibility to define metadata (e.g. set up and tear down functions) both at the suite and at the case levels, e.g.:
RX_TEST_SUITE(my_suite, .set_up=my_set_up_fn, .tear_down=my_tear_down_fn);
RX_TEST_CASE(my_suite, my_case, .set_up=another_set_up_fn)
{
...
}
I still have to figure out a nice approach to have the cases inherit the metadata from their suite while being able to override some specific fields (e.g.: the set_up
function in the snippet above).
Sorry it took me a while but it's finally here and I'm really happy with the results!
A minimal version now looks like this:
#include <rexo.h>
RX_TEST_CASE(my_test_suite, my_test_case)
{
RX_CHECK_STR_EQUAL("Hello", "world!");
}
int
main(int argc, const char **argv)
{
return rx_run(argc, argv, 0, NULL) == RX_SUCCESS ? 0 : 1;
}
Let me know what you think @peterleegolang! :blush:
Having to write tests and then register them as part of a suite feels a bite redundant, error-prone since it would be easy to forget, and also quite verbose.
While reading CTest's source code, I noticed that data sections defined through compilers attributes could be accessed within the same translation unit, as seen below:
I'm planning to experiment with this approach and will look on how to expend it to the suites. Ideally, we'd end up with this kind of code on the user-side:
Where both test cases would be registered within a same (default initialized) test suite.
In the case where a more specific test suite is required, such as one providing a fixture, then we would be able to explicitely define it as such: