From 5e4527c661fc5410294d760d6edcaad7d839a54a Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 12:41:47 -0400 Subject: [PATCH] test: fix temp-fixture directory collisions in parser test suites `TempLogFixture::new` (and the shared `TempBenchFile`) derived their temp directory name from `SystemTime::now().as_nanos()`. Clock granularity is coarser than a nanosecond, so two fixtures constructed in the same observed tick got the same path. Because each impl'd `Drop` as `remove_dir_all(&dir)`, whichever test finished first deleted the other's fixture file; the victim then parsed zero entries and panicked indexing `entries[0]`. The harness runs each test binary's tests on parallel threads, so this made `cargo test` intermittently fail (roughly 1 run in 5 on an M-series Mac). Switch all four fixtures to `tempfile::TempDir` (already a dev-dependency), which creates the directory with a genuinely unique name and removes it on drop, so the hand-rolled `Drop` impls go away too. Verified with 12 consecutive clean `cargo test --locked` runs plus one `--all-features` run; `cargo clippy --all-targets -- -D warnings` is clean. Co-Authored-By: Claude Opus 5 --- src-tauri/tests/cmtlog_parser.rs | 26 +++++++-------------- src-tauri/tests/common/mod.rs | 25 +++++++------------- src-tauri/tests/parser_expanded_corpus.rs | 23 ++++++------------ src-tauri/tests/parser_regression_corpus.rs | 26 +++++++-------------- 4 files changed, 33 insertions(+), 67 deletions(-) diff --git a/src-tauri/tests/cmtlog_parser.rs b/src-tauri/tests/cmtlog_parser.rs index 5376befa8..1bd020a46 100644 --- a/src-tauri/tests/cmtlog_parser.rs +++ b/src-tauri/tests/cmtlog_parser.rs @@ -2,26 +2,24 @@ mod common; use std::fs; use std::path::PathBuf; -use std::time::{SystemTime, UNIX_EPOCH}; + +use tempfile::TempDir; struct TempLogFixture { - dir: PathBuf, + /// Held so the directory outlives the fixture; `TempDir` removes it on drop. + _dir: TempDir, path: PathBuf, } impl TempLogFixture { fn new(file_name: &str, content: &str) -> Self { - let unique = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("system time before unix epoch") - .as_nanos(); - let dir = std::env::temp_dir().join(format!("cmtrace-open-cmtlog-test-{unique}")); - fs::create_dir_all(&dir).expect("create temp fixture dir"); - - let path = dir.join(file_name); + let dir = + TempDir::with_prefix("cmtrace-open-cmtlog-test-").expect("create temp fixture dir"); + + let path = dir.path().join(file_name); fs::write(&path, content).expect("write temp fixture"); - Self { dir, path } + Self { _dir: dir, path } } fn detect(&self) -> app_lib::parser::ResolvedParser { @@ -30,12 +28,6 @@ impl TempLogFixture { } } -impl Drop for TempLogFixture { - fn drop(&mut self) { - let _ = fs::remove_dir_all(&self.dir); - } -} - fn fixture_content() -> &'static str { concat!( "\n", diff --git a/src-tauri/tests/common/mod.rs b/src-tauri/tests/common/mod.rs index b5097d72c..342626c60 100644 --- a/src-tauri/tests/common/mod.rs +++ b/src-tauri/tests/common/mod.rs @@ -3,26 +3,23 @@ use std::fmt::Write; use std::fs; use std::path::{Path, PathBuf}; -use std::time::{SystemTime, UNIX_EPOCH}; + +use tempfile::TempDir; pub struct TempBenchFile { - dir: PathBuf, + /// Held so the directory outlives the fixture; `TempDir` removes it on drop. + _dir: TempDir, path: PathBuf, } impl TempBenchFile { pub fn new(file_name: &str, content: String) -> Self { - let unique = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("system time before unix epoch") - .as_nanos(); - let dir = std::env::temp_dir().join(format!("cmtrace-open-bench-{unique}")); - fs::create_dir_all(&dir).expect("create temp benchmark dir"); - - let path = dir.join(file_name); + let dir = TempDir::with_prefix("cmtrace-open-bench-").expect("create temp benchmark dir"); + + let path = dir.path().join(file_name); fs::write(&path, content).expect("write benchmark fixture"); - Self { dir, path } + Self { _dir: dir, path } } pub fn path(&self) -> &Path { @@ -34,12 +31,6 @@ impl TempBenchFile { } } -impl Drop for TempBenchFile { - fn drop(&mut self) { - let _ = fs::remove_dir_all(&self.dir); - } -} - pub struct IntuneBenchFixture { file: TempBenchFile, pub logical_record_count: usize, diff --git a/src-tauri/tests/parser_expanded_corpus.rs b/src-tauri/tests/parser_expanded_corpus.rs index bf348808e..2e947f528 100644 --- a/src-tauri/tests/parser_expanded_corpus.rs +++ b/src-tauri/tests/parser_expanded_corpus.rs @@ -3,28 +3,25 @@ mod common; use common::{detect_fixture, parse_fixture}; use std::fs; use std::path::PathBuf; -use std::time::{SystemTime, UNIX_EPOCH}; + +use tempfile::TempDir; // --------------------------------------------------------------------------- // Helper: create a temp file for detection/parsing tests // --------------------------------------------------------------------------- struct TempLogFixture { - dir: PathBuf, + /// Held so the directory outlives the fixture; `TempDir` removes it on drop. + _dir: TempDir, path: PathBuf, } impl TempLogFixture { fn new(file_name: &str, content: &str) -> Self { - let unique = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("system time") - .as_nanos(); - let dir = std::env::temp_dir().join(format!("cmtrace-open-expanded-{unique}")); - fs::create_dir_all(&dir).expect("create temp dir"); - let path = dir.join(file_name); + let dir = TempDir::with_prefix("cmtrace-open-expanded-").expect("create temp dir"); + let path = dir.path().join(file_name); fs::write(&path, content).expect("write fixture"); - Self { dir, path } + Self { _dir: dir, path } } fn detect(&self) -> common::SelectionSnapshot { @@ -63,12 +60,6 @@ impl TempLogFixture { } } -impl Drop for TempLogFixture { - fn drop(&mut self) { - let _ = fs::remove_dir_all(&self.dir); - } -} - fn snapshot(s: &app_lib::parser::ResolvedParser) -> common::SelectionSnapshot { common::SelectionSnapshot { parser: format!("{:?}", s.parser), diff --git a/src-tauri/tests/parser_regression_corpus.rs b/src-tauri/tests/parser_regression_corpus.rs index 9dadfc22c..b64971e06 100644 --- a/src-tauri/tests/parser_regression_corpus.rs +++ b/src-tauri/tests/parser_regression_corpus.rs @@ -2,28 +2,26 @@ mod common; use std::fs; use std::path::PathBuf; -use std::time::{SystemTime, UNIX_EPOCH}; + +use tempfile::TempDir; use common::{detect_fixture, parse_fixture, ParsedFixture, SelectionSnapshot}; struct TempLogFixture { - dir: PathBuf, + /// Held so the directory outlives the fixture; `TempDir` removes it on drop. + _dir: TempDir, path: PathBuf, } impl TempLogFixture { fn new(file_name: &str, content: &str) -> Self { - let unique = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("system time before unix epoch") - .as_nanos(); - let dir = std::env::temp_dir().join(format!("cmtrace-open-parser-regression-{unique}")); - fs::create_dir_all(&dir).expect("create temp fixture dir"); - - let path = dir.join(file_name); + let dir = TempDir::with_prefix("cmtrace-open-parser-regression-") + .expect("create temp fixture dir"); + + let path = dir.path().join(file_name); fs::write(&path, content).expect("write temp fixture"); - Self { dir, path } + Self { _dir: dir, path } } fn detect(&self) -> SelectionSnapshot { @@ -65,12 +63,6 @@ impl TempLogFixture { } } -impl Drop for TempLogFixture { - fn drop(&mut self) { - let _ = fs::remove_dir_all(&self.dir); - } -} - fn selection_snapshot(selection: &app_lib::parser::ResolvedParser) -> SelectionSnapshot { SelectionSnapshot { parser: format!("{:?}", selection.parser),