Quantcast
Channel: James Grenning's Blog » CppUTest

Zune Bug: Test Driven Bug Fix

$
0
0

The Microsoft Zune 30G had a well known crash to bring in the new year. Here is the snippet of code that is the alleged culprit, from one of MS’s suppliers (Freescale).

The job of this function is to convert the input days to the current day, month, year, and day of the week. The input, days, is the number of days since January 1, 1980.

BOOL ConvertDays(UINT32 days, SYSTEMTIME* lpTime)
{
    int dayofweek, month, year;
    UINT8 *month_tab;
 
    //Calculate current day of the week
    dayofweek = GetDayOfWeek(days);
 
    year = ORIGINYEAR;
 
    while (days > 365)
    {
        if (IsLeapYear(year))
        {
            if (days > 366)
            {
                days -= 366;
                year += 1;
            }
        }
        else
        {
            days -= 365;
            year += 1;
        }
 
    }
 
    // Determine whether it is a leap year
    month_tab = (UINT8 *)((IsLeapYear(year))
                      ? monthtable_leap : monthtable);
 
    for (month=0; month<12; month++)
    {
        if (days <= month_tab[month])
            break;
        days -= month_tab[month];
    }
 
    month += 1;
 
    lpTime->wDay = (WORD) days;
    lpTime->wDayOfWeek = (WORD) dayofweek;
    lpTime->wMonth = (WORD) month;
    lpTime->wYear = (WORD) year;
 
    return TRUE;
}

Isn’t this exactly the kind of problem that QA is supposed to find? Someone at least should have advanced the clock to check that leap year was handled. Ahhh, but it was released in 2006, who would be thinking of leap year :-) Date problems have been out since the start of the millennium!

Like most bugs, they have simple roots. But some have dire consequences. Thankfully this bug did not reveal itself in some safety critical device. Or maybe there are safety issues for a Zune. Not having music for the New Years Eve party might drive someone to do things they normally would not.

As it happens, what seems to be a simple typo leads to an infinite loop on the last day of a leap year. This code had never seen the last day of a leap year, until it saw December 31, 2008 in real time!

It looks to me like defensive coding coupled with a missed keystroke are at least partially to blame. My hypothesis is that “if (days > 366)” should have been “if (days >= 366)”. My guess is the well-intentioned programmer did not press that “=” key quite hard enough. I used to work with a guy who spent seeks looking for these little typos. These boundaries are never as easy as they look.

Test Driven Bug Fixing

Let’s just say that from now on, if you need to fix a bug, you first demonstrate the bug with some unit tests. I’ll test drive fixing this bug. I first have to have an idea of how to test this problem area. If I can feed in the days parameter, I can test the behavior of various days.

To test the specific failure date, I need to feed in the number of days that represents December 31, 2008, the day of the Zune crash. Where will I get the number of days since 1980 for the crash date? Counting them manually sounds like a drag. I need a helper function to generate my input test data. I am not sure how to code the helper yet, but to get my TDD legs under me I write this test…

TEST(Rtc, Days1980to1981)
{
    days = daysSince1980(1981);
    LONGS_EQUAL(366, days);
}

1980 was a leap year, so the days from the start of 1980 to the start of 1981 should be 366 days. I can get that to pass with a simple hard coded return result.

What would be the answer for January 1, 1982? I think I’ll add a second parameter to represent the day of the year. This test expresses it.

TEST(Rtc, Days1980to1982)
{
    int days = daysSince1980(1982, 1);
    LONGS_EQUAL(366+365+1, days);
}

In the production code there is an IsLeapYear() function. It’s a static function; I expose it for test purposes. Here is the test data generator that passes those two tests.

int daysSince1980(int endYear, int dayOfYear)
{
    int days = 0;
 
    for (int year = 1980; year < endYear; year++)
    {
        days += 365;
        if (IsLeapYear(year))
            days++;
    }
    return days + dayOfYear;
}

Now a little experiment to get the days up until just before the Zune bug date.

TEST(Rtc, calcDaysFor20081229)
{
    int days = daysSince1980(2008, 364);
 
    LONGS_EQUAL(2, days);
}

The failing test reports the actual number of days: 10591. I get the calculator out and do a sanity check… 10591/365.25 -> 28.996 and then some. It could be right. That’s almost 29 years. Let’s write a test and check all the other data about this number of days. It should be Tuesday, 12-Dec-2008. Notice this checks the day before the problem day. It seems that the week starts on Monday, with day == 1.

TEST(Rtc, check20081229)
{
    days = daysSince1980(2008, 364);
 
    ConvertDays(days, &time);
    LONGS_EQUAL(MON, time.wDayOfWeek);
    LONGS_EQUAL(29, time.wDay);
    LONGS_EQUAL(12, time.wMonth);
    LONGS_EQUAL(2008, time.wYear);
}

I’ll be writing a few more tests like this, so I might as well add a helper function to do the asserts, and add an enum to make day of the week more readable. All the tests so far have an “int days” so it should become part of the test fixture. I’ll show the whole fixture when we’re done.

..snip..
enum Day {MON=1, TUE, WED, THU, FRI, SAT, SUN };
..snip...
 
void assertDate(Day dayOfWeek, int day, int month, int year)
{
    LONGS_EQUAL(dayOfWeek, time.wDayOfWeek);
    LONGS_EQUAL(day, time.wDay);
    LONGS_EQUAL(month, time.wMonth);
    LONGS_EQUAL(year, time.wYear);
}

If the bug report is correct, this test should never return. Eclipse/cdt is not going to like this. I have eclipse running my tests with every file save. Eclipse does not like makefiles that never return.

TEST(Rtc, check20081231)
{
    days = daysSince1980(2008, 366);
 
    ConvertDays(days, &time);
    assertDate(WED, 31, 12, 2008);
}

Sure enough, it’s hung! There is no killing it from eclipse. .. Be right back..

I’ll rig this code so it can’t get stuck. Conveniently enough this function has a BOOL return type, but currently only returns TRUE, or not at all! I’ll add in a safety relief valve.

