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 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.
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)) |
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.
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.
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).
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()()
.
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)
.
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.
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.
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.
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.
This article does not describe all the features of ModAssert. See its documentation for a description of all the features.