mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-25 08:00:22 +00:00
Merge branch 'pratik/otel-phase2-rpc-tracing' into pratik/otel-phase3-tx-tracing
# Conflicts: # include/xrpl/telemetry/SpanGuard.h # src/libxrpl/telemetry/SpanGuard.cpp # src/libxrpl/tx/applySteps.cpp
This commit is contained in:
@@ -83,7 +83,7 @@ TEST_F(BufferTest, buffer)
|
||||
x = b0;
|
||||
EXPECT_EQ(x, b0);
|
||||
EXPECT_TRUE(sane(x));
|
||||
#if defined(__clang__)
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wself-assign-overloaded"
|
||||
#endif
|
||||
@@ -95,7 +95,7 @@ TEST_F(BufferTest, buffer)
|
||||
EXPECT_EQ(y, b3);
|
||||
EXPECT_TRUE(sane(y));
|
||||
|
||||
#if defined(__clang__)
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -122,6 +122,73 @@ struct BaseUintTest : public ::testing::Test
|
||||
}
|
||||
};
|
||||
|
||||
using BaseUintDeathTest = BaseUintTest;
|
||||
|
||||
TEST_F(BaseUintDeathTest, fromRaw_size_mismatch)
|
||||
{
|
||||
// ENABLE_VOIDSTAR is a debug build, but does not crash on failed asserts. Rather than twist
|
||||
// these tests into knots to make them work, just skip them.
|
||||
#ifdef ENABLE_VOIDSTAR
|
||||
GTEST_SKIP() << "ENABLE_VOIDSTAR is a debug build, but does not crash on failed asserts.";
|
||||
#else
|
||||
auto smallConstruct = [] {
|
||||
// Container smaller than the base_uint (8 bytes vs 12 bytes for
|
||||
// test96). Only the first 8 bytes are copied; the remaining 4 bytes
|
||||
// stay zero.
|
||||
Blob const tooSmall{1, 2, 3, 4, 5, 6, 7, 8};
|
||||
BaseUInt96 const result = BaseUInt96::fromRaw(tooSmall);
|
||||
auto const resultText = to_string(result);
|
||||
EXPECT_EQ(resultText, "010203040506070800000000") << resultText;
|
||||
};
|
||||
EXPECT_DEBUG_DEATH(smallConstruct(), "input size match");
|
||||
|
||||
auto largeConstruct = [] {
|
||||
// Container larger than the base_uint (16 bytes vs 12 bytes for
|
||||
// test96). Only the first 12 bytes are copied; the extra bytes are
|
||||
// ignored.
|
||||
Blob const tooBig{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
||||
BaseUInt96 const result = BaseUInt96::fromRaw(tooBig);
|
||||
auto const resultText = to_string(result);
|
||||
EXPECT_EQ(resultText, "0102030405060708090A0B0C") << resultText;
|
||||
};
|
||||
EXPECT_DEBUG_DEATH(largeConstruct(), "input size match");
|
||||
|
||||
auto smallCopy = [] {
|
||||
// Container smaller than the base_uint (8 bytes vs 12 bytes for
|
||||
// test96). Only the first 8 bytes are copied; the remaining 4 bytes
|
||||
// stay zero.
|
||||
Blob const tooSmall{1, 2, 3, 4, 5, 6, 7, 8};
|
||||
BaseUInt96 result{};
|
||||
--result;
|
||||
{
|
||||
auto const originalText = to_string(result);
|
||||
EXPECT_EQ(originalText, "FFFFFFFFFFFFFFFFFFFFFFFF") << originalText;
|
||||
}
|
||||
result = tooSmall;
|
||||
auto const resultText = to_string(result);
|
||||
EXPECT_EQ(resultText, "010203040506070800000000") << resultText;
|
||||
};
|
||||
EXPECT_DEBUG_DEATH(smallCopy(), "input size match");
|
||||
|
||||
auto const largeCopy = [] {
|
||||
// Container larger than the base_uint (16 bytes vs 12 bytes for
|
||||
// test96). Only the first 12 bytes are copied; the extra bytes are
|
||||
// ignored.
|
||||
Blob const tooBig{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
||||
BaseUInt96 result{};
|
||||
--result;
|
||||
{
|
||||
auto const originalText = to_string(result);
|
||||
EXPECT_EQ(originalText, "FFFFFFFFFFFFFFFFFFFFFFFF") << originalText;
|
||||
}
|
||||
result = tooBig;
|
||||
auto const resultText = to_string(result);
|
||||
EXPECT_EQ(resultText, "0102030405060708090A0B0C") << resultText;
|
||||
};
|
||||
EXPECT_DEBUG_DEATH(largeCopy(), "input size match");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_F(BaseUintTest, base_uint)
|
||||
{
|
||||
static_assert(!std::is_constructible_v<BaseUInt96, std::complex<double>>);
|
||||
@@ -198,6 +265,19 @@ TEST_F(BaseUintTest, base_uint)
|
||||
EXPECT_EQ(d, 0);
|
||||
}
|
||||
|
||||
{
|
||||
// There are several ways to create a zero. beast::kZero is tested above. Test some
|
||||
// others.
|
||||
BaseUInt96 const z1;
|
||||
EXPECT_EQ(z1, z) << to_string(z1);
|
||||
|
||||
BaseUInt96 const z2{};
|
||||
EXPECT_EQ(z2, z) << to_string(z2);
|
||||
|
||||
BaseUInt96 const z3{0u};
|
||||
EXPECT_EQ(z3, z) << to_string(z3);
|
||||
}
|
||||
|
||||
BaseUInt96 n{z};
|
||||
n++;
|
||||
EXPECT_EQ(n, BaseUInt96(1));
|
||||
@@ -271,25 +351,6 @@ TEST_F(BaseUintTest, base_uint)
|
||||
static_assert(BaseUInt96("000000000000000000000001").signum() == 1);
|
||||
static_assert(BaseUInt96("800000000000000000000000").signum() == 1);
|
||||
|
||||
// Everything within the #if should fail during compilation.
|
||||
#if 0
|
||||
// Too few characters
|
||||
static_assert(BaseUInt96("00000000000000000000000").signum() == 0);
|
||||
|
||||
// Too many characters
|
||||
static_assert(BaseUInt96("0000000000000000000000000").signum() == 0);
|
||||
|
||||
// Non-hex characters
|
||||
static_assert(BaseUInt96("00000000000000000000000 ").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000/").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000:").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000@").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000G").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000`").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000g").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000~").signum() == 1);
|
||||
#endif // 0
|
||||
|
||||
// Using the constexpr constructor in a non-constexpr context
|
||||
// with an error in the parsing throws an exception.
|
||||
{
|
||||
|
||||
@@ -38,35 +38,45 @@ public:
|
||||
*/
|
||||
explicit Account(std::string_view name, KeyType type = KeyType::Secp256k1);
|
||||
|
||||
/** @brief Return the human-readable name. */
|
||||
/**
|
||||
* @brief Return the human-readable name.
|
||||
*/
|
||||
[[nodiscard]] std::string const&
|
||||
name() const noexcept
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
/** @brief Return the AccountID. */
|
||||
/**
|
||||
* @brief Return the AccountID.
|
||||
*/
|
||||
[[nodiscard]] AccountID const&
|
||||
id() const noexcept
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
/** @brief Return the public key. */
|
||||
/**
|
||||
* @brief Return the public key.
|
||||
*/
|
||||
[[nodiscard]] PublicKey const&
|
||||
pk() const noexcept
|
||||
{
|
||||
return keyPair_.first;
|
||||
}
|
||||
|
||||
/** @brief Return the secret key. */
|
||||
/**
|
||||
* @brief Return the secret key.
|
||||
*/
|
||||
[[nodiscard]] SecretKey const&
|
||||
sk() const noexcept
|
||||
{
|
||||
return keyPair_.second;
|
||||
}
|
||||
|
||||
/** @brief Implicit conversion to AccountID. */
|
||||
/**
|
||||
* @brief Implicit conversion to AccountID.
|
||||
*/
|
||||
operator AccountID const&() const noexcept
|
||||
{
|
||||
return id_;
|
||||
|
||||
@@ -20,11 +20,12 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
/** Test implementation of Family for unit tests.
|
||||
|
||||
Uses an in-memory NodeStore database and simple caches.
|
||||
The missingNode methods throw since tests shouldn't encounter missing nodes.
|
||||
*/
|
||||
/**
|
||||
* Test implementation of Family for unit tests.
|
||||
*
|
||||
* Uses an in-memory NodeStore database and simple caches.
|
||||
* The missingNode methods throw since tests shouldn't encounter missing nodes.
|
||||
*/
|
||||
class TestFamily : public Family
|
||||
{
|
||||
private:
|
||||
@@ -109,7 +110,9 @@ public:
|
||||
(*tnCache_).reset();
|
||||
}
|
||||
|
||||
/** Access the test clock for time manipulation in tests. */
|
||||
/**
|
||||
* Access the test clock for time manipulation in tests.
|
||||
*/
|
||||
TestStopwatch&
|
||||
clock()
|
||||
{
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
/** Logs implementation that creates TestSink instances. */
|
||||
/**
|
||||
* Logs implementation that creates TestSink instances.
|
||||
*/
|
||||
class TestLogs : public Logs
|
||||
{
|
||||
public:
|
||||
@@ -39,7 +41,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/** Simple NetworkIDService implementation for tests. */
|
||||
/**
|
||||
* Simple NetworkIDService implementation for tests.
|
||||
*/
|
||||
class TestNetworkIDService final : public NetworkIDService
|
||||
{
|
||||
public:
|
||||
@@ -57,14 +61,15 @@ private:
|
||||
std::uint32_t networkID_;
|
||||
};
|
||||
|
||||
/** Test implementation of ServiceRegistry for unit tests.
|
||||
|
||||
This class provides real implementations for services that can be
|
||||
instantiated from libxrpl (such as Logs, io_context, caches), and
|
||||
throws std::logic_error for services that require the full Application.
|
||||
|
||||
Tests can subclass this to provide additional services they need.
|
||||
*/
|
||||
/**
|
||||
* Test implementation of ServiceRegistry for unit tests.
|
||||
*
|
||||
* This class provides real implementations for services that can be
|
||||
* instantiated from libxrpl (such as Logs, io_context, caches), and
|
||||
* throws std::logic_error for services that require the full Application.
|
||||
*
|
||||
* Tests can subclass this to provide additional services they need.
|
||||
*/
|
||||
class TestServiceRegistry : public ServiceRegistry
|
||||
{
|
||||
TestLogs logs_{beast::Severity::Warning};
|
||||
|
||||
@@ -157,10 +157,10 @@ allFeatures();
|
||||
*/
|
||||
struct TxResult
|
||||
{
|
||||
TER ter; /**< The transaction engine result code. */
|
||||
bool applied; /**< Whether the transaction was applied to the ledger. */
|
||||
std::optional<TxMeta> metadata; /**< Transaction metadata, if available. */
|
||||
std::shared_ptr<STTx const> tx; /**< Pointer to the submitted transaction. */
|
||||
TER ter; ///< The transaction engine result code.
|
||||
bool applied; ///< Whether the transaction was applied to the ledger.
|
||||
std::optional<TxMeta> metadata; ///< Transaction metadata, if available.
|
||||
std::shared_ptr<STTx const> tx; ///< Pointer to the submitted transaction.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -360,10 +360,14 @@ private:
|
||||
std::shared_ptr<Ledger const> closedLedger_;
|
||||
std::shared_ptr<OpenView> openLedger_;
|
||||
|
||||
/** Transactions submitted to the open ledger, for canonical reordering on close. */
|
||||
/**
|
||||
* Transactions submitted to the open ledger, for canonical reordering on close.
|
||||
*/
|
||||
std::vector<std::shared_ptr<STTx const>> pendingTxs_;
|
||||
|
||||
/** Current time (can be advanced arbitrarily for testing). */
|
||||
/**
|
||||
* Current time (can be advanced arbitrarily for testing).
|
||||
*/
|
||||
NetClock::time_point now_;
|
||||
};
|
||||
|
||||
|
||||
@@ -40,6 +40,9 @@ TEST(AccountRootTests, BuilderSettersRoundTrip)
|
||||
auto const mintedNFTokensValue = canonical_UINT32();
|
||||
auto const burnedNFTokensValue = canonical_UINT32();
|
||||
auto const firstNFTokenSequenceValue = canonical_UINT32();
|
||||
auto const sponsoredOwnerCountValue = canonical_UINT32();
|
||||
auto const sponsoringOwnerCountValue = canonical_UINT32();
|
||||
auto const sponsoringAccountCountValue = canonical_UINT32();
|
||||
auto const aMMIDValue = canonical_UINT256();
|
||||
auto const vaultIDValue = canonical_UINT256();
|
||||
auto const loanBrokerIDValue = canonical_UINT256();
|
||||
@@ -67,6 +70,9 @@ TEST(AccountRootTests, BuilderSettersRoundTrip)
|
||||
builder.setMintedNFTokens(mintedNFTokensValue);
|
||||
builder.setBurnedNFTokens(burnedNFTokensValue);
|
||||
builder.setFirstNFTokenSequence(firstNFTokenSequenceValue);
|
||||
builder.setSponsoredOwnerCount(sponsoredOwnerCountValue);
|
||||
builder.setSponsoringOwnerCount(sponsoringOwnerCountValue);
|
||||
builder.setSponsoringAccountCount(sponsoringAccountCountValue);
|
||||
builder.setAMMID(aMMIDValue);
|
||||
builder.setVaultID(vaultIDValue);
|
||||
builder.setLoanBrokerID(loanBrokerIDValue);
|
||||
@@ -228,6 +234,30 @@ TEST(AccountRootTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(entry.hasFirstNFTokenSequence());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponsoredOwnerCountValue;
|
||||
auto const actualOpt = entry.getSponsoredOwnerCount();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfSponsoredOwnerCount");
|
||||
EXPECT_TRUE(entry.hasSponsoredOwnerCount());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponsoringOwnerCountValue;
|
||||
auto const actualOpt = entry.getSponsoringOwnerCount();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfSponsoringOwnerCount");
|
||||
EXPECT_TRUE(entry.hasSponsoringOwnerCount());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponsoringAccountCountValue;
|
||||
auto const actualOpt = entry.getSponsoringAccountCount();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfSponsoringAccountCount");
|
||||
EXPECT_TRUE(entry.hasSponsoringAccountCount());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = aMMIDValue;
|
||||
auto const actualOpt = entry.getAMMID();
|
||||
@@ -285,6 +315,9 @@ TEST(AccountRootTests, BuilderFromSleRoundTrip)
|
||||
auto const mintedNFTokensValue = canonical_UINT32();
|
||||
auto const burnedNFTokensValue = canonical_UINT32();
|
||||
auto const firstNFTokenSequenceValue = canonical_UINT32();
|
||||
auto const sponsoredOwnerCountValue = canonical_UINT32();
|
||||
auto const sponsoringOwnerCountValue = canonical_UINT32();
|
||||
auto const sponsoringAccountCountValue = canonical_UINT32();
|
||||
auto const aMMIDValue = canonical_UINT256();
|
||||
auto const vaultIDValue = canonical_UINT256();
|
||||
auto const loanBrokerIDValue = canonical_UINT256();
|
||||
@@ -311,6 +344,9 @@ TEST(AccountRootTests, BuilderFromSleRoundTrip)
|
||||
sle->at(sfMintedNFTokens) = mintedNFTokensValue;
|
||||
sle->at(sfBurnedNFTokens) = burnedNFTokensValue;
|
||||
sle->at(sfFirstNFTokenSequence) = firstNFTokenSequenceValue;
|
||||
sle->at(sfSponsoredOwnerCount) = sponsoredOwnerCountValue;
|
||||
sle->at(sfSponsoringOwnerCount) = sponsoringOwnerCountValue;
|
||||
sle->at(sfSponsoringAccountCount) = sponsoringAccountCountValue;
|
||||
sle->at(sfAMMID) = aMMIDValue;
|
||||
sle->at(sfVaultID) = vaultIDValue;
|
||||
sle->at(sfLoanBrokerID) = loanBrokerIDValue;
|
||||
@@ -566,6 +602,45 @@ TEST(AccountRootTests, BuilderFromSleRoundTrip)
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfFirstNFTokenSequence");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponsoredOwnerCountValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getSponsoredOwnerCount();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getSponsoredOwnerCount();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfSponsoredOwnerCount");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfSponsoredOwnerCount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponsoringOwnerCountValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getSponsoringOwnerCount();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getSponsoringOwnerCount();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfSponsoringOwnerCount");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfSponsoringOwnerCount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponsoringAccountCountValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getSponsoringAccountCount();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getSponsoringAccountCount();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfSponsoringAccountCount");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfSponsoringAccountCount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = aMMIDValue;
|
||||
|
||||
@@ -697,6 +772,12 @@ TEST(AccountRootTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(entry.getBurnedNFTokens().has_value());
|
||||
EXPECT_FALSE(entry.hasFirstNFTokenSequence());
|
||||
EXPECT_FALSE(entry.getFirstNFTokenSequence().has_value());
|
||||
EXPECT_FALSE(entry.hasSponsoredOwnerCount());
|
||||
EXPECT_FALSE(entry.getSponsoredOwnerCount().has_value());
|
||||
EXPECT_FALSE(entry.hasSponsoringOwnerCount());
|
||||
EXPECT_FALSE(entry.getSponsoringOwnerCount().has_value());
|
||||
EXPECT_FALSE(entry.hasSponsoringAccountCount());
|
||||
EXPECT_FALSE(entry.getSponsoringAccountCount().has_value());
|
||||
EXPECT_FALSE(entry.hasAMMID());
|
||||
EXPECT_FALSE(entry.getAMMID().has_value());
|
||||
EXPECT_FALSE(entry.hasVaultID());
|
||||
|
||||
@@ -31,6 +31,8 @@ TEST(RippleStateTests, BuilderSettersRoundTrip)
|
||||
auto const highNodeValue = canonical_UINT64();
|
||||
auto const highQualityInValue = canonical_UINT32();
|
||||
auto const highQualityOutValue = canonical_UINT32();
|
||||
auto const highSponsorValue = canonical_ACCOUNT();
|
||||
auto const lowSponsorValue = canonical_ACCOUNT();
|
||||
|
||||
RippleStateBuilder builder{
|
||||
balanceValue,
|
||||
@@ -46,6 +48,8 @@ TEST(RippleStateTests, BuilderSettersRoundTrip)
|
||||
builder.setHighNode(highNodeValue);
|
||||
builder.setHighQualityIn(highQualityInValue);
|
||||
builder.setHighQualityOut(highQualityOutValue);
|
||||
builder.setHighSponsor(highSponsorValue);
|
||||
builder.setLowSponsor(lowSponsorValue);
|
||||
|
||||
builder.setLedgerIndex(index);
|
||||
builder.setFlags(0x1u);
|
||||
@@ -134,6 +138,22 @@ TEST(RippleStateTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(entry.hasHighQualityOut());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = highSponsorValue;
|
||||
auto const actualOpt = entry.getHighSponsor();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfHighSponsor");
|
||||
EXPECT_TRUE(entry.hasHighSponsor());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = lowSponsorValue;
|
||||
auto const actualOpt = entry.getLowSponsor();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfLowSponsor");
|
||||
EXPECT_TRUE(entry.hasLowSponsor());
|
||||
}
|
||||
|
||||
EXPECT_TRUE(entry.hasLedgerIndex());
|
||||
auto const ledgerIndex = entry.getLedgerIndex();
|
||||
ASSERT_TRUE(ledgerIndex.has_value());
|
||||
@@ -158,6 +178,8 @@ TEST(RippleStateTests, BuilderFromSleRoundTrip)
|
||||
auto const highNodeValue = canonical_UINT64();
|
||||
auto const highQualityInValue = canonical_UINT32();
|
||||
auto const highQualityOutValue = canonical_UINT32();
|
||||
auto const highSponsorValue = canonical_ACCOUNT();
|
||||
auto const lowSponsorValue = canonical_ACCOUNT();
|
||||
|
||||
auto sle = std::make_shared<SLE>(RippleState::entryType, index);
|
||||
|
||||
@@ -172,6 +194,8 @@ TEST(RippleStateTests, BuilderFromSleRoundTrip)
|
||||
sle->at(sfHighNode) = highNodeValue;
|
||||
sle->at(sfHighQualityIn) = highQualityInValue;
|
||||
sle->at(sfHighQualityOut) = highQualityOutValue;
|
||||
sle->at(sfHighSponsor) = highSponsorValue;
|
||||
sle->at(sfLowSponsor) = lowSponsorValue;
|
||||
|
||||
RippleStateBuilder builderFromSle{sle};
|
||||
EXPECT_TRUE(builderFromSle.validate());
|
||||
@@ -310,6 +334,32 @@ TEST(RippleStateTests, BuilderFromSleRoundTrip)
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfHighQualityOut");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = highSponsorValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getHighSponsor();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getHighSponsor();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfHighSponsor");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfHighSponsor");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = lowSponsorValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getLowSponsor();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getLowSponsor();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfLowSponsor");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfLowSponsor");
|
||||
}
|
||||
|
||||
EXPECT_EQ(entryFromSle.getKey(), index);
|
||||
EXPECT_EQ(entryFromBuilder.getKey(), index);
|
||||
}
|
||||
@@ -384,5 +434,9 @@ TEST(RippleStateTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(entry.getHighQualityIn().has_value());
|
||||
EXPECT_FALSE(entry.hasHighQualityOut());
|
||||
EXPECT_FALSE(entry.getHighQualityOut().has_value());
|
||||
EXPECT_FALSE(entry.hasHighSponsor());
|
||||
EXPECT_FALSE(entry.getHighSponsor().has_value());
|
||||
EXPECT_FALSE(entry.hasLowSponsor());
|
||||
EXPECT_FALSE(entry.getLowSponsor().has_value());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
// Auto-generated unit tests for ledger entry Sponsorship
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol_autogen/ledger_entries/Sponsorship.h>
|
||||
#include <xrpl/protocol_autogen/ledger_entries/Ticket.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::ledger_entries {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed for both the
|
||||
// builder's STObject and the wrapper's SLE.
|
||||
TEST(SponsorshipTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
uint256 const index{1u};
|
||||
|
||||
auto const previousTxnIDValue = canonical_UINT256();
|
||||
auto const previousTxnLgrSeqValue = canonical_UINT32();
|
||||
auto const ownerValue = canonical_ACCOUNT();
|
||||
auto const sponseeValue = canonical_ACCOUNT();
|
||||
auto const feeAmountValue = canonical_AMOUNT();
|
||||
auto const maxFeeValue = canonical_AMOUNT();
|
||||
auto const remainingOwnerCountValue = canonical_UINT32();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
auto const sponseeNodeValue = canonical_UINT64();
|
||||
|
||||
SponsorshipBuilder builder{
|
||||
previousTxnIDValue,
|
||||
previousTxnLgrSeqValue,
|
||||
ownerValue,
|
||||
sponseeValue,
|
||||
ownerNodeValue,
|
||||
sponseeNodeValue
|
||||
};
|
||||
|
||||
builder.setFeeAmount(feeAmountValue);
|
||||
builder.setMaxFee(maxFeeValue);
|
||||
builder.setRemainingOwnerCount(remainingOwnerCountValue);
|
||||
|
||||
builder.setLedgerIndex(index);
|
||||
builder.setFlags(0x1u);
|
||||
|
||||
EXPECT_TRUE(builder.validate());
|
||||
|
||||
auto const entry = builder.build(index);
|
||||
|
||||
EXPECT_TRUE(entry.validate());
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnIDValue;
|
||||
auto const actual = entry.getPreviousTxnID();
|
||||
expectEqualField(expected, actual, "sfPreviousTxnID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnLgrSeqValue;
|
||||
auto const actual = entry.getPreviousTxnLgrSeq();
|
||||
expectEqualField(expected, actual, "sfPreviousTxnLgrSeq");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = ownerValue;
|
||||
auto const actual = entry.getOwner();
|
||||
expectEqualField(expected, actual, "sfOwner");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponseeValue;
|
||||
auto const actual = entry.getSponsee();
|
||||
expectEqualField(expected, actual, "sfSponsee");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = ownerNodeValue;
|
||||
auto const actual = entry.getOwnerNode();
|
||||
expectEqualField(expected, actual, "sfOwnerNode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponseeNodeValue;
|
||||
auto const actual = entry.getSponseeNode();
|
||||
expectEqualField(expected, actual, "sfSponseeNode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = feeAmountValue;
|
||||
auto const actualOpt = entry.getFeeAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfFeeAmount");
|
||||
EXPECT_TRUE(entry.hasFeeAmount());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = maxFeeValue;
|
||||
auto const actualOpt = entry.getMaxFee();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfMaxFee");
|
||||
EXPECT_TRUE(entry.hasMaxFee());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = remainingOwnerCountValue;
|
||||
auto const actualOpt = entry.getRemainingOwnerCount();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfRemainingOwnerCount");
|
||||
EXPECT_TRUE(entry.hasRemainingOwnerCount());
|
||||
}
|
||||
|
||||
EXPECT_TRUE(entry.hasLedgerIndex());
|
||||
auto const ledgerIndex = entry.getLedgerIndex();
|
||||
ASSERT_TRUE(ledgerIndex.has_value());
|
||||
EXPECT_EQ(*ledgerIndex, index);
|
||||
EXPECT_EQ(entry.getKey(), index);
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an SLE, set fields directly on it, construct a builder
|
||||
// from that SLE, build a new wrapper, and verify all fields (and validate()).
|
||||
TEST(SponsorshipTests, BuilderFromSleRoundTrip)
|
||||
{
|
||||
uint256 const index{2u};
|
||||
|
||||
auto const previousTxnIDValue = canonical_UINT256();
|
||||
auto const previousTxnLgrSeqValue = canonical_UINT32();
|
||||
auto const ownerValue = canonical_ACCOUNT();
|
||||
auto const sponseeValue = canonical_ACCOUNT();
|
||||
auto const feeAmountValue = canonical_AMOUNT();
|
||||
auto const maxFeeValue = canonical_AMOUNT();
|
||||
auto const remainingOwnerCountValue = canonical_UINT32();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
auto const sponseeNodeValue = canonical_UINT64();
|
||||
|
||||
auto sle = std::make_shared<SLE>(Sponsorship::entryType, index);
|
||||
|
||||
sle->at(sfPreviousTxnID) = previousTxnIDValue;
|
||||
sle->at(sfPreviousTxnLgrSeq) = previousTxnLgrSeqValue;
|
||||
sle->at(sfOwner) = ownerValue;
|
||||
sle->at(sfSponsee) = sponseeValue;
|
||||
sle->at(sfFeeAmount) = feeAmountValue;
|
||||
sle->at(sfMaxFee) = maxFeeValue;
|
||||
sle->at(sfRemainingOwnerCount) = remainingOwnerCountValue;
|
||||
sle->at(sfOwnerNode) = ownerNodeValue;
|
||||
sle->at(sfSponseeNode) = sponseeNodeValue;
|
||||
|
||||
SponsorshipBuilder builderFromSle{sle};
|
||||
EXPECT_TRUE(builderFromSle.validate());
|
||||
|
||||
auto const entryFromBuilder = builderFromSle.build(index);
|
||||
|
||||
Sponsorship entryFromSle{sle};
|
||||
EXPECT_TRUE(entryFromBuilder.validate());
|
||||
EXPECT_TRUE(entryFromSle.validate());
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnIDValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getPreviousTxnID();
|
||||
auto const fromBuilder = entryFromBuilder.getPreviousTxnID();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfPreviousTxnID");
|
||||
expectEqualField(expected, fromBuilder, "sfPreviousTxnID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnLgrSeqValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getPreviousTxnLgrSeq();
|
||||
auto const fromBuilder = entryFromBuilder.getPreviousTxnLgrSeq();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfPreviousTxnLgrSeq");
|
||||
expectEqualField(expected, fromBuilder, "sfPreviousTxnLgrSeq");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = ownerValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getOwner();
|
||||
auto const fromBuilder = entryFromBuilder.getOwner();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfOwner");
|
||||
expectEqualField(expected, fromBuilder, "sfOwner");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponseeValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getSponsee();
|
||||
auto const fromBuilder = entryFromBuilder.getSponsee();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfSponsee");
|
||||
expectEqualField(expected, fromBuilder, "sfSponsee");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = ownerNodeValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getOwnerNode();
|
||||
auto const fromBuilder = entryFromBuilder.getOwnerNode();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfOwnerNode");
|
||||
expectEqualField(expected, fromBuilder, "sfOwnerNode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponseeNodeValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getSponseeNode();
|
||||
auto const fromBuilder = entryFromBuilder.getSponseeNode();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfSponseeNode");
|
||||
expectEqualField(expected, fromBuilder, "sfSponseeNode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = feeAmountValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getFeeAmount();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getFeeAmount();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfFeeAmount");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfFeeAmount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = maxFeeValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getMaxFee();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getMaxFee();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfMaxFee");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfMaxFee");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = remainingOwnerCountValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getRemainingOwnerCount();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getRemainingOwnerCount();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfRemainingOwnerCount");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfRemainingOwnerCount");
|
||||
}
|
||||
|
||||
EXPECT_EQ(entryFromSle.getKey(), index);
|
||||
EXPECT_EQ(entryFromBuilder.getKey(), index);
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong ledger entry type.
|
||||
TEST(SponsorshipTests, WrapperThrowsOnWrongEntryType)
|
||||
{
|
||||
uint256 const index{3u};
|
||||
|
||||
// Build a valid ledger entry of a different type
|
||||
// Ticket requires: Account, OwnerNode, TicketSequence, PreviousTxnID, PreviousTxnLgrSeq
|
||||
// Check requires: Account, Destination, SendMax, Sequence, OwnerNode, DestinationNode, PreviousTxnID, PreviousTxnLgrSeq
|
||||
TicketBuilder wrongBuilder{
|
||||
canonical_ACCOUNT(),
|
||||
canonical_UINT64(),
|
||||
canonical_UINT32(),
|
||||
canonical_UINT256(),
|
||||
canonical_UINT32()};
|
||||
auto wrongEntry = wrongBuilder.build(index);
|
||||
|
||||
EXPECT_THROW(Sponsorship{wrongEntry.getSle()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong ledger entry type.
|
||||
TEST(SponsorshipTests, BuilderThrowsOnWrongEntryType)
|
||||
{
|
||||
uint256 const index{4u};
|
||||
|
||||
// Build a valid ledger entry of a different type
|
||||
TicketBuilder wrongBuilder{
|
||||
canonical_ACCOUNT(),
|
||||
canonical_UINT64(),
|
||||
canonical_UINT32(),
|
||||
canonical_UINT256(),
|
||||
canonical_UINT32()};
|
||||
auto wrongEntry = wrongBuilder.build(index);
|
||||
|
||||
EXPECT_THROW(SponsorshipBuilder{wrongEntry.getSle()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(SponsorshipTests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
uint256 const index{3u};
|
||||
|
||||
auto const previousTxnIDValue = canonical_UINT256();
|
||||
auto const previousTxnLgrSeqValue = canonical_UINT32();
|
||||
auto const ownerValue = canonical_ACCOUNT();
|
||||
auto const sponseeValue = canonical_ACCOUNT();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
auto const sponseeNodeValue = canonical_UINT64();
|
||||
|
||||
SponsorshipBuilder builder{
|
||||
previousTxnIDValue,
|
||||
previousTxnLgrSeqValue,
|
||||
ownerValue,
|
||||
sponseeValue,
|
||||
ownerNodeValue,
|
||||
sponseeNodeValue
|
||||
};
|
||||
|
||||
auto const entry = builder.build(index);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(entry.hasFeeAmount());
|
||||
EXPECT_FALSE(entry.getFeeAmount().has_value());
|
||||
EXPECT_FALSE(entry.hasMaxFee());
|
||||
EXPECT_FALSE(entry.getMaxFee().has_value());
|
||||
EXPECT_FALSE(entry.hasRemainingOwnerCount());
|
||||
EXPECT_FALSE(entry.getRemainingOwnerCount().has_value());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// Auto-generated unit tests for transaction SponsorshipSet
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/SponsorshipSet.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsSponsorshipSetTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testSponsorshipSet"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const counterpartySponsorValue = canonical_ACCOUNT();
|
||||
auto const sponseeValue = canonical_ACCOUNT();
|
||||
auto const feeAmountValue = canonical_AMOUNT();
|
||||
auto const maxFeeValue = canonical_AMOUNT();
|
||||
auto const remainingOwnerCountValue = canonical_UINT32();
|
||||
|
||||
SponsorshipSetBuilder builder{
|
||||
accountValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
builder.setCounterpartySponsor(counterpartySponsorValue);
|
||||
builder.setSponsee(sponseeValue);
|
||||
builder.setFeeAmount(feeAmountValue);
|
||||
builder.setMaxFee(maxFeeValue);
|
||||
builder.setRemainingOwnerCount(remainingOwnerCountValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = counterpartySponsorValue;
|
||||
auto const actualOpt = tx.getCounterpartySponsor();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfCounterpartySponsor should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfCounterpartySponsor");
|
||||
EXPECT_TRUE(tx.hasCounterpartySponsor());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponseeValue;
|
||||
auto const actualOpt = tx.getSponsee();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfSponsee should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfSponsee");
|
||||
EXPECT_TRUE(tx.hasSponsee());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = feeAmountValue;
|
||||
auto const actualOpt = tx.getFeeAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfFeeAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfFeeAmount");
|
||||
EXPECT_TRUE(tx.hasFeeAmount());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = maxFeeValue;
|
||||
auto const actualOpt = tx.getMaxFee();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfMaxFee should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfMaxFee");
|
||||
EXPECT_TRUE(tx.hasMaxFee());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = remainingOwnerCountValue;
|
||||
auto const actualOpt = tx.getRemainingOwnerCount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfRemainingOwnerCount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfRemainingOwnerCount");
|
||||
EXPECT_TRUE(tx.hasRemainingOwnerCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsSponsorshipSetTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testSponsorshipSetFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const counterpartySponsorValue = canonical_ACCOUNT();
|
||||
auto const sponseeValue = canonical_ACCOUNT();
|
||||
auto const feeAmountValue = canonical_AMOUNT();
|
||||
auto const maxFeeValue = canonical_AMOUNT();
|
||||
auto const remainingOwnerCountValue = canonical_UINT32();
|
||||
|
||||
// Build an initial transaction
|
||||
SponsorshipSetBuilder initialBuilder{
|
||||
accountValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
initialBuilder.setCounterpartySponsor(counterpartySponsorValue);
|
||||
initialBuilder.setSponsee(sponseeValue);
|
||||
initialBuilder.setFeeAmount(feeAmountValue);
|
||||
initialBuilder.setMaxFee(maxFeeValue);
|
||||
initialBuilder.setRemainingOwnerCount(remainingOwnerCountValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
SponsorshipSetBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = counterpartySponsorValue;
|
||||
auto const actualOpt = rebuiltTx.getCounterpartySponsor();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfCounterpartySponsor should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfCounterpartySponsor");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponseeValue;
|
||||
auto const actualOpt = rebuiltTx.getSponsee();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfSponsee should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfSponsee");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = feeAmountValue;
|
||||
auto const actualOpt = rebuiltTx.getFeeAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfFeeAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfFeeAmount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = maxFeeValue;
|
||||
auto const actualOpt = rebuiltTx.getMaxFee();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfMaxFee should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfMaxFee");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = remainingOwnerCountValue;
|
||||
auto const actualOpt = rebuiltTx.getRemainingOwnerCount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfRemainingOwnerCount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfRemainingOwnerCount");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsSponsorshipSetTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(SponsorshipSet{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsSponsorshipSetTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(SponsorshipSetBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(TransactionsSponsorshipSetTests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testSponsorshipSetNullopt"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 3;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific required field values
|
||||
|
||||
SponsorshipSetBuilder builder{
|
||||
accountValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Do NOT set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(tx.hasCounterpartySponsor());
|
||||
EXPECT_FALSE(tx.getCounterpartySponsor().has_value());
|
||||
EXPECT_FALSE(tx.hasSponsee());
|
||||
EXPECT_FALSE(tx.getSponsee().has_value());
|
||||
EXPECT_FALSE(tx.hasFeeAmount());
|
||||
EXPECT_FALSE(tx.getFeeAmount().has_value());
|
||||
EXPECT_FALSE(tx.hasMaxFee());
|
||||
EXPECT_FALSE(tx.getMaxFee().has_value());
|
||||
EXPECT_FALSE(tx.hasRemainingOwnerCount());
|
||||
EXPECT_FALSE(tx.getRemainingOwnerCount().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
// Auto-generated unit tests for transaction SponsorshipTransfer
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/SponsorshipTransfer.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsSponsorshipTransferTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testSponsorshipTransfer"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const objectIDValue = canonical_UINT256();
|
||||
auto const sponseeValue = canonical_ACCOUNT();
|
||||
|
||||
SponsorshipTransferBuilder builder{
|
||||
accountValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
builder.setObjectID(objectIDValue);
|
||||
builder.setSponsee(sponseeValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = objectIDValue;
|
||||
auto const actualOpt = tx.getObjectID();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfObjectID should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfObjectID");
|
||||
EXPECT_TRUE(tx.hasObjectID());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponseeValue;
|
||||
auto const actualOpt = tx.getSponsee();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfSponsee should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfSponsee");
|
||||
EXPECT_TRUE(tx.hasSponsee());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsSponsorshipTransferTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testSponsorshipTransferFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const objectIDValue = canonical_UINT256();
|
||||
auto const sponseeValue = canonical_ACCOUNT();
|
||||
|
||||
// Build an initial transaction
|
||||
SponsorshipTransferBuilder initialBuilder{
|
||||
accountValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
initialBuilder.setObjectID(objectIDValue);
|
||||
initialBuilder.setSponsee(sponseeValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
SponsorshipTransferBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = objectIDValue;
|
||||
auto const actualOpt = rebuiltTx.getObjectID();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfObjectID should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfObjectID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sponseeValue;
|
||||
auto const actualOpt = rebuiltTx.getSponsee();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfSponsee should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfSponsee");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsSponsorshipTransferTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(SponsorshipTransfer{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsSponsorshipTransferTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(SponsorshipTransferBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(TransactionsSponsorshipTransferTests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testSponsorshipTransferNullopt"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 3;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific required field values
|
||||
|
||||
SponsorshipTransferBuilder builder{
|
||||
accountValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Do NOT set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(tx.hasObjectID());
|
||||
EXPECT_FALSE(tx.getObjectID().has_value());
|
||||
EXPECT_FALSE(tx.hasSponsee());
|
||||
EXPECT_FALSE(tx.getSponsee().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
420
src/tests/libxrpl/telemetry/SpanGuardScope.cpp
Normal file
420
src/tests/libxrpl/telemetry/SpanGuardScope.cpp
Normal file
@@ -0,0 +1,420 @@
|
||||
// Tests for SpanGuard::rootSpan() and SpanGuard::detached().
|
||||
//
|
||||
// These verify the cross-thread scope-leak fix at the trace level using an
|
||||
// in-memory span exporter: rootSpan() must start a fresh trace root (ignoring
|
||||
// the ambient active span), and detached() must strip the thread-local Scope
|
||||
// on the origin thread so the guard can be moved to and ended on another
|
||||
// thread without corrupting the origin thread's context stack.
|
||||
//
|
||||
// The whole file is telemetry-only: when XRPL_ENABLE_TELEMETRY is not defined
|
||||
// SpanGuard is a no-op stub and the OpenTelemetry SDK headers are unavailable,
|
||||
// so the translation unit compiles empty.
|
||||
|
||||
#ifdef XRPL_ENABLE_TELEMETRY
|
||||
|
||||
#include <xrpl/telemetry/SpanGuard.h>
|
||||
#include <xrpl/telemetry/Telemetry.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <opentelemetry/context/context.h>
|
||||
#include <opentelemetry/exporters/memory/in_memory_span_data.h>
|
||||
#include <opentelemetry/exporters/memory/in_memory_span_exporter_factory.h>
|
||||
#include <opentelemetry/nostd/shared_ptr.h>
|
||||
#include <opentelemetry/sdk/resource/resource.h>
|
||||
#include <opentelemetry/sdk/trace/samplers/always_on_factory.h>
|
||||
#include <opentelemetry/sdk/trace/simple_processor_factory.h>
|
||||
#include <opentelemetry/sdk/trace/span_data.h>
|
||||
#include <opentelemetry/sdk/trace/tracer_provider.h>
|
||||
#include <opentelemetry/sdk/trace/tracer_provider_factory.h>
|
||||
#include <opentelemetry/trace/span.h>
|
||||
#include <opentelemetry/trace/span_id.h>
|
||||
#include <opentelemetry/trace/span_metadata.h>
|
||||
#include <opentelemetry/trace/span_startoptions.h>
|
||||
#include <opentelemetry/trace/trace_id.h>
|
||||
#include <opentelemetry/trace/tracer.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::telemetry {
|
||||
namespace {
|
||||
|
||||
namespace otel_sdk_trace = opentelemetry::sdk::trace;
|
||||
namespace otel_memory = opentelemetry::exporter::memory;
|
||||
|
||||
/**
|
||||
* In-memory Telemetry backing for SpanGuard scope tests.
|
||||
*
|
||||
* Reports every trace category as enabled and creates spans through an SDK
|
||||
* TracerProvider whose SimpleSpanProcessor forwards ended spans to an
|
||||
* InMemorySpanExporter, so a test can read the exact exported SpanData
|
||||
* (trace id, span id, parent id, name).
|
||||
*
|
||||
* Inheritance:
|
||||
*
|
||||
* +-----------+
|
||||
* | Telemetry | (abstract interface)
|
||||
* +-----+-----+
|
||||
* |
|
||||
* +-----+---------+
|
||||
* | TestTelemetry | in-memory exporter pipeline
|
||||
* +---------------+
|
||||
*
|
||||
* @note Test-only. Install with Telemetry::setInstance() and clear it in
|
||||
* teardown. The exporter buffer is drained by InMemorySpanData::GetSpans(),
|
||||
* so each GetSpans() call returns only spans exported since the previous one.
|
||||
*/
|
||||
class TestTelemetry : public Telemetry
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Build the SDK export pipeline and keep the exporter's span buffer.
|
||||
*/
|
||||
TestTelemetry()
|
||||
{
|
||||
// Factory populates spanData_ with the exporter's shared buffer.
|
||||
auto exporter = otel_memory::InMemorySpanExporterFactory::Create(spanData_);
|
||||
auto processor = otel_sdk_trace::SimpleSpanProcessorFactory::Create(std::move(exporter));
|
||||
provider_ = otel_sdk_trace::TracerProviderFactory::Create(
|
||||
std::move(processor),
|
||||
opentelemetry::sdk::resource::Resource::Create({}),
|
||||
otel_sdk_trace::AlwaysOnSamplerFactory::Create());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The exporter's span buffer (drained by GetSpans()).
|
||||
*/
|
||||
std::shared_ptr<otel_memory::InMemorySpanData>
|
||||
spanData() const
|
||||
{
|
||||
return spanData_;
|
||||
}
|
||||
|
||||
void
|
||||
start() override
|
||||
{
|
||||
}
|
||||
void
|
||||
stop() override
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isEnabled() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
shouldTraceTransactions() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
shouldTraceConsensus() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
shouldTraceRpc() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
shouldTracePeer() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
shouldTraceLedger() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
opentelemetry::nostd::shared_ptr<opentelemetry::trace::Tracer>
|
||||
getTracer(std::string_view name) override
|
||||
{
|
||||
return provider_->GetTracer(std::string(name));
|
||||
}
|
||||
|
||||
opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>
|
||||
startSpan(std::string_view name, opentelemetry::trace::SpanKind kind) override
|
||||
{
|
||||
opentelemetry::trace::StartSpanOptions opts;
|
||||
opts.kind = kind;
|
||||
return getTracer(kTracerName)->StartSpan(std::string(name), opts);
|
||||
}
|
||||
|
||||
opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>
|
||||
startSpan(
|
||||
std::string_view name,
|
||||
opentelemetry::context::Context const& parentContext,
|
||||
opentelemetry::trace::SpanKind kind) override
|
||||
{
|
||||
opentelemetry::trace::StartSpanOptions opts;
|
||||
opts.kind = kind;
|
||||
opts.parent = parentContext;
|
||||
return getTracer(kTracerName)->StartSpan(std::string(name), opts);
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* SDK provider owning the export pipeline.
|
||||
*/
|
||||
std::shared_ptr<otel_sdk_trace::TracerProvider> provider_;
|
||||
|
||||
/**
|
||||
* Shared buffer that receives ended spans from the exporter.
|
||||
*/
|
||||
std::shared_ptr<otel_memory::InMemorySpanData> spanData_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return The span's name as a std::string for equality checks.
|
||||
*/
|
||||
std::string
|
||||
nameOf(otel_sdk_trace::SpanData const& span)
|
||||
{
|
||||
auto view = span.GetName();
|
||||
return std::string(view.data(), view.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Pointer to the first exported span with the given name, or null.
|
||||
*/
|
||||
otel_sdk_trace::SpanData*
|
||||
findSpan(std::vector<std::unique_ptr<otel_sdk_trace::SpanData>> const& spans, std::string_view name)
|
||||
{
|
||||
for (auto const& span : spans)
|
||||
{
|
||||
if (nameOf(*span) == name)
|
||||
return span.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Number of exported spans with the given name.
|
||||
*/
|
||||
std::size_t
|
||||
countSpans(
|
||||
std::vector<std::unique_ptr<otel_sdk_trace::SpanData>> const& spans,
|
||||
std::string_view name)
|
||||
{
|
||||
std::size_t count = 0;
|
||||
for (auto const& span : spans)
|
||||
{
|
||||
if (nameOf(*span) == name)
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a TestTelemetry as the global instance for each test and clears it
|
||||
* afterwards so the singleton never dangles between cases.
|
||||
*/
|
||||
class SpanGuardScopeTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
telemetry_ = std::make_unique<TestTelemetry>();
|
||||
Telemetry::setInstance(telemetry_.get());
|
||||
}
|
||||
|
||||
void
|
||||
TearDown() override
|
||||
{
|
||||
Telemetry::setInstance(nullptr);
|
||||
telemetry_.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The exporter's span buffer for the active TestTelemetry.
|
||||
*/
|
||||
std::shared_ptr<otel_memory::InMemorySpanData>
|
||||
spanData() const
|
||||
{
|
||||
return telemetry_->spanData();
|
||||
}
|
||||
|
||||
/**
|
||||
* The in-memory Telemetry installed for the duration of a test.
|
||||
*/
|
||||
std::unique_ptr<TestTelemetry> telemetry_;
|
||||
};
|
||||
|
||||
// rootSpan() must ignore the ambient active span and start a brand-new trace.
|
||||
TEST_F(SpanGuardScopeTest, rootSpan_ignores_ambient_parent)
|
||||
{
|
||||
{
|
||||
// Ambient span becomes the active span on this thread.
|
||||
auto ambient = SpanGuard::span(TraceCategory::Rpc, "rpc", "command");
|
||||
ASSERT_TRUE(static_cast<bool>(ambient));
|
||||
|
||||
// rootSpan must NOT inherit the ambient span as its parent.
|
||||
auto root = SpanGuard::rootSpan(TraceCategory::Peer, "peer", "validation.receive");
|
||||
ASSERT_TRUE(static_cast<bool>(root));
|
||||
} // root ends first, then ambient -- both on this thread.
|
||||
|
||||
auto spans = spanData()->GetSpans();
|
||||
auto* ambient = findSpan(spans, "rpc.command");
|
||||
auto* root = findSpan(spans, "peer.validation.receive");
|
||||
ASSERT_NE(ambient, nullptr);
|
||||
ASSERT_NE(root, nullptr);
|
||||
|
||||
// The ambient span is itself a root (first span, no parent).
|
||||
EXPECT_FALSE(ambient->GetParentSpanId().IsValid());
|
||||
EXPECT_TRUE(ambient->GetTraceId().IsValid());
|
||||
|
||||
// The rootSpan has NO parent and lives in a DIFFERENT trace than ambient.
|
||||
EXPECT_FALSE(root->GetParentSpanId().IsValid());
|
||||
EXPECT_TRUE(root->GetTraceId().IsValid());
|
||||
EXPECT_NE(root->GetTraceId(), ambient->GetTraceId());
|
||||
}
|
||||
|
||||
// detached() pops the Scope on the origin thread, so ending the guard on
|
||||
// another thread leaves the origin thread's context stack clean.
|
||||
TEST_F(SpanGuardScopeTest, detached_does_not_corrupt_origin_stack)
|
||||
{
|
||||
// Scoped guard is active on this (origin) thread.
|
||||
auto guard = SpanGuard::span(TraceCategory::Ledger, "ledger", "build");
|
||||
ASSERT_TRUE(static_cast<bool>(guard));
|
||||
|
||||
// Strip the thread-local Scope here on the origin thread.
|
||||
auto detached = std::move(guard).detached();
|
||||
ASSERT_TRUE(static_cast<bool>(detached));
|
||||
|
||||
// End the detached span on a worker thread.
|
||||
std::thread worker([d = std::move(detached)]() mutable {});
|
||||
worker.join();
|
||||
|
||||
// Back on the origin thread: a new span must be a fresh root, proving the
|
||||
// origin stack top was restored by detached() (not left holding
|
||||
// ledger.build).
|
||||
{
|
||||
auto after = SpanGuard::span(TraceCategory::Rpc, "rpc", "command");
|
||||
ASSERT_TRUE(static_cast<bool>(after));
|
||||
}
|
||||
|
||||
auto spans = spanData()->GetSpans();
|
||||
auto* build = findSpan(spans, "ledger.build");
|
||||
auto* after = findSpan(spans, "rpc.command");
|
||||
ASSERT_NE(build, nullptr);
|
||||
ASSERT_NE(after, nullptr);
|
||||
|
||||
// 'after' is a new root: zero parent and a different trace than
|
||||
// ledger.build.
|
||||
EXPECT_FALSE(after->GetParentSpanId().IsValid());
|
||||
EXPECT_NE(after->GetTraceId(), build->GetTraceId());
|
||||
}
|
||||
|
||||
// A detached span is ended exactly once, by the worker thread that owns it.
|
||||
TEST_F(SpanGuardScopeTest, detached_span_ends_once)
|
||||
{
|
||||
auto guard = SpanGuard::span(TraceCategory::Ledger, "ledger", "build");
|
||||
auto detached = std::move(guard).detached();
|
||||
ASSERT_TRUE(static_cast<bool>(detached));
|
||||
|
||||
// Before the worker ends it, the span is still open: nothing exported yet.
|
||||
auto before = spanData()->GetSpans();
|
||||
EXPECT_EQ(countSpans(before, "ledger.build"), 0u);
|
||||
|
||||
// Ending happens once, on the worker thread, when the guard is destroyed.
|
||||
{
|
||||
std::thread worker([d = std::move(detached)]() mutable {});
|
||||
worker.join();
|
||||
}
|
||||
|
||||
auto after = spanData()->GetSpans();
|
||||
EXPECT_EQ(countSpans(after, "ledger.build"), 1u);
|
||||
}
|
||||
|
||||
// rootSpan().detached() is the combination used at RPC coroutine entry points
|
||||
// (e.g. RipplePathFind) that hold a span across a coro yield: the span must be
|
||||
// a fresh root AND must not leave a Scope on the worker's context stack.
|
||||
TEST_F(SpanGuardScopeTest, root_then_detached_is_root_and_leaves_stack_clean)
|
||||
{
|
||||
{
|
||||
// Ambient span active on this (origin) thread.
|
||||
auto ambient = SpanGuard::span(TraceCategory::Rpc, "rpc", "command");
|
||||
ASSERT_TRUE(static_cast<bool>(ambient));
|
||||
|
||||
// Fresh root, then detach the Scope on the origin thread.
|
||||
auto req = SpanGuard::rootSpan(TraceCategory::Rpc, "pathfind", "request").detached();
|
||||
ASSERT_TRUE(static_cast<bool>(req));
|
||||
|
||||
// End the detached root span on a worker thread (mimics coro resume).
|
||||
std::thread worker([r = std::move(req)]() mutable {});
|
||||
worker.join();
|
||||
|
||||
// Origin stack must be clean: a new span here is still parented by the
|
||||
// ambient span (proving the detached root did not linger on the stack).
|
||||
{
|
||||
auto child = SpanGuard::span(TraceCategory::Rpc, "rpc", "sub");
|
||||
ASSERT_TRUE(static_cast<bool>(child));
|
||||
}
|
||||
}
|
||||
|
||||
auto spans = spanData()->GetSpans();
|
||||
auto* ambient = findSpan(spans, "rpc.command");
|
||||
auto* req = findSpan(spans, "pathfind.request");
|
||||
auto* child = findSpan(spans, "rpc.sub");
|
||||
ASSERT_NE(ambient, nullptr);
|
||||
ASSERT_NE(req, nullptr);
|
||||
ASSERT_NE(child, nullptr);
|
||||
|
||||
// The rooted-then-detached span has NO parent and a DIFFERENT trace.
|
||||
EXPECT_FALSE(req->GetParentSpanId().IsValid());
|
||||
EXPECT_NE(req->GetTraceId(), ambient->GetTraceId());
|
||||
|
||||
// The detached root did not corrupt the origin stack: 'child' still nests
|
||||
// under the ambient span (same trace, parent = ambient), NOT under req.
|
||||
EXPECT_EQ(child->GetParentSpanId(), ambient->GetSpanId());
|
||||
EXPECT_EQ(child->GetTraceId(), ambient->GetTraceId());
|
||||
}
|
||||
|
||||
// Negative control (documents the bug detached() fixes): a scoped guard NOT
|
||||
// detached, moved to and destroyed on a worker thread, pops the WRONG thread's
|
||||
// stack, so a later span on the origin thread inherits the stale span. The
|
||||
// whole scenario runs on a dedicated origin thread so the corrupted
|
||||
// thread-local stack cannot leak into other tests.
|
||||
TEST_F(SpanGuardScopeTest, non_detached_cross_thread_corrupts_origin_stack)
|
||||
{
|
||||
std::thread origin([&] {
|
||||
auto guard = SpanGuard::span(TraceCategory::Ledger, "ledger", "build");
|
||||
{
|
||||
// ~SpanGuard on the worker ends the span and Detaches on the
|
||||
// WORKER stack -- the origin stack is never popped.
|
||||
std::thread worker([d = std::move(guard)]() mutable {});
|
||||
worker.join();
|
||||
}
|
||||
// Origin stack still holds the stale ledger.build as active, so this
|
||||
// span inherits it as parent instead of being a root.
|
||||
auto after = SpanGuard::span(TraceCategory::Rpc, "rpc", "command");
|
||||
ASSERT_TRUE(static_cast<bool>(after));
|
||||
});
|
||||
origin.join();
|
||||
|
||||
auto spans = spanData()->GetSpans();
|
||||
auto* build = findSpan(spans, "ledger.build");
|
||||
auto* after = findSpan(spans, "rpc.command");
|
||||
ASSERT_NE(build, nullptr);
|
||||
ASSERT_NE(after, nullptr);
|
||||
|
||||
// BUG: 'after' inherited the stale span -- non-zero parent, same trace.
|
||||
EXPECT_TRUE(after->GetParentSpanId().IsValid());
|
||||
EXPECT_EQ(after->GetParentSpanId(), build->GetSpanId());
|
||||
EXPECT_EQ(after->GetTraceId(), build->GetTraceId());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace xrpl::telemetry
|
||||
|
||||
#endif // XRPL_ENABLE_TELEMETRY
|
||||
Reference in New Issue
Block a user