lib

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

commit dd375d2301fc3f8d05184fd6d00a37ab522cae0e
parent fa59bd9214b646cdb441a2a65105d0d01a5589af
Author: triesap <tyson@radroots.org>
Date:   Sat, 11 Apr 2026 16:55:22 +0000

runtime_paths: close coverage gaps

Diffstat:
Mcrates/runtime_paths/src/conventions.rs | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/runtime_paths/src/migration.rs | 15++++++++++++++-
Mcrates/runtime_paths/src/namespace.rs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/runtime_paths/src/platform.rs | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mcrates/runtime_paths/src/roots.rs | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpolicy/coverage/policy.toml | 8--------
6 files changed, 356 insertions(+), 20 deletions(-)

diff --git a/crates/runtime_paths/src/conventions.rs b/crates/runtime_paths/src/conventions.rs @@ -133,4 +133,62 @@ mod tests { PathBuf::from("/Users/treesap/.radroots/logs/shared/runtime") ); } + + #[test] + fn namespaced_bootstrap_paths_propagate_resolver_errors() { + let resolver = + crate::RadrootsPathResolver::new(RadrootsPlatform::Linux, Default::default()); + let namespace = + RadrootsRuntimeNamespace::service("radrootsd").expect("service namespace should parse"); + + let err = default_namespaced_bootstrap_paths( + &resolver, + crate::RadrootsPathProfile::InteractiveUser, + &crate::RadrootsPathOverrides::default(), + &namespace, + DEFAULT_SERVICE_IDENTITY_FILE_NAME, + ) + .expect_err("interactive user should require a home dir"); + + assert_eq!( + err, + crate::RadrootsRuntimePathsError::MissingHomeDir { + platform: RadrootsPlatform::Linux, + } + ); + } + + #[test] + fn shared_defaults_propagate_profile_errors() { + let resolver = + crate::RadrootsPathResolver::new(RadrootsPlatform::Android, Default::default()); + + let identity_err = default_shared_identity_path( + &resolver, + crate::RadrootsPathProfile::InteractiveUser, + &crate::RadrootsPathOverrides::default(), + ) + .expect_err("interactive_user should be unsupported on android"); + assert_eq!( + identity_err, + crate::RadrootsRuntimePathsError::UnsupportedProfilePlatform { + profile: crate::RadrootsPathProfile::InteractiveUser, + platform: RadrootsPlatform::Android, + } + ); + + let logs_err = default_shared_runtime_logs_dir( + &resolver, + crate::RadrootsPathProfile::ServiceHost, + &crate::RadrootsPathOverrides::default(), + ) + .expect_err("service_host should be unsupported on android"); + assert_eq!( + logs_err, + crate::RadrootsRuntimePathsError::UnsupportedProfilePlatform { + profile: crate::RadrootsPathProfile::ServiceHost, + platform: RadrootsPlatform::Android, + } + ); + } } diff --git a/crates/runtime_paths/src/migration.rs b/crates/runtime_paths/src/migration.rs @@ -102,7 +102,7 @@ mod tests { use super::{ RADROOTS_MIGRATION_COMPATIBILITY_WINDOW, RADROOTS_MIGRATION_POSTURE, - RadrootsLegacyPathCandidate, inspect_legacy_paths, + RadrootsLegacyPathCandidate, RadrootsMigrationReport, inspect_legacy_paths, }; fn unique_test_dir() -> PathBuf { @@ -168,4 +168,17 @@ mod tests { assert!(report.detected_legacy_paths.is_empty()); std::fs::remove_dir_all(temp).expect("remove temp test dir"); } + + #[test] + fn empty_report_matches_ready_state() { + let report = RadrootsMigrationReport::empty(); + assert_eq!(report.posture, RADROOTS_MIGRATION_POSTURE); + assert_eq!(report.state, "ready"); + assert!(!report.silent_startup_relocation); + assert_eq!( + report.compatibility_window, + RADROOTS_MIGRATION_COMPATIBILITY_WINDOW + ); + assert!(report.detected_legacy_paths.is_empty()); + } } diff --git a/crates/runtime_paths/src/namespace.rs b/crates/runtime_paths/src/namespace.rs @@ -84,3 +84,65 @@ fn validate_component(value: &str) -> Result<(), RadrootsRuntimePathsError> { } Ok(()) } + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::{RadrootsRuntimeNamespace, RadrootsRuntimeNamespaceKind}; + use crate::RadrootsRuntimePathsError; + + #[test] + fn namespace_kind_path_segments_are_canonical() { + assert_eq!(RadrootsRuntimeNamespaceKind::App.path_segment(), "apps"); + assert_eq!( + RadrootsRuntimeNamespaceKind::Service.path_segment(), + "services" + ); + assert_eq!( + RadrootsRuntimeNamespaceKind::Worker.path_segment(), + "workers" + ); + assert_eq!( + RadrootsRuntimeNamespaceKind::Shared.path_segment(), + "shared" + ); + } + + #[test] + fn namespace_constructors_preserve_kind_and_value() { + let app = RadrootsRuntimeNamespace::app("cli").expect("app namespace"); + assert_eq!(app.kind(), RadrootsRuntimeNamespaceKind::App); + assert_eq!(app.value(), "cli"); + assert_eq!(app.relative_path(), PathBuf::from("apps/cli")); + + let service = RadrootsRuntimeNamespace::service("myc").expect("service namespace"); + assert_eq!(service.kind(), RadrootsRuntimeNamespaceKind::Service); + assert_eq!(service.value(), "myc"); + assert_eq!(service.relative_path(), PathBuf::from("services/myc")); + + let worker = RadrootsRuntimeNamespace::worker("rhi").expect("worker namespace"); + assert_eq!(worker.kind(), RadrootsRuntimeNamespaceKind::Worker); + assert_eq!(worker.value(), "rhi"); + assert_eq!(worker.relative_path(), PathBuf::from("workers/rhi")); + + let shared = RadrootsRuntimeNamespace::shared("runtime").expect("shared namespace"); + assert_eq!(shared.kind(), RadrootsRuntimeNamespaceKind::Shared); + assert_eq!(shared.value(), "runtime"); + assert_eq!(shared.relative_path(), PathBuf::from("shared/runtime")); + } + + #[test] + fn namespace_validation_rejects_invalid_components() { + for invalid in ["", " ", ".", "..", "a/b", r"a\b"] { + let err = RadrootsRuntimeNamespace::new(RadrootsRuntimeNamespaceKind::App, invalid) + .expect_err("invalid namespace component should fail"); + assert_eq!( + err, + RadrootsRuntimePathsError::InvalidNamespaceComponent { + value: invalid.to_owned(), + } + ); + } + } +} diff --git a/crates/runtime_paths/src/platform.rs b/crates/runtime_paths/src/platform.rs @@ -12,18 +12,38 @@ pub enum RadrootsPlatform { impl RadrootsPlatform { #[must_use] + #[cfg(target_os = "android")] pub fn current() -> Self { - if cfg!(target_os = "android") { - Self::Android - } else if cfg!(target_os = "ios") { - Self::Ios - } else if cfg!(target_os = "macos") { - Self::Macos - } else if cfg!(target_os = "windows") { - Self::Windows - } else { - Self::Linux - } + Self::Android + } + + #[must_use] + #[cfg(target_os = "ios")] + pub fn current() -> Self { + Self::Ios + } + + #[must_use] + #[cfg(target_os = "macos")] + pub fn current() -> Self { + Self::Macos + } + + #[must_use] + #[cfg(target_os = "windows")] + pub fn current() -> Self { + Self::Windows + } + + #[must_use] + #[cfg(all( + not(target_os = "android"), + not(target_os = "ios"), + not(target_os = "macos"), + not(target_os = "windows") + ))] + pub fn current() -> Self { + Self::Linux } #[must_use] @@ -82,3 +102,78 @@ impl RadrootsHostEnvironment { } } } + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::{RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPlatform}; + + #[test] + fn current_matches_compiled_target_platform() { + #[cfg(target_os = "android")] + let expected = RadrootsPlatform::Android; + #[cfg(target_os = "ios")] + let expected = RadrootsPlatform::Ios; + #[cfg(target_os = "macos")] + let expected = RadrootsPlatform::Macos; + #[cfg(target_os = "windows")] + let expected = RadrootsPlatform::Windows; + #[cfg(all( + not(target_os = "android"), + not(target_os = "ios"), + not(target_os = "macos"), + not(target_os = "windows") + ))] + let expected = RadrootsPlatform::Linux; + + assert_eq!(RadrootsPlatform::current(), expected); + } + + #[test] + fn unix_like_classification_is_explicit() { + assert!(RadrootsPlatform::Linux.is_unix_like()); + assert!(RadrootsPlatform::Macos.is_unix_like()); + assert!(!RadrootsPlatform::Windows.is_unix_like()); + assert!(!RadrootsPlatform::Android.is_unix_like()); + assert!(!RadrootsPlatform::Ios.is_unix_like()); + } + + #[test] + fn display_uses_canonical_labels() { + assert_eq!(RadrootsPlatform::Linux.to_string(), "linux"); + assert_eq!(RadrootsPlatform::Macos.to_string(), "macos"); + assert_eq!(RadrootsPlatform::Windows.to_string(), "windows"); + assert_eq!(RadrootsPlatform::Android.to_string(), "android"); + assert_eq!(RadrootsPlatform::Ios.to_string(), "ios"); + + assert_eq!( + RadrootsPathProfile::InteractiveUser.to_string(), + "interactive_user" + ); + assert_eq!(RadrootsPathProfile::ServiceHost.to_string(), "service_host"); + assert_eq!(RadrootsPathProfile::RepoLocal.to_string(), "repo_local"); + assert_eq!( + RadrootsPathProfile::MobileNative.to_string(), + "mobile_native" + ); + } + + #[test] + fn host_environment_reads_current_process_variables() { + let env = RadrootsHostEnvironment::from_current_process(); + assert_eq!(env.home_dir, std::env::var_os("HOME").map(PathBuf::from)); + assert_eq!( + env.appdata_dir, + std::env::var_os("APPDATA").map(PathBuf::from) + ); + assert_eq!( + env.localappdata_dir, + std::env::var_os("LOCALAPPDATA").map(PathBuf::from) + ); + assert_eq!( + env.programdata_dir, + std::env::var_os("ProgramData").map(PathBuf::from) + ); + } +} diff --git a/crates/runtime_paths/src/roots.rs b/crates/runtime_paths/src/roots.rs @@ -197,3 +197,119 @@ impl RadrootsPathResolver { } } } + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::{RadrootsPathOverrides, RadrootsPathResolver, RadrootsPaths}; + use crate::{ + RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPlatform, RadrootsRuntimePathsError, + }; + + #[test] + fn path_override_helpers_only_populate_their_owned_slot() { + let repo_local = RadrootsPathOverrides::repo_local("/repo/.local/radroots"); + assert_eq!( + repo_local.repo_local_root, + Some(PathBuf::from("/repo/.local/radroots")) + ); + assert!(repo_local.mobile_roots.is_none()); + + let mobile_roots = RadrootsPaths::from_base_root("/sandbox"); + let mobile = RadrootsPathOverrides::mobile(mobile_roots.clone()); + assert!(mobile.repo_local_root.is_none()); + assert_eq!(mobile.mobile_roots, Some(mobile_roots)); + } + + #[test] + fn resolver_current_uses_process_platform_and_environment() { + let resolver = RadrootsPathResolver::current(); + assert_eq!(resolver.platform(), RadrootsPlatform::current()); + assert_eq!( + resolver, + RadrootsPathResolver::new( + RadrootsPlatform::current(), + RadrootsHostEnvironment::from_current_process() + ) + ); + } + + #[test] + fn mobile_profile_is_rejected_on_non_mobile_platforms() { + let resolver = + RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); + + let err = resolver + .resolve( + RadrootsPathProfile::MobileNative, + &RadrootsPathOverrides::default(), + ) + .expect_err("mobile profile should be rejected on linux"); + + assert_eq!( + err, + RadrootsRuntimePathsError::UnsupportedProfilePlatform { + profile: RadrootsPathProfile::MobileNative, + platform: RadrootsPlatform::Linux, + } + ); + } + + #[test] + fn interactive_user_is_rejected_on_mobile_platforms() { + for platform in [RadrootsPlatform::Android, RadrootsPlatform::Ios] { + let resolver = RadrootsPathResolver::new(platform, RadrootsHostEnvironment::default()); + let err = resolver + .resolve( + RadrootsPathProfile::InteractiveUser, + &RadrootsPathOverrides::default(), + ) + .expect_err("interactive_user should be unsupported on mobile"); + assert_eq!( + err, + RadrootsRuntimePathsError::UnsupportedProfilePlatform { + profile: RadrootsPathProfile::InteractiveUser, + platform, + } + ); + } + } + + #[test] + fn service_host_windows_requires_programdata() { + let resolver = RadrootsPathResolver::new( + RadrootsPlatform::Windows, + RadrootsHostEnvironment::default(), + ); + + let err = resolver + .resolve( + RadrootsPathProfile::ServiceHost, + &RadrootsPathOverrides::default(), + ) + .expect_err("service_host on windows should require programdata"); + + assert_eq!(err, RadrootsRuntimePathsError::MissingWindowsProgramDataDir); + } + + #[test] + fn service_host_is_rejected_on_mobile_platforms() { + for platform in [RadrootsPlatform::Android, RadrootsPlatform::Ios] { + let resolver = RadrootsPathResolver::new(platform, RadrootsHostEnvironment::default()); + let err = resolver + .resolve( + RadrootsPathProfile::ServiceHost, + &RadrootsPathOverrides::default(), + ) + .expect_err("service_host should be unsupported on mobile"); + assert_eq!( + err, + RadrootsRuntimePathsError::UnsupportedProfilePlatform { + profile: RadrootsPathProfile::ServiceHost, + platform, + } + ); + } + } +} diff --git a/policy/coverage/policy.toml b/policy/coverage/policy.toml @@ -15,14 +15,6 @@ require_branches = true # temporary = true # reason = "publish 0.1.0-alpha.1 blocker" -[overrides.radroots_runtime_paths] -fail_under_exec_lines = 88.322 -fail_under_functions = 79.245 -fail_under_regions = 87.735 -fail_under_branches = 66.666 -temporary = true -reason = "publish 0.1.0-alpha.1 temporary coverage override" - [overrides.radroots_runtime_distribution] fail_under_exec_lines = 87.279 fail_under_functions = 74.074