diff --git a/crates/lanspread-peer/src/download/planning.rs b/crates/lanspread-peer/src/download/planning.rs index 371a1e3..c378b19 100644 --- a/crates/lanspread-peer/src/download/planning.rs +++ b/crates/lanspread-peer/src/download/planning.rs @@ -225,12 +225,16 @@ mod tests { 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); + let assigned_bytes = [ + byte_counts.get(&peers[0]).copied().unwrap_or_default(), + byte_counts.get(&peers[1]).copied().unwrap_or_default(), + ]; + assert_eq!(assigned_bytes.iter().sum::(), 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}" + assigned_bytes[0].abs_diff(assigned_bytes[1]) <= CHUNK_SIZE, + "large file bytes should be balanced within one chunk: {} vs {}", + assigned_bytes[0], + assigned_bytes[1] ); } diff --git a/crates/lanspread-peer/src/handlers.rs b/crates/lanspread-peer/src/handlers.rs index 10e68c9..0ead982 100644 --- a/crates/lanspread-peer/src/handlers.rs +++ b/crates/lanspread-peer/src/handlers.rs @@ -1588,11 +1588,12 @@ mod tests { } fn test_ctx(game_dir: PathBuf) -> Ctx { + let state_dir = game_dir.join(".test-state"); Ctx::new( Arc::new(RwLock::new(PeerGameDB::new())), "peer".to_string(), - game_dir.clone(), - game_dir.join(".test-state"), + game_dir, + state_dir, Arc::new(FakeUnpacker), CancellationToken::new(), TaskTracker::new(), @@ -1688,15 +1689,15 @@ mod tests { game } - fn assert_active_update(event: PeerEvent, expected: Vec) { + fn assert_active_update(event: PeerEvent, expected: &[ActiveOperation]) { let PeerEvent::ActiveOperationsChanged { active_operations } = event else { panic!("expected ActiveOperationsChanged"); }; assert_eq!(active_operations, expected); } - fn active_update(id: &str, operation: ActiveOperationKind) -> Vec { - vec![ActiveOperation { + fn active_update(id: &str, operation: ActiveOperationKind) -> [ActiveOperation; 1] { + [ActiveOperation { id: id.to_string(), operation, }] @@ -1934,7 +1935,7 @@ mod tests { ); assert_active_update( recv_event(&mut rx).await, - vec![ActiveOperation { + &[ActiveOperation { id: "game".to_string(), operation: ActiveOperationKind::Updating, }], @@ -1971,12 +1972,12 @@ mod tests { assert!(token.is_cancelled()); assert_active_update( recv_event(&mut rx).await, - vec![ActiveOperation { + &[ActiveOperation { id: "game".to_string(), operation: ActiveOperationKind::Updating, }], ); - assert_active_update(recv_event(&mut rx).await, Vec::new()); + assert_active_update(recv_event(&mut rx).await, &[]); assert!( !ctx.active_operations.read().await.contains_key("game"), "timed-out drain should not leave the operation stuck active" @@ -2029,7 +2030,7 @@ mod tests { assert_active_update( recv_event(&mut rx).await, - active_update("game", ActiveOperationKind::Updating), + &active_update("game", ActiveOperationKind::Updating), ); assert!(matches!( recv_event(&mut rx).await, @@ -2038,7 +2039,7 @@ mod tests { operation: InstallOperation::Updating } if id == "game" )); - assert_active_update(recv_event(&mut rx).await, Vec::new()); + assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( recv_event(&mut rx).await, PeerEvent::InstallGameFinished { id } if id == "game" @@ -2060,7 +2061,7 @@ mod tests { assert_active_update( recv_event(&mut rx).await, - active_update("game", ActiveOperationKind::Installing), + &active_update("game", ActiveOperationKind::Installing), ); match recv_event(&mut rx).await { PeerEvent::InstallGameBegin { id, operation } => { @@ -2070,7 +2071,7 @@ mod tests { _ => panic!("expected InstallGameBegin"), } assert_local_update(recv_event(&mut rx).await, true, true); - assert_active_update(recv_event(&mut rx).await, Vec::new()); + assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( recv_event(&mut rx).await, PeerEvent::InstallGameFinished { id } if id == "game" @@ -2121,7 +2122,7 @@ mod tests { assert_active_update( recv_event(&mut rx).await, - active_update("game", ActiveOperationKind::Installing), + &active_update("game", ActiveOperationKind::Installing), ); match recv_event(&mut rx).await { PeerEvent::InstallGameBegin { id, operation } => { @@ -2131,7 +2132,7 @@ mod tests { _ => panic!("expected InstallGameBegin"), } assert_local_update(recv_event(&mut rx).await, true, true); - assert_active_update(recv_event(&mut rx).await, Vec::new()); + assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( recv_event(&mut rx).await, PeerEvent::InstallGameFinished { id } if id == "game" @@ -2181,7 +2182,7 @@ mod tests { assert_active_update( recv_event(&mut rx).await, - active_update("game", ActiveOperationKind::Updating), + &active_update("game", ActiveOperationKind::Updating), ); match recv_event(&mut rx).await { PeerEvent::InstallGameBegin { id, operation } => { @@ -2191,7 +2192,7 @@ mod tests { _ => panic!("expected InstallGameBegin"), } assert_local_update(recv_event(&mut rx).await, true, true); - assert_active_update(recv_event(&mut rx).await, Vec::new()); + assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( recv_event(&mut rx).await, PeerEvent::InstallGameFinished { id } if id == "game" @@ -2212,7 +2213,7 @@ mod tests { run_install_operation(&ctx, &tx, "game".to_string()).await; assert_active_update( recv_event(&mut rx).await, - active_update("game", ActiveOperationKind::Installing), + &active_update("game", ActiveOperationKind::Installing), ); assert!(matches!( recv_event(&mut rx).await, @@ -2223,7 +2224,7 @@ mod tests { )); let game = local_update_game(recv_event(&mut rx).await, true, true); assert_eq!(game.local_version.as_deref(), Some("20240101")); - assert_active_update(recv_event(&mut rx).await, Vec::new()); + assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( recv_event(&mut rx).await, PeerEvent::InstallGameFinished { id } if id == "game" @@ -2235,7 +2236,7 @@ mod tests { run_install_operation(&ctx, &tx, "game".to_string()).await; assert_active_update( recv_event(&mut rx).await, - active_update("game", ActiveOperationKind::Updating), + &active_update("game", ActiveOperationKind::Updating), ); assert!(matches!( recv_event(&mut rx).await, @@ -2246,7 +2247,7 @@ mod tests { )); let game = local_update_game(recv_event(&mut rx).await, true, true); assert_eq!(game.local_version.as_deref(), Some("20250101")); - assert_active_update(recv_event(&mut rx).await, Vec::new()); + assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( recv_event(&mut rx).await, PeerEvent::InstallGameFinished { id } if id == "game" @@ -2255,7 +2256,7 @@ mod tests { run_uninstall_operation(&ctx, &tx, "game".to_string()).await; assert_active_update( recv_event(&mut rx).await, - active_update("game", ActiveOperationKind::Uninstalling), + &active_update("game", ActiveOperationKind::Uninstalling), ); assert!(matches!( recv_event(&mut rx).await, @@ -2263,7 +2264,7 @@ mod tests { )); let game = local_update_game(recv_event(&mut rx).await, false, true); assert_eq!(game.local_version.as_deref(), Some("20250101")); - assert_active_update(recv_event(&mut rx).await, Vec::new()); + assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( recv_event(&mut rx).await, PeerEvent::UninstallGameFinished { id } if id == "game" @@ -2286,14 +2287,14 @@ mod tests { assert_active_update( recv_event(&mut rx).await, - active_update("game", ActiveOperationKind::Uninstalling), + &active_update("game", ActiveOperationKind::Uninstalling), ); assert!(matches!( recv_event(&mut rx).await, PeerEvent::UninstallGameBegin { id } if id == "game" )); assert_local_update(recv_event(&mut rx).await, false, true); - assert_active_update(recv_event(&mut rx).await, Vec::new()); + assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( recv_event(&mut rx).await, PeerEvent::UninstallGameFinished { id } if id == "game" @@ -2321,7 +2322,7 @@ mod tests { assert_active_update( recv_event(&mut rx).await, - active_update("game", ActiveOperationKind::RemovingDownload), + &active_update("game", ActiveOperationKind::RemovingDownload), ); assert!(matches!( recv_event(&mut rx).await, @@ -2331,7 +2332,7 @@ mod tests { panic!("expected LocalLibraryChanged"); }; assert!(games.is_empty()); - assert_active_update(recv_event(&mut rx).await, Vec::new()); + assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( recv_event(&mut rx).await, PeerEvent::RemoveDownloadedGameFinished { id } if id == "game" diff --git a/crates/lanspread-peer/src/install/transaction.rs b/crates/lanspread-peer/src/install/transaction.rs index 531cba9..eb9fc66 100644 --- a/crates/lanspread-peer/src/install/transaction.rs +++ b/crates/lanspread-peer/src/install/transaction.rs @@ -1044,53 +1044,8 @@ mod tests { const INSTALLING_PAYLOAD: &[u8] = b"installing"; const BACKUP_PAYLOAD: &[u8] = b"backup"; - fn seed_recovery_case(root: &Path, case: &RecoveryCase) { - write_file(&root.join("version.ini"), b"20250101"); - if case.has_local { - write_file(&root.join(LOCAL_DIR).join("payload.txt"), LOCAL_PAYLOAD); - } - if case.has_installing { - write_file( - &root.join(INSTALLING_DIR).join("payload.txt"), - INSTALLING_PAYLOAD, - ); - } - if case.has_backup { - write_file(&root.join(BACKUP_DIR).join("payload.txt"), BACKUP_PAYLOAD); - } - } - - fn assert_recovered_case(root: &Path, case: &RecoveryCase) { - let local_payload = root.join(LOCAL_DIR).join("payload.txt"); - match case.expected_local_payload { - Some(expected) => assert_eq!( - std::fs::read(&local_payload) - .unwrap_or_else(|err| panic!("{} local payload: {err}", case.name)), - expected, - "{} local payload", - case.name - ), - None => assert!( - !root.join(LOCAL_DIR).exists(), - "{} local dir should be absent", - case.name - ), - } - assert!( - !root.join(INSTALLING_DIR).exists(), - "{} installing dir should be absent", - case.name - ); - assert!( - !root.join(BACKUP_DIR).exists(), - "{} backup dir should be absent", - case.name - ); - } - - #[tokio::test] - async fn recovery_covers_install_matrix_rows() { - let cases = [ + fn recovery_cases() -> [RecoveryCase; 10] { + [ RecoveryCase { name: "installing_commit_landed", intent_state: InstallIntentState::Installing, @@ -1171,35 +1126,86 @@ mod tests { has_backup: false, expected_local_payload: None, }, - ]; + ] + } - let state = test_state(); - for case in cases { - let temp = TempDir::new("lanspread-install"); - let root = temp.game_root(); - seed_recovery_case(&root, &case); - write_intent( - state.path(), - "game", - &InstallIntent::new("game", case.intent_state.clone(), Some("20250101".into())), - ) - .await - .unwrap_or_else(|err| panic!("{} intent should be written: {err}", case.name)); - - recover_game_root(&root, state.path(), "game") - .await - .unwrap_or_else(|err| panic!("{} recovery should succeed: {err}", case.name)); - - assert_recovered_case(&root, &case); - let intent = read_intent(state.path(), "game").await; - assert_eq!(intent.state, InstallIntentState::None, "{}", case.name); - assert_eq!( - intent.eti_version.as_deref(), - Some("20250101"), - "{}", - case.name + fn seed_recovery_case(root: &Path, case: &RecoveryCase) { + write_file(&root.join("version.ini"), b"20250101"); + if case.has_local { + write_file(&root.join(LOCAL_DIR).join("payload.txt"), LOCAL_PAYLOAD); + } + if case.has_installing { + write_file( + &root.join(INSTALLING_DIR).join("payload.txt"), + INSTALLING_PAYLOAD, ); } + if case.has_backup { + write_file(&root.join(BACKUP_DIR).join("payload.txt"), BACKUP_PAYLOAD); + } + } + + fn assert_recovered_case(root: &Path, case: &RecoveryCase) { + let local_payload = root.join(LOCAL_DIR).join("payload.txt"); + match case.expected_local_payload { + Some(expected) => assert_eq!( + std::fs::read(&local_payload) + .unwrap_or_else(|err| panic!("{} local payload: {err}", case.name)), + expected, + "{} local payload", + case.name + ), + None => assert!( + !root.join(LOCAL_DIR).exists(), + "{} local dir should be absent", + case.name + ), + } + assert!( + !root.join(INSTALLING_DIR).exists(), + "{} installing dir should be absent", + case.name + ); + assert!( + !root.join(BACKUP_DIR).exists(), + "{} backup dir should be absent", + case.name + ); + } + + async fn assert_recovery_case(state_dir: &Path, case: RecoveryCase) { + let temp = TempDir::new("lanspread-install"); + let root = temp.game_root(); + seed_recovery_case(&root, &case); + write_intent( + state_dir, + "game", + &InstallIntent::new("game", case.intent_state.clone(), Some("20250101".into())), + ) + .await + .unwrap_or_else(|err| panic!("{} intent should be written: {err}", case.name)); + + recover_game_root(&root, state_dir, "game") + .await + .unwrap_or_else(|err| panic!("{} recovery should succeed: {err}", case.name)); + + assert_recovered_case(&root, &case); + let intent = read_intent(state_dir, "game").await; + assert_eq!(intent.state, InstallIntentState::None, "{}", case.name); + assert_eq!( + intent.eti_version.as_deref(), + Some("20250101"), + "{}", + case.name + ); + } + + #[tokio::test] + async fn recovery_covers_install_matrix_rows() { + let state = test_state(); + for case in recovery_cases() { + assert_recovery_case(state.path(), case).await; + } } #[tokio::test] diff --git a/crates/lanspread-peer/src/services/local_monitor.rs b/crates/lanspread-peer/src/services/local_monitor.rs index f0ce2e9..6103e6f 100644 --- a/crates/lanspread-peer/src/services/local_monitor.rs +++ b/crates/lanspread-peer/src/services/local_monitor.rs @@ -374,11 +374,12 @@ mod tests { } fn test_ctx(game_dir: PathBuf, catalog: GameCatalog) -> Ctx { + let state_dir = game_dir.join(".test-state"); Ctx::new( Arc::new(RwLock::new(PeerGameDB::new())), "peer".to_string(), - game_dir.clone(), - game_dir.join(".test-state"), + game_dir, + state_dir, Arc::new(NoopUnpacker), CancellationToken::new(), TaskTracker::new(), @@ -388,8 +389,8 @@ mod tests { ) } - fn watch_event(path: PathBuf) -> notify::Result { - Ok(Event::new(EventKind::Any).add_path(path)) + fn watch_event(path: PathBuf) -> Event { + Event::new(EventKind::Any).add_path(path) } async fn recv_local_update( @@ -460,7 +461,7 @@ mod tests { &ctx, &tx, &gate, - watch_event(temp.path().join("game").join("version.ini")), + Ok(watch_event(temp.path().join("game").join("version.ini"))), ) .await; ctx.task_tracker.close(); diff --git a/crates/lanspread-peer/src/services/stream.rs b/crates/lanspread-peer/src/services/stream.rs index 7dee903..5e906fe 100644 --- a/crates/lanspread-peer/src/services/stream.rs +++ b/crates/lanspread-peer/src/services/stream.rs @@ -479,11 +479,12 @@ mod tests { fn test_ctx(game_dir: PathBuf, catalog: GameCatalog) -> PeerCtx { let (tx_notify_ui, _rx) = mpsc::unbounded_channel(); + let state_dir = game_dir.join(".test-state"); Ctx::new( Arc::new(RwLock::new(PeerGameDB::new())), "peer".to_string(), - game_dir.clone(), - game_dir.join(".test-state"), + game_dir, + state_dir, Arc::new(NoopUnpacker), CancellationToken::new(), TaskTracker::new(), diff --git a/crates/lanspread-peer/src/stream_install.rs b/crates/lanspread-peer/src/stream_install.rs index 1849f8f..afe0c36 100644 --- a/crates/lanspread-peer/src/stream_install.rs +++ b/crates/lanspread-peer/src/stream_install.rs @@ -918,15 +918,11 @@ Details: RAR 5 #[test] fn sender_archive_integrity_accepts_matching_size_and_crc32() { let bytes = b"payload"; - let integrity = - SenderArchiveIntegrity::new(u64::try_from(bytes.len()).unwrap(), crc32_of(bytes)); + let byte_len = u64::try_from(bytes.len()).expect("test payload length should fit in u64"); + let integrity = SenderArchiveIntegrity::new(byte_len, crc32_of(bytes)); integrity - .verify( - "bin/payload.bin", - u64::try_from(bytes.len()).unwrap(), - crc32_of(bytes), - ) + .verify("bin/payload.bin", byte_len, crc32_of(bytes)) .expect("matching sender archive metadata should verify"); }