chore: initial sanitized public snapshot

This commit is contained in:
Aria2 Rust Pro Contributors
2026-07-18 14:51:59 +08:00
commit 14dcf8c9bf
321 changed files with 76893 additions and 0 deletions
@@ -0,0 +1,929 @@
use super::*;
#[test]
fn request_context_replace_uris_filters_blank_entries_and_duplicates() {
let mut context = RequestContext::new(String::new());
assert_eq!(context.uri(), "");
assert!(context.uris().is_empty());
context.replace_uris(vec![
String::new(),
"https://example.org/file.iso".to_owned(),
"https://example.org/file.iso".to_owned(),
" ".to_owned(),
"https://mirror.example.org/file.iso".to_owned(),
]);
assert_eq!(context.uri(), "https://example.org/file.iso");
assert_eq!(
context.uris(),
&[
"https://example.org/file.iso".to_owned(),
"https://mirror.example.org/file.iso".to_owned(),
]
);
}
#[test]
fn request_context_insert_and_append_reposition_existing_uris_without_duplicates() {
let mut context = RequestContext::new("https://example.org/file.iso");
context.replace_uris(vec![
"https://example.org/file.iso".to_owned(),
"https://mirror-a.example.org/file.iso".to_owned(),
"https://mirror-b.example.org/file.iso".to_owned(),
]);
context.insert_uri(0, "https://mirror-b.example.org/file.iso");
assert_eq!(context.uri(), "https://mirror-b.example.org/file.iso");
assert_eq!(
context.uris(),
&[
"https://mirror-b.example.org/file.iso".to_owned(),
"https://example.org/file.iso".to_owned(),
"https://mirror-a.example.org/file.iso".to_owned(),
]
);
context.append_uri("https://example.org/file.iso");
context.append_uri(" ");
assert_eq!(context.uri(), "https://mirror-b.example.org/file.iso");
assert_eq!(
context.uris(),
&[
"https://mirror-b.example.org/file.iso".to_owned(),
"https://mirror-a.example.org/file.iso".to_owned(),
"https://example.org/file.iso".to_owned(),
]
);
}
#[test]
fn request_context_remove_last_uri_clears_primary_uri() {
let mut context = RequestContext::new("https://example.org/last.iso");
assert!(context.remove_first_matching_uri("https://example.org/last.iso"));
assert_eq!(context.uri(), "");
assert!(context.uris().is_empty());
assert!(!context.remove_first_matching_uri("https://example.org/last.iso"));
}
#[test]
fn request_group_with_context_normalizes_stale_primary_and_uri_list() {
let group = RequestGroup::with_context(
DownloadId::new(0x77),
RequestContext {
source: None,
uri: "https://stale.example.org/file.iso".to_owned(),
uris: vec![
String::new(),
"https://mirror-a.example.org/file.iso".to_owned(),
"https://mirror-a.example.org/file.iso".to_owned(),
"https://mirror-b.example.org/file.iso".to_owned(),
],
referer: None,
headers: Vec::new(),
group_id: None,
note: None,
},
);
assert_eq!(group.uri(), "https://mirror-a.example.org/file.iso");
assert_eq!(
group.uris(),
&[
"https://mirror-a.example.org/file.iso".to_owned(),
"https://mirror-b.example.org/file.iso".to_owned(),
]
);
}
#[test]
fn request_group_tracks_retry_attempt_history() {
let mut group = RequestGroup::new(DownloadId::new(0x1234), "https://example.test/file");
group.increment_retry_count();
group.push_retry_attempt(RetryAttempt {
attempt: group.retry_count(),
offset: 8192,
length: Some(4096),
error: Some("connection reset".to_string()),
recoverable: true,
});
assert_eq!(group.retry_count(), 1);
let [attempt] = group.retry_attempts() else {
panic!("retry_attempts should contain exactly one entry");
};
assert_eq!(attempt.offset, 8192);
assert_eq!(attempt.length, Some(4096));
assert_eq!(attempt.error.as_deref(), Some("connection reset"));
}
#[test]
fn request_group_resume_state_roundtrip() {
let mut group = RequestGroup::new(DownloadId::new(0x66), "https://example.test/file");
group.set_resume_state(ResumeState {
persisted: true,
resume_offset: 32768,
validated_length: Some(4096),
segment_cursor: Some(PieceId(8)),
});
let resume = group.resume_state().expect("resume state should exist");
assert!(resume.persisted);
assert_eq!(resume.resume_offset, 32768);
assert_eq!(resume.validated_length, Some(4096));
assert_eq!(resume.segment_cursor, Some(PieceId(8)));
group.clear_resume_state();
assert!(group.resume_state().is_none());
}
#[test]
fn request_group_dht_token_roundtrip() {
let mut group = RequestGroup::new(DownloadId::new(0x67), "magnet:?xt=urn:btih:token");
assert!(group.dht_token().is_none());
group.set_dht_token(Some(b"tok".to_vec()));
assert_eq!(group.dht_token(), Some(&b"tok"[..]));
group.set_dht_token(None);
assert!(group.dht_token().is_none());
}
#[test]
fn request_group_clear_retry_attempts() {
let mut group = RequestGroup::new(DownloadId::new(0x9), "https://example.test/file");
group.push_retry_attempt(RetryAttempt::new(1, 0));
group.push_retry_attempt(RetryAttempt::new(2, 1024));
assert_eq!(group.retry_attempts().len(), 2);
group.clear_retry_attempts();
assert!(group.retry_attempts().is_empty());
}
#[test]
fn request_group_tracks_segment_assignments() {
let mut group = RequestGroup::new(DownloadId::new(0xa), "https://example.test/file");
group.set_segment_assignments(vec![
SegmentAssignment::new(0, PieceRange::new(0, 1024)),
SegmentAssignment::new(1, PieceRange::new(1024, 2048)),
]);
assert_eq!(group.num_connections(), 2);
let [_, second_assignment] = group.segment_assignments() else {
panic!("segment_assignments should contain exactly two entries");
};
assert_eq!(second_assignment.range, PieceRange::new(1024, 2048));
group.clear_segment_assignments();
assert_eq!(group.num_connections(), 0);
assert!(group.segment_assignments().is_empty());
}
#[test]
fn request_group_bt_runtime_state_roundtrip_with_full_payload() {
let mut group = RequestGroup::new(DownloadId::new(0xb), "magnet:?xt=urn:btih:ABCDEF");
let bt = BtRuntimeState {
info_hash: "0123456789ABCDEF0123456789ABCDEF01234567".to_owned(),
name: Some("ubuntu.iso".to_owned()),
magnet_uri: Some("magnet:?xt=urn:btih:0123456789ABCDEF0123456789ABCDEF01234567".to_owned()),
metadata_only: true,
metadata_size: Some(32_768),
metadata_extension_ids: BTreeMap::from([("192.0.2.10:51413".to_owned(), 3_u8)]),
metadata_piece_payloads: BTreeMap::from([(0_u32, b"metadata-piece-0".to_vec())]),
creation_date: Some("2026-05-26T12:00:00Z".to_owned()),
comment: Some("bt runtime".to_owned()),
dht_nodes: vec!["router.bittorrent.com:6881".to_owned()],
files: vec![
BtFileInfo {
path: "ubuntu.iso".to_owned(),
length: 2048,
piece_offset: Some(0),
selected: true,
},
BtFileInfo {
path: "readme.txt".to_owned(),
length: 128,
piece_offset: Some(2),
selected: false,
},
],
trackers: vec![
BtTrackerInfo {
url: "udp://tracker.example.org:6969/announce".to_owned(),
tier: Some(0),
id: Some("trk-0".to_owned()),
seeders: Some(10),
leechers: Some(3),
},
BtTrackerInfo {
url: "https://tracker2.example.org/announce".to_owned(),
tier: Some(1),
id: None,
seeders: None,
leechers: None,
},
],
peers: vec![BtPeerInfo {
peer_id: Some("-TR3000-ABCDEF123456".to_owned()),
ip: "192.0.2.10".to_owned(),
port: 51413,
client_name: Some("Transmission".to_owned()),
interested: true,
choked: false,
download_speed: 4096,
upload_speed: 2048,
seeder: false,
}],
};
group.set_bt(bt.clone());
let saved = group.bt().expect("bt runtime state should be set");
assert_eq!(saved, &bt);
assert_eq!(
saved.dht_nodes(),
&["router.bittorrent.com:6881".to_owned()]
);
assert_eq!(saved.files.len(), 2);
assert_eq!(saved.trackers.len(), 2);
assert_eq!(saved.peers.len(), 1);
}
#[test]
fn request_group_bt_runtime_state_mutation_via_bt_mut() {
let mut group = RequestGroup::new(DownloadId::new(0xc), "magnet:?xt=urn:btih:AAAA");
group.set_bt(BtRuntimeState {
info_hash: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned(),
name: Some("seed".to_owned()),
magnet_uri: Some("magnet:?xt=urn:btih:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned()),
metadata_only: true,
metadata_size: None,
metadata_extension_ids: BTreeMap::new(),
metadata_piece_payloads: BTreeMap::new(),
creation_date: None,
comment: None,
dht_nodes: vec!["dht.transmissionbt.com:6881".to_owned()],
files: vec![BtFileInfo {
path: "seed.bin".to_owned(),
length: 1,
piece_offset: Some(0),
selected: true,
}],
trackers: vec![],
peers: vec![],
});
let bt = group.bt_mut().expect("bt runtime state should be mutable");
bt.metadata_only = false;
bt.comment = Some("metadata complete".to_owned());
bt.dht_nodes.push("router.utorrent.com:6881".to_owned());
bt.files.push(BtFileInfo {
path: "extra.bin".to_owned(),
length: 512,
piece_offset: Some(1),
selected: true,
});
bt.trackers.push(BtTrackerInfo {
url: "https://tracker.example.org/announce".to_owned(),
tier: Some(0),
id: Some("trk-a".to_owned()),
seeders: Some(1),
leechers: Some(0),
});
bt.peers.push(BtPeerInfo {
peer_id: None,
ip: "198.51.100.20".to_owned(),
port: 60000,
client_name: None,
interested: true,
choked: true,
download_speed: 0,
upload_speed: 0,
seeder: true,
});
let after = group.bt().expect("bt runtime state should still exist");
assert!(!after.metadata_only);
assert_eq!(after.comment.as_deref(), Some("metadata complete"));
assert_eq!(after.dht_nodes.len(), 2);
assert_eq!(after.files.len(), 2);
assert_eq!(after.trackers.len(), 1);
assert_eq!(after.peers.len(), 1);
}
#[test]
fn request_group_bt_runtime_state_can_be_cleared() {
let mut group = RequestGroup::new(DownloadId::new(0xd), "magnet:?xt=urn:btih:BBBB");
group.set_bt(BtRuntimeState {
info_hash: "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".to_owned(),
name: None,
magnet_uri: Some("magnet:?xt=urn:btih:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".to_owned()),
metadata_only: true,
metadata_size: None,
metadata_extension_ids: BTreeMap::new(),
metadata_piece_payloads: BTreeMap::new(),
creation_date: None,
comment: None,
dht_nodes: Vec::new(),
files: Vec::new(),
trackers: Vec::new(),
peers: Vec::new(),
});
assert!(group.bt().is_some());
assert!(group.bt_mut().is_some());
group.clear_bt();
assert!(group.bt().is_none());
assert!(group.bt_mut().is_none());
}
#[test]
fn bt_runtime_state_reports_selected_file_helpers() {
let bt = BtRuntimeState {
info_hash: "0123456789abcdef0123456789abcdef01234567".to_owned(),
name: Some("example".to_owned()),
magnet_uri: None,
metadata_only: false,
metadata_size: None,
metadata_extension_ids: BTreeMap::new(),
metadata_piece_payloads: BTreeMap::new(),
creation_date: None,
comment: None,
dht_nodes: vec![
"router.bittorrent.com:6881".to_owned(),
"router.utorrent.com:6881".to_owned(),
],
files: vec![
BtFileInfo {
path: "selected.iso".to_owned(),
length: 2048,
piece_offset: Some(0),
selected: true,
},
BtFileInfo {
path: "ignored.txt".to_owned(),
length: 128,
piece_offset: Some(2048),
selected: false,
},
],
trackers: vec![],
peers: vec![],
};
let [first_file, ..] = bt.files() else {
panic!("bt files should contain at least one entry");
};
assert!(first_file.is_selected());
assert_eq!(bt.dht_nodes().len(), 2);
assert!(bt.has_selected_files());
assert_eq!(bt.selected_file_count(), 1);
assert_eq!(bt.selected_total_length(), 2048);
let selected_paths: Vec<_> = bt.selected_files().map(|file| file.path.as_str()).collect();
assert_eq!(selected_paths, vec!["selected.iso"]);
}
#[test]
fn request_group_bt_share_state_roundtrip_and_accessors_work() {
let mut group = RequestGroup::new(DownloadId::new(0xe), "magnet:?xt=urn:btih:CCCC");
group.set_bt_share_state(BtShareRuntimeState {
seeding: true,
share_ratio_milli: Some(1500),
share_time_secs: 3600,
seeding_time_secs: 900,
seeding_started_at_secs: None,
last_runtime_tick_secs: None,
});
group.set_bt(BtRuntimeState {
info_hash: "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC".to_owned(),
name: Some("payload".to_owned()),
magnet_uri: None,
metadata_only: false,
metadata_size: None,
metadata_extension_ids: BTreeMap::new(),
metadata_piece_payloads: BTreeMap::new(),
creation_date: None,
comment: None,
dht_nodes: vec!["router.bittorrent.com:6881".to_owned()],
files: vec![BtFileInfo {
path: "payload.bin".to_owned(),
length: 4096,
piece_offset: Some(0),
selected: true,
}],
trackers: vec![],
peers: vec![],
});
assert!(group.bt_is_seeding());
assert_eq!(group.bt_share_ratio_milli(), Some(1500));
assert_eq!(group.bt_share_time_secs(), Some(3600));
assert_eq!(group.bt_seeding_time_secs(), Some(900));
assert_eq!(group.bt_selected_file_count(), Some(1));
assert_eq!(group.bt_selected_total_length(), Some(4096));
assert!(group.bt_has_selected_files());
let share = group
.bt_share_state_mut()
.expect("share state should exist");
share.add_share_time_secs(120);
share.add_seeding_time_secs(30);
share.set_seeding(false);
assert!(!group.bt_is_seeding());
assert_eq!(group.bt_share_time_secs(), Some(3720));
assert_eq!(group.bt_seeding_time_secs(), Some(930));
group.clear_bt_share_state();
assert!(group.bt_share_state().is_none());
assert!(!group.bt_is_seeding());
}
#[test]
fn bt_share_runtime_state_tracks_seeding_runtime_and_ratio_derivation() {
let mut state = BtShareRuntimeState::new();
state.start_seeding(100);
state.tick_runtime(130);
state.tick_runtime(170);
state.stop_seeding(200);
state.tick_runtime(250);
state.refresh_share_ratio_from_lengths(6000, 4000);
assert!(!state.is_seeding());
assert_eq!(state.share_time_secs(), 150);
assert_eq!(state.seeding_time_secs(), 100);
assert_eq!(state.share_ratio_milli(), Some(1500));
assert_eq!(BtShareRuntimeState::derive_share_ratio_milli(100, 0), None);
}
#[test]
fn request_group_bt_runtime_tick_updates_true_seeding_and_share_runtime() {
let mut group = RequestGroup::new(DownloadId::new(0x101), "magnet:?xt=urn:btih:RUNTIME");
group.set_total_length(2_048);
group.set_completed_length(2_048);
group.set_bt(BtRuntimeState {
metadata_only: false,
files: vec![BtFileInfo {
path: "payload.bin".to_owned(),
length: 2_048,
piece_offset: Some(0),
selected: true,
}],
..BtRuntimeState::default()
});
let started = group.set_bt_seeding_state(true, Some(100));
assert!(group.bt_is_seeding());
assert!(group.bt_is_true_seeding());
assert!(started.seeding);
assert_eq!(started.share_ratio_milli, Some(0));
let advanced = group.tick_bt_runtime_clock(130, true);
assert_eq!(advanced.share_time_secs, 30);
assert_eq!(advanced.seeding_time_secs, 30);
assert!(advanced.seeding);
let tick = group.apply_bt_runtime_tick(0, 512, 64, 128, 10, 10, true, Some(8));
assert_eq!(group.upload_length(), 512);
assert_eq!(group.download_speed(), 64);
assert_eq!(group.upload_speed(), 128);
assert_eq!(group.num_connections(), 8);
assert_eq!(tick.share_time_secs, 40);
assert_eq!(tick.seeding_time_secs, 40);
assert_eq!(tick.share_ratio_milli, Some(250));
assert!(tick.seeding);
let stopped = group.tick_bt_runtime_clock(160, false);
assert!(!group.bt_is_seeding());
assert!(!group.bt_is_true_seeding());
assert!(!stopped.seeding);
assert_eq!(stopped.share_time_secs, 70);
assert_eq!(stopped.seeding_time_secs, 70);
}
#[test]
fn request_group_bt_share_ratio_base_length_follows_selected_payload() {
let mut group = RequestGroup::new(DownloadId::new(0x102), "magnet:?xt=urn:btih:BASE");
group.set_total_length(10_000);
group.set_completed_length(3_000);
group.set_bt(BtRuntimeState {
metadata_only: false,
files: vec![
BtFileInfo {
path: "selected-a.bin".to_owned(),
length: 2_000,
piece_offset: Some(0),
selected: true,
},
BtFileInfo {
path: "selected-b.bin".to_owned(),
length: 1_000,
piece_offset: Some(2),
selected: true,
},
BtFileInfo {
path: "ignored.bin".to_owned(),
length: 7_000,
piece_offset: Some(3),
selected: false,
},
],
..BtRuntimeState::default()
});
assert_eq!(group.bt_share_ratio_base_length(), Some(3_000));
group.set_completed_length(1_000);
assert_eq!(group.bt_share_ratio_base_length(), Some(3_000));
group.set_completed_length(5_000);
assert_eq!(group.bt_share_ratio_base_length(), Some(5_000));
}
#[test]
fn request_group_refresh_bt_share_runtime_uses_share_ratio_base_length() {
let mut group = RequestGroup::new(DownloadId::new(0x103), "magnet:?xt=urn:btih:RATIO");
group.set_total_length(10_000);
group.set_completed_length(6_000);
group.set_upload_length(3_000);
group.set_bt(BtRuntimeState {
metadata_only: false,
files: vec![
BtFileInfo {
path: "selected.bin".to_owned(),
length: 4_000,
piece_offset: Some(0),
selected: true,
},
BtFileInfo {
path: "ignored.bin".to_owned(),
length: 6_000,
piece_offset: Some(4),
selected: false,
},
],
..BtRuntimeState::default()
});
group.set_bt_share_state(BtShareRuntimeState::default());
let snapshot = group.refresh_bt_share_runtime();
assert_eq!(group.bt_share_ratio_base_length(), Some(6_000));
assert_eq!(snapshot.share_ratio_milli, Some(500));
assert_eq!(group.bt_share_ratio_milli(), Some(500));
}
#[test]
fn request_group_set_bt_refreshes_cached_share_ratio_after_selection_changes() {
let mut group = RequestGroup::new(DownloadId::new(0x104), "magnet:?xt=urn:btih:SELECT");
group.set_total_length(10_000);
group.set_completed_length(4_000);
group.set_upload_length(2_000);
group.set_bt(BtRuntimeState {
metadata_only: false,
files: vec![
BtFileInfo {
path: "disc-a.bin".to_owned(),
length: 5_000,
piece_offset: Some(0),
selected: true,
},
BtFileInfo {
path: "disc-b.bin".to_owned(),
length: 5_000,
piece_offset: Some(5),
selected: true,
},
],
..BtRuntimeState::default()
});
group.set_bt_share_state(BtShareRuntimeState::default());
assert_eq!(
group.refresh_bt_share_runtime().share_ratio_milli,
Some(200)
);
group.set_bt(BtRuntimeState {
metadata_only: false,
files: vec![
BtFileInfo {
path: "disc-a.bin".to_owned(),
length: 4_000,
piece_offset: Some(0),
selected: true,
},
BtFileInfo {
path: "disc-b.bin".to_owned(),
length: 6_000,
piece_offset: Some(4),
selected: false,
},
],
..BtRuntimeState::default()
});
assert_eq!(group.bt_selected_total_length(), Some(4_000));
assert_eq!(group.bt_share_ratio_milli(), Some(500));
}
#[test]
fn request_group_set_upload_length_refreshes_cached_bt_share_ratio() {
let mut group = RequestGroup::new(DownloadId::new(0x105), "magnet:?xt=urn:btih:UPLOAD");
group.set_total_length(4_000);
group.set_completed_length(4_000);
group.set_upload_length(1_000);
group.set_bt(BtRuntimeState {
metadata_only: false,
files: vec![BtFileInfo {
path: "payload.bin".to_owned(),
length: 4_000,
piece_offset: Some(0),
selected: true,
}],
..BtRuntimeState::default()
});
group.set_bt_share_state(BtShareRuntimeState::default());
assert_eq!(
group.refresh_bt_share_runtime().share_ratio_milli,
Some(250)
);
group.set_upload_length(2_000);
assert_eq!(group.bt_share_ratio_milli(), Some(500));
}
#[test]
fn request_group_bt_piece_helpers_report_counts_and_requestable_ids() {
let mut group = RequestGroup::new(DownloadId::new(0xf), "magnet:?xt=urn:btih:DDDD");
group.set_piece_state(PieceId(0), PieceState::Verified);
group.set_piece_state(PieceId(1), PieceState::Pending);
group.set_piece_state(PieceId(2), PieceState::Queued);
group.set_piece_state(PieceId(3), PieceState::Downloading);
group.set_piece_state(PieceId(4), PieceState::Missing);
group.set_piece_state(PieceId(5), PieceState::Skipped);
assert_eq!(group.bt_verified_piece_count(), 1);
assert_eq!(group.piece_state_counts(), (1, 1, 1, 1, 1, 1));
assert_eq!(
group.bt_requestable_piece_ids(false, 8),
vec![PieceId(1), PieceId(2), PieceId(4)]
);
assert_eq!(
group.bt_requestable_piece_ids(true, 8),
vec![PieceId(1), PieceId(2), PieceId(4), PieceId(3)]
);
}
#[test]
fn request_group_bt_effective_target_and_remaining_length_follow_selection() {
let mut group = RequestGroup::new(DownloadId::new(0x10), "magnet:?xt=urn:btih:EEEE");
group.set_total_length(10_000);
group.set_completed_length(4_000);
group.set_bt(BtRuntimeState {
metadata_only: false,
files: vec![
BtFileInfo {
path: "wanted-a.bin".to_owned(),
length: 3_000,
piece_offset: Some(0),
selected: true,
},
BtFileInfo {
path: "wanted-b.bin".to_owned(),
length: 2_000,
piece_offset: Some(3),
selected: true,
},
BtFileInfo {
path: "ignored.bin".to_owned(),
length: 5_000,
piece_offset: Some(5),
selected: false,
},
],
..BtRuntimeState::default()
});
assert_eq!(group.bt_effective_target_length(), Some(5_000));
assert_eq!(group.bt_remaining_work_length(), Some(1_000));
group.set_completed_length(9_000);
assert_eq!(group.bt_remaining_work_length(), Some(0));
}
#[test]
fn request_group_bt_piece_block_update_tracks_piece_progress_and_selected_span() {
let mut group = RequestGroup::new(DownloadId::new(0x11), "magnet:?xt=urn:btih:FFFF");
group.set_total_length(4_096);
group.set_piece_length(1_024);
group.set_bt(BtRuntimeState {
metadata_only: false,
files: vec![BtFileInfo {
path: "wanted.bin".to_owned(),
length: 2_500,
piece_offset: Some(0),
selected: true,
}],
..BtRuntimeState::default()
});
let partial = group.apply_bt_piece_block_update(BtPieceBlockUpdate {
piece_id: PieceId(2),
completed_blocks: 2,
total_blocks: 4,
});
assert_eq!(group.piece_state(PieceId(2)), Some(PieceState::Downloading));
assert_eq!(group.completed_length(), 0);
assert_eq!(partial.completed_length_delta, 0);
assert_eq!(partial.previous_state, None);
assert_eq!(partial.next_state, PieceState::Downloading);
assert_eq!(partial.piece_span_length, 452);
assert_eq!(partial.block_completion_milli, 500);
assert!(!partial.transitioned_to_verified);
let verified = group.apply_bt_piece_block_update(BtPieceBlockUpdate {
piece_id: PieceId(2),
completed_blocks: 4,
total_blocks: 4,
});
assert_eq!(group.piece_state(PieceId(2)), Some(PieceState::Verified));
assert_eq!(group.completed_length(), 452);
assert_eq!(verified.completed_length_delta, 452);
assert_eq!(verified.previous_state, Some(PieceState::Downloading));
assert_eq!(verified.next_state, PieceState::Verified);
assert_eq!(verified.block_completion_milli, 1000);
assert!(verified.transitioned_to_verified);
let missing = group.apply_bt_piece_block_update(BtPieceBlockUpdate {
piece_id: PieceId(2),
completed_blocks: 0,
total_blocks: 0,
});
assert_eq!(group.piece_state(PieceId(2)), Some(PieceState::Missing));
assert_eq!(group.completed_length(), 0);
assert_eq!(missing.completed_length_delta, -452);
assert_eq!(missing.previous_state, Some(PieceState::Verified));
assert_eq!(missing.next_state, PieceState::Missing);
assert_eq!(missing.piece_span_length, 452);
assert_eq!(missing.block_completion_milli, 0);
}
#[test]
fn request_group_bt_peer_and_availability_updates_report_runtime_stats() {
let mut group = RequestGroup::new(DownloadId::new(0x12), "magnet:?xt=urn:btih:9999");
group.set_piece_state(PieceId(0), PieceState::Missing);
group.set_piece_state(PieceId(1), PieceState::Queued);
group.set_piece_state(PieceId(2), PieceState::Verified);
group.set_bt(BtRuntimeState {
info_hash: "9999".to_owned(),
metadata_only: false,
..BtRuntimeState::default()
});
let available = group.apply_bt_piece_availability_update(BtPieceAvailabilityUpdate {
piece_id: PieceId(0),
peers_with_piece: 3,
});
assert_eq!(available.available_piece_count, 1);
assert_eq!(available.peers_with_piece, 3);
assert!(available.piece_is_requestable);
assert!(!available.piece_is_verified);
let verified_piece = group.apply_bt_piece_availability_update(BtPieceAvailabilityUpdate {
piece_id: PieceId(2),
peers_with_piece: 5,
});
assert_eq!(verified_piece.available_piece_count, 2);
assert!(!verified_piece.piece_is_requestable);
assert!(verified_piece.piece_is_verified);
let peer_a = group.apply_bt_peer_update(BtPeerInfo {
peer_id: Some("peer-a".to_owned()),
ip: "127.0.0.1".to_owned(),
port: 6881,
client_name: Some("client-a".to_owned()),
interested: true,
choked: false,
download_speed: 512,
upload_speed: 64,
seeder: false,
});
assert_eq!(peer_a.peer_count, 1);
assert_eq!(peer_a.seeder_count, 0);
assert_eq!(peer_a.leecher_count, 1);
assert_eq!(peer_a.total_download_speed, 512);
assert_eq!(peer_a.total_upload_speed, 64);
assert!(!peer_a.replaced_existing);
let peer_b = group.apply_bt_peer_update(BtPeerInfo {
peer_id: Some("peer-a".to_owned()),
ip: "127.0.0.1".to_owned(),
port: 6881,
client_name: Some("client-a2".to_owned()),
interested: false,
choked: true,
download_speed: 1_024,
upload_speed: 256,
seeder: true,
});
assert_eq!(peer_b.peer_count, 1);
assert_eq!(peer_b.seeder_count, 1);
assert_eq!(peer_b.leecher_count, 0);
assert_eq!(peer_b.total_download_speed, 1_024);
assert_eq!(peer_b.total_upload_speed, 256);
assert!(peer_b.replaced_existing);
}
#[test]
fn request_group_segment_runtime_stats_capture_assignment_load() {
let mut group = RequestGroup::new(DownloadId::new(0x13), "https://example.org/segments.bin");
group.set_segment_assignments(vec![
SegmentAssignment {
slot: 0,
range: PieceRange::new(0, 1024),
completed_length: 512,
state: SegmentState::Active,
},
SegmentAssignment {
slot: 1,
range: PieceRange::new(1024, 2048),
completed_length: 1024,
state: SegmentState::Complete,
},
SegmentAssignment {
slot: 2,
range: PieceRange::new(2048, 3072),
completed_length: 128,
state: SegmentState::Retrying,
},
]);
let stats = group.segment_runtime_stats();
assert_eq!(stats.segment_count, 3);
assert_eq!(stats.active_count, 1);
assert_eq!(stats.retrying_count, 1);
assert_eq!(stats.complete_count, 1);
assert_eq!(stats.planned_bytes, 3072);
assert_eq!(stats.completed_bytes, 1664);
assert_eq!(stats.remaining_bytes, 1408);
assert_eq!(stats.covered_range, Some(PieceRange::new(0, 3072)));
}
#[test]
fn request_group_bt_pressure_snapshot_reports_requestable_and_scarcity() {
let mut group = RequestGroup::new(DownloadId::new(0x14), "magnet:?xt=urn:btih:PRESSURE2");
group.set_piece_state(PieceId(0), PieceState::Verified);
group.set_piece_state(PieceId(1), PieceState::Pending);
group.set_piece_state(PieceId(2), PieceState::Downloading);
group.set_piece_state(PieceId(3), PieceState::Queued);
group.set_piece_state(PieceId(4), PieceState::Missing);
group.set_bt(BtRuntimeState {
metadata_only: false,
peers: vec![
BtPeerInfo {
peer_id: Some("peer-a".to_owned()),
ip: "198.51.100.10".to_owned(),
port: 6881,
client_name: None,
interested: true,
choked: false,
download_speed: 256,
upload_speed: 64,
seeder: false,
},
BtPeerInfo {
peer_id: Some("peer-b".to_owned()),
ip: "198.51.100.11".to_owned(),
port: 6882,
client_name: None,
interested: false,
choked: true,
download_speed: 0,
upload_speed: 32,
seeder: true,
},
],
..BtRuntimeState::default()
});
group.apply_bt_piece_availability_update(BtPieceAvailabilityUpdate {
piece_id: PieceId(1),
peers_with_piece: 1,
});
group.apply_bt_piece_availability_update(BtPieceAvailabilityUpdate {
piece_id: PieceId(3),
peers_with_piece: 3,
});
let pressure = group
.bt_pressure_snapshot()
.expect("bt pressure snapshot should exist");
assert_eq!(pressure.total_pieces, 5);
assert_eq!(pressure.requestable_pieces, 3);
assert_eq!(pressure.active_pieces, 2);
assert_eq!(pressure.available_requestable_pieces, 2);
assert_eq!(pressure.scarce_requestable_pieces, 1);
assert_eq!(pressure.peer_count, 2);
assert_eq!(pressure.seeder_count, 1);
assert_eq!(pressure.leecher_count, 1);
}