Introduce random number (Integral and Float) generators

This commit is contained in:
Martin Hořeňovský 2019-02-19 22:09:14 +01:00
parent e8bfd882e8
commit 269303d9d9
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
5 changed files with 107 additions and 2 deletions

View File

@ -43,6 +43,9 @@ a test case,
* `RepeatGenerator<T>` -- repeats output from a generator `n` times * `RepeatGenerator<T>` -- repeats output from a generator `n` times
* `MapGenerator<T, U, Func>` -- returns the result of applying `Func` * `MapGenerator<T, U, Func>` -- returns the result of applying `Func`
on elements from a different generator on elements from a different generator
* 2 specific purpose generators
* `RandomIntegerGenerator<Integral>` -- generates random Integrals from range
* `RandomFloatGenerator<Float>` -- generates random Floats from range
The generators also have associated helper functions that infer their The generators also have associated helper functions that infer their
type, making their usage much nicer. These are type, making their usage much nicer. These are
@ -54,6 +57,7 @@ type, making their usage much nicer. These are
* `repeat(repeats, GeneratorWrapper<T>&&)` for `RepeatGenerator<T>` * `repeat(repeats, GeneratorWrapper<T>&&)` for `RepeatGenerator<T>`
* `map(func, GeneratorWrapper<T>&&)` for `MapGenerator<T, T, Func>` (map `T` to `T`) * `map(func, GeneratorWrapper<T>&&)` for `MapGenerator<T, T, Func>` (map `T` to `T`)
* `map<T>(func, GeneratorWrapper<U>&&)` for `MapGenerator<T, U, Func>` (map `U` to `T`) * `map<T>(func, GeneratorWrapper<U>&&)` for `MapGenerator<T, U, Func>` (map `U` to `T`)
* `range(IntegerOrFloat a, IntegerOrFloat b)` for `RandomIntegerGenerator` or `RandomFloatGenerator`
And can be used as shown in the example below to create a generator And can be used as shown in the example below to create a generator
that returns 100 odd random number: that returns 100 odd random number:
@ -69,8 +73,6 @@ TEST_CASE("Generating random ints", "[example][generator]") {
} }
``` ```
_Note that `random` is currently not a part of the first-party generators_.
Apart from registering generators with Catch2, the `GENERATE` macro has Apart from registering generators with Catch2, the `GENERATE` macro has
one more purpose, and that is to provide simple way of generating trivial one more purpose, and that is to provide simple way of generating trivial

View File

@ -63,6 +63,7 @@
#include "internal/catch_capture_matchers.h" #include "internal/catch_capture_matchers.h"
#endif #endif
#include "internal/catch_generators.hpp" #include "internal/catch_generators.hpp"
#include "internal/catch_generators_specific.hpp"
// These files are included here so the single_include script doesn't put them // These files are included here so the single_include script doesn't put them
// in the conditionally compiled sections // in the conditionally compiled sections

View File

@ -0,0 +1,88 @@
/*
* Created by Martin on 15/6/2018.
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef TWOBLUECUBES_CATCH_GENERATORS_SPECIFIC_HPP_INCLUDED
#define TWOBLUECUBES_CATCH_GENERATORS_SPECIFIC_HPP_INCLUDED
#include "catch_context.h"
#include "catch_generators.hpp"
#include "catch_interfaces_config.h"
#include <random>
namespace Catch {
namespace Generators {
template <typename Float>
class RandomFloatingGenerator final : public IGenerator<Float> {
// FIXME: What is the right seed?
std::minstd_rand m_rand;
std::uniform_real_distribution<Float> m_dist;
Float m_current_number;
public:
RandomFloatingGenerator(Float a, Float b):
m_rand(getCurrentContext().getConfig()->rngSeed()),
m_dist(a, b) {
static_cast<void>(next());
}
Float const& get() const override {
return m_current_number;
}
bool next() override {
m_current_number = m_dist(m_rand);
return true;
}
};
template <typename Integer>
class RandomIntegerGenerator final : public IGenerator<Integer> {
std::minstd_rand m_rand;
std::uniform_int_distribution<Integer> m_dist;
Integer m_current_number;
public:
RandomIntegerGenerator(Integer a, Integer b):
m_rand(getCurrentContext().getConfig()->rngSeed()),
m_dist(a, b) {
static_cast<void>(next());
}
Integer const& get() const override {
return m_current_number;
}
bool next() override {
m_current_number = m_dist(m_rand);
return true;
}
};
// TODO: Ideally this would be also constrained against the various char types,
// but I don't expect users to run into that in practice.
template <typename T>
typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value,
GeneratorWrapper<T>>::type
random(T a, T b) {
return GeneratorWrapper<T>(
pf::make_unique<RandomIntegerGenerator<T>>(a, b)
);
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value,
GeneratorWrapper<T>>::type
random(T a, T b) {
return GeneratorWrapper<T>(
pf::make_unique<RandomFloatingGenerator<T>>(a, b)
);
}
} // namespace Generators
} // namespace Catch
#endif // TWOBLUECUBES_CATCH_GENERATORS_SPECIFIC_HPP_INCLUDED

View File

@ -102,6 +102,7 @@ set(INTERNAL_HEADERS
${HEADER_DIR}/internal/catch_external_interfaces.h ${HEADER_DIR}/internal/catch_external_interfaces.h
${HEADER_DIR}/internal/catch_fatal_condition.h ${HEADER_DIR}/internal/catch_fatal_condition.h
${HEADER_DIR}/internal/catch_generators.hpp ${HEADER_DIR}/internal/catch_generators.hpp
${HEADER_DIR}/internal/catch_generators_specific.hpp
${HEADER_DIR}/internal/catch_impl.hpp ${HEADER_DIR}/internal/catch_impl.hpp
${HEADER_DIR}/internal/catch_interfaces_capture.h ${HEADER_DIR}/internal/catch_interfaces_capture.h
${HEADER_DIR}/internal/catch_interfaces_config.h ${HEADER_DIR}/internal/catch_interfaces_config.h

View File

@ -145,3 +145,16 @@ TEST_CASE("Generators -- adapters", "[generators]") {
REQUIRE(j > 0); REQUIRE(j > 0);
} }
} }
// Note that because of the non-reproducibility of distributions,
// anything involving the random generators cannot be part of approvals
TEST_CASE("Random generator", "[generators][.][approvals]") {
SECTION("Infer int from integral arguments") {
auto val = GENERATE(take(4, random(0, 1)));
STATIC_REQUIRE(std::is_same<decltype(val), int>::value);
}
SECTION("Infer double from double arguments") {
auto val = GENERATE(take(4, random(0., 1.)));
STATIC_REQUIRE(std::is_same<decltype(val), double>::value);
}
}