diff --git a/include/internal/catch_context.cpp b/include/internal/catch_context.cpp index 52fca302..e444a6b3 100644 --- a/include/internal/catch_context.cpp +++ b/include/internal/catch_context.cpp @@ -7,6 +7,7 @@ */ #include "catch_context.h" #include "catch_common.h" +#include "catch_random_number_generator.h" namespace Catch { @@ -59,4 +60,11 @@ namespace Catch { IContext::~IContext() = default; IMutableContext::~IMutableContext() = default; Context::~Context() = default; + + + SimplePcg32& rng() { + static SimplePcg32 s_rng; + return s_rng; + } + } diff --git a/include/internal/catch_context.h b/include/internal/catch_context.h index 3d3c6bad..436621f6 100644 --- a/include/internal/catch_context.h +++ b/include/internal/catch_context.h @@ -55,6 +55,9 @@ namespace Catch { } void cleanUpContext(); + + class SimplePcg32; + SimplePcg32& rng(); } #endif // TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED diff --git a/include/internal/catch_random_number_generator.cpp b/include/internal/catch_random_number_generator.cpp index ec8a5eb9..1ef08d4c 100644 --- a/include/internal/catch_random_number_generator.cpp +++ b/include/internal/catch_random_number_generator.cpp @@ -7,23 +7,67 @@ #include "catch_random_number_generator.h" #include "catch_context.h" +#include "catch_run_context.h" #include "catch_interfaces_config.h" namespace Catch { - std::mt19937& rng() { - static std::mt19937 s_rng; - return s_rng; +namespace { + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4146) // we negate uint32 during the rotate +#endif + // Safe rotr implementation thanks to John Regehr + uint32_t rotate_right(uint32_t val, uint32_t count) { + const uint32_t mask = 31; + count &= mask; + return (val >> count) | (val << (-count & mask)); + } + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +} + + + SimplePcg32::SimplePcg32(result_type seed_) { + seed(seed_); } - void seedRng( IConfig const& config ) { - if( config.rngSeed() != 0 ) { - std::srand( config.rngSeed() ); - rng().seed( config.rngSeed() ); + + void SimplePcg32::seed(result_type seed_) { + m_state = 0; + (*this)(); + m_state += seed_; + (*this)(); + } + + void SimplePcg32::discard(uint64_t skip) { + // We could implement this to run in O(log n) steps, but this + // should suffice for our use case. + for (uint64_t s = 0; s < skip; ++s) { + static_cast((*this)()); } } - unsigned int rngSeed() { - return getCurrentContext().getConfig()->rngSeed(); + SimplePcg32::result_type SimplePcg32::operator()() { + // prepare the output value + const uint32_t xorshifted = static_cast(((m_state >> 18u) ^ m_state) >> 27u); + const auto output = rotate_right(xorshifted, m_state >> 59u); + + // advance state + m_state = m_state * 6364136223846793005ULL + s_inc; + + return output; + } + + bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { + return lhs.m_state == rhs.m_state; + } + + bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { + return lhs.m_state != rhs.m_state; } } diff --git a/include/internal/catch_random_number_generator.h b/include/internal/catch_random_number_generator.h index 817b7841..3f0d6d38 100644 --- a/include/internal/catch_random_number_generator.h +++ b/include/internal/catch_random_number_generator.h @@ -7,17 +7,52 @@ #ifndef TWOBLUECUBES_CATCH_RANDOM_NUMBER_GENERATOR_H_INCLUDED #define TWOBLUECUBES_CATCH_RANDOM_NUMBER_GENERATOR_H_INCLUDED -#include -#include +#include namespace Catch { - struct IConfig; + // This is a simple implementation of C++11 Uniform Random Number + // Generator. It does not provide all operators, because Catch2 + // does not use it, but it should behave as expected inside stdlib's + // distributions. + // The implementation is based on the PCG family (http://pcg-random.org) + class SimplePcg32 { + using state_type = std::uint64_t; + public: + using result_type = std::uint32_t; + static constexpr result_type min() { + return 0; + } + static constexpr result_type max() { + return static_cast(-1); + } - std::mt19937& rng(); - void seedRng( IConfig const& config ); - unsigned int rngSeed(); + // Provide some default initial state for the default constructor + SimplePcg32():SimplePcg32(0xed743cc4U) {} -} + explicit SimplePcg32(result_type seed_); + + void seed(result_type seed_); + void discard(uint64_t skip); + + result_type operator()(); + + private: + friend bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs); + friend bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs); + + // In theory we also need operator<< and operator>> + // In practice we do not use them, so we will skip them for now + + + std::uint64_t m_state; + // This part of the state determines which "stream" of the numbers + // is chosen -- we take it as a constant for Catch2, so we only + // need to deal with seeding the main state. + // Picked by reading 8 bytes from `/dev/random` :-) + static const std::uint64_t s_inc = (0x13ed0cc53f939476ULL << 1ULL) | 1ULL; + }; + +} // end namespace Catch #endif // TWOBLUECUBES_CATCH_RANDOM_NUMBER_GENERATOR_H_INCLUDED diff --git a/include/internal/catch_run_context.cpp b/include/internal/catch_run_context.cpp index 79f906d6..2eb84fb9 100644 --- a/include/internal/catch_run_context.cpp +++ b/include/internal/catch_run_context.cpp @@ -506,4 +506,16 @@ namespace Catch { else CATCH_INTERNAL_ERROR("No result capture instance"); } + + void seedRng(IConfig const& config) { + if (config.rngSeed() != 0) { + std::srand(config.rngSeed()); + rng().seed(config.rngSeed()); + } + } + + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + } diff --git a/include/internal/catch_run_context.h b/include/internal/catch_run_context.h index 66a58c5e..2a8e72dc 100644 --- a/include/internal/catch_run_context.h +++ b/include/internal/catch_run_context.h @@ -151,6 +151,8 @@ namespace Catch { bool m_includeSuccessfulResults; }; + void seedRng(IConfig const& config); + unsigned int rngSeed(); } // end namespace Catch #endif // TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED diff --git a/include/internal/catch_test_case_registry_impl.cpp b/include/internal/catch_test_case_registry_impl.cpp index 84bfdae1..b254ca08 100644 --- a/include/internal/catch_test_case_registry_impl.cpp +++ b/include/internal/catch_test_case_registry_impl.cpp @@ -11,6 +11,7 @@ #include "catch_enforce.h" #include "catch_interfaces_registry_hub.h" #include "catch_random_number_generator.h" +#include "catch_run_context.h" #include "catch_string_manip.h" #include "catch_test_case_info.h" diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index 0eb7cb56..b2c67a64 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -21,6 +21,7 @@ set(TEST_SOURCES ${SELF_TEST_DIR}/IntrospectiveTests/GeneratorsImpl.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/InternalBenchmark.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/PartTracker.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/RandomNumberGeneration.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/Tag.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/String.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/StringManip.tests.cpp diff --git a/projects/SelfTest/Baselines/compact.sw.approved.txt b/projects/SelfTest/Baselines/compact.sw.approved.txt index 89276649..ac775b4f 100644 --- a/projects/SelfTest/Baselines/compact.sw.approved.txt +++ b/projects/SelfTest/Baselines/compact.sw.approved.txt @@ -276,6 +276,10 @@ Tricky.tests.cpp:: passed: true Tricky.tests.cpp:: passed: std::vector{1, 2} == std::vector{1, 2} for: { 1, 2 } == { 1, 2 } Tricky.tests.cpp:: passed: a for: 0x Tricky.tests.cpp:: passed: a == &foo for: 0x == 0x +RandomNumberGeneration.tests.cpp:: passed: SimplePcg32{} == SimplePcg32{} for: {?} == {?} +RandomNumberGeneration.tests.cpp:: passed: SimplePcg32{ 0 } != SimplePcg32{} for: {?} != {?} +RandomNumberGeneration.tests.cpp:: passed: !(SimplePcg32{ 1 } == SimplePcg32{ 2 }) for: !({?} == {?}) +RandomNumberGeneration.tests.cpp:: passed: !(SimplePcg32{ 1 } != SimplePcg32{ 1 }) for: !({?} != {?}) Approx.tests.cpp:: passed: td == Approx(10.0) for: StrongDoubleTypedef(10) == Approx( 10.0 ) Approx.tests.cpp:: passed: Approx(10.0) == td for: Approx( 10.0 ) == StrongDoubleTypedef(10) Approx.tests.cpp:: passed: td != Approx(11.0) for: StrongDoubleTypedef(10) != Approx( 11.0 ) @@ -731,6 +735,51 @@ Condition.tests.cpp:: passed: data.str_hello < "hellp" for: "hello" Condition.tests.cpp:: passed: data.str_hello < "zebra" for: "hello" < "zebra" Condition.tests.cpp:: passed: data.str_hello > "hellm" for: "hello" > "hellm" Condition.tests.cpp:: passed: data.str_hello > "a" for: "hello" > "a" +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 4242248763 (0x) +== +4242248763 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 1867888929 (0x) +== +1867888929 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 1276619030 (0x) +== +1276619030 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 1911218783 (0x) +== +1911218783 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 1827115164 (0x) +== +1827115164 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 1472234645 (0x) +== +1472234645 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 868832940 (0x) +== +868832940 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 570883446 (0x) +== +570883446 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 889299803 (0x) +== +889299803 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 4261393167 (0x) +== +4261393167 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 1472234645 (0x) +== +1472234645 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 868832940 (0x) +== +868832940 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 570883446 (0x) +== +570883446 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 889299803 (0x) +== +889299803 (0x) +RandomNumberGeneration.tests.cpp:: passed: rng() == 0x for: 4261393167 (0x) +== +4261393167 (0x) Message.tests.cpp:: failed: explicitly with 1 message: 'Message from section one' Message.tests.cpp:: failed: explicitly with 1 message: 'Message from section two' CmdLine.tests.cpp:: passed: spec.hasFilters() == false for: false == false diff --git a/projects/SelfTest/Baselines/console.std.approved.txt b/projects/SelfTest/Baselines/console.std.approved.txt index 49363a68..0bd5c84f 100644 --- a/projects/SelfTest/Baselines/console.std.approved.txt +++ b/projects/SelfTest/Baselines/console.std.approved.txt @@ -1380,6 +1380,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 301 | 227 passed | 70 failed | 4 failed as expected -assertions: 1570 | 1418 passed | 131 failed | 21 failed as expected +test cases: 303 | 229 passed | 70 failed | 4 failed as expected +assertions: 1589 | 1437 passed | 131 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/console.sw.approved.txt b/projects/SelfTest/Baselines/console.sw.approved.txt index 9bb87218..d491697f 100644 --- a/projects/SelfTest/Baselines/console.sw.approved.txt +++ b/projects/SelfTest/Baselines/console.sw.approved.txt @@ -2179,6 +2179,32 @@ Tricky.tests.cpp:: PASSED: with expansion: 0x == 0x +------------------------------------------------------------------------------- +Comparison ops +------------------------------------------------------------------------------- +RandomNumberGeneration.tests.cpp: +............................................................................... + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( SimplePcg32{} == SimplePcg32{} ) +with expansion: + {?} == {?} + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( SimplePcg32{ 0 } != SimplePcg32{} ) +with expansion: + {?} != {?} + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE_FALSE( SimplePcg32{ 1 } == SimplePcg32{ 2 } ) +with expansion: + !({?} == {?}) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE_FALSE( SimplePcg32{ 1 } != SimplePcg32{ 1 } ) +with expansion: + !({?} != {?}) + ------------------------------------------------------------------------------- Comparison with explicitly convertible types ------------------------------------------------------------------------------- @@ -5494,6 +5520,125 @@ Condition.tests.cpp:: PASSED: with expansion: "hello" > "a" +------------------------------------------------------------------------------- +Our PCG implementation provides expected results for known seeds + Default seeded +------------------------------------------------------------------------------- +RandomNumberGeneration.tests.cpp: +............................................................................... + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 4242248763 (0x) + == + 4242248763 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 1867888929 (0x) + == + 1867888929 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 1276619030 (0x) + == + 1276619030 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 1911218783 (0x) + == + 1911218783 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 1827115164 (0x) + == + 1827115164 (0x) + +------------------------------------------------------------------------------- +Our PCG implementation provides expected results for known seeds + Specific seed +------------------------------------------------------------------------------- +RandomNumberGeneration.tests.cpp: +............................................................................... + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 1472234645 (0x) + == + 1472234645 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 868832940 (0x) + == + 868832940 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 570883446 (0x) + == + 570883446 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 889299803 (0x) + == + 889299803 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 4261393167 (0x) + == + 4261393167 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 1472234645 (0x) + == + 1472234645 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 868832940 (0x) + == + 868832940 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 570883446 (0x) + == + 570883446 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 889299803 (0x) + == + 889299803 (0x) + +RandomNumberGeneration.tests.cpp:: PASSED: + REQUIRE( rng() == 0x ) +with expansion: + 4261393167 (0x) + == + 4261393167 (0x) + ------------------------------------------------------------------------------- Output from all sections is reported one @@ -12565,6 +12710,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 301 | 211 passed | 86 failed | 4 failed as expected -assertions: 1587 | 1418 passed | 148 failed | 21 failed as expected +test cases: 303 | 213 passed | 86 failed | 4 failed as expected +assertions: 1606 | 1437 passed | 148 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/junit.sw.approved.txt b/projects/SelfTest/Baselines/junit.sw.approved.txt index d1ec9db4..a78ac477 100644 --- a/projects/SelfTest/Baselines/junit.sw.approved.txt +++ b/projects/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -242,6 +242,7 @@ Exception.tests.cpp: + @@ -577,6 +578,8 @@ Condition.tests.cpp: + + Message from section one diff --git a/projects/SelfTest/Baselines/xml.sw.approved.txt b/projects/SelfTest/Baselines/xml.sw.approved.txt index 9df28085..a05b0ec8 100644 --- a/projects/SelfTest/Baselines/xml.sw.approved.txt +++ b/projects/SelfTest/Baselines/xml.sw.approved.txt @@ -2529,6 +2529,41 @@ Nor would this + + + + SimplePcg32{} == SimplePcg32{} + + + {?} == {?} + + + + + SimplePcg32{ 0 } != SimplePcg32{} + + + {?} != {?} + + + + + !(SimplePcg32{ 1 } == SimplePcg32{ 2 }) + + + !({?} == {?}) + + + + + !(SimplePcg32{ 1 } != SimplePcg32{ 1 }) + + + !({?} != {?}) + + + + @@ -6728,6 +6763,165 @@ Nor would this + +
+ + + rng() == 0x + + + 4242248763 (0x) +== +4242248763 (0x) + + + + + rng() == 0x + + + 1867888929 (0x) +== +1867888929 (0x) + + + + + rng() == 0x + + + 1276619030 (0x) +== +1276619030 (0x) + + + + + rng() == 0x + + + 1911218783 (0x) +== +1911218783 (0x) + + + + + rng() == 0x + + + 1827115164 (0x) +== +1827115164 (0x) + + + +
+
+ + + rng() == 0x + + + 1472234645 (0x) +== +1472234645 (0x) + + + + + rng() == 0x + + + 868832940 (0x) +== +868832940 (0x) + + + + + rng() == 0x + + + 570883446 (0x) +== +570883446 (0x) + + + + + rng() == 0x + + + 889299803 (0x) +== +889299803 (0x) + + + + + rng() == 0x + + + 4261393167 (0x) +== +4261393167 (0x) + + + + + rng() == 0x + + + 1472234645 (0x) +== +1472234645 (0x) + + + + + rng() == 0x + + + 868832940 (0x) +== +868832940 (0x) + + + + + rng() == 0x + + + 570883446 (0x) +== +570883446 (0x) + + + + + rng() == 0x + + + 889299803 (0x) +== +889299803 (0x) + + + + + rng() == 0x + + + 4261393167 (0x) +== +4261393167 (0x) + + + +
+ +
@@ -14923,7 +15117,7 @@ loose text artifact
- + - + diff --git a/projects/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp b/projects/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp new file mode 100644 index 00000000..f8085dbf --- /dev/null +++ b/projects/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp @@ -0,0 +1,45 @@ +/* + * Created by Martin on 06/10/2019. + * + * 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) + */ + +#include "catch.hpp" + +#include "internal/catch_random_number_generator.h" + +TEST_CASE("Our PCG implementation provides expected results for known seeds", "[rng]") { + Catch::SimplePcg32 rng; + SECTION("Default seeded") { + REQUIRE(rng() == 0xfcdb943b); + REQUIRE(rng() == 0x6f55b921); + REQUIRE(rng() == 0x4c17a916); + REQUIRE(rng() == 0x71eae25f); + REQUIRE(rng() == 0x6ce7909c); + } + SECTION("Specific seed") { + rng.seed(0xabcd1234); + REQUIRE(rng() == 0x57c08495); + REQUIRE(rng() == 0x33c956ac); + REQUIRE(rng() == 0x2206fd76); + REQUIRE(rng() == 0x3501a35b); + REQUIRE(rng() == 0xfdffb30f); + + // Also check repeated output after reseeding + rng.seed(0xabcd1234); + REQUIRE(rng() == 0x57c08495); + REQUIRE(rng() == 0x33c956ac); + REQUIRE(rng() == 0x2206fd76); + REQUIRE(rng() == 0x3501a35b); + REQUIRE(rng() == 0xfdffb30f); + } +} + +TEST_CASE("Comparison ops", "[rng]") { + using Catch::SimplePcg32; + REQUIRE(SimplePcg32{} == SimplePcg32{}); + REQUIRE(SimplePcg32{ 0 } != SimplePcg32{}); + REQUIRE_FALSE(SimplePcg32{ 1 } == SimplePcg32{ 2 }); + REQUIRE_FALSE(SimplePcg32{ 1 } != SimplePcg32{ 1 }); +}