Click here to Skip to main content
15,885,537 members
Articles / Desktop Programming / MFC
Article

ModAssert, an advanced assertion framework for C++

Rate me:
Please Sign up or sign in to vote.
4.79/5 (19 votes)
2 Mar 200715 min read 66.3K   750   48   25
An article on using ModAssert, an advanced assertion framework of 24 assertions that can use Rich Booleans.

To have the most recent version, download ModAssert and Rich Booleans from SourceForge (first two links). Otherwise, use the downloads on CodeProject, which are stripped of Linux and wxWidgets support (next two links).

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:

  • 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, 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 and 2005, and gcc on Windows and Linux), with most of the warning levels on. Project files and configure scripts are provided.

Background

I wrote ModAssert because I don't like assertions that don't separate the assertion and the condition they test, like ASSERT_EQUAL(a,b). If you want assertions that add a message, or evaluate their arguments if assertions are disabled, or throw an exception, or check if a is less than b, you end up with a lot of combinations. To solve this problem, the assertions in ModAssert can have Rich Booleans as their argument just as well as the boolean conditions. Rich Booleans perform an analysis, and hold the debugging information if the condition fails, that is displayed. An example is MOD_ASSERT(rbEQUAL(a,b)). Here, rbEQUAL(a,b) is the Rich Boolean, but it could also be rbLESS(a,b), rbEQUAL_TYPES(a,b), rbEQUAL_BITWISE(a,b) or any other of the nearly sixty Rich Booleans.

Furthermore, most assertion frameworks are not very flexible or extensible, unlike ModAssert.

Using the code

Getting started

First, build the Rich Booleans library and the ModAssert library in the configuration(s) that you need. Project files are provided for this.

It's best to use environment variables to point to the root directory of the Rich Booleans and ModAssert installation (e.g.: c:/modassert-1.3). This makes migrating and upgrading a lot easier. The demos and the text below use RICHBOOL and MODASSERT, so it's best to use these names.

In your project, add $(RICHBOOL)/include and $(MODASSERT)/include to your include path, and $(RICHBOOL)/<configuration> and $(MODASSERT)/<configuration> to your library include path, where <configuration> is the subdirectory of your configuration (e.g. DebugMTD). Finally, link your application with richbool.lib and modassert.lib.

On POSIX systems, use the makefiles. Enter ./configure, followed by make and make install, for both Rich Booleans and ModAssert. This will install the headerfiles and libraries in a place that compilers look already.

Build the library in the Console, Win32 or wxgui subdirectory of the main directory, in the same configuration(s) as the Rich Booleans and ModAssert libraries, and link to it. Activate the code in these files, so that assertions are shown, by respectively calling ModAssert::SetupForConsole() for console applications, ModAssert::SetupForWin32(hInstance) for Win32 applications, or ModAssert::SetupForWxWidgets() for WxWidgets applications. The console version writes the information about the failed assertion to the standard output and asks for an action to be taken on the standard input. The Win32 and WxWidgets versions shows a dialog with all the information that asks for an action to be taken, and traces the same information to the debug window, so that you can look back at it later.

An alternative to the last step is to write your own displayers and loggers. In any case you can add other loggers, especially fileloggers.

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:

C++
#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):

Image 1

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:

C++
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:

C++
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:

C++
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:

C++
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:

C++
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:

C++
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):

Image 2

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

Example:

C++
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:

C++
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:

C++
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:

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

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

Points of interest

Usually, assertion macros (and other statement macros) are written using 'do { ... } while (false)'. However, because a developer can give actions as argument of some of the assertion macros, this could also be break or continue, which obviously wouldn't work. Therefore, I had to use a similar trick, namely 'if { ... } else 0'. You can (should, actually) also add a semicolon to this, and 0 is a valid C++ command that does nothing. I tested this in if and if-else without braces, and it works fine.

So you can write the following code:

C++
for (int i=0; i<10000; ++i)
{
    ... // do some processing
    int a = foo(i);
    MOD_ASSERT_O(break, "Stop processing", rbLESS(a, 10));
}

I also learned that the action can be quite long, as long as all the commas are between the parentheses, like:

C++
MOD_ASSERT_O(
    ++p; // semicolon is allowed here
    if (b)
    {
        int n=0;
        foo(n, 1); // fine, comma is between parentheses
    },
    "call foo if b",
    rbMORE(a, 0)
);

