9. Advanced Usage

9.1. Custom Constraints

Sometimes the built-in constraints that Cgreen provide are not sufficient. With Cgreen it is possible to create custom constraints, although you will be depending on some internal structures if you do so.

Here’s how to implement a simple example custom constraint that asserts that the value is bigger than 5. We’ll implement this using a static constraint since it does not take any parameter.

First we need the actual compare function:

#include <cgreen/cgreen.h>

bool compare_want_greater_than_5(Constraint *constraint, CgreenValue actual) {
    return actual.value.integer_value > 5;
}

And then the static constraint structure, for which we’ll need some of Cgreen's internal functions:

#include <cgreen/message_formatting.h>
#include "constraint_internal.h"

Constraint static_is_bigger_than_5 = {
        /* .type */ VALUE_COMPARER,
        /* .name */ "bigger than 5",
        /* .destroy */ destroy_static_constraint,
        /* .compare */ compare_want_greater_than_5,
        /* .test */ test_want,
        /* .format_failure_message_for */ failure_message_for,
        /* .actual_value_message */ "",
        /* .expected_value_message */ "",
        /* .expected_value */ {INTEGER, {5}},
        /* .stored_value_name */ "null",
        /* .parameter_name */ NULL,
        /* .size_of_stored_value */ 0
};

This implementation can use a statically declared Constraint structure that is prefilled since it does not need to store the value to be checked. This static custom constraint can then be used directly in the assert like this:

Ensure(TestConstraint, custom_constraint_using_static_function) {
    Constraint * is_bigger_than_5 = &static_is_bigger_than_5;
    assert_that(10, is_bigger_than_5);
}

To create a custom constraint that takes an input parameter, we need to add a function that creates a constraint structure that correctly saves the value to be checked, and, for convenience, a macro. This time we need to dig into how Cgreen stores expected values and we’ll also make use of Cgreen's utility function string_dup().

#include <cgreen/message_formatting.h>
#include "cgreen_value_internal.h"
#include "utils.h"


bool compare_want_smaller_value(Constraint *constraint, CgreenValue actual) {
    return actual.value.integer_value < constraint->expected_value.value.integer_value ;
}

Constraint *create_smaller_than_constraint(intptr_t expected_value, const char *expected_value_name) {
    Constraint *constraint = create_constraint();

    constraint->expected_value = make_cgreen_integer_value(expected_value);
    constraint->expected_value_name = string_dup(expected_value_name);
    constraint->type = VALUE_COMPARER;

    constraint->compare = &compare_want_smaller_value;
    constraint->execute = &test_want;
    constraint->name = "smaller than";
    constraint->size_of_expected_value = sizeof(intptr_t);

    return constraint;
}
#define is_smaller_than(value) create_smaller_than_constraint(value, #value)

This gives a custom constraint that can be used in the assert in the same way as Cgreen's built-in constraints:

Ensure(TestConstraint, custom_constraint_using_a_function_with_arguments_function) {
    assert_that(9, is_smaller_than(10));
}

The last, and definitely more complex, example is a constraint that takes two structures and compares fields in them. The constraint will, given a structure representing a piece and another structure representing a box, check if the piece can fit inside the box using a size field.

Assuming two "application" structures with size fields:

typedef struct Box {
    int id;
    int size;
} Box;

typedef struct Piece {
    int id;
    int size;
} Piece;

We want to be able to write a test like this:

Ensure(TestConstraint, more_complex_custom_constraint_function) {
    Box box1 = {.id = 1, .size = 5};
    Piece piece99 = {.id = 99, .size = 6};
    assert_that(&piece99, can_fit_in_box(&box1));
}

To implement the can_fit_in_box constraint we first need a comparer function:

bool compare_piece_and_box_size(Constraint *constraint, CgreenValue actual) {
    return ((Piece *)actual.value.pointer_value)->size
        < ((Box*)constraint->expected_value.value.pointer_value)->size ;
}

And this time we can’t rely on Cgreen's checker and message generating function test_want() which we used in the previous examples. So we also need a custom function that calls the comparison and formats a possible error message:

static void test_fit_piece(Constraint *constraint, const char *function_name, CgreenValue actual,
                           const char *test_file, int test_line, TestReporter *reporter) {
    (*reporter->assert_true)(
            reporter,
            test_file,
            test_line,
            (*constraint->compare)(constraint, actual),
            "Piece [%f], does not fit in [%f] in function [%s] parameter [%s]",
            ((Piece *)constraint->expected_value.value.pointer_value)->id,
            ((Box *)actual.value.pointer_value)->id,
            function_name,
            constraint->parameter_name);
}

Finally we’ll use both of those in the constraint creating function and add the convenience macro:

Constraint *create_piece_fit_in_box_constraint(intptr_t expected_value, const char *expected_value_name) {
    Constraint *constraint = create_constraint();

    constraint->expected_value = make_cgreen_pointer_value((void*)expected_value);
    constraint->expected_value_name = string_dup(expected_value_name);
    constraint->type = CONTENT_COMPARER;

    constraint->compare = &compare_piece_and_box_size;
    constraint->execute = &test_fit_piece;
    constraint->name = "fit in box";
    constraint->size_of_expected_value = sizeof(intptr_t);

    return constraint;
}
#define can_fit_in_box(box) create_piece_fit_in_box_constraint((intptr_t)box, #box)
As stated above, using custom constraints makes your tests vulnurable to changes in Cgreen's internals. Hopefully a method to avoid this will emerge in the future.
You can write custom constraints directly in a test file, but they can of course also be collected into a separately compiled module which is linked with your tests.