transmission/tests/libtransmission/watchdir-test.cc

271 lines
7.6 KiB
C++
Raw Normal View History

// This file Copyright (C) 2015-2022 Mnemosyne LLC.
2022-08-08 18:05:39 +00:00
// 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.
2022-08-10 13:34:51 +00:00
#include <chrono>
#include <cstdint>
#include <memory>
2023-07-08 15:24:03 +00:00
#include <set>
#include <string>
2023-07-08 15:24:03 +00:00
#include <string_view>
#include <utility>
2022-08-10 13:34:51 +00:00
#include <vector>
2023-07-08 15:24:03 +00:00
#ifdef _WIN32
#include <ws2tcpip.h>
#else
#include <sys/time.h> // timeval
#endif
#include <event2/event.h>
2023-07-08 15:24:03 +00:00
#define LIBTRANSMISSION_WATCHDIR_MODULE
#include <libtransmission/file.h>
2023-07-08 15:24:03 +00:00
#include <libtransmission/timer.h>
#include <libtransmission/timer-ev.h>
#include <libtransmission/tr-macros.h>
#include <libtransmission/tr-strbuf.h>
#include <libtransmission/utils.h>
#include <libtransmission/watchdir.h>
#include <libtransmission/watchdir-base.h>
2023-07-08 15:24:03 +00:00
#include "gtest/gtest.h"
#include "test-fixtures.h"
2022-08-10 13:34:51 +00:00
using namespace std::literals;
/***
****
***/
2022-08-10 13:34:51 +00:00
static auto constexpr GenericRescanInterval = 100ms;
static auto constexpr RetryDuration = 100ms;
2022-08-10 13:34:51 +00:00
// should be at least 2x the watchdir-generic size to ensure that
// we have time to pump all events at least once in processEvents()
static auto constexpr ProcessEventsTimeout = 300ms;
static_assert(ProcessEventsTimeout > GenericRescanInterval);
namespace libtransmission::test
{
enum class WatchMode : uint8_t
{
NATIVE,
GENERIC
};
class WatchDirTest
: public SandboxedTest
, public ::testing::WithParamInterface<WatchMode>
{
private:
std::shared_ptr<struct event_base> ev_base_;
2022-08-10 13:34:51 +00:00
std::unique_ptr<libtransmission::TimerMaker> timer_maker_;
std::unique_ptr<tr_net_init_mgr> init_mgr_;
protected:
void SetUp() override
{
SandboxedTest::SetUp();
init_mgr_ = tr_lib_init();
ev_base_.reset(event_base_new(), event_base_free);
2022-08-10 13:34:51 +00:00
timer_maker_ = std::make_unique<libtransmission::EvTimerMaker>(ev_base_.get());
Watchdir::set_generic_rescan_interval(GenericRescanInterval);
}
void TearDown() override
{
ev_base_.reset();
SandboxedTest::TearDown();
}
2022-08-10 13:34:51 +00:00
auto createWatchDir(std::string_view path, Watchdir::Callback callback)
{
auto const force_generic = GetParam() == WatchMode::GENERIC;
2022-08-10 13:34:51 +00:00
auto watchdir = force_generic ?
Watchdir::create_generic(path, std::move(callback), *timer_maker_, GenericRescanInterval) :
2022-08-10 13:34:51 +00:00
Watchdir::create(path, std::move(callback), *timer_maker_, ev_base_.get());
if (auto* const base_watchdir = dynamic_cast<impl::BaseWatchdir*>(watchdir.get()); base_watchdir != nullptr)
{
base_watchdir->setRetryDuration(RetryDuration);
}
2022-08-10 13:34:51 +00:00
return watchdir;
}
2022-08-10 13:34:51 +00:00
void createFile(std::string_view dirname, std::string_view basename, std::string_view contents = ""sv)
{
2022-08-10 13:34:51 +00:00
createFileWithContents(tr_pathbuf{ dirname, '/', basename }, contents);
}
2022-08-10 13:34:51 +00:00
static std::string createDir(std::string_view dirname, std::string_view basename)
{
auto path = fmt::format("{:s}/{:s}", dirname, basename);
tr_sys_dir_create(path, 0, 0700);
return path;
}
2022-08-10 13:34:51 +00:00
void processEvents(std::chrono::milliseconds wait_interval = ProcessEventsTimeout)
{
2022-08-10 13:34:51 +00:00
auto tv = timeval{};
auto const seconds = std::chrono::duration_cast<std::chrono::seconds>(wait_interval);
tv.tv_sec = static_cast<decltype(tv.tv_sec)>(seconds.count());
2022-08-10 13:34:51 +00:00
wait_interval -= seconds;
auto const usec = std::chrono::duration_cast<std::chrono::microseconds>(wait_interval);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec.count());
2022-08-10 13:34:51 +00:00
event_base_loopexit(ev_base_.get(), &tv);
event_base_dispatch(ev_base_.get());
}
};
TEST_P(WatchDirTest, construct)
{
auto const path = sandboxDir();
2022-08-10 13:34:51 +00:00
auto callback = [](std::string_view /*dirname*/, std::string_view /*basename*/)
{
return Watchdir::Action::Done;
};
auto watchdir = createWatchDir(path, callback);
EXPECT_TRUE(watchdir);
EXPECT_EQ(path, watchdir->dirname());
processEvents();
}
TEST_P(WatchDirTest, initialScan)
{
auto const path = sandboxDir();
// setup: start with an empty directory.
// this block confirms that it's empty
{
fix: more clang-tidy warnings (#6608) * fix: readability-redundant-casting warnings in gtk * fix: bugprone-move-forwarding-reference warnings in gtk * fix: readability-redundant-casting warnings in qt * fix: bugprone-switch-missing-default-case warnings in qt * fix: readability-use-std-min-max warning in qt client * fix: readability-static-accessed-through-instance warning in qt client * fix: cppcoreguidelines-avoid-const-or-ref-data-members warning in qt client * fix: readability-avoid-nested-conditional-operator warning in qt client * fixup! fix: readability-use-std-min-max warning in qt client * fix: readability-redundant-member-init warnings in gtk client * fix: performance-avoid-endl warnings in gtk client * chore: disable readability-qualified-auto too many false warnings * chore: disable cppcoreguidelines-avoid-const-or-ref-data-members * chore: fix readability-duplicate-include warning in gtk client * chore: fix modernize-use-nodiscard warning in gtk client * chore: fix readability-convert-member-functions-to-static warning in gtk client * fixup! fix: bugprone-move-forwarding-reference warnings in gtk * chore: fix performance-enum-size warning in gtk client * fix: cppcoreguidelines-prefer-member-initializer warning in gtk client * fix: readability-identifier-naming warning in qt client * Revert "chore: fix performance-enum-size warning in gtk client" This reverts commit 5ce6b562f849c2499fa34f4d903234f1945f9c3e. * fix: readability-redundant-member-init warning in move tests * fix: readability-redundant-casting warnings in tests * fixup! fix: readability-identifier-naming warning in qt client * fixup! fix: readability-avoid-nested-conditional-operator warning in qt client * fix: readability-static-accessed-through-instance warning in qt client * fix: readability-redundant-casting warning in watchdir tests
2024-02-17 19:31:49 +00:00
auto called = false;
2022-08-10 13:34:51 +00:00
auto callback = [&called](std::string_view /*dirname*/, std::string_view /*basename*/)
{
called = true;
return Watchdir::Action::Done;
};
auto watchdir = createWatchDir(path, callback);
EXPECT_TRUE(watchdir);
processEvents();
2022-08-10 13:34:51 +00:00
EXPECT_FALSE(called);
}
// add a file
2022-08-10 13:34:51 +00:00
auto const base_name = "test.txt"sv;
createFile(path, base_name);
// confirm that a wd will pick up the file that
// was created before the wd was instantiated
{
2022-08-10 13:34:51 +00:00
auto names = std::set<std::string>{};
auto callback = [&names](std::string_view /*dirname*/, std::string_view basename)
{
names.insert(std::string{ basename });
return Watchdir::Action::Done;
};
auto watchdir = createWatchDir(path, callback);
EXPECT_TRUE(watchdir);
processEvents();
2022-08-10 13:34:51 +00:00
EXPECT_EQ(1U, std::size(names));
EXPECT_EQ(base_name, *names.begin());
}
}
TEST_P(WatchDirTest, watch)
{
2022-08-10 13:34:51 +00:00
auto const dirname = sandboxDir();
// create a new watchdir and confirm it's empty
2022-08-10 13:34:51 +00:00
auto names = std::vector<std::string>{};
auto callback = [&names](std::string_view /*dirname*/, std::string_view basename)
{
names.emplace_back(basename);
2022-08-10 13:34:51 +00:00
return Watchdir::Action::Done;
};
auto watchdir = createWatchDir(dirname, callback);
processEvents();
2022-08-10 13:34:51 +00:00
EXPECT_TRUE(watchdir);
EXPECT_TRUE(std::empty(names));
// test that a new file in an empty directory shows up
2022-08-10 13:34:51 +00:00
auto const file1 = "test1"sv;
createFile(dirname, file1);
processEvents();
2022-08-10 13:34:51 +00:00
EXPECT_EQ(1U, std::size(names));
if (!std::empty(names))
{
EXPECT_EQ(file1, names.front());
}
// test that a new file in a nonempty directory shows up
2022-08-10 13:34:51 +00:00
names.clear();
auto const file2 = "test2"sv;
createFile(dirname, file2);
processEvents();
processEvents();
2022-08-10 13:34:51 +00:00
EXPECT_EQ(1U, std::size(names));
if (!std::empty(names))
{
EXPECT_EQ(file2, names.front());
}
2022-08-10 13:34:51 +00:00
// test that folders don't trigger the callback
names.clear();
createDir(dirname, "test3"sv);
processEvents();
2022-08-10 13:34:51 +00:00
EXPECT_TRUE(std::empty(names));
}
TEST_P(WatchDirTest, DISABLED_retry)
{
auto const path = sandboxDir();
// test setup:
// Start watching the test directory.
2022-08-10 13:34:51 +00:00
// Create a file and return 'retry' back to the watchdir code from our callback.
// This should cause the wd to wait a bit and try again.
auto names = std::vector<std::string>{};
auto callback = [&names](std::string_view /*dirname*/, std::string_view basename)
{
names.emplace_back(basename);
2022-08-10 13:34:51 +00:00
return Watchdir::Action::Retry;
};
auto watchdir = createWatchDir(path, callback);
auto constexpr FastRetryWaitTime = 20ms;
2022-09-22 20:42:06 +00:00
auto constexpr SlowRetryWaitTime = 200ms;
auto* const base_watchdir = dynamic_cast<impl::BaseWatchdir*>(watchdir.get());
ASSERT_TRUE(base_watchdir != nullptr);
base_watchdir->setRetryDuration(FastRetryWaitTime);
2022-09-22 20:42:06 +00:00
processEvents(SlowRetryWaitTime);
2022-08-10 13:34:51 +00:00
EXPECT_EQ(0U, std::size(names));
2022-08-10 13:34:51 +00:00
auto const test_file = "test.txt"sv;
createFile(path, test_file);
2022-09-22 20:42:06 +00:00
processEvents(SlowRetryWaitTime);
2022-08-11 19:30:00 +00:00
EXPECT_LE(2U, std::size(names));
2022-08-10 13:34:51 +00:00
for (auto const& name : names)
{
EXPECT_EQ(test_file, name);
}
}
INSTANTIATE_TEST_SUITE_P( //
WatchDir,
WatchDirTest,
::testing::Values( //
WatchMode::NATIVE,
WatchMode::GENERIC));
} // namespace libtransmission::test