test(peer): consolidate temp dir helper

Move the repeated test TempDir implementations into a single peer
test_support module. The shared helper keeps the existing automatic cleanup
behavior and uses an atomic suffix plus timestamp so parallel tests do not
collide on the same path.

This is intentionally limited to test hygiene. It does not change the
availability model, split download.rs, or touch production scan/install
behavior beyond importing the shared helper from test modules.

Test Plan:
- git diff --check
- just fmt
- just clippy
- just test

Follow-up-Plan: FOLLOW_UP_2.md
This commit is contained in:
2026-05-16 09:21:43 +02:00
parent 7731a9daa0
commit 894eb5af6a
10 changed files with 103 additions and 307 deletions
+5 -32
View File
@@ -920,39 +920,12 @@ pub async fn download_game_files(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::test_support::TempDir;
fn loopback_addr(port: u16) -> SocketAddr { fn loopback_addr(port: u16) -> SocketAddr {
SocketAddr::from(([127, 0, 0, 1], port)) SocketAddr::from(([127, 0, 0, 1], port))
} }
struct TempDir(PathBuf);
impl TempDir {
fn new() -> Self {
let mut path = std::env::temp_dir();
path.push(format!(
"lanspread-download-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
));
std::fs::create_dir_all(&path).expect("temp dir should be created");
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
#[test] #[test]
fn build_peer_plans_handles_partial_final_chunk() { fn build_peer_plans_handles_partial_final_chunk() {
let peers = vec![loopback_addr(12000), loopback_addr(12001)]; let peers = vec![loopback_addr(12000), loopback_addr(12001)];
@@ -1041,7 +1014,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn prepare_game_storage_skips_version_ini_sentinel() { async fn prepare_game_storage_skips_version_ini_sentinel() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-download");
let descs = vec![GameFileDescription { let descs = vec![GameFileDescription {
game_id: "game".to_string(), game_id: "game".to_string(),
relative_path: "game/version.ini".to_string(), relative_path: "game/version.ini".to_string(),
@@ -1080,7 +1053,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn commit_version_ini_writes_sentinel_last_and_sweeps_discarded() { async fn commit_version_ini_writes_sentinel_last_and_sweeps_discarded() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-download");
let game_root = temp.path().join("game"); let game_root = temp.path().join("game");
tokio::fs::create_dir_all(&game_root) tokio::fs::create_dir_all(&game_root)
.await .await
@@ -1115,7 +1088,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn begin_version_ini_transaction_parks_existing_sentinel() { async fn begin_version_ini_transaction_parks_existing_sentinel() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-download");
let game_root = temp.path().join("game"); let game_root = temp.path().join("game");
tokio::fs::create_dir_all(&game_root) tokio::fs::create_dir_all(&game_root)
.await .await
@@ -1142,7 +1115,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn rollback_version_ini_transaction_sweeps_transients() { async fn rollback_version_ini_transaction_sweeps_transients() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-download");
let game_root = temp.path().join("game"); let game_root = temp.path().join("game");
tokio::fs::create_dir_all(&game_root) tokio::fs::create_dir_all(&game_root)
.await .await
+2 -34
View File
@@ -652,46 +652,14 @@ mod tests {
collections::HashSet, collections::HashSet,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
time::{Duration, SystemTime, UNIX_EPOCH}, time::Duration,
}; };
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio_util::{sync::CancellationToken, task::TaskTracker}; use tokio_util::{sync::CancellationToken, task::TaskTracker};
use super::*; use super::*;
use crate::{UnpackFuture, Unpacker}; use crate::{UnpackFuture, Unpacker, test_support::TempDir};
struct TempDir(PathBuf);
impl TempDir {
fn new(prefix: &str) -> Self {
let mut path = std::env::temp_dir();
path.push(format!(
"{prefix}-{}-{}",
std::process::id(),
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
));
std::fs::create_dir_all(&path).expect("temp dir should be created");
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
fn game_root(&self) -> PathBuf {
self.0.join("game")
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
struct FakeUnpacker; struct FakeUnpacker;
+6 -33
View File
@@ -119,39 +119,12 @@ fn sync_parent_dir(_path: &Path) -> std::io::Result<()> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::path::{Path, PathBuf};
use super::*; use super::*;
use crate::test_support::TempDir;
struct TempDir(PathBuf);
impl TempDir {
fn new() -> Self {
let mut path = std::env::temp_dir();
path.push(format!(
"lanspread-intent-{}-{}",
std::process::id(),
now_unix_secs()
));
path.push(format!("{:?}", std::thread::current().id()).replace(['(', ')'], ""));
std::fs::create_dir_all(&path).expect("temp dir should be created");
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
#[tokio::test] #[tokio::test]
async fn tmp_write_without_rename_leaves_previous_intent_intact() { async fn tmp_write_without_rename_leaves_previous_intent_intact() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-intent");
let previous = InstallIntent::new( let previous = InstallIntent::new(
"game", "game",
InstallIntentState::Updating, InstallIntentState::Updating,
@@ -180,7 +153,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn schema_mismatch_is_treated_as_missing() { async fn schema_mismatch_is_treated_as_missing() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-intent");
tokio::fs::write( tokio::fs::write(
intent_path(temp.path()), intent_path(temp.path()),
r#"{"schema_version":2,"id":"game","recorded_at":0,"state":"Updating"}"#, r#"{"schema_version":2,"id":"game","recorded_at":0,"state":"Updating"}"#,
@@ -194,7 +167,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn mismatched_id_is_treated_as_missing() { async fn mismatched_id_is_treated_as_missing() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-intent");
tokio::fs::write( tokio::fs::write(
intent_path(temp.path()), intent_path(temp.path()),
r#"{"schema_version":1,"id":"other","recorded_at":0,"state":"Updating"}"#, r#"{"schema_version":1,"id":"other","recorded_at":0,"state":"Updating"}"#,
@@ -208,7 +181,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn corrupt_intent_is_treated_as_missing() { async fn corrupt_intent_is_treated_as_missing() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-intent");
tokio::fs::write(intent_path(temp.path()), b"not json") tokio::fs::write(intent_path(temp.path()), b"not json")
.await .await
.expect("intent should be written"); .expect("intent should be written");
@@ -219,7 +192,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn old_manifest_hash_field_is_ignored_and_new_writes_omit_it() { async fn old_manifest_hash_field_is_ignored_and_new_writes_omit_it() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-intent");
tokio::fs::write( tokio::fs::write(
intent_path(temp.path()), intent_path(temp.path()),
r#"{"schema_version":1,"id":"game","recorded_at":0,"state":"Updating","eti_version":"20240101","manifest_hash":42}"#, r#"{"schema_version":1,"id":"game","recorded_at":0,"state":"Updating","eti_version":"20240101","manifest_hash":42}"#,
@@ -464,15 +464,11 @@ mod tests {
use std::{ use std::{
collections::HashSet, collections::HashSet,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{ sync::{Arc, Mutex},
Arc,
Mutex,
atomic::{AtomicU64, Ordering},
},
}; };
use super::*; use super::*;
use crate::install::unpack::UnpackFuture; use crate::{install::unpack::UnpackFuture, test_support::TempDir};
#[derive(Default)] #[derive(Default)]
struct FakeUnpacker { struct FakeUnpacker {
@@ -523,38 +519,6 @@ mod tests {
} }
} }
struct TempDir(PathBuf);
static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0);
impl TempDir {
fn new() -> Self {
let mut path = std::env::temp_dir();
let unique_id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed);
path.push(format!(
"lanspread-install-{}-{}-{}",
std::process::id(),
unique_id,
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
));
std::fs::create_dir_all(&path).expect("temp dir should be created");
Self(path)
}
fn game_root(&self) -> PathBuf {
self.0.join("game")
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn write_file(path: &Path, bytes: &[u8]) { fn write_file(path: &Path, bytes: &[u8]) {
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("parent dir should be created"); std::fs::create_dir_all(parent).expect("parent dir should be created");
@@ -568,7 +532,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn install_success_promotes_staging_and_clears_intent() { async fn install_success_promotes_staging_and_clears_intent() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-install");
let root = temp.game_root(); let root = temp.game_root();
write_file(&root.join("game.eti"), b"archive"); write_file(&root.join("game.eti"), b"archive");
write_file(&root.join("version.ini"), b"20250101"); write_file(&root.join("version.ini"), b"20250101");
@@ -585,7 +549,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn install_unpacks_multiple_root_eti_archives_in_sorted_order() { async fn install_unpacks_multiple_root_eti_archives_in_sorted_order() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-install");
let root = temp.game_root(); let root = temp.game_root();
write_file(&root.join("b.eti"), b"archive"); write_file(&root.join("b.eti"), b"archive");
write_file(&root.join("a.eti"), b"archive"); write_file(&root.join("a.eti"), b"archive");
@@ -608,7 +572,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn update_failure_restores_previous_local() { async fn update_failure_restores_previous_local() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-install");
let root = temp.game_root(); let root = temp.game_root();
write_file(&root.join("game.eti"), b"archive"); write_file(&root.join("game.eti"), b"archive");
write_file(&root.join("version.ini"), b"20250101"); write_file(&root.join("version.ini"), b"20250101");
@@ -628,7 +592,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn update_commit_rename_failure_restores_previous_local() { async fn update_commit_rename_failure_restores_previous_local() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-install");
let root = temp.game_root(); let root = temp.game_root();
write_file(&root.join("game.eti"), b"archive"); write_file(&root.join("game.eti"), b"archive");
write_file(&root.join("version.ini"), b"20250101"); write_file(&root.join("version.ini"), b"20250101");
@@ -656,7 +620,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn update_success_promotes_new_local_and_removes_backup() { async fn update_success_promotes_new_local_and_removes_backup() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-install");
let root = temp.game_root(); let root = temp.game_root();
write_file(&root.join("game.eti"), b"archive"); write_file(&root.join("game.eti"), b"archive");
write_file(&root.join("version.ini"), b"20250101"); write_file(&root.join("version.ini"), b"20250101");
@@ -676,7 +640,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn uninstall_removes_only_local_install() { async fn uninstall_removes_only_local_install() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-install");
let root = temp.game_root(); let root = temp.game_root();
write_file(&root.join("game.eti"), b"archive"); write_file(&root.join("game.eti"), b"archive");
write_file(&root.join("version.ini"), b"20250101"); write_file(&root.join("version.ini"), b"20250101");
@@ -696,7 +660,7 @@ mod tests {
async fn uninstall_delete_failure_restores_backup() { async fn uninstall_delete_failure_restores_backup() {
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
let temp = TempDir::new(); let temp = TempDir::new("lanspread-install");
let root = temp.game_root(); let root = temp.game_root();
let locked_dir = root.join("local").join("locked"); let locked_dir = root.join("local").join("locked");
write_file(&root.join("version.ini"), b"20250101"); write_file(&root.join("version.ini"), b"20250101");
@@ -881,7 +845,7 @@ mod tests {
]; ];
for case in cases { for case in cases {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-install");
let root = temp.game_root(); let root = temp.game_root();
seed_recovery_case(&root, &case); seed_recovery_case(&root, &case);
write_intent( write_intent(
@@ -909,7 +873,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn none_recovery_leaves_markerless_reserved_dirs_untouched() { async fn none_recovery_leaves_markerless_reserved_dirs_untouched() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-install");
let root = temp.game_root(); let root = temp.game_root();
write_file(&root.join(".local.backup").join("user.txt"), b"user"); write_file(&root.join(".local.backup").join("user.txt"), b"user");
@@ -922,7 +886,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn download_recovery_sweeps_reserved_version_files() { async fn download_recovery_sweeps_reserved_version_files() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-install");
let root = temp.game_root(); let root = temp.game_root();
write_file(&root.join(VERSION_TMP_FILE), b"tmp"); write_file(&root.join(VERSION_TMP_FILE), b"tmp");
write_file(&root.join(VERSION_DISCARDED_FILE), b"old"); write_file(&root.join(VERSION_DISCARDED_FILE), b"old");
@@ -937,13 +901,13 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn startup_recovery_skips_active_game_roots() { async fn startup_recovery_skips_active_game_roots() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-install");
let active_root = temp.0.join("active"); let active_root = temp.path().join("active");
let inactive_root = temp.0.join("inactive"); let inactive_root = temp.path().join("inactive");
write_file(&active_root.join(VERSION_TMP_FILE), b"tmp"); write_file(&active_root.join(VERSION_TMP_FILE), b"tmp");
write_file(&inactive_root.join(VERSION_TMP_FILE), b"tmp"); write_file(&inactive_root.join(VERSION_TMP_FILE), b"tmp");
recover_on_startup(&temp.0, &HashSet::from(["active".to_string()])) recover_on_startup(temp.path(), &HashSet::from(["active".to_string()]))
.await .await
.expect("recovery should succeed"); .expect("recovery should succeed");
+2
View File
@@ -29,6 +29,8 @@ mod peer_db;
mod remote_peer; mod remote_peer;
mod services; mod services;
mod startup; mod startup;
#[cfg(test)]
mod test_support;
// ============================================================================= // =============================================================================
// Public re-exports // Public re-exports
+5 -33
View File
@@ -620,41 +620,13 @@ pub async fn get_game_file_descriptions(
mod tests { mod tests {
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
path::{Path, PathBuf}, path::Path,
}; };
use lanspread_proto::Availability; use lanspread_proto::Availability;
use super::*; use super::*;
use crate::context::OperationKind; use crate::{context::OperationKind, test_support::TempDir};
struct TempDir(PathBuf);
impl TempDir {
fn new() -> Self {
let mut path = std::env::temp_dir();
path.push(format!(
"lanspread-local-games-{}-{}",
std::process::id(),
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
));
std::fs::create_dir_all(&path).expect("temp dir should be created");
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn write_file(path: &Path, bytes: &[u8]) { fn write_file(path: &Path, bytes: &[u8]) {
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
@@ -665,7 +637,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn scan_uses_version_ini_and_local_dir_as_independent_state() { async fn scan_uses_version_ini_and_local_dir_as_independent_state() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-local-games");
let catalog = HashSet::from([ let catalog = HashSet::from([
"ready".to_string(), "ready".to_string(),
"local-only".to_string(), "local-only".to_string(),
@@ -718,7 +690,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn rescan_promotes_installed_only_game_to_ready_when_sentinel_appears() { async fn rescan_promotes_installed_only_game_to_ready_when_sentinel_appears() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-local-games");
let catalog = HashSet::from(["game".to_string()]); let catalog = HashSet::from(["game".to_string()]);
std::fs::create_dir_all(temp.path().join("game").join("local")) std::fs::create_dir_all(temp.path().join("game").join("local"))
.expect("local install dir should be created"); .expect("local install dir should be created");
@@ -751,7 +723,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn local_download_available_gates_on_catalog_operation_and_sentinel() { async fn local_download_available_gates_on_catalog_operation_and_sentinel() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-local-games");
let game_root = temp.path().join("game"); let game_root = temp.path().join("game");
write_file(&game_root.join("version.ini"), b"20250101"); write_file(&game_root.join("version.ini"), b"20250101");
+9 -42
View File
@@ -99,44 +99,11 @@ pub fn validate_game_file_path(game_dir: &Path, relative_path: &str) -> eyre::Re
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::test_support::TempDir;
fn create_temp_dir() -> std::io::Result<std::path::PathBuf> {
let mut dir = std::env::temp_dir();
let unique = format!(
"lanspread_test_{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
);
dir.push(unique);
std::fs::create_dir_all(&dir)?;
Ok(dir)
}
struct TempDir(std::path::PathBuf);
impl TempDir {
fn new() -> std::io::Result<Self> {
let path = create_temp_dir()?;
Ok(TempDir(path))
}
fn path(&self) -> &std::path::Path {
&self.0
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
#[test] #[test]
fn test_valid_paths() { fn test_valid_paths() {
let temp_dir = TempDir::new().expect("Failed to create temp dir for test"); let temp_dir = TempDir::new("lanspread-path-validation");
let base = temp_dir.path(); let base = temp_dir.path();
// Valid relative paths // Valid relative paths
@@ -156,7 +123,7 @@ mod tests {
#[test] #[test]
fn test_traversal_attempts() { fn test_traversal_attempts() {
let temp_dir = TempDir::new().expect("Failed to create temp dir for test"); let temp_dir = TempDir::new("lanspread-path-validation");
let base = temp_dir.path(); let base = temp_dir.path();
// These should all fail // These should all fail
@@ -167,7 +134,7 @@ mod tests {
#[test] #[test]
fn test_double_dot_in_filename_allowed() { fn test_double_dot_in_filename_allowed() {
let temp_dir = TempDir::new().expect("Failed to create temp dir for test"); let temp_dir = TempDir::new("lanspread-path-validation");
let base = temp_dir.path(); let base = temp_dir.path();
assert!(validate_game_file_path(base, "data/file..txt").is_ok()); assert!(validate_game_file_path(base, "data/file..txt").is_ok());
@@ -175,7 +142,7 @@ mod tests {
#[test] #[test]
fn test_missing_file_stays_within_base() { fn test_missing_file_stays_within_base() {
let temp_dir = TempDir::new().expect("Failed to create temp dir for test"); let temp_dir = TempDir::new("lanspread-path-validation");
let base = temp_dir.path(); let base = temp_dir.path();
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
@@ -191,7 +158,7 @@ mod tests {
#[test] #[test]
fn test_absolute_paths() { fn test_absolute_paths() {
let temp_dir = TempDir::new().expect("Failed to create temp dir for test"); let temp_dir = TempDir::new("lanspread-path-validation");
let base = temp_dir.path(); let base = temp_dir.path();
// Absolute paths should fail // Absolute paths should fail
@@ -203,7 +170,7 @@ mod tests {
#[test] #[test]
fn test_windows_specific() { fn test_windows_specific() {
let temp_dir = TempDir::new().expect("Failed to create temp dir for test"); let temp_dir = TempDir::new("lanspread-path-validation");
let base = temp_dir.path(); let base = temp_dir.path();
// Windows-specific paths that should fail // Windows-specific paths that should fail
@@ -217,8 +184,8 @@ mod tests {
fn test_symlink_escape_rejected() { fn test_symlink_escape_rejected() {
use std::os::unix::fs::symlink; use std::os::unix::fs::symlink;
let base_dir = TempDir::new().expect("Failed to create base temp dir"); let base_dir = TempDir::new("lanspread-path-validation-base");
let outside_dir = TempDir::new().expect("Failed to create outside temp dir"); let outside_dir = TempDir::new("lanspread-path-validation-outside");
let base = base_dir.path(); let base = base_dir.path();
let outside = outside_dir.path(); let outside = outside_dir.path();
@@ -334,11 +334,8 @@ mod tests {
use std::{ use std::{
collections::HashSet, collections::HashSet,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{ sync::Arc,
Arc, time::Duration,
atomic::{AtomicU64, Ordering},
},
time::{Duration, SystemTime, UNIX_EPOCH},
}; };
use notify::EventKind; use notify::EventKind;
@@ -346,39 +343,13 @@ mod tests {
use tokio_util::{sync::CancellationToken, task::TaskTracker}; use tokio_util::{sync::CancellationToken, task::TaskTracker};
use super::*; use super::*;
use crate::{UnpackFuture, Unpacker, context::OperationKind, peer_db::PeerGameDB}; use crate::{
UnpackFuture,
struct TempDir(PathBuf); Unpacker,
context::OperationKind,
static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0); peer_db::PeerGameDB,
test_support::TempDir,
impl TempDir { };
fn new() -> Self {
let mut path = std::env::temp_dir();
let unique_id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed);
path.push(format!(
"lanspread-local-monitor-{}-{}-{}",
std::process::id(),
unique_id,
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
));
std::fs::create_dir_all(&path).expect("temp dir should be created");
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
struct NoopUnpacker; struct NoopUnpacker;
@@ -467,7 +438,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn watch_event_for_active_game_is_dropped() { async fn watch_event_for_active_game_is_dropped() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-local-monitor");
let ctx = test_ctx( let ctx = test_ctx(
temp.path().to_path_buf(), temp.path().to_path_buf(),
HashSet::from(["game".to_string()]), HashSet::from(["game".to_string()]),
@@ -501,7 +472,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn burst_watch_events_collapse_to_two_rescans_for_same_game() { async fn burst_watch_events_collapse_to_two_rescans_for_same_game() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-local-monitor");
let game_root = temp.path().join("game"); let game_root = temp.path().join("game");
write_file(&game_root.join("version.ini"), b"20250101"); write_file(&game_root.join("version.ini"), b"20250101");
let ctx = test_ctx( let ctx = test_ctx(
@@ -538,7 +509,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn fallback_scan_picks_up_sideloaded_catalog_game() { async fn fallback_scan_picks_up_sideloaded_catalog_game() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-local-monitor");
write_file(&temp.path().join("game").join("version.ini"), b"20250101"); write_file(&temp.path().join("game").join("version.ini"), b"20250101");
let ctx = test_ctx( let ctx = test_ctx(
temp.path().to_path_buf(), temp.path().to_path_buf(),
@@ -560,7 +531,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn fallback_scan_ignores_non_catalog_game_without_library_delta() { async fn fallback_scan_ignores_non_catalog_game_without_library_delta() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-local-monitor");
write_file( write_file(
&temp.path().join("non-catalog").join("version.ini"), &temp.path().join("non-catalog").join("version.ini"),
b"20250101", b"20250101",
+4 -39
View File
@@ -398,11 +398,7 @@ mod tests {
use std::{ use std::{
collections::HashSet, collections::HashSet,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{ sync::Arc,
Arc,
atomic::{AtomicU64, Ordering},
},
time::{SystemTime, UNIX_EPOCH},
}; };
use tokio::sync::{RwLock, mpsc}; use tokio::sync::{RwLock, mpsc};
@@ -414,40 +410,9 @@ mod tests {
Unpacker, Unpacker,
context::{Ctx, OperationKind}, context::{Ctx, OperationKind},
peer_db::PeerGameDB, peer_db::PeerGameDB,
test_support::TempDir,
}; };
struct TempDir(PathBuf);
static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0);
impl TempDir {
fn new() -> Self {
let mut path = std::env::temp_dir();
let unique_id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed);
path.push(format!(
"lanspread-stream-{}-{}-{}",
std::process::id(),
unique_id,
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
));
std::fs::create_dir_all(&path).expect("temp dir should be created");
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
struct NoopUnpacker; struct NoopUnpacker;
impl Unpacker for NoopUnpacker { impl Unpacker for NoopUnpacker {
@@ -488,7 +453,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn get_game_response_respects_serve_gates() { async fn get_game_response_respects_serve_gates() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-stream");
write_file(&temp.path().join("ready").join("version.ini"), b"20250101"); write_file(&temp.path().join("ready").join("version.ini"), b"20250101");
write_file( write_file(
&temp.path().join("non-catalog").join("version.ini"), &temp.path().join("non-catalog").join("version.ini"),
@@ -531,7 +496,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn file_transfer_dispatch_respects_serve_gates() { async fn file_transfer_dispatch_respects_serve_gates() {
let temp = TempDir::new(); let temp = TempDir::new("lanspread-stream");
write_file(&temp.path().join("ready").join("version.ini"), b"20250101"); write_file(&temp.path().join("ready").join("version.ini"), b"20250101");
write_file( write_file(
&temp.path().join("non-catalog").join("version.ini"), &temp.path().join("non-catalog").join("version.ini"),
+41
View File
@@ -0,0 +1,41 @@
use std::{
path::{Path, PathBuf},
sync::atomic::{AtomicU64, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0);
pub(crate) struct TempDir(PathBuf);
impl TempDir {
pub(crate) fn new(prefix: &str) -> Self {
let mut path = std::env::temp_dir();
let unique_id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed);
path.push(format!(
"{prefix}-{}-{}-{}",
std::process::id(),
unique_id,
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
));
std::fs::create_dir_all(&path).expect("temp dir should be created");
Self(path)
}
pub(crate) fn path(&self) -> &Path {
&self.0
}
pub(crate) fn game_root(&self) -> PathBuf {
self.0.join("game")
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}