BOOL ConvertDays(UINT32 days, SYSTEMTIME* lpTime)
{
    int safetyReleaseValve = 0;
    ..snip..
 
    while (days > 365)
    {
        if (IsLeapYear(year))
        {
            if (days > 366)
            {
                days -= 366;
                year += 1;
            }
        }
        else
        {
            days -= 365;
            year += 1;
        }
 
        if (safetyReleaseValve++ > 32000)
            return FALSE;
    }
    ..snip..

When the valve is triggered, it returns FALSE. I can test for that.

TEST(Rtc, check20081231)
{
    days = daysSince1980(2008, 366);
 
    CHECK(ConvertDays(days, &time));
    assertDate(WED, 31, 12, 2008);
}

The valve releases and the test fails. Now the popular fix for the problem is to change “if (days > 366)” to if “(days >= 366)”. Let’s try it…

tests/zune/RtcTest.cpp:39: error: Failure in TEST(Rtc, check20081231)
    expected <31 0x1f>
    but was  < 0 0x00>

Adding the “=” is part of the fix. It no longer blows the pressure relief valve, but the Zune reports the wrong day of the month, unless the 31-Dec-2008 is somehow interpreted as 0-Dec-2009! Interesting things happen at boundaries. That’s why you better test them.

The algorithm needs to exit on 366, the last day of the leap year. It should transition the year on 367 or greater.

while (days > 365)
{
    if (IsLeapYear(year))
    {
        if (days >= 367)
        {
            days -= 366;
            year += 1;
        }
        else
            break;
    }
    else
    {
        days -= 365;
        year += 1;
    }
 
    if (safetyReleaseValve++ > 32000)
        return FALSE;
}

It works, but it’s not pretty. In another blog, I’ll refactor this function to get it to better tell its story. But, we need a couple more tests. The zune won’t have to live through the year 2000. But would it?

TEST(Rtc, check20001231)
{
    days = daysSince1980(2000, 366);
 
    CHECK(ConvertDays(days, &time));
    assertDate(SUN, 31, 12, 2000);
}

This test fails the first time. It turns out that days of the week start with Sunday (==0).
After fixing the enum the test passes. What about the 1-Jan the day after a leap year ends?

TEST(Rtc, check20090101)
{
    days = daysSince1980(2009, 1);
 
    CHECK(ConvertDays(days, &time));
    assertDate(THU, 1, 1, 2009);
}

That’s good too. But those dates are all in the past. The Zune needs to work in the future. What happens at the end of this year and the next leap year? It’s so easy to find out now that the test fixture is in place.

TEST(Rtc, check20091231)
{
    days = daysSince1980(2009, 365);
 
    CHECK(ConvertDays(days, &time));
    assertDate(THU, 31, 12, 2009);
}
 
TEST(Rtc, check20100101)
{
    days = daysSince1980(2010, 1);
 
    CHECK(ConvertDays(days, &time));
    assertDate(FRI, 1, 1, 2010);
}

And the next leap year.

TEST(Rtc, check20121231)
{
    days = daysSince1980(2012, 366);
 
    CHECK(ConvertDays(days, &time));
    assertDate(MON, 31, 12, 2012);
}
 
TEST(Rtc, check20130101)
{
    days = daysSince1980(2013, 1);
 
    CHECK(ConvertDays(days, &time));
    assertDate(TUE, 1, 1, 2013);
}

I can be confident in the day to date conversion now, at least with regards to leap year. I am not sure that inspections alone would find this problem. Even if one does, the only sure way to know if code works is to run it, testing that it meets its requirements.

I hear all the time that low-level device driving code cannot be tested in a platform independent way. Sure there are parts of a driver that can only be tested in the real hardware, but that excuse does not work here.

It only took me 30 minutes to write these tests. I bet the original developer spent more than 30 minutes manually testing this code. Look at the results: the developer spent time manually testing, and a critical bug gets through anyway.

There is quite a bit more test code than production code. So What. It makes you go faster and helps avoid bugs.

The final test fixture looks like this:

#include "CppUTest/TestHarness.h"
#include <memory.h>
 
extern "C"
{
#include "Rtc.h"
}
 
enum Day
{
    SUN = 0, MON, TUE, WED, THU, FRI, SAT
};
 
TEST_GROUP(Rtc)
{
    SYSTEMTIME time;
    int days;
 
    void setup()
    {
        memset(&time, 0, sizeof(time));
    }
 
    void teardown()
    {
    }
 
    int daysSince1980(int endYear, int dayOfYear)
    {
        int days = 0;
 
        for (int year = 1980; year < endYear; year++)
        {
            days += 365;
            if (IsLeapYear(year))
                days++;
        }
        return days + dayOfYear;
    }
 
    void assertDate(Day dayOfWeek, int day, int month, int year)
    {
        LONGS_EQUAL(dayOfWeek, time.wDayOfWeek);
        LONGS_EQUAL(day, time.wDay);
        LONGS_EQUAL(month, time.wMonth);
        LONGS_EQUAL(year, time.wYear);
    }
};

The production code shown in this article the property of Freescale semiconductor.


Legacy Code Change – a Boy Scout Adds Tests

$
0
0

Here is a legacy code change policy for a team adopting TDD that has a legacy code base:

  • Test-drive new code
  • Add tests to legacy code before modification
  • Test-drive changes to legacy code

Refactoring without tests is dangerous; with all the details we must keep straight, a mistake is easy to make. How many code reviews have you been in where the recommended design changes are not made because “we already tested it”? You avoid the change because it’s dangerous to change code without tests. So, the Boy Scout adds tests too. For more on Boy Scouts, see previous post.

Test-Drive New Code

When you are adding functionality and some or all of it can be implemented in a new function or module, create the code using TDD (In Working Effectively with Legacy Code, Michael Feathers calls this Sprouting.). Then the newly test-driven code can be called as need from the existing legacy code. Michael says “You might not be able to get all those call points under test easily, but at the very least, you can write tests for this new code.” This typifies the Boy Scout approach to always working to make the code a little better than you found it.

Sprouting can be very safe when operations done by the sprouted code do not impact the flow of control of the calling code. When return results are used in conditionals and data structures are modified, then sprouting might not be enough; some behavior preserving tests for the legacy code might be needed also. Where did this policy come from? Good policies come from good principles like the Boy Scout Principle.

Add Tests to Legacy Code Before Modification

Adding tests is easy to say, but not so easy to do when your legacy code was not designed to be tested. Michael wrote a whole book on the topic. You will find yourself in the position of having to make some changes without tests to get the code testable. Do safe refactorings, if there is such a thing. Some reasonably safe changes are adding sense points, or sensing variables. Also it would certainly help to with a partner.

In C, converting a direct call to a call through a function pointer is a pretty safe way to create a test point in otherwise tough to test code. Converting to a function pointer is not so safe when your code is not warning free and some of the callers don’t get to see the function prototype. (Bad things will happen when a direct call is made where an indirect call is what is implemented. I suggest warning free code.)

Don’t feel left out non-C programmers; you can override individual member functions by subclassing your legacy object.

Let’s say your legacy C code already does some printing. We could intercept and capture everything being printed with a spy. Here’s an example of refactoring a direct call to a function pointer, and having a spy infiltrate the legacy code:

//Original prototype from FormatOutput.h
 
int FormatOutput(const char *, ...);
//Original implementation from FormatOutput.c
 
int FormatOutput(const char *, ...)
{
  //snip.
}

Change the signature to be a function pointer, make it extern.

//Modified prototype from FormatOutput.h
 
extern int (*FormatOutput)(const char *, ...);

Rename the implementation, hide the name using static so no code calls it directly. Then initialize the pointer to your implementation.

//Modified implementation from FormatOutput.c
 
static int FormatOutput_Impl(const char * format, ...)
{
    //snip
}
 
int (*FormatOutput)(const char* format, ...) = FormatOutput_Impl;

Now FormatOutput can be overridden during the Setup phase of the test. Here is an example using CppUTest.

TEST_GROUP(ThingBeingTested)
{
    //snip
    void setup()
    {
        UT_PTR_SET(FormatOutput, FormatOutputSpy);
        //snip
    }
 
    void teardown()
    {
        //snip
    }
};

The FormatOutputSpy can capture everything printed, so that tests can inspect what is printed. Notice that Boy Scouts also return function pointers to their original value after each test case runs. In CppUTest, UT_PTR_SET macro overrides a function pointer. During teardown() overridden function pointers are restored.

Test-Drive Changes to Legacy Code

Once you have a few tests in place, the clean up can start. Then you can get to add the new functionality that got you to this code in the first place. The Boy Scout will test-drive in the new functionality.

Don’t feel left out ladies, Girl Scouts add tests too.

Spying on Embedded ‘asm’ directives

$
0
0

Sometimes embedded developers have to use inline assembler instructions to get better control of the processor, or to improve performance. How should we deal with those when we’re doing TDD and testing off the target?

What’s the problem? The embedded asm statements cause compilation errors if the assembler instructions are not part of the off-target test platform instruction set. Also some of the instructions might not be legal in the test environment. This article shows how to insert a test double for the asm directives with gcc and CppUTest.


In this example, I’ll assume that whatever the asm instructions were supposed to do, that they are not needed to run in the test environment. We’ll look at two alternatives: ignoring asm directives and spying on them.

Here is example production code using the asm directive:

//snip...
do
{
  asm("NOP");
  asm("FLOP");
  asm("POP");
  status = IORead(0);
} while ((status & ReadyBit) == 0);

For some reason NOP, FLOP and POP need to be executed before an I/O read operation. in the unit test environment we don’t want them assembled or executed.

The simplest thing to do is to make asm go away with the preprocessor. We can force the preprocessor to include a file that makes asm go away. If you are using the CppUTest makefile system you can add this preprocessor directive:

  CPPUTEST_CFLAGS += -include mocks/NullAsm.h

NullAsm.h would simply be this:

#define asm(x)

This macro makes asm and its parameters go away. There might be a gcc command line option to ignore asm also, but I could not find one.

But maybe you want more than to ignore the asm directives. Let’s spy on them. AsmSpy captures the assembly instructions so that the test can check that the right instructions are specified to execute.

Let’s look at the tests for the AsmSpy.

#include "CppUTest/TestHarness.h"
 
TEST_GROUP(AsmSpy)
{
    void setup()
    {
      AsmSpy_Create(20);
    }
 
    void teardown()
    {
       AsmSpy_Destroy();
    }
};
 
TEST(AsmSpy, CaptureAsmInstructions)
{
    AsmSpy("NOP");
    AsmSpy("FLOP");
    AsmSpy("POP");
    STRCMP_EQUAL("NOP;FLOP;POP;", AsmSpy_Debrief());
}

In setup the spy is created and given a capacity. This spy will remember a string of 20 characters of assembler. In the TEST, you can see that the spy separates each assembly string with a semicolon.

Now we’ve got to sneak the spy into my code. We do this with the preprocessor because asm is a keyword and we need text replacement. We can force the gcc compiler to include AsmSpy.h like this:

  CPPUTEST_CFLAGS = -include mocks/AsmSpy.h

This directive causes the AsmSpy to be the first include file of the compilied .c file. We don’t edit in the AsmSpy because we don’t want any evidence of the spy in the production code. Here is AsmSpy.h.

#ifndef D_AsmSpy_H
#define D_AsmSpy_H
 
#define asm AsmSpy
 
void AsmSpy_Create(int size);
void AsmSpy_Destroy(void);
void AsmSpy(const char *);
const char * AsmSpy_Debrief(void);
 
#endif

Notice #define asm AsmSpy. This causes asm directives to be converted to AsmSpy calls when we build for test. The production code only needs the AsmSpy prototype, but it does no harm to have the spy’s full interface.

AsmSpy.c looks like this:

#include "AsmSpy.h"
#include <string.h>
 
static char * instructions = 0;
static int capacity = 0;
 
void AsmSpy_Create(int cap)
{
    capacity = cap;
    instructions = malloc(capacity+1);
    instructions[0] = 0;
}
 
void AsmSpy_Destroy(void)
{
    free(instructions);
}
 
void AsmSpy(const char * i)
{
    if (strlen(i) + strlen(instructions) &lt; capacity)
    {
        strcat(instructions, i);
        strcat(instructions, ";");
    }
}
 
const char * AsmSpy_Debrief(void)
{
    return instructions;
}

Being that the asm code is machine dependent, we could also isolate this asm code in a separate machine dependent function, and then override that function with a linker test double. Then the preprocessor substitution would not be needed. I might go this route if the asm has to return some value, or I was using Extended Asm.

Mocking the ‘asm’ directive with CppUMock

$
0
0

My last article featured a hand crafted a spy to monitor asm directives. Now let’s use CppUMock (the mock support companion CppUTest) to create a mock version of asm.

Starting with the test (where else would you start), MockAsm works like this:

#include "CppUTest/TestHarness.h"
#include "CppUTestExt/MockSupport.h"
 
extern "C"
{
#include "MockAsm.h"
}
 
TEST_GROUP(MockAsm)
{
    void setup()
    {
    }
 
    void teardown()
    {
       mock().checkExpectations();
       mock().clear();
    }
};
 
TEST(MockAsm, CaptureAsmInstructions)
{
    mock().expectOneCall("asm")
          .withParameter("instruction", "NOP");
    mock().expectOneCall("asm")
          .withParameter("instruction", "FLOP");
    mock().expectOneCall("asm")
          .withParameter("instruction", "POP");
 
	MockAsm("NOP");
	MockAsm("FLOP");
	MockAsm("POP");
}

To get a successful build and link, you need to build CppUTest extensions. You can do this with make extensions. You will also have to tell CppUTest’s makefile system to use extensions by adding this to your makefile.

CPPUTEST_USE_EXTENSIONS = Y

Like the last example, use the preprocessor to sneak the mock into the production code by forcing each compilation unit to include MockAsm.h.

CPPUTEST_CFLAGS += -include mocks/MockAsm.h

MockAsm.h looks like this:

#ifndef D_MockAsm_H
#define D_MockAsm_H
 
#define asm MockAsm
 
void MockAsm(const char * instruction);
 
#endif

Here’s MockAsm.c.

#include "MockAsm.h"
#include "CppUTestExt/MockSupport_c.h"
 
void MockAsm(const char * instruction)
{
  mock_c()->actualCall("asm")
          ->withStringParameters("instruction", instruction);
}

That was easy.

#include Test Double

$
0
0

It’s day one of adding tests to your legacy C code. You get stopped dead when the compiler announces that the code you are coaxing into the test harness can’t be compiled on this machine. You are stuck on the Make it compile step of Crash to Pass.

Moving your embedded legacy C code (embedded C code without tests) into a test harness can be a challenge. The legacy C code is likely to be tightly bound to the target processor. This might not be a problem for production, but for off-target unit testing, it is a big problem.

For C we have a limited mechanisms for breaking dependencies. In my book, I describe at length link-time and function pointer substitutions, but only touch on preprocessor stubbing.

In this article we’ll look at #include Test-Double as a way to break dependencies on a problem #include file.

You have a header file, provided by your silicon vendor, that depends on the target. This is similar to one I ran into last week:

/*
 *  ---- acmetypes.h ----
 */
 
#ifndef _ACME_STD_TYPES
#define _ACME_STD_TYPES
 
#if defined(_ACME_X42)
    typedef unsigned int        Uint_32;
    typedef unsigned short      Uint_16;
    typedef unsigned char       Uint_8;
 
    typedef int                 Int_32;
    typedef short               Int_16;
    typedef char                Int_8;
 
#elif defined(_ACME_A12)
    typedef unsigned long       Uint_32;
    typedef unsigned int        Uint_16;
    typedef unsigned char       Uint_8;
 
    typedef long                Int_32;
    typedef int                 Int_16;
    typedef char                Int_8;
#else
    #error <acmetypes.h> is not supported for this environment
#endif
 
#endif  /* _ACME_STD_TYPES */

The C preprocessor stops when it gets to the #error. You might think you can just define _ACME_X42 or _ACME_A12, but you are testing off-target, and likely on a 64 bit machine (as I am at the time of this writing), and that can lead to problems where int size matters.

It is always best to include the production header file, but sometimes that won’t work, especially in legacy situations. We don’t have to give up though. We can employ a #include Test-Double.

In this case it will be pretty easy. We can define a new header file with the same name, and put it in the include path in front of the production code include. #include Test-Double looks like this:

/*
 * acmetypes.h - test double for off target testing
 */
 
#ifndef ACMETYPES_H_
#define ACMETYPES_H_
 
#include <stdint.h>
 
typedef uint32_t		Uint_32;
typedef uint16_t		Uint_16;
typedef uint8_t			Uint_8;
 
typedef int32_t			Int_32;
typedef int16_t			Int_16;
typedef int8_t			Int_8;
 
#endif /* ACMETYPES_H_ */

Off target you have your development system’s native implementation of stdint.h. It is the standard way to deal with specific sized ints when you must. Above we just redefined the Acme types in therms of the portable types.

If you are using CppUTest and its makefile system, you can direct the makefile to look at your test-double header files before the production code header files, like this:

INCLUDE_DIRS =\
  .\
  include \
  include/* \
  $(CPPUTEST_HOME)/include/ \
  mocks/includes \
  $(ACME_INCLUDES) \

Point $(ACME_INCLUDES) at the silicon vendor dependent include directory. Place the test-double header file in some directory like mocks/includes. As long as mocks/includes is before $(ACME_INCLUDES) in the INCLUDE_DIRS, the test-double will take the place of the production code header file. During the production build mocks/includes will not be part of the include path.

You don’t need to use CppUTest’s makefile system for this. All compilers I am aware of use the include path approach.

To make sure this all works as we expect, we can also add this test case.

extern "C"
{
#include "acmetypes.h"
}
 
#include "CppUTest/TestHarness.h"
 
TEST_GROUP(acmetypes) {};
 
TEST(acmetypes, checkIntSizes)
{
	LONGS_EQUAL(1, sizeof(Uint_8));
	LONGS_EQUAL(1, sizeof(Int_8));
	LONGS_EQUAL(2, sizeof(Uint_16));
	LONGS_EQUAL(2, sizeof(Int_16));
	LONGS_EQUAL(4, sizeof(Uint_32));
	LONGS_EQUAL(4, sizeof(Int_32));
}

There are other situations where a #include stub can be used. For example break a long chain of #includes where you just need a couple symbols. But keep in mind, it is still better to use the production code header when you can.

If this helped, tell me about it. If I’m missing something, tell me that too.

Hiding Non-standard C Keywords for Off-Target Testing

$
0
0

Some silicon vendors extend the C language so the programmers can easily interact with the silicon. Using these extensions tie production code to the silicon vendors compiler and consequently the code can only run on the target system. This is not a problem during production, but is a problem for off-target unit testing.

The good news is that we may be able to get around this problem without having to change production code, one of our goals when adding tests to legacy code.

Two common offenders are specialized C adornments like cregister and interrupt. cregister might be used like this

//snip...
/* Address Mode Register */
extern cregister volatile unsigned int AMR; 
//snip...

interrupt is used like this:

//snip...
interrupt void one_ms_tic(void)
{
  ...
}
//snip...

These are pretty easy portability problems to get rid of. Use the C preprocessor to define cregister and interrupt to be nothing when compiling for off target execution with an include file like this:

#define cregister
#define interrupt

Now we have to go add this as the first line in every source file. What! never! you say. Yes that is too much trouble; let’s let the preprocessor do it for us using a forced include. If you are using CppUTest with the GCC toolchain, add this to your CppUTest based Makefile.

  CPPUTEST_CPPFLAGS += -include mocks/hideStuff.h

If you are not using CppUTest, but are using GCC, add the forced include to the CPPFLAGS variable or somehow get it to your compilation command line.

If you are off-target testing with some form of Visual Studio, it too has a forced include control in the project settings.

I’ve shown another example of using forced includes in Spying on Embedded ‘asm’ directives.

OK, so we have work arounds for header file dependencies on the target compiler. I am hoping that you also know that you should limit your production code dependencies on the target-only specific constructs.

If this helped, tell me about it. If I’m missing something or there is a better way, tell me that too.

Unit testing RTOS dependent code – RTOS Test-Double

$
0
0

When you’ve got legacy code that depends on the Real-time Operating System, you have a challenge to get your code off the target for unit testing. If you want to test with the concurrency provided by the RTOS, these are not really unit tests, and you won’t be able to write thorough tests, and you need unit tests to be through.

You’re going to need to make a test-double so you can get your code off the RTOS and into your development system for unit testing. In this article we’ll go through the steps to get started.


Let’s consider this scenario: Our legacy code waits on a semaphore for some condition to occur. In a concurrent application, one thread waits while an interrupt routine or another thread satisfies the need. For example, a message processor waits for a line of text to be entered. Under the hood, there is a UART serviced by an interrupt. The interrupt saves characters until the line terminator is detected and then posts to the semaphore, waking the message processor.

To unit test the message processor, we can introduce a test-double for the semaphore pend operation using the linker. In the test-double we can put the message string into the buffer that we want the message processor to process.

So, from the message processor point of view, nothing is changed. It waits on the semaphore and processes the message when it wakes up.

To make this vision come true, we need an RTOS test-double. So let’s test drive that using the Crash to Pass algorithm. The tests will guide us, and document the test-double.

First, we have to create a test case for the test-double, that calls some RTOS function. I am going to use the Micrium uCos-II RTOS in my example. The fist test-double we need is OSSemPend(), that is defined in the ucos_ii.h. This test drives me to discover the include dependencies that come with ucos_ii.h.

#include "CppUTest/TestHarness.h"
 
extern "C"
{
#include "ucos_ii.h"
}
 
TEST_GROUP(uCosIITestDouble)
{
    void setup() { }
    void teardown() { }
};
 
TEST(uCosIITestDouble, OSSemPend)
{
    FAIL("tilt");
}

This fails because the compiler can’t find ucos_ii.h. Now I go to the command line an look for this file in RTOS directory using the unix find command.

$ find . -name "ucos_ii.h"
./uCOS-II/Micrium/uCOS-II/Source/ucos_ii.h
$

I can grab the output line and paste it right into your makefile or IDE include path. Here is what I put in my makefile:

    $(MICRIUM_HOME)/uCOS-II/Micrium/uCOS-II/Source/  

MICRIUM_HOME points to the base Micrium directory. The next build causes a bunch more failures. Don’t be fooled, only look at the first error or errors, which in this case is other include files that cannot be found (app_cfg.h, os_cfg.h). So we search again like this:

$ find . -name "app_cfg.h"
./App/app_cfg.h
$ find . -name "os_cfg.h"
./App/os_cfg.h

Add $(MICRIUM_HOME)/App to the include path. Now os_cpu.h can’t be found.

If you are compiling with gcc, it helps to set these preprocessor flags:

    -Wfatal-errors

That makes the compiler stop complaining after the first compilation error.

$ find . -name "os_cfg.h"
./uCOS-II/Micrium/uCOS-II/Ports/ARM-Cortex-M3/Generic/GNU/os_cpu.h
./uCOS-II/os_cpu.h

The last search yields two directories. I’ll pick the generic one, rather than the one for the ARM. With $(MICRIUM_HOME)/uCOS-II added to the include path we get a clean build. My test reports “tilt”.

We’ve gotten the header to compile, now let’s try to get the a call to OSSemPend to compile in the test case.

Here is the prototype for OSSemPend():

void  OSSemPend   (OS_EVENT *pevent,
                  INT32U           timeout,
                  INT8U           *perr);

Add a call to OSSemPend to the TEST.

TEST(uCosIITestDouble, OSSemPend)
{
    OS_EVENT event;
    INT8U error = -1;
    OSSemPend(&event, 0, &error);
}

Miraculously, this compiles, though it won’t link, as we can only build with uCos-II on the target. We need a link time stub. Add a stub implementation in a new file mocks/uCosIITestDouble.c. The initial implementation looks like this:

// uCosIITestDouble.c
#include "ucos_ii.h"
 
void  OSSemPend(OS_EVENT *event, INT32U timeout, INT8U *error)
{
}

Now we can teach this OSSemPend test-double to satisfy the condition that the production code was looking for, a message to process. inputqueue.h defines a FIFO to hold the characters. The production message processor gets characters from the input queue; the interrupt routine puts characters into the queue. So, here’s a test-double implementation:

// uCosIITestDouble.c
#include "ucos_ii.h"
#include "inputqueue.h"
 
void  OSSemPend2(OS_EVENT *event, INT32U timeout, INT8U *error)
{
    const char * input = "help";
    while (*input)
    {
      InputQueue_Put(*input);
      input++;
    }
}

This will help us get through the first test for the first client of OSSemPend(), but we’ll have to evolve it for other tests and other users of OSSemPend(). That is the topic of a coming article. If you want to see it, tweet this article or post a reply.

Unit testing RTOS dependent code – RTOS Test-Double – Part 3

$
0
0

I’m going to bend the example a little, and look at how to write tests that interact with an interrupt service routine and again with a semaphore. I’ll also get some help with the tedious parts of building fakes by using Mike Long’s Fake Function Framework. (BTW: I am using a beta version, that has changes the released version does not yet have.)

This figure shows the concurrent runtime relationship between the MessageProcessor and its SerialDriver.



The MessageProcessor tells the SerialDriver that is wants to wait for some characters, also passing along the Semaphore it will wait on. MessageProcessor then waits on its Semaphore. The ISR collects characters one at a time until it has satisfied its request. Then wakes up the MessageProcessor by posting to its Semaphore.

The MessageProcessor is oblivious to what happens concurrently. It just wants some characters and the request is satisfied asynchronously. Here is the MessageProcessor code that interacts with the ISR and the RTOS.

int MessageProcessor_WaitForMessage(char * buffer, size_t length)
{
    INT8U error = 0;
    SerialInterrupt_WaitForString(buffer, length, int_sync);
    OSSemPend(int_sync, 1000, &error);
    return error == OS_ERR_NONE;
}

Like in the last article, The OSSempend test-double satisfies what the caller is waiting for. This picture helps to visualize the faked concurrency.

In addition to simulating that MessageProcessor is given a message, the test should check that all the right parameters are passed to SerialDriver and OSSemPend. We will also need tests for the timeout case.

Instead of hand crafting the test-double like the last two articles, we can generate the boring parts of the test-double with the Fake Function Framework (fff). To use it, we need an fff file. Here is SerialInterruptFake.fff:

#include "SerialInterrupt.h"
#include "fff2.h"
 
FAKE_VALUE_FUNCTION(int, SerialInterrupt_WaitingFor, 
      char *, int, OS_EVENT *);

An fff file is like a header file but without include guards. I use the file extension fff so I can tell an fff file from an h file. The macro expansion for FAKE_VALUE_FUNCTION declares all the boiler plate structs and functions needed to build a decent test-double. The parameters of FAKE_VALUE_FUNCTION exactly match the signature of the function you are faking. Here is the signature for SerialInterrupt_WaitingFor:

int SerialInterrupt_WaitingFor(char *, int, OS_EVENT *);

The fff file is included in test case files so the fake can be setup and checked. The fff file is also included in a .c or .cpp file to generate the implementation of the test-double. Here is SerialInterruptFake.c:

#include "SerialInterruptFake.fff"
#define GENERATE_FAKES
#include "SerialInterruptFake.fff"

Here is a snip of uCosII_TestDouble.fff for the uCos-II RTOS fake:

#include "fff2.h"
#include "ucos_ii.h"
 
//snip...
 
FAKE_VALUE_FUNCTION(OS_EVENT *, OSSemCreate, INT16U)
 
FAKE_VALUE_FUNCTION(OS_EVENT *, OSSemDel, OS_EVENT *,\
                    INT8U,\
                    INT8U *)
 
FAKE_VOID_FUNCTION(OSSemPend, OS_EVENT *,\
                    INT32U,\
                    INT8U *)
 
//snip...

By the way, if you need a fake for some 3rd party code (like an RTOS), you only need to define fakes for the functions that you use.

Let’s look at an all inclusive test, and then we’ll refactor it to make it read better.

static const char * fakeInput;
static char buffer[100];
void Successful_OSSemPend(OS_EVENT *event, INT32U timeout, INT8U *error)
{
    memcpy(buffer, fakeInput, strlen(fakeInput));
    *error = OS_ERR_NONE;
}
 
TEST(MessageProcessor, WaitForMessage_succeeds)
{
  OSSemPend_reset();
  SerialInterrupt_WaitingFor_reset();
  OSSemPend_fake.custom_fake = Successful_OSSemPend;
  fakeInput = "sched lightOn 5 Monday 20:00";
  CHECK(MessageProcessor_WaitForMessage(
          (char*)&buffer, sizeof(buffer)));
  LONGS_EQUAL(1, SerialInterrupt_WaitingFor_fake.call_count);
  POINTERS_EQUAL(&buffer, SerialInterrupt_WaitingFor_fake.arg0_val);
  LONGS_EQUAL(sizeof(buffer), 
          SerialInterrupt_WaitingFor_fake.arg1_val);
  LONGS_EQUAL(1, OSSemPend_fake.call_count);
  POINTERS_EQUAL(SerialInterrupt_WaitingFor_fake.arg2_val, 
          OSSemPend_fake.arg0_val);
  STRCMP_EQUAL(fakeInput, buffer);
}

Look that test over carefully. The functions OSSemPend_reset and SerialInterrupt_WaitingFor_reset as well as the data structures SerialInterrupt_WaitingFor_fake and OSSemPend_fake were generated by fff.

The good news is that the tedium of creating fakes goes ways, but it makes for tests that don’t tell their story so well. No worries, it can be refactored onto a nice clean test.

  TEST(MessageProcessor, WaitForMessage_succeeds)
  {
      fakeInput = "sched lightOn 5 Monday 20:00";
      result = MessageProcessor_WaitForMessage(buffer, length);
      CHECK_TRUE(result);
      CheckSerialInterruptCalledWith(buffer, length);
      CheckOSSPendCall();
      CheckBufferContains(fakeInput);
  }

With the test refactored, let’s look at the TEST_GROUP and other elements needed.

extern "C"
{
#include "MessageProcessor.h"
#include "SerialInterruptFake.fff"
#include "uCosII_TestDouble.fff"
}
 
#include "CppUTest/TestHarness.h"
 
static const char * fakeInput;
static char buf[100];
void Successful_OSSemPend(OS_EVENT *event, 
                          INT32U timeout, INT8U *error)
{
    memcpy(buf, fakeInput, strlen(fakeInput));
    *error = OS_ERR_NONE;
}
 
TEST_GROUP(MessageProcessor)
{
    int result;
    size_t length;
    char * buffer;
 
    void setup()
    {
        OSSemCreate_reset();
        OSSemDel_reset();
        OSSemPend_reset();
        OSSemPend_fake.custom_fake = Successful_OSSemPend;
        SerialInterrupt_WaitingFor_reset();
        length = sizeof(buffer);
        buffer = (char*)&buf;
        MessageProcessor_Create();
    }
 
    void teardown()
    {
       MessageProcessor_Destroy();
    }
 
    void CheckSerialInterruptCalledWith(char * buffer, int length)
    {
        LONGS_EQUAL(1, SerialInterrupt_WaitingFor_fake.call_count);
        POINTERS_EQUAL(buffer,
                SerialInterrupt_WaitingFor_fake.arg0_val);
        LONGS_EQUAL(length, 
                SerialInterrupt_WaitingFor_fake.arg1_val);
    }
 
    void CheckOSSPendCall()
    {
        LONGS_EQUAL(1, OSSemPend_fake.call_count);
        POINTERS_EQUAL(SerialInterrupt_WaitingFor_fake.arg2_val,
                OSSemPend_fake.arg0_val);
    }
 
    void CheckBufferContains(const char * expected)
    {
        STRCMP_EQUAL(expected, buffer);
    }
};

setup() is pretty straightforward, except maybe the installation of Successful_OSSemPend into the custom_fake member of OSSemPend_fake. When a test provides a custom_fake, the generated fake calls it after collecting the arguments.

Notice that there is no custom_fake for OSSemCreate, OSSemDel, and SerialInterrupt_WaitingFor. They do not need custom_fake implementations. OSSemPend needs the custom_fake to do the pseudo concurrent activities.

CheckSerialInterruptCalledWith makes sure that OSSemPend is only called once. It also checks each of the passed arguments that were captured in arg0_val and arg1_val.

The CheckOSSPendCall test helper is kind of interesting. To check its arguments we need access to a hidden part of the MessageProcessor. Not shown in this article is that MessageProcessor creates a Semaphore during its creation. The Semaphore‘s data structure is returned as an OS_EVENT pointer. Even though the pointer is hidden in MessageProcessor it is passed to both SerialDriver and OSSemPend. The test helper makes sure the passed pointers are equal. If we were really worried about it, the fake for OSSemCreate could be customized to pass a pointer that we could check.

This next test simulates a timeout condition from the SerialDriver. We need a different custom_fake to get the right error code to be returned by OSSemPend in error.

void TimeOut_OSSemPend(OS_EVENT *event, INT32U timeout, INT8U *error)
{
  *error = OS_ERR_TIMEOUT;
}
 
TEST(MessageProcessor, WaitForMessage_timeout)
{
  OSSemPend_fake.custom_fake = TimeOut_OSSemPend;
  strncpy(buffer, "some leftover garbage", length);
  result = MessageProcessor_WaitForMessage(buffer, length);
  CHECK_FALSE(result);
  CheckBufferContains("");
}

Had OSSemPend returned its result as a return value instead of through *error, we could have provided the return value through OSSemPend_fake‘s return_val or return_val_seq[] members. Then a custom_fake would not be needed.

I pruned the above test to only include the essential differences from the prior test. I made sure the buffer contains some garbage, that we get a FALSE result and an empty string. There is no need to re-check the parameters passed to MessageProcessor‘s collaborators.

That test drove me to add the error handling to MessageProcessor like this:

int MessageProcessor_WaitForMessage(char * buffer, size_t length)
{
    INT8U error = 0;
    SerialInterrupt_WaitingFor(buffer, length, int_sync);
    OSSemPend(int_sync, 1000, &error);
 
    if (error == OS_ERR_NONE)
        return TRUE;
 
    buffer[0] = 0;
    return FALSE;
}

I’m treating all OSSemPend errors the same, but if the clients of MessageProcessor_WaitForMessage needed to know about the specific error, you can see that we could cause other OSSemPend errors easily. I would resist letting the uCos-II error codes work their way higher in the application, but now I am off on a design tangent.

You can see an RTOS can be stubbed out with no problem with fff and test can be written for code that is concurrency aware. It took me about an hour the edit the Micrium uCos-II header file into submission. The I was off and writing tests for my concurrent code that interacts with the RTOS. I hope you saw the importance of test case refactoring. It will help with future maintenance and increase the documentation value of the tests.

If you want the code that goes with this example, register at my web site. Use course code BLOG-READER. Then look at Registered User Extras.


Accessing static Data and Functions in Legacy C — Part 1

$
0
0

And a Happy Leap Year Bug

It’s a new year; last year was a leap year; so the quadrennial reports of leap year bugs are coming in. Apologies are in the press from Apple, TomTom, and Microsoft. Trains we stopped from running in China. Somehow calling them glitches seems to make it someone else’s fault, something out of their control. How long have leap years been around? Julius Caesar introduced Leap Years in the Roman empire over 2000 years ago. The Gregorian calendar has been around since 1682. This is not a new idea, or a new bug.

I’m going to try to take one excuse away from the programmers that create these bugs by answering a question that comes up all the time, “How do I test static functions in my C code?”

In code developed using TDD, static functions are tested indirectly through the public interface. As I mentioned in a this article TDD is a code rot radar. It helps you see design problems. Needing direct access to hidden functions and data in C is a sign of code rot. It is time to refactor.

But what about existing legacy code that has statics? It is probably way past the time for idealism and time for some pragmatism. In this article and the next, we’ll look at how to get your code untouched into the test harness and access those pesky static variables and functions.

If you don’t mind touching your code, you could change all mentions of static to STATIC. Then using the preprocessor, STATIC can he set to static during production and to nothing for test, making the names globally accessible. In gcc you would use these command line options

  • For production builds use -dSTATIC=static
  • For unit test builds use -dSTATIC

Let’s look at two options that, at least for access to statics, you will not have to touch your code to get it under test. First is #include-ing your .c in the test file. In the next article we’ll build a test adapter .c that give access to the hidden parts.

We are going to revisit code that is similar to the original code responsible for the Zune Bug. I rewrote the code to avoid attracting any lawyers but it is structured similarly to the original Zune driver, complete with static data and functions that must be correct for the function to work.

The header file provides a data structure and initialization function for figuring out the information about the date. Here is the header:

typedef struct Date
{
    int daysSince1980;
    int year;
    int dayOfYear;
    int month;
    int dayOfMonth;
    int dayOfWeek;
} Date;
 
void Date_Init(Date *, int daysSince1980);
 
enum {
    SUN = 0, MON, TUE, WED, THU, FRI, SAT
};
 
enum {
    JAN = 0, FEB, MAR, APR, MAY, JUN,
    JUL, AUG, SEP, OCT, NOV, DEC
};

Date_Init populates the Date instance passed in. Ignoring the fact that I can probably fully test this by rigging the daysSince1980, and inspecting the contents of the resulting Date structure, we are going to see how we can directly test the hidden functions and data.

Date_Init has three hidden helper functions.

void Date_Init(Date* date, int daysSince1980)
{
     date->daysSince1980 = daysSince1980;
     FirstSetYearAndDayOfYear(date);
     ThenSetMonthAndDayOfMonth(date);
     FinallySetDayOfWeek(date);
}

Date_Init is the tip of the iceberg. All the interesting stuff is happening in the hidden data and functions:

#include "Date.h"
#include <stdbool.h>
 
enum
{
    STARTING_YEAR = 1980, STARTING_WEEKDAY = TUE
};
 
static const int nonLeapYearDaysPerMonth[12] =
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
 
static const int leapYearDaysPerMonth[12] =
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
 
static bool IsLeapYear(int year)
{
    if (year % 400 == 0)
        return true;
    if (year % 100 == 0)
        return false;
    if (year % 4 == 0)
        return true;
    return false;
}
 
static int GetDaysInYear(int year)
{
    if (IsLeapYear(year))
        return 366;
    else
        return 365;
}
 
static void FirstSetYearAndDayOfYear(Date * date)
{
    int days = date->daysSince1980;
    int year = STARTING_YEAR;
    int daysInYear = GetDaysInYear(year);
 
    while (days > daysInYear)
    {
        year++;
        days -= daysInYear;
        daysInYear = GetDaysInYear(year);
    }
 
    date->dayOfYear = days;
    date->year = year;
}
 
static void ThenSetMonthAndDayOfMonth(Date * date)
{
    int month = 0;
    int days = date->dayOfYear;
    const int * daysPerMonth = nonLeapYearDaysPerMonth;
    if (IsLeapYear(date->year))
        daysPerMonth = leapYearDaysPerMonth;
 
    while (days > daysPerMonth[month])
    {
        days -= daysPerMonth[month];
        month++;
    }
    date->month = month + 1;
    date->dayOfMonth = days;
}
 
static void FinallySetDayOfWeek(Date * date)
{
     date->dayOfWeek =  ((date->daysSince1980-1)+STARTING_WEEKDAY)%7;
}
 
void Date_Init(Date* date, int daysSince1980)
{
     date->daysSince1980 = daysSince1980;
     FirstSetYearAndDayOfYear(date);
     ThenSetMonthAndDayOfMonth(date);
     FinallySetDayOfWeek(date);
}

Let’s say you want to check the days per month vectors. You might want to write a test to check the months against the handy poem we use here in the US: Thirty days, has September, April, June and November; all the rest have thirty-one, except for February. It has 28 except in leap year it has 29.

If you started by writing this test…

TEST(Date, sept_has_30_days)
{
    LONGS_EQUAL(30, nonLeapYearDaysPerMonth[SEP]);
}

… you get this error:

DateTest.cpp:41: error: 'nonLeapYearDaysPerMonth' was not declared in this scope

Let’s get access to the hidden statics in the test case by including Date.c instead of Date.h in DateTest.cpp. The full test case file looks like this now:

#include "CppUTest/TestHarness.h"
 
extern "C"
{
#include "Date.c"
}
 
TEST_GROUP(Date)
{
};
 
TEST(Date, sept_has_30_days)
{
    LONGS_EQUAL(30, nonLeapYearDaysPerMonth[SEP]);
}

With a little refactoring days per month vectors can be checked like this:

#include "CppUTest/TestHarness.h"
 
extern "C"
{
#include "Date.c"
}
 
TEST_GROUP(Date)
{
    const int * daysPerYearVector;
 
    void setup()
    {
        daysPerYearVector = nonLeapYearDaysPerMonth;
    }
 
    void itsLeapYear()
    {
        daysPerYearVector = leapYearDaysPerMonth;
    }
 
    void CheckNumberOfDaysPerMonth(int month, int days)
    {
        LONGS_EQUAL(days, daysPerYearVector[month]);
    }
 
    void ThirtyDaysHasSeptEtc()
    {
        CheckNumberOfDaysPerMonth(SEP, 30);
        CheckNumberOfDaysPerMonth(APR, 30);
        CheckNumberOfDaysPerMonth(JUN, 30);
        CheckNumberOfDaysPerMonth(NOV, 30);
 
        CheckNumberOfDaysPerMonth(OCT, 31);
        CheckNumberOfDaysPerMonth(DEC, 31);
        CheckNumberOfDaysPerMonth(JAN, 31);
        CheckNumberOfDaysPerMonth(MAR, 31);
        CheckNumberOfDaysPerMonth(MAY, 31);
        CheckNumberOfDaysPerMonth(JUL, 31);
        CheckNumberOfDaysPerMonth(AUG, 31);
    }
 
    void ExceptFebruaryHas(int days)
    {
      CheckNumberOfDaysPerMonth(FEB, days);
    }
};
 
TEST(Date, non_leap_year_day_per_month_table)
{
    ThirtyDaysHasSeptEtc();
    ExceptFebruaryHas(28);
}
 
TEST(Date, leap_year_day_per_month_table)
{
    itsLeapYear();
    ThirtyDaysHasSeptEtc();
    ExceptFebruaryHas(28);
}

You have access to all the hidden stuff, so you can write the test for the static functions: IsLeapYear(), GetDaysInYear(), FirstSetYearAndDayOfYear(), ThenSetMonthAndDayOfMonth(), and FinallySetDayOfWeek().

If Date been an abstract data type, with its data structure hidden in the .c file, the tests would all have access to structure members hidden from the rest of the world.

There is a downside to this approach, which probably does not matter in this case, but could in others. You can only have one test file that can include a given .c file. In the next article we’ll solve that problem.

Have you heard of any interesting leap year bugs? Did you prevent your own?

Accessing static Data and Functions in Legacy C — Part 2

$
0
0

Maybe you read Part 1 of this article. If you did you’ll know it concerns adding tests to legacy code (legacy code is code without tests). You will also know that the code has file scope functions and data that we want to test directly.

My opinion on accessing private parts of well designed code, is that you do not need to. You can test well design code through its public interface. Take it as a sign that the design is deteriorating when you cannot find a way to fully test a module through its public interface.

Part 1 showed how to #include the code under test in the test file to gain access to the private parts, a pragmatic thing to do when wrestling untested code into a test harness. This article shows another technique that may have an advantage for you over the technique shown in Part 1. Including the code under test in a test case can only be done once in a test build. What if you need access to the hidden parts in two test cases? You can’t. That causes multiple definition errors at link time.

This article shows how to create a test access adapter to overcome that problem.

We’d like to get IsLeapYear() under test. IsLeapYear() is a static function from Date.c introduced in Part 1. I’d like to write this test, but IsLeapYear is hidden, so we get compilation and/or link errors.

TEST(Date, regular_non_leap_year)
{
    CHECK_FALSE(IsLeapYear(1954));
    CHECK_FALSE(IsLeapYear(2013));
}

You get compilation errors, because there is no function declaration in Date.h for the hidden functions and data. If you overcame those errors by adding declarations, in the test file or tolerating the related warnings, you would be rewarded with unresolved external reference errors by the linker.

The solution is pretty straight-forward; write the test using a test access adaptor function:

TEST(Date, regular_non_leap_year)
{
    CHECK_FALSE(CallPrivate_IsLeapYear(1954));
    CHECK_FALSE(CallPrivate_IsLeapYear(2013));
}

CallPrivate_IsLeapYear() is a global function declared in DateTestAccess.h like this:

#ifndef DATE_TEST_ADAPTOR_INCLUDED
#define DATE_TEST_ADAPTOR_INCLUDED
 
#include "Date.h"
 
bool CallPrivate_IsLeapYear(int year);
 
#endif

CallPrivate_IsLeapYear() is implemented in DateTestAccess.c like this:

#include "Date.c"
 
bool CallPrivate_IsLeapYear(int year)
{
return IsLeapYear(year);
}

You could add similar accessors for private data to DateTestAccess.h

#ifndef DATE_TEST_ADAPTOR_INCLUDED
#define DATE_TEST_ADAPTOR_INCLUDED
 
#include "Date.h"
 
bool CallPrivate_IsLeapYear(int year);
 
const int * GetPrivate_nonLeapYearDaysPerMonth(void);
 
#endif

Implemented like this:

const int * GetPrivate_nonLeapYearDaysPerMonth(void)
{
    return nonLeapYearDaysPerMonth;
}

There are some variations of this that could be helpful. If the code under test has problem #include dependencies, you could #define symbols that are needed in DateTestAdaptor.c and also #define the include guard symbol that prevents the real header from being included.

I don’t love doing any of this, except when I do. That is limited to when it solves the problem of getting hidden code under test without modifying the code under test. A plus is that the code under test does not know it is being tested, that’s a good thing. Neither of these approaches are long term solutions. They are pragmatic steps toward getting code under tests so that it can be safely refactored and have new functionality test-driven into it.