One nice thing about Haskell is Quickcheck, if you're not familiar with Haskell or Quickcheck it works like this:

Prelude> let evens xs = [ x | x <- xs, x `mod` 2 == 0 ]
Prelude> evens [1..10]
[2,4,6,8,10]

Here we define a function that takes a list of numbers and returns only the even numbers. We can use Quickcheck to test this function:

Prelude> import Test.QuickCheck
Prelude Test.QuickCheck> let test_evens xs = [ x | x <- evens xs, x `mod` 2 /= 0 ] == []
Prelude Test.QuickCheck> quickCheck test_evens
+++ OK, passed 100 tests.

Here we define a test function that asserts that a list of even numbers shouldn't contain any odd ones. Passing this function to Quickcheck shows that the function passed 100 tests. Quickcheck sees that our test_evens function takes a list of numbers and returns a boolean, and it tests our code by generating a set of random inputs and executing it with those inputs.

Clearly this type of testing can be very useful, and not just for Haskell programs. Theft is a C library that brings Quickcheck style testing to C. This sort of testing is known as Property Testing.

Let's reimplement our Haskell code in C,

#include <stdio.h>
#include <stdlib.h>
#include <theft.h>

struct IntArray {
    int len;
    int arr[];
};

struct IntArray *evens(struct IntArray *input)
{
    int nevens = 0;
    struct IntArray *output;

    if (input == NULL) {
        return NULL;
    }

    output = malloc(sizeof (struct IntArray) + input->len * sizeof(int));

    if (output == NULL) {
        return NULL;
    }

    for (int i = 0; i < input->len; i++) {
        if (input->arr[i] % 2 == 0) {
            output->arr[nevens++] = input->arr[i];
        }
    }

    output->len = nevens;
    return output;
}

Here we define a function that takes an array of integers and outputs an array of even integers.

Now let's do some testing!

Since C is not strongly typed like Haskell we need to define a function that describes what a test input should look like. Providing this information to Theft will allow it to generate real test input data. The function should have the following prototype,

enum theft_alloc_res allocate_int_array(struct theft *, void *, void **)

Let's write it!

enum theft_alloc_res allocate_int_array(struct theft *t, void *data, void **result)
{
    int SIZE_LIMIT = 100;

    int size = theft_random_choice(t, SIZE_LIMIT);

    struct IntArray *numbers = malloc(sizeof (struct IntArray) + size * sizeof(int));

    if (numbers == NULL) {
        return THEFT_ALLOC_ERROR;
    }

    for (int i = 0; i < size; i++) {
        numbers->arr[i] = theft_random_choice(t, INT_MAX);
    }

    numbers->len = size;

    *result = numbers;

    return THEFT_ALLOC_OK;
}

theft_random_choice is a function that will pick a random number between 0 and some defined limit. The result is not truly random, but instead based on the complexity of the input Theft requires. The documentation for Theft points out that the main thing with this is to ensure that wherever theft_random_choice returns 0 our alloc_int_array function should return the simplest input possible, in our case that would be an empty array.

Theft passes a reference pointer to the alloc_int_array function, this must be updated to point to the array we have allocated before the function returns with THEFT_ALLOC_OK. In the event of some kind of error the function should return THEFT_ALLOC_ERROR

Next we write the property function, this function takes an input array of integers generated by Theft, runs our evens function over that input and asserts that the resultant output doesn't contain any odd numbers.

enum theft_trial_res property_array_of_evens_has_no_odd_numbers(struct theft *t, void *test_input)
{
    struct IntArray *test_array = test_input;

    struct IntArray *result = evens(test_array);

    // Array of even numbers should not contain any odd numbers
    for (int i = 0; i < result->len; i++) {
        if (result->arr[i] % 2 != 0) {
            return THEFT_TRIAL_FAIL;
        }
    }

    return THEFT_TRIAL_PASS;
}

Putting this together, we define some boiler plate to cover the various functions we just defined for generating test inputs,

struct theft_type_info random_array_info = {
    .alloc = allocate_int_array,
    .free = theft_generic_free_cb,
    .autoshrink_config = {
        .enable = true,
    }
};

The alloc member is updated to point to the function we just defined. Since the test inputs are dynamically allocated with malloc they will need to be freed later on. Theft provides a generic function for freeing which is sufficient for our purposes: theft_generic_free_cb.

The last member of this structure needs more explanation. If Theft encounters an input which causes the test to fail, it will try to pare down the input to the smallest input that causes failure; this is called shrinking.

Theft lets you define a function that can provide some control over the shrinking process, or it can use its own shrinking functions: autoshrinking. If autoshrinking is used however, the function that allocates test inputs must base the complexity of the input it generates upon the result of one of the theft_random functions, such as theft_random_bits, or theft_random_choice. This is why our alloc_int_array function uses theft_random_choice rather than standard pseudo random number generating functions.

Finally we write a function to execute the tests,

int main(void)
{
    theft_seed seed = theft_seed_of_time();
    struct theft_run_config config = {
        .name = __func__,
        .prop1 = property_array_of_evens_has_no_odd_numbers,
        .type_info = { &random_array_info },
        .seed = seed
    };

    return (theft_run(&config) == THEFT_RUN_PASS) ? EXIT_SUCCESS : EXIT_FAILURE;
}

Compiling and running:

$ gcc -o test test.c -ltheft
$ ./test 

== PROP 'main': 100 trials, seed 0x62a401b7fa52ac8b
.......................................................................
.............................
== PASS 'main': pass 100, fail 0, skip 0, dup 0

I hope this helps anyone looking to try out Property Testing in C. Another guide that might be useful can be found here, it has an example that uses a manually defined shrinking function, which may be useful for more complex situations.