From 269303d9d97231bc6b32413760ab00e6925a342a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Tue, 19 Feb 2019 22:09:14 +0100 Subject: [PATCH] Introduce random number (Integral and Float) generators --- docs/generators.md | 6 +- include/catch.hpp | 1 + .../internal/catch_generators_specific.hpp | 88 +++++++++++++++++++ projects/CMakeLists.txt | 1 + .../SelfTest/UsageTests/Generators.tests.cpp | 13 +++ 5 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 include/internal/catch_generators_specific.hpp diff --git a/docs/generators.md b/docs/generators.md index 340ff7c5..997feab7 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -43,6 +43,9 @@ a test case, * `RepeatGenerator` -- repeats output from a generator `n` times * `MapGenerator` -- returns the result of applying `Func` on elements from a different generator +* 2 specific purpose generators + * `RandomIntegerGenerator` -- generates random Integrals from range + * `RandomFloatGenerator` -- generates random Floats from range The generators also have associated helper functions that infer their type, making their usage much nicer. These are @@ -54,6 +57,7 @@ type, making their usage much nicer. These are * `repeat(repeats, GeneratorWrapper&&)` for `RepeatGenerator` * `map(func, GeneratorWrapper&&)` for `MapGenerator` (map `T` to `T`) * `map(func, GeneratorWrapper&&)` for `MapGenerator` (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 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 one more purpose, and that is to provide simple way of generating trivial diff --git a/include/catch.hpp b/include/catch.hpp index 0d4a86a7..56bd9652 100644 --- a/include/catch.hpp +++ b/include/catch.hpp @@ -63,6 +63,7 @@ #include "internal/catch_capture_matchers.h" #endif #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 // in the conditionally compiled sections diff --git a/include/internal/catch_generators_specific.hpp b/include/internal/catch_generators_specific.hpp new file mode 100644 index 00000000..9d9f5e65 --- /dev/null +++ b/include/internal/catch_generators_specific.hpp @@ -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 + +namespace Catch { +namespace Generators { + +template +class RandomFloatingGenerator final : public IGenerator { + // FIXME: What is the right seed? + std::minstd_rand m_rand; + std::uniform_real_distribution m_dist; + Float m_current_number; +public: + + RandomFloatingGenerator(Float a, Float b): + m_rand(getCurrentContext().getConfig()->rngSeed()), + m_dist(a, b) { + static_cast(next()); + } + + Float const& get() const override { + return m_current_number; + } + bool next() override { + m_current_number = m_dist(m_rand); + return true; + } +}; + +template +class RandomIntegerGenerator final : public IGenerator { + std::minstd_rand m_rand; + std::uniform_int_distribution m_dist; + Integer m_current_number; +public: + + RandomIntegerGenerator(Integer a, Integer b): + m_rand(getCurrentContext().getConfig()->rngSeed()), + m_dist(a, b) { + static_cast(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 std::enable_if::value && !std::is_same::value, +GeneratorWrapper>::type +random(T a, T b) { + return GeneratorWrapper( + pf::make_unique>(a, b) + ); +} + +template +typename std::enable_if::value, +GeneratorWrapper>::type +random(T a, T b) { + return GeneratorWrapper( + pf::make_unique>(a, b) + ); +} + +} // namespace Generators +} // namespace Catch + + +#endif // TWOBLUECUBES_CATCH_GENERATORS_SPECIFIC_HPP_INCLUDED diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index bc3914e0..8f4e4c1e 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -102,6 +102,7 @@ set(INTERNAL_HEADERS ${HEADER_DIR}/internal/catch_external_interfaces.h ${HEADER_DIR}/internal/catch_fatal_condition.h ${HEADER_DIR}/internal/catch_generators.hpp + ${HEADER_DIR}/internal/catch_generators_specific.hpp ${HEADER_DIR}/internal/catch_impl.hpp ${HEADER_DIR}/internal/catch_interfaces_capture.h ${HEADER_DIR}/internal/catch_interfaces_config.h diff --git a/projects/SelfTest/UsageTests/Generators.tests.cpp b/projects/SelfTest/UsageTests/Generators.tests.cpp index 0110b442..db5d378b 100644 --- a/projects/SelfTest/UsageTests/Generators.tests.cpp +++ b/projects/SelfTest/UsageTests/Generators.tests.cpp @@ -145,3 +145,16 @@ TEST_CASE("Generators -- adapters", "[generators]") { 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::value); + } + SECTION("Infer double from double arguments") { + auto val = GENERATE(take(4, random(0., 1.))); + STATIC_REQUIRE(std::is_same::value); + } +}