I like from the C++ unit test framework Catch2 its sections -- much more than the xUnit test fixtures. Therefore I wrote me some macros which mimic the Catch2 sections.
This is the first macro heavy code I wrote, so I am mostly interested whether the macros do what they should do, or not.
#include <utility>
#define TOKENPASTE_HELPER(x, y) x ## y
#define TOKENPASTE(x, y) TOKENPASTE_HELPER(x, y)
#define CASE( name ) \
if ( int executed_section = 0, first_run = 0; true ) \
while ( std::exchange(executed_section, 0) || first_run++ == 0 )
#define SECTION( name ) \
static int TOKENPASTE(counter_done_, __LINE__) = 0; \
goto TOKENPASTE(section_start_, __LINE__); \
TOKENPASTE(section_end_, __LINE__): \
continue; \
TOKENPASTE(section_start_, __LINE__): \
while ( true ) \
if ( TOKENPASTE(counter_done_, __LINE__) == 2 ) { \
break; \
} else if( TOKENPASTE(counter_done_, __LINE__) == 1 ) { \
TOKENPASTE(counter_done_, __LINE__) = 2; \
goto TOKENPASTE(section_end_, __LINE__); \
} else if ( TOKENPASTE(counter_done_, __LINE__) = 1, executed_section=1; true )
They are used like this:
#include <cassert>
int main() {
CASE( i_test ) {
int i = 0;
SECTION( i==1 ) {
++i;
assert( i == 1 );
}
SECTION( i == 0 ) {
assert( i == 0 );
}
}
CASE( ii_test ) {
int i = 10;
SECTION() {
assert( i==10 );
}
}
}
1 Answer 1
The output of gcc -E
is
int main() {
if ( int executed_section = 0, first_run = 0; true ) while ( std::exchange(executed_section, 0) || first_run++ == 0 ) {
int i = 0;
static int counter_done_27 = 0; goto section_start_27; section_end_27: continue; section_start_27: while ( true ) if ( counter_done_27 == 2 ) { break; } else if( counter_done_27 == 1 ) { counter_done_27 = 2; goto section_end_27; } else if ( counter_done_27 = 1, executed_section=1; true ) {
++i;
assert( i == 1 );
}
static int counter_done_32 = 0; goto section_start_32; section_end_32: continue; section_start_32: while ( true ) if ( counter_done_32 == 2 ) { break; } else if( counter_done_32 == 1 ) { counter_done_32 = 2; goto section_end_32; } else if ( counter_done_32 = 1, executed_section=1; true ) {
assert( i == 0 );
}
}
if ( int executed_section = 0, first_run = 0; true ) while ( std::exchange(executed_section, 0) || first_run++ == 0 ) {
int i = 10;
static int counter_done_39 = 0; goto section_start_39; section_end_39: continue; section_start_39: while ( true ) if ( counter_done_39 == 2 ) { break; } else if( counter_done_39 == 1 ) { counter_done_39 = 2; goto section_end_39; } else if ( counter_done_39 = 1, executed_section=1; true ) {
assert( i==10 );
}
}
}
I do recommend that you check output like this for macro-heavy code. And... what does it even do. It feels like it's written to be a puzzle, and test frameworks shouldn't be a puzzle. The goto
, exchange
, non-re-entrant statics... None of this should see the light of day, and it should all be deleted. I strongly recommend that you retry this project and pretend that the precompiler does not exist. The same features can be implemented with contemporary C++, especially lambdas.
-
\$\begingroup\$ Well, the output of gcc -E is exactly what it should be. \$\endgroup\$tommsch– tommsch2025年08月05日 07:27:24 +00:00Commented Aug 5 at 7:27
-
\$\begingroup\$ While I agree it would be better to write a test framework that doesn't require macros, if the goal is to make a drop-in replacement for Catch2, I don't see how you could do that. \$\endgroup\$G. Sliepen– G. Sliepen2025年08月07日 08:28:32 +00:00Commented Aug 7 at 8:28
gcc -E
or equivalent. \$\endgroup\$