I recently finished (in terms of features) a major code project, but I'm not happy with the quality of the yet. Besides the usual ways code can be buggy, it's especially easy to crash a program written in C (which can be exploited if the program is available online, as this one probably should be).

To fix that, I decided to write some unit tests. I've been considering adding this for a while, but I was operating under significant time pressure. I've also never written unit tests in C, and expected it to pointlessly difficult and arcane (like most things involving C).

check

In turns out that check is no harder to use than any other unit testing tool. Here's the source for one of my simpler test files.

#include <check.h>
#include <stdlib.h>

#include "bitreader.h"
#include "test_common.h"

START_TEST(test_bitreader_aligned)
    uint8_t bytes[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 170};
    bitreader_t* b = bitreader_new(bytes, sizeof(bytes));

    ck_assert_int_eq(bitreader_read_uint8(b), 1);
    ck_assert_int_eq(bitreader_read_uint16(b), 515);
    ck_assert_int_eq(bitreader_read_uint24(b), 263430);
    ck_assert_int_eq(bitreader_read_uint32(b), 117967114);
    ck_assert_int_eq(bitreader_read_uint64(b), 72623859790382856);
    ck_assert(!bitreader_eof(b));
    ck_assert(bitreader_read_bit(b));
    ck_assert(!bitreader_eof(b));
    ck_assert(!bitreader_read_bit(b));
    ck_assert_int_eq(bitreader_read_bits(b, 2), 2);
    ck_assert_int_eq(bitreader_read_bits(b, 4), 10);

    ck_assert(bitreader_eof(b));
    ck_assert(!b->error);
    bitreader_skip_bits(b, 1);
    ck_assert(b->error);

    bitreader_free(b);
END_TEST

START_TEST(test_bitreader_unaligned)
    uint8_t bytes[] = {255, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8};
    bitreader_t* b = bitreader_new(bytes, sizeof(bytes));

    ck_assert(bitreader_read_bit(b));
    ck_assert_int_eq(bitreader_read_uint8(b), 254);
    ck_assert_int_eq(bitreader_read_uint16(b), 516);
    ck_assert_int_eq(bitreader_read_uint24(b), 395274);
    ck_assert_int_eq(bitreader_read_uint32(b), 202248210);
    ck_assert_int_eq(bitreader_read_uint64(b), 1441719254663171086);
    ck_assert(!bitreader_eof(b));
    ck_assert(!bitreader_read_bit(b));
    ck_assert_int_eq(bitreader_read_bits(b, 6), 8);
    ck_assert(!b->error);

    ck_assert(bitreader_eof(b));
    ck_assert(!b->error);
    bitreader_skip_bits(b, 1);
    ck_assert(b->error);

    bitreader_free(b);
END_TEST

static Suite *bitreader_suite(void)
{
    Suite *s;
    TCase *tc_core;

    s = suite_create("Bit Reader");

    /* Core test case */
    tc_core = tcase_create("Core");

    tcase_add_test(tc_core, test_bitreader_aligned);
    tcase_add_test(tc_core, test_bitreader_unaligned);

    suite_add_tcase(s, tc_core);

    return s;
}

int main(void)
{
    int number_failed;
    Suite *s;
    SRunner *sr;

    s = bitreader_suite();
    sr = srunner_create(s);

    srunner_run_all(sr, CK_NORMAL);
    number_failed = srunner_ntests_failed(sr);
    srunner_free(sr);
    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}

I put that code in "tests/bitreader.c" and told Autotools to build it (see next section), and the output is reasonably good:

Running suite(s): Bit Reader
100%: Checks: 2, Failures: 0, Errors: 0

The only problem with "check" is that the built-in asserts leave a bit to be desired. I had to write my own assert_array_eq, but it wasn't too hard:

inline void assert_bytes_eq(uint8_t* x, size_t x_len, uint8_t* y, size_t y_len)
{
    ck_assert_int_eq(x_len, y_len);
    for (size_t i = 0; i < x_len; ++i) {
        ck_assert(x[i] == y[i]);
    }
}

Autotools

Adding this to Autotools was also much easier than expected. I needed to check for "check" in my "configure.ac":

PKG_CHECK_MODULES([CHECK], [check])

And then build the tests in Makefile.am:

noinst_PROGRAMS = tests/check_bitreader
TESTS = tests/check_bitreader

tests_check_bitreader_SOURCES = tests/bitreader.c
tests_check_bitreader_CFLAGS = $(CFLAGS) $(CHECK_CFLAGS)
tests_check_bitreader_LDADD = tslib/libmylib.a $(LDFLAGS) $(CHECK_LIBS)

And then I was able to run make check:

============================================================================
Testsuite summary for MyProgram 1.0
============================================================================
# TOTAL: 1
# PASS:  1
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================

Now to write unit tests until I run out of time on this project..