-
Notifications
You must be signed in to change notification settings - Fork 13
Programming with Assertions
Longbow's Assertions offer a way to check runtime invariance throughout your program. Traps are a subset of Assertions that are executed at runtime even if Assertions have been turned off during deployment. They should be used when program failure is the correct response to assertion failure.
The assertion framework is based on the following design principles:
- It is intentionally simple and can be extended.
- You don't have to change your program design to accommodate assertions.
- It is designed specifically for C programs.
- It is based on programming with invariance.
- Assertions may be used only during development as a debugging aid, or in deployment as well to offer more descriptive errors to users.
Assertions can be used to debug code and give better information about errors to users. They should not be used for error handling. Assertions should be used to explicitly test for conditions that must be true in order for an operation to work. Examples include testing for NULL pointers, out-of-bounds array indices, and incorrect dependent relationships. Ultimately your code should work every time under all input conditions without ever triggering an assertion although passing tests don't guarantee proper design. You should aim for 100% code coverage.
Be strategic about where the assertions are located and what they test for. A failed assertion should be considered a bug should be treated as such. For example a failure to open a file is likely not a bug in your program, per se, but indicative of some other problem and programmatic error handling would probably be the best approach to handling the missing file.
Assertions can be included or excluded at compile-time. In many cases, it is reasonable to keep the assertions in production releases as an aid to future bug reporting.
LongBow provides a basic set of assertions that test a condition and trigger the assertion if the condition fails to be true. When an assertion triggers the following occurs:
- An Event is created which records the kind of assertion that failed and the location of the assertion's failure.
- The formatted message of the failed assertion is reported via the LongBow report library.
- The running program is sent a
SIGABRT
signal.
The following four assertions are currently supported:
assertTrue
assertFalse
assertNull
assertNotNull
The function signatures for assertions follow the pattern:
void assertX(condition, "message", ...);
Where "message"
is a printf(3)
format,
nul-terminated C-String that will be displayed when the assertion triggers.
The ...
represents the variable number of arguments that might be used to
create the string.
There is one C header file that must be required to use the assertion mechanism:
LongBow/runtime.h
is the basic header file needed for assertions.
LongBow traps are subsets of assertions and are intended for simple error reporting. There is no functional difference between a trap and an assertion, however, a traps cannot be shut off during runtime so they are good to use for deployment if you plan to turn assertions off and the program should terminate when the assertion is not met.
Traps take as arguments a condition and a printf(3) format C string of explanatory text along with any values necessary to format the string.
A typical trap function signature is:
void trapX(condition, "message", ...);
Where Kind
represents the kind of trap (see LongBow/traps.h
),
condition
is a logical expression that, if true, evaluated to true
, triggers the trap and issues "message"
.
Where ...
is the printf format string and values.
Currently supported traps are defined in traps.h
and include:
-
trapCannotObtainLock
Signal that a lock could not be obtained. -
trapCannotObtainLockIf
If the given condition is true, signal that a lock could not be obtained. -
trapCoreDump
Cause the running program to receive aSIGTRAP
. -
trapIllegalValue
Used for capturing failed parameter validation, for example. -
trapIllegalValueIf
If the given condition is true, signal an illegal value. -
trapInvalidValueIf
If the given condition is true, signal an invalid value condition. -
trapNotImplemented
Used to report and abort an unimplemented capability. -
trapOutOfBounds
Used to trap an out-of-bounds condition on an index. -
trapOutOfBoundsIf
If the given condition is true, signal an out-of-bounds condition. -
trapOutOfMemory
Used to signal that no more memory can be allocated. -
trapOutOfMemoryIf
If the given condition is true, signal that no more memory can be allocated. -
trapUnexpectedState
Used to signal that an unexpected or inconsistent state was encountered. -
trapUnexpectedStateIf
If the given condition is true, used to signal that an unexpected state was encountered. -
trapUnrecoverableState
Used to report an unrecoverable state in program execution.
#include <LongBow/assertions>
#include <unistd.h>
#include <string.h>
void
function(char *pointer)
{
assertNotNull(pointer, "The pointer cannot be NULL.");
write(1, pointer, strlen(pointer));
}
int
main(int argc, char \*argv[])
{
function(0);
}
In this case the assertNotNull
will trigger and the program will immediately terminate with the following output:
Assert pointer.c:8 function() pointer != NULL The pointer cannot be NULL.
0 pointer 0x0000000107840d4c function + 188
1 pointer 0x0000000107840dd1 main + 33
2 libdyld.dylib 0x00007fff887595fd start + 1
LONGBOW_TEST_CASE(Global, myTest)
{
struct timeval timeval;
timeval.tv_sec = 0;
timeval.tv_usec = 1000;
char *expected = "0.001000";
char *actual = parcTime\_FormatTimeval(timeval);
assertTrue(strcmp(expected, actual) == 0, "Expected \%s, actual \%s", expected, actual);
parc_free(actual);
}
static void
parcDeque_AssertInvariants(const PARCDeque \*deque)
{
assertNotNull(deque, "Parameter cannot be null.");
if (deque->head != NULL) {
assertTrue(deque->size != 0, "PARCDeque head is not-null, but size is zero.");
assertNotNull(deque->tail, "PARCDeque head is not-null, but tail is null.");
parcDequeNode_AssertInvariants(deque->head);
parcDequeNode_AssertInvariants(deque->tail);
} else {
assertNull(deque->tail, "PARCDeque head is null, but tail is not null.");
assertTrue(deque->size == 0, "PARCDeque head is null, but size is not zero.");
}
}
RtaCommand *
rtaCommand_Read(int fd)
{
ssize_t readlen;
uint32_t netbyteorder;
size_t len;
char *p;
RtaCommand *command;
readlen = read(fd, &netbyteorder, 4);
assertFalse(readlen < 0, "socket read error: \%s\n", strerror(errno));
assertTrue(readlen == 4, "Partial read on command length");
len = ntohl(netbyteorder);
p = parc_malloc(len);
readlen = read(fd, p, len);
assertTrue(readlen == len, "Partial read on command");
command = rtaCommand_Parse(p);
parc_free(p);
return command;
}