// This file Copyright (C) 2022 Mnemosyne LLC. // It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only), // or any future license endorsed by Mnemosyne LLC. // License text can be found in the licenses/ folder. #include #include #include #include "transmission.h" #include "dns-ev.h" #include "dns.h" #include "trevent.h" // for tr_evthread_init(); #include "gtest/gtest.h" #include "test-fixtures.h" using namespace std::literals; namespace libtransmission::test { class EvDnsTest : public ::testing::Test { protected: void SetUp() override { ::testing::Test::SetUp(); tr_evthread_init(); event_base_ = event_base_new(); } void TearDown() override { event_base_free(event_base_); event_base_ = nullptr; ::testing::Test::TearDown(); } static bool waitFor( struct event_base* evb, std::function const& test, std::chrono::milliseconds msec = DefaultTimeout) { auto const deadline = std::chrono::steady_clock::now() + msec; for (;;) { if (test()) { return true; } if (std::chrono::steady_clock::now() > deadline) { return false; } event_base_loop(evb, EVLOOP_ONCE); } } static auto constexpr DefaultTimeout = 5s; struct event_base* event_base_ = nullptr; }; TEST_F(EvDnsTest, canLookup) { auto dns = EvDns{ event_base_, tr_time }; auto done = false; dns.lookup( "example.com", [&done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at) { EXPECT_NE(nullptr, ai); EXPECT_GT(ailen, 0); EXPECT_GT(expires_at, tr_time()); done = true; }); waitFor(event_base_, [&done]() { return done; }); EXPECT_TRUE(done); } TEST_F(EvDnsTest, canRequestWhilePending) { auto dns = EvDns{ event_base_, tr_time }; auto n_done = size_t{ 0 }; dns.lookup( "example.com", [&n_done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at) { EXPECT_NE(nullptr, ai); EXPECT_GT(ailen, 0); EXPECT_GT(expires_at, tr_time()); ++n_done; }); dns.lookup( "example.com", [&n_done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at) { EXPECT_NE(nullptr, ai); EXPECT_GT(ailen, 0); EXPECT_GT(expires_at, tr_time()); ++n_done; }); // wait for both callbacks to be called waitFor(event_base_, [&n_done]() { return n_done >= 2U; }); EXPECT_EQ(2U, n_done); } TEST_F(EvDnsTest, canCancel) { auto dns = EvDns{ event_base_, tr_time }; auto n_done = size_t{ 0 }; static auto constexpr Name = "example.com"sv; auto tag = dns.lookup( Name, [&n_done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at) { ++n_done; // we cancelled this req, so `ai` and `ailen` should be zeroed out EXPECT_EQ(nullptr, ai); EXPECT_EQ(0, ailen); EXPECT_EQ(0, expires_at); }); dns.lookup( Name, [&n_done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at) { ++n_done; // this one did _not_ get cancelled so it should be OK EXPECT_NE(nullptr, ai); EXPECT_GT(ailen, 0); EXPECT_GT(expires_at, tr_time()); }); dns.cancel(tag); // wait for both callbacks to be called waitFor(event_base_, [&n_done]() { return n_done >= 2U; }); EXPECT_EQ(2U, n_done); } TEST_F(EvDnsTest, doesCacheEntries) { auto dns = EvDns{ event_base_, tr_time }; static auto constexpr Name = "example.com"sv; struct sockaddr const* ai_addr = nullptr; dns.lookup( Name, [&ai_addr](struct sockaddr const* ai, socklen_t ailen, time_t expires_at) { EXPECT_NE(nullptr, ai); EXPECT_GT(ailen, 0); EXPECT_GT(expires_at, tr_time()); ai_addr = ai; }); // wait for the lookup waitFor(event_base_, [&ai_addr]() { return ai_addr != nullptr; }); ASSERT_NE(nullptr, ai_addr); auto second_callback_called = false; dns.lookup( Name, [&ai_addr, &second_callback_called](struct sockaddr const* ai, socklen_t ailen, time_t expires_at) { EXPECT_NE(nullptr, ai); EXPECT_GT(ailen, 0); EXPECT_EQ(ai_addr, ai); EXPECT_GT(expires_at, tr_time()); second_callback_called = true; }); // since it's cached, the callback should have been invoked // without waiting for the event loop EXPECT_TRUE(second_callback_called); // confirm that `cached()` returns the cached value immediately auto res = dns.cached(Name); EXPECT_TRUE(res); EXPECT_EQ(ai_addr, res->first); EXPECT_GT(res->second, 0); } } // namespace libtransmission::test