1use crate::error::{PlatformError, Result};
7use std::process::Command;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum Os {
13 Linux,
15 Darwin,
17 Windows,
19}
20
21impl std::fmt::Display for Os {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 match self {
24 Os::Linux => write!(f, "linux"),
25 Os::Darwin => write!(f, "darwin"),
26 Os::Windows => write!(f, "windows"),
27 }
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
33#[serde(rename_all = "snake_case")]
34pub enum Arch {
35 X86_64,
37 Aarch64,
39}
40
41impl std::fmt::Display for Arch {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 Arch::X86_64 => write!(f, "x86_64"),
45 Arch::Aarch64 => write!(f, "aarch64"),
46 }
47 }
48}
49
50#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
52pub struct Platform {
53 pub name: String,
55 pub os: Os,
57 pub arch: Arch,
59 pub docker_available: bool,
61}
62
63impl Platform {
64 pub fn current() -> Result<Self> {
66 let os = detect_os()?;
67 let arch = detect_arch()?;
68 let docker_available = check_docker_availability();
69
70 let name = match (os, arch) {
71 (Os::Linux, Arch::X86_64) => "Linux x86_64",
72 (Os::Linux, Arch::Aarch64) => "Linux ARM64",
73 (Os::Darwin, Arch::X86_64) => "macOS x86_64",
74 (Os::Darwin, Arch::Aarch64) => "macOS ARM64",
75 (Os::Windows, Arch::X86_64) => "Windows x86_64",
76 (Os::Windows, Arch::Aarch64) => "Windows ARM64",
77 };
78
79 Ok(Platform {
80 name: name.to_string(),
81 os,
82 arch,
83 docker_available,
84 })
85 }
86
87 pub fn supports_testcontainers(&self) -> bool {
89 match self.os {
90 Os::Linux => true,
91 Os::Darwin => self.docker_available,
92 Os::Windows => self.docker_available,
93 }
94 }
95
96 pub fn target_triple(&self) -> &'static str {
98 match (self.os, self.arch) {
99 (Os::Linux, Arch::X86_64) => "x86_64-unknown-linux-gnu",
100 (Os::Linux, Arch::Aarch64) => "aarch64-unknown-linux-gnu",
101 (Os::Darwin, Arch::X86_64) => "x86_64-apple-darwin",
102 (Os::Darwin, Arch::Aarch64) => "aarch64-apple-darwin",
103 (Os::Windows, Arch::X86_64) => "x86_64-pc-windows-msvc",
104 (Os::Windows, Arch::Aarch64) => "aarch64-pc-windows-msvc",
105 }
106 }
107
108 pub fn is_macos(&self) -> bool {
110 self.os == Os::Darwin
111 }
112
113 pub fn is_linux(&self) -> bool {
115 self.os == Os::Linux
116 }
117
118 pub fn uses_native_execution(&self) -> bool {
120 self.is_macos()
121 }
122
123 pub fn uses_container_execution(&self) -> bool {
125 self.is_linux()
126 }
127}
128
129impl std::fmt::Display for Platform {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 write!(f, "{}", self.name)
132 }
133}
134
135impl PartialEq for Platform {
136 fn eq(&self, other: &Self) -> bool {
137 self.os == other.os && self.arch == other.arch
138 }
139}
140
141impl Eq for Platform {}
142
143impl std::hash::Hash for Platform {
144 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
145 self.os.hash(state);
146 self.arch.hash(state);
147 }
148}
149
150fn detect_os() -> Result<Os> {
152 match std::env::consts::OS {
153 "linux" => Ok(Os::Linux),
154 "macos" => Ok(Os::Darwin),
155 "windows" => Ok(Os::Windows),
156 os => Err(PlatformError::UnsupportedOs(os.to_string()).into()),
157 }
158}
159
160fn detect_arch() -> Result<Arch> {
162 match std::env::consts::ARCH {
163 "x86_64" => Ok(Arch::X86_64),
164 "aarch64" => Ok(Arch::Aarch64),
165 arch => Err(PlatformError::UnsupportedArch(arch.to_string()).into()),
166 }
167}
168
169fn check_docker_availability() -> bool {
171 Command::new("docker")
172 .arg("--version")
173 .output()
174 .map(|output| output.status.success())
175 .unwrap_or(false)
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_os_display() {
184 assert_eq!(Os::Linux.to_string(), "linux");
185 assert_eq!(Os::Darwin.to_string(), "darwin");
186 }
187
188 #[test]
189 fn test_arch_display() {
190 assert_eq!(Arch::X86_64.to_string(), "x86_64");
191 assert_eq!(Arch::Aarch64.to_string(), "aarch64");
192 }
193
194 #[test]
195 fn test_current_platform() {
196 let platform = Platform::current().expect("Failed to detect platform");
197 assert!(!platform.name.is_empty());
198 assert!(platform.supports_testcontainers());
199 }
200
201 #[test]
202 fn test_target_triple() {
203 let platform = Platform {
204 name: "test".to_string(),
205 os: Os::Linux,
206 arch: Arch::X86_64,
207 docker_available: true,
208 };
209 assert_eq!(platform.target_triple(), "x86_64-unknown-linux-gnu");
210 }
211
212 #[test]
213 fn test_platform_equality() {
214 let p1 = Platform {
215 name: "Linux x86_64".to_string(),
216 os: Os::Linux,
217 arch: Arch::X86_64,
218 docker_available: true,
219 };
220 let p2 = Platform {
221 name: "Different name".to_string(),
222 os: Os::Linux,
223 arch: Arch::X86_64,
224 docker_available: false,
225 };
226 assert_eq!(p1, p2); }
228
229 #[test]
230 fn test_platform_is_macos() {
231 let macos = Platform {
232 name: "macOS".to_string(),
233 os: Os::Darwin,
234 arch: Arch::Aarch64,
235 docker_available: true,
236 };
237 assert!(macos.is_macos());
238 assert!(!macos.is_linux());
239 }
240
241 #[test]
242 fn test_platform_execution_type() {
243 let macos = Platform {
244 name: "macOS".to_string(),
245 os: Os::Darwin,
246 arch: Arch::Aarch64,
247 docker_available: true,
248 };
249 assert!(macos.uses_native_execution());
250 assert!(!macos.uses_container_execution());
251
252 let linux = Platform {
253 name: "Linux".to_string(),
254 os: Os::Linux,
255 arch: Arch::X86_64,
256 docker_available: true,
257 };
258 assert!(!linux.uses_native_execution());
259 assert!(linux.uses_container_execution());
260 }
261}