Boost C++ Libraries of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards


BOOST_TEST: details on expressions

Let's consider the following example:

Example: BOOST_TEST reporting


#define BOOST_TEST_MODULE boost_test_macro3
#include <boost/test/included/unit_test.hpp>

BOOST_AUTO_TEST_CASE( test_op_reportings )
  int a = 13, b = 12;
  BOOST_TEST(a == b);
  BOOST_TEST(a < b);
  BOOST_TEST(a - 1 < b);
  BOOST_TEST(b > a - 1);


> ./boost_test_macro3 --log_level=all
Running 1 test case...
Entering test module "boost_test_macro3"
test.cpp(12): Entering test case "test_op_reportings"
test.cpp(15): error: in "test_op_reportings": check a == b has failed [13 != 12]
test.cpp(16): error: in "test_op_reportings": check a < b has failed [13 >= 12]
test.cpp(17): error: in "test_op_reportings": check a - 1 < b has failed [13 - 1 >= 12]
test.cpp(18): error: in "test_op_reportings": check b > a - 1 has failed [12 <= 12]
test.cpp(12): Leaving test case "test_op_reportings"; testing time: 484us
Leaving test module "boost_test_macro3"; testing time: 588us

*** 2 failures are detected in the test module "boost_test_macro3"

It was already mentioned that the reporting is not symmetrical (see here). An expression is constructed from the statement appearing in the BOOST_TEST macro. This expression allows evaluation and reporting such as "13 - 1 >= 12" failed along with a copy of the statement, which contains more details than "a - 1 < b" failed. In details, what happens is the following:

  1. a special object, the seed of the expression, is composed from the left side of statement. This initial composition has highest precedence over the supported operations. The expression below:

    a op1 b op2 c op3 d

    is actually seen as

    ( seed a ) op1 b op2 c op3 d
  2. The "seed a" returns an expression object that keep tracks of the type of a. This expression has overloads for left-to-right associativity, and the operations op1, op2 ... are chained to the right of this expression object:

    a op1 b

    yields to the pseudo-code

    expression1 = create-expression(a)
    expression2 = create-expression(expression1, op1, b)

    expression1 and expression2 keep track of their left and right operands, and the operation on those operands. The expressions keep also track of the result type of the associated sub-expression. In the above example, expression1 and expression2 have result type decltype(a) and decltype(a op1 b) respectively. The result type allows for chaining sub-expressions.

  3. The C++ operators precedence rules apply in any case. What is seen by the expression is what is reachable with left-to-right composition. Any other operation that happens before it reaches the expression's right operand is not parsed as a sub-expression and is seen as a single operand: the right operand is not developed further by the framework. Let's suppose op2 below has higher precedence than op1, then

    a op1 b op2 c

    is equivalent to:

    create-expression(create-expression(a), op1, (b op2 c))

    In the above statement, the final expression can only see the result of (b op2 c) to its right, for which no further detail can be provided in the logs. This is also the case for right-to-left associative operators, such as !, ~, - (unary negation) etc.

    [Caution] Caution

    Since the expression object is composed from left-to-right, it actually observes a chain of operations and not the full expression tree.

  4. Once the full expression chain is built, it is evaluated as a chain of sub-expressions from left-to-right, exactly as the composition rule above. The evaluated elements are the ones of the expression itself. The expression

    a op1 b

    yields to the following evaluation chain:

    expression2.result = expression1.result op1 b
    expression1.result = a

    The final expression of the statement is cast to a boolean, which is in turn evaluated by the Unit Test Framework.

The example below illustrates the construction of the left-to-right chained expression.

Example: BOOST_TEST compound statements


#define BOOST_TEST_MODULE boost_test_macro2
#include <boost/test/included/unit_test.hpp>

BOOST_AUTO_TEST_CASE( test_op_precedence )
  int a = 13, b = 2, c = 12;
  // left term of == is expanded in the logs
  BOOST_TEST(a % b == c);
  // right term of == is not expanded in the logs
  BOOST_TEST(a == c % b);

BOOST_AUTO_TEST_CASE( test_op_right_associative )
  int a = 1;


> ./boost_test_macro2 --log_level=all
Running 2 test cases...
test.cpp(16): error: in "test_op_precedence": check a % b == c has failed [13 % 2 != 12]
test.cpp(18): error: in "test_op_precedence": check a == c % b has failed [13 != 0]
test.cpp(25): error: in "test_op_right_associative": check !a has failed
test.cpp(26): error: in "test_op_right_associative": check --a has failed [(bool)0 is false]

*** 4 failures are detected in the test module "boost_test_macro2"