Skip to content
This repository has been archived by the owner on Apr 13, 2019. It is now read-only.

Programming with Assertions

Glenn Scott edited this page Jul 25, 2016 · 1 revision

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.

Designing with Assertions

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.

Using the Assertion Libraries

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.

Using Traps

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 a SIGTRAP.
  • 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.

Examples

assertNotNull Example

#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

assertTrue Example

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);
}

assertNull, assertNotNull, and assertTrue Example

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.");
    }
}

assertFalse Example

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;
}