2022-04-29 02:35:47 +00:00
|
|
|
// This file copyright Transmission authors and contributors.
|
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),
|
2022-04-29 02:35:47 +00:00
|
|
|
// or any future license endorsed by Mnemosyne LLC.
|
|
|
|
// License text can be found in the licenses/ folder.
|
|
|
|
|
|
|
|
#include <algorithm>
|
2023-07-08 15:24:03 +00:00
|
|
|
#include <array>
|
|
|
|
#include <cassert>
|
|
|
|
#include <cstddef> // size_t
|
|
|
|
#include <cstdint> // uint64_t
|
2022-04-29 02:35:47 +00:00
|
|
|
#include <string_view>
|
|
|
|
|
2023-07-08 15:24:03 +00:00
|
|
|
#include <fmt/core.h>
|
|
|
|
|
2023-01-02 16:23:51 +00:00
|
|
|
#include <libtransmission/transmission.h>
|
2022-04-29 02:35:47 +00:00
|
|
|
|
2023-01-02 16:23:51 +00:00
|
|
|
#include <libtransmission/error.h>
|
|
|
|
#include <libtransmission/file.h>
|
2023-07-08 15:24:03 +00:00
|
|
|
#include <libtransmission/open-files.h>
|
2023-01-02 16:23:51 +00:00
|
|
|
#include <libtransmission/tr-strbuf.h>
|
2022-04-29 02:35:47 +00:00
|
|
|
|
2023-07-08 15:24:03 +00:00
|
|
|
#include "gtest/gtest.h"
|
2022-04-29 02:35:47 +00:00
|
|
|
#include "test-fixtures.h"
|
|
|
|
|
|
|
|
using namespace std::literals;
|
|
|
|
|
|
|
|
using OpenFilesTest = libtransmission::test::SessionTest;
|
|
|
|
|
|
|
|
TEST_F(OpenFilesTest, getCachedFailsIfNotCached)
|
|
|
|
{
|
2023-11-12 03:09:23 +00:00
|
|
|
auto const fd_top = session_->openFiles().get(0, 0, false);
|
|
|
|
EXPECT_FALSE(fd_top);
|
2022-04-29 02:35:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(OpenFilesTest, getOpensIfNotCached)
|
|
|
|
{
|
|
|
|
static auto constexpr Contents = "Hello, World!\n"sv;
|
|
|
|
auto filename = tr_pathbuf{ sandboxDir(), "/test-file.txt" };
|
|
|
|
createFileWithContents(filename, Contents);
|
|
|
|
|
|
|
|
// confirm that it's not pre-cached
|
|
|
|
EXPECT_FALSE(session_->openFiles().get(0, 0, false));
|
|
|
|
|
|
|
|
// confirm that we can cache the file
|
2023-11-12 03:09:23 +00:00
|
|
|
auto fd_top = session_->openFiles().get(0, 0, false, filename, TR_PREALLOCATE_FULL, std::size(Contents));
|
|
|
|
EXPECT_TRUE(fd_top.has_value());
|
|
|
|
assert(fd_top.has_value());
|
|
|
|
auto& [fd, tag] = *fd_top;
|
|
|
|
EXPECT_NE(TR_BAD_SYS_FILE, fd);
|
2022-04-29 02:35:47 +00:00
|
|
|
|
|
|
|
// test the file contents to confirm that fd points to the right file
|
|
|
|
auto buf = std::array<char, std::size(Contents) + 1>{};
|
|
|
|
auto bytes_read = uint64_t{};
|
2023-11-12 03:09:23 +00:00
|
|
|
EXPECT_TRUE(tr_sys_file_read_at(fd, std::data(buf), std::size(Contents), 0, &bytes_read));
|
2022-05-31 23:58:20 +00:00
|
|
|
auto const contents = std::string_view{ std::data(buf), static_cast<size_t>(bytes_read) };
|
|
|
|
EXPECT_EQ(Contents, contents);
|
2022-04-29 02:35:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(OpenFilesTest, getCacheSucceedsIfCached)
|
|
|
|
{
|
|
|
|
static auto constexpr Contents = "Hello, World!\n"sv;
|
|
|
|
auto filename = tr_pathbuf{ sandboxDir(), "/test-file.txt" };
|
|
|
|
createFileWithContents(filename, Contents);
|
|
|
|
|
|
|
|
EXPECT_FALSE(session_->openFiles().get(0, 0, false));
|
|
|
|
EXPECT_TRUE(session_->openFiles().get(0, 0, false, filename, TR_PREALLOCATE_FULL, std::size(Contents)));
|
|
|
|
EXPECT_TRUE(session_->openFiles().get(0, 0, false));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(OpenFilesTest, getCachedReturnsTheSameFd)
|
|
|
|
{
|
|
|
|
static auto constexpr Contents = "Hello, World!\n"sv;
|
|
|
|
auto filename = tr_pathbuf{ sandboxDir(), "/test-file.txt" };
|
|
|
|
createFileWithContents(filename, Contents);
|
|
|
|
|
|
|
|
EXPECT_FALSE(session_->openFiles().get(0, 0, false));
|
2023-11-12 03:09:23 +00:00
|
|
|
auto const fd1_top = session_->openFiles().get(0, 0, false, filename, TR_PREALLOCATE_FULL, std::size(Contents));
|
|
|
|
auto const fd2_top = session_->openFiles().get(0, 0, false);
|
|
|
|
EXPECT_TRUE(fd1_top.has_value());
|
|
|
|
EXPECT_TRUE(fd2_top.has_value());
|
|
|
|
assert(fd1_top.has_value());
|
|
|
|
assert(fd2_top.has_value());
|
|
|
|
EXPECT_EQ(fd1_top->first, fd2_top->first);
|
2022-04-29 02:35:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(OpenFilesTest, getCachedFailsIfWrongPermissions)
|
|
|
|
{
|
|
|
|
static auto constexpr Contents = "Hello, World!\n"sv;
|
|
|
|
auto filename = tr_pathbuf{ sandboxDir(), "/test-file.txt" };
|
|
|
|
createFileWithContents(filename, Contents);
|
|
|
|
|
|
|
|
// cache it in ro mode
|
|
|
|
EXPECT_FALSE(session_->openFiles().get(0, 0, false));
|
|
|
|
EXPECT_TRUE(session_->openFiles().get(0, 0, false, filename, TR_PREALLOCATE_FULL, std::size(Contents)));
|
|
|
|
|
|
|
|
// now try to get it in r/w mode
|
|
|
|
EXPECT_TRUE(session_->openFiles().get(0, 0, false));
|
|
|
|
EXPECT_FALSE(session_->openFiles().get(0, 0, true));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(OpenFilesTest, opensInReadOnlyUnlessWritableIsRequested)
|
|
|
|
{
|
|
|
|
static auto constexpr Contents = "Hello, World!\n"sv;
|
|
|
|
auto filename = tr_pathbuf{ sandboxDir(), "/test-file.txt" };
|
|
|
|
createFileWithContents(filename, Contents);
|
|
|
|
|
|
|
|
// cache a file read-only mode
|
2023-11-12 03:09:23 +00:00
|
|
|
auto fd_top = session_->openFiles().get(0, 0, false, filename, TR_PREALLOCATE_FULL, std::size(Contents));
|
|
|
|
EXPECT_TRUE(fd_top.has_value());
|
|
|
|
assert(fd_top.has_value());
|
2022-04-29 02:35:47 +00:00
|
|
|
|
|
|
|
// confirm that writing to it fails
|
2023-11-04 16:39:41 +00:00
|
|
|
auto error = tr_error{};
|
2023-11-12 03:09:23 +00:00
|
|
|
EXPECT_FALSE(tr_sys_file_write(fd_top->first, std::data(Contents), std::size(Contents), nullptr, &error));
|
2023-11-04 16:39:41 +00:00
|
|
|
EXPECT_TRUE(error);
|
2022-04-29 02:35:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(OpenFilesTest, createsMissingFileIfWriteRequested)
|
|
|
|
{
|
|
|
|
static auto constexpr Contents = "Hello, World!\n"sv;
|
|
|
|
auto filename = tr_pathbuf{ sandboxDir(), "/test-file.txt" };
|
|
|
|
EXPECT_FALSE(tr_sys_path_exists(filename));
|
|
|
|
|
2023-11-12 03:09:23 +00:00
|
|
|
auto fd_top = session_->openFiles().get(0, 0, false);
|
|
|
|
EXPECT_FALSE(fd_top);
|
2022-04-29 02:35:47 +00:00
|
|
|
EXPECT_FALSE(tr_sys_path_exists(filename));
|
|
|
|
|
2023-11-12 03:09:23 +00:00
|
|
|
fd_top = session_->openFiles().get(0, 0, true, filename, TR_PREALLOCATE_FULL, std::size(Contents));
|
|
|
|
EXPECT_TRUE(fd_top.has_value());
|
|
|
|
assert(fd_top.has_value());
|
|
|
|
EXPECT_NE(TR_BAD_SYS_FILE, fd_top->first);
|
2022-04-29 02:35:47 +00:00
|
|
|
EXPECT_TRUE(tr_sys_path_exists(filename));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(OpenFilesTest, closeFileClosesTheFile)
|
|
|
|
{
|
|
|
|
static auto constexpr Contents = "Hello, World!\n"sv;
|
|
|
|
auto filename = tr_pathbuf{ sandboxDir(), "/test-file.txt" };
|
|
|
|
createFileWithContents(filename, Contents);
|
|
|
|
|
|
|
|
// cache a file read-only mode
|
|
|
|
EXPECT_TRUE(session_->openFiles().get(0, 0, false, filename, TR_PREALLOCATE_FULL, std::size(Contents)));
|
|
|
|
EXPECT_TRUE(session_->openFiles().get(0, 0, false));
|
|
|
|
|
|
|
|
// close the file
|
2023-05-06 04:11:05 +00:00
|
|
|
session_->openFiles().close_file(0, 0);
|
2022-04-29 02:35:47 +00:00
|
|
|
|
|
|
|
// confirm that its fd is no longer cached
|
|
|
|
EXPECT_FALSE(session_->openFiles().get(0, 0, false));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(OpenFilesTest, closeTorrentClosesTheTorrentFiles)
|
|
|
|
{
|
|
|
|
static auto constexpr Contents = "Hello, World!\n"sv;
|
|
|
|
static auto constexpr TorId = tr_torrent_id_t{ 0 };
|
|
|
|
|
|
|
|
auto filename = tr_pathbuf{ sandboxDir(), "/a.txt" };
|
|
|
|
createFileWithContents(filename, Contents);
|
|
|
|
EXPECT_TRUE(session_->openFiles().get(TorId, 1, false, filename, TR_PREALLOCATE_FULL, std::size(Contents)));
|
|
|
|
|
|
|
|
filename.assign(sandboxDir(), "/b.txt");
|
|
|
|
createFileWithContents(filename, Contents);
|
|
|
|
EXPECT_TRUE(session_->openFiles().get(TorId, 3, false, filename, TR_PREALLOCATE_FULL, std::size(Contents)));
|
|
|
|
|
|
|
|
// confirm that closing a different torrent does not affect these files
|
2023-05-06 04:11:05 +00:00
|
|
|
session_->openFiles().close_torrent(TorId + 1);
|
2022-04-29 02:35:47 +00:00
|
|
|
EXPECT_TRUE(session_->openFiles().get(TorId, 1, false));
|
|
|
|
EXPECT_TRUE(session_->openFiles().get(TorId, 3, false));
|
|
|
|
|
|
|
|
// confirm that closing this torrent closes and uncaches the files
|
2023-05-06 04:11:05 +00:00
|
|
|
session_->openFiles().close_torrent(TorId);
|
2022-04-29 02:35:47 +00:00
|
|
|
EXPECT_FALSE(session_->openFiles().get(TorId, 1, false));
|
|
|
|
EXPECT_FALSE(session_->openFiles().get(TorId, 3, false));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(OpenFilesTest, closesLeastRecentlyUsedFile)
|
|
|
|
{
|
|
|
|
static auto constexpr Contents = "Hello, World!\n"sv;
|
|
|
|
static auto constexpr TorId = tr_torrent_id_t{ 0 };
|
|
|
|
static auto constexpr LargerThanCacheLimit = 100;
|
|
|
|
|
|
|
|
// Walk through a number of files. Confirm that they all succeed
|
|
|
|
// even when the number exhausts the cache size, and newer files
|
|
|
|
// supplant older ones.
|
|
|
|
for (int i = 0; i < LargerThanCacheLimit; ++i)
|
|
|
|
{
|
2022-07-23 07:13:18 +00:00
|
|
|
auto filename = tr_pathbuf{ sandboxDir(), fmt::format("/file-{:d}.txt"sv, i) };
|
2022-04-29 02:35:47 +00:00
|
|
|
EXPECT_TRUE(session_->openFiles().get(TorId, i, true, filename, TR_PREALLOCATE_FULL, std::size(Contents)));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do a lookup-only for the files again *in the same order*. By following the
|
|
|
|
// order, the first files we check will be the oldest from the last pass and
|
|
|
|
// should have aged out. So we should have a nonzero number of failures; but
|
|
|
|
// once we get a success, all the remaining should also succeed.
|
|
|
|
auto results = std::array<bool, LargerThanCacheLimit>{};
|
|
|
|
auto sorted = std::array<bool, LargerThanCacheLimit>{};
|
|
|
|
for (int i = 0; i < LargerThanCacheLimit; ++i)
|
|
|
|
{
|
2022-07-23 07:13:18 +00:00
|
|
|
auto filename = tr_pathbuf{ sandboxDir(), fmt::format("/file-{:d}.txt"sv, i) };
|
2023-11-12 03:09:23 +00:00
|
|
|
results[i] = static_cast<bool>(session_->openFiles().get(TorId, i, false));
|
2022-04-29 02:35:47 +00:00
|
|
|
}
|
|
|
|
sorted = results;
|
|
|
|
std::sort(std::begin(sorted), std::end(sorted));
|
|
|
|
EXPECT_EQ(sorted, results);
|
|
|
|
EXPECT_GT(std::count(std::begin(results), std::end(results), true), 0);
|
|
|
|
}
|