diff --git a/simulation/Jamfile b/simulation/Jamfile index cf1efbfcb..9c17ad1e1 100644 --- a/simulation/Jamfile +++ b/simulation/Jamfile @@ -30,5 +30,6 @@ alias libtorrent-sims : [ run test_pe_crypto.cpp ] [ run test_metadata_extension.cpp ] [ run test_trackers_extension.cpp ] + [ run test_tracker.cpp ] ; diff --git a/simulation/libsimulator b/simulation/libsimulator index a1985197c..0a91d85ac 160000 --- a/simulation/libsimulator +++ b/simulation/libsimulator @@ -1 +1 @@ -Subproject commit a1985197c925d7668922b9066c378c232ebc06c4 +Subproject commit 0a91d85ac0aa2203e71564d1cedd41a4748e60d7 diff --git a/simulation/setup_swarm.cpp b/simulation/setup_swarm.cpp index f32de092a..579dd05eb 100644 --- a/simulation/setup_swarm.cpp +++ b/simulation/setup_swarm.cpp @@ -51,8 +51,9 @@ namespace { struct swarm { - swarm(int num_nodes, swarm_setup_provider& config) - : m_config(config) + swarm(int num_nodes, sim::simulation& sim, swarm_setup_provider& config) + : m_sim(sim) + , m_config(config) , m_ios(m_sim, asio::ip::address_v4::from_string("0.0.0.0")) , m_start_time(lt::clock_type::now()) , m_timer(m_ios) @@ -94,13 +95,9 @@ struct swarm { if (ec || m_shutting_down) return; - lt::time_duration d = lt::clock_type::now() - m_start_time; - boost::uint32_t millis = lt::duration_cast(d).count(); - printf("%4d.%03d: TICK %d\n", millis / 1000, millis % 1000, m_tick); - ++m_tick; - if (m_tick > 120) + if (m_config.tick(m_tick)) { terminate(); return; @@ -190,10 +187,9 @@ struct swarm private: + sim::simulation& m_sim; swarm_setup_provider& m_config; - sim::default_config cfg; - sim::simulation m_sim{cfg}; asio::io_service m_ios; lt::time_point m_start_time; @@ -210,7 +206,14 @@ private: void setup_swarm(int num_nodes, swarm_setup_provider& cfg) { - swarm s(num_nodes, cfg); + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + setup_swarm(num_nodes, sim, cfg); +} + +void setup_swarm(int num_nodes, sim::simulation& sim, swarm_setup_provider& cfg) +{ + swarm s(num_nodes, sim, cfg); s.run(); } diff --git a/simulation/setup_swarm.hpp b/simulation/setup_swarm.hpp index fa3c23375..85a013c80 100644 --- a/simulation/setup_swarm.hpp +++ b/simulation/setup_swarm.hpp @@ -70,9 +70,14 @@ struct swarm_setup_provider // called for every session that's added virtual libtorrent::settings_pack add_session(int idx) = 0; + + // called once a second. if it returns true, the simulation is terminated + // by default, simulations end after 200 seconds + virtual bool tick(int t) { return t > 200; } }; void setup_swarm(int num_nodes, swarm_setup_provider& config); +void setup_swarm(int num_nodes, sim::simulation& sim, swarm_setup_provider& config); #endif diff --git a/simulation/test_tracker.cpp b/simulation/test_tracker.cpp new file mode 100644 index 000000000..349899ba1 --- /dev/null +++ b/simulation/test_tracker.cpp @@ -0,0 +1,266 @@ +/* + +Copyright (c) 2010, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "settings.hpp" +#include "setup_swarm.hpp" +#include "swarm_config.hpp" +#include "simulator/simulator.hpp" +#include "simulator/http_server.hpp" +#include "libtorrent/alert_types.hpp" + +using namespace libtorrent; +using namespace sim; +namespace lt = libtorrent; + +// seconds +const int duration = 10000; + +struct test_swarm_config : swarm_config +{ + test_swarm_config(int num_torrents, std::vector* announces=NULL) + : swarm_config() + , m_announces(announces) + , m_num_torrents(num_torrents) + {} + + void on_session_added(int idx, session& ses) override + { + } + + virtual libtorrent::add_torrent_params add_torrent(int idx) override + { + add_torrent_params p = swarm_config::add_torrent(idx); + + // add the tracker to the last torrent (if there's only one, it will be a + // seed from the start, if there are more than one, it will be a torrent + // that downloads the torrent and turns into a seed) + if (m_num_torrents - 1 == idx) + { + p.trackers.push_back("http://2.2.2.2:8080/announce"); + } + + return p; + } + + bool on_alert(libtorrent::alert const* alert + , int session_idx + , std::vector const& handles + , libtorrent::session& ses) override + { + if (m_announces == NULL) return false; + + char type = 0; + if (lt::alert_cast(alert)) + { + type = 'A'; + } + else if (lt::alert_cast(alert)) + { + type = 'E'; + } + else if (lt::alert_cast(alert)) + { + type = 'W'; + } + else if (lt::alert_cast(alert)) + { + type = 'R'; + } + else + { + return false; + } + + char msg[500]; + snprintf(msg, sizeof(msg), "%c: %d %s", type + , int(duration_cast(alert->timestamp().time_since_epoch()).count()) + , alert->message().c_str()); + m_announces->push_back(msg); + + return false; + } + + virtual void on_exit(std::vector const& torrents) override + { + } + + virtual bool tick(int t) override { return t > duration; } + +private: + std::vector* m_announces; + int m_num_torrents; +}; + +void test_interval(int interval) +{ + using sim::asio::ip::address_v4; + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + + lt::time_point start = lt::clock_type::now(); + + sim::asio::io_service web_server(sim, address_v4::from_string("2.2.2.2")); + // listen on port 8080 + sim::http_server http(web_server, 8080); + + // the timestamps (in seconds) of all announces + std::vector announces; + + http.register_handler("/announce" + , [&announces,interval,start](std::string method, std::string req) + { + boost::uint32_t seconds = chrono::duration_cast( + lt::clock_type::now() - start).count(); + announces.push_back(seconds); + + char response[500]; + int size = snprintf(response, sizeof(response), "d8:intervali%de5:peers0:e", interval); + return sim::send_response(200, "OK", size) + response; + }); + + std::vector announce_alerts; + test_swarm_config cfg(1, &announce_alerts); + setup_swarm(1, sim, cfg); + + int counter = 0; + for (int i = 0; i < int(announces.size()); ++i) + { + TEST_EQUAL(announces[i], counter); + counter += interval; + if (counter > duration + 1) counter = duration + 1; + } + + // TODO: verify that announce_alerts seem right as well +} + +void test_completed() +{ + using sim::asio::ip::address_v4; + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + + lt::time_point start = lt::clock_type::now(); + + sim::asio::io_service web_server(sim, address_v4::from_string("2.2.2.2")); + // listen on port 8080 + sim::http_server http(web_server, 8080); + + // the timestamps (in seconds) of all announces + std::vector announces; + + const int interval = 500; + + http.register_handler("/announce" + , [&announces,interval,start](std::string method, std::string req) + { + TEST_EQUAL(method, "GET"); + announces.push_back(req); + + char response[500]; + int size = snprintf(response, sizeof(response), "d8:intervali%de5:peers0:e", interval); + return sim::send_response(200, "OK", size) + response; + }); + + test_swarm_config cfg(2); + setup_swarm(2, sim, cfg); + + // the first announce should be event=started, the second should be + // event=completed, then all but the last should have no event and the last + // be event=stopped. + for (int i = 0; i < int(announces.size()); ++i) + { + std::string const& str = announces[i]; + const bool has_start = str.find("&event=started") + != std::string::npos; + const bool has_completed = str.find("&event=completed") + != std::string::npos; + const bool has_stopped = str.find("&event=stopped") + != std::string::npos; + + // we there can only be one event + const bool has_event = str.find("&event=") != std::string::npos; + + fprintf(stderr, "- %s\n", str.c_str()); + + TEST_EQUAL(int(has_start) + int(has_completed) + int(has_stopped) + , int(has_event)); + + switch (i) + { + case 0: + TEST_CHECK(has_start); + break; + case 1: + TEST_CHECK(has_completed); + break; + default: + if (i == int(announces.size()) - 1) + { + TEST_CHECK(has_stopped); + } + else + { + TEST_CHECK(!has_event); + } + break; + } + } +} + +TORRENT_TEST(event_completed) +{ + test_completed(); +} + +TORRENT_TEST(announce_interval_440) +{ + test_interval(440); +} + +TORRENT_TEST(announce_interval_1800) +{ + test_interval(1800); +} + +TORRENT_TEST(announce_interval_1200) +{ + test_interval(3600); +} + +// TODO: test with different queuing settings +// TODO: test when a torrent transitions from downloading to finished and +// finished to seeding +// TODO: test that left, downloaded and uploaded are reported correctly + +// TODO: test scrape + diff --git a/src/http_connection.cpp b/src/http_connection.cpp index cd033c2ad..25c20be32 100644 --- a/src/http_connection.cpp +++ b/src/http_connection.cpp @@ -432,8 +432,8 @@ void http_connection::on_timeout(boost::weak_ptr p time_point now = clock_type::now(); - if (c->m_start_time + c->m_completion_timeout < now - || c->m_last_receive + c->m_read_timeout < now) + if (c->m_start_time + c->m_completion_timeout <= now + || c->m_last_receive + c->m_read_timeout <= now) { // the connection timed out. If we have more endpoints to try, just // close this connection. The on_connect handler will try the next