use super::public_extern::*; use super::*; use crate::process::Command; #[test] fn test_general_available() { // Lowest version always available. assert_eq!(__isOSVersionAtLeast(0, 0, 0), 1); // This high version never available. assert_eq!(__isOSVersionAtLeast(9999, 99, 99), 0); } #[test] fn test_saturating() { // Higher version than supported by OSVersion -> make sure we saturate. assert_eq!(__isOSVersionAtLeast(0x10000, 0, 0), 0); } #[test] #[cfg_attr(not(target_os = "macos"), ignore = "`sw_vers` is only available on host macOS")] fn compare_against_sw_vers() { let sw_vers = Command::new("sw_vers").arg("-productVersion").output().unwrap().stdout; let sw_vers = String::from_utf8(sw_vers).unwrap(); let mut sw_vers = sw_vers.trim().split('.'); let major: i32 = sw_vers.next().unwrap().parse().unwrap(); let minor: i32 = sw_vers.next().unwrap_or("0").parse().unwrap(); let subminor: i32 = sw_vers.next().unwrap_or("0").parse().unwrap(); assert_eq!(sw_vers.count(), 0); // Test directly against the lookup assert_eq!(lookup_version().get(), pack_os_version(major as _, minor as _, subminor as _)); // Current version is available assert_eq!(__isOSVersionAtLeast(major, minor, subminor), 1); // One lower is available assert_eq!(__isOSVersionAtLeast(major, minor, (subminor as u32).saturating_sub(1) as i32), 1); assert_eq!(__isOSVersionAtLeast(major, (minor as u32).saturating_sub(1) as i32, subminor), 1); assert_eq!(__isOSVersionAtLeast((major as u32).saturating_sub(1) as i32, minor, subminor), 1); // One higher isn't available assert_eq!(__isOSVersionAtLeast(major, minor, subminor + 1), 0); assert_eq!(__isOSVersionAtLeast(major, minor + 1, subminor), 0); assert_eq!(__isOSVersionAtLeast(major + 1, minor, subminor), 0); } #[test] fn sysctl_same_as_in_plist() { if let Some(version) = version_from_sysctl() { assert_eq!(version, version_from_plist()); } } #[test] fn lookup_idempotent() { let version = lookup_version(); for _ in 0..10 { assert_eq!(version, lookup_version()); } } /// Test parsing a bunch of different PLists found in the wild, to ensure that /// if we decide to parse it without CoreFoundation in the future, that it /// would continue to work, even on older platforms. #[test] fn parse_plist() { #[track_caller] fn check( (major, minor, patch): (u16, u8, u8), ios_version: Option<(u16, u8, u8)>, plist: &str, ) { let expected = if cfg!(target_os = "ios") { if let Some((ios_major, ios_minor, ios_patch)) = ios_version { pack_os_version(ios_major, ios_minor, ios_patch) } else if cfg!(target_abi = "macabi") { // Skip checking iOS version on Mac Catalyst. return; } else { // iOS version will be parsed from ProductVersion pack_os_version(major, minor, patch) } } else { pack_os_version(major, minor, patch) }; let cf_handle = CFHandle::new(); assert_eq!(expected, parse_version_from_plist(&cf_handle, plist.as_bytes())); } // macOS 10.3.0 let plist = r#" ProductBuildVersion 7B85 ProductCopyright Apple Computer, Inc. 1983-2003 ProductName Mac OS X ProductUserVisibleVersion 10.3 ProductVersion 10.3 "#; check((10, 3, 0), None, plist); // macOS 10.7.5 let plist = r#" ProductBuildVersion 11G63 ProductCopyright 1983-2012 Apple Inc. ProductName Mac OS X ProductUserVisibleVersion 10.7.5 ProductVersion 10.7.5 "#; check((10, 7, 5), None, plist); // macOS 14.7.4 let plist = r#" BuildID 6A558D8A-E2EA-11EF-A1D3-6222CAA672A8 ProductBuildVersion 23H420 ProductCopyright 1983-2025 Apple Inc. ProductName macOS ProductUserVisibleVersion 14.7.4 ProductVersion 14.7.4 iOSSupportVersion 17.7 "#; check((14, 7, 4), Some((17, 7, 0)), plist); // SystemVersionCompat.plist on macOS 14.7.4 let plist = r#" BuildID 6A558D8A-E2EA-11EF-A1D3-6222CAA672A8 ProductBuildVersion 23H420 ProductCopyright 1983-2025 Apple Inc. ProductName Mac OS X ProductUserVisibleVersion 10.16 ProductVersion 10.16 iOSSupportVersion 17.7 "#; check((10, 16, 0), Some((17, 7, 0)), plist); // macOS 15.4 Beta 24E5238a let plist = r#" BuildID 67A50F62-00DA-11F0-BDB6-F99BB8310D2A ProductBuildVersion 24E5238a ProductCopyright 1983-2025 Apple Inc. ProductName macOS ProductUserVisibleVersion 15.4 ProductVersion 15.4 iOSSupportVersion 18.4 "#; check((15, 4, 0), Some((18, 4, 0)), plist); // iOS Simulator 17.5 let plist = r#" BuildID 210B8A2C-09C3-11EF-9DB8-273A64AEFA1C ProductBuildVersion 21F79 ProductCopyright 1983-2024 Apple Inc. ProductName iPhone OS ProductVersion 17.5 "#; check((17, 5, 0), None, plist); // visionOS Simulator 2.3 let plist = r#" BuildID 57CEFDE6-D079-11EF-837C-8B8C7961D0AC ProductBuildVersion 22N895 ProductCopyright 1983-2025 Apple Inc. ProductName xrOS ProductVersion 2.3 SystemImageID D332C7F1-08DF-4DD9-8122-94EF39A1FB92 iOSSupportVersion 18.3 "#; check((2, 3, 0), Some((18, 3, 0)), plist); // tvOS Simulator 18.2 let plist = r#" BuildID 617587B0-B059-11EF-BE70-4380EDE44645 ProductBuildVersion 22K154 ProductCopyright 1983-2024 Apple Inc. ProductName Apple TVOS ProductVersion 18.2 SystemImageID 8BB5A425-33F0-4821-9F93-40E7ED92F4E0 "#; check((18, 2, 0), None, plist); // watchOS Simulator 11.2 let plist = r#" BuildID BAAE2D54-B122-11EF-BF78-C6C6836B724A ProductBuildVersion 22S99 ProductCopyright 1983-2024 Apple Inc. ProductName Watch OS ProductVersion 11.2 SystemImageID 79F773E2-2041-43B4-98EE-FAE52402AE95 "#; check((11, 2, 0), None, plist); // iOS 9.3.6 let plist = r#" ProductBuildVersion 13G37 ProductCopyright 1983-2019 Apple Inc. ProductName iPhone OS ProductVersion 9.3.6 "#; check((9, 3, 6), None, plist); } #[test] #[should_panic = "SystemVersion.plist did not contain a dictionary at the top level"] fn invalid_plist() { let cf_handle = CFHandle::new(); let _ = parse_version_from_plist(&cf_handle, b"INVALID"); } #[test] #[cfg_attr( target_abi = "macabi", should_panic = "expected iOSSupportVersion in SystemVersion.plist" )] #[cfg_attr( not(target_abi = "macabi"), should_panic = "expected ProductVersion in SystemVersion.plist" )] fn empty_plist() { let plist = r#" "#; let cf_handle = CFHandle::new(); let _ = parse_version_from_plist(&cf_handle, plist.as_bytes()); } #[test] fn parse_version() { #[track_caller] fn check(major: u16, minor: u8, patch: u8, version: &str) { assert_eq!( pack_os_version(major, minor, patch), parse_os_version(version.as_bytes()).unwrap() ) } check(0, 0, 0, "0"); check(0, 0, 0, "0.0.0"); check(1, 0, 0, "1"); check(1, 2, 0, "1.2"); check(1, 2, 3, "1.2.3"); check(9999, 99, 99, "9999.99.99"); // Check leading zeroes check(10, 0, 0, "010"); check(10, 20, 0, "010.020"); check(10, 20, 30, "010.020.030"); check(10000, 100, 100, "000010000.00100.00100"); // Too many parts assert!(parse_os_version(b"1.2.3.4").is_err()); // Empty assert!(parse_os_version(b"").is_err()); // Invalid digit assert!(parse_os_version(b"A.B").is_err()); // Missing digits assert!(parse_os_version(b".").is_err()); assert!(parse_os_version(b".1").is_err()); assert!(parse_os_version(b"1.").is_err()); // Too large assert!(parse_os_version(b"100000").is_err()); assert!(parse_os_version(b"1.1000").is_err()); assert!(parse_os_version(b"1.1.1000").is_err()); }