mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -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.
|
// the most significant bit of the last byte of the hash is set.
|
||||||
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
|
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);
|
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::cerr << "No fuzz target compiled for " << g_fuzz_target << "." << std::endl;
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
if constexpr (!G_FUZZING) {
|
if constexpr (!G_FUZZING_BUILD && !G_ABORT_ON_FAILED_ASSUME) {
|
||||||
std::cerr << "Must compile with -DBUILD_FOR_FUZZING=ON to execute a fuzz target." << std::endl;
|
std::cerr << "Must compile with -DBUILD_FOR_FUZZING=ON or in Debug mode to execute a fuzz target." << std::endl;
|
||||||
std::exit(EXIT_FAILURE);
|
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);
|
Assert(!g_test_one_input);
|
||||||
g_test_one_input = &it->second.test_one_input;
|
g_test_one_input = &it->second.test_one_input;
|
||||||
it->second.opts.init();
|
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
|
// no longer truly random. It should be enough to get the seed once for the
|
||||||
// process.
|
// process.
|
||||||
static const auto g_ctx_seed = []() -> std::optional<uint256> {
|
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 RANDOM_CTX_SEED is set, use that as seed.
|
||||||
if (const char* num{std::getenv(RANDOM_CTX_SEED)}) {
|
if (const char* num{std::getenv(RANDOM_CTX_SEED)}) {
|
||||||
if (auto num_parsed{uint256::FromUserHex(num)}) {
|
if (auto num_parsed{uint256::FromUserHex(num)}) {
|
||||||
|
@ -40,7 +40,7 @@ void SeedRandomStateForTest(SeedRand seedtype)
|
||||||
}();
|
}();
|
||||||
|
|
||||||
g_seeded_g_prng_zero = seedtype == SeedRand::ZEROS;
|
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_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)
|
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)
|
BasicTestingSetup::BasicTestingSetup(const ChainType chainType, TestOpts opts)
|
||||||
: m_args{}
|
: m_args{}
|
||||||
{
|
{
|
||||||
if constexpr (!G_FUZZING) {
|
if (!EnableFuzzDeterminism()) {
|
||||||
SeedRandomForTest(SeedRand::FIXED_SEED);
|
SeedRandomForTest(SeedRand::FIXED_SEED);
|
||||||
}
|
}
|
||||||
m_node.shutdown_signal = &m_interrupt;
|
m_node.shutdown_signal = &m_interrupt;
|
||||||
|
@ -203,7 +203,7 @@ BasicTestingSetup::~BasicTestingSetup()
|
||||||
{
|
{
|
||||||
m_node.ecc_context.reset();
|
m_node.ecc_context.reset();
|
||||||
m_node.kernel.reset();
|
m_node.kernel.reset();
|
||||||
if constexpr (!G_FUZZING) {
|
if (!EnableFuzzDeterminism()) {
|
||||||
SetMockTime(0s); // Reset mocktime for following tests
|
SetMockTime(0s); // Reset mocktime for following tests
|
||||||
}
|
}
|
||||||
LogInstance().DisconnectTestLogger();
|
LogInstance().DisconnectTestLogger();
|
||||||
|
@ -229,7 +229,8 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts)
|
||||||
m_node.scheduler->m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { m_node.scheduler->serviceQueue(); });
|
m_node.scheduler->m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { m_node.scheduler->serviceQueue(); });
|
||||||
m_node.validation_signals =
|
m_node.validation_signals =
|
||||||
// Use synchronous task runner while fuzzing to avoid non-determinism
|
// Use synchronous task runner while fuzzing to avoid non-determinism
|
||||||
G_FUZZING ? std::make_unique<ValidationSignals>(std::make_unique<util::ImmediateTaskRunner>()) :
|
EnableFuzzDeterminism() ?
|
||||||
|
std::make_unique<ValidationSignals>(std::make_unique<util::ImmediateTaskRunner>()) :
|
||||||
std::make_unique<ValidationSignals>(std::make_unique<SerialTaskRunner>(*m_node.scheduler));
|
std::make_unique<ValidationSignals>(std::make_unique<SerialTaskRunner>(*m_node.scheduler));
|
||||||
{
|
{
|
||||||
// Ensure deterministic coverage by waiting for m_service_thread to be running
|
// Ensure deterministic coverage by waiting for m_service_thread to be running
|
||||||
|
@ -255,7 +256,7 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts)
|
||||||
.notifications = *m_node.notifications,
|
.notifications = *m_node.notifications,
|
||||||
.signals = m_node.validation_signals.get(),
|
.signals = m_node.validation_signals.get(),
|
||||||
// Use no worker threads while fuzzing to avoid non-determinism
|
// 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) {
|
if (opts.min_validation_cache) {
|
||||||
chainman_opts.script_execution_cache_bytes = 0;
|
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);
|
fwrite(str.data(), 1, str.size(), stderr);
|
||||||
std::abort();
|
std::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::atomic<bool> g_enable_dynamic_fuzz_determinism{false};
|
||||||
|
|
|
@ -7,19 +7,44 @@
|
||||||
|
|
||||||
#include <attributes.h>
|
#include <attributes.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <cassert> // IWYU pragma: export
|
#include <cassert> // IWYU pragma: export
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
constexpr bool G_FUZZING{
|
constexpr bool G_FUZZING_BUILD{
|
||||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||||
true
|
true
|
||||||
#else
|
#else
|
||||||
false
|
false
|
||||||
#endif
|
#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);
|
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>
|
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)
|
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
|
if (IS_ASSERT || std::is_constant_evaluated() || G_FUZZING_BUILD || G_ABORT_ON_FAILED_ASSUME) {
|
||||||
#ifdef ABORT_ON_FAILED_ASSUME
|
|
||||||
|| true
|
|
||||||
#endif
|
|
||||||
) {
|
|
||||||
if (!val) {
|
if (!val) {
|
||||||
assertion_fail(file, line, func, assertion);
|
assertion_fail(file, line, func, assertion);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue