use std::fmt;
use std::path::PathBuf;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
#[non_exhaustive]
#[must_use = "errors should be inspected, propagated, or logged"]
pub enum Error {
Io(std::io::Error),
InvalidPath {
path: PathBuf,
reason: String,
},
HardwareProbeFailed {
detail: String,
},
UnsupportedPlatform {
detail: String,
},
UnsupportedMethod {
method: &'static str,
},
AlignmentRequired {
detail: &'static str,
},
AtomicReplaceFailed {
step: &'static str,
source: std::io::Error,
},
PartialDirectoryOp {
failed_step: String,
completed_steps: Vec<String>,
},
ShutdownInProgress,
QueueFull,
IoUringSetupFailed {
source: std::io::Error,
},
MmapFailed {
reason: String,
},
BufferPoolExhausted,
PlpDetectionUnavailable {
detail: String,
},
NvmePassthroughUnsupported {
detail: String,
},
NvmePassthroughDenied {
detail: String,
},
AsyncRuntimeRequired,
GlobPatternInvalid {
reason: String,
},
HandlePoisoned {
reason: String,
},
IoUringSubmitFailed {
errno: i32,
},
CompletionDriverDead,
FeatureNotEnabled {
feature: &'static str,
},
SpdkUnavailable {
reason: crate::capability::SpdkSkipReason,
},
}
impl Error {
#[must_use]
pub fn code(&self) -> &'static str {
match self {
Error::Io(_) => "FS-00001",
Error::InvalidPath { .. } => "FS-00002",
Error::HardwareProbeFailed { .. } => "FS-00003",
Error::UnsupportedPlatform { .. } => "FS-00004",
Error::UnsupportedMethod { .. } => "FS-00005",
Error::AlignmentRequired { .. } => "FS-00006",
Error::AtomicReplaceFailed { .. } => "FS-00007",
Error::PartialDirectoryOp { .. } => "FS-00008",
Error::ShutdownInProgress => "FS-00009",
Error::QueueFull => "FS-00010",
Error::IoUringSetupFailed { .. } => "FS-00011",
Error::MmapFailed { .. } => "FS-00012",
Error::BufferPoolExhausted => "FS-00013",
Error::PlpDetectionUnavailable { .. } => "FS-00014",
Error::NvmePassthroughUnsupported { .. } => "FS-00015",
Error::NvmePassthroughDenied { .. } => "FS-00016",
Error::AsyncRuntimeRequired => "FS-00017",
Error::GlobPatternInvalid { .. } => "FS-00018",
Error::HandlePoisoned { .. } => "FS-00019",
Error::IoUringSubmitFailed { .. } => "FS-00020",
Error::CompletionDriverDead => "FS-00021",
Error::FeatureNotEnabled { .. } => "FS-00022",
Error::SpdkUnavailable { .. } => "FS-00023",
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Io(e) => write!(f, "[{}] io error: {}", self.code(), e),
Error::InvalidPath { path, reason } => write!(
f,
"[{}] invalid path {:?}: {}",
self.code(),
path.display(),
reason
),
Error::HardwareProbeFailed { detail } => {
write!(f, "[{}] hardware probe failed: {}", self.code(), detail)
}
Error::UnsupportedPlatform { detail } => {
write!(f, "[{}] unsupported platform: {}", self.code(), detail)
}
Error::UnsupportedMethod { method } => {
write!(
f,
"[{}] method '{}' is not implemented in this release",
self.code(),
method
)
}
Error::AlignmentRequired { detail } => {
write!(
f,
"[{}] alignment requirement failed: {}",
self.code(),
detail
)
}
Error::AtomicReplaceFailed { step, source } => {
write!(
f,
"[{}] atomic write-replace failed at step '{}': {}",
self.code(),
step,
source
)
}
Error::PartialDirectoryOp {
failed_step,
completed_steps,
} => {
write!(
f,
"[{}] directory op failed at '{}' after {} completed step(s)",
self.code(),
failed_step,
completed_steps.len()
)
}
Error::ShutdownInProgress => {
write!(
f,
"[{}] handle is shutting down; batch submission rejected",
self.code()
)
}
Error::QueueFull => {
write!(
f,
"[{}] group-lane queue is full (reserved variant; never emitted in 0.4.0)",
self.code()
)
}
Error::IoUringSetupFailed { source } => {
write!(f, "[{}] io_uring_setup failed: {}", self.code(), source)
}
Error::MmapFailed { reason } => {
write!(f, "[{}] mmap operation failed: {}", self.code(), reason)
}
Error::BufferPoolExhausted => {
write!(
f,
"[{}] aligned buffer pool exhausted (reserved variant; never emitted in 0.5.0)",
self.code()
)
}
Error::PlpDetectionUnavailable { detail } => {
write!(f, "[{}] PLP detection unavailable: {}", self.code(), detail)
}
Error::NvmePassthroughUnsupported { detail } => {
write!(
f,
"[{}] NVMe passthrough unsupported: {}",
self.code(),
detail
)
}
Error::NvmePassthroughDenied { detail } => {
write!(f, "[{}] NVMe passthrough denied: {}", self.code(), detail)
}
Error::AsyncRuntimeRequired => {
write!(
f,
"[{}] async method called outside an active tokio runtime",
self.code()
)
}
Error::GlobPatternInvalid { reason } => {
write!(f, "[{}] invalid glob pattern: {}", self.code(), reason)
}
Error::HandlePoisoned { reason } => {
write!(f, "[{}] async substrate poisoned: {}", self.code(), reason)
}
Error::IoUringSubmitFailed { errno } => {
write!(
f,
"[{}] io_uring submit failed (errno {})",
self.code(),
errno
)
}
Error::CompletionDriverDead => {
write!(
f,
"[{}] io_uring completion driver task is no longer running",
self.code()
)
}
Error::FeatureNotEnabled { feature } => {
write!(
f,
"[{}] required Cargo feature '{}' is not enabled in this build",
self.code(),
feature
)
}
Error::SpdkUnavailable { reason } => {
write!(f, "[{}] SPDK backend unavailable: {}", self.code(), reason)
}
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Io(e) => Some(e),
Error::AtomicReplaceFailed { source, .. } => Some(source),
Error::IoUringSetupFailed { source } => Some(source),
Error::InvalidPath { .. }
| Error::HardwareProbeFailed { .. }
| Error::UnsupportedPlatform { .. }
| Error::UnsupportedMethod { .. }
| Error::AlignmentRequired { .. }
| Error::PartialDirectoryOp { .. }
| Error::ShutdownInProgress
| Error::QueueFull
| Error::MmapFailed { .. }
| Error::BufferPoolExhausted
| Error::PlpDetectionUnavailable { .. }
| Error::NvmePassthroughUnsupported { .. }
| Error::NvmePassthroughDenied { .. }
| Error::AsyncRuntimeRequired
| Error::GlobPatternInvalid { .. }
| Error::HandlePoisoned { .. }
| Error::IoUringSubmitFailed { .. }
| Error::CompletionDriverDead
| Error::FeatureNotEnabled { .. }
| Error::SpdkUnavailable { .. } => None,
}
}
}
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Error::Io(value)
}
}
#[derive(Debug)]
#[non_exhaustive]
#[must_use = "errors should be inspected, propagated, or logged"]
pub struct BatchError {
pub(crate) failed_at: usize,
pub(crate) completed: usize,
pub(crate) source: Box<Error>,
}
impl BatchError {
#[must_use]
#[inline]
pub fn failed_at(&self) -> usize {
self.failed_at
}
#[must_use]
#[inline]
pub fn completed(&self) -> usize {
self.completed
}
pub fn inner(&self) -> &Error {
&self.source
}
pub fn into_inner(self) -> Box<Error> {
self.source
}
}
impl fmt::Display for BatchError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"batch failed at op {} after {} successful op(s): {}",
self.failed_at, self.completed, self.source
)
}
}
impl std::error::Error for BatchError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&*self.source)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io;
#[test]
fn test_error_code_io_returns_fs00001() {
let err = Error::Io(io::Error::from(io::ErrorKind::NotFound));
assert_eq!(err.code(), "FS-00001");
}
#[test]
fn test_error_code_invalid_path_returns_fs00002() {
let err = Error::InvalidPath {
path: PathBuf::from("bad"),
reason: "empty segment".into(),
};
assert_eq!(err.code(), "FS-00002");
}
#[test]
fn test_error_code_hardware_probe_returns_fs00003() {
let err = Error::HardwareProbeFailed {
detail: "nvme ioctl unavailable".into(),
};
assert_eq!(err.code(), "FS-00003");
}
#[test]
fn test_error_code_unsupported_platform_returns_fs00004() {
let err = Error::UnsupportedPlatform {
detail: "io_uring requires Linux 5.1+".into(),
};
assert_eq!(err.code(), "FS-00004");
}
#[test]
fn test_error_display_unsupported_platform_includes_detail() {
let err = Error::UnsupportedPlatform {
detail: "io_uring not available".into(),
};
let s = err.to_string();
assert!(s.starts_with("[FS-00004]"));
assert!(s.contains("io_uring not available"));
}
#[test]
fn test_error_display_io_includes_code_and_kind() {
let err = Error::Io(io::Error::from(io::ErrorKind::NotFound));
let s = err.to_string();
assert!(s.starts_with("[FS-00001]"));
assert!(s.contains("io error"));
}
#[test]
fn test_error_display_invalid_path_does_not_panic_on_unicode() {
let err = Error::InvalidPath {
path: PathBuf::from("名前/test"),
reason: "rejected".into(),
};
let s = err.to_string();
assert!(s.contains("FS-00002"));
}
#[test]
fn test_error_source_io_returns_inner() {
let inner = io::Error::from(io::ErrorKind::PermissionDenied);
let err = Error::Io(inner);
assert!(std::error::Error::source(&err).is_some());
}
#[test]
fn test_error_source_invalid_path_returns_none() {
let err = Error::InvalidPath {
path: PathBuf::from("x"),
reason: "y".into(),
};
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_from_io_error_converts() {
let io_err = io::Error::from(io::ErrorKind::Other);
let err: Error = io_err.into();
assert_eq!(err.code(), "FS-00001");
}
#[test]
fn test_result_alias_compiles_for_ok_and_err_paths() {
fn returns_ok() -> Result<u8> {
Ok(1)
}
fn returns_err() -> Result<u8> {
Err(Error::HardwareProbeFailed {
detail: "test".into(),
})
}
assert_eq!(returns_ok().ok(), Some(1));
assert!(returns_err().is_err());
}
#[test]
fn test_error_code_unsupported_method_returns_fs00005() {
let err = Error::UnsupportedMethod { method: "Mmap" };
assert_eq!(err.code(), "FS-00005");
}
#[test]
fn test_error_display_unsupported_method_includes_name() {
let err = Error::UnsupportedMethod { method: "Journal" };
let s = err.to_string();
assert!(s.starts_with("[FS-00005]"));
assert!(s.contains("Journal"));
}
#[test]
fn test_error_code_alignment_required_returns_fs00006() {
let err = Error::AlignmentRequired {
detail: "size not a multiple of sector size",
};
assert_eq!(err.code(), "FS-00006");
}
#[test]
fn test_error_display_alignment_required_includes_detail() {
let err = Error::AlignmentRequired {
detail: "buffer not aligned to 4096",
};
let s = err.to_string();
assert!(s.starts_with("[FS-00006]"));
assert!(s.contains("4096"));
}
#[test]
fn test_error_code_atomic_replace_failed_returns_fs00007() {
let err = Error::AtomicReplaceFailed {
step: "rename",
source: io::Error::from(io::ErrorKind::PermissionDenied),
};
assert_eq!(err.code(), "FS-00007");
}
#[test]
fn test_error_display_atomic_replace_includes_step() {
let err = Error::AtomicReplaceFailed {
step: "flush",
source: io::Error::from(io::ErrorKind::Other),
};
let s = err.to_string();
assert!(s.starts_with("[FS-00007]"));
assert!(s.contains("flush"));
}
#[test]
fn test_error_source_atomic_replace_returns_inner() {
let err = Error::AtomicReplaceFailed {
step: "write",
source: io::Error::from(io::ErrorKind::NotFound),
};
assert!(std::error::Error::source(&err).is_some());
}
#[test]
fn test_error_code_partial_dir_op_returns_fs00008() {
let err = Error::PartialDirectoryOp {
failed_step: "create /a/b".into(),
completed_steps: vec!["create /a".into()],
};
assert_eq!(err.code(), "FS-00008");
}
#[test]
fn test_error_display_partial_dir_op_includes_step() {
let err = Error::PartialDirectoryOp {
failed_step: "create /a/b/c".into(),
completed_steps: vec!["create /a".into(), "create /a/b".into()],
};
let s = err.to_string();
assert!(s.starts_with("[FS-00008]"));
assert!(s.contains("/a/b/c"));
}
#[test]
fn test_error_source_partial_dir_op_returns_none() {
let err = Error::PartialDirectoryOp {
failed_step: "create /x".into(),
completed_steps: vec![],
};
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_shutdown_in_progress_returns_fs00009() {
let err = Error::ShutdownInProgress;
assert_eq!(err.code(), "FS-00009");
}
#[test]
fn test_error_display_shutdown_in_progress_includes_code() {
let err = Error::ShutdownInProgress;
let s = err.to_string();
assert!(s.starts_with("[FS-00009]"));
assert!(s.contains("shutting down"));
}
#[test]
fn test_error_source_shutdown_in_progress_returns_none() {
let err = Error::ShutdownInProgress;
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_queue_full_returns_fs00010() {
let err = Error::QueueFull;
assert_eq!(err.code(), "FS-00010");
}
#[test]
fn test_error_display_queue_full_marked_reserved() {
let err = Error::QueueFull;
let s = err.to_string();
assert!(s.starts_with("[FS-00010]"));
assert!(s.to_ascii_lowercase().contains("reserved"));
}
#[test]
fn test_error_source_queue_full_returns_none() {
let err = Error::QueueFull;
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_batch_error_fields_round_trip() {
let inner = Error::Io(io::Error::from(io::ErrorKind::NotFound));
let be = BatchError {
failed_at: 3,
completed: 3,
source: Box::new(inner),
};
assert_eq!(be.failed_at, 3);
assert_eq!(be.completed, 3);
assert_eq!(be.inner().code(), "FS-00001");
}
#[test]
fn test_batch_error_display_includes_indices_and_inner() {
let inner = Error::HardwareProbeFailed {
detail: "probe stub".into(),
};
let be = BatchError {
failed_at: 7,
completed: 7,
source: Box::new(inner),
};
let s = be.to_string();
assert!(s.contains("op 7"));
assert!(s.contains("7 successful"));
assert!(s.contains("FS-00003"));
}
#[test]
fn test_batch_error_implements_std_error_with_inner_source() {
let inner = Error::Io(io::Error::from(io::ErrorKind::PermissionDenied));
let be = BatchError {
failed_at: 0,
completed: 0,
source: Box::new(inner),
};
let dyn_err: &dyn std::error::Error = &be;
assert!(dyn_err.source().is_some());
}
#[test]
fn test_batch_error_into_inner_returns_boxed_error() {
let inner = Error::ShutdownInProgress;
let be = BatchError {
failed_at: 0,
completed: 0,
source: Box::new(inner),
};
let unboxed: Box<Error> = be.into_inner();
assert_eq!(unboxed.code(), "FS-00009");
}
#[test]
fn test_error_code_io_uring_setup_failed_returns_fs00011() {
let err = Error::IoUringSetupFailed {
source: io::Error::from(io::ErrorKind::PermissionDenied),
};
assert_eq!(err.code(), "FS-00011");
}
#[test]
fn test_error_display_io_uring_setup_failed_includes_source() {
let err = Error::IoUringSetupFailed {
source: io::Error::from(io::ErrorKind::PermissionDenied),
};
let s = err.to_string();
assert!(s.starts_with("[FS-00011]"));
assert!(s.contains("io_uring_setup"));
}
#[test]
fn test_error_source_io_uring_setup_failed_returns_inner() {
let err = Error::IoUringSetupFailed {
source: io::Error::from(io::ErrorKind::PermissionDenied),
};
assert!(std::error::Error::source(&err).is_some());
}
#[test]
fn test_error_code_mmap_failed_returns_fs00012() {
let err = Error::MmapFailed {
reason: "page-size alignment failed".into(),
};
assert_eq!(err.code(), "FS-00012");
}
#[test]
fn test_error_display_mmap_failed_includes_reason() {
let err = Error::MmapFailed {
reason: "fallback to Sync also failed on procfs".into(),
};
let s = err.to_string();
assert!(s.starts_with("[FS-00012]"));
assert!(s.contains("procfs"));
}
#[test]
fn test_error_source_mmap_failed_returns_none() {
let err = Error::MmapFailed {
reason: "test".into(),
};
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_buffer_pool_exhausted_returns_fs00013() {
let err = Error::BufferPoolExhausted;
assert_eq!(err.code(), "FS-00013");
}
#[test]
fn test_error_display_buffer_pool_exhausted_marked_reserved() {
let err = Error::BufferPoolExhausted;
let s = err.to_string();
assert!(s.starts_with("[FS-00013]"));
assert!(s.to_ascii_lowercase().contains("reserved"));
}
#[test]
fn test_error_source_buffer_pool_exhausted_returns_none() {
let err = Error::BufferPoolExhausted;
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_plp_detection_unavailable_returns_fs00014() {
let err = Error::PlpDetectionUnavailable {
detail: "CAP_SYS_ADMIN required".into(),
};
assert_eq!(err.code(), "FS-00014");
}
#[test]
fn test_error_display_plp_detection_unavailable_includes_detail() {
let err = Error::PlpDetectionUnavailable {
detail: "IOKit property missing".into(),
};
let s = err.to_string();
assert!(s.starts_with("[FS-00014]"));
assert!(s.contains("IOKit property missing"));
}
#[test]
fn test_error_source_plp_detection_unavailable_returns_none() {
let err = Error::PlpDetectionUnavailable {
detail: "test".into(),
};
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_nvme_passthrough_unsupported_returns_fs00015() {
let err = Error::NvmePassthroughUnsupported {
detail: "macOS does not expose IOCTL_STORAGE_PROTOCOL_COMMAND".into(),
};
assert_eq!(err.code(), "FS-00015");
}
#[test]
fn test_error_display_nvme_passthrough_unsupported_includes_detail() {
let err = Error::NvmePassthroughUnsupported {
detail: "kernel < 5.19".into(),
};
let s = err.to_string();
assert!(s.starts_with("[FS-00015]"));
assert!(s.contains("kernel < 5.19"));
}
#[test]
fn test_error_source_nvme_passthrough_unsupported_returns_none() {
let err = Error::NvmePassthroughUnsupported {
detail: "test".into(),
};
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_nvme_passthrough_denied_returns_fs00016() {
let err = Error::NvmePassthroughDenied {
detail: "EACCES on /dev/nvme0".into(),
};
assert_eq!(err.code(), "FS-00016");
}
#[test]
fn test_error_display_nvme_passthrough_denied_includes_detail() {
let err = Error::NvmePassthroughDenied {
detail: "ERROR_ACCESS_DENIED on STORAGE_PROTOCOL_COMMAND".into(),
};
let s = err.to_string();
assert!(s.starts_with("[FS-00016]"));
assert!(s.contains("STORAGE_PROTOCOL_COMMAND"));
}
#[test]
fn test_error_source_nvme_passthrough_denied_returns_none() {
let err = Error::NvmePassthroughDenied {
detail: "test".into(),
};
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_async_runtime_required_returns_fs00017() {
let err = Error::AsyncRuntimeRequired;
assert_eq!(err.code(), "FS-00017");
}
#[test]
fn test_error_display_async_runtime_required_mentions_tokio() {
let err = Error::AsyncRuntimeRequired;
let s = err.to_string();
assert!(s.starts_with("[FS-00017]"));
assert!(s.to_ascii_lowercase().contains("tokio"));
}
#[test]
fn test_error_source_async_runtime_required_returns_none() {
let err = Error::AsyncRuntimeRequired;
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_glob_pattern_invalid_returns_fs00018() {
let err = Error::GlobPatternInvalid {
reason: "unmatched bracket".into(),
};
assert_eq!(err.code(), "FS-00018");
}
#[test]
fn test_error_display_glob_pattern_invalid_includes_reason() {
let err = Error::GlobPatternInvalid {
reason: "stray '['".into(),
};
let s = err.to_string();
assert!(s.starts_with("[FS-00018]"));
assert!(s.contains("stray"));
}
#[test]
fn test_error_source_glob_pattern_invalid_returns_none() {
let err = Error::GlobPatternInvalid {
reason: "test".into(),
};
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_handle_poisoned_returns_fs00019() {
let err = Error::HandlePoisoned {
reason: "completion driver panicked".into(),
};
assert_eq!(err.code(), "FS-00019");
}
#[test]
fn test_error_display_handle_poisoned_includes_reason() {
let err = Error::HandlePoisoned {
reason: "driver task aborted".into(),
};
let s = err.to_string();
assert!(s.starts_with("[FS-00019]"));
assert!(s.contains("driver task aborted"));
}
#[test]
fn test_error_source_handle_poisoned_returns_none() {
let err = Error::HandlePoisoned {
reason: "test".into(),
};
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_iouring_submit_failed_returns_fs00020() {
let err = Error::IoUringSubmitFailed { errno: 22 };
assert_eq!(err.code(), "FS-00020");
}
#[test]
fn test_error_display_iouring_submit_failed_includes_errno() {
let err = Error::IoUringSubmitFailed { errno: 9 };
let s = err.to_string();
assert!(s.starts_with("[FS-00020]"));
assert!(s.contains("9"));
}
#[test]
fn test_error_source_iouring_submit_failed_returns_none() {
let err = Error::IoUringSubmitFailed { errno: 0 };
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_completion_driver_dead_returns_fs00021() {
let err = Error::CompletionDriverDead;
assert_eq!(err.code(), "FS-00021");
}
#[test]
fn test_error_display_completion_driver_dead_mentions_driver() {
let err = Error::CompletionDriverDead;
let s = err.to_string();
assert!(s.starts_with("[FS-00021]"));
assert!(s.to_ascii_lowercase().contains("driver"));
}
#[test]
fn test_error_source_completion_driver_dead_returns_none() {
let err = Error::CompletionDriverDead;
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_feature_not_enabled_returns_fs00022() {
let err = Error::FeatureNotEnabled { feature: "spdk" };
assert_eq!(err.code(), "FS-00022");
}
#[test]
fn test_error_display_feature_not_enabled_includes_feature_name() {
let err = Error::FeatureNotEnabled { feature: "spdk" };
let s = err.to_string();
assert!(s.starts_with("[FS-00022]"));
assert!(s.contains("'spdk'"));
assert!(s.to_ascii_lowercase().contains("feature"));
}
#[test]
fn test_error_source_feature_not_enabled_returns_none() {
let err = Error::FeatureNotEnabled { feature: "test" };
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn test_error_code_spdk_unavailable_returns_fs00023() {
let err = Error::SpdkUnavailable {
reason: crate::capability::SpdkSkipReason::NotLinux,
};
assert_eq!(err.code(), "FS-00023");
}
#[test]
fn test_error_display_spdk_unavailable_includes_reason_text() {
let err = Error::SpdkUnavailable {
reason: crate::capability::SpdkSkipReason::NotLinux,
};
let s = err.to_string();
assert!(s.starts_with("[FS-00023]"));
assert!(s.to_ascii_lowercase().contains("spdk"));
assert!(s.to_ascii_lowercase().contains("linux"));
}
#[test]
fn test_error_source_spdk_unavailable_returns_none() {
let err = Error::SpdkUnavailable {
reason: crate::capability::SpdkSkipReason::NotLinux,
};
assert!(std::error::Error::source(&err).is_none());
}
}