chore: initial sanitized public snapshot

This commit is contained in:
Aria2 Rust Pro Contributors
2026-07-18 14:16:57 +08:00
commit 17688c3e34
321 changed files with 76859 additions and 0 deletions
@@ -0,0 +1,170 @@
#![expect(
missing_docs,
reason = "integration test scenarios are documented by case names rather than item-level rustdoc"
)]
use std::{
fs,
path::{Path, PathBuf},
time::{SystemTime, UNIX_EPOCH},
};
use aria2_rust_pro_cli::{
BtStatusReport, Invocation, TransferSelection, execute_runtime, execute_runtime_with_downloader,
};
use aria2_rust_pro_compat as _;
use aria2_rust_pro_core as _;
use aria2_rust_pro_protocol::FixtureHttpDownloader;
use aria2_rust_pro_storage as _;
use aria2_rust_pro_tests as _;
use criterion as _;
#[path = "../test_support/support.rs"]
mod support;
fn unique_temp_path(stem: &str) -> PathBuf {
let unique = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time should be after unix epoch")
.as_nanos();
std::env::temp_dir().join(format!("aria2-rust-pro-tests-{stem}-{unique}"))
}
fn write_config(path: &Path, download_dir: &Path) {
fs::write(path, format!("dir={}", download_dir.display())).expect("config should write");
}
fn assert_bt_snapshot(
bt: &BtStatusReport,
expected_metadata_only: bool,
expected_total_length: Option<u64>,
) {
assert_eq!(bt.is_bt, Some(true));
assert_eq!(bt.metadata_only, Some(expected_metadata_only));
assert_eq!(bt.share_time, Some(0));
assert_eq!(bt.share_ratio.as_deref(), Some("0.000"));
assert_eq!(bt.share_ratio_progress.as_deref(), Some("0.000"));
assert_eq!(bt.share_ratio_remaining.as_deref(), Some("0.000"));
assert!(
bt.announce_list_tier_count.unwrap_or_default() >= 1,
"bt snapshot should retain at least one announce tier"
);
if !expected_metadata_only {
assert!(
bt.magnet_uri
.as_deref()
.is_some_and(|uri| uri.starts_with("magnet:?xt=urn:btih:"))
);
assert_eq!(expected_total_length, Some(32_768));
}
}
#[test]
fn foreground_magnet_runtime_report_keeps_bt_snapshot_visible() {
let report = execute_runtime(Invocation::Run {
config_path: None,
uris: vec![String::from(
"magnet:?xt=urn:btih:00112233445566778899aabbccddeeff00112233&dn=dht-metadata.iso&tr=http%3A%2F%2Ftracker.example.org%2Fannounce&tr=udp%3A%2F%2Ftracker.example.org%3A6969",
)],
})
.expect("runtime should execute for a bt magnet");
assert_eq!(report.accepted_uri_count, 1);
assert_eq!(report.tracked_download_count, 1);
assert_eq!(report.completed_download_count, 0);
assert_eq!(report.transfer_kinds, vec![TransferSelection::Magnet]);
assert_eq!(report.recognized_schemes, vec![String::from("magnet")]);
assert_eq!(report.first_total_length, Some(0));
assert_eq!(report.first_completed_length, Some(0));
let bt = report
.first_bt_status
.as_ref()
.expect("magnet foreground execution should surface a bt snapshot");
assert_bt_snapshot(bt, true, report.first_total_length);
assert_eq!(bt.num_seeders, Some(0));
}
#[test]
fn foreground_local_torrent_file_runs_as_bt_session_not_plain_uri() {
let temp_root = unique_temp_path("local-torrent");
let download_dir = temp_root.join("downloads");
let torrent_path = temp_root.join("fixture.torrent");
let config_path = temp_root.join("aria2.conf");
fs::create_dir_all(&download_dir).expect("download directory should create");
support::write_torrent_fixture(&torrent_path);
write_config(&config_path, &download_dir);
let report = execute_runtime_with_downloader(
Invocation::Run {
config_path: Some(config_path),
uris: vec![torrent_path.to_string_lossy().into_owned()],
},
&FixtureHttpDownloader::new(),
)
.expect("runtime should accept a local torrent input");
assert_eq!(report.accepted_uri_count, 1);
assert_eq!(report.tracked_download_count, 1);
assert_eq!(report.completed_download_count, 0);
assert_eq!(report.transfer_kinds, vec![TransferSelection::Torrent]);
assert_eq!(report.first_total_length, Some(32_768));
assert_eq!(report.first_completed_length, Some(0));
let bt = report
.first_bt_status
.as_ref()
.expect("local torrent foreground execution should expose bt status");
assert_bt_snapshot(bt, false, report.first_total_length);
assert!(
!download_dir.join("fixture.torrent").exists(),
"torrent metainfo should bootstrap a bt session instead of being persisted as the final download artifact"
);
let _ = fs::remove_dir_all(&temp_root);
}
#[test]
fn foreground_remote_torrent_url_bootstraps_bt_session_instead_of_saving_metainfo_payload() {
let temp_root = unique_temp_path("remote-torrent");
let download_dir = temp_root.join("downloads");
let config_path = temp_root.join("aria2.conf");
fs::create_dir_all(&download_dir).expect("download directory should create");
write_config(&config_path, &download_dir);
let torrent_url = "https://tracker.example.org/files/ubuntu.torrent";
let mut downloader = FixtureHttpDownloader::new();
downloader.register(torrent_url, support::BT_TORRENT_FIXTURE_BYTES);
let report = execute_runtime_with_downloader(
Invocation::Run {
config_path: Some(config_path),
uris: vec![torrent_url.to_owned()],
},
&downloader,
)
.expect("runtime should accept a remote torrent url");
assert_eq!(report.accepted_uri_count, 1);
assert_eq!(report.tracked_download_count, 1);
assert_eq!(report.completed_download_count, 0);
assert_eq!(report.transfer_kinds, vec![TransferSelection::Torrent]);
assert_eq!(report.recognized_schemes, vec![String::from("https")]);
assert_eq!(report.first_total_length, Some(32_768));
assert_eq!(report.first_completed_length, Some(0));
let bt = report
.first_bt_status
.as_ref()
.expect("remote torrent bootstrap should expose bt status");
assert_bt_snapshot(bt, false, report.first_total_length);
assert!(
!download_dir.join("ubuntu.torrent").exists(),
"remote torrent bootstrap should not leave the .torrent payload behind as the downloaded artifact"
);
let _ = fs::remove_dir_all(&temp_root);
}
@@ -0,0 +1,168 @@
#![expect(
missing_docs,
reason = "integration test scenarios are documented by case names rather than item-level rustdoc"
)]
use std::collections::BTreeMap;
use aria2_rust_pro_cli as _;
use aria2_rust_pro_compat as _;
use aria2_rust_pro_core as _;
use aria2_rust_pro_protocol::{
DhtMessageModel, DhtNodeModel,
torrent::{PeerWireExtensionHandshakeModel, PeerWireMessageKind, PeerWireMetadataMessageModel},
};
use aria2_rust_pro_rpc::{InProcessRpcDispatcher, RpcMethod, RpcValue};
use aria2_rust_pro_storage as _;
use aria2_rust_pro_tests as _;
use criterion as _;
#[path = "../test_support/support.rs"]
mod support;
fn tell_status(dispatcher: &mut InProcessRpcDispatcher, gid: &str) -> BTreeMap<String, RpcValue> {
support::rpc_result_object(dispatcher.dispatch_json(support::rpc_request(
RpcMethod::Aria2TellStatus,
vec![RpcValue::String(gid.to_owned())],
)))
}
fn get_files(dispatcher: &mut InProcessRpcDispatcher, gid: &str) -> Vec<RpcValue> {
support::rpc_result_array(dispatcher.dispatch_json(support::rpc_request(
RpcMethod::Aria2GetFiles,
vec![RpcValue::String(gid.to_owned())],
)))
}
fn first_file<'a>(files: &'a [RpcValue], context: &str) -> &'a BTreeMap<String, RpcValue> {
match files.first() {
Some(RpcValue::Object(file)) => file,
other => panic!("unexpected first file payload for {context}: {other:?}"),
}
}
fn seed_peer_and_promote_metadata(
dispatcher: &mut InProcessRpcDispatcher,
gid: &str,
info_hash_hex: &str,
) {
dispatcher
.apply_dht_get_peers_result(
gid,
&DhtNodeModel {
node_id: String::new(),
address: "203.0.113.77".to_owned(),
port: 51413,
},
&DhtMessageModel::get_peers_response(
b"gp".to_vec(),
vec![0x55; 20],
Some(b"dht-token".to_vec()),
None,
vec![support::compact_peer([198, 51, 100, 42], 51415)],
),
)
.expect("dht get_peers should seed a connectable peer");
let connector = support::FakePeerWireConnector::new(support::peer_wire_handshake_and_frames(
support::decode_hex_20(info_hash_hex),
*b"-AZ2060-META-PROMO01",
&[
PeerWireMessageKind::Unchoke,
PeerWireMessageKind::Extension(
PeerWireExtensionHandshakeModel {
extensions: BTreeMap::from([("ut_metadata".to_owned(), 3_u8)]),
client_name: Some("libtorrent/2.0.11".to_owned()),
metadata_size: Some(
u32::try_from(support::BT_TORRENT_FIXTURE_BYTES.len())
.expect("fixture metadata length should fit u32"),
),
request_queue: Some(64),
}
.to_peer_wire_message(),
),
PeerWireMessageKind::Extension(
PeerWireMetadataMessageModel::data(
0,
u32::try_from(support::BT_TORRENT_FIXTURE_BYTES.len())
.expect("fixture metadata length should fit u32"),
support::BT_TORRENT_FIXTURE_BYTES.to_vec(),
)
.to_peer_wire_message(3),
),
],
));
dispatcher
.execute_peer_wire_exchange(gid, &connector)
.expect("peer-wire metadata exchange should promote the magnet session");
}
#[test]
fn magnet_piece_observation_clears_metadata_only_and_pending_snapshot_flags() {
let mut dispatcher = InProcessRpcDispatcher::new();
let reference_gid = support::add_torrent(&mut dispatcher);
let reference_status = tell_status(&mut dispatcher, &reference_gid);
let magnet_uri = support::rpc_string_field(&reference_status, "magnetUri");
let info_hash = support::rpc_string_field(&reference_status, "infoHash");
let gid = support::add_magnet(&mut dispatcher, &magnet_uri);
let before = tell_status(&mut dispatcher, &gid);
assert!(support::rpc_bool_field(&before, "metadataOnly"));
assert_eq!(support::rpc_string_field(&before, "magnetUri"), magnet_uri);
let before_snapshot = dispatcher
.bt_runtime_coordinator_snapshot(&gid)
.expect("snapshot should inspect metadata-only magnet state");
assert!(before_snapshot.metadata_only);
assert!(before_snapshot.metadata_exchange_pending);
seed_peer_and_promote_metadata(&mut dispatcher, &gid, &info_hash);
let after = tell_status(&mut dispatcher, &gid);
assert!(!support::rpc_bool_field(&after, "metadataOnly"));
assert_eq!(support::rpc_string_field(&after, "magnetUri"), magnet_uri);
let after_snapshot = dispatcher
.bt_runtime_coordinator_snapshot(&gid)
.expect("snapshot should inspect promoted magnet state");
assert!(!after_snapshot.metadata_only);
assert!(!after_snapshot.metadata_exchange_pending);
}
#[test]
fn promoted_magnet_session_should_match_reference_torrent_file_surface() {
let mut dispatcher = InProcessRpcDispatcher::new();
let reference_gid = support::add_torrent(&mut dispatcher);
let reference_status = tell_status(&mut dispatcher, &reference_gid);
let reference_files = get_files(&mut dispatcher, &reference_gid);
let reference_file = first_file(&reference_files, "reference torrent");
let magnet_uri = support::rpc_string_field(&reference_status, "magnetUri");
let info_hash = support::rpc_string_field(&reference_status, "infoHash");
let gid = support::add_magnet(&mut dispatcher, &magnet_uri);
seed_peer_and_promote_metadata(&mut dispatcher, &gid, &info_hash);
let promoted_status = tell_status(&mut dispatcher, &gid);
assert!(
!support::rpc_bool_field(&promoted_status, "metadataOnly"),
"metadata promotion should leave metadata-only mode before comparing the public torrent surface"
);
assert_eq!(
support::rpc_u64_field(&promoted_status, "totalLength"),
support::rpc_u64_field(&reference_status, "totalLength"),
"promoted magnet sessions should hydrate the same totalLength visible on the equivalent .torrent bootstrap"
);
let promoted_files = get_files(&mut dispatcher, &gid);
let promoted_file = first_file(&promoted_files, "promoted magnet");
assert_eq!(
promoted_file.get("path"),
reference_file.get("path"),
"promoted magnet sessions should expose the real torrent file path instead of a synthetic placeholder row"
);
assert_eq!(
promoted_file.get("length"),
reference_file.get("length"),
"promoted magnet sessions should expose the same per-file length as the equivalent .torrent bootstrap"
);
}
@@ -0,0 +1,317 @@
#![expect(
missing_docs,
reason = "integration test scenarios are documented by case names rather than item-level rustdoc"
)]
use std::collections::BTreeMap;
use aria2_rust_pro_cli as _;
use aria2_rust_pro_compat as _;
use aria2_rust_pro_core as _;
use aria2_rust_pro_protocol::{
DhtMessageModel, TrackerRequestModel, TrackerTransport,
torrent::{
DhtMessageBody, DhtQueryModel, PeerWireBitfieldModel, PeerWireMessageKind,
PeerWirePieceBlockModel,
},
};
use aria2_rust_pro_rpc::{InProcessRpcDispatcher, RpcMethod, RpcValue};
use aria2_rust_pro_storage as _;
use aria2_rust_pro_tests as _;
use criterion as _;
#[path = "../test_support/support.rs"]
mod support;
#[test]
#[expect(
clippy::too_many_lines,
reason = "integration scenario keeps one end-to-end bt surface in a single readable flow"
)]
fn tracker_dht_peer_wire_and_seeding_updates_remain_rpc_visible_across_public_dispatcher_api() {
let tracker_server =
support::LocalTrackerServer::spawn(support::compact_peer([198, 51, 100, 9], 51413), 900);
let tracker = support::tracker_transport();
let mut dispatcher = InProcessRpcDispatcher::new();
let gid = support::add_torrent(&mut dispatcher);
let bootstrap_status =
support::rpc_result_object(dispatcher.dispatch_json(support::rpc_request(
RpcMethod::Aria2TellStatus,
vec![RpcValue::String(gid.clone())],
)));
let info_hash = support::rpc_string_field(&bootstrap_status, "infoHash");
let magnet_uri = support::rpc_string_field(&bootstrap_status, "magnetUri");
let announce = tracker
.announce(&TrackerRequestModel {
announce_url: tracker_server.announce_url().to_owned(),
info_hash: info_hash.clone(),
peer_id: "0123456789abcdef0123456789abcdef01234567".to_owned(),
port: 6881,
uploaded: 0,
downloaded: 0,
left: 32_768,
event: Some("started".to_owned()),
compact: true,
numwant: Some(10),
})
.expect("live tracker announce should succeed");
dispatcher
.apply_tracker_announce_result(&gid, &announce)
.expect("tracker announce should update the bt view");
let dht_get_peers = support::RecordingDhtTransport::new(DhtMessageModel::get_peers_response(
b"gp".to_vec(),
vec![0x55; 20],
Some(b"dht-token".to_vec()),
Some(support::compact_node(0x44, [203, 0, 113, 99], 51414)),
vec![support::compact_peer([198, 51, 100, 10], 51415)],
));
dispatcher
.execute_dht_get_peers(&gid, &dht_get_peers)
.expect("dht get_peers should succeed");
let dht_find_node = support::RecordingDhtTransport::new(DhtMessageModel::find_node_response(
b"fn".to_vec(),
vec![0x66; 20],
vec![aria2_rust_pro_protocol::torrent::DhtCompactNodeModel {
node_id: [0x77; 20],
address: [203, 0, 113, 100],
port: 51416,
}],
));
dispatcher
.execute_dht_find_node(&gid, &dht_find_node)
.expect("dht find_node should succeed");
let dht_announce = support::RecordingDhtTransport::new(DhtMessageModel::ping_response(
b"ap".to_vec(),
vec![0x88; 20],
));
dispatcher
.execute_dht_announce_peer(&gid, &dht_announce)
.expect("dht announce_peer should succeed after token handoff");
let info_hash_bytes = support::decode_hex_20(&info_hash);
let remote_peer_id = *b"-AZ2060-MODERN-PEER!";
let connector = support::FakePeerWireConnector::new(support::peer_wire_handshake_and_frames(
info_hash_bytes,
remote_peer_id,
&[
PeerWireMessageKind::Unchoke,
PeerWireMessageKind::Bitfield(PeerWireBitfieldModel::from_piece_flags(&[true, true])),
PeerWireMessageKind::Piece(PeerWirePieceBlockModel {
piece_index: 0,
block_offset: 0,
block: vec![0x5a; 16_384],
}),
],
));
dispatcher
.execute_peer_wire_exchange(&gid, &connector)
.expect("peer-wire exchange should succeed");
dispatcher
.apply_bt_runtime_tick(&gid, 16_384, 0, 512, 0, 0, 0, false, Some(8))
.expect("runtime tick should finish the remaining payload");
dispatcher
.set_bt_seeding_state(&gid, true, Some(1_000))
.expect("completed torrent should enter seeding");
dispatcher
.tick_bt_runtime_clock(&gid, 1_030, true)
.expect("share clock should advance");
dispatcher
.apply_bt_runtime_tick(&gid, 0, 16_384, 90, 180, 5, 5, true, Some(8))
.expect("upload tick should populate visible share fields");
let _ = dispatcher.dispatch_json(support::rpc_request(
RpcMethod::Aria2ChangeOption,
vec![
RpcValue::String(gid.clone()),
RpcValue::Object(BTreeMap::from([(
"select-file".to_owned(),
RpcValue::String("1".to_owned()),
)])),
],
));
let status = support::rpc_result_object(dispatcher.dispatch_json(support::rpc_request(
RpcMethod::Aria2TellStatus,
vec![RpcValue::String(gid.clone())],
)));
assert!(support::rpc_bool_field(&status, "isBt"));
assert!(!support::rpc_bool_field(&status, "metadataOnly"));
assert_eq!(support::rpc_string_field(&status, "magnetUri"), magnet_uri);
assert_eq!(support::rpc_u64_field(&status, "completedLength"), 32_768);
assert_eq!(support::rpc_u64_field(&status, "connections"), 8);
assert_eq!(support::rpc_string_field(&status, "shareRatio"), "0.500");
assert_eq!(support::rpc_u64_field(&status, "shareTime"), 35);
assert!(support::rpc_bool_field(&status, "seeder"));
assert_eq!(support::rpc_u64_field(&status, "numSeeders"), 1);
let files = support::rpc_result_array(dispatcher.dispatch_json(support::rpc_request(
RpcMethod::Aria2GetFiles,
vec![RpcValue::String(gid.clone())],
)));
match files.first() {
Some(RpcValue::Object(file)) => {
assert_eq!(
file.get("selected"),
Some(&RpcValue::String("true".to_owned()))
);
assert_eq!(
file.get("path"),
Some(&RpcValue::String("ubuntu.iso".to_owned()))
);
}
other => panic!("unexpected file payload after bt orchestration: {other:?}"),
}
let peers = support::rpc_result_array(dispatcher.dispatch_json(support::rpc_request(
RpcMethod::Aria2GetPeers,
vec![RpcValue::String(gid.clone())],
)));
match peers.first() {
Some(RpcValue::Object(peer)) => {
assert_eq!(
peer.get("peerId"),
Some(&RpcValue::String(
"2d415a323036302d4d4f4445524e2d5045455221".to_owned()
))
);
assert_eq!(
peer.get("peerChoking"),
Some(&RpcValue::String("false".to_owned()))
);
assert_eq!(
peer.get("seeder"),
Some(&RpcValue::String("true".to_owned()))
);
}
other => panic!("unexpected peer payload after bt orchestration: {other:?}"),
}
let seen_announce = dht_announce.seen();
assert_eq!(
seen_announce.len(),
1,
"announce_peer should send exactly one query"
);
match &seen_announce
.first()
.expect("announce_peer should record one query")
.1
.body
{
DhtMessageBody::Query(DhtQueryModel::AnnouncePeer(query)) => {
assert_eq!(query.info_hash, info_hash_bytes);
assert_eq!(query.token, b"dht-token".to_vec());
assert_eq!(query.port, 6881);
}
other => panic!("unexpected announce_peer query payload: {other:?}"),
}
let seen_peer_wire = connector.seen();
assert_eq!(
seen_peer_wire.len(),
1,
"peer-wire transport should see one request"
);
assert!(
seen_peer_wire
.first()
.expect("peer-wire transport should record one request")
.payload
.len()
> 68,
"peer-wire request should include the handshake plus follow-up frames"
);
}
#[test]
fn bt_status_surfaces_remain_monotonic_under_public_runtime_tick_pressure() {
let mut dispatcher = InProcessRpcDispatcher::new();
let gid = support::add_torrent(&mut dispatcher);
let mut last_completed = 0_u64;
let mut last_share_time = 0_u64;
for round in 0..48_u64 {
let downloaded_delta = if round < 16 { 2_048 } else { 0 };
let uploaded_delta = if round >= 16 { 512 } else { 0 };
let seeding = round >= 16;
dispatcher
.apply_bt_runtime_tick(
&gid,
downloaded_delta,
uploaded_delta,
256 + round,
128 + round,
u64::from(seeding),
u64::from(seeding),
seeding,
Some(2 + u32::try_from(round % 5).expect("round modulo 5 should fit u32")),
)
.expect("runtime tick should succeed under repeated probing");
if round == 16 {
dispatcher
.set_bt_seeding_state(&gid, true, Some(2_000))
.expect("completed torrent should enter seeding");
}
if seeding {
dispatcher
.tick_bt_runtime_clock(&gid, 2_000 + round, true)
.expect("runtime clock should advance while seeding");
}
let status = support::rpc_result_object(dispatcher.dispatch_json(support::rpc_request(
RpcMethod::Aria2TellStatus,
vec![
RpcValue::String(gid.clone()),
RpcValue::Array(vec![
RpcValue::String("status".to_owned()),
RpcValue::String("completedLength".to_owned()),
RpcValue::String("connections".to_owned()),
RpcValue::String("files".to_owned()),
RpcValue::String("bitfield".to_owned()),
RpcValue::String("isBt".to_owned()),
RpcValue::String("shareRatio".to_owned()),
RpcValue::String("shareTime".to_owned()),
RpcValue::String("seeder".to_owned()),
]),
],
)));
let completed = support::rpc_u64_field(&status, "completedLength");
let share_time = support::rpc_u64_field(&status, "shareTime");
assert!(support::rpc_bool_field(&status, "isBt"));
assert!(
completed >= last_completed,
"completedLength should stay monotonic under repeated probes"
);
assert!(
share_time >= last_share_time,
"shareTime should stay monotonic under repeated probes"
);
assert!(
status.contains_key("bitfield"),
"bt tellStatus should retain bitfield visibility under pressure"
);
assert!(
status.contains_key("files"),
"bt tellStatus should retain files visibility under pressure"
);
last_completed = completed;
last_share_time = share_time;
let files = support::rpc_result_array(dispatcher.dispatch_json(support::rpc_request(
RpcMethod::Aria2GetFiles,
vec![RpcValue::String(gid.clone())],
)));
assert_eq!(files.len(), 1, "torrent fixture should expose one file");
match files.first() {
Some(RpcValue::Object(file)) => {
assert!(file.contains_key("selected"));
assert!(file.contains_key("completedLength"));
}
other => panic!("unexpected getFiles payload under pressure: {other:?}"),
}
}
}