ggen_e2e/
platform.rs

1//! Platform detection and OS/Arch abstractions
2//!
3//! Provides type-safe platform identification with support for Linux, macOS,
4//! and architecture detection (x86_64, aarch64).
5
6use crate::error::{PlatformError, Result};
7use std::process::Command;
8
9/// Operating system enumeration
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum Os {
13    /// Linux
14    Linux,
15    /// macOS (Darwin)
16    Darwin,
17    /// Windows
18    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/// CPU architecture enumeration
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
33#[serde(rename_all = "snake_case")]
34pub enum Arch {
35    /// x86_64 / amd64
36    X86_64,
37    /// ARM64 / aarch64
38    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/// Target platform with capabilities
51#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
52pub struct Platform {
53    /// Human-readable name
54    pub name: String,
55    /// Operating system
56    pub os: Os,
57    /// CPU architecture
58    pub arch: Arch,
59    /// Whether Docker is available
60    pub docker_available: bool,
61}
62
63impl Platform {
64    /// Detect the current platform
65    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    /// Check if this platform supports testcontainers
88    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    /// Get the target triple for this platform
97    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    /// Check if this is a macOS platform
109    pub fn is_macos(&self) -> bool {
110        self.os == Os::Darwin
111    }
112
113    /// Check if this is a Linux platform
114    pub fn is_linux(&self) -> bool {
115        self.os == Os::Linux
116    }
117
118    /// Check if platform uses native execution (vs container)
119    pub fn uses_native_execution(&self) -> bool {
120        self.is_macos()
121    }
122
123    /// Check if platform uses container execution
124    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
150/// Detect the current operating system
151fn 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
160/// Detect the current CPU architecture
161fn 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
169/// Check if Docker is available by running `docker --version`
170fn 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); // Names and docker_available don't affect equality
227    }
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}