lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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:
Mcrates/runtime_manager/src/lib.rs | 21+++++++++++----------
Mcrates/runtime_manager/src/managed.rs | 1+
Mcrates/runtime_manager/src/paths.rs | 1+
Mcrates/runtime_paths/src/conventions.rs | 15+++++++++------
Mcrates/runtime_paths/src/error.rs | 6++++++
Mcrates/runtime_paths/src/lib.rs | 43+++++++++++++++++++++++++++++++++----------
Mcrates/runtime_paths/src/platform.rs | 30++++++++++++++++++++++++++++++
Mcrates/runtime_paths/src/roots.rs | 362+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
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());