Taylor Price
Taylor Price

Reputation: 642

C Unit Testing - Returning from a stubbed graceful exit routine

Here is the scenario I've got. The function I'm testing has an error condition that, if hit, calls a graceful exit function to free any global memory, close handles, and exit the program.

Obviously, I'll want to write a test that tickles this condition to make sure that it is handled correctly but I don't want the graceful exit routine to actually exit the program since that would stop any remaining tests. This means stubbing the graceful exit routine. The problem with stubbing and not calling exit is that the flow of control returns to the function under test (which is bad since the routine was supposed to exit).

Here is the actual question: How do we return control from the stubbed function to the test instead of to the function under test?

I can do a setjmp / longjmp, but since "gotos" are bad in general, I'd love any other suggestions. (Keep in mind that this is procedural C, not C++, so exceptions aren't going to work as far as I know)

EDIT As Soren and others have suggested below, making exit do nothing when testing is a great idea. There are several ways to do this whether it be through a #define statement or a stub for the exit() routine.

HOWEVER, doing so presents the problem that I'm really after a solution for (other than setjmp / longjmp). Take a look at this scenario:

void gracefulExit() {
    // Clean Up
    exit();
}

void routineUnderTest() {
    // Do some calcs

    if (someBadCondition == TRUE)
        gracefulExit()

    // Do some more calcs
}

If exit() does nothing in this scenario, gracefulExit() will return control back to the routine under test, which should not happen. Hence, I need a way to make exit() (or the stubbed version of gracefulExit()) return control to the test instead of the function under test.

setjmp / longjmp (aka goto) is a way to do this, although not really an elegant way. Any ideas on how to solve that?

EDIT #2

As fizzer mentioned, setjmp/longjmp is a valid way to handle this situation. It is the likely way that I will handle it permanently.

I have, however, received another possible solution from a co-worker. Instead of #defining the gracefulExit() routine to a stub routine, do the following:

#define gracefulExit return NULL

The particular function under test handles this just fine since NULL is a valid return value for it. I haven't tested this in every possible case yet (e.g. a function that has a void return value). As mentioned earlier, I will likely use the setjmp/longjmp way of solving this problem, but if this additional solution sparks an idea for somebody, great!

Upvotes: 6

Views: 864

Answers (5)

Andrew Edgecombe
Andrew Edgecombe

Reputation: 40382

You may be able to achieve this through the use of weak symbols, if your platform supports them.

You would write two versions of the graceful_exit() function, one used by the normal application defined as a weak symbol, and another used when running under your unit-test framework defined normally.

The intention would be that if you are linking against your unit-test code that the strong version of the function would be used instead of the weak version.

This has the benefit that no conditional compilation is required to support your unit-testing.

There is a useful discussion on the use of weak symbols in this question.

Upvotes: 1

Soren
Soren

Reputation: 14708

I would suggest your clean-up method creates an unpublished interface to do the exit.

so

   void (*exitfunc((int) = exit;
   void myCleanUp() {
      .... do the cleanup
      (*exitfunc)(-1); // this will in normal operation call exit
   }

and in your unit test code, you "override" the exit function as

   void donothing(int exitcode) {}
   unittest(){
      extern void (*exitfunc((int);
      exitfunc = donothing; // or use a longjump if clean exit cannot be made without
      ... do the test.....

This would mean that your code compiles to the same whether in a unit test or somewhere else, and the difference in behaviour is only occuring when you execute the unit-test. The alternatives using conditional compiling for unit testing means that you will end up with .o files where you don't know if they were intended for test or production, and you will have bad stuff happening to your project.

Upvotes: 3

fizzer
fizzer

Reputation: 13806

I use setjmp / longjmp (wrapped in a convenience macro) for this and similar scenarios (e.g. test that an assertion failed).

Upvotes: 1

mgalgs
mgalgs

Reputation: 16789

like jonfen said, I think that using the preprocessor is the way to do it. Something like:

if (bad_stuff_happened) {
  do_cleanup();
#ifdef UNIT_TEST
  return;
#endif
}

Upvotes: 0

feathj
feathj

Reputation: 3069

You might consider using a #define block to specify when your "cleanup and exit" routines are run and only compile this in when building for production or release. That way, you could still see if the condition hits, but it won't actually execute the code.

Upvotes: 0

Related Questions