#inclu" /> #inclu" /> #inclu"/>

C++ Catch2 : Generate a array with random values

142 views Asked by At

I try to implement a generator in catch2 of an std::array of 1024 random values. Below my code

#include "catch2/catch_all.hpp"
#include <array>
#include <vector>

    TEST_CASE("Foo") {
        auto s = GENERATE(as<std::array<char, 1024> >{}, 'a');
        REQUIRE(!s.empty());
    }

The following error is generated

 In file included from /opt/compiler-explorer/libs/catch2/v3.0.0-preview3/src/catch2/generators/catch_generators_all.hpp:25,
                 from /opt/compiler-explorer/libs/catch2/v3.0.0-preview3/src/catch2/catch_all.hpp:46,
                 from <source>:1:
/opt/compiler-explorer/libs/catch2/v3.0.0-preview3/src/catch2/generators/catch_generators.hpp: In instantiation of 'Catch::Generators::Generators<T> Catch::Generators::makeGenerators(as<T>, U&&, Gs&& ...) [with T = std::array<char, 1024>; U = char; Gs = {}]':
<source>:7:14:   required from here
/opt/compiler-explorer/libs/catch2/v3.0.0-preview3/src/catch2/generators/catch_generators.hpp:181:39: error: array must be initialized with a brace-enclosed initializer
  181 |         return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... );
      |   

Any idea how to overcome this issue?

Example

2

There are 2 answers

0
Marek R On BEST ANSWER

Documentation how to provide custom generators in Catch2 sucks. Had to review Catch2 code to figure out how other generators are implemented and got this:

template <typename T, typename Dist>
struct FillRandomGenerator : Catch::Generators::IGenerator<T> {
    template <typename D>
    explicit FillRandomGenerator(size_t repeatCount, D&& dist)
        : repeatCount { repeatCount }
        , distr { std::forward<D>(dist) }
    {
        fillRandom();
    }

    T value;
    size_t repeatCount;
    Dist distr;
    std::default_random_engine randGen {};

    bool next() override
    {
        fillRandom();
        return --repeatCount;
    }

    T const& get() const override
    {
        return value;
    }

    void fillRandom()
    {
        std::generate(std::begin(value), std::end(value), [&]() { return distr(randGen); });
    }
};

template <typename T, typename Dist>
Catch::Generators::GeneratorWrapper<T> fillRandom(size_t repeatCount, Dist&& distr)
{
    return Catch::Generators::GeneratorWrapper<T>(Catch::Detail::make_unique<FillRandomGenerator<T, std::decay_t<Dist>>>(repeatCount, std::forward<Dist>(distr)));
}

https://godbolt.org/z/jfzT15Tcv

There is lots of room for improvement, but general idea should be clear. With current implementation works only for std::array, other containers will remain empty.

I do not like that I had to use Catch::Detail::make_unique. Detail usually means user of library should not touch symbols in this namespace since. symbols there can change without any warning in next release.

0
Terence Simpson On

The problem is that the macro GENERATE is using the wrong kind of initialization for array types, you can create a simple type to translate it like this (full example)

template<typename T, std::size_t N>
struct array_wrapper : std::array<T, N>
{
    template<typename... ArgTs>
    constexpr array_wrapper(ArgTs&&... args)
        : std::array<T, N>{std::forward<ArgTs>(args)...}
    {
    }
};

Note that you would either assign it to a variable of type std::array<T, N> (as opposed to auto) or cast it to the base type if you need a "pure" std::array. Talue is implicitly convertible to std::array due to public inheritance anyway, so it can be used as one.