Skip to content

Commit

Permalink
Add test download_dual_torrents
Browse files Browse the repository at this point in the history
  • Loading branch information
Zitrax committed Sep 17, 2023
1 parent 1a0ef63 commit 2576041
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 36 deletions.
7 changes: 4 additions & 3 deletions src/torrent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,13 @@ auto random_string(std::size_t len) -> std::string {

// Could possibly be grouped in a struct
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
Torrent::Torrent(const filesystem::path& file,
Torrent::Torrent(filesystem::path file,
std::filesystem::path data_dir,
const Config& config,
HttpGet http_get)
: m_config(config),
m_data_dir(std::move(data_dir)),
m_torrent_file(std::move(file)),
m_peer_id(random_string(20)),
m_listening_port(
numeric_cast<unsigned short>(config.get(IntSetting::LISTENING_PORT),
Expand All @@ -124,7 +125,7 @@ Torrent::Torrent(const filesystem::path& file,
numeric_cast<unsigned short>(config.get(IntSetting::CONNECTION_PORT),
"connection port out of range")),
m_http_get(std::move(http_get)) {
auto root = bencode::decode(read_file(file));
auto root = bencode::decode(read_file(m_torrent_file));

const auto& root_dict = root->to<TypedElement<BeDict>>()->val();

Expand All @@ -143,7 +144,7 @@ Torrent::Torrent(const filesystem::path& file,
m_name = (m_data_dir / info.at("name")->to<TypedElement<string>>()->val())
.string();
m_tmpfile = m_name + Torrent::tmpfileExtension();
logger()->debug("Using tmpfile {} for {}", m_tmpfile, file);
logger()->debug("Using tmpfile {} for {}", m_tmpfile, m_torrent_file);
auto pieces = info.at("pieces")->to<TypedElement<string>>()->val();
if (pieces.size() % 20) {
throw runtime_error("Unexpected pieces length");
Expand Down
8 changes: 7 additions & 1 deletion src/torrent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class Torrent {
* @param http_get a http request getter function
*/
explicit Torrent(
const std::filesystem::path& file,
std::filesystem::path file,
std::filesystem::path data_dir = "",
const Config& config = SingletonDirectoryFileConfig::getInstance(),
HttpGet http_get = [](const Url& url) { return Net::httpGet(url); });
Expand Down Expand Up @@ -204,6 +204,11 @@ class Torrent {
*/
[[nodiscard]] auto tmpfile() const { return m_tmpfile; }

/**
* The .torrent file used to create this torrent.
*/
[[nodiscard]] auto torrent_file() const { return m_torrent_file; }

/**
* The suffix of a file used during transfer (before completion)
*/
Expand Down Expand Up @@ -387,6 +392,7 @@ class Torrent {
Sha1 m_info_hash{};
std::filesystem::path m_tmpfile{};
std::filesystem::path m_data_dir{};
std::filesystem::path m_torrent_file{};
std::vector<PieceCallback> m_piece_callbacks{};
DisconnectCallback m_disconnect_callback{};
std::string m_peer_id{};
Expand Down
158 changes: 126 additions & 32 deletions tests/test_integrate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class TestConfig : public zit::Config {
// we need to make sure we initiate the connections.
m_bool_settings[zit::BoolSetting::INITIATE_PEER_CONNECTIONS] = true;
}

void set(IntSetting setting, int val) { m_int_settings[setting] = val; }
};

/**
Expand Down Expand Up @@ -80,6 +82,7 @@ class Process {
cerr << "Failed launching " << argv[0] << ": " << strerror(errno) << endl;
exit(1);
} else {
logger()->trace("Started '{}'", fmt::join(argv, " "));
// Slight delay to verify that process did not immediately die
// for more clear and faster error response.
this_thread::sleep_for(200ms);
Expand Down Expand Up @@ -128,7 +131,9 @@ class Process {
logger()->info("{} still not dead, sending SIGKILL", m_name);
kill(m_pid, SIGKILL);
waitpid(m_pid, &status, 0);
logger()->info("{} exited with status: {}", m_name, WEXITSTATUS(status));
const auto fstatus = WEXITSTATUS(status);
logger()->log(fstatus ? spdlog::level::warn : spdlog::level::info,
"{} exited with status: {}", m_name, fstatus);
}
m_pid = 0;
}
Expand Down Expand Up @@ -163,7 +168,9 @@ fs::path home_dir() {
return {home};
}

auto start_seeder(const fs::path& data_dir, const fs::path& torrent_file) {
auto start_seeder(const fs::path& data_dir,
const fs::path& torrent_file,
const std::string& name = "seeder") {
// FIXME: A downside of Transmission is that it refuses to connect to
// localhost addresses which make it trickier to test properly. We either have
// to find a way to ensure that Zit and Transmission operate on different IP
Expand All @@ -173,9 +180,13 @@ auto start_seeder(const fs::path& data_dir, const fs::path& torrent_file) {
// give us the correct coverage. KTorrent is a tool that does seem to work
// nicely with localhost addresses, but it seems to be GUI only :(

// Ensure to start each transmission instance on different ports
static int transmission_port = 51413;

fs::remove_all(home_dir() / ".config/transmission");
return Process("leecher", {"transmission-cli", "-w", data_dir.c_str(),
torrent_file.c_str()});
return Process(name, {"transmission-cli", "--download-dir", data_dir.c_str(),
"--port", std::to_string(transmission_port++).c_str(),
torrent_file.c_str()});
}

/**
Expand All @@ -187,43 +198,65 @@ auto start_seeder(const fs::path& data_dir, const fs::path& torrent_file) {
*/
auto start_leecher(const fs::path& target, const fs::path& torrent_file) {
fs::remove(target);
return start_seeder(target, torrent_file);
return start_seeder(target, torrent_file, "leecher");
}

void start(zit::Torrent& torrent) {
torrent.start();
torrent.run();
}

auto download(const fs::path& data_dir,
const fs::path& torrent_file,
zit::Torrent& torrent,
void download(const fs::path& data_dir,
std::vector<std::reference_wrapper<zit::Torrent>>& torrents,
uint8_t number_of_seeders) {
logger()->info("Starting {} seeders...", number_of_seeders);
zit::FileWriterThread file_writer([](zit::Torrent& torrent) {
logger()->info("Download completed of " + torrent.name());
torrent.stop();
});

vector<Process> seeders;
for (int i = 0; i < number_of_seeders; ++i) {
seeders.emplace_back(start_seeder(data_dir, torrent_file));

for (auto torrent_ref : torrents) {
auto& torrent = torrent_ref.get();
logger()->info("Starting {} seeders...", number_of_seeders);
const auto torrent_file = torrent.torrent_file();
for (int i = 0; i < number_of_seeders; ++i) {
seeders.emplace_back(start_seeder(data_dir, torrent_file));
}

// Ensure we do not already have it
if (torrent.is_single_file()) {
fs::remove(torrent.name());
}
file_writer.register_torrent(torrent);
}

// Allow some time for the seeders to start
// FIXME: Avoid this sleep
this_thread::sleep_for(15s);

// Download torrent with zit
auto target = torrent.name();

// Ensure we do not already have it
if (torrent.is_single_file()) {
fs::remove(target);
std::vector<std::thread> torrent_threads;
std::ranges::transform(
torrents, std::back_inserter(torrent_threads), [](auto& torrent) {
return std::thread([&]() {
try {
start(torrent);
} catch (const std::exception& ex) {
zit::logger()->error("Exception in thread: {}", ex.what());
}
});
});

for (auto& torrent_thread : torrent_threads) {
torrent_thread.join();
}
}

zit::FileWriterThread file_writer([&torrent](zit::Torrent&) {
logger()->info("Download completed");
torrent.stop();
});
file_writer.register_torrent(torrent);
start(torrent);
return target;
void download(const fs::path& data_dir,
zit::Torrent& torrent,
uint8_t number_of_seeders) {
std::vector<std::reference_wrapper<zit::Torrent>> torrents{torrent};
download(data_dir, torrents, number_of_seeders);
}

} // namespace
Expand All @@ -246,7 +279,8 @@ TEST_P(IntegrateF, DISABLED_download) {
TestConfig test_config;
zit::Torrent torrent(torrent_file, download_dir, test_config);
ASSERT_FALSE(torrent.done());
auto target = download(data_dir, torrent_file, torrent, number_of_seeders);
download(data_dir, {torrent}, number_of_seeders);
const auto target = torrent.name();

// Transfer done - Verify content
auto source = data_dir / "1MiB.dat";
Expand Down Expand Up @@ -289,7 +323,8 @@ TEST_F(IntegrateOodF, DISABLED_download_ood) {
ASSERT_LT(available(), 2 * torrent.length());

ASSERT_FALSE(torrent.done());
auto target = download(data_dir, torrent_file, torrent, number_of_seeders);
download(data_dir, torrent, number_of_seeders);
const auto target = torrent.name();

// Transfer done - Verify content
auto source = data_dir / "1MiB.dat";
Expand All @@ -300,6 +335,63 @@ TEST_F(IntegrateOodF, DISABLED_download_ood) {

using Integrate = TestWithTmpDir;

// Verify we can download two torrents at the same time
#ifdef INTEGRATION_TESTS
TEST_F(IntegrateF, download_dual_torrents) {
#else
TEST_F(IntegrateF, DISABLED_download_dual_torrents) {
#endif // INTEGRATION_TESTS
auto tracker = start_tracker();

const auto data_dir = fs::path(DATA_DIR);
constexpr uint8_t number_of_seeders = 1;
const auto download_dir = tmp_dir();
TestConfig test_config;

const auto torrent_file_1 = data_dir / "1MiB.torrent";
const auto torrent_file_2 = data_dir / "multi.torrent";

zit::Torrent torrent_1(torrent_file_1, download_dir, test_config);
// FIXME: This is not what we want in the end. Without this we currently
// try to start multiple listeners on the same port which will
// not work. One port per torrent does not scale.
test_config.set(IntSetting::LISTENING_PORT, 20002);
zit::Torrent torrent_2(torrent_file_2, download_dir, test_config);

ASSERT_FALSE(torrent_1.done());
ASSERT_FALSE(torrent_2.done());

std::vector<std::reference_wrapper<zit::Torrent>> torrents{torrent_1,
torrent_2};
download(data_dir, torrents, number_of_seeders);

// Transfer done

// Verify content of torrent_1
{
const auto target = torrent_1.name();
auto source = data_dir / "1MiB.dat";
auto source_sha1 = zit::Sha1::calculateFile(source).hex();
auto target_sha1 = zit::Sha1::calculateFile(target).hex();
EXPECT_EQ(source_sha1, target_sha1);
}

// Verify content of torrent_2
{
const auto name = torrent_2.name();
for (const auto& fi : torrent_2.files()) {
auto source = data_dir / name / fi.path();
auto dst = name / fi.path();
auto source_sha1 = zit::Sha1::calculateFile(source).hex();
auto target_sha1 = zit::Sha1::calculateFile(dst).hex();
EXPECT_EQ(source_sha1, target_sha1) << fi.path();
// Delete downloaded file
fs::remove(dst);
}
fs::remove(name);
}
}

// This test verifies that we can resume a download
// where one piece is missing
#ifdef INTEGRATION_TESTS
Expand Down Expand Up @@ -331,7 +423,8 @@ TEST_P(IntegrateF, DISABLED_download_part) {
torrent.add_piece_callback(
[&pieces_downloaded](auto, auto) { pieces_downloaded++; });

auto target = download(data_dir, torrent_file, torrent, 1);
download(data_dir, torrent, 1);
const auto target = torrent.name();

// Transfer done - Verify content
auto source = data_dir / "1MiB.dat";
Expand Down Expand Up @@ -375,7 +468,8 @@ TEST_F(Integrate, DISABLED_download_multi_part) {
torrent.add_piece_callback(
[&pieces_downloaded](auto, auto) { pieces_downloaded++; });

auto target = download(data_dir, torrent_file, torrent, 1);
download(data_dir, torrent, 1);
const auto target = torrent.name();

// Transfer done - Verify content
const auto name = torrent.name();
Expand All @@ -396,21 +490,21 @@ TEST_F(Integrate, DISABLED_download_multi_part) {
}

#ifdef INTEGRATION_TESTS
TEST_F(Integrate, download_multi) {
TEST_F(Integrate, download_multi_file) {
#else
TEST_F(Integrate, DISABLED_download_multi) {
TEST_F(Integrate, DISABLED_download_multi_file) {
#endif // INTEGRATION_TESTS
auto tracker = start_tracker();

const auto data_dir = fs::path(DATA_DIR);
const auto torrent_file = data_dir / "multi.torrent";
// const uint8_t max = GetParam();

const auto download_dir = tmp_dir();
TestConfig test_config;
zit::Torrent torrent(torrent_file, download_dir, test_config);
ASSERT_FALSE(torrent.done());
auto target = download(data_dir, torrent_file, torrent, 1);
download(data_dir, torrent, 1);
const auto target = torrent.name();

// Transfer done - Verify content
const auto name = torrent.name();
Expand Down

0 comments on commit 2576041

Please sign in to comment.