421 lines
16 KiB
Rust
421 lines
16 KiB
Rust
use super::*;
|
|
|
|
#[expect(
|
|
clippy::too_many_lines,
|
|
reason = "segment-plan regression fixture keeps response bodies, ranges, and request assertions adjacent"
|
|
)]
|
|
#[test]
|
|
fn execute_runtime_uses_segment_plan_after_bootstrap_partial_response() {
|
|
let downloader = SequencedHttpDownloader::new(vec![
|
|
Ok(HttpResponseModel {
|
|
status: 206,
|
|
reason: "Partial Content".to_owned(),
|
|
version: HttpVersion::Http11,
|
|
headers: HttpResponseHeaders {
|
|
headers: vec![
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-length".to_owned(),
|
|
value: "4".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-range".to_owned(),
|
|
value: "bytes 0-3/10".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
],
|
|
},
|
|
body: ResponseBody::Inline(b"1234".to_vec()),
|
|
content_range: Some(aria2_rust_pro_protocol::ContentRangeSpec {
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
start: 0,
|
|
end_inclusive: 3,
|
|
total_size: Some(10),
|
|
unsatisfied: false,
|
|
}),
|
|
partial_content: true,
|
|
checksum: None,
|
|
redirected_from: None,
|
|
}),
|
|
Ok(HttpResponseModel {
|
|
status: 206,
|
|
reason: "Partial Content".to_owned(),
|
|
version: HttpVersion::Http11,
|
|
headers: HttpResponseHeaders {
|
|
headers: vec![
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-length".to_owned(),
|
|
value: "4".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-range".to_owned(),
|
|
value: "bytes 4-7/10".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
],
|
|
},
|
|
body: ResponseBody::Inline(b"5678".to_vec()),
|
|
content_range: Some(aria2_rust_pro_protocol::ContentRangeSpec {
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
start: 4,
|
|
end_inclusive: 7,
|
|
total_size: Some(10),
|
|
unsatisfied: false,
|
|
}),
|
|
partial_content: true,
|
|
checksum: None,
|
|
redirected_from: None,
|
|
}),
|
|
Ok(HttpResponseModel {
|
|
status: 206,
|
|
reason: "Partial Content".to_owned(),
|
|
version: HttpVersion::Http11,
|
|
headers: HttpResponseHeaders {
|
|
headers: vec![
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-length".to_owned(),
|
|
value: "2".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-range".to_owned(),
|
|
value: "bytes 8-9/10".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
],
|
|
},
|
|
body: ResponseBody::Inline(b"90".to_vec()),
|
|
content_range: Some(aria2_rust_pro_protocol::ContentRangeSpec {
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
start: 8,
|
|
end_inclusive: 9,
|
|
total_size: Some(10),
|
|
unsatisfied: false,
|
|
}),
|
|
partial_content: true,
|
|
checksum: None,
|
|
redirected_from: None,
|
|
}),
|
|
]);
|
|
let temp_dir = std::env::temp_dir().join("aria2-rust-pro-cli-explicit-segments-test");
|
|
let _ = fs::create_dir_all(&temp_dir);
|
|
let config_path = temp_dir.join("aria2.conf");
|
|
fs::write(
|
|
&config_path,
|
|
"max-tries=3\nsplit=3\nmin-split-size=4\npiece-length=4\n",
|
|
)
|
|
.expect("config should write");
|
|
|
|
let report = execute_runtime_with_downloader(
|
|
Invocation::Run {
|
|
config_path: Some(config_path.clone()),
|
|
uris: vec!["http://example.com/segment-plan.bin".to_owned()],
|
|
},
|
|
&downloader,
|
|
)
|
|
.expect("runtime should execute via explicit segments");
|
|
|
|
assert_eq!(report.completed_download_count, 1);
|
|
assert_eq!(report.first_status.as_deref(), Some("complete"));
|
|
assert_eq!(report.first_completed_length, Some(10));
|
|
|
|
let recorded = downloader.recorded_requests();
|
|
let [bootstrap, first_followup, second_followup] = recorded.as_slice() else {
|
|
panic!("expected exactly three recorded requests");
|
|
};
|
|
assert_eq!(bootstrap.request.range, None);
|
|
let mut followup_ranges = [first_followup, second_followup]
|
|
.iter()
|
|
.map(|task| task.request.range)
|
|
.collect::<Vec<_>>();
|
|
followup_ranges.sort_by_key(|range| range.as_ref().map(|range| range.start));
|
|
assert_eq!(
|
|
followup_ranges,
|
|
vec![
|
|
Some(aria2_rust_pro_protocol::RangeSpec {
|
|
start: 4,
|
|
end_inclusive: Some(9),
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
}),
|
|
Some(aria2_rust_pro_protocol::RangeSpec {
|
|
start: 8,
|
|
end_inclusive: Some(9),
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
}),
|
|
]
|
|
);
|
|
|
|
let _ = fs::remove_file(config_path);
|
|
}
|
|
|
|
#[expect(
|
|
clippy::too_many_lines,
|
|
reason = "initial range-probe regression keeps bootstrap and follow-up range fixtures adjacent"
|
|
)]
|
|
#[test]
|
|
fn execute_runtime_uses_initial_range_probe_when_split_budget_allows_parallel_segments() {
|
|
let downloader = SequencedHttpDownloader::new(vec![
|
|
Ok(HttpResponseModel {
|
|
status: 206,
|
|
reason: "Partial Content".to_owned(),
|
|
version: HttpVersion::Http11,
|
|
headers: HttpResponseHeaders {
|
|
headers: vec![
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-length".to_owned(),
|
|
value: "4".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-range".to_owned(),
|
|
value: "bytes 0-3/10".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
],
|
|
},
|
|
body: ResponseBody::Inline(b"1234".to_vec()),
|
|
content_range: Some(aria2_rust_pro_protocol::ContentRangeSpec {
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
start: 0,
|
|
end_inclusive: 3,
|
|
total_size: Some(10),
|
|
unsatisfied: false,
|
|
}),
|
|
partial_content: true,
|
|
checksum: None,
|
|
redirected_from: None,
|
|
}),
|
|
Ok(HttpResponseModel {
|
|
status: 206,
|
|
reason: "Partial Content".to_owned(),
|
|
version: HttpVersion::Http11,
|
|
headers: HttpResponseHeaders {
|
|
headers: vec![
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-length".to_owned(),
|
|
value: "4".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-range".to_owned(),
|
|
value: "bytes 4-7/10".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
],
|
|
},
|
|
body: ResponseBody::Inline(b"5678".to_vec()),
|
|
content_range: Some(aria2_rust_pro_protocol::ContentRangeSpec {
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
start: 4,
|
|
end_inclusive: 7,
|
|
total_size: Some(10),
|
|
unsatisfied: false,
|
|
}),
|
|
partial_content: true,
|
|
checksum: None,
|
|
redirected_from: None,
|
|
}),
|
|
Ok(HttpResponseModel {
|
|
status: 206,
|
|
reason: "Partial Content".to_owned(),
|
|
version: HttpVersion::Http11,
|
|
headers: HttpResponseHeaders {
|
|
headers: vec![
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-length".to_owned(),
|
|
value: "2".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-range".to_owned(),
|
|
value: "bytes 8-9/10".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
],
|
|
},
|
|
body: ResponseBody::Inline(b"90".to_vec()),
|
|
content_range: Some(aria2_rust_pro_protocol::ContentRangeSpec {
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
start: 8,
|
|
end_inclusive: 9,
|
|
total_size: Some(10),
|
|
unsatisfied: false,
|
|
}),
|
|
partial_content: true,
|
|
checksum: None,
|
|
redirected_from: None,
|
|
}),
|
|
]);
|
|
let temp_dir = std::env::temp_dir().join("aria2-rust-pro-cli-segment-probe-test");
|
|
let _ = fs::create_dir_all(&temp_dir);
|
|
let config_path = temp_dir.join("aria2.conf");
|
|
fs::write(
|
|
&config_path,
|
|
"split=3\nmax-connection-per-server=3\nmin-split-size=4\npiece-length=4\n",
|
|
)
|
|
.expect("config should write");
|
|
|
|
let report = execute_runtime_with_downloader(
|
|
Invocation::Run {
|
|
config_path: Some(config_path.clone()),
|
|
uris: vec!["http://example.com/probe-plan.bin".to_owned()],
|
|
},
|
|
&downloader,
|
|
)
|
|
.expect("runtime should execute via initial range probe");
|
|
|
|
assert_eq!(report.completed_download_count, 1);
|
|
assert_eq!(report.first_status.as_deref(), Some("complete"));
|
|
assert_eq!(report.first_completed_length, Some(10));
|
|
|
|
let recorded = downloader.recorded_requests();
|
|
let [probe, first_followup, second_followup] = recorded.as_slice() else {
|
|
panic!("expected exactly three recorded requests");
|
|
};
|
|
assert_eq!(
|
|
probe.request.range,
|
|
Some(aria2_rust_pro_protocol::RangeSpec {
|
|
start: 0,
|
|
end_inclusive: Some(3),
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
})
|
|
);
|
|
let mut followup_ranges = [first_followup, second_followup]
|
|
.iter()
|
|
.map(|task| task.request.range)
|
|
.collect::<Vec<_>>();
|
|
followup_ranges.sort_by_key(|range| range.as_ref().map(|range| range.start));
|
|
assert_eq!(
|
|
followup_ranges,
|
|
vec![
|
|
Some(aria2_rust_pro_protocol::RangeSpec {
|
|
start: 4,
|
|
end_inclusive: Some(7),
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
}),
|
|
Some(aria2_rust_pro_protocol::RangeSpec {
|
|
start: 8,
|
|
end_inclusive: Some(9),
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
}),
|
|
]
|
|
);
|
|
|
|
let _ = fs::remove_file(config_path);
|
|
}
|
|
|
|
#[expect(
|
|
clippy::too_many_lines,
|
|
reason = "tiny-tail regression keeps the coalesced probe fixture next to the expected request ranges"
|
|
)]
|
|
#[test]
|
|
fn execute_runtime_coalesces_tiny_followup_tail_after_initial_probe() {
|
|
let downloader = SequencedHttpDownloader::new(vec![
|
|
Ok(HttpResponseModel {
|
|
status: 206,
|
|
reason: "Partial Content".to_owned(),
|
|
version: HttpVersion::Http11,
|
|
headers: HttpResponseHeaders {
|
|
headers: vec![
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-length".to_owned(),
|
|
value: "32".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-range".to_owned(),
|
|
value: "bytes 0-31/36".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
],
|
|
},
|
|
body: ResponseBody::Inline(b"1234567890abcdefghijklmnopqrstuv".to_vec()),
|
|
content_range: Some(aria2_rust_pro_protocol::ContentRangeSpec {
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
start: 0,
|
|
end_inclusive: 31,
|
|
total_size: Some(36),
|
|
unsatisfied: false,
|
|
}),
|
|
partial_content: true,
|
|
checksum: None,
|
|
redirected_from: None,
|
|
}),
|
|
Ok(HttpResponseModel {
|
|
status: 206,
|
|
reason: "Partial Content".to_owned(),
|
|
version: HttpVersion::Http11,
|
|
headers: HttpResponseHeaders {
|
|
headers: vec![
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-length".to_owned(),
|
|
value: "4".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
aria2_rust_pro_protocol::HttpHeader {
|
|
name: "content-range".to_owned(),
|
|
value: "bytes 32-35/36".to_owned(),
|
|
kind: aria2_rust_pro_protocol::HeaderKind::Response,
|
|
},
|
|
],
|
|
},
|
|
body: ResponseBody::Inline(b"ghij".to_vec()),
|
|
content_range: Some(aria2_rust_pro_protocol::ContentRangeSpec {
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
start: 32,
|
|
end_inclusive: 35,
|
|
total_size: Some(36),
|
|
unsatisfied: false,
|
|
}),
|
|
partial_content: true,
|
|
checksum: None,
|
|
redirected_from: None,
|
|
}),
|
|
]);
|
|
let temp_dir = std::env::temp_dir().join("aria2-rust-pro-cli-tiny-tail-coalesce-test");
|
|
let _ = fs::create_dir_all(&temp_dir);
|
|
let config_path = temp_dir.join("aria2.conf");
|
|
fs::write(
|
|
&config_path,
|
|
"split=4\nmax-connection-per-server=4\nmin-split-size=4\npiece-length=4\n",
|
|
)
|
|
.expect("config should write");
|
|
|
|
let report = execute_runtime_with_downloader(
|
|
Invocation::Run {
|
|
config_path: Some(config_path.clone()),
|
|
uris: vec!["http://example.com/coalesced-tail.bin".to_owned()],
|
|
},
|
|
&downloader,
|
|
)
|
|
.expect("runtime should coalesce tiny follow-up tail after probe");
|
|
|
|
assert_eq!(report.completed_download_count, 1);
|
|
assert_eq!(report.first_status.as_deref(), Some("complete"));
|
|
assert_eq!(report.first_completed_length, Some(36));
|
|
|
|
let recorded = downloader.recorded_requests();
|
|
let [probe, first_followup] = recorded.as_slice() else {
|
|
panic!("expected exactly two recorded requests");
|
|
};
|
|
assert_eq!(
|
|
probe.request.range,
|
|
Some(aria2_rust_pro_protocol::RangeSpec {
|
|
start: 0,
|
|
end_inclusive: Some(31),
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
})
|
|
);
|
|
assert_eq!(
|
|
first_followup.request.range,
|
|
Some(aria2_rust_pro_protocol::RangeSpec {
|
|
start: 32,
|
|
end_inclusive: Some(35),
|
|
unit: aria2_rust_pro_protocol::RangeUnit::Bytes,
|
|
})
|
|
);
|
|
|
|
let _ = fs::remove_file(config_path);
|
|
}
|