Files
libtorrent/simulation/test_torrent_status.cpp
2023-08-26 11:15:27 +02:00

568 lines
15 KiB
C++

/*
Copyright (c) 2015-2018, 2020-2022, Arvid Norberg
Copyright (c) 2017, Falcosc
All rights reserved.
You may use, distribute and modify this code under the terms of the BSD license,
see LICENSE file.
*/
#include "test.hpp"
#include "setup_swarm.hpp"
#include "utils.hpp"
#include "simulator/simulator.hpp"
#include "libtorrent/alert_types.hpp"
#include "libtorrent/settings_pack.hpp"
#include "libtorrent/session.hpp"
#include <iostream>
using namespace lt;
using namespace sim;
time_point32 time_now()
{
return lt::time_point_cast<seconds32>(lt::clock_type::now());
}
template <typename Tp1, typename Tp2>
bool eq(Tp1 const lhs, Tp2 const rhs)
{
return std::abs(lt::duration_cast<seconds>(lhs - rhs).count()) <= 2;
}
// this is a test for torrent_status time counters are correct
TORRENT_TEST(status_timers)
{
lt::time_point32 start_time;
lt::torrent_handle handle;
bool ran_to_completion = false;
setup_swarm(1, swarm_test::upload
// add session
, [](lt::settings_pack&) {}
// add torrent
, [](lt::add_torrent_params&) {}
// on alert
, [&](lt::alert const* a, lt::session&) {
if (auto ta = alert_cast<add_torrent_alert>(a))
{
TEST_CHECK(!handle.is_valid());
start_time = time_now();
handle = ta->handle;
}
}
// terminate
, [&](int ticks, lt::session&) -> bool
{
// simulate 20 hours of uptime. Currently, the session_time and internal
// peer timestamps are 16 bits counting seconds, so they can only
// represent about 18 hours. The clock steps forward in 4 hour increments
// to stay within that range
if (ticks > 20 * 60 * 60)
{
ran_to_completion = true;
return true;
}
// once an hour, verify that the timers seem correct
if ((ticks % 3600) == 0)
{
lt::time_point32 const now = time_now();
// finish is 1 tick after start
auto const since_finish = duration_cast<seconds>(now - start_time);
torrent_status st = handle.status();
TEST_EQUAL(st.active_duration.count(), since_finish.count());
TEST_EQUAL(st.seeding_duration.count(), since_finish.count());
TEST_EQUAL(st.finished_duration.count(), since_finish.count());
// does not upload without peers
TEST_CHECK(st.last_upload == time_point(seconds(0)));
// does not download in seeding mode
TEST_CHECK(st.last_download == time_point(seconds(0)));
}
return false;
});
TEST_CHECK(ran_to_completion);
}
TORRENT_TEST(status_timers_last_upload)
{
bool ran_to_completion = false;
lt::torrent_handle handle;
setup_swarm(2, swarm_test::upload
// add session
, [](lt::settings_pack&) {}
// add torrent
, [](lt::add_torrent_params&) {}
// on alert
, [&](lt::alert const* a, lt::session&) {
if (auto ta = alert_cast<add_torrent_alert>(a))
{
TEST_CHECK(!handle.is_valid());
handle = ta->handle;
torrent_status st = handle.status();
// test last upload and download state before we go through
// torrent states
TEST_CHECK(st.last_upload == time_point(seconds(0)));
TEST_CHECK(st.last_download == time_point(seconds(0)));
}
}
// terminate
, [&](int ticks, lt::session&) -> bool
{
if (ticks > 10)
{
ran_to_completion = true;
return true;
}
torrent_status st = handle.status();
// upload time is 0 seconds behind now
TEST_CHECK(eq(st.last_upload, time_now()));
// does not download in seeding mode
TEST_CHECK(st.last_download == time_point(seconds(0)));
return false;
});
TEST_CHECK(ran_to_completion);
}
TORRENT_TEST(status_timers_time_shift_with_active_torrent)
{
bool ran_to_completion = false;
lt::torrent_handle handle;
seconds expected_active_duration = seconds(1);
bool tick_is_in_active_range = false;
int tick_check_intervall = 1;
setup_swarm(1, swarm_test::upload
// add session
, [](lt::settings_pack&) {}
// add torrent
, [](lt::add_torrent_params&) {}
// on alert
, [&](lt::alert const* a, lt::session&) {
if (auto ta = alert_cast<add_torrent_alert>(a))
{
TEST_CHECK(!handle.is_valid());
handle = ta->handle;
torrent_status st = handle.status();
// test last upload and download state before we go through
// torrent states
TEST_CHECK(st.last_download == time_point(seconds(0)));
TEST_CHECK(st.last_upload == time_point(seconds(0)));
}
}
// terminate
, [&](int ticks, lt::session&) -> bool
{
if(tick_is_in_active_range){
// 1 second per tick
expected_active_duration++;
}
switch(ticks)
{
case 0:
// torrent get ready for seeding on first tick, means time +1s
tick_is_in_active_range = true;
break;
case 1:
// pause after we did have the first upload tick
handle.pause();
tick_is_in_active_range = false;
break;
case 64000:
// resume just before we hit the time shift handling
// this is needed to test what happens if we want to
// shift more time then we have active time because
// we shift 4 hours and have less then 1 hour active time
handle.resume();
tick_is_in_active_range = true;
// don't check every tick
tick_check_intervall = 600;
break;
case 68000:
// simulate at least 68000 seconds because timestamps are
// 16 bits counting seconds
ran_to_completion = true;
return true;
}
// verify that the timers seem correct
if (tick_is_in_active_range && (ticks % tick_check_intervall) == 0)
{
torrent_status st = handle.status();
TEST_EQUAL(st.active_duration.count(), expected_active_duration.count());
TEST_EQUAL(st.seeding_duration.count(), expected_active_duration.count());
TEST_EQUAL(st.finished_duration.count(), expected_active_duration.count());
// does not upload without peers
TEST_CHECK(st.last_upload == time_point(seconds(0)));
// does not download in seeding mode
TEST_CHECK(st.last_download == time_point(seconds(0)));
}
return false;
});
TEST_CHECK(ran_to_completion);
}
TORRENT_TEST(finish_time_shift_active)
{
bool ran_to_completion = false;
lt::torrent_handle handle;
seconds expected_active_duration = seconds(1);
bool tick_is_in_active_range = false;
setup_swarm(1, swarm_test::upload
// add session
, [](lt::settings_pack&) {}
// add torrent
, [](lt::add_torrent_params&) {}
// on alert
, [&](lt::alert const* a, lt::session&) {
if (auto ta = alert_cast<add_torrent_alert>(a))
{
TEST_CHECK(!handle.is_valid());
handle = ta->handle;
torrent_status st = handle.status();
// test last upload and download state before we go through
// torrent states
TEST_CHECK(st.last_download == time_point(seconds(0)));
TEST_CHECK(st.last_upload == time_point(seconds(0)));
}
}
// terminate
, [&](int ticks, lt::session&) -> bool
{
if(tick_is_in_active_range){
// 1 second per tick
expected_active_duration++;
}
switch(ticks)
{
case 0:
// torrent get ready for seeding on first tick, means time +1s
tick_is_in_active_range = true;
break;
case 7000:
// pause before 4 hours to get a become finish timestamp which
// will be clamped
handle.pause();
// resume to get an become finish update
handle.resume();
tick_is_in_active_range = true;
break;
case 70000:
// simulate at least 70000 seconds because timestamps are
// 16 bits counting seconds
ran_to_completion = true;
return true;
}
// verify that the timers seem correct
if ((ticks % 3600) == 0)
{
torrent_status st = handle.status();
TEST_EQUAL(st.active_duration.count(), expected_active_duration.count());
TEST_EQUAL(st.seeding_duration.count(), expected_active_duration.count());
TEST_EQUAL(st.finished_duration.count(), expected_active_duration.count());
// does not upload without peers
TEST_CHECK(st.last_upload == time_point(seconds(0)));
// does not download in seeding mode
TEST_CHECK(st.last_download == time_point(seconds(0)));
}
return false;
});
TEST_CHECK(ran_to_completion);
}
TORRENT_TEST(finish_time_shift_paused)
{
bool ran_to_completion = false;
lt::torrent_handle handle;
seconds expected_active_duration = seconds(1);
bool tick_is_in_active_range = false;
setup_swarm(1, swarm_test::upload
// add session
, [](lt::settings_pack&) {}
// add torrent
, [](lt::add_torrent_params&) {}
// on alert
, [&](lt::alert const* a, lt::session&) {
if (auto ta = alert_cast<add_torrent_alert>(a))
{
TEST_CHECK(!handle.is_valid());
handle = ta->handle;
torrent_status st = handle.status();
// test last upload and download state before we go through
// torrent states
TEST_CHECK(st.last_upload == time_point(seconds(0)));
TEST_CHECK(st.last_download == time_point(seconds(0)));
}
}
// terminate
, [&](int ticks, lt::session&) -> bool
{
if(tick_is_in_active_range){
// 1 second per tick
expected_active_duration++;
}
switch(ticks)
{
case 0:
// torrent get ready for seeding on first tick, means time +1s
tick_is_in_active_range = true;
break;
case 7000:
// pause before 4 hours to get a become finish timestamp which
// will be clamped
handle.pause();
// resume to get an become finish update
handle.resume();
// pause to test timeshift in paused state
handle.pause();
tick_is_in_active_range = false;
break;
case 70000:
// simulate at least 70000 seconds because timestamps are
// 16 bits counting seconds
ran_to_completion = true;
return true;
}
// verify that the timers seem correct
if (tick_is_in_active_range && (ticks % 3600) == 0)
{
torrent_status st = handle.status();
TEST_EQUAL(st.active_duration.count(), expected_active_duration.count());
TEST_EQUAL(st.seeding_duration.count(), expected_active_duration.count());
TEST_EQUAL(st.finished_duration.count(), expected_active_duration.count());
// does not upload without peers
TEST_CHECK(st.last_upload == time_point(seconds(0)));
// does not download in seeding mode
TEST_CHECK(st.last_download == time_point(seconds(0)));
}
return false;
});
TEST_CHECK(ran_to_completion);
}
// This test makes sure that adding a torrent causes no torrent related alert to
// be posted _before_ the add_torrent_alert, which is expected to always be the
// first
TORRENT_TEST(alert_order)
{
#if TORRENT_ABI_VERSION == 1
bool received_torrent_add_alert = false;
#endif
bool received_add_torrent_alert = false;
int num_torrent_alerts = 0;
lt::torrent_handle handle;
setup_swarm(1, swarm_test::upload
// add session
, [](lt::settings_pack& sett) {
sett.set_int(settings_pack::alert_mask, alert_category::all);
}
// add torrent
, [](lt::add_torrent_params ) {}
// on alert
, [&](lt::alert const* a, lt::session&) {
#if TORRENT_ABI_VERSION == 1
if (alert_cast<torrent_added_alert>(a))
{
TEST_EQUAL(received_torrent_add_alert, false);
received_torrent_add_alert = true;
}
else
#endif
if (auto ta = alert_cast<add_torrent_alert>(a))
{
#if TORRENT_ABI_VERSION == 1
TEST_EQUAL(received_torrent_add_alert, true);
#endif
TEST_EQUAL(received_add_torrent_alert, false);
received_add_torrent_alert = true;
handle = ta->handle;
}
else if (dynamic_cast<torrent_log_alert const*>(a))
{
// it's acceptable to receive torrent_log_alert before
// add_torrent_alert, since they are for debugging
return;
}
else if (auto ta = dynamic_cast<torrent_alert const*>(a))
{
TEST_EQUAL(received_add_torrent_alert, true);
TEST_CHECK(handle == ta->handle);
++num_torrent_alerts;
}
}
// terminate
, [&](int ticks, lt::session&) -> bool
{ return ticks > 10; }
);
TEST_EQUAL(received_add_torrent_alert, true);
TEST_CHECK(num_torrent_alerts > 1);
}
// this tests a torrent completing the download when `active_seeds` is set to 0.
// the torrent should be paused when it completes the download
TORRENT_TEST(active_timer_no_seed)
{
lt::time_point32 start_time;
lt::torrent_handle handle;
bool ran_to_completion = false;
int active_time = 0;
setup_swarm(4, swarm_test::download
// add session
, [](lt::settings_pack& p ) {
p.set_int(settings_pack::active_seeds, 0);
p.set_bool(settings_pack::dont_count_slow_torrents, false);
}
// add torrent
, [](lt::add_torrent_params& p) {
p.flags |= torrent_flags::auto_managed;
}
// on alert
, [&](lt::alert const* a, lt::session&) {
if (auto ta = alert_cast<add_torrent_alert>(a))
{
TEST_CHECK(!handle.is_valid());
start_time = time_now();
handle = ta->handle;
}
}
// terminate
, [&](int const ticks, lt::session& ses) -> bool
{
if (!is_seed(ses))
{
++active_time;
}
else
{
// some part of the simulation is not deterministic, and causes this to vary
// between platforms/compilers
TEST_CHECK(active_time >= 6);
TEST_CHECK(active_time <= 9);
}
torrent_status st = handle.status();
TEST_EQUAL(st.active_duration.count(), active_time);
TEST_EQUAL(st.seeding_duration.count(), 0);
TEST_EQUAL(st.finished_duration.count(), 0);
// does not upload without peers
TEST_CHECK(st.last_upload == time_point(seconds(0)));
if (ticks > 2 * 60)
{
ran_to_completion = true;
return true;
}
return false;
});
TEST_CHECK(ran_to_completion);
}
template <typename PauseFun>
void test_pause(PauseFun f)
{
lt::time_point32 start_time;
lt::torrent_handle handle;
bool ran_to_completion = false;
int const pause_time = 5;
int active_time = 0;
int paused_alert_count = 0;
setup_swarm(5, swarm_test::download
// add session
, [](lt::settings_pack& p ) {}
// add torrent
, [](lt::add_torrent_params& p) {}
// on alert
, [&](lt::alert const* a, lt::session&) {
if (auto ta = alert_cast<add_torrent_alert>(a))
{
TEST_CHECK(!handle.is_valid());
start_time = time_now();
handle = ta->handle;
}
if (alert_cast<torrent_paused_alert>(a))
{
++paused_alert_count;
}
}
// terminate
, [&](int const ticks, lt::session& ses) -> bool
{
if (ticks == pause_time)
{
f(handle, ses);
}
if (ticks <= pause_time)
++active_time;
torrent_status st = handle.status();
TEST_EQUAL(st.active_duration.count(), active_time);
TEST_EQUAL(st.seeding_duration.count(), 0);
TEST_EQUAL(st.finished_duration.count(), 0);
// does not upload without peers
TEST_CHECK(st.last_upload == time_point(seconds(0)));
if (ticks > 2 * 60)
{
ran_to_completion = true;
return true;
}
return false;
});
TEST_EQUAL(paused_alert_count, 1);
TEST_CHECK(ran_to_completion);
}
TORRENT_TEST(active_timer_graceful_pause)
{
test_pause([](lt::torrent_handle h, lt::session&)
{
h.pause(torrent_handle::graceful_pause);
});
}
TORRENT_TEST(active_timer_pause)
{
test_pause([](lt::torrent_handle h, lt::session&)
{
h.pause();
});
}
TORRENT_TEST(active_timer_session_pause)
{
test_pause([](lt::torrent_handle, lt::session& s)
{
s.pause();
});
}