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.

Example:

ASSERT(a+b==c);

will log something like

condition a+b==c failed

On the other hand

MOD_ASSERT_P(a << b, rbEQUAL(a+b, c));

will log something like

condition rbEQUAL(a+b, c) failed
<a+b>: `17' == <c>:`19' - nok
expression a: 11
expression b: 6
Time: 2009-12-07, 15:43:02
Thread id: 433145 (main thread)

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.

Download

SourceForge.net Logo

Bookmark ModAssert:

img Addthis img Ask img Blinklist imgdel.icio.us img Digg img Fark img Google img Lycos img Ma.gnolia Add this page to Mister Wong Mr Wong img Netscape img Netvousz img Newsvine img Reddit img StumbleUpon img Slashdot img Tailrank img Technorati img Wink img Yahoo

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:

Support is available on the mailinglist.

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.

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

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.