Unit Test How? Infinite Loops

This is part of an ongoing series of articles about using Unity to Unit Test C applications, often for Embedded Applications. If you're looking for other tips, be sure to check out the others here.

Today we're going to talk about infinite loops. While they might strike fear into the hearts of many a software developer, we embedded software types know that they are just darn useful in the right place. We use them to control our main loops. We use them to trap errors that we want to halt everything for. We use them to build realtime operating systems. Sometimes a well-placed infinite loop is the best tool for a job.

But, how do we test such a thing? If the loop is indeed infinite, it's never going to return for our test to actually finish. Clearly, a little hack is required.

If your mind jumped to our friend the TEST macro, your intuition served you well. If not, well, we'll keep working on it. We like to define the keyword TEST when compiling for all of our tests. This allows us to make an occasional tiny change in our release code. We try not to abuse this power... after all, if our code changes much during a test, it's not really a valid test, right?

But, an infinite loop is a great place to apply our powerful TEST define friend. We're going to create a macro called FOREVER. During a release, FOREVER is going to be defined as 1. This allows us to easily make infinite loops, like this:

void InfiniteLoopBelow(void)
{
  while(FOREVER())
  {
    //do stuff until the batteries die
  }
}

During a release, this translates to while(1) which will always evaluate to true, and will therefore always run. 

But what do we do with this macro during a test? Here are three different ways you could use it to solve your problem. Choose the one that fits best for you.

RUN ONCE

The simplest approach is to make it so we always run our loop contents only once during tests. This is fairly straightforward. Instead of structuring the loop as we did above, we use this form:

do {
  //do stuff until the power company shuts us down
} while (FOREVER());

When our loops are shaped like this, we can write tests that just redefine FOREVER to be 0 during a test. This will cause us to only run the loop once.

#ifndef TEST
#define FOREVER()1
#else
#define FOREVER()0
#endif

COUNTER

A more flexible solution is to replace our FOREVER call with a post-decrementing variable. Something like this:

#ifndef TEST
#define FOREVER()1
#else
extern int NumLoops;
#define FOREVER() NumLoops--
#endif

Then, in our test, we declare an instance of this variable. We can then sprinkle it about in our tests as needed.

int NumLoops;

void setUp(void)
{
  //Assume no loops unless we specify in a test
  NumLoops = 0; 
}

void tearDown(void)
{
  TEST_ASSERT_EQUAL(0, NumLoops, "Forever called unexpected number of times);
}

void test_SillyStuff(void)
{
  NumLoops = 5; 
  CallFuncUnderTest();
  //It should iterate over the internal FOREVER loop 5 times
  //Otherwise tearDown will throw an error for us
}

This is a useful and simple way to control the number of times we execute a loop. It also gives us the ability to verify that the loop was called as many times as expected (that we didn't secretly get another a way out early).

CMOCK

The most advanced technique is to use CMock. Like the others, it still involves using our handy macro. This time, though, we put our macro in a mockable release header file. 

#ifndef TEST
#define FOREVER()1
#else
int FOREVER(void);
#endif

As you can see, if we have TEST defined, we have a function prototype. If we tell CMock to mock this file, CMock is going to notice this function prototype and create mock for it. It doesn't matter that under normal circumstances FOREVER() always returns 1... an integer is just like a function that returns that int, just more efficient. Now that the function is mocked, we have full control of what it returns through CMock.

#include "MockUtils.h"
void test_SillyStuff(void);
{
  FOREVER_ExpectAndReturn(1);
  //Other Expectations and Stuff
  FOREVER_ExpectAndReturn(1);
  //Other Expectations and Stuff
  FOREVER_ExpectAndReturn(0);
  //Expectations for Anything AFTER the Loop!

  CallFunctionUnderTest();
}

There we are. Three different approaches to solving the infinite loop problem. There likely are others, but these three have served me well. 

Happy Coding!