ModAssert, an advanced assertion framework for C++

Know why your application is failing!

Lack of information

When your application is failing at a customers site, you need to know what
happened. Even when you're testing your application yourself, you need to know.
This is best done using assertions, but traditional assertions don't
log much information. Programmers can add code to log all this information at every point where
their application could fail, but doing this consistently is tedious, even unrealistic.

How ModAssert helps

ModAssert solves this problem by providing over 80 different types of
analysis that you can use in assertions. These analyses are called Rich Booleans,
conditionlike expressions that, if the condition fails, create an
analysis that is logged. And you can easily add several expressions
to an assertion macro that are also logged when the condition fails.
Furthermore it logs a lot of info that is of interest to the programmer
(time and date, amount of free memory, thread id, current working directory, ...),
programmers can add other types of information that automatically will be
logged every time an assertion fails.

Here rbEQUAL(a+b, c)) is an example of a Rich Boolean,
and <a+b>: `17' == <c>:`19' - nok is the analysis it created.
So with ModAssert you know the values of the expressions that are supposed to be equal.
Knowing the values is a lot more useful than just knowing
that they are different.

ModAssert is very flexible. Programmers can create custom loggers and displayers,
custom analyses in addition to the more than 80 existing ones, disable assertions in
performance critical code (per source file, level, group or all), both at compile time
and runtime, and more.

ModAssert is developed by Q-Mentum, that also develops
UquoniTest, an advanced unit testing library for C++,
where the unit test assertions can also use Rich Booleans.

Bookmark ModAssert:

Why ModAssert

There are many assertion packages, so what makes ModAssert different? The main difference is the modularity explained above. 112 of its 144 assertion macros have a condition, which can be an expression that evaluates to a boolean, or can be a Rich Boolean.

This lets the assertion macros vary in their functionality instead
of doing the analysis. It has assertion macros that can accept
expressions to display, a level and or a group, an optional action or a
failure action. And you can combine these. So ModAssert has no
assertions like ASSERT_EQUAL(a, b), ASSERT_LESS(a, b), etc. Instead you would use MOD_ASSERT(rbEQUAL(a, b)) and MOD_ASSERT(rbLESS(a, b)). But you can just as easily write MOD_ASSERT_P(a<<b, rbEQUAL(a+b, c)) and MOD_ASSERT_P(a<<b, rbLESS(a+b, c)), which display the values of a and b if the assertion fails.

Other assertions packages

ModAssert

ASSERT_EQUAL(a,b)

MOD_ASSERT(rbEQUAL(a,b))

ASSERT_LESS(a,b)

MOD_ASSERT(rbLESS(a,b))

ASSERT_MORE(a,b)

MOD_ASSERT(rbMORE(a,b))

ASSERT_MSG_EQUAL("message", a,b)

MOD_ASSERT_P("message", rbEQUAL(a,b))

ASSERT_MSG_LESS("message", a,b)

MOD_ASSERT_P("message", rbLESS(a,b))

ASSERT_MSG_MORE("message", a,b)

MOD_ASSERT_P("message", rbMORE(a,b))

ASSERT_LEVEL_EQUAL(Warning, a,b)

MOD_ASSERT_G(Warning, rbEQUAL(a,b))

ASSERT_LEVEL_LESS(Warning, a,b)

MOD_ASSERT_G(Warning, rbLESS(a,b))

ASSERT_LEVEL_MORE(Warning, a,b)

MOD_ASSERT_G(Warning, rbMORE(a,b))

What ModAssert can do with 3 macros where other assertion packages need 9 macros.Actually this shows only a small part of what ModAssert can do.

