commit dd375d2301fc3f8d05184fd6d00a37ab522cae0e
parent fa59bd9214b646cdb441a2a65105d0d01a5589af
Author: triesap <tyson@radroots.org>
Date: Sat, 11 Apr 2026 16:55:22 +0000
runtime_paths: close coverage gaps
Diffstat:
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