/* Copyright (c) 2015-2022, Arvid Norberg Copyright (c) 2017, Antoine Dahan All rights reserved. You may use, distribute and modify this code under the terms of the BSD license, see LICENSE file. */ #include "setup_swarm.hpp" #include "test.hpp" #include "utils.hpp" #include "libtorrent/alert.hpp" #include "libtorrent/alert_types.hpp" #include "libtorrent/session.hpp" #include "libtorrent/session_stats.hpp" #include "libtorrent/aux_/path.hpp" #include "libtorrent/aux_/random.hpp" #include "libtorrent/torrent_info.hpp" #include "libtorrent/time.hpp" #include "settings.hpp" #include "setup_transfer.hpp" // for ep() #include "fake_peer.hpp" #include "simulator/nat.hpp" #include "simulator/queue.hpp" #include "utils.hpp" #include using namespace lt; TORRENT_TEST(seed_mode) { // with seed mode setup_swarm(3, swarm_test::upload // add session , [](lt::settings_pack&) {} // add torrent , [](lt::add_torrent_params& params) { params.flags |= torrent_flags::seed_mode; } // on alert , [](lt::alert const*, lt::session&) {} // terminate , [](int, lt::session&) -> bool { return false; }); } TORRENT_TEST(seed_mode_disable_hash_checks) { // all nodes need to disable hash checking, otherwise the downloader would // just fail settings_pack swarm_settings = settings(); swarm_settings.set_bool(settings_pack::disable_hash_checks, true); dsl_config network_cfg; sim::simulation sim{network_cfg}; // with seed mode setup_swarm(2, swarm_test::upload, sim, swarm_settings, add_torrent_params() // add session , [](lt::settings_pack& pack) { pack.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); } // add torrent , [](lt::add_torrent_params& params) { params.flags |= torrent_flags::seed_mode; // just to make sure the disable_hash_checks really work, we // shouldn't be verifying anything from the storage // params.storage = disabled_storage_constructor; } // on alert , [](lt::alert const*, lt::session&) {} // terminate , [](int, lt::session&) -> bool { return false; }); } TORRENT_TEST(seed_mode_suggest) { setup_swarm(2, swarm_test::upload // add session , [](lt::settings_pack& pack) { pack.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); #if TORRENT_ABI_VERSION == 1 pack.set_int(settings_pack::cache_size, 2); #endif } // add torrent , [](lt::add_torrent_params& params) { params.flags |= torrent_flags::seed_mode; } // on alert , [](lt::alert const*, lt::session&) {} // terminate , [](int, lt::session&) -> bool { return true; }); } TORRENT_TEST(plain) { setup_swarm(2, swarm_test::download // add session , [](lt::settings_pack&) {} // add torrent , [](lt::add_torrent_params&) {} // on alert , [](lt::alert const*, lt::session&) {} // terminate , [](int const ticks, lt::session& ses) -> bool { if (ticks > 80) { TEST_ERROR("timeout"); return true; } if (!is_seed(ses)) return false; std::printf("completed in %d ticks\n", ticks); return true; }); } TORRENT_TEST(session_stats) { std::vector stats = session_stats_metrics(); int const downloading_idx = find_metric_idx("ses.num_downloading_torrents"); TEST_CHECK(downloading_idx >= 0); int const incoming_extended_idx = find_metric_idx("ses.num_incoming_extended"); TEST_CHECK(incoming_extended_idx >= 0); setup_swarm(2, swarm_test::download // add session , [](lt::settings_pack&) {} // add torrent , [](lt::add_torrent_params&) {} // on alert , [=](lt::alert const* a, lt::session&) { auto const* ss = lt::alert_cast(a); if (!ss) return; // there's one downloading torrent TEST_EQUAL(ss->counters()[downloading_idx], 1); TEST_EQUAL(ss->counters()[incoming_extended_idx], 1); } // terminate , [](int const ticks, lt::session& ses) -> bool { ses.post_session_stats(); if (ticks > 80) { TEST_ERROR("timeout"); return true; } if (!is_seed(ses)) return false; std::printf("completed in %d ticks\n", ticks); return true; }); } // this test relies on picking up log alerts #ifndef TORRENT_DISABLE_LOGGING TORRENT_TEST(suggest) { int num_suggests = 0; setup_swarm(10, swarm_test::upload // add session , [](lt::settings_pack& pack) { pack.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); pack.set_int(settings_pack::max_suggest_pieces, 10); #if TORRENT_ABI_VERSION == 1 pack.set_int(settings_pack::cache_size, 2); #endif } // add torrent , [](lt::add_torrent_params&) {} // on alert , [&num_suggests](lt::alert const* a, lt::session&) { if (auto pl = alert_cast(a)) { if (pl->direction == peer_log_alert::outgoing_message && pl->event_type == peer_log_alert::suggest_piece) { ++num_suggests; } } } // terminate , [](int const ticks, lt::session&) -> bool { if (ticks > 500) { return true; } return false; }); // for now, just make sure we send any suggests at all. This feature is // experimental and it's not entirely clear it's correct or how to verify // that it does what it's supposed to do. // perhaps a better way would be to look at piece upload distribution over // time TEST_CHECK(num_suggests > 0); } #endif TORRENT_TEST(utp_only) { setup_swarm(2, swarm_test::download // add session , [](lt::settings_pack& pack) { pack.set_bool(settings_pack::enable_incoming_utp, true); pack.set_bool(settings_pack::enable_outgoing_utp, true); pack.set_bool(settings_pack::enable_incoming_tcp, false); pack.set_bool(settings_pack::enable_outgoing_tcp, false); } // add torrent , [](lt::add_torrent_params&) {} // on alert , [](lt::alert const*, lt::session&) {} // terminate , [](int const ticks, lt::session& ses) -> bool { if (ticks > 80) { TEST_ERROR("timeout"); return true; } if (!is_seed(ses)) return false; return true; }); } void test_stop_start_download(swarm_test_t type, bool graceful) { bool paused_once = false; bool resumed = false; setup_swarm(3, type // add session , [](lt::settings_pack& pack) { // this test will pause and resume the torrent immediately, we expect // to reconnect immediately too, so disable the min reconnect time // limit. pack.set_int(settings_pack::min_reconnect_time, 0); } // add torrent , [](lt::add_torrent_params&) { } // on alert , [&](lt::alert const* a, lt::session& ses) { if (lt::alert_cast(a)) add_extra_peers(ses); if (auto tp = lt::alert_cast(a)) { TEST_EQUAL(resumed, false); std::printf("\nSTART\n\n"); tp->handle.resume(); resumed = true; } } // terminate , [&](int const ticks, lt::session& ses) -> bool { if (paused_once == false) { auto st = get_status(ses); const bool limit_reached = (type & swarm_test::download) ? st.total_wanted_done > st.total_wanted / 2 : st.total_payload_upload >= 3 * 16 * 1024; if (limit_reached) { std::printf("\nSTOP\n\n"); auto h = ses.get_torrents()[0]; h.pause(graceful ? torrent_handle::graceful_pause : pause_flags_t{}); paused_once = true; } } std::printf("tick: %d\n", ticks); const int timeout = (type & swarm_test::download) ? 22 : 100; if (ticks > timeout) { TEST_ERROR("timeout"); return true; } if (type & swarm_test::upload) return false; if (!is_seed(ses)) return false; std::printf("completed in %d ticks\n", ticks); return true; }); TEST_EQUAL(paused_once, true); TEST_EQUAL(resumed, true); } TORRENT_TEST(stop_start_download) { test_stop_start_download(swarm_test::download, false); } TORRENT_TEST(stop_start_download_graceful) { test_stop_start_download(swarm_test::download, true); } TORRENT_TEST(stop_start_download_graceful_no_peers) { bool paused_once = false; bool resumed = false; setup_swarm(1, swarm_test::download // add session , [](lt::settings_pack&) {} // add torrent , [](lt::add_torrent_params&) {} // on alert , [&](lt::alert const* a, lt::session&) { if (auto tp = lt::alert_cast(a)) { TEST_EQUAL(resumed, false); std::printf("\nSTART\n\n"); tp->handle.resume(); resumed = true; } } // terminate , [&](int const ticks, lt::session& ses) -> bool { if (paused_once == false && ticks == 6) { std::printf("\nSTOP\n\n"); auto h = ses.get_torrents()[0]; h.pause(torrent_handle::graceful_pause); paused_once = true; } std::printf("tick: %d\n", ticks); // when there's only one node (i.e. no peers) we won't ever download // the torrent. It's just a test to make sure we still get the // torrent_paused_alert return ticks > 60; }); TEST_EQUAL(paused_once, true); TEST_EQUAL(resumed, true); } TORRENT_TEST(stop_start_seed) { test_stop_start_download(swarm_test::upload, false); } TORRENT_TEST(stop_start_seed_graceful) { test_stop_start_download(swarm_test::upload, true); } TORRENT_TEST(shutdown) { setup_swarm(4, swarm_test::download // add session , [](lt::settings_pack&) {} // add torrent , [](lt::add_torrent_params&) {} // on alert , [](lt::alert const*, lt::session&) {} // terminate , [](int, lt::session& ses) -> bool { if (completed_pieces(ses) == 0) return false; TEST_EQUAL(is_seed(ses), false); return true; }); } // make the delays on the connections unreasonable long, so libtorrent times-out // the connection attempts struct timeout_config : sim::default_config { virtual sim::route incoming_route(lt::address ip) override { auto it = m_incoming.find(ip); if (it != m_incoming.end()) return sim::route().append(it->second); it = m_incoming.insert(it, std::make_pair(ip, std::make_shared( m_sim->get_io_context() , 1000 , lt::duration_cast(seconds(10)) , 1000, "packet-loss modem in"))); return sim::route().append(it->second); } virtual sim::route outgoing_route(lt::address ip) override { auto it = m_outgoing.find(ip); if (it != m_outgoing.end()) return sim::route().append(it->second); it = m_outgoing.insert(it, std::make_pair(ip, std::make_shared( m_sim->get_io_context(), 1000 , lt::duration_cast(seconds(5)), 200 * 1000, "packet-loss out"))); return sim::route().append(it->second); } }; // make sure peers that are no longer alive are handled correctly. TORRENT_TEST(dead_peers) { int num_connect_timeout = 0; timeout_config network_cfg; sim::simulation sim{network_cfg}; setup_swarm(1, swarm_test::download, sim // add session , [](lt::settings_pack& p) { p.set_int(settings_pack::peer_connect_timeout, 1); } // add torrent , [](lt::add_torrent_params& params) { params.peers.assign({ ep("66.66.66.60", 9999) , ep("66.66.66.61", 9999) , ep("66.66.66.62", 9999) }); } // on alert , [&](lt::alert const* a, lt::session&) { auto* e = alert_cast(a); if (e && e->op == operation_t::connect && e->error == error_code(errors::timed_out)) { ++num_connect_timeout; } } // terminate , [](int t, lt::session&) -> bool { return t > 100; }); TEST_EQUAL(num_connect_timeout, 3); } // the address 50.0.0.1 sits behind a NAT. All of its outgoing connections have // their source address rewritten to 51.51.51.51 struct nat_config : sim::default_config { nat_config() : m_nat_hop(std::make_shared(addr("51.51.51.51"))) {} sim::route outgoing_route(lt::address ip) override { // This is extremely simplistic. It will simply alter the perceived source // IP of the connecting client. sim::route r; if (ip == addr("50.0.0.1")) r.append(m_nat_hop); return r; } std::shared_ptr m_nat_hop; }; TORRENT_TEST(self_connect) { int num_self_connection_disconnects = 0; nat_config network_cfg; sim::simulation sim{network_cfg}; setup_swarm(1, swarm_test::download, sim // add session , [](lt::settings_pack& p) { p.set_bool(settings_pack::enable_incoming_utp, false); p.set_bool(settings_pack::enable_outgoing_utp, false); } // add torrent , [](lt::add_torrent_params& params) { // this is our own address and listen port, just to make sure we get // ourself as a peer (which normally happens one way or another in the // wild) params.peers.assign({ep("50.0.0.1", 6881)}); } // on alert , [&](lt::alert const* a, lt::session&) { auto* e = alert_cast(a); if (e && e->op == operation_t::bittorrent && e->error == error_code(errors::self_connection)) { ++num_self_connection_disconnects; } } // terminate , [](int t, lt::session&) -> bool { return t > 100; }); TEST_EQUAL(num_self_connection_disconnects, 1); } TORRENT_TEST(delete_files) { std::string save_path; setup_swarm(2, swarm_test::download // add session , [](lt::settings_pack&) {} // add torrent , [](lt::add_torrent_params&) {} // on alert , [](lt::alert const*, lt::session&) {} // terminate , [&save_path](int, lt::session& ses) -> bool { if (completed_pieces(ses) == 0) return false; auto h = ses.get_torrents()[0]; save_path = h.status().save_path; ses.remove_torrent(h, session::delete_files); return true; }); // assert the file is no longer there file_status st; error_code ec; stat_file(combine_path(save_path, "temporary"), &st, ec); std::printf("expecting \"%s/temporary\" to NOT exist [%s | %s]\n" , save_path.c_str() , ec.category().name() , ec.message().c_str()); TEST_EQUAL(ec, error_code(boost::system::errc::no_such_file_or_directory, system_category())); } TORRENT_TEST(delete_partfile) { std::string save_path; setup_swarm(2, swarm_test::download | swarm_test::real_disk // add session , [](lt::settings_pack&) {} // add torrent , [](lt::add_torrent_params&) {} // on alert , [](lt::alert const*, lt::session&) {} // terminate , [&save_path](int, lt::session& ses) -> bool { if (completed_pieces(ses) == 0) return false; auto h = ses.get_torrents()[0]; save_path = h.status().save_path; ses.remove_torrent(h, session::delete_partfile); return true; }); // assert the file *is* still there file_status st; error_code ec; stat_file(combine_path(save_path, "temporary"), &st, ec); std::printf("expecting \"%s/temporary\" to exist [%s]\n", save_path.c_str() , ec.message().c_str()); TEST_CHECK(!ec); } TORRENT_TEST(torrent_completed_alert) { int num_file_completed = false; setup_swarm(2, swarm_test::download // add session , [](lt::settings_pack& pack) { pack.set_int(lt::settings_pack::alert_mask, alert_category::file_progress); } // add torrent , [](lt::add_torrent_params&) {} // on alert , [&](lt::alert const* a, lt::session&) { auto tc = alert_cast(a); if (tc == nullptr) return; ++num_file_completed; } // terminate , [](int ticks, lt::session& ses) -> bool { if (ticks > 80) { TEST_ERROR("timeout"); return true; } if (!is_seed(ses)) return false; printf("completed in %d ticks\n", ticks); return true; }); TEST_EQUAL(num_file_completed, 1); } TORRENT_TEST(block_uploaded_alert) { // blocks[piece count][number of blocks per piece] (each block's element will // be set to true when a block_uploaded_alert alert is received for that block) std::vector> blocks; setup_swarm(2, swarm_test::upload // add session , [](lt::settings_pack& pack) { pack.set_int(lt::settings_pack::alert_mask, alert_category::upload | alert_category::status); } // add torrent , [](lt::add_torrent_params&) {} // on alert , [&](lt::alert const* a, lt::session&) { if (auto at = lt::alert_cast(a)) { // init blocks vector, MUST happen before any block_uploaded_alert alerts int blocks_per_piece = at->handle.torrent_file()->piece_length() / 0x4000; blocks.resize(at->handle.torrent_file()->num_pieces(), std::vector(blocks_per_piece, false)); } else if (auto ua = lt::alert_cast(a)) { TEST_EQUAL(blocks[static_cast(ua->piece_index)][ua->block_index], false); blocks[static_cast(ua->piece_index)][ua->block_index] = true; } } // terminate , [](int, lt::session&) -> bool { return false; }); // ensure a block_uploaded_alert was received for each block in the torrent TEST_CHECK(std::all_of(blocks.begin(), blocks.end(), [](std::vector const& piece_row) { return std::all_of(piece_row.begin(), piece_row.end(), [](bool upload_alert_received) { return upload_alert_received; } ); } )); } // template for testing running swarms with edge case settings template void test_settings(SettingsFun fun) { setup_swarm(2, swarm_test::download // add session , fun // add torrent , [](lt::add_torrent_params&) {} // on alert , [](lt::alert const*, lt::session&) {} // terminate , [](int ticks, lt::session& ses) -> bool { if (ticks > 89) { TEST_ERROR("timeout"); return true; } if (!is_seed(ses)) return false; return true; }); } TORRENT_TEST(unlimited_connections) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::connections_limit, std::numeric_limits::max()); } ); } TORRENT_TEST(default_connections_limit) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::connections_limit, 0); } ); } TORRENT_TEST(default_connections_limit_negative) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::connections_limit, -1); } ); } TORRENT_TEST(redundant_have) { test_settings([](lt::settings_pack& pack) { pack.set_bool(settings_pack::send_redundant_have, false); } ); } #if TORRENT_ABI_VERSION == 1 TORRENT_TEST(lazy_bitfields) { test_settings([](lt::settings_pack& pack) { pack.set_bool(settings_pack::lazy_bitfields, true); } ); } #endif TORRENT_TEST(prioritize_partial_pieces) { test_settings([](lt::settings_pack& pack) { pack.set_bool(settings_pack::prioritize_partial_pieces, true); } ); } TORRENT_TEST(active_downloads) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::active_downloads, std::numeric_limits::max()); } ); } TORRENT_TEST(active_seeds) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::active_seeds, std::numeric_limits::max()); } ); } TORRENT_TEST(active_seeds_negative) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::active_seeds, -1); } ); } TORRENT_TEST(active_limit) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::active_limit, std::numeric_limits::max()); } ); } TORRENT_TEST(active_limit_negative) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::active_limit, -1); } ); } TORRENT_TEST(upload_rate_limit) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::upload_rate_limit, std::numeric_limits::max()); } ); } TORRENT_TEST(upload_rate_limit_negative) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::upload_rate_limit, -1); } ); } TORRENT_TEST(download_rate_limit) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::download_rate_limit, std::numeric_limits::max()); } ); } TORRENT_TEST(download_rate_limit_negative) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::download_rate_limit, -1); } ); } TORRENT_TEST(unchoke_slots_limit) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::unchoke_slots_limit, std::numeric_limits::max()); } ); } TORRENT_TEST(unchoke_slots_limit_negative) { test_settings([](lt::settings_pack& pack) { pack.set_int(settings_pack::unchoke_slots_limit, -1); pack.set_int(settings_pack::choking_algorithm, settings_pack::fixed_slots_choker); }); } TORRENT_TEST(settings_stress_test) { std::array const settings{{ settings_pack::unchoke_slots_limit, settings_pack::connections_limit, settings_pack::predictive_piece_announce, settings_pack::allow_multiple_connections_per_ip, settings_pack::send_redundant_have, settings_pack::rate_limit_ip_overhead, settings_pack::rate_limit_ip_overhead, settings_pack::anonymous_mode, // settings_pack::enable_upnp, // settings_pack::enable_natpmp, settings_pack::enable_lsd, settings_pack::enable_ip_notifier, settings_pack::piece_extent_affinity, }}; std::array const values{{-1, 0, 1, std::numeric_limits::max()}}; for (auto t : { swarm_test::download, swarm_test::upload}) { for (auto s1 : settings) { for (auto s2 : settings) { if (s1 == s2) continue; setup_swarm(2, t // add session , [](lt::settings_pack& p) { p.set_int(settings_pack::choking_algorithm, settings_pack::fixed_slots_choker); } // add torrent , [](lt::add_torrent_params& params) {} // on alert , [](lt::alert const*, lt::session&) {} // terminate , [&](int tick, lt::session& session) -> bool { int const s = (tick & 1) ? s2 : s1; settings_pack p; if ((s & settings_pack::type_mask) == settings_pack::bool_type_base) p.set_bool(s, bool(tick & 2)); else p.set_int(s, values[(tick >> 1) % values.size()]); session.apply_settings(std::move(p)); return tick > int(settings.size() * values.size() * 2); }); } } } } TORRENT_TEST(pex) { // we create 3 nodes. Node 0 seeds and node 1 and 2 are downloaders. // node 0 is initially only connected to node 1. The test ensures that node // 0 eventually is connected to node 2, as it should have been introduced // via PEX dsl_config network_cfg; sim::simulation sim{network_cfg}; asio::io_context ios(sim); lt::time_point start_time(lt::clock_type::now()); std::vector> nodes; std::vector> io_service; std::vector zombies; lt::aux::deadline_timer timer(ios); lt::error_code ec; int const swarm_id = unit_test::test_counter(); std::string path = save_path(swarm_id, 0); lt::create_directory(path, ec); if (ec) std::printf("failed to create directory: \"%s\": %s\n" , path.c_str(), ec.message().c_str()); std::ofstream file(lt::combine_path(path, "temporary").c_str()); lt::add_torrent_params p = ::create_torrent(&file, "temporary", 0x4000, 50, false); file.close(); int const num_nodes = 3; bool done = false; // session 0 is the seeding one. // the IPs are 50.0.0.1, 50.0.0.2 and 50.0.0.3 for (int i = 0; i < num_nodes; ++i) { // create a new io_service char ep[30]; std::snprintf(ep, sizeof(ep), "50.0.%d.%d", (i + 1) >> 8, (i + 1) & 0xff); io_service.push_back(std::make_shared(sim, addr(ep))); lt::settings_pack pack = settings(); // make sure the sessions have different peer ids lt::peer_id pid; lt::aux::random_bytes(pid); pack.set_str(lt::settings_pack::peer_fingerprint, pid.to_string()); std::shared_ptr ses = std::make_shared(pack, *io_service.back()); nodes.push_back(ses); p.flags &= ~lt::torrent_flags::paused; p.flags &= ~lt::torrent_flags::auto_managed; // node 0 and 1 are downloaders and node 2 is a seed // save path 0 is where the files are, so that's for seeds // It's important that node 1 and 2 want to stay connected, otherwise // node 1 won't be able to gossip about 2 to 0. p.save_path = save_path(swarm_id, i > 1 ? 0 : 1); ses->async_add_torrent(p); ses->set_alert_notify([&, i]() { // this function is called inside libtorrent and we cannot perform work // immediately in it. We have to notify the outside to pull all the alerts post(*io_service[i], [&,i]() { lt::session* ses = nodes[i].get(); // when shutting down, we may have destructed the session if (ses == nullptr) return; std::vector alerts; ses->pop_alerts(&alerts); for (lt::alert* a : alerts) { // only print alerts from the session under test lt::time_duration d = a->timestamp() - start_time; std::uint32_t const millis = std::uint32_t( lt::duration_cast(d).count()); if (i == 0) { std::printf("%4u.%03u: %-25s %s\n" , millis / 1000, millis % 1000 , a->what() , a->message().c_str()); } // if a torrent was added save the torrent handle if (lt::add_torrent_alert* at = lt::alert_cast(a)) { lt::torrent_handle h = at->handle; if (i == 0) { // node only connects to node 1 h.connect_peer(lt::tcp::endpoint(addr("50.0.0.2"), 6881)); } else { // other nodes connect to each other for (int k = 1; k < num_nodes; ++k) { char ep[30]; std::snprintf(ep, sizeof(ep), "50.0.%d.%d" , (k + 1) >> 8, (k + 1) & 0xff); h.connect_peer(lt::tcp::endpoint(addr(ep), 6881)); } } } if (i == 0) { // if node 0 was connected to 50.0.0.3, we're done if (lt::peer_connect_alert* ca = lt::alert_cast(a)) { if (auto i = std::get_if(&ca->ep)) { if (i->address() == addr("50.0.0.3")) done = true; } } if (lt::incoming_connection_alert* ca = lt::alert_cast(a)) { if (ca->endpoint.address() == addr("50.0.0.3")) done = true; } } } }); }); } std::function on_done = [&](lt::error_code const& ec) { if (ec) return; std::printf("TERMINATING\n"); // terminate simulation for (int i = 0; i < int(nodes.size()); ++i) { zombies.push_back(nodes[i]->abort()); nodes[i].reset(); } }; timer.expires_after(lt::seconds(65)); timer.async_wait(on_done); sim.run(); TEST_EQUAL(done, true); } // TODO: add test that makes sure a torrent in graceful pause mode won't make // outgoing connections // TODO: add test that makes sure a torrent in graceful pause mode won't accept // incoming connections // TODO: test the different storage allocation modes // TODO: test contiguous buffer