migrated tests to gtest

This commit is contained in:
Valentin Balaschenko
2026-07-07 14:26:25 +01:00
parent 3c6c2f336b
commit d026e5aec9
2 changed files with 61 additions and 318 deletions

View File

@@ -1,308 +0,0 @@
#include <test/unit_test/SuiteJournal.h>
#include <xrpl/basics/IntrusivePointer.h>
#include <xrpl/basics/IntrusiveRefCounts.h>
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/basics/TaggedCache.ipp> // IWYU pragma: keep
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/Protocol.h>
#include <memory>
#include <string>
#include <utility>
namespace xrpl {
/*
I guess you can put some items in, make sure they're still there. Let some
time pass, make sure they're gone. Keep a strong pointer to one of them, make
sure you can still find it even after time passes. Create two objects with
the same key, canonicalize them both and make sure you get the same object.
Put an object in but keep a strong pointer to it, advance the clock a lot,
then canonicalize a new object with the same key, make sure you get the
original object.
*/
class TaggedCache_test : public beast::unit_test::Suite
{
public:
// Mutable value type for testing fetchAndModify
struct MutableValue
{
int counter = 0;
std::string name;
};
void
run() override
{
using namespace std::chrono_literals;
using beast::Severity;
test::SuiteJournal journal("TaggedCache_test", *this);
TestStopwatch clock;
clock.set(0);
using Key = LedgerIndex;
using Value = std::string;
using Cache = TaggedCache<Key, Value>;
Cache c("test", 1, 1s, clock, journal);
// Insert an item, retrieve it, and age it so it gets purged.
{
BEAST_EXPECT(c.getCacheSize() == 0);
BEAST_EXPECT(c.getTrackSize() == 0);
BEAST_EXPECT(!c.insert(1, "one"));
BEAST_EXPECT(c.getCacheSize() == 1);
BEAST_EXPECT(c.getTrackSize() == 1);
{
std::string s;
BEAST_EXPECT(c.retrieve(1, s));
BEAST_EXPECT(s == "one");
}
++clock;
c.sweep();
BEAST_EXPECT(c.getCacheSize() == 0);
BEAST_EXPECT(c.getTrackSize() == 0);
}
// Insert an item, maintain a strong pointer, age it, and
// verify that the entry still exists.
{
BEAST_EXPECT(!c.insert(2, "two"));
BEAST_EXPECT(c.getCacheSize() == 1);
BEAST_EXPECT(c.getTrackSize() == 1);
{
auto p = c.fetch(2);
BEAST_EXPECT(p != nullptr);
++clock;
c.sweep();
BEAST_EXPECT(c.getCacheSize() == 0);
BEAST_EXPECT(c.getTrackSize() == 1);
}
// Make sure its gone now that our reference is gone
++clock;
c.sweep();
BEAST_EXPECT(c.getCacheSize() == 0);
BEAST_EXPECT(c.getTrackSize() == 0);
}
// Insert the same key/value pair and make sure we get the same result
{
BEAST_EXPECT(!c.insert(3, "three"));
{
auto const p1 = c.fetch(3);
auto p2 = std::make_shared<Value>("three");
c.canonicalizeReplaceClient(3, p2);
BEAST_EXPECT(p1.get() == p2.get());
}
++clock;
c.sweep();
BEAST_EXPECT(c.getCacheSize() == 0);
BEAST_EXPECT(c.getTrackSize() == 0);
}
// Put an object in but keep a strong pointer to it, advance the clock a
// lot, then canonicalize a new object with the same key, make sure you
// get the original object.
{
// Put an object in
BEAST_EXPECT(!c.insert(4, "four"));
BEAST_EXPECT(c.getCacheSize() == 1);
BEAST_EXPECT(c.getTrackSize() == 1);
{
// Keep a strong pointer to it
auto const p1 = c.fetch(4);
BEAST_EXPECT(p1 != nullptr);
BEAST_EXPECT(c.getCacheSize() == 1);
BEAST_EXPECT(c.getTrackSize() == 1);
// Advance the clock a lot
++clock;
c.sweep();
BEAST_EXPECT(c.getCacheSize() == 0);
BEAST_EXPECT(c.getTrackSize() == 1);
// Canonicalize a new object with the same key
auto p2 = std::make_shared<std::string>("four");
BEAST_EXPECT(c.canonicalizeReplaceClient(4, p2));
BEAST_EXPECT(c.getCacheSize() == 1);
BEAST_EXPECT(c.getTrackSize() == 1);
// Make sure we get the original object
BEAST_EXPECT(p1.get() == p2.get());
}
++clock;
c.sweep();
BEAST_EXPECT(c.getCacheSize() == 0);
BEAST_EXPECT(c.getTrackSize() == 0);
}
// Test fetchAndModify: insert on miss, modify on hit
{
using MutCache = TaggedCache<Key, MutableValue>;
MutCache mc("mutable_test", 2, 2s, clock, journal);
// A. Insert on miss: fetchAndModify creates entry and mutates it
mc.fetchAndModify(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 fetchAndModify
mc.fetchAndModify(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);
}
{
BEAST_EXPECT(!c.insert(5, "five"));
BEAST_EXPECT(c.getCacheSize() == 1);
BEAST_EXPECT(c.size() == 1);
{
auto const p1 = c.fetch(5);
BEAST_EXPECT(p1 != nullptr);
BEAST_EXPECT(c.getCacheSize() == 1);
BEAST_EXPECT(c.size() == 1);
// Advance the clock a lot
++clock;
c.sweep();
BEAST_EXPECT(c.getCacheSize() == 0);
BEAST_EXPECT(c.size() == 1);
auto p2 = std::make_shared<std::string>("five_2");
BEAST_EXPECT(c.canonicalizeReplaceCache(5, p2));
BEAST_EXPECT(c.getCacheSize() == 1);
BEAST_EXPECT(c.size() == 1);
// Make sure the caller's original pointer is unchanged
BEAST_EXPECT(p1.get() != p2.get());
BEAST_EXPECT(*p2 == "five_2");
auto const p3 = c.fetch(5);
BEAST_EXPECT(p3 != nullptr);
BEAST_EXPECT(p3.get() == p2.get());
BEAST_EXPECT(p3.get() != p1.get());
}
++clock;
c.sweep();
BEAST_EXPECT(c.getCacheSize() == 0);
BEAST_EXPECT(c.size() == 0);
}
{
testcase("intrptr");
struct MyRefCountObject : IntrusiveRefCounts
{
std::string data;
// Needed to support weak intrusive pointers
virtual void
partialDestructor() {};
MyRefCountObject() = default;
explicit MyRefCountObject(std::string data) : data(std::move(data))
{
}
bool
operator==(std::string const& other) const
{
return data == other;
}
};
using IntrPtrCache = TaggedCache<
Key,
MyRefCountObject,
/*IsKeyCache*/ false,
intr_ptr::SharedWeakUnionPtr<MyRefCountObject>,
intr_ptr::SharedPtr<MyRefCountObject>>;
IntrPtrCache intrPtrCache("IntrPtrTest", 1, 1s, clock, journal);
intrPtrCache.canonicalizeReplaceCache(1, intr_ptr::makeShared<MyRefCountObject>("one"));
BEAST_EXPECT(intrPtrCache.getCacheSize() == 1);
BEAST_EXPECT(intrPtrCache.size() == 1);
{
{
intrPtrCache.canonicalizeReplaceCache(
1, intr_ptr::makeShared<MyRefCountObject>("one_replaced"));
auto p = intrPtrCache.fetch(1);
BEAST_EXPECT(*p == "one_replaced");
// Advance the clock a lot
++clock;
intrPtrCache.sweep();
BEAST_EXPECT(intrPtrCache.getCacheSize() == 0);
BEAST_EXPECT(intrPtrCache.size() == 1);
intrPtrCache.canonicalizeReplaceCache(
1, intr_ptr::makeShared<MyRefCountObject>("one_replaced_2"));
auto p2 = intrPtrCache.fetch(1);
BEAST_EXPECT(*p2 == "one_replaced_2");
intrPtrCache.del(1, true);
}
intrPtrCache.canonicalizeReplaceCache(
1, intr_ptr::makeShared<MyRefCountObject>("one_replaced_3"));
auto p3 = intrPtrCache.fetch(1);
BEAST_EXPECT(*p3 == "one_replaced_3");
}
++clock;
intrPtrCache.sweep();
BEAST_EXPECT(intrPtrCache.getCacheSize() == 0);
BEAST_EXPECT(intrPtrCache.size() == 0);
}
}
};
BEAST_DEFINE_TESTSUITE(TaggedCache, basics, xrpl);
} // namespace xrpl

View File

@@ -16,16 +16,6 @@
namespace xrpl {
/*
I guess you can put some items in, make sure they're still there. Let some
time pass, make sure they're gone. Keep a strong pointer to one of them, make
sure you can still find it even after time passes. Create two objects with
the same key, canonicalize them both and make sure you get the same object.
Put an object in but keep a strong pointer to it, advance the clock a lot,
then canonicalize a new object with the same key, make sure you get the
original object.
*/
TEST(TaggedCacheTest, tagged_cache)
{
using namespace std::chrono_literals;
@@ -243,4 +233,65 @@ TEST(TaggedCacheTest, tagged_cache)
}
}
TEST(TaggedCacheTest, fetch_and_modify)
{
using namespace std::chrono_literals;
beast::Journal const journal{TestSink::instance()};
TestStopwatch clock;
clock.set(0);
struct MutableValue
{
int counter = 0;
std::string name;
};
using Key = LedgerIndex;
using MutCache = TaggedCache<Key, MutableValue>;
MutCache mc("mutable_test", 2, 2s, clock, journal);
// A. Insert on miss: fetchAndModify creates entry and mutates it.
mc.fetchAndModify(5, [](MutableValue& v) {
v.counter = 42;
v.name = "initial";
});
EXPECT_EQ(mc.getCacheSize(), 1);
EXPECT_EQ(mc.getTrackSize(), 1);
// Verify the mutation persisted.
auto p1 = mc.fetch(5);
ASSERT_NE(p1, nullptr);
EXPECT_EQ(p1->counter, 42);
EXPECT_EQ(p1->name, "initial");
// Keep a second strong pointer to verify in-place modification.
auto p2 = mc.fetch(5);
ASSERT_NE(p2, nullptr);
EXPECT_EQ(p1.get(), p2.get()); // Same object
// B. Modify existing object on hit.
mc.fetchAndModify(5, [](MutableValue& v) {
v.counter += 10;
v.name = "modified";
});
// No new entry was created.
EXPECT_EQ(mc.getCacheSize(), 1);
EXPECT_EQ(mc.getTrackSize(), 1);
// The same object was mutated (both strong pointers see the change).
EXPECT_EQ(p1->counter, 52);
EXPECT_EQ(p1->name, "modified");
EXPECT_EQ(p2->counter, 52);
// Fresh fetch returns the same object identity.
auto p3 = mc.fetch(5);
ASSERT_NE(p3, nullptr);
EXPECT_EQ(p3.get(), p1.get());
EXPECT_EQ(p3->counter, 52);
}
} // namespace xrpl