diff options
author | Pieter Wuille <pieter@wuille.net> | 2024-03-10 19:49:42 -0400 |
---|---|---|
committer | Pieter Wuille <pieter@wuille.net> | 2024-07-01 10:26:46 -0400 |
commit | 810cdf6b4e12a1fdace7998d75b4daf8b67d7028 (patch) | |
tree | 60e77d0360dc21f2271e946491987d308aeb767a /src/test | |
parent | 6cfdc5b104caf9952393f9dac2a36539d964077f (diff) |
tests: overhaul deterministic test randomness
The existing code provides two randomness mechanisms for test purposes:
- g_insecure_rand_ctx (with its wrappers InsecureRand*), which during tests is
initialized using either zeros (SeedRand::ZEROS), or using environment-provided
randomness (SeedRand::SEED).
- g_mock_deterministic_tests, which controls some (but not all) of the normal
randomness output if set, but then makes it extremely predictable (identical
output repeatedly).
Replace this with a single mechanism, which retains the SeedRand modes to control
all randomness. There is a new internal deterministic PRNG inside the random
module, which is used in GetRandBytes() when in test mode, and which is also used
to initialize g_insecure_rand_ctx. This means that during tests, all random numbers
are made deterministic. There is one exception, GetStrongRandBytes(), which even
in test mode still uses the normal PRNG state.
This probably opens the door to removing a lot of the ad-hoc "deterministic" mode
functions littered through the codebase (by simply running relevant tests in
SeedRand::ZEROS mode), but this isn't done yet.
Diffstat (limited to 'src/test')
-rw-r--r-- | src/test/bloom_tests.cpp | 8 | ||||
-rw-r--r-- | src/test/coins_tests.cpp | 5 | ||||
-rw-r--r-- | src/test/cuckoocache_tests.cpp | 10 | ||||
-rw-r--r-- | src/test/fuzz/fuzz.cpp | 1 | ||||
-rw-r--r-- | src/test/prevector_tests.cpp | 2 | ||||
-rw-r--r-- | src/test/random_tests.cpp | 56 | ||||
-rw-r--r-- | src/test/streams_tests.cpp | 2 | ||||
-rw-r--r-- | src/test/util/random.cpp | 31 | ||||
-rw-r--r-- | src/test/util/random.h | 20 | ||||
-rw-r--r-- | src/test/util/setup_common.cpp | 2 | ||||
-rw-r--r-- | src/test/util_tests.cpp | 2 |
11 files changed, 75 insertions, 64 deletions
diff --git a/src/test/bloom_tests.cpp b/src/test/bloom_tests.cpp index cbf85277a8..6699afdbfa 100644 --- a/src/test/bloom_tests.cpp +++ b/src/test/bloom_tests.cpp @@ -463,8 +463,7 @@ static std::vector<unsigned char> RandomData() BOOST_AUTO_TEST_CASE(rolling_bloom) { - SeedInsecureRand(SeedRand::ZEROS); - g_mock_deterministic_tests = true; + SeedRandomForTest(SeedRand::ZEROS); // last-100-entry, 1% false positive: CRollingBloomFilter rb1(100, 0.01); @@ -491,7 +490,7 @@ BOOST_AUTO_TEST_CASE(rolling_bloom) ++nHits; } // Expect about 100 hits - BOOST_CHECK_EQUAL(nHits, 75U); + BOOST_CHECK_EQUAL(nHits, 71U); BOOST_CHECK(rb1.contains(data[DATASIZE-1])); rb1.reset(); @@ -519,7 +518,7 @@ BOOST_AUTO_TEST_CASE(rolling_bloom) ++nHits; } // Expect about 5 false positives - BOOST_CHECK_EQUAL(nHits, 6U); + BOOST_CHECK_EQUAL(nHits, 3U); // last-1000-entry, 0.01% false positive: CRollingBloomFilter rb2(1000, 0.001); @@ -530,7 +529,6 @@ BOOST_AUTO_TEST_CASE(rolling_bloom) for (int i = 0; i < DATASIZE; i++) { BOOST_CHECK(rb2.contains(data[i])); } - g_mock_deterministic_tests = false; } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index b6d3e7d567..a992e2fa03 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -307,8 +307,7 @@ UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) { // has the expected effect (the other duplicate is overwritten at all cache levels) BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) { - SeedInsecureRand(SeedRand::ZEROS); - g_mock_deterministic_tests = true; + SeedRandomForTest(SeedRand::ZEROS); bool spent_a_duplicate_coinbase = false; // A simple map to track what we expect the cache stack to represent. @@ -496,8 +495,6 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) // Verify coverage. BOOST_CHECK(spent_a_duplicate_coinbase); - - g_mock_deterministic_tests = false; } BOOST_AUTO_TEST_CASE(ccoins_serialization) diff --git a/src/test/cuckoocache_tests.cpp b/src/test/cuckoocache_tests.cpp index eafbcf5681..906fbb4afa 100644 --- a/src/test/cuckoocache_tests.cpp +++ b/src/test/cuckoocache_tests.cpp @@ -37,7 +37,7 @@ BOOST_AUTO_TEST_SUITE(cuckoocache_tests); */ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes) { - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); CuckooCache::cache<uint256, SignatureCacheHasher> cc{}; size_t megabytes = 4; cc.setup_bytes(megabytes << 20); @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes) template <typename Cache> static double test_cache(size_t megabytes, double load) { - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); std::vector<uint256> hashes; Cache set{}; size_t bytes = megabytes * (1 << 20); @@ -126,7 +126,7 @@ template <typename Cache> static void test_cache_erase(size_t megabytes) { double load = 1; - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); std::vector<uint256> hashes; Cache set{}; size_t bytes = megabytes * (1 << 20); @@ -189,7 +189,7 @@ template <typename Cache> static void test_cache_erase_parallel(size_t megabytes) { double load = 1; - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); std::vector<uint256> hashes; Cache set{}; size_t bytes = megabytes * (1 << 20); @@ -293,7 +293,7 @@ static void test_cache_generations() // iterations with non-deterministic values, so it isn't "overfit" to the // specific entropy in FastRandomContext(true) and implementation of the // cache. - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); // block_activity models a chunk of network activity. n_insert elements are // added to the cache. The first and last n/4 are stored for removal later diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp index c1c9945a04..115cf2fc99 100644 --- a/src/test/fuzz/fuzz.cpp +++ b/src/test/fuzz/fuzz.cpp @@ -6,6 +6,7 @@ #include <netaddress.h> #include <netbase.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <util/check.h> #include <util/fs.h> diff --git a/src/test/prevector_tests.cpp b/src/test/prevector_tests.cpp index 1559011fcd..9abdd84c5a 100644 --- a/src/test/prevector_tests.cpp +++ b/src/test/prevector_tests.cpp @@ -210,7 +210,7 @@ public: } prevector_tester() { - SeedInsecureRand(); + SeedRandomForTest(); rand_seed = InsecureRand256(); rand_cache = FastRandomContext(rand_seed); } diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 8392a2bc5d..89546166b4 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -20,19 +20,30 @@ BOOST_AUTO_TEST_CASE(osrandom_tests) BOOST_CHECK(Random_SanityCheck()); } -BOOST_AUTO_TEST_CASE(fastrandom_tests) +BOOST_AUTO_TEST_CASE(fastrandom_tests_deterministic) { // Check that deterministic FastRandomContexts are deterministic - g_mock_deterministic_tests = true; - FastRandomContext ctx1(true); - FastRandomContext ctx2(true); - - for (int i = 10; i > 0; --i) { - BOOST_CHECK_EQUAL(GetRand<uint64_t>(), uint64_t{10393729187455219830U}); - BOOST_CHECK_EQUAL(GetRand<int>(), int{769702006}); - BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2917185654); - BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2144374); + SeedRandomForTest(SeedRand::ZEROS); + FastRandomContext ctx1{true}; + FastRandomContext ctx2{true}; + + { + BOOST_CHECK_EQUAL(GetRand<uint64_t>(), uint64_t{9330418229102544152u}); + BOOST_CHECK_EQUAL(GetRand<int>(), int{618925161}); + BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 1271170921); + BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2803534); + + BOOST_CHECK_EQUAL(GetRand<uint64_t>(), uint64_t{10170981140880778086u}); + BOOST_CHECK_EQUAL(GetRand<int>(), int{1689082725}); + BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2464643716); + BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2312205); + + BOOST_CHECK_EQUAL(GetRand<uint64_t>(), uint64_t{5689404004456455543u}); + BOOST_CHECK_EQUAL(GetRand<int>(), int{785839937}); + BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 93558804); + BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 507022); } + { constexpr SteadySeconds time_point{1s}; FastRandomContext ctx{true}; @@ -65,15 +76,28 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests) // Check with time-point type BOOST_CHECK_EQUAL(2782, ctx.rand_uniform_duration<SteadySeconds>(9h).count()); } +} +BOOST_AUTO_TEST_CASE(fastrandom_tests_nondeterministic) +{ // Check that a nondeterministic ones are not - g_mock_deterministic_tests = false; - for (int i = 10; i > 0; --i) { - BOOST_CHECK(GetRand<uint64_t>() != uint64_t{10393729187455219830U}); - BOOST_CHECK(GetRand<int>() != int{769702006}); - BOOST_CHECK(GetRandMicros(std::chrono::hours{1}) != std::chrono::microseconds{2917185654}); - BOOST_CHECK(GetRandMillis(std::chrono::hours{1}) != std::chrono::milliseconds{2144374}); + { + BOOST_CHECK(GetRand<uint64_t>() != uint64_t{9330418229102544152u}); + BOOST_CHECK(GetRand<int>() != int{618925161}); + BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 1271170921); + BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 2803534); + + BOOST_CHECK(GetRand<uint64_t>() != uint64_t{10170981140880778086u}); + BOOST_CHECK(GetRand<int>() != int{1689082725}); + BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 2464643716); + BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 2312205); + + BOOST_CHECK(GetRand<uint64_t>() != uint64_t{5689404004456455543u}); + BOOST_CHECK(GetRand<int>() != int{785839937}); + BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 93558804); + BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 507022); } + { FastRandomContext ctx3, ctx4; BOOST_CHECK(ctx3.rand64() != ctx4.rand64()); // extremely unlikely to be equal diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index e666e11758..eed932b6d2 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -436,7 +436,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_skip) BOOST_AUTO_TEST_CASE(streams_buffered_file_rand) { // Make this test deterministic. - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); fs::path streams_test_filename = m_args.GetDataDirBase() / "streams_test_tmp"; for (int rep = 0; rep < 50; ++rep) { diff --git a/src/test/util/random.cpp b/src/test/util/random.cpp index 4c87ab8df8..aa8c16e837 100644 --- a/src/test/util/random.cpp +++ b/src/test/util/random.cpp @@ -13,21 +13,26 @@ FastRandomContext g_insecure_rand_ctx; -/** Return the unsigned from the environment var if available, otherwise 0 */ -static uint256 GetUintFromEnv(const std::string& env_name) -{ - const char* num = std::getenv(env_name.c_str()); - if (!num) return {}; - return uint256S(num); -} +extern void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept; -void Seed(FastRandomContext& ctx) +void SeedRandomForTest(SeedRand seedtype) { - // Should be enough to get the seed once for the process - static uint256 seed{}; static const std::string RANDOM_CTX_SEED{"RANDOM_CTX_SEED"}; - if (seed.IsNull()) seed = GetUintFromEnv(RANDOM_CTX_SEED); - if (seed.IsNull()) seed = GetRandHash(); + + // Do this once, on the first call, regardless of seedtype, because once + // MakeRandDeterministicDANGEROUS is called, the output of GetRandHash is + // no longer truly random. It should be enough to get the seed once for the + // process. + static const uint256 ctx_seed = []() { + // If RANDOM_CTX_SEED is set, use that as seed. + const char* num = std::getenv(RANDOM_CTX_SEED.c_str()); + if (num) return uint256S(num); + // Otherwise use a (truly) random value. + return GetRandHash(); + }(); + + const uint256& seed{seedtype == SeedRand::SEED ? ctx_seed : uint256::ZERO}; LogPrintf("%s: Setting random seed for current tests to %s=%s\n", __func__, RANDOM_CTX_SEED, seed.GetHex()); - ctx = FastRandomContext(seed); + MakeRandDeterministicDANGEROUS(seed); + g_insecure_rand_ctx = FastRandomContext(GetRandHash()); } diff --git a/src/test/util/random.h b/src/test/util/random.h index 18ab425e48..09a475f8b3 100644 --- a/src/test/util/random.h +++ b/src/test/util/random.h @@ -19,27 +19,13 @@ */ extern FastRandomContext g_insecure_rand_ctx; -/** - * Flag to make GetRand in random.h return the same number - */ -extern bool g_mock_deterministic_tests; - enum class SeedRand { ZEROS, //!< Seed with a compile time constant of zeros - SEED, //!< Call the Seed() helper + SEED, //!< Use (and report) random seed from environment, or a (truly) random one. }; -/** Seed the given random ctx or use the seed passed in via an environment var */ -void Seed(FastRandomContext& ctx); - -static inline void SeedInsecureRand(SeedRand seed = SeedRand::SEED) -{ - if (seed == SeedRand::ZEROS) { - g_insecure_rand_ctx = FastRandomContext(/*fDeterministic=*/true); - } else { - Seed(g_insecure_rand_ctx); - } -} +/** Seed the RNG for testing. This affects all randomness, except GetStrongRandBytes(). */ +void SeedRandomForTest(SeedRand seed = SeedRand::SEED); static inline uint32_t InsecureRand32() { diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index cc7b2d6546..283e19971c 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -178,7 +178,7 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root)); SelectParams(chainType); - SeedInsecureRand(); + SeedRandomForTest(); if (G_TEST_LOG_FUN) LogInstance().PushBackCallback(G_TEST_LOG_FUN); InitLogging(*m_node.args); AppInitParameterInteraction(*m_node.args); diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index a371753adf..9f452d5f8f 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -459,7 +459,7 @@ BOOST_AUTO_TEST_CASE(util_IsHexNumber) BOOST_AUTO_TEST_CASE(util_seed_insecure_rand) { - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); for (int mod=2;mod<11;mod++) { int mask = 1; |