From ebae86f3e6f840b1e0ee3703412a828ec9ffd1c7 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 20 Aug 2025 21:37:34 +0700 Subject: [PATCH] feat: detect cpu arch in runtime --- .../plugins/tauri-plugin-hardware/src/cpu.rs | 29 ++++- .../tauri-plugin-hardware/src/tests.rs | 123 ++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) diff --git a/src-tauri/plugins/tauri-plugin-hardware/src/cpu.rs b/src-tauri/plugins/tauri-plugin-hardware/src/cpu.rs index 5b35088cd..aa3b238e6 100644 --- a/src-tauri/plugins/tauri-plugin-hardware/src/cpu.rs +++ b/src-tauri/plugins/tauri-plugin-hardware/src/cpu.rs @@ -24,11 +24,38 @@ impl CpuStaticInfo { CpuStaticInfo { name, core_count: System::physical_core_count().unwrap_or(0), - arch: std::env::consts::ARCH.to_string(), + arch: CpuStaticInfo::get_runtime_arch(), extensions: CpuStaticInfo::get_extensions(), } } + fn get_runtime_arch() -> String { + // Use sysinfo to get the actual CPU architecture at runtime + let mut system = System::new(); + system.refresh_cpu_all(); + + if let Some(cpu) = system.cpus().first() { + let brand = cpu.brand().to_lowercase(); + + // Detect architecture based on CPU brand/model + if brand.contains("aarch64") || brand.contains("arm64") || brand.contains("apple m") { + "aarch64".to_string() + } else if brand.contains("x86") + || brand.contains("amd64") + || brand.contains("i386") + || brand.contains("i686") + { + "x86_64".to_string() + } else { + // Fallback to compile-time architecture if we can't detect + std::env::consts::ARCH.to_string() + } + } else { + // Fallback to compile-time architecture if no CPU info available + std::env::consts::ARCH.to_string() + } + } + // TODO: see if we need to check for all CPU extensions #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn get_extensions() -> Vec { diff --git a/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs b/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs index 394092543..213cbb223 100644 --- a/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs +++ b/src-tauri/plugins/tauri-plugin-hardware/src/tests.rs @@ -1,4 +1,5 @@ use crate::commands::*; +use crate::types::CpuStaticInfo; use tauri::test::mock_app; #[test] @@ -14,3 +15,125 @@ fn test_system_usage() { let usage = get_system_usage(app.handle().clone()); println!("System Usage Info: {:?}", usage); } + +#[cfg(test)] +mod cpu_tests { + use super::*; + + #[test] + fn test_cpu_static_info_new() { + let cpu_info = CpuStaticInfo::new(); + + // Test that all fields are populated + assert!(!cpu_info.name.is_empty()); + assert_ne!(cpu_info.name, "unknown"); // Should have detected a CPU name + assert!(cpu_info.core_count > 0); + assert!(!cpu_info.arch.is_empty()); + + // Architecture should be one of the expected values + assert!( + cpu_info.arch == "aarch64" || + cpu_info.arch == "x86_64" || + cpu_info.arch == std::env::consts::ARCH + ); + + // Extensions should be a valid list (can be empty on non-x86) + assert!(cpu_info.extensions.is_empty() || !cpu_info.extensions.is_empty()); + + println!("CPU Info: {:?}", cpu_info); + } + + #[test] + fn test_cpu_info_consistency() { + // Test that multiple calls return consistent information + let info1 = CpuStaticInfo::new(); + let info2 = CpuStaticInfo::new(); + + assert_eq!(info1.name, info2.name); + assert_eq!(info1.core_count, info2.core_count); + assert_eq!(info1.arch, info2.arch); + assert_eq!(info1.extensions, info2.extensions); + } + + #[test] + fn test_cpu_name_not_empty() { + let cpu_info = CpuStaticInfo::new(); + assert!(!cpu_info.name.is_empty()); + assert!(cpu_info.name.len() > 0); + } + + #[test] + fn test_core_count_positive() { + let cpu_info = CpuStaticInfo::new(); + assert!(cpu_info.core_count > 0); + } + + #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn test_x86_extensions() { + let cpu_info = CpuStaticInfo::new(); + + // On x86/x86_64, we should always have at least FPU + assert!(cpu_info.extensions.contains(&"fpu".to_string())); + + // Check that all extensions are valid x86 feature names + let valid_extensions = [ + "fpu", "mmx", "sse", "sse2", "sse3", "ssse3", "sse4_1", "sse4_2", + "pclmulqdq", "avx", "avx2", "avx512_f", "avx512_dq", "avx512_ifma", + "avx512_pf", "avx512_er", "avx512_cd", "avx512_bw", "avx512_vl", + "avx512_vbmi", "avx512_vbmi2", "avx512_vnni", "avx512_bitalg", + "avx512_vpopcntdq", "avx512_vp2intersect", "aes", "f16c" + ]; + + for ext in &cpu_info.extensions { + assert!( + valid_extensions.contains(&ext.as_str()), + "Unknown extension: {}", + ext + ); + } + } + + #[test] + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + fn test_non_x86_extensions() { + let cpu_info = CpuStaticInfo::new(); + + // On non-x86 architectures, extensions should be empty + assert!(cpu_info.extensions.is_empty()); + } + + #[test] + fn test_arch_detection() { + let cpu_info = CpuStaticInfo::new(); + + // Architecture should be a valid string + assert!(!cpu_info.arch.is_empty()); + + // Should be one of the common architectures + let common_archs = ["x86_64", "aarch64", "arm", "x86"]; + let is_common_arch = common_archs.iter().any(|&arch| cpu_info.arch == arch); + let is_compile_time_arch = cpu_info.arch == std::env::consts::ARCH; + + assert!( + is_common_arch || is_compile_time_arch, + "Unexpected architecture: {}", + cpu_info.arch + ); + } + + #[test] + fn test_cpu_info_serialization() { + let cpu_info = CpuStaticInfo::new(); + + // Test that the struct can be serialized (since it derives Serialize) + let serialized = serde_json::to_string(&cpu_info); + assert!(serialized.is_ok()); + + let json_str = serialized.unwrap(); + assert!(json_str.contains("name")); + assert!(json_str.contains("core_count")); + assert!(json_str.contains("arch")); + assert!(json_str.contains("extensions")); + } +}