chore: initial sanitized public snapshot

This commit is contained in:
Aria2 Rust Pro Contributors
2026-07-18 15:24:15 +08:00
commit e489b29e01
321 changed files with 76890 additions and 0 deletions
@@ -0,0 +1,287 @@
use super::*;
use crate::{
piece::{PieceId, PieceState},
request::{BtFileInfo, BtPeerInfo, BtRuntimeState, DownloadId},
};
#[test]
fn segment_plan_uses_runtime_limits() {
let runtime = RuntimeConfig {
split: 8,
max_connections_per_server: 3,
max_connection_per_server: 2,
min_split_size: 1024,
..RuntimeConfig::default()
};
let plan = SegmentPlan::from_runtime(&runtime);
assert_eq!(plan.split, 8);
assert_eq!(plan.max_connections_per_server, 3);
assert_eq!(plan.min_split_size, 1024);
}
#[test]
fn plan_active_segments_respects_remaining_size_and_limits() {
let scheduler = Scheduler::new();
let runtime = RuntimeConfig {
split: 6,
max_connections_per_server: 4,
max_connection_per_server: 4,
min_split_size: 1024,
..RuntimeConfig::default()
};
let mut group = RequestGroup::new(DownloadId::new(1), "https://example.org/file.bin");
group.set_status(DownloadStatus::Active);
group.set_total_length(10 * 1024);
group.set_completed_length(2 * 1024);
assert_eq!(scheduler.plan_active_segments(&group, &runtime), 4);
group.set_completed_length(9 * 1024 + 900);
assert_eq!(scheduler.plan_active_segments(&group, &runtime), 1);
group.set_completed_length(group.total_length());
assert_eq!(scheduler.plan_active_segments(&group, &runtime), 0);
}
#[test]
fn plan_active_segments_returns_zero_for_non_runnable_states() {
let scheduler = Scheduler::new();
let runtime = RuntimeConfig::default();
let mut group = RequestGroup::new(DownloadId::new(2), "https://example.org/file.bin");
group.set_total_length(2048);
group.set_completed_length(0);
group.set_status(DownloadStatus::Paused);
assert_eq!(scheduler.plan_active_segments(&group, &runtime), 0);
group.set_status(DownloadStatus::Error);
assert_eq!(scheduler.plan_active_segments(&group, &runtime), 0);
}
#[test]
fn plan_active_segments_returns_zero_for_bt_metadata_only() {
let scheduler = Scheduler::new();
let runtime = RuntimeConfig {
split: 4,
max_connections_per_server: 4,
max_connection_per_server: 4,
min_split_size: 1024,
..RuntimeConfig::default()
};
let mut group = RequestGroup::new(DownloadId::new(3), "magnet:?xt=urn:btih:ABC");
group.set_status(DownloadStatus::Active);
group.set_total_length(8 * 1024);
group.set_completed_length(1024);
group.set_bt(BtRuntimeState {
metadata_only: true,
..BtRuntimeState::default()
});
assert_eq!(scheduler.plan_active_segments(&group, &runtime), 0);
}
#[test]
fn plan_active_segments_uses_bt_selected_files_length() {
let scheduler = Scheduler::new();
let runtime = RuntimeConfig {
split: 8,
max_connections_per_server: 8,
max_connection_per_server: 8,
min_split_size: 1024,
..RuntimeConfig::default()
};
let mut group = RequestGroup::new(DownloadId::new(4), "magnet:?xt=urn:btih:DEF");
group.set_status(DownloadStatus::Active);
group.set_total_length(10 * 1024);
group.set_completed_length(3500);
group.set_bt(BtRuntimeState {
files: vec![
BtFileInfo {
path: "wanted.bin".to_owned(),
length: 4 * 1024,
piece_offset: Some(0),
selected: true,
},
BtFileInfo {
path: "unwanted.bin".to_owned(),
length: 6 * 1024,
piece_offset: Some(4),
selected: false,
},
],
..BtRuntimeState::default()
});
// Selected total is 4096; after completed 3500 only 596 bytes remain,
// so the scheduler should avoid over-planning and keep a single segment.
assert_eq!(scheduler.plan_active_segments(&group, &runtime), 1);
}
#[test]
fn scheduler_selects_bt_piece_candidates_and_endgame_behavior() {
let scheduler = Scheduler::new();
let mut group = RequestGroup::new(DownloadId::new(5), "magnet:?xt=urn:btih:FFF");
group.set_piece_state(PieceId(0), PieceState::Verified);
group.set_piece_state(PieceId(1), PieceState::Pending);
group.set_piece_state(PieceId(2), PieceState::Missing);
group.set_piece_state(PieceId(3), PieceState::Downloading);
group.set_piece_state(PieceId(4), PieceState::Queued);
let normal = scheduler.select_bt_piece_candidates(
&group,
BtPieceSelectionOptions {
max_candidates: 8,
include_downloading_in_endgame: true,
endgame_mode: false,
},
);
assert_eq!(normal, vec![PieceId(1), PieceId(2), PieceId(4)]);
let endgame = scheduler.select_bt_piece_candidates(
&group,
BtPieceSelectionOptions {
max_candidates: 8,
include_downloading_in_endgame: true,
endgame_mode: true,
},
);
assert_eq!(
endgame,
vec![PieceId(1), PieceId(2), PieceId(4), PieceId(3)]
);
}
#[test]
fn scheduler_plans_bt_piece_ranges_from_runtime_piece_length() {
let scheduler = Scheduler::new();
let runtime = RuntimeConfig {
piece_length: 1024,
..RuntimeConfig::default()
};
let mut group = RequestGroup::new(DownloadId::new(6), "magnet:?xt=urn:btih:GGG");
group.set_piece_state(PieceId(2), PieceState::Pending);
group.set_piece_state(PieceId(5), PieceState::Missing);
let ranges = scheduler.plan_bt_piece_request_ranges(
&group,
&runtime,
BtPieceSelectionOptions {
max_candidates: 2,
include_downloading_in_endgame: false,
endgame_mode: false,
},
);
assert_eq!(
ranges,
vec![PieceRange::new(2048, 3072), PieceRange::new(5120, 6144)]
);
}
#[test]
fn runtime_schedule_state_reports_endgame_readiness() {
assert!(RuntimeScheduleState::is_endgame_ready(1, 3));
assert!(RuntimeScheduleState::is_endgame_ready(3, 3));
assert!(!RuntimeScheduleState::is_endgame_ready(4, 3));
assert!(!RuntimeScheduleState::is_endgame_ready(0, 3));
}
#[test]
fn scheduler_activity_counters_track_ticks_and_decisions() {
let mut scheduler = Scheduler::new();
assert_eq!(scheduler.activity_counters().tick_count, 0);
assert_eq!(scheduler.activity_counters().schedule_run_count, 0);
scheduler.record_schedule_run();
let _ = scheduler.tick();
scheduler.record_decision(&ScheduleDecision::Queue(DownloadId::new(0x21)));
scheduler.record_decision(&ScheduleDecision::RunNow(DownloadId::new(0x21)));
scheduler.record_decision(&ScheduleDecision::RetryLater(DownloadId::new(0x21)));
scheduler.record_decision(&ScheduleDecision::Noop);
let counters = scheduler.activity_counters();
assert_eq!(counters.tick_count, 1);
assert_eq!(counters.schedule_run_count, 1);
assert_eq!(counters.queue_decision_count, 1);
assert_eq!(counters.run_now_decision_count, 1);
assert_eq!(counters.retry_later_decision_count, 1);
assert_eq!(counters.noop_decision_count, 1);
assert_eq!(counters.last_decision, Some(ScheduleDecisionKind::Noop));
}
#[test]
fn scheduler_records_last_planning_observation_for_bt_pressure() {
let mut scheduler = Scheduler::new();
let runtime = RuntimeConfig {
split: 5,
max_connections_per_server: 3,
max_connection_per_server: 3,
min_split_size: 1024,
..RuntimeConfig::default()
};
let mut group = RequestGroup::new(DownloadId::new(0x22), "magnet:?xt=urn:btih:PRESSURE");
group.set_status(DownloadStatus::Active);
group.set_total_length(6 * 1024);
group.set_completed_length(1024);
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,
files: vec![BtFileInfo {
path: "payload.bin".to_owned(),
length: 6 * 1024,
piece_offset: Some(0),
selected: true,
}],
peers: vec![
BtPeerInfo {
peer_id: Some("peer-a".to_owned()),
ip: "192.0.2.1".to_owned(),
port: 6881,
client_name: None,
interested: true,
choked: false,
download_speed: 64,
upload_speed: 32,
seeder: false,
},
BtPeerInfo {
peer_id: Some("peer-b".to_owned()),
ip: "192.0.2.2".to_owned(),
port: 6882,
client_name: None,
interested: false,
choked: true,
download_speed: 0,
upload_speed: 16,
seeder: true,
},
],
..BtRuntimeState::default()
});
group.apply_bt_piece_availability_update(crate::request::BtPieceAvailabilityUpdate {
piece_id: PieceId(1),
peers_with_piece: 1,
});
group.apply_bt_piece_availability_update(crate::request::BtPieceAvailabilityUpdate {
piece_id: PieceId(3),
peers_with_piece: 2,
});
let planned = scheduler.plan_active_segments(&group, &runtime);
scheduler.observe_plan(&group, &runtime, planned);
let observation = scheduler
.last_planning_observation()
.expect("planning observation should be recorded");
assert_eq!(observation.gid, DownloadId::new(0x22));
assert_eq!(observation.planned_segments, 3);
assert_eq!(observation.remaining_bytes, 5 * 1024);
assert_eq!(observation.requestable_pieces, 3);
assert_eq!(observation.active_piece_count, 2);
assert_eq!(observation.available_requestable_pieces, 2);
assert_eq!(observation.scarce_requestable_pieces, 1);
assert_eq!(observation.peer_count, 2);
assert!(observation.bt_endgame_ready);
}