file manager
git clone https://git.lucas.co/cce-files.git
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")
+ );
}
}