#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl::PeerFinder { bool operator==(Endpoint const& a, Endpoint const& b) { return (a.hops == b.hops && a.address == b.address); } class Livecache_test : public beast::unit_test::Suite { TestStopwatch clock_; test::SuiteJournal journal_; public: Livecache_test() : journal_("Livecache_test", *this) { } // Add the address as an endpoint template void add(beast::IP::Endpoint ep, C& c, std::uint32_t hops = 0) { Endpoint const cep{ep, hops}; c.insert(cep); } void testBasicInsert() { testcase("Basic Insert"); Livecache<> c(clock_, journal_); BEAST_EXPECT(c.empty()); for (auto i = 0; i < 10; ++i) add(beast::IP::randomEP(true), c); BEAST_EXPECT(!c.empty()); BEAST_EXPECT(c.size() == 10); for (auto i = 0; i < 10; ++i) add(beast::IP::randomEP(false), c); BEAST_EXPECT(!c.empty()); BEAST_EXPECT(c.size() == 20); } void testInsertUpdate() { testcase("Insert/Update"); Livecache<> c(clock_, journal_); auto ep1 = Endpoint{beast::IP::randomEP(), 2}; c.insert(ep1); BEAST_EXPECT(c.size() == 1); // third position list will contain the entry BEAST_EXPECT((c.hops.begin() + 2)->begin()->hops == 2); auto ep2 = Endpoint{ep1.address, 4}; // this will not change the entry has higher hops c.insert(ep2); BEAST_EXPECT(c.size() == 1); // still in third position list BEAST_EXPECT((c.hops.begin() + 2)->begin()->hops == 2); auto ep3 = Endpoint{ep1.address, 2}; // this will not change the entry has the same hops as existing c.insert(ep3); BEAST_EXPECT(c.size() == 1); // still in third position list BEAST_EXPECT((c.hops.begin() + 2)->begin()->hops == 2); auto ep4 = Endpoint{ep1.address, 1}; c.insert(ep4); BEAST_EXPECT(c.size() == 1); // now at second position list BEAST_EXPECT((c.hops.begin() + 1)->begin()->hops == 1); } void testExpire() { testcase("Expire"); using namespace std::chrono_literals; Livecache<> c(clock_, journal_); auto ep1 = Endpoint{beast::IP::randomEP(), 1}; c.insert(ep1); BEAST_EXPECT(c.size() == 1); c.expire(); BEAST_EXPECT(c.size() == 1); // verify that advancing to 1 sec before expiration // leaves our entry intact clock_.advance(Tuning::kLiveCacheSecondsToLive - 1s); c.expire(); BEAST_EXPECT(c.size() == 1); // now advance to the point of expiration clock_.advance(1s); c.expire(); BEAST_EXPECT(c.empty()); } void testHistogram() { testcase("Histogram"); static constexpr auto kNumEps = 40; Livecache<> c(clock_, journal_); for (auto i = 0; i < kNumEps; ++i) add(beast::IP::randomEP(true), c, xrpl::randInt()); auto h = c.hops.histogram(); if (!BEAST_EXPECT(!h.empty())) return; std::vector v; boost::split(v, h, boost::algorithm::is_any_of(",")); auto sum = 0; for (auto const& n : v) { auto val = boost::lexical_cast(boost::trim_copy(n)); sum += val; BEAST_EXPECT(val >= 0); } BEAST_EXPECT(sum == kNumEps); } void testShuffle() { testcase("Shuffle"); Livecache<> c(clock_, journal_); for (auto i = 0; i < 100; ++i) add(beast::IP::randomEP(true), c, xrpl::randInt(Tuning::kMaxHops + 1)); using at_hop = std::vector; using all_hops = std::array; auto cmpEp = [](Endpoint const& a, Endpoint const& b) { return (b.hops < a.hops || (b.hops == a.hops && b.address < a.address)); }; all_hops before; all_hops beforeSorted; for (auto i = std::make_pair(0, c.hops.begin()); i.second != c.hops.end(); ++i.first, ++i.second) { std::ranges::copy(*i.second, std::back_inserter(before[i.first])); std::ranges::copy(*i.second, std::back_inserter(beforeSorted[i.first])); std::ranges::sort(beforeSorted[i.first], cmpEp); } c.hops.shuffle(); all_hops after; all_hops afterSorted; for (auto i = std::make_pair(0, c.hops.begin()); i.second != c.hops.end(); ++i.first, ++i.second) { std::ranges::copy(*i.second, std::back_inserter(after[i.first])); std::ranges::copy(*i.second, std::back_inserter(afterSorted[i.first])); std::ranges::sort(afterSorted[i.first], cmpEp); } // each hop bucket should contain the same items // before and after sort, albeit in different order bool allMatch = true; for (auto i = 0; i < before.size(); ++i) { BEAST_EXPECT(before[i].size() == after[i].size()); allMatch = allMatch && (before[i] == after[i]); BEAST_EXPECT(beforeSorted[i] == afterSorted[i]); } BEAST_EXPECT(!allMatch); } void run() override { testBasicInsert(); testInsertUpdate(); testExpire(); testHistogram(); testShuffle(); } }; BEAST_DEFINE_TESTSUITE(Livecache, peerfinder, xrpl); } // namespace xrpl::PeerFinder