4. Special Cases

4.1. Working with doubles

We are not talking about test doubles here, but about values of C/C++ double type (a.k.a double float.)

Cgreen is designed to make it easy and natural to write assertions and expectations. Many functions can be used for multiple data types, e.g. is_equal_to() applies to all integer type values, actually including pointers.

But the C language has its quirks. One of them is the fact that it is impossible to inspect the datatypes of values during run-time. This has e.g. forced the introduction of is_equal_to_string() to enable string comparisons.

4.1.1. Assertions and Constraints

When it comes to double typed values this has spilled over even further. For double typed values we have

Constraint

is_equal_to_double(value)

is_not_equal_to_double(value)

is_less_than_double(value)

is_greater_than_double(value)

But there is also the special assert that you must use when asserting doubles

Assertion

assert_that_double(expected, constraint)

and the utility function

Utility

significant_figures_for_assert_double_are(int figures)

And of course they are designed to go together. So, if you want to assert an expression yeilding a double typed value, you need to combine them:

Ensure(Doubles, can_assert_double_values) {
    significant_figures_for_assert_double_are(3);
    assert_that_double(3.14, is_equal_to_double(5.0));
}
You have to use assert_that_double() and is_equal_to_double() together.

and you would get

double_tests.c:13: Failure: can_assert_double_values
	Expected [3.14] to [equal double] [5.0] within [3] significant figures
		actual value:			[3.140000]
		expected value:			[5.000000]

4.1.2. Mocks

The general mechanism Cgreen uses to transport values to and from mock functions is based on the simple idea that most types fit into a "large" integer and can be type converted to and from whatever type you need.

Since a double float will not fit into the same memory space as an integer Cgreen handles that by encapsulating ("boxing") the double into an area which is represented by the pointer to it. And that pointer can fit into the integer type value (intptr_t) that Cgreen uses to transport values into and out of mock(). To get the value back you "unbox" it.

There are two possible uses of double that you need to be aware of

  1. When a parameter to the mocked function is of double type and needs to be matched in an constraint in an expect call.

  2. When the mock function itself should return a double type value.

In the test you should use the special double type constraints and the will_return_double() convenience function. In the mock function you will have to take care to box and unbox as required.

Boxing and unboxing in mock functions

Description

box_double(double value)

Wrap the value in an allocated memory area and return a pointer to it

unbox_double(BoxedDouble *box)

Unwrap the value by freeing the area and returning the value

Here’s an example of that:

static double double_out(int i, double d) {
    return unbox_double(mock(i, box_double(d))); (1)
}

Ensure(Doubles, can_be_arguments_to_mock_and_returned) {
    expect(double_out,
           when(i, is_equal_to(15)),
           when(d, is_equal_to_double(31.32)), (2)
           will_return_double(3.1415926));     (3)
    assert_that_double(double_out(15, 31.32), is_equal_to_double(3.1415926));
}
1 We can see that the parameter d to the mock function, since it is a double, it will have to be used as box_double(d) in the call to mock().
2 The corresponding expect() uses a double constraint.
3 The mock function in this small example also returns a double. The expect() uses will_return_double() so the mock function needs to unbox the return value from mock() to be able to return the double type value.
Strange errors may occur if you box and/or unbox or combine double constraints incorrectly.

4.1.3. Details of floating point comparison algorithm

The number of significant digits set with significant_figures_for_assert_double_are() specifies a relative tolerance. Cgreen considers two double precision numbers x and y equal if their difference normalized by the larger of the two is smaller than 10^(1 - significant_figures)^. Mathematically, we check that |x - y| < max(|x|, |y|) * 10^(1 - significant_figures)^.

Well documented subtleties arise when comparing floating point numbers close to zero using this algorithm. The article Comparing Floating Point Numbers, 2012 Edition by Bruce Dawson has an excellent discussion of the issue. The essence of the problem can be appreciated if we consider the special case where y == 0. In that case, our condition reduces to |x| < |x| * 10^(1 - significant_figures)^. After cancelling |x| this simplifies to 1 < 10^(1 - significant_figures)^. But this is only true if significant_figures < 1. In words this can be summarized by saying that, in a relative sense, all numbers are very different from zero. To circumvent this difficulty we recommend to use a constraint of the following form when comparing numbers close to zero:

assert_that(fabs(x - y) < abs_tolerance);

4.2. Using Cgreen with C++

The examples in this guide uses the C langauge to shows how to use CGreen. You can also use CGreen with C++.

The following needs expansion and more details as the support for C++ is extended.

All you have to do is

  • Use the cgreen namespace by adding using namespace cgreen; at the beginning of the file with your tests

There is also one extra feature when you use C++, the assert_throws function.

If you use the runner, as described in [runner], and thus link your tests into a shared library, don’t forget to link it with the same C++ library that was used to create the cgreen-runner.