ModAssert, an advanced assertion framework for C++

Know why your application is failing!

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.

ModAssert solves this problem by providing nearly 60 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.

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

Download

SourceForge.net Logo

Why ModAssert

There are many assertion packages, so what makes ModAssert different? The main difference is modularity. 96 of its 128 assertion macros have a condition, which can be an expression that evaluates to a boolean, or can be a Rich Boolean. A Rich Boolean is a macro that evaluates a condition, and creates an analysis if the condition fails, which is passed on to the assertion macro. The assertion macro then displays and/or logs this analysis, along with other useful data. A simple example is the Rich Boolean rbEQUAL, which checks for the equality of its two arguments. If they're not equal, it creates an analysis which contains the value of both arguments. Knowing the values is a lot more useful than just knowing that they are different.

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 assertion 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 packagesModAssert
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 nearly 60 Rich Booleans, and 96 modular assertions, so that makes nearly 6000 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, rbIN_CONTAINER and rbIN_XCONTAINER 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, or sorted strictly, 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 6000.

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

Using the code

See the documentation on how to install ModAssert (and the Rich Booleans)

Then include "modassert/assert.hpp" in the source files where you want to use ModAssert.

The 8 basic assertion macros

There are 8 basic assertion macros, that can be extended. They are MOD_ASSERT, MOD_VERIFY, MOD_CHECK, MOD_FAIL, MOD_CHECK_FAIL, MOD_VERIFY_V, 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_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. 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.

Example:

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

Note: if you use a Rich Boolean in MOD_VERIFY_V, 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.

Example:

MOD_ASSERT_P(a << b << c << d, rbEQUAL(a+b, c+d));
int sum = MOD_VERIFY_VP((a, b, c, d), rbEQUAL(a+b, c+d));

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:

ModAssert::Group<ModAssert::ReportAlways> group;
MOD_ASSERT_G(group % ModAssert::Warning, rbLESS(foo(a), foo(b)));

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()().

Example:

for (int i=0; i<10000; ++i)
{
    ...
    MOD_ASSERT_O(return false, "Stop processing", rbLESS(a, b));
}

You can also combine these suffixes, in the order P, G, O. Their arguments are in the same order.

Example:

MOD_ASSERT_PGO(a<<b, ModAssert::Fatal, return false, 
         "Stop processing", rbLESS(foo(a), foo(b)));

This gives you a total of 64 assertion macros. Actually there are 64 more that have to do with default parameters (see the documentation), so the total is actually 128. 96 of them can have a Rich Boolean as the condition, of which there are almost 60. This gives you almost 6000 combinations. No other assertion package offers this. Most assertion packages don't even have 128 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 nearly 60 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.

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.