Files
transmission/tests/libtransmission/move-test.cc
Charles Kerr 849cd0ecea refactor: remove write cache (#8669)
* refactor: mark cache_size_mib as deprecated in RPC

* refactor: make RPC session_set cache_size_mib a no-op

* refactor: tr_ioRead(), tr_ioWrite() now take a std::span

* refactor: add BufferReader::to_buf(std::span<uint8_t>)

* refactor: migrate some methods to to_buf(std::span<uint8_t>)

* refactor: remove the write memory cache

* chore: simplify recalculate_hash()

* refactor: simplify span use in tr_peerMsgsImpl::read_piece_data()
2026-03-11 07:16:39 -05:00

200 lines
6.1 KiB
C++

// This file Copyright (C) 2013-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 <algorithm>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <gtest/gtest.h>
#include <libtransmission/transmission.h>
#include <libtransmission/block-info.h>
#include <libtransmission/file.h> // tr_sys_path_*()
#include <libtransmission/inout.h>
#include <libtransmission/quark.h>
#include <libtransmission/torrent-files.h>
#include <libtransmission/torrent.h>
#include <libtransmission/tr-strbuf.h>
#include <libtransmission/variant.h>
#include "test-fixtures.h"
using namespace std::literals;
namespace tr::test
{
auto constexpr MaxWaitMsec = 5000;
class IncompleteDirTest
: public SessionTest
, public ::testing::WithParamInterface<std::pair<std::string, std::string>>
{
protected:
void SetUp() override
{
if (auto* map = settings()->get_if<tr_variant::Map>(); map != nullptr)
{
auto const download_dir = GetParam().second;
map->insert_or_assign(TR_KEY_download_dir, download_dir);
auto const incomplete_dir = GetParam().first;
map->insert_or_assign(TR_KEY_incomplete_dir, incomplete_dir);
map->insert_or_assign(TR_KEY_incomplete_dir_enabled, true);
}
SessionTest::SetUp();
}
static auto constexpr MaxWaitMsec = 3000;
};
TEST_P(IncompleteDirTest, incompleteDir)
{
std::string const download_dir = tr_sessionGetDownloadDir(session_);
std::string const incomplete_dir = tr_sessionGetIncompleteDir(session_);
// init an incomplete torrent.
// the test zero_torrent will be missing its first piece.
tr_sessionSetIncompleteFileNamingEnabled(session_, true);
auto* const tor = zeroTorrentInit(ZeroTorrentState::Partial);
auto path = tr_pathbuf{};
path.assign(incomplete_dir, '/', tr_torrentFile(tor, 0).name, tr_torrent_files::PartialFileSuffix);
EXPECT_EQ(path, tr_torrentFindFile(tor, 0));
path.assign(incomplete_dir, '/', tr_torrentFile(tor, 1).name);
EXPECT_EQ(path, tr_torrentFindFile(tor, 1));
EXPECT_EQ(tor->piece_size(), tr_torrentStat(tor).left_until_done);
auto completeness = TR_LEECH;
tr_sessionSetCompletenessCallback(
session_,
[&completeness](tr_torrent_id_t const /*tor_id*/, tr_completeness const c, bool const /*was_running*/) noexcept
{ completeness = c; });
struct TestIncompleteDirData
{
tr_session* session = {};
tr_torrent* tor = {};
tr_block_index_t block = {};
tr_piece_index_t pieceIndex = {};
std::vector<uint8_t> buf;
bool done = {};
};
auto const test_incomplete_dir_threadfunc = [](TestIncompleteDirData* data) noexcept
{
auto& tor = *data->tor;
if (tr_ioWrite(tor, tor.block_loc(data->block), data->buf) == 0)
{
data->tor->on_block_received(data->block);
}
data->done = true;
};
// now finish writing it
{
auto data = TestIncompleteDirData{};
data.session = session_;
data.tor = tor;
auto const [begin, end] = tor->block_span_for_piece(data.pieceIndex);
for (tr_block_index_t block_index = begin; block_index < end; ++block_index)
{
data.buf.resize(tr_block_info::BlockSize);
std::ranges::fill(data.buf, '\0');
data.block = block_index;
data.done = false;
session_->run_in_session_thread(test_incomplete_dir_threadfunc, &data);
auto const test = [&data]()
{
return data.done;
};
EXPECT_TRUE(waitFor(test, MaxWaitMsec));
}
}
blockingTorrentVerify(tor);
EXPECT_EQ(0, tr_torrentStat(tor).left_until_done);
auto test = [&completeness]()
{
return completeness != TR_LEECH;
};
EXPECT_TRUE(waitFor(test, MaxWaitMsec));
EXPECT_EQ(TR_SEED, completeness);
auto const n = tr_torrentFileCount(tor);
for (tr_file_index_t i = 0; i < n; ++i)
{
auto const expected = tr_pathbuf{ download_dir, '/', tr_torrentFile(tor, i).name };
EXPECT_EQ(expected, tr_torrentFindFile(tor, i));
}
// cleanup
tr_torrentRemove(tor, true);
}
INSTANTIATE_TEST_SUITE_P(
IncompleteDir,
IncompleteDirTest,
::testing::Values(
// what happens when incompleteDir is a subdir of downloadDir
std::make_pair(std::string{ "Downloads/Incomplete" }, std::string{ "Downloads" }),
// test what happens when downloadDir is a subdir of incompleteDir
std::make_pair(std::string{ "Downloads" }, std::string{ "Downloads/Complete" }),
// test what happens when downloadDir and incompleteDir are siblings
std::make_pair(std::string{ "Incomplete" }, std::string{ "Downloads" })));
/***
****
***/
using MoveTest = SessionTest;
TEST_F(MoveTest, setLocation)
{
auto const target_dir = tr_pathbuf{ session_->configDir(), "/target"sv };
tr_sys_dir_create(target_dir, TR_SYS_DIR_CREATE_PARENTS, 0777, nullptr);
// init a torrent.
auto* const tor = zeroTorrentInit(ZeroTorrentState::Complete);
blockingTorrentVerify(tor);
EXPECT_EQ(0, tr_torrentStat(tor).left_until_done);
// now move it
auto state = -1;
tr_torrentSetLocation(tor, target_dir, true, &state);
auto test = [&state]()
{
return state == TR_LOC_DONE;
};
EXPECT_TRUE(waitFor(test, MaxWaitMsec));
EXPECT_EQ(TR_LOC_DONE, state);
// confirm the torrent is still complete after being moved
blockingTorrentVerify(tor);
EXPECT_EQ(0, tr_torrentStat(tor).left_until_done);
// confirm the files really got moved
sync();
auto const n = tr_torrentFileCount(tor);
for (tr_file_index_t i = 0; i < n; ++i)
{
auto const expected = tr_pathbuf{ target_dir, '/', tr_torrentFile(tor, i).name };
EXPECT_EQ(expected, tr_torrentFindFile(tor, i));
}
// cleanup
tr_torrentRemove(tor, true);
}
} // namespace tr::test