There are over 80 Rich Booleans, and 112 modular assertions, so
that makes over 9000 combinations. And some Rich Booleans are modular
themselves. E.g. the Rich Boolean rbSTRING compares two
strings. Its second argument is an operator (==, <, <=, >,
>= or !=), and its fourth argument is an object that specifies how
the comparison is done (case sensitive or not, with a locale or not,
string type), so it has two levels of modularity. The Rich Booleans rbIN_RANGE, rbIN_ARRAY and rbIN_CONTAINER
work on a range, array or container, and have as the last argument an
object that tells what kind of test to perform on it. This could be
e.g. checking if it is sorted, sorted strictly or that all elements are unique, which adds a level
of modularity in the Rich Boolean. Or it could check if all elements,
or at least one element, or exactly one element satisifies a given
condition. So here you have two levels of modularity in the Rich
Boolean. There are similar ones that work on two ranges, arrays,
containers or combinations. So actually the number of combinations is
much more than 9000.

ModAssert has more features that let you do just about anything that you want to do with assertions:

Add expressions and messages to be shown along with the error message.

Assign a level and/or a group to an assertion, and decide which levels and groups should be displayed.

Let the user choose if an optional action should be taken (e.g. break out of a loop if an assertion keeps failing).

Let the user choose if an assertion should no longer be displayed, or the assertions in the file, or all assertions.

Have an action executed after the displaying of the assertion (e.g. throw an exception), even if assertions are disabled.

Create custom assertion displayers and loggers, or use the ones provided.

Give extra information to every displayer and logger if an
assertion fails (used automatically for all assertions), like the time, thread id, current directory ...; you
can add your own.

Is thread-safe.

Disable reporting to reduce the executable size. This can be done per program, source file, level or group.

Tested with five different compilers (Visual C++ 6.0, 2003,
2005 and 2008, and gcc on Windows and Linux), with most of the warning levels
on. Project files and configure scripts are provided.

Using the code

The 9 basic assertion macros

There are 9 basic assertion macros, that can be extended. They are MOD_ASSERT, MOD_VERIFY, MOD_CHECK, MOD_FAIL, MOD_CHECK_FAIL, MOD_VERIFY_V, MOD_VERIFY_B, MOD_CHECK_V and MOD_CHECK_B.