Here, the macro MOD_ASSERT_O has only three arguments: the action, the description, and the Rich Boolean.

An interesting problem was that GetLastError() always returned 0, even when I was sure it wouldn't. I found out that logging to a file resets the return value of GetLastError() to 0. Therefore, I added the system of hooks (see the documentation in the download) that are called by ModAssert before the loggers are called. Then I created a hook that stores the return value of GetLastError(), which is later used by the InfoProviders.

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Belgium Belgium
I graduated at the university of Ghent (Belgium) in 1994, after which I started working at Sophis Systems. I was responsible for the colour quality of the software for textile design, which meant implementing sophisticated mathematical algorithms. In 1998 I joined Dekimo, where I wrote software for processing documents and some other things. In 2000 I joined Applied Maths, where I wrote mathematical algorithms for biological research. Now I have my own company, developing software for unit testing.

Comments and Discussions

 
GeneralrbEQUAL_TYPES and rbHAS_TYPE compilation error Pin
bishopnator29a3-May-11 6:12
bishopnator29a3-May-11 6:12 
GeneralRe: rbEQUAL_TYPES and rbHAS_TYPE compilation error Pin
Markvp4-May-11 11:32
Markvp4-May-11 11:32 
Questionmodassert with multiple projects Pin
bishopnator29a29-Mar-11 2:41
bishopnator29a29-Mar-11 2:41 
AnswerRe: modassert with multiple projects Pin
Markvp30-Mar-11 22:28
Markvp30-Mar-11 22:28 
GeneralRe: modassert with multiple projects Pin
Markvp1-Apr-11 2:29
Markvp1-Apr-11 2:29 
GeneralRe: modassert with multiple projects Pin
bishopnator29a1-Apr-11 3:02
bishopnator29a1-Apr-11 3:02 
GeneralRe: modassert with multiple projects Pin
Markvp7-Apr-11 8:40
Markvp7-Apr-11 8:40 
GeneralRe: modassert with multiple projects Pin
bishopnator29a19-Apr-11 4:45
bishopnator29a19-Apr-11 4:45 
GeneralRe: modassert with multiple projects Pin
Markvp20-Apr-11 10:06
Markvp20-Apr-11 10:06 
GeneralRe: modassert with multiple projects Pin
bishopnator29a2-May-11 22:01
bishopnator29a2-May-11 22:01 
GeneralRe: modassert with multiple projects Pin
Markvp4-May-11 11:24
Markvp4-May-11 11:24 
GeneralRe: modassert with multiple projects Pin
Markvp4-May-11 23:18
Markvp4-May-11 23:18 
GeneralRe: modassert with multiple projects Pin
bishopnator29a5-May-11 3:12
bishopnator29a5-May-11 3:12 
GeneralRe: modassert with multiple projects Pin
bishopnator29a4-Apr-11 23:30
bishopnator29a4-Apr-11 23:30 
GeneralRe: modassert with multiple projects Pin
bishopnator29a5-Apr-11 0:00
bishopnator29a5-Apr-11 0:00 
GeneralExcellent!!! Thanks for contributing this! [modified] Pin
Mitchel Haas3-Aug-09 11:04
Mitchel Haas3-Aug-09 11:04 
GeneralRe: Excellent!!! Thanks for contributing this! Pin
Markvp4-Aug-09 22:38
Markvp4-Aug-09 22:38 
QuestionWhat changed in the new update? Pin
Hans Dietrich27-Sep-06 11:16
mentorHans Dietrich27-Sep-06 11:16 
AnswerRe: What changed in the new update? Pin
Markvp27-Sep-06 20:57
Markvp27-Sep-06 20:57 
GeneralQuestion about licensing Pin
Hans Dietrich14-Jun-06 5:22
mentorHans Dietrich14-Jun-06 5:22 
GeneralRe: Question about licensing Pin
Markvp14-Jun-06 23:32
Markvp14-Jun-06 23:32 
Generalhmmmm... Pin
Hans Dietrich15-Jun-06 7:06
mentorHans Dietrich15-Jun-06 7:06 
GeneralRe: hmmmm... Pin
Markvp15-Jun-06 23:27
Markvp15-Jun-06 23:27 
GeneralGreat project Pin
JasonReese20-May-06 1:23
JasonReese20-May-06 1:23 
GeneralRe: Great project Pin
Markvp21-May-06 18:58
Markvp21-May-06 18:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.