mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 06:49:38 -04:00
fuzz: enable running fuzz test cases in Debug mode
When building with BUILD_FOR_FUZZING=OFF BUILD_FUZZ_BINARY=ON CMAKE_BUILD_TYPE=Debug allow the fuzz binary to execute given test cases (without actual fuzzing) to make it easier to reproduce fuzz test failures in a more normal debug build. In Debug builds, deterministic fuzz behaviour is controlled via a runtime variable, which is normally false, but set to true automatically in the fuzz binary, unless the FUZZ_NONDETERMINISM environment variable is set.
This commit is contained in:
parent
639279e86a
commit
c1d01f59ac
6 changed files with 48 additions and 16 deletions
|
@ -139,7 +139,7 @@ bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t heig
|
|||
// the most significant bit of the last byte of the hash is set.
|
||||
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
|
||||
{
|
||||
if constexpr (G_FUZZING) return (hash.data()[31] & 0x80) == 0;
|
||||
if (EnableFuzzDeterminism()) return (hash.data()[31] & 0x80) == 0;
|
||||
return CheckProofOfWorkImpl(hash, nBits, params);
|
||||
}
|
||||
|
||||
|
|
|
@ -147,10 +147,18 @@ static void initialize()
|
|||
std::cerr << "No fuzz target compiled for " << g_fuzz_target << "." << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
if constexpr (!G_FUZZING) {
|
||||
std::cerr << "Must compile with -DBUILD_FOR_FUZZING=ON to execute a fuzz target." << std::endl;
|
||||
if constexpr (!G_FUZZING_BUILD && !G_ABORT_ON_FAILED_ASSUME) {
|
||||
std::cerr << "Must compile with -DBUILD_FOR_FUZZING=ON or in Debug mode to execute a fuzz target." << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
if (!EnableFuzzDeterminism()) {
|
||||
if (std::getenv("FUZZ_NONDETERMINISM")) {
|
||||
std::cerr << "Warning: FUZZ_NONDETERMINISM env var set, results may be inconsistent with fuzz build" << std::endl;
|
||||
} else {
|
||||
g_enable_dynamic_fuzz_determinism = true;
|
||||
assert(EnableFuzzDeterminism());
|
||||
}
|
||||
}
|
||||
Assert(!g_test_one_input);
|
||||
g_test_one_input = &it->second.test_one_input;
|
||||
it->second.opts.init();
|
||||
|
|
|
@ -25,7 +25,7 @@ void SeedRandomStateForTest(SeedRand seedtype)
|
|||
// no longer truly random. It should be enough to get the seed once for the
|
||||
// process.
|
||||
static const auto g_ctx_seed = []() -> std::optional<uint256> {
|
||||
if constexpr (G_FUZZING) return {};
|
||||
if (EnableFuzzDeterminism()) return {};
|
||||
// If RANDOM_CTX_SEED is set, use that as seed.
|
||||
if (const char* num{std::getenv(RANDOM_CTX_SEED)}) {
|
||||
if (auto num_parsed{uint256::FromUserHex(num)}) {
|
||||
|
@ -40,7 +40,7 @@ void SeedRandomStateForTest(SeedRand seedtype)
|
|||
}();
|
||||
|
||||
g_seeded_g_prng_zero = seedtype == SeedRand::ZEROS;
|
||||
if constexpr (G_FUZZING) {
|
||||
if (EnableFuzzDeterminism()) {
|
||||
Assert(g_seeded_g_prng_zero); // Only SeedRandomStateForTest(SeedRand::ZEROS) is allowed in fuzz tests
|
||||
Assert(!g_used_g_prng); // The global PRNG must not have been used before SeedRandomStateForTest(SeedRand::ZEROS)
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ static void ExitFailure(std::string_view str_err)
|
|||
BasicTestingSetup::BasicTestingSetup(const ChainType chainType, TestOpts opts)
|
||||
: m_args{}
|
||||
{
|
||||
if constexpr (!G_FUZZING) {
|
||||
if (!EnableFuzzDeterminism()) {
|
||||
SeedRandomForTest(SeedRand::FIXED_SEED);
|
||||
}
|
||||
m_node.shutdown_signal = &m_interrupt;
|
||||
|
@ -203,7 +203,7 @@ BasicTestingSetup::~BasicTestingSetup()
|
|||
{
|
||||
m_node.ecc_context.reset();
|
||||
m_node.kernel.reset();
|
||||
if constexpr (!G_FUZZING) {
|
||||
if (!EnableFuzzDeterminism()) {
|
||||
SetMockTime(0s); // Reset mocktime for following tests
|
||||
}
|
||||
LogInstance().DisconnectTestLogger();
|
||||
|
@ -229,8 +229,9 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts)
|
|||
m_node.scheduler->m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { m_node.scheduler->serviceQueue(); });
|
||||
m_node.validation_signals =
|
||||
// Use synchronous task runner while fuzzing to avoid non-determinism
|
||||
G_FUZZING ? std::make_unique<ValidationSignals>(std::make_unique<util::ImmediateTaskRunner>()) :
|
||||
std::make_unique<ValidationSignals>(std::make_unique<SerialTaskRunner>(*m_node.scheduler));
|
||||
EnableFuzzDeterminism() ?
|
||||
std::make_unique<ValidationSignals>(std::make_unique<util::ImmediateTaskRunner>()) :
|
||||
std::make_unique<ValidationSignals>(std::make_unique<SerialTaskRunner>(*m_node.scheduler));
|
||||
{
|
||||
// Ensure deterministic coverage by waiting for m_service_thread to be running
|
||||
std::promise<void> promise;
|
||||
|
@ -255,7 +256,7 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts)
|
|||
.notifications = *m_node.notifications,
|
||||
.signals = m_node.validation_signals.get(),
|
||||
// Use no worker threads while fuzzing to avoid non-determinism
|
||||
.worker_threads_num = G_FUZZING ? 0 : 2,
|
||||
.worker_threads_num = EnableFuzzDeterminism() ? 0 : 2,
|
||||
};
|
||||
if (opts.min_validation_cache) {
|
||||
chainman_opts.script_execution_cache_bytes = 0;
|
||||
|
|
|
@ -33,3 +33,5 @@ void assertion_fail(std::string_view file, int line, std::string_view func, std:
|
|||
fwrite(str.data(), 1, str.size(), stderr);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
std::atomic<bool> g_enable_dynamic_fuzz_determinism{false};
|
||||
|
|
|
@ -7,19 +7,44 @@
|
|||
|
||||
#include <attributes.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert> // IWYU pragma: export
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
constexpr bool G_FUZZING{
|
||||
constexpr bool G_FUZZING_BUILD{
|
||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
};
|
||||
constexpr bool G_ABORT_ON_FAILED_ASSUME{
|
||||
#ifdef ABORT_ON_FAILED_ASSUME
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
};
|
||||
|
||||
extern std::atomic<bool> g_enable_dynamic_fuzz_determinism;
|
||||
|
||||
inline bool EnableFuzzDeterminism()
|
||||
{
|
||||
if constexpr (G_FUZZING_BUILD) {
|
||||
return true;
|
||||
} else if constexpr (!G_ABORT_ON_FAILED_ASSUME) {
|
||||
// Running fuzz tests is always disabled if Assume() doesn't abort
|
||||
// (ie, non-fuzz non-debug builds), as otherwise tests which
|
||||
// should fail due to a failing Assume may still pass. As such,
|
||||
// we also statically disable fuzz determinism in that case.
|
||||
return false;
|
||||
} else {
|
||||
return g_enable_dynamic_fuzz_determinism;
|
||||
}
|
||||
}
|
||||
|
||||
std::string StrFormatInternalBug(std::string_view msg, std::string_view file, int line, std::string_view func);
|
||||
|
||||
|
@ -50,11 +75,7 @@ void assertion_fail(std::string_view file, int line, std::string_view func, std:
|
|||
template <bool IS_ASSERT, typename T>
|
||||
constexpr T&& inline_assertion_check(LIFETIMEBOUND T&& val, [[maybe_unused]] const char* file, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* assertion)
|
||||
{
|
||||
if (IS_ASSERT || std::is_constant_evaluated() || G_FUZZING
|
||||
#ifdef ABORT_ON_FAILED_ASSUME
|
||||
|| true
|
||||
#endif
|
||||
) {
|
||||
if (IS_ASSERT || std::is_constant_evaluated() || G_FUZZING_BUILD || G_ABORT_ON_FAILED_ASSUME) {
|
||||
if (!val) {
|
||||
assertion_fail(file, line, func, assertion);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue