In the
previous episode
I described how a custom assert function declared a local array which I did
not initialize.
static void assert_fizz_buzz(const char * expected, int n)
{
char actual[16];
...
}
The effect of
not initializing the array was that state from one test leaked into another
test and I got an unexpectedly
passing test. I fixed the problem by initializing the array.
static void assert_fizz_buzz(const char * expected, int n)
{
char actual[16] = "";
...
}
The most recent episode (in
cyber-dojo naturally)
revolved around this same issue. This time I was redoing the
roman numerals exercise (1 → "I", 2 → "II", etc) in C.
My custom assert function started like this.
...
#define PRINT(s) print_string(#s, s)
static void print_string(const char * name, const char * s)
{
printf("%10s: \"%s\"\n", name, s);
}
static void assert_to_roman(const char * expected, int n)
{
char actual[32];
to_roman(actual, sizeof actual, n);
if (strcmp(expected, actual) != 0)
{
printf("to_roman(%d) FAILED\n", n);
PRINT(expected);
PRINT(actual);
assert(false);
}
}
Once again I failed to initialize the array. I'm a slow learner.
This time the test
failed unexpectedly!
And the
diagnostic was even more unexpected:
to_roman(1) FAILED
expected: "I"
actual: "I"
This is a less than ideal diagnostic!
It seemed that
strcmp
and
printf
had differing opinions on what a string is!
I fixed it by adding initialization.
...
static void assert_to_roman(const char * expected, int n)
{
char actual[32] = "";
to_roman(actual, sizeof actual, n);
if (strcmp(expected, actual) != 0)
{
printf("to_roman(%d) FAILED\n", n);
PRINT(expected);
PRINT(actual);
assert(false);
}
}
After this the tests passed.
This addressed the immediate problem but it did
not address to the root cause.
So I removed the initialization and (with a hat tip to Mr Jonathon Wakely) I reworked
print_string
to display the length of the string as well as an indication
of the (un)printability of each character:
static void print_string(const char * name, const char * s)
{
printf("%10s: \"%s\" %d ", name, s, (int)strlen(s));
for (size_t i = 0; i != strlen(s); i++)
{
putchar(isprint(s[i]) ? 'P' : 'U');
}
putchar('\n');
}
With this change the diagnostic became:
to_roman(1) FAILED
expected: "I" 1 P
actual: "I" 4 UUUP
Much better. Then I thought about initializing the array a bit more.
I realized that initializing the array to the empty string
was a poor choice since it masked a fault in the implementation of
to_roman
which did
not start like this:
void to_roman(char buffer[], size_t size, int n)
{
buffer[0] = '\0';
...
}
So I added that and reworked
print_string
as follows:
static void assert_to_roman(const char * expected, int n)
{
char actual[32];
memset(actual, '!', sizeof actual);
to_roman(actual, sizeof actual, n);
...
}
And I was back to green :-)