Skip to content

A C++ header only library providing functionalitiy for design by contract

License

Notifications You must be signed in to change notification settings

bernedom/bertrand

Repository files navigation

GitHub Releases GitHub license Build Status Language grade: C/C++ Codacy Badge

bertrand

A C++ header only library providing a trivial implementation for design by contract. For a good introduction into design by contract check the Eiffel Software Explanation

An illustrative example:

#include <bertrand/bertrand.hpp>

float divide(float dividend, float divisor) {
  Require(divisor != 0, "No division by zero");
  auto result = dividend / divisor;
  Ensure(dividend - result * divisor < std::numeric_limits<float>::epsilon(),
         "Division is correct");
  return result;
}

The contract related keywords Require, Ensure and Invariant are implemented. A failing contract results in immediate program termination by an abort. The contract message is printed to stderr. For gcc and clang the stack trace is also printed out. Passing multiple values to the contract for additional debug information is possible as long as the type is supported by the stream operator <<

Require(false, "Something is not right: ", someVariable, " and ", anotherVariable)

By default contracts are enabled unless the NDEBUG compiler flag is set. Contracts can be force enabled or disabled by passing the compiler flag BERTRAND_ENABLE_CONTRACTS or BERTRAND_DISABLE_CONTRACTS passing both will lead to a compiler error.

In order to facilitate testing of the contract functionality contracts are throwing an exception instead of calling abort, if the preprocessor-flag BERTRAND_CONTRACTS_ARE_EXCEPTIONS is set.

By default printing the stacktrace is enabled for gcc or clang, but it can be disabled by passing the preprocessor-flag BERTRAND_DISABLE_STACKTRACE. To enable proper demangling of the symbols of the stack trace pass -export-dynamic to the linker. Either directly or by passing -rdynamic to the compiler.

Extended helpers for more complicated tests

Bertrand provides functionality to check if a value is matched against a predefined set of values. For example to check if a raw variable can be casted into an enum. (Casting from an to enums is a code smell, but especially when dealing with data from low-level firmware written in C or assembly this might be required)

#include <bertrand/bertrand.hpp>
enum class Color { Red, Green, Blue };

Color toColor(int raw_value) {
  Require(bertrand::find(static_cast<Color>(raw_value))
              .in(Color::Red, Color::Green, Color::Blue),
          "raw_value is a valid color");
  return static_cast<Color>(raw_value);
}

...

toColor(0); // Passes
toColor(5); // Fails with a contract

Building and Installation

To build the unit tests the Catch2 unit testing framework is needed. It is retrieved with conan.io

cmake installation

mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX:PATH=${HOME}/local-install
cmake --build . --target install

cmake sub-directory

bertrand can be included using add_subdirectory if it cannot be installed into a system.

If bertrand is included and the EXCLUDE_FROM_ALL flag is set, bertrand test can be disabled by setting the BERTRAND_BUILD_TESTING option

  set(BERTRAND_BUILD_TESTING OFF)
  add_subdirectory(${bertrand_SOURCE_DIR} ${bertrand_BINARY_DIR}
                   EXCLUDE_FROM_ALL)

If bertrand is added in a subdirectory and should be installed together with the parent project, set the CMake option BERTRAND_INSTALL_LIBRARY to force installation. This applies also if bertrand is pulled using CMakes FetchContent or ExternalProject

conan.io

bertrand is available from ConanCenter

in the conanfile.txt then add

[requires]
bertrand[>0.0 <2.0]