Add contract-based testing infrastructure

Merged Daniel Hugo Campora Perez requested to merge dcampora_contracts into master

This MR introduces the ability to define contracts in Allen algorithms. Contracts can now be defined in the form of preconditions and postconditions for each algorithm. Pre and post conditions run before and after the operator() execution of the algorithm and check that the data is in the expected state by guaranteeing developer-defined data tests.

In order to define a contract, it is enough to define a struct that inherits from either Allen::contract::Precondition or Allen::contract::Postcondition, implement an operator() with a concrete signature, and register the contract in the algorithm. For instance, the code below defines a Precondition for algorithm velo_search_by_triplet_t:

namespace velo_search_by_triplet {
  struct Parameters { ... };

  // Define a precondition that checks the cluster container input
  struct cluster_container_checks : Allen::contract::Precondition {
    void operator()(
      const ArgumentReferences<Parameters>&,
      const RuntimeOptions&,
      const Constants&,
      const Allen::Context&) const;

  struct velo_search_by_triplet_t : public DeviceAlgorithm, Parameters {
    // Register contracts for this algorithm
    using contracts = std::tuple<cluster_container_checks>;
} // namespace velo_search_by_triplet

The above contract should be then defined in a source file. Concretely, the above example is defined in

Changes introduced by this MR:

  • Introduce the namespace Allen::contract and two new structs Precondition and Postcondition to allow the definition of contracts.

  • Created the first contract for Search by triplet that checks the state of the hit container prior to running the algorithm's operator().

  • Added a new CMake option ENABLE_CONTRACTS which is disabled by default. If enabled, it guarantees that contracts are run for each algorithm that has registered contracts.

  • Added make_vector free-standing function that takes an argument and produces an std::vector container and populates it with the contents of the argument. If the argument's type is bool, the resulting datatype is std::vector<char>.

  • Simplified slightly syntax of Scheduler Machinery to add contract support.

  • Created function require(bool, std::string), which allows to define a condition that should be fulfilled and a string describing the requirement of the condition. This method of writing requirements has several advantages over plain asserts, since the behaviour can be defined in the backend and it decouples contracts from macro NDEBUG. Currently, if a requirement is not met, an exception of type ContractException is thrown with a message similar to the following (the demangle trick is used here @raaij ):

    terminate called after throwing an instance of 'Allen::contract::ContractException'
      what():  Contract exception in algorithm velo_search_by_triplet, precondition velo_search_by_triplet::cluster_container_checks: Require that y be lower than max value
Edited by Rosen Matev

Merge request reports