test(peer-cli): add large exact-transfer coverage
Add deeper peer CLI coverage for file-transfer integrity and multi-peer chunking. The alpha fixture now carries a real renamed RAR archive larger than 100 MB for alienswarm, which gives the chunk planner enough work to split a single game archive across multiple peers. Expose completed chunk source details as a peer event and have the CLI print that event as JSONL. This keeps transfer behavior in lanspread-peer while the CLI remains a harness that reports what the peer runtime did. The Tauri shell logs the event at debug level so the shared PeerEvent enum stays exhaustive. Document the new S13/S14 scenarios and record the manual run evidence, including SHA-256 manifests and the per-peer byte split for the large archive. Test Plan: - just fmt - just test - just peer-cli-build - just clippy - just peer-cli-image - unrar t -idq crates/lanspread-peer-cli/fixtures/fixture-alpha/alienswarm/alienswarm.eti - Manual peer CLI: bravo -> deep-small-client bfbc2 download with matching SHA-256 manifests - Manual peer CLI: alpha -> deep-stage-b alienswarm download with matching SHA-256 manifests - Manual peer CLI: alpha + deep-stage-b -> deep-stage-c alienswarm download with chunk events from both peers and matching SHA-256 manifests Refs: PEER_CLI_SCENARIOS.md S13 S14
This commit is contained in:
@@ -220,6 +220,54 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_peer_plans_spreads_large_file_chunks_across_shared_peers() {
|
||||
let peers = vec![loopback_addr(12000), loopback_addr(12001)];
|
||||
let large_file = "game/large.eti";
|
||||
let file_size = 120 * 1024 * 1024;
|
||||
let mut file_peer_map = HashMap::new();
|
||||
file_peer_map.insert("game/version.ini".to_string(), peers.clone());
|
||||
file_peer_map.insert(large_file.to_string(), peers.clone());
|
||||
let file_descs = vec![
|
||||
GameFileDescription {
|
||||
game_id: "game".to_string(),
|
||||
relative_path: "game/version.ini".to_string(),
|
||||
is_dir: false,
|
||||
size: 9,
|
||||
},
|
||||
GameFileDescription {
|
||||
game_id: "game".to_string(),
|
||||
relative_path: large_file.to_string(),
|
||||
is_dir: false,
|
||||
size: file_size,
|
||||
},
|
||||
];
|
||||
|
||||
let plans = build_peer_plans(&peers, &file_descs, &file_peer_map);
|
||||
let mut chunk_counts = HashMap::new();
|
||||
let mut byte_counts = HashMap::new();
|
||||
|
||||
for (peer, plan) in plans {
|
||||
for chunk in plan.chunks {
|
||||
if chunk.relative_path == large_file {
|
||||
*chunk_counts.entry(peer).or_insert(0usize) += 1;
|
||||
*byte_counts.entry(peer).or_insert(0u64) += chunk.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(chunk_counts.get(&peers[0]), Some(&2));
|
||||
assert_eq!(chunk_counts.get(&peers[1]), Some(&2));
|
||||
|
||||
let peer_a_bytes = byte_counts.get(&peers[0]).copied().unwrap_or_default();
|
||||
let peer_b_bytes = byte_counts.get(&peers[1]).copied().unwrap_or_default();
|
||||
assert_eq!(peer_a_bytes + peer_b_bytes, file_size);
|
||||
assert!(
|
||||
peer_a_bytes.abs_diff(peer_b_bytes) <= CHUNK_SIZE,
|
||||
"large file bytes should be balanced within one chunk: {peer_a_bytes} vs {peer_b_bytes}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_peer_plans_respects_file_peer_map() {
|
||||
let shared_a = loopback_addr(12010);
|
||||
|
||||
Reference in New Issue
Block a user