git.lucas.co / cce-files
file manager
git clone https://git.lucas.co/cce-files.git

commit8fb62d12905f27ad30d184dae5af0cf4b3e9a274
parent3dd0380f87
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-06 13:02
test: pass config dirs to the readers instead of setting HOME

test_directory_persistence and test_kdl pointed HOME at a temp dir and
cleared XDG_CONFIG_HOME. #[serial] only ordered them against each other,
so every other test ran inside that window with the wrong config -- and
cce-ui's style registry loads once per process, so a first load landing
there left the whole run on built-in defaults. That is what made
the_graph_fits_its_pane_and_its_names_fit_their_columns flaky (17 of 200
runs failed here).

The last-dir and mime.kdl readers now have _in(config_dir) forms, with
the env-resolving functions as thin wrappers; the tests pass a temp dir
and touch no env at all. serial_test is gone with them. 0 of 500 runs
fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 CLAUDE.md           |  2 ++
 Cargo.lock          | 37 -----------------------
 Cargo.toml          |  5 ----
 src/pages/browse.rs | 53 ++++++++-------------------------
 src/services/fs.rs  | 86 +++++++++++++++++++++++++++--------------------------
 5 files changed, 59 insertions(+), 124 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 232914f..2c37cca 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -16,6 +16,8 @@ cargo test      # run unit tests (nine modules have them; `browse.rs` has the mo
 cargo test test_is_project_dir_detection    # run a single test by name
 ```
 
+**Tests never set `HOME` / `XDG_CONFIG_HOME`.** The env is process-wide, so every test running alongside one sees the temp dir too — and cce-ui's style registry loads once per process, so a first load inside that window left the whole run on built-in defaults (that is what made the network fit test flaky). A config reader takes the dir as a parameter instead (`read_last_dir_in`, `save_last_dir_in`, `load_kdl_associations_in`), with the env-resolving wrapper beside it; tests pass a temp dir.
+
 Running the binary requires a Wayland session — it will not run headless. Edition is **2024**; the `cce-ui` sibling is edition 2021. When touching layout/widget behavior, the actual widget implementations live in `../cce-ui/src/widget/`, not here.
 
 ## Architecture
diff --git a/Cargo.lock b/Cargo.lock
index 2646915..23e26ea 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -481,7 +481,6 @@ dependencies = [
  "kdl 6.7.1",
  "log",
  "notify",
- "serial_test",
  "tokio",
  "wayland-client",
 ]
@@ -921,17 +920,6 @@ version = "0.3.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
 
-[[package]]
-name = "futures-executor"
-version = "0.3.33"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458"
-dependencies = [
- "futures-core",
- "futures-task",
- "futures-util",
-]
-
 [[package]]
 name = "futures-io"
 version = "0.3.33"
@@ -2433,31 +2421,6 @@ dependencies = [
  "syn 3.0.3",
 ]
 
-[[package]]
-name = "serial_test"
-version = "3.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d"
-dependencies = [
- "futures-executor",
- "futures-util",
- "log",
- "once_cell",
- "parking_lot",
- "serial_test_derive",
-]
-
-[[package]]
-name = "serial_test_derive"
-version = "3.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.119",
-]
-
 [[package]]
 name = "shlex"
 version = "2.0.1"
diff --git a/Cargo.toml b/Cargo.toml
index 9004e38..c3c3967 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,11 +14,6 @@ image = { version = "0.25.10", features = ["png", "jpeg"] }
 log = "0.4"
 kdl = "6.7.1"
 
-
-
-[dev-dependencies]
-serial_test = "3"
-
 [lib]
 name = "cce_files"
 path = "src/lib.rs"
diff --git a/src/pages/browse.rs b/src/pages/browse.rs
index 5ccfc6a..e57026c 100644
--- a/src/pages/browse.rs
+++ b/src/pages/browse.rs
@@ -834,47 +834,20 @@ mod tests {
     }
 
     #[test]
-    #[serial_test::serial]
     fn test_directory_persistence() {
-        let temp_dir = std::env::temp_dir();
-        let original_home = std::env::var("HOME");
-        let original_xdg = std::env::var("XDG_CONFIG_HOME");
-        
-        // Mock HOME env variable so we don't overwrite user's actual config
-        let mock_home = temp_dir.join("mock_home_dir_cce");
-        let _ = std::fs::create_dir_all(&mock_home);
-        unsafe {
-            std::env::set_var("HOME", &mock_home);
-            std::env::remove_var("XDG_CONFIG_HOME");
-        }
-        
-        let test_dir = temp_dir.join("test_persist_dir");
-        let _ = std::fs::create_dir_all(&test_dir);
-        
-        // Save last directory
-        crate::services::fs::save_last_dir_internal(&test_dir);
-        
-        // Read last directory
-        let restored = crate::services::fs::read_last_dir_internal();
-        assert_eq!(restored, Some(test_dir.clone()));
-        
-        // Restore HOME env var
-        if let Ok(val) = original_home {
-            unsafe { std::env::set_var("HOME", val); }
-        } else {
-            unsafe { std::env::remove_var("HOME"); }
-        }
-        
-        // Restore XDG_CONFIG_HOME
-        if let Ok(val) = original_xdg {
-            unsafe { std::env::set_var("XDG_CONFIG_HOME", val); }
-        } else {
-            unsafe { std::env::remove_var("XDG_CONFIG_HOME"); }
-        }
-        
-        // Clean up
-        let _ = std::fs::remove_dir_all(&mock_home);
-        let _ = std::fs::remove_dir(&test_dir);
+        let root = std::env::temp_dir().join(format!(
+            "cce_test_persist_{}",
+            chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)
+        ));
+        let config_dir = root.join("config");
+        let test_dir = root.join("persisted");
+        std::fs::create_dir_all(&test_dir).unwrap();
+
+        crate::services::fs::save_last_dir_in(&config_dir, &test_dir);
+        let restored = crate::services::fs::read_last_dir_in(&config_dir);
+        let _ = std::fs::remove_dir_all(&root);
+
+        assert_eq!(restored, Some(test_dir));
     }
 
     #[tokio::test]
diff --git a/src/services/fs.rs b/src/services/fs.rs
index fb59ab2..1cd3294 100644
--- a/src/services/fs.rs
+++ b/src/services/fs.rs
@@ -566,14 +566,19 @@ pub fn cce_config_dir() -> Option<PathBuf> {
     Some(cce_ui::config::cce_config_dir())
 }
 
-fn get_last_dir_file_path() -> Option<PathBuf> {
-    let dir = cce_config_dir()?.join("cce-files");
-    let _ = fs::create_dir_all(&dir);
-    Some(dir.join("cce-files-last-dir.txt"))
+/// The last-dir file under a cce config dir (`cce_config_dir()` in the app; a temp dir
+/// in tests, which is why it is a parameter: setting HOME in a test points every test
+/// running alongside it at the wrong config too).
+fn last_dir_file_in(config_dir: &Path) -> PathBuf {
+    config_dir.join("cce-files").join("cce-files-last-dir.txt")
 }
 
 pub fn read_last_dir_internal() -> Option<PathBuf> {
-    let path = get_last_dir_file_path()?;
+    read_last_dir_in(&cce_config_dir()?)
+}
+
+pub fn read_last_dir_in(config_dir: &Path) -> Option<PathBuf> {
+    let path = last_dir_file_in(config_dir);
     if path.exists() {
         let content = fs::read_to_string(path).ok()?;
         let trimmed = content.trim();
@@ -588,9 +593,17 @@ pub fn read_last_dir_internal() -> Option<PathBuf> {
 }
 
 pub fn save_last_dir_internal(dir: &Path) {
-    if let Some(path) = get_last_dir_file_path() {
-        let _ = fs::write(path, dir.to_string_lossy().as_bytes());
+    if let Some(config_dir) = cce_config_dir() {
+        save_last_dir_in(&config_dir, dir);
+    }
+}
+
+pub fn save_last_dir_in(config_dir: &Path, dir: &Path) {
+    let path = last_dir_file_in(config_dir);
+    if let Some(parent) = path.parent() {
+        let _ = fs::create_dir_all(parent);
     }
+    let _ = fs::write(path, dir.to_string_lossy().as_bytes());
 }
 
 pub fn get_mime_type(path: &Path) -> Option<String> {
@@ -618,7 +631,12 @@ pub fn get_mime_type(path: &Path) -> Option<String> {
 }
 
 pub fn load_kdl_associations() -> Option<std::collections::HashMap<String, String>> {
-    let path = cce_config_dir()?.join("mime.kdl");
+    load_kdl_associations_in(&cce_config_dir()?)
+}
+
+/// `mime.kdl` under `config_dir`; see `last_dir_file_in` for why it is a parameter.
+pub fn load_kdl_associations_in(config_dir: &Path) -> Option<std::collections::HashMap<String, String>> {
+    let path = config_dir.join("mime.kdl");
     if !path.exists() {
         return None;
     }
@@ -820,41 +838,25 @@ mod tests {
     }
 
     #[test]
-    #[serial_test::serial]
     fn test_kdl() {
-        let unique_dir_name = format!("cce_test_kdl_{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos());
-        let temp_path = std::env::temp_dir().join(unique_dir_name);
-        let config_dir = temp_path.join(".config").join("cce");
-        std::fs::create_dir_all(&config_dir).unwrap();
-        let mime_file = config_dir.join("mime.kdl");
-        std::fs::write(&mime_file, "associations { association \"text/plain\" \"cce-text-editor\" }").unwrap();
-        
-        let old_home = std::env::var("HOME").ok();
-        let old_xdg = std::env::var("XDG_CONFIG_HOME").ok();
-        unsafe {
-            std::env::set_var("HOME", &temp_path);
-            std::env::remove_var("XDG_CONFIG_HOME");
-        }
-
-        let assoc = load_kdl_associations();
-
-        unsafe {
-            if let Some(ref h) = old_home {
-                std::env::set_var("HOME", h);
-            } else {
-                std::env::remove_var("HOME");
-            }
-            if let Some(ref x) = old_xdg {
-                std::env::set_var("XDG_CONFIG_HOME", x);
-            } else {
-                std::env::remove_var("XDG_CONFIG_HOME");
-            }
-        }
-
-        let _ = std::fs::remove_dir_all(&temp_path);
-        
-        println!("Parsed associations: {:?}", assoc);
-        assert!(assoc.is_some());
+        let config_dir = std::env::temp_dir().join(format!(
+            "cce_test_kdl_{}",
+            std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
+        ));
+        fs::create_dir_all(&config_dir).unwrap();
+        fs::write(
+            config_dir.join("mime.kdl"),
+            "associations { association \"text/plain\" \"cce-text-editor\" }",
+        )
+        .unwrap();
+
+        let assoc = load_kdl_associations_in(&config_dir);
+        let _ = fs::remove_dir_all(&config_dir);
+
+        assert_eq!(
+            assoc.and_then(|m| m.get("text/plain").cloned()).as_deref(),
+            Some("cce-text-editor")
+        );
     }
 }