0

I have read that C does not support dynamic function calls. My program has an ever growing number of test cases implemented as separate functions like -

int testcase1(void);
int testcase2(void);
int testcase3(void);

Each time I add a new test case, I also have have to add the call to my main function like -

int main(int argc, char **argv){
  assert(!testcase1());
  assert(!testcase2());
  assert(!testcase3());
}

I would prefer to call something like assert(!testcase*()) where * matches any string which resolves to a valid function name in my program.

Can you think of a more convenient solution?

Community
  • 1
  • 1
user2309803
  • 541
  • 5
  • 15
  • Shouldn't those prototypes rather be `bool testcase1 (void);`? – Lundin Oct 31 '16 at 07:38
  • Time to consider a proper unit testing framework. – taskinoor Oct 31 '16 at 07:44
  • 1
    POSIX does provide [`dlsym()`](http://man7.org/linux/man-pages/man3/dlsym.3.html), which looks up dynamic symbols at run time. It (and `dlopen()`) is often used to provide run-time plugin functionality in POSIXy operating systems (i.e., nowadays basically everywhere except Microsoft Windows). – Nominal Animal Oct 31 '16 at 10:39
  • @taskinoor I did consider a unit testing framework but I want my code and test cases to be as portable as possible i.e Can you recommend one that runs on Linux, Windows, OSX, iOS, Android, etc? – user2309803 Oct 31 '16 at 20:46
  • @Nominal Animal - your solution looks good, at least for platforms that support shared libraries / DLLs – user2309803 Oct 31 '16 at 20:48
  • @Lundin, int prototype - I try to stick with [POLA](https://en.wikipedia.org/wiki/Principle_of_least_astonishment) design principals and use exit status 0 to indicate success. The result of `!(integer)` is 1 if the integer operand equal 0. I agree with you that I meant `testcase1 (void)`; i.e no parameters – user2309803 Oct 31 '16 at 20:56
  • @user2309803 That's a strange rationale, given that bool is more intuitive than int. It can only be true or false, unlike int that can be anything. – Lundin Nov 01 '16 at 07:10
  • 1
    @Lundin - What is intuitive is always subjective. I am more interested in avoiding code with undefined behavior. According to [ISO/IEC 9899:201x 7.2.1.1](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf) assert's operand should be a scalar expression which would include `!(integer)`. – user2309803 Nov 01 '16 at 20:02
  • 1
    @user2309803 Widely recognized coding standards like MISRA-C frown at the use of `int` for boolean expressions, so it isn't really subjective, it is industry de facto standard. – Lundin Nov 02 '16 at 08:40
  • @Lundin - are you saying it is wrong to use an `int` whose value neither 0 nor 1 as the argument for the `assert` macro? or are you saying my testcase functions should not return a zero value as an argument for `assert` to indicate a successful test result? – user2309803 Nov 09 '16 at 20:02

4 Answers4

7

If you all your testcases have same signature then you can use an array of function pointers:

void (*func[])() = { testcase1, testcase2 };

for (size_t i = 0; i < sizeof(func)/sizeof(func[0]); i++) {
   assert(!func[i]());
}
P.P
  • 117,907
  • 20
  • 175
  • 238
  • I had considered using function pointers, but I would have to manually update the `void (*func[])()` intialization every time I add new test case (or change it's name) - so I don't see any advantage over my current `assert(!testcasennn())` repetitive approach. – user2309803 Oct 31 '16 at 19:53
  • There's no "regex" kind of way to call *all* testcase functions automatically. It's true you still have to update the array. But that's easier to manage than having a whole bunch of function calls. In short, this is probably the way to do it in C. Otherwise, you can look at unit testing frameworks such as CPPunit. – P.P Oct 31 '16 at 19:56
  • Well, there is x macros. Which will solve the "problem" of having to write a function name at two places instead of one . They will however turn the code into an unreadable mess. A function pointer array is much more elegant. – Lundin Nov 02 '16 at 07:13
1

The best solution is likely to write a few extra lines of code when you add new test cases - it really isn't a big issue. I would recommend something along the lines of the function pointer array, as suggested in another answer.

However, just to show that everything is possible in C if you throw ugly macros at the problem, here is a not recommended alternative:

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>

#define TEST_CASES \                    // list of "x macros"
  X(testcase1) \
  X(testcase2) \
  X(testcase3)

#define X(func) bool func (void);       // declare function prototypes
  TEST_CASES
#undef X

bool (*const test_cases[])(void) =      // array of read-only function pointers
{
  #define X(func) &func,                // point at each function
    TEST_CASES
  #undef X
};

int main (void)
{
  for(size_t i=0; i<sizeof(test_cases)/sizeof(test_cases[0]); i++)
  {
    assert(test_cases[i]());
  }
}

bool testcase1 (void) { puts(__func__); return true; }
bool testcase2 (void) { puts(__func__); return true; }
bool testcase3 (void) { puts(__func__); return false; }

Output:

testcase1
testcase2
testcase3
Assertion failed!

For each new test case, you would only have to write a function definition and then add it to the "x macro" list TEST_CASES. However, you need very good reasons to introduce ugly tricks like these in production code!

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Interesting solution. You say it's no big issue to type a few extra lines of code, but if those extra lines are (1) transformed from the original lines by a unchanging rule (for example, the rule is - after adding implementation of `int testcaseNNN(void)`, then add call to `testcaseNNN()`) (2) need to be synchronised with the original lines (for example, I decide to rename `testcase1` as `testcaseInsertRecord`, then I have to similarly rename each call of testcase1), then I think it is best to avoid it. Alternatively, managing it with a program is better than an error prone human. – user2309803 Nov 09 '16 at 21:15
0

You can use function pointers. Read also about closures (but C99 or C11 don't have them) and callbacks.

Many operating systems provide dynamic loading. On POSIX operating systems (such as Linux or MacOSX) you can get a function pointer (actually an address) from its name in some library (or in the program executable) using dlopen & dlsym. Other operating systems may provide similar functionalities.

At last, you should consider having your testing main function be generated by some script (or some program emitting C code), using metaprogramming techniques. So you would write something which generates the C code of your testing main having a long sequence of assert, and improve your build procedure (e.g. your Makefile if using make) to run appropriately that specialized C code generator. Details are of course specific to your code. You might add some conventions (e.g. add some special comment to be parsed by your test generator, etc...).

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
0

I decided to follow @Nominal Animal and @Basile Starynkevitch's approach. In mymainprog.c, I added -

int runtests(void){
    void *testh;
    int (*testp)(void);
    char *dlmsg;
    int rc;
    char funcname[8];
    int testnum;

    testh = dlopen("libsmtests.so", RTLD_LAZY);

    if (!testh){
        printf("%s\n", dlerror());
        return 1;
    } 
    dlerror();
    for (testnum =1; testnum < 1000; testnum++){
        sprintf(funcname,"testcase%d", testnum);
        *(void **) (&testp) = dlsym(testh, funcname);
        dlmsg = dlerror();
        if (dlmsg == NULL) {
            rc = (*testp)();
            printf("%s called, rc=%d\n", funcname, rc);
        }
    }
    dlclose(testh);
    return 0;
}

I add my testcases to a separate file (testcases.c) like this -

int testcase1(void){
    return [some testcase expression]
}   
int testcase2(void){
    return [another testcase expression]
}

and then compile it as a shared library with position-independant code (-fPIC) to libsmtests.so. The advantage is slightly less typing since I don't need to code a call to testNNNN() after adding the implementation of a new functionint testcaseNNN(void) to testcases.c

user2309803
  • 541
  • 5
  • 15