diff --git a/src/test/app/LedgerHistory_test.cpp b/src/test/app/LedgerHistory_test.cpp index 93ab4a6210..a408991497 100644 --- a/src/test/app/LedgerHistory_test.cpp +++ b/src/test/app/LedgerHistory_test.cpp @@ -67,6 +67,67 @@ public: return res; } + void + testHashIndexInvariant() + { + testcase("LedgerHistory hash/index invariant"); + using namespace jtx; + using namespace std::chrono; + + Env env{*this}; + LedgerHistory lh{beast::insight::NullCollector::New(), env.app()}; + + // Create and insert validated ledgers + auto const genesis = makeLedger({}, env, lh, 0s); + auto const ledger1 = makeLedger(genesis, env, lh, 4s); + auto const ledger2 = makeLedger(ledger1, env, lh, 4s); + auto const ledger3 = makeLedger(ledger2, env, lh, 4s); + + // Insert as validated (so they go into by_index) + lh.insert(genesis, true); + lh.insert(ledger1, true); + lh.insert(ledger2, true); + lh.insert(ledger3, true); + + // Verify the hash/index invariant holds + // Can retrieve by sequence and get correct hash + BEAST_EXPECT(lh.getLedgerHash(genesis->header().seq) == genesis->header().hash); + BEAST_EXPECT(lh.getLedgerHash(ledger1->header().seq) == ledger1->header().hash); + BEAST_EXPECT(lh.getLedgerHash(ledger2->header().seq) == ledger2->header().hash); + BEAST_EXPECT(lh.getLedgerHash(ledger3->header().seq) == ledger3->header().hash); + + // Can retrieve by sequence and get correct ledger + auto fetched1 = lh.getLedgerBySeq(ledger1->header().seq); + BEAST_EXPECT(fetched1 != nullptr); + BEAST_EXPECT(fetched1->header().hash == ledger1->header().hash); + + auto fetched2 = lh.getLedgerBySeq(ledger2->header().seq); + BEAST_EXPECT(fetched2 != nullptr); + BEAST_EXPECT(fetched2->header().hash == ledger2->header().hash); + + // Clear ledgers prior to ledger2's sequence + lh.clearLedgerCachePrior(ledger2->header().seq); + + // Verify old entries are gone from the in-memory by_index map + // Note: getLedgerHash checks by_index directly without DB fallback + BEAST_EXPECT(lh.getLedgerHash(genesis->header().seq).isZero()); + BEAST_EXPECT(lh.getLedgerHash(ledger1->header().seq).isZero()); + + // Verify newer entries are still present in by_index + BEAST_EXPECT(lh.getLedgerHash(ledger2->header().seq) == ledger2->header().hash); + BEAST_EXPECT(lh.getLedgerHash(ledger3->header().seq) == ledger3->header().hash); + + // Verify newer entries remain retrievable and consistent + // getLedgerBySeq uses by_index first, then falls back to DB if needed + auto fetched2After = lh.getLedgerBySeq(ledger2->header().seq); + BEAST_EXPECT(fetched2After != nullptr); + BEAST_EXPECT(fetched2After->header().hash == ledger2->header().hash); + + auto fetched3After = lh.getLedgerBySeq(ledger3->header().seq); + BEAST_EXPECT(fetched3After != nullptr); + BEAST_EXPECT(fetched3After->header().hash == ledger3->header().hash); + } + void testHandleMismatch() { @@ -163,6 +224,7 @@ public: void run() override { + testHashIndexInvariant(); testHandleMismatch(); } }; diff --git a/src/test/basics/TaggedCache_test.cpp b/src/test/basics/TaggedCache_test.cpp index 78dc25380b..6d4c3198ba 100644 --- a/src/test/basics/TaggedCache_test.cpp +++ b/src/test/basics/TaggedCache_test.cpp @@ -20,6 +20,13 @@ original object. class TaggedCache_test : public beast::unit_test::suite { public: + // Mutable value type for testing fetch_and_modify + struct MutableValue + { + int counter = 0; + std::string name; + }; + void run() override { @@ -129,6 +136,54 @@ public: BEAST_EXPECT(c.getCacheSize() == 0); BEAST_EXPECT(c.getTrackSize() == 0); } + + // Test fetch_and_modify: insert on miss, modify on hit + { + using MutCache = TaggedCache; + MutCache mc("mutable_test", 2, 2s, clock, journal); + + // A. Insert on miss: fetch_and_modify creates entry and mutates it + mc.fetch_and_modify(5, [](MutableValue& v) { + v.counter = 42; + v.name = "initial"; + }); + + BEAST_EXPECT(mc.getCacheSize() == 1); + BEAST_EXPECT(mc.getTrackSize() == 1); + + // Verify the mutation persisted + auto p1 = mc.fetch(5); + BEAST_EXPECT(p1 != nullptr); + BEAST_EXPECT(p1->counter == 42); + BEAST_EXPECT(p1->name == "initial"); + + // B. Modify existing object on hit + // Keep strong pointer to verify in-place modification + auto p2 = mc.fetch(5); + BEAST_EXPECT(p2 != nullptr); + BEAST_EXPECT(p1.get() == p2.get()); // Same object + + // Modify through fetch_and_modify + mc.fetch_and_modify(5, [](MutableValue& v) { + v.counter += 10; + v.name = "modified"; + }); + + // Verify no new entry was created + BEAST_EXPECT(mc.getCacheSize() == 1); + BEAST_EXPECT(mc.getTrackSize() == 1); + + // Verify the same object was mutated (strong pointer sees change) + BEAST_EXPECT(p1->counter == 52); + BEAST_EXPECT(p1->name == "modified"); + BEAST_EXPECT(p2->counter == 52); // Original pointer sees mutation + + // Verify via fresh fetch + auto p3 = mc.fetch(5); + BEAST_EXPECT(p3 != nullptr); + BEAST_EXPECT(p3.get() == p1.get()); // Same object identity + BEAST_EXPECT(p3->counter == 52); + } } }; diff --git a/src/tests/libxrpl/ledger/main.cpp b/src/tests/libxrpl/ledger/main.cpp deleted file mode 100644 index 5142bbe08a..0000000000 --- a/src/tests/libxrpl/ledger/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int -main(int argc, char** argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -}