diff --git a/.testnet/scenarios/export-suite.yml b/.testnet/scenarios/export-suite.yml index fcf46b185..741ec9f33 100644 --- a/.testnet/scenarios/export-suite.yml +++ b/.testnet/scenarios/export-suite.yml @@ -20,5 +20,52 @@ defaults: XAHAU_RNG_POLL_MS: "333" tests: - - name: steady_state_export + # --- CE + Export (80% quorum, SHAMap convergence) --- + - name: steady_state_export_ce script: .testnet/scenarios/export/steady_state_export.py + + - name: retriable_export_ce + script: .testnet/scenarios/export/retriable_export.py + + - name: export_degradation_ce + script: .testnet/scenarios/export/export_degradation.py + network: + node_env: + 3: + XAHAUD_NO_EXPORT_SIG: "1" + 4: + XAHAUD_NO_EXPORT_SIG: "1" + + # CE + Export: 1 node suppressed, 4/5 = 80% quorum, should succeed + - name: export_ce_one_node_down + script: .testnet/scenarios/export/export_unanimity.py + params: + expect_success: true + network: + node_env: + 4: + XAHAUD_NO_EXPORT_SIG: "1" + + # --- Export only, no CE (100% quorum, unanimity) --- + - name: export_unanimity_all_up + script: .testnet/scenarios/export/export_unanimity.py + params: + expect_success: true + network: + features: + - Export + track_features: + - Export + + - name: export_unanimity_node_down + script: .testnet/scenarios/export/export_unanimity.py + params: + expect_success: false + network: + features: + - Export + track_features: + - Export + node_env: + 4: + XAHAUD_NO_EXPORT_SIG: "1" diff --git a/.testnet/scenarios/suite.yml b/.testnet/scenarios/suite.yml index 55175b2cd..b6ca50fb6 100644 --- a/.testnet/scenarios/suite.yml +++ b/.testnet/scenarios/suite.yml @@ -39,78 +39,4 @@ tests: log_levels: LedgerConsensus: trace - # --- Export scenarios: CE + Export (80% quorum, SHAMap convergence) --- - - name: steady_state_export_ce - script: .testnet/scenarios/export/steady_state_export.py - network: - features: - - ConsensusEntropy - - Export - track_features: - - ConsensusEntropy - - Export - - - name: retriable_export_ce - script: .testnet/scenarios/export/retriable_export.py - network: - features: - - ConsensusEntropy - - Export - track_features: - - ConsensusEntropy - - Export - - - name: export_degradation_ce - script: .testnet/scenarios/export/export_degradation.py - network: - features: - - ConsensusEntropy - - Export - track_features: - - ConsensusEntropy - - Export - node_env: - 3: - XAHAUD_NO_EXPORT_SIG: "1" - 4: - XAHAUD_NO_EXPORT_SIG: "1" - - # --- CE + Export: 1 node suppressed, 4/5 = 80% quorum, should succeed --- - - name: export_ce_one_node_down - script: .testnet/scenarios/export/export_unanimity.py - params: - expect_success: true - network: - features: - - ConsensusEntropy - - Export - track_features: - - ConsensusEntropy - - Export - node_env: - 4: - XAHAUD_NO_EXPORT_SIG: "1" - - # --- Export only, no CE (100% quorum, unanimity) --- - - name: export_unanimity_all_up - script: .testnet/scenarios/export/export_unanimity.py - params: - expect_success: true - network: - features: - - Export - track_features: - - Export - - - name: export_unanimity_node_down - script: .testnet/scenarios/export/export_unanimity.py - params: - expect_success: false - network: - features: - - Export - track_features: - - Export - node_env: - 4: - XAHAUD_NO_EXPORT_SIG: "1" + # Export scenarios: see export-suite.yml diff --git a/src/test/unit_test/SuiteLogsWithOverrides.h b/src/test/unit_test/SuiteLogsWithOverrides.h new file mode 100644 index 000000000..e32ef8f72 --- /dev/null +++ b/src/test/unit_test/SuiteLogsWithOverrides.h @@ -0,0 +1,134 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2026 XRPL Labs + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef TEST_UNIT_TEST_SUITE_LOGS_WITH_OVERRIDES_H +#define TEST_UNIT_TEST_SUITE_LOGS_WITH_OVERRIDES_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +/** A Journal::Sink that writes directly to stderr. + * + * Unlike SuiteJournalSink (which writes to suite_.log and is only + * visible when tests fail), this always produces visible output. + */ +class StderrJournalSink : public beast::Journal::Sink +{ + std::string partition_; + +public: + StderrJournalSink( + std::string const& partition, + beast::severities::Severity threshold) + : Sink(threshold, false), partition_(partition) + { + } + + bool + active(beast::severities::Severity level) const override + { + return level >= threshold(); + } + + void + write( + beast::severities::Severity level, + std::string const& text) override + { + if (level >= threshold()) + writeAlways(level, text); + } + + void + writeAlways( + beast::severities::Severity level, + std::string const& text) override + { + static std::mutex mtx; + std::lock_guard lock(mtx); + std::cerr << partition_ << ":" << text << std::endl; + } +}; + +/** SuiteLogs with per-partition severity overrides written to stderr. + * + * Overridden partitions write to stderr (always visible). + * All other partitions use SuiteJournalSink (suite_.log, only on failure). + * + * Usage: + * #include + * + * using Sev = beast::severities::Severity; + * Env env{*this, cfg, features, + * std::make_unique( + * *this, + * SuiteLogsWithOverrides::Overrides{ + * {"Export", Sev::kTrace}, + * {"TxQ", Sev::kInfo}, + * {"View", Sev::kDebug}, + * })}; + */ +class SuiteLogsWithOverrides : public Logs +{ + beast::unit_test::suite& suite_; + std::set overridden_; + +public: + using Overrides = std::initializer_list< + std::pair>; + + SuiteLogsWithOverrides( + beast::unit_test::suite& suite, + Overrides overrides, + beast::severities::Severity defaultThresh = beast::severities::kError) + : Logs(defaultThresh), suite_(suite) + { + for (auto const& [name, sev] : overrides) + { + overridden_.insert(name); + get(name).threshold(sev); + } + } + + ~SuiteLogsWithOverrides() override = default; + + std::unique_ptr + makeSink( + std::string const& partition, + beast::severities::Severity threshold) override + { + if (overridden_.count(partition)) + return std::make_unique(partition, threshold); + return std::make_unique(partition, threshold, suite_); + } +}; + +} // namespace test +} // namespace ripple + +#endif