commit 7b4d584fb0f7251528f3433c384b71966a9ab91d
parent fa2a8c862607216d340ea97c74a1c3cbb70f84e9
Author: triesap <tyson@radroots.org>
Date: Tue, 11 Aug 2026 06:51:47 +0000
runtime-paths: adopt xdg interactive roots
- derive Linux interactive roots from XDG variables and defaults
- require an explicit valid XDG runtime directory without fallback
- use native macOS Library roots and reject invalid HOME values
- cover consumer derivation with injected race-free path vectors
Diffstat:
8 files changed, 445 insertions(+), 34 deletions(-)
diff --git a/crates/runtime_manager/src/lib.rs b/crates/runtime_manager/src/lib.rs
@@ -201,6 +201,7 @@ preferred_cli_binding = true
RadrootsPlatform::Linux,
RadrootsHostEnvironment {
home_dir: Some(PathBuf::from("/home/treesap")),
+ xdg_runtime_dir: Some(PathBuf::from("/run/user/1000")),
..RadrootsHostEnvironment::default()
},
);
@@ -216,31 +217,31 @@ preferred_cli_binding = true
assert_eq!(
paths.instance_registry_path,
- PathBuf::from("/home/treesap/.radroots/config/shared/runtime-manager/instances.toml")
+ PathBuf::from("/home/treesap/.config/radroots/shared/runtime-manager/instances.toml")
);
assert_eq!(
paths.install_root,
- PathBuf::from("/home/treesap/.radroots/data/shared/runtime-manager/installs")
+ PathBuf::from("/home/treesap/.local/share/radroots/shared/runtime-manager/installs")
);
assert_eq!(
paths.artifact_cache_dir,
- PathBuf::from("/home/treesap/.radroots/cache/shared/runtime-manager/artifacts")
+ PathBuf::from("/home/treesap/.cache/radroots/shared/runtime-manager/artifacts")
);
assert_eq!(
paths.state_root,
- PathBuf::from("/home/treesap/.radroots/data/shared/runtime-manager/state")
+ PathBuf::from("/home/treesap/.local/share/radroots/shared/runtime-manager/state")
);
assert_eq!(
paths.logs_root,
- PathBuf::from("/home/treesap/.radroots/logs/shared/runtime-manager")
+ PathBuf::from("/home/treesap/.local/state/radroots/logs/shared/runtime-manager")
);
assert_eq!(
paths.run_root,
- PathBuf::from("/home/treesap/.radroots/run/shared/runtime-manager")
+ PathBuf::from("/run/user/1000/radroots/shared/runtime-manager")
);
assert_eq!(
paths.secrets_root,
- PathBuf::from("/home/treesap/.radroots/secrets/shared/runtime-manager")
+ PathBuf::from("/home/treesap/.config/radroots/secrets/shared/runtime-manager")
);
}
@@ -288,19 +289,19 @@ preferred_cli_binding = true
assert_eq!(
instance_paths.install_dir,
PathBuf::from(
- "/Users/treesap/.radroots/data/shared/runtime-manager/installs/radrootsd/local"
+ "/Users/treesap/Library/Application Support/Radroots/data/shared/runtime-manager/installs/radrootsd/local"
)
);
assert_eq!(
instance_paths.pid_file_path,
PathBuf::from(
- "/Users/treesap/.radroots/run/shared/runtime-manager/radrootsd/local/runtime.pid"
+ "/Users/treesap/Library/Application Support/Radroots/run/shared/runtime-manager/radrootsd/local/runtime.pid"
)
);
assert_eq!(
instance_paths.metadata_path,
PathBuf::from(
- "/Users/treesap/.radroots/data/shared/runtime-manager/state/radrootsd/local/instance.toml"
+ "/Users/treesap/Library/Application Support/Radroots/data/shared/runtime-manager/state/radrootsd/local/instance.toml"
)
);
}
diff --git a/crates/runtime_manager/src/managed.rs b/crates/runtime_manager/src/managed.rs
@@ -794,6 +794,7 @@ health_surface = "jsonrpc_status"
RadrootsPlatform::Linux,
RadrootsHostEnvironment {
home_dir: Some(home_dir),
+ xdg_runtime_dir: Some(PathBuf::from("/run/user/1000")),
..RadrootsHostEnvironment::default()
},
)
diff --git a/crates/runtime_manager/src/paths.rs b/crates/runtime_manager/src/paths.rs
@@ -268,6 +268,7 @@ preferred_cli_binding = true
RadrootsPlatform::Linux,
RadrootsHostEnvironment {
home_dir: Some(PathBuf::from("/home/treesap")),
+ xdg_runtime_dir: Some(PathBuf::from("/run/user/1000")),
..RadrootsHostEnvironment::default()
},
)
diff --git a/crates/runtime_paths/src/conventions.rs b/crates/runtime_paths/src/conventions.rs
@@ -143,6 +143,7 @@ mod tests {
RadrootsPlatform::Linux,
RadrootsHostEnvironment {
home_dir: Some(PathBuf::from("/home/treesap")),
+ xdg_runtime_dir: Some(PathBuf::from("/run/user/1000")),
..RadrootsHostEnvironment::default()
},
);
@@ -160,16 +161,16 @@ mod tests {
assert_eq!(
paths.config_path,
- PathBuf::from("/home/treesap/.radroots/config/services/radrootsd/config.toml")
+ PathBuf::from("/home/treesap/.config/radroots/services/radrootsd/config.toml")
);
assert_eq!(
paths.logs_dir,
- PathBuf::from("/home/treesap/.radroots/logs/services/radrootsd")
+ PathBuf::from("/home/treesap/.local/state/radroots/logs/services/radrootsd")
);
assert_eq!(
paths.identity_path,
PathBuf::from(
- "/home/treesap/.radroots/secrets/services/radrootsd/identity.secret.json"
+ "/home/treesap/.config/radroots/secrets/services/radrootsd/identity.secret.json"
)
);
}
@@ -192,8 +193,10 @@ mod tests {
.expect("shared identity path should resolve");
assert_eq!(
identity_path,
- PathBuf::from("/Users/treesap/.radroots/secrets/shared/identities")
- .join(DEFAULT_SHARED_IDENTITY_FILE_NAME)
+ PathBuf::from(
+ "/Users/treesap/Library/Application Support/Radroots/secrets/shared/identities"
+ )
+ .join(DEFAULT_SHARED_IDENTITY_FILE_NAME)
);
let logs_dir = default_shared_runtime_logs_dir(
@@ -204,7 +207,7 @@ mod tests {
.expect("shared runtime logs dir should resolve");
assert_eq!(
logs_dir,
- PathBuf::from("/Users/treesap/.radroots/logs/shared/runtime")
+ PathBuf::from("/Users/treesap/Library/Logs/Radroots/shared/runtime")
);
}
diff --git a/crates/runtime_paths/src/error.rs b/crates/runtime_paths/src/error.rs
@@ -12,6 +12,12 @@ pub enum RadrootsRuntimePathsError {
#[error("interactive_user on windows requires APPDATA and LOCALAPPDATA roots")]
MissingWindowsUserDirs,
+ #[error("interactive_user on linux requires XDG_RUNTIME_DIR")]
+ MissingXdgRuntimeDir,
+
+ #[error("interactive_user on {platform} requires HOME to be an absolute path")]
+ InvalidHomeDir { platform: RadrootsPlatform },
+
#[error("repo_local requires an explicit repo-local base root")]
MissingRepoLocalRoot,
diff --git a/crates/runtime_paths/src/lib.rs b/crates/runtime_paths/src/lib.rs
@@ -47,11 +47,12 @@ mod tests {
};
#[test]
- fn interactive_user_linux_uses_home_dotradroots_root() {
+ fn interactive_user_linux_uses_xdg_defaults_and_explicit_runtime() {
let resolver = RadrootsPathResolver::new(
RadrootsPlatform::Linux,
RadrootsHostEnvironment {
home_dir: Some(PathBuf::from("/home/treesap")),
+ xdg_runtime_dir: Some(PathBuf::from("/run/user/1000")),
..RadrootsHostEnvironment::default()
},
);
@@ -64,13 +65,26 @@ mod tests {
.expect("resolve linux interactive roots");
assert_eq!(
- roots,
- RadrootsPaths::from_base_root("/home/treesap/.radroots")
+ roots.config,
+ PathBuf::from("/home/treesap/.config/radroots")
+ );
+ assert_eq!(
+ roots.data,
+ PathBuf::from("/home/treesap/.local/share/radroots")
+ );
+ assert_eq!(
+ roots.logs,
+ PathBuf::from("/home/treesap/.local/state/radroots/logs")
+ );
+ assert_eq!(roots.run, PathBuf::from("/run/user/1000/radroots"));
+ assert_eq!(
+ roots.secrets,
+ PathBuf::from("/home/treesap/.config/radroots/secrets")
);
}
#[test]
- fn interactive_user_macos_uses_home_dotradroots_root() {
+ fn interactive_user_macos_uses_native_library_roots() {
let resolver = RadrootsPathResolver::new(
RadrootsPlatform::Macos,
RadrootsHostEnvironment {
@@ -88,7 +102,16 @@ mod tests {
assert_eq!(
roots,
- RadrootsPaths::from_base_root("/Users/treesap/.radroots")
+ RadrootsPaths {
+ config: PathBuf::from("/Users/treesap/Library/Application Support/Radroots/config",),
+ data: PathBuf::from("/Users/treesap/Library/Application Support/Radroots/data",),
+ cache: PathBuf::from("/Users/treesap/Library/Caches/Radroots"),
+ logs: PathBuf::from("/Users/treesap/Library/Logs/Radroots"),
+ run: PathBuf::from("/Users/treesap/Library/Application Support/Radroots/run",),
+ secrets: PathBuf::from(
+ "/Users/treesap/Library/Application Support/Radroots/secrets",
+ ),
+ }
);
}
@@ -261,20 +284,20 @@ mod tests {
#[test]
fn namespace_derivation_keeps_runtime_segments_explicit() {
let namespace = RadrootsRuntimeNamespace::service("myc").expect("namespace");
- let roots = RadrootsPaths::from_base_root("/home/treesap/.radroots");
+ let roots = RadrootsPaths::from_base_root("/logical-root");
let namespaced = roots.namespaced(&namespace);
assert_eq!(
namespaced.config,
- PathBuf::from("/home/treesap/.radroots/config/services/myc")
+ PathBuf::from("/logical-root/config/services/myc")
);
assert_eq!(
namespaced.data,
- PathBuf::from("/home/treesap/.radroots/data/services/myc")
+ PathBuf::from("/logical-root/data/services/myc")
);
assert_eq!(
namespaced.secrets,
- PathBuf::from("/home/treesap/.radroots/secrets/services/myc")
+ PathBuf::from("/logical-root/secrets/services/myc")
);
}
@@ -290,7 +313,7 @@ mod tests {
}
#[test]
- fn interactive_user_unix_requires_home_dir() {
+ fn interactive_user_linux_requires_home_for_xdg_defaults() {
let resolver =
RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default());
diff --git a/crates/runtime_paths/src/platform.rs b/crates/runtime_paths/src/platform.rs
@@ -95,6 +95,11 @@ impl fmt::Display for RadrootsPathProfile {
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RadrootsHostEnvironment {
pub home_dir: Option<PathBuf>,
+ pub xdg_config_home: Option<PathBuf>,
+ pub xdg_data_home: Option<PathBuf>,
+ pub xdg_state_home: Option<PathBuf>,
+ pub xdg_cache_home: Option<PathBuf>,
+ pub xdg_runtime_dir: Option<PathBuf>,
pub appdata_dir: Option<PathBuf>,
pub localappdata_dir: Option<PathBuf>,
}
@@ -104,6 +109,11 @@ impl RadrootsHostEnvironment {
pub fn from_current_process() -> Self {
Self {
home_dir: std::env::var_os("HOME").map(PathBuf::from),
+ xdg_config_home: std::env::var_os("XDG_CONFIG_HOME").map(PathBuf::from),
+ xdg_data_home: std::env::var_os("XDG_DATA_HOME").map(PathBuf::from),
+ xdg_state_home: std::env::var_os("XDG_STATE_HOME").map(PathBuf::from),
+ xdg_cache_home: std::env::var_os("XDG_CACHE_HOME").map(PathBuf::from),
+ xdg_runtime_dir: std::env::var_os("XDG_RUNTIME_DIR").map(PathBuf::from),
appdata_dir: std::env::var_os("APPDATA").map(PathBuf::from),
localappdata_dir: std::env::var_os("LOCALAPPDATA").map(PathBuf::from),
}
@@ -176,6 +186,26 @@ mod tests {
let env = RadrootsHostEnvironment::from_current_process();
assert_eq!(env.home_dir, std::env::var_os("HOME").map(PathBuf::from));
assert_eq!(
+ env.xdg_config_home,
+ std::env::var_os("XDG_CONFIG_HOME").map(PathBuf::from)
+ );
+ assert_eq!(
+ env.xdg_data_home,
+ std::env::var_os("XDG_DATA_HOME").map(PathBuf::from)
+ );
+ assert_eq!(
+ env.xdg_state_home,
+ std::env::var_os("XDG_STATE_HOME").map(PathBuf::from)
+ );
+ assert_eq!(
+ env.xdg_cache_home,
+ std::env::var_os("XDG_CACHE_HOME").map(PathBuf::from)
+ );
+ assert_eq!(
+ env.xdg_runtime_dir,
+ std::env::var_os("XDG_RUNTIME_DIR").map(PathBuf::from)
+ );
+ assert_eq!(
env.appdata_dir,
std::env::var_os("APPDATA").map(PathBuf::from)
);
diff --git a/crates/runtime_paths/src/roots.rs b/crates/runtime_paths/src/roots.rs
@@ -5,6 +5,30 @@ use crate::{
RadrootsRuntimePathsError,
};
+const APPLICATION_DIRECTORY: &str = "radroots";
+const MACOS_APPLICATION_DIRECTORY: &str = "Radroots";
+
+#[derive(Clone, Copy)]
+enum XdgDirectory {
+ Config,
+ Data,
+ State,
+ Cache,
+ Runtime,
+}
+
+impl XdgDirectory {
+ const fn home_default(self) -> Option<&'static str> {
+ match self {
+ Self::Config => Some(".config"),
+ Self::Data => Some(".local/share"),
+ Self::State => Some(".local/state"),
+ Self::Cache => Some(".cache"),
+ Self::Runtime => None,
+ }
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RadrootsPaths {
pub config: PathBuf,
@@ -128,14 +152,8 @@ impl RadrootsPathResolver {
fn resolve_interactive_user(&self) -> Result<RadrootsPaths, RadrootsRuntimePathsError> {
match self.platform {
- RadrootsPlatform::Linux | RadrootsPlatform::Macos => self
- .host_environment
- .home_dir
- .as_ref()
- .map(|home| RadrootsPaths::from_base_root(home.join(".radroots")))
- .ok_or(RadrootsRuntimePathsError::MissingHomeDir {
- platform: self.platform,
- }),
+ RadrootsPlatform::Linux => self.resolve_linux_interactive_user(),
+ RadrootsPlatform::Macos => self.resolve_macos_interactive_user(),
RadrootsPlatform::Windows => {
let appdata = self
.host_environment
@@ -167,6 +185,75 @@ impl RadrootsPathResolver {
}
}
+ fn resolve_linux_interactive_user(&self) -> Result<RadrootsPaths, RadrootsRuntimePathsError> {
+ let config = self.resolve_xdg_directory(XdgDirectory::Config)?;
+ let data = self.resolve_xdg_directory(XdgDirectory::Data)?;
+ let state = self.resolve_xdg_directory(XdgDirectory::State)?;
+ let cache = self.resolve_xdg_directory(XdgDirectory::Cache)?;
+ let run = self.resolve_xdg_directory(XdgDirectory::Runtime)?;
+ Ok(RadrootsPaths {
+ config: config.join(APPLICATION_DIRECTORY),
+ data: data.join(APPLICATION_DIRECTORY),
+ cache: cache.join(APPLICATION_DIRECTORY),
+ logs: state.join(APPLICATION_DIRECTORY).join("logs"),
+ run: run.join(APPLICATION_DIRECTORY),
+ secrets: config.join(APPLICATION_DIRECTORY).join("secrets"),
+ })
+ }
+
+ fn resolve_xdg_directory(
+ &self,
+ directory: XdgDirectory,
+ ) -> Result<PathBuf, RadrootsRuntimePathsError> {
+ let configured = match directory {
+ XdgDirectory::Config => &self.host_environment.xdg_config_home,
+ XdgDirectory::Data => &self.host_environment.xdg_data_home,
+ XdgDirectory::State => &self.host_environment.xdg_state_home,
+ XdgDirectory::Cache => &self.host_environment.xdg_cache_home,
+ XdgDirectory::Runtime => &self.host_environment.xdg_runtime_dir,
+ };
+ if let Some(path) = configured.as_ref().filter(|path| path.is_absolute()) {
+ return Ok(path.clone());
+ }
+ let Some(default) = directory.home_default() else {
+ return Err(RadrootsRuntimePathsError::MissingXdgRuntimeDir);
+ };
+ Ok(self.valid_home_dir(RadrootsPlatform::Linux)?.join(default))
+ }
+
+ fn resolve_macos_interactive_user(&self) -> Result<RadrootsPaths, RadrootsRuntimePathsError> {
+ let home = self.valid_home_dir(RadrootsPlatform::Macos)?;
+ let application_support = home
+ .join("Library/Application Support")
+ .join(MACOS_APPLICATION_DIRECTORY);
+ Ok(RadrootsPaths {
+ config: application_support.join("config"),
+ data: application_support.join("data"),
+ cache: home
+ .join("Library/Caches")
+ .join(MACOS_APPLICATION_DIRECTORY),
+ logs: home.join("Library/Logs").join(MACOS_APPLICATION_DIRECTORY),
+ run: application_support.join("run"),
+ secrets: application_support.join("secrets"),
+ })
+ }
+
+ fn valid_home_dir(
+ &self,
+ platform: RadrootsPlatform,
+ ) -> Result<&Path, RadrootsRuntimePathsError> {
+ let home = self
+ .host_environment
+ .home_dir
+ .as_deref()
+ .ok_or(RadrootsRuntimePathsError::MissingHomeDir { platform })?;
+ if home.is_absolute() {
+ Ok(home)
+ } else {
+ Err(RadrootsRuntimePathsError::InvalidHomeDir { platform })
+ }
+ }
+
fn resolve_service_host(&self) -> Result<RadrootsPaths, RadrootsRuntimePathsError> {
match self.platform {
RadrootsPlatform::Linux => Ok(RadrootsPaths {
@@ -229,6 +316,265 @@ mod tests {
}
#[test]
+ fn linux_interactive_uses_exact_config_data_state_cache_and_runtime_roots() {
+ let resolver = RadrootsPathResolver::new(
+ RadrootsPlatform::Linux,
+ RadrootsHostEnvironment {
+ xdg_config_home: Some(PathBuf::from("/xdg/config")),
+ xdg_data_home: Some(PathBuf::from("/xdg/data")),
+ xdg_state_home: Some(PathBuf::from("/xdg/state")),
+ xdg_cache_home: Some(PathBuf::from("/xdg/cache")),
+ xdg_runtime_dir: Some(PathBuf::from("/xdg/run")),
+ ..RadrootsHostEnvironment::default()
+ },
+ );
+
+ assert_eq!(
+ resolver
+ .resolve(
+ RadrootsPathProfile::InteractiveUser,
+ &RadrootsPathOverrides::default(),
+ )
+ .expect("configured XDG roots"),
+ RadrootsPaths {
+ config: PathBuf::from("/xdg/config/radroots"),
+ data: PathBuf::from("/xdg/data/radroots"),
+ cache: PathBuf::from("/xdg/cache/radroots"),
+ logs: PathBuf::from("/xdg/state/radroots/logs"),
+ run: PathBuf::from("/xdg/run/radroots"),
+ secrets: PathBuf::from("/xdg/config/radroots/secrets"),
+ }
+ );
+ }
+
+ #[test]
+ fn linux_interactive_uses_xdg_home_defaults_but_never_invents_runtime() {
+ let resolver = RadrootsPathResolver::new(
+ RadrootsPlatform::Linux,
+ RadrootsHostEnvironment {
+ home_dir: Some(PathBuf::from("/home/treesap")),
+ xdg_runtime_dir: Some(PathBuf::from("/run/user/1000")),
+ ..RadrootsHostEnvironment::default()
+ },
+ );
+
+ assert_eq!(
+ resolver
+ .resolve(
+ RadrootsPathProfile::InteractiveUser,
+ &RadrootsPathOverrides::default(),
+ )
+ .expect("defaulted XDG roots"),
+ RadrootsPaths {
+ config: PathBuf::from("/home/treesap/.config/radroots"),
+ data: PathBuf::from("/home/treesap/.local/share/radroots"),
+ cache: PathBuf::from("/home/treesap/.cache/radroots"),
+ logs: PathBuf::from("/home/treesap/.local/state/radroots/logs"),
+ run: PathBuf::from("/run/user/1000/radroots"),
+ secrets: PathBuf::from("/home/treesap/.config/radroots/secrets"),
+ }
+ );
+
+ let missing_runtime = RadrootsPathResolver::new(
+ RadrootsPlatform::Linux,
+ RadrootsHostEnvironment {
+ home_dir: Some(PathBuf::from("/home/treesap")),
+ ..RadrootsHostEnvironment::default()
+ },
+ );
+ assert_eq!(
+ missing_runtime
+ .resolve(
+ RadrootsPathProfile::InteractiveUser,
+ &RadrootsPathOverrides::default(),
+ )
+ .expect_err("XDG runtime has no home fallback"),
+ RadrootsRuntimePathsError::MissingXdgRuntimeDir
+ );
+ }
+
+ #[test]
+ fn linux_interactive_ignores_empty_and_relative_xdg_directories() {
+ let valid = RadrootsHostEnvironment {
+ home_dir: Some(PathBuf::from("/home/treesap")),
+ xdg_config_home: Some(PathBuf::from("/xdg/config")),
+ xdg_data_home: Some(PathBuf::from("/xdg/data")),
+ xdg_state_home: Some(PathBuf::from("/xdg/state")),
+ xdg_cache_home: Some(PathBuf::from("/xdg/cache")),
+ xdg_runtime_dir: Some(PathBuf::from("/xdg/run")),
+ ..RadrootsHostEnvironment::default()
+ };
+ for (environment, expected) in [
+ (
+ RadrootsHostEnvironment {
+ xdg_config_home: Some(PathBuf::new()),
+ ..valid.clone()
+ },
+ PathBuf::from("/home/treesap/.config/radroots"),
+ ),
+ (
+ RadrootsHostEnvironment {
+ xdg_config_home: Some(PathBuf::from("relative/config")),
+ ..valid.clone()
+ },
+ PathBuf::from("/home/treesap/.config/radroots"),
+ ),
+ (
+ RadrootsHostEnvironment {
+ xdg_data_home: Some(PathBuf::new()),
+ ..valid.clone()
+ },
+ PathBuf::from("/home/treesap/.local/share/radroots"),
+ ),
+ (
+ RadrootsHostEnvironment {
+ xdg_data_home: Some(PathBuf::from("relative/data")),
+ ..valid.clone()
+ },
+ PathBuf::from("/home/treesap/.local/share/radroots"),
+ ),
+ (
+ RadrootsHostEnvironment {
+ xdg_state_home: Some(PathBuf::new()),
+ ..valid.clone()
+ },
+ PathBuf::from("/home/treesap/.local/state/radroots/logs"),
+ ),
+ (
+ RadrootsHostEnvironment {
+ xdg_state_home: Some(PathBuf::from("relative/state")),
+ ..valid.clone()
+ },
+ PathBuf::from("/home/treesap/.local/state/radroots/logs"),
+ ),
+ (
+ RadrootsHostEnvironment {
+ xdg_cache_home: Some(PathBuf::new()),
+ ..valid.clone()
+ },
+ PathBuf::from("/home/treesap/.cache/radroots"),
+ ),
+ (
+ RadrootsHostEnvironment {
+ xdg_cache_home: Some(PathBuf::from("relative/cache")),
+ ..valid.clone()
+ },
+ PathBuf::from("/home/treesap/.cache/radroots"),
+ ),
+ ] {
+ let resolver = RadrootsPathResolver::new(RadrootsPlatform::Linux, environment);
+ let roots = resolver
+ .resolve(
+ RadrootsPathProfile::InteractiveUser,
+ &RadrootsPathOverrides::default(),
+ )
+ .expect("invalid optional XDG directory is ignored");
+ assert!(
+ [roots.config, roots.data, roots.logs, roots.cache].contains(&expected),
+ "expected fallback root {expected:?}"
+ );
+ }
+
+ for xdg_runtime_dir in [Some(PathBuf::new()), Some(PathBuf::from("relative/run"))] {
+ let resolver = RadrootsPathResolver::new(
+ RadrootsPlatform::Linux,
+ RadrootsHostEnvironment {
+ xdg_runtime_dir,
+ ..valid.clone()
+ },
+ );
+ assert_eq!(
+ resolver
+ .resolve(
+ RadrootsPathProfile::InteractiveUser,
+ &RadrootsPathOverrides::default(),
+ )
+ .expect_err("invalid XDG runtime is treated as missing"),
+ RadrootsRuntimePathsError::MissingXdgRuntimeDir
+ );
+ }
+ }
+
+ #[test]
+ fn interactive_home_derived_roots_require_an_absolute_nonempty_home() {
+ for home_dir in [Some(PathBuf::new()), Some(PathBuf::from("relative/home"))] {
+ let linux = RadrootsPathResolver::new(
+ RadrootsPlatform::Linux,
+ RadrootsHostEnvironment {
+ home_dir: home_dir.clone(),
+ xdg_runtime_dir: Some(PathBuf::from("/run/user/1000")),
+ ..RadrootsHostEnvironment::default()
+ },
+ );
+ assert_eq!(
+ linux
+ .resolve(
+ RadrootsPathProfile::InteractiveUser,
+ &RadrootsPathOverrides::default(),
+ )
+ .expect_err("Linux HOME defaults require an absolute HOME"),
+ RadrootsRuntimePathsError::InvalidHomeDir {
+ platform: RadrootsPlatform::Linux,
+ }
+ );
+
+ let macos = RadrootsPathResolver::new(
+ RadrootsPlatform::Macos,
+ RadrootsHostEnvironment {
+ home_dir,
+ ..RadrootsHostEnvironment::default()
+ },
+ );
+ assert_eq!(
+ macos
+ .resolve(
+ RadrootsPathProfile::InteractiveUser,
+ &RadrootsPathOverrides::default(),
+ )
+ .expect_err("macOS native roots require an absolute HOME"),
+ RadrootsRuntimePathsError::InvalidHomeDir {
+ platform: RadrootsPlatform::Macos,
+ }
+ );
+ }
+ }
+
+ #[test]
+ fn macos_interactive_uses_native_library_roots_and_ignores_xdg() {
+ let resolver = RadrootsPathResolver::new(
+ RadrootsPlatform::Macos,
+ RadrootsHostEnvironment {
+ home_dir: Some(PathBuf::from("/Users/treesap")),
+ xdg_config_home: Some(PathBuf::from("relative/config")),
+ xdg_data_home: Some(PathBuf::from("relative/data")),
+ xdg_state_home: Some(PathBuf::from("relative/state")),
+ xdg_cache_home: Some(PathBuf::from("relative/cache")),
+ xdg_runtime_dir: Some(PathBuf::from("relative/run")),
+ ..RadrootsHostEnvironment::default()
+ },
+ );
+
+ assert_eq!(
+ resolver
+ .resolve(
+ RadrootsPathProfile::InteractiveUser,
+ &RadrootsPathOverrides::default(),
+ )
+ .expect("macOS native roots"),
+ RadrootsPaths {
+ config: PathBuf::from("/Users/treesap/Library/Application Support/Radroots/config",),
+ data: PathBuf::from("/Users/treesap/Library/Application Support/Radroots/data",),
+ cache: PathBuf::from("/Users/treesap/Library/Caches/Radroots"),
+ logs: PathBuf::from("/Users/treesap/Library/Logs/Radroots"),
+ run: PathBuf::from("/Users/treesap/Library/Application Support/Radroots/run",),
+ secrets: PathBuf::from(
+ "/Users/treesap/Library/Application Support/Radroots/secrets",
+ ),
+ }
+ );
+ }
+
+ #[test]
fn mobile_profile_is_rejected_on_non_mobile_platforms() {
let resolver =
RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default());