MOD_ASSERT and MOD_VERIFY have one
argument, the condition. This can be a boolean expression or a Rich
Boolean. A Rich Boolean is preferred, because it gives more
information. The difference between them is that MOD_ASSERT is removed entirely by the preprocessor if NDEBUG is defined (e.g. in Release mode in Visual Studio), while MOD_VERIFY still evaluates its argument (but doesn't report if the condition fails). These two are meant for unexpected errors, i.e. errors that are the consequence of a bug in your code.

Example:

#include "modassert/assert.hpp"
...
MOD_ASSERT(rbEQUAL(a,b));

If the condition fails, this would show the following dialog in a Win32 application (the values may be different, of course):

As you can see, there is lot of room for the analysis, that is hardly used here, but other analyses are longer.

MOD_CHECK has a second argument, an action that should be executed if the condition fails. This is meant for so called expected errors,
because they are not the consequence of a bug, but of an incorrect
action of the user or some other source, e.g. a user enters an invalid
value or a file is read-only. The condition is still evaluated if
NDEBUG is defined, and if the condition fails, the action is still
executed. Anytime you want error handling, you should use a MOD_CHECK macro, so that all the information about it is displayed and/or logged in the same way.

Example:

MOD_CHECK(rbLESS(n, 100), throw MyException());

MOD_FAIL has no arguments, and is equivalent to MOD_ASSERT(false). It should be used in places where you expect your application can't come.

Example:

for (int i=0; i<n; ++i)
{
if (a[i]>10)
return a[i];
}
MOD_FAIL; // at least one should be bigger than 10

MOD_CHECK_FAIL has one argument, the failure action, and is equivalent to MOD_CHECK(false, action). It should be used in places where your application shouldn't come, unless some expected error occurs (as with MOD_CHECK).

Example:

for (int i=0; i<n; ++i)
{
if (a[i]>10)
return a[i];
}
MOD_CHECK_FAIL(throw MyException()); // at least one should be bigger than 10

MOD_VERIFY_V is like MOD_VERIFY, but
returns a value. If you don't use a Rich Boolean, the returned value is
the condition. This is handy for functions that return a pointer that
shouldn't be NULL. If you use a Rich Boolean, it returns one of the
arguments of the Rich Boolean, usually the first one.

Example:

Widget *widget = MOD_VERIFY_V(CreateWidget());

MOD_VERIFY_B is like MOD_VERIFY_V, but
returns a ModAssert::UseBool instead of a value.
This is a class of which the objects can be converted to a
boolean, so you can use it e.g. as the condition in an if-statement. If
you don't convert it to a boolean, an assertion will fail in its
destructor. It has transfer semantics, so you can use it as the return
value of a function, and leave the checking of the value to the caller
of the function. This is handy as a condition in e.g. an if-statement.

This macro is only useful in applications that have to be very safe,
that have to try to recover from a bug that is detected.

MOD_CHECK_V is like MOD_CHECK, but returns
a value. If you don't use a Rich Boolean, the returned value is the
condition. This is handy for functions that return a pointer that
shouldn't be NULL. If you use a Rich Boolean, it returns one of the
arguments of the Rich Boolean, usually the first one. Unlike MOD_CHECK, the failure action should be an expression on which operator()() can be called.

MOD_CHECK_B is like MOD_CHECK, but doesn't have a failure action and returns a ModAssert::UseBool
object. See MOD_VERIFY_B above for info on the ModAssert::UseBool class.

Example:

if (!MOD_CHECK_B(a==10))
{
...
}

Note: if you use a Rich Boolean in MOD_VERIFY_V, MOD_VERIFY_B, MOD_CHECK_V or MOD_CHECK_B, you should use one of a different kind, namely one that starts with rbv instead of rb.

Extending the basic macros

The basic macros can be extended by adding suffixes that specify
which extra arguments you can give. If you add one or more suffixes,
they should be preceded by an underscore. The extra arguments are
always before the condition (unlike the failure action of MOD_CHECK).

The suffix P lets you add expressions and messages. You can give more than one by separating them with <<. If you use MOD_VERIFY_V, MOD_CHECK_V or MOD_CHECK_B the parameters should not be separated with << but with commas, and embraced by parentheses.

In this example, the value of a+b and c+d will be shown because they are the arguments of a Rich Boolean, but a, b, c, and d will also be shown.

If the condition fails, this would show the following dialog in a Win32 application (the values may be different, of course):

You can mix messages and parameters. A message is recognized by ModAssert when it is a literal string,
i.e. it's between ".

Example:

MOD_ASSERT_P("number of spaces and tabs should be less than the string length"
<< nrSpaces << nrTabs,
rbLESS(nrSpaces+nrTabs, str.size()));

The suffix G lets you add a group or a level. A group is an object of type ModAssert::Group<ModAssert::ReportFailure>, ModAssert::Group<ModAssert::ReportAlways>, or ModAssert::Group<ModAssert::ReportNone>,
used to respectively let the assertion be displayed and logged when it
fails, always, or never. If you use such a group in a number of
assertions, you can turn them on or off by just changing the type of
the group. If you want more information during debugging, you can even
change it to ModAssert::Group<ModAssert::ReportAlways>,
but don't forget to change it back if you don't need it anymore. If you
want to do this with assertions that don't have a group, you can use
the predefined object ModAssert::IfSuccess instead. Groups can be combined with || and &&.

A level can be of type ModAssert::Info, ModAssert::Warning, ModAssert::Error, or ModAssert::Fatal. A level can be added to a group or a combination of groups with %,
but only one level can be added. Adding a level is useful because you
can control the displaying and logging of assertions per level. By
default assertions have the level ModAssert::Error (as you can see in the dialog boxes above).

Example:

The suffix O (capital o, not the number 0) lets you add an optional action.
This requires two extra arguments. The first is the action, the second
is a description of the optional action that is shown to the user. This
is useful to let the user escape from a long loop where a lot of
assertions fail (another option is to select 'This assert' in the box
labeled 'Stop displaying assertions' in the dialog box, with the
difference that the execution of the problematic code will continue).
The action is the code itself, unless you use MOD_VERIFY_V, MOD_CHECK_V or MOD_CHECK_B, then it should be an expression on which you can call operator()(), e.g. function that takes no arguments or an object of a class that has operator()().

This gives you a total of 72 assertion macros. Actually there are 72
more that have to do with default parameters (see the documentation),
so the total is actually 144. 112 of them can have a Rich Boolean as the
condition, of which there are over 80. This gives you over 9000
combinations. No other assertion package offers this. Most assertion
packages don't even have 144 macros, even including the ones that
perform an analysis, like ASSERT_EQUAL(a,b).

Adding information

You can add extra information to the displayer and loggers, by deriving a class from InfoProviders::InfoProvider, creating an object of your class, and passing it to InfoProviders::AddInfoProvider. InfoProviders::InfoProvider has two pure virtual methods, std::string GetType() and std::string GetInfo(bool success, const ModAssert::Context& context, const ModAssert::GroupList* groupList).
The first should tell what kind of information it gives (e.g. "thread
ID"), and the second should give the information. The infoproviders
that you add in this way, are called by the provided loggers and
displayers. If you create your own loggers and displayers, they should
do this as well.

If you use ModAssert::SetWin32Handler, many such
infoproviders are added automatically, e.g. one with the thread ID, one
with the time and date, and one with the return value of GetLastError() (it even adds the corresponding text). In the dialog boxes above you see the thread ID, but not the return value of GetLastError(), because that is shown only if it is not zero.

When assertions are reported

By reporting, we mean logging and displaying. By default, assertions
are only reported if the symbol NDEBUG is not defined. So for e.g. with
MS Visual Studio they would be reported in Debug mode, but not in
Release mode. When reporting of assertions is disabled, the executable
size is reduced, because MOD_ASSERT and MOD_FAIL macros are removed completely, while MOD_VERIFY, MOD_CHECK and MOD_CHECK_FAIL are reduced.

However, by defining the symbol MOD_ASSERT_REPORT
globally, you can turn on the reporting of assertions if NDEBUG is
defined. This can be handy if you want logging of errors in an
application at a customer's site. Defining MOD_ASSERT_DONT_REPORT if NDEBUG is not defined, disables the reporting of assertions.

These two symbols can be overridden per source file by defining MOD_ASSERT_REPORT_FILE and MOD_ASSERT_DONT_REPORT_FILE before including modassert/assert.hpp.

Rich Booleans

The examples above showed some Rich Booleans. Discussing the more
than 80 Rich Booleans would take too long, but I'll give an overview of the
available ones. See the manual of the Rich Booleans package for a full
description.

Relations between two objects: rbEQUAL, rbLESS, ...

Bitwise comparisons: rbEQUAL_BITWISE, rbBITS_ON, rbBITS_OFF, ...

Type checking (with RTTI): rbEQUAL_TYPES, rbHAS_TYPE, ...

Working on ranges and containers: rbIN_RANGE, rbIN_RANGES,
rbIN_CONTAINER, rbIN_CONTAINERS, ... These have an extra argument that
tells what kind of check to do, e.g. Sorted, Compare, Has, Unique, IsSubsetOf ...

String comparisons: rbSTRING, rbSTRING_BEGINS_WITH,
rbSTRING_ENDS_WITH, rbSTRING_CONTAINS. These have an extra argument
that tells what kind of check to do, e.g. case sensitive or not.

Logical expressions: rbAND, rbOR, rbXOR. These can have Rich Booleans or plain boolean expressions as their arguments.

Exceptions: rbEXCEPTION takes an exception as its argument, and uses the information in it.

License

The ModAssert package, as well as the Rich Booleans package, are
distributed under the wxWindows Library Licence. This is basically the
LGPL, but with an exception added that you can use the library without
making your source code available. You don't even have to mention that
you use ModAssert or the Rich Booleans. See http://opensource.org/licenses/wxwindows.php for more information about this license.

More information

This article does not describe all the features of ModAssert. See its documentation for a description of all the features.