1use std::fmt;
4
5#[derive(Debug)]
7pub enum GpuError {
8 InsufficientMemory {
10 budget_mb: usize,
11 available_mb: usize,
12 reserved_mb: usize,
13 total_mb: usize,
14 },
15 Timeout { budget_mb: usize, timeout_secs: u64 },
17 LedgerCorrupt(String),
19 Io(std::io::Error),
21}
22
23impl fmt::Display for GpuError {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 Self::InsufficientMemory { budget_mb, available_mb, reserved_mb, total_mb } => {
27 write!(
28 f,
29 "insufficient VRAM: need {budget_mb} MB, available {available_mb} MB \
30 (reserved {reserved_mb} MB / total {total_mb} MB)"
31 )
32 }
33 Self::Timeout { budget_mb, timeout_secs } => {
34 write!(f, "VRAM wait timed out after {timeout_secs}s (need {budget_mb} MB)")
35 }
36 Self::LedgerCorrupt(msg) => write!(f, "ledger corrupt: {msg}"),
37 Self::Io(e) => write!(f, "GPU ledger I/O: {e}"),
38 }
39 }
40}
41
42impl std::error::Error for GpuError {
43 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
44 match self {
45 Self::Io(e) => Some(e),
46 _ => None,
47 }
48 }
49}
50
51impl From<std::io::Error> for GpuError {
52 fn from(e: std::io::Error) -> Self {
53 Self::Io(e)
54 }
55}
56
57#[cfg(test)]
58#[allow(clippy::unwrap_used)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn test_gpu_error_insufficient_memory_display() {
64 let err = GpuError::InsufficientMemory {
65 budget_mb: 8000,
66 available_mb: 4000,
67 reserved_mb: 12000,
68 total_mb: 16000,
69 };
70 let msg = format!("{err}");
71 assert!(msg.contains("8000"));
72 assert!(msg.contains("4000"));
73 assert!(msg.contains("12000"));
74 assert!(msg.contains("16000"));
75 }
76
77 #[test]
78 fn test_gpu_error_timeout_display() {
79 let err = GpuError::Timeout { budget_mb: 4000, timeout_secs: 30 };
80 let msg = format!("{err}");
81 assert!(msg.contains("30"));
82 assert!(msg.contains("4000"));
83 }
84
85 #[test]
86 fn test_gpu_error_ledger_corrupt_display() {
87 let err = GpuError::LedgerCorrupt("bad checksum".into());
88 let msg = format!("{err}");
89 assert!(msg.contains("bad checksum"));
90 }
91
92 #[test]
93 fn test_gpu_error_io_display() {
94 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
95 let err = GpuError::Io(io_err);
96 let msg = format!("{err}");
97 assert!(msg.contains("file missing"));
98 }
99
100 #[test]
101 fn test_gpu_error_source_io() {
102 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "test");
103 let err = GpuError::Io(io_err);
104 assert!(std::error::Error::source(&err).is_some());
105 }
106
107 #[test]
108 fn test_gpu_error_source_non_io() {
109 let err = GpuError::Timeout { budget_mb: 100, timeout_secs: 5 };
110 assert!(std::error::Error::source(&err).is_none());
111
112 let err = GpuError::LedgerCorrupt("test".into());
113 assert!(std::error::Error::source(&err).is_none());
114
115 let err = GpuError::InsufficientMemory {
116 budget_mb: 1,
117 available_mb: 0,
118 reserved_mb: 1,
119 total_mb: 1,
120 };
121 assert!(std::error::Error::source(&err).is_none());
122 }
123
124 #[test]
125 fn test_gpu_error_from_io() {
126 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "no access");
127 let err: GpuError = io_err.into();
128 match err {
129 GpuError::Io(e) => assert_eq!(e.kind(), std::io::ErrorKind::PermissionDenied),
130 other => panic!("Expected Io variant, got: {other:?}"),
131 }
132 }
133
134 #[test]
135 fn test_gpu_error_debug() {
136 let err = GpuError::Timeout { budget_mb: 100, timeout_secs: 5 };
137 let debug = format!("{err:?}");
138 assert!(debug.contains("Timeout"));
139 }
140}