Mustafa Quraish
Mustafa Quraish

Reputation: 694

Sequential function names using preprocessor

I'm writing a simple unit-testing library in C to run some tests on code, and am curious if it is possible to make the process more efficient using macros/the preprocessor. Yes, I realize that I probably shouldn't be writing my own, but I would still like to know if this is possible.

Here's how I currently do it:

#include <stdio.h>

/* Each test exits with status 1 if fail */
void test_00() { ... }     
void test_01() { ... }
...
void test_09() { ... }

#define NUM_TESTS 10
void (*TESTS[NUM_TESTS])() = {test_00, test_01, ..., test_09};

/* Only want to run the test number passed in on the command line */
int main(int argc, char *argv) {
  int test_num = atoi(argv[1]);    // Error checking in actual code
  TESTS[test_num]();
}

In particular, I am interested in getting rid of the TESTS array where I have to manually add in every new test that is made. Ideally, have it be automatically generated using NUM_TESTS since it's honestly always repetitive.

It doesn't have to be particularly general, I won't have more than 100 tests cases so 2 digits for the test number is fine, and all the tests are named in exactly the same way.


Sorry if this question doesn't follow some guidelines. I have no reasonable attempt to show, and am at a loss for what exactly to even look for.

Upvotes: 0

Views: 86

Answers (2)

Bernhard
Bernhard

Reputation: 364

You could dynamically load and call the symbol at runtime by using dlsym().

// compile with `gcc -Wall -o test -Wl,--export-dynamic test.c -ldl`
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>


/* Each test exits with status 1 if fail */
void test_00() { exit(10); }     
void test_01() { exit(11); }
void test_09() { exit(19); }

/* Only want to run the test number passed in on the command line */
int main(int argc, char **argv)
{
   void (*test)(void);
   char symname[32];

   snprintf(symname, sizeof(symname), "test_%s", argv[1]);
   if ((test = dlsym(NULL, symname)) == NULL)
   {
      fprintf(stderr, "%s\n", dlerror());
      exit(1);
   }

   test();
}

Upvotes: 1

KamilCuk
KamilCuk

Reputation: 140990

// Provide the counts as macros:
#define TEST_LISTS_0() test_00
//$ for i in $(seq 1 10); do echo "#define TEST_LISTS_$i() TEST_LISTS_$((i-1))(),$(printf "%02d" "$i")"; done
#define TEST_LISTS_1() TEST_LISTS_0(),test_01
#define TEST_LISTS_2() TEST_LISTS_1(),test_02
#define TEST_LISTS_3() TEST_LISTS_2(),test_03
#define TEST_LISTS_4() TEST_LISTS_3(),test_04
#define TEST_LISTS_5() TEST_LISTS_4(),test_05
#define TEST_LISTS_6() TEST_LISTS_5(),test_06
#define TEST_LISTS_7() TEST_LISTS_6(),test_07
#define TEST_LISTS_8() TEST_LISTS_7(),test_08
#define TEST_LISTS_9() TEST_LISTS_8(),test_09
#define TEST_LISTS_10() TEST_LISTS_9(),test_10
// etc.

// Then just reexpand the macro expansion with the prefix:
#define TEST_LIST_GEN_IN(x)  TEST_LISTS_##x()
#define TEST_LIST_GEN(x)  TEST_LIST_GEN_IN(x)

#define NUM_TESTS 10
void (*TESTS[NUM_TESTS])() = {TEST_LIST_GEN(NUM_TESTS)};

have it be automatically generated using NUM_TESTS since it's honestly always repetitive.

When using gcc compiler (or just a compiler that supports that) you can use a section and put pointers to tests in there.

#define XCONCAT(a, b) CONCAT(a, b)
#define CONCAT(a, b) a##b

// adds a pointer to the test function inside section tests
#define ADD_TEST(TEST_FUNCTION)  \
__attribute__((__section__("tests"))) \
void (*XCONCAT(_test_, __LINE__)) = TEST_FUNCTION

void test_01() {
    printf("%s\n", __func__);
}
// registers the test
ADD_TEST(test_01);

void test_02() {
    printf("%s\n", __func__);
}
ADD_TEST(test_02);

int main() {
    extern void (*__start_tests)();
    extern void (*__stop_tests)();
    int count = &__stop_tests - &__start_tests;
    printf("%d\n", count);
    for (int i = 0; i < count; ++i) {
        (&__start_tests)[i]();
    }
}
// will print 2 test_01 test_02

Upvotes: 1

Related Questions