Criterion - Upcoming assert API

Introduction

In this tutorial we will take a look at Criterion’s upcoming assert API. If you’re not familiar with Criterion, you may start with the first part of our Criterion documentation.

Setup

Before using the new assert API, it is necessary to download and build the development version of Criterion, as we are working with unreleased functionality.

Start by downloading the code with this command :

$ git clone --recursive git@github.com/Snaipe/Criterion.git

Note

Take note of the –recursive option, it is necessary to compile the code.

Once the code is downloaded, let’s build it.

$ mkdir build
$ cd build
$ cmake ..
$ cmake --build .
$ sudo make install

Now, to use any of the features described in this page, you will need to include the following headers to your test files.

#include <criterion/criterion.h>
#include <criterion/new/assert.h>

Assertions

With the old Criterion, there was a multitude of assertion functions, covering a lot of cases. This led to a few useless repetitions, such as cr_assert_eq, cr_assert_str_eq, cr_assert_arr_eq… The new API is much more straightforward, there are only 5 assertion functions :

Reference

cr_assert(Criterion)

Passes if Criterion is true, abort if not.

cr_expect(Criterion)

Passes if Criterion is true, fails if not.

Note

The difference between cr_assert and cr_expect is the behavior when the test fails. An assert will stop the test right away while an expect will continue until the end of the test.

cr_fail()

Marks the test as failed. Can be used when testing manually with conditions for example.

cr_fatal()

Marks the test as failed then abort.

cr_skip()

Marks the test as skipped then abort.

Note

All of those macros can take an optional printf-like string which will be printed if the test fails (see below for an example).

Example

Test(my_suite, my_test) {
    FILE *myfile = fopen("myfile.txt", "r");
    if (myfile == NULL) {
        cr_fatal("Test mytest failed, file could not be opened : %s\n", strerror(errno));
    } else {
        // Do something...
    }
}

Criteria

As you can see, there aren’t any macros that compare values. This new API does not expect you to write the comparison functions yourself, you may use a set of predefined Criteria (or _Criterions_) which are the recommended parameters of cr_assert and cr_expect.

Reference

Logical Criteria

Logical criteria are simple helpers to test multiple criteria at once.

not(Criterion)

Evaluates to !Criterion.

all(...)

Takes a sequence of Criteria as parameters. Will be true if all Criteria given are true. (equivalent to an &&)

any(...)

Takes a sequence of Criteria as parameters. Will be true if any Criteria given is true. (equivalent to an ||)

none(...)

A combination of a not over each criteria and an all.

Tagged Criteria

Those are the real useful testing macros.

Note

Do not worry with the following Tag parameters, the complete list of tags are described in the nex section.

General Purpose

eq(Tag, Actual, Expected)

Tests if Actual is equal to Expected.

Note

This function may only use the operator == if the tag specifies numeric values. It is also able to the the equality between strings if given the tag str.

ne(Tag, Actual, Expected)

Tests if Actual is not equal to Expected.

lt(Tag, Actual, Expected)

Tests if Actual is less than Expected.

le(Tag, Actual, Expected)

Tests if Actual is less than or equal to Expected.

gt(Tag, Actual, Expected)

Tests if Actual is greater than Expected.

ge(Tag, Actual, Expected)

Tests if Actual is greater than or equal to Expected.

Floating point

Warning

The following criteria only work with the following tags : flt, dbl and ldbl.

Epsilon

Warning

This method of comparison is more accurate when comparing two IEEE 754 floating point values that are near zero. When comparing against values that aren’t near zero, please use ieee_ulp_eq instead.

It is recommended to have Epsilon be equal to a small multiple of the type epsilon (FLT_EPSILON, DBL_EPSILON, LDBL_EPSILON) and the input parameters.

epsilon_eq(Tag, Actual, Expected, Epsilon)

Tests if Actual is almost equal to Expected, the difference being the Epsilon value.

epsilon_ne(Tag, Actual, Expected, Epsilon)

Tests if Actual is different to Expected, the difference being more than the Epsilon value.

ULP

Warning

This method of comparison is more accurate when comparing two IEEE 754 floating point values when Expected is non-zero. When comparing against zero, please use epsilon_ne instead.

A good general-purpose value for Ulp is 4.

epsilon_eq(Tag, Actual, Expected, Epsilon)

Tests if Actual is almost equal to Expected, by being between Ulp units from each other.

epsilon_ne(Tag, Actual, Expected, Epsilon)

Tests if Actual is different to Expected, the difference being more than Ulp units from each other.

Example

All the following tests pass :

Test(strings, eq) {
    cr_assert(eq(str, "Hello", "Hello"));
}
Test(strings, ne) {
    cr_assert(ne(str, "Hello", "Hella"));
}
Test(integers, eq) {
    cr_assert(eq(int, 8, 8));
}
Test(logical, all) {
    cr_assert(all(eq(int, 8, 8), eq(str, "Hello", "Hello")));
}

Tags

The tags are Criterion-defined macros that represent standard C types.

Predefined Tags

Here is the complete list of all predefined tags :

int8_t i8
int16_t i16
int32_t i32
int64_t i64
uint8_t u8
uint16_t u16
uint32_t u32
uint64_t u64
size_t sz
void * ptr
intptr_t iptr
uintptr_t uptr
char chr
int int
unsigned int uint
long long
unsigned long ulong
unsigned long long ullong
float flt
double dbl
long double ldbl
complex double cx_dbl
complex long double cx_ldbl

See below for details about the implementation of this structure.

const char * str
const wchar_t * wcs

String of wide characters

const TCHAR * tcs

Windows character string.

Mem struct

Here is the definition of the cr_mem structure :

struct cr_mem
const void * data

data is a pointer to the data to test

size_t size

size is the size of data to test.

User-Defined Type

You can use the following macro to use you own types as Tags :

type(UserType)

You may then use you type the same way as any other tag :

cr_assert(eq(type(my_type), var1, var2));

However there are some functions to implement in order to use this type in your code.

Warning

Due to implementation restrictions, UserType must either be a structure, an union, an enum, or a typedef.

For instance, these are fine:

type(foo)
type(struct foo)

and these are not:

type(foo *)
type(int (&foo)(void))

in these cases, use a typedef to alias those types to a single-word token.

In any case

The type must be printable, and thus should implement a “to-string” operation. These functions are mandatory in any case.

C
char *cr_user_<type>_tostr(const <type> *val);

For example if you have a character type, you must implement the char *cr_mem_character_tostr(const character *val);

C++
std::ostream &operator<<(std::ostream &os, const <type> &val);

eq, ne, le, ge

These functions are mandatory in order to use the operators eq, ne, le or ge with your type. They should return 1 (or true) if the two parameters are equal, 0 (or false) otherwise.

C
int cr_user_<type>_eq(const <type> *lhs, const <type> *rhs);
C++
bool operator==(const <type> &lhs, const <type> &rhs);

lt, gt, le, ge

These functions are mandatory in order to use the operators lt, gt, le or ge with your type. They should return 1 (or true) if the first parameter is less than the second, 0 (or false) otherwise.

C
int cr_user_<type>_lt(const <type> *lhs, const <type> *rhs);
C++
bool operator<(const <type> &lhs, const <type> &rhs);

User-defined type example

#include <stdio.h>
#include <criterion/criterion.h>
#include <criterion/new/assert.h>


typedef struct vector3d {
    int x;
    int y;
    int z;
} vector3d;

/*
    Defines the string representation of a vector3d
*/
char *cr_user_vector3d_tostr(const vector3d *val)
{
    char *str = malloc(sizeof(char) * (12 + 3 * 9));

    if (str == NULL) {
        return "";
    }
    sprintf(str, "X:%d; Y:%d: Z:%d\n", val->x, val->y, val->z);
    return str;
}

/*
    Defines an equality between two vector3d
*/
int cr_user_vector3d_eq(const vector3d *lhs, const vector3d *rhs)
{
    if (lhs->x == rhs->x && lhs->y == rhs->y && lhs->z == rhs->z) {
        return 1;
    }
    return 0;
}

/*
    Creates a new vector with predefined values
*/
vector3d *create_vector(void)
{
    vector3d *vect = malloc(sizeof(vector3d));

    vect->x = 8;
    vect->y = 7;
    vect->z = 2;
    return vect;
}

Test(usertype, test) {
    vector3d *test_vect = malloc(sizeof(vector3d));
    vector3d *vect = create_vector();

    test_vect->x = 8;
    test_vect->y = 7;
    test_vect->z = 2;

    cr_assert(eq(type(vector3d), *vect, *test_vect));
}