chore: initial sanitized public snapshot
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user