From bbb4de0514091fa427e95a3ad7cc0e4fc25e30b8 Mon Sep 17 00:00:00 2001 From: Philip Niedertscheider Date: Mon, 1 Dec 2025 15:40:38 +0100 Subject: [PATCH 1/3] feat: Add isiOSAppOnVisionOS, isiOSAppOnMac, isMacCatalystApp to device context --- Samples/iOS-Swift/iOS-Swift/AppDelegate.swift | 2 +- Sentry.xcodeproj/project.pbxproj | 4 + .../SentryTestUtils-ObjC-BridgingHeader.h | 6 -- .../Sources/TestDisplayLinkWrapper.swift | 8 +- .../TestSentryNSProcessInfoWrapper.swift | 5 + Sources/Swift/Helper/SentryProcessInfo.swift | 14 +++ .../SentryCrash/SentryCrashWrapper.swift | 9 +- Tests/SentryTests/Helper/SentryDeviceTests.m | 18 ++++ .../Helper/SentryProcessInfoTests.swift | 20 ++++ .../SentryANRTrackingIntegrationTests.swift | 2 +- .../SentryTimeToDisplayTrackerTest.swift | 5 +- .../Session/SentrySessionTrackerTests.swift | 22 ++--- .../PrivateSentrySDKOnlyTests.swift | 14 +-- Tests/SentryTests/SentryClientTests.swift | 4 +- .../SentryCrash/SentryCrashWrapperTests.swift | 93 ++++++++++++++++++- Tests/SentryTests/SentryHubTests.swift | 2 +- Tests/SentryTests/SentryOptionsTest.m | 5 +- Tests/SentryTests/SentrySDKTests.swift | 2 +- .../SentryTests/SentryTests-Bridging-Header.h | 6 +- .../SentryTests/TestSentryNSApplication.swift | 2 +- .../Transaction/SentrySpanTests.swift | 10 +- .../Transaction/SentryTracerTests.swift | 2 +- Tests/SentryTests/UIImageHelperTests.swift | 4 +- 23 files changed, 211 insertions(+), 48 deletions(-) create mode 100644 Tests/SentryTests/Helper/SentryProcessInfoTests.swift diff --git a/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift b/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift index b685e071d6a..17d87f9778b 100644 --- a/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift +++ b/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift @@ -26,7 +26,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { SampleAppDebugMenu.shared.display() metricKit.receiveReports() - + return true } diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 54332885150..e870aeea473 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -817,6 +817,7 @@ D4D0E1E82E9D040A00358814 /* SentrySessionReplayEnvironmentCheckerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D0E1E22E9D040800358814 /* SentrySessionReplayEnvironmentCheckerTests.swift */; }; D4D12E7A2DFC608800DC45C4 /* SentryScreenshotOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D12E792DFC607F00DC45C4 /* SentryScreenshotOptionsTests.swift */; }; D4DEE6592E439B2E00FCA5A9 /* SentryProfileTimeseriesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D4DEE6582E439B2E00FCA5A9 /* SentryProfileTimeseriesTests.m */; }; + D4E1CE6A2EDDCBD900D2EAC1 /* SentryProcessInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E1CE642EDDCBD300D2EAC1 /* SentryProcessInfoTests.swift */; }; D4E3F35D2D4A864600F79E2B /* SentryNSDictionarySanitizeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42E48582D48FC8F00D251BC /* SentryNSDictionarySanitizeTests.swift */; }; D4E3F35E2D4A877300F79E2B /* SentryNSDictionarySanitize+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = D41909942D490006002B83D0 /* SentryNSDictionarySanitize+Tests.m */; }; D4E9420A2E9D1CFB00DB7521 /* TestSessionReplayEnvironmentChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E942042E9D1CF300DB7521 /* TestSessionReplayEnvironmentChecker.swift */; }; @@ -2183,6 +2184,7 @@ D4D0E1E22E9D040800358814 /* SentrySessionReplayEnvironmentCheckerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySessionReplayEnvironmentCheckerTests.swift; sourceTree = ""; }; D4D12E792DFC607F00DC45C4 /* SentryScreenshotOptionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryScreenshotOptionsTests.swift; sourceTree = ""; }; D4DEE6582E439B2E00FCA5A9 /* SentryProfileTimeseriesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryProfileTimeseriesTests.m; sourceTree = ""; }; + D4E1CE642EDDCBD300D2EAC1 /* SentryProcessInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryProcessInfoTests.swift; sourceTree = ""; }; D4E942042E9D1CF300DB7521 /* TestSessionReplayEnvironmentChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSessionReplayEnvironmentChecker.swift; sourceTree = ""; }; D4E9420B2E9D1D7600DB7521 /* TestSessionReplayEnvironmentCheckerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSessionReplayEnvironmentCheckerTests.swift; sourceTree = ""; }; D4ECA3FF2E3CBEDE00C757EA /* SentryDummyPrivateEmptyClass.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryDummyPrivateEmptyClass.m; sourceTree = ""; }; @@ -3596,6 +3598,7 @@ 7BD7299B24654CD500EA3610 /* Helper */ = { isa = PBXGroup; children = ( + D4E1CE642EDDCBD300D2EAC1 /* SentryProcessInfoTests.swift */, D4A0C22A2E9E3CE100791353 /* InfoPlist */, F4A930242E661856006DA6EF /* SentryMobileProvisionParserTests.swift */, D4F7BD7C2E4373BB004A2D77 /* SentryLevelMapperTests.swift */, @@ -6284,6 +6287,7 @@ D8019910286B089000C277F0 /* SentryCrashReportSinkTests.swift in Sources */, D885266427739D01001269FC /* SentryFileIOTrackingIntegrationTests.swift in Sources */, 7BBD18992449DE9D00427C76 /* TestRateLimits.swift in Sources */, + D4E1CE6A2EDDCBD900D2EAC1 /* SentryProcessInfoTests.swift in Sources */, 7B04A9AB24EA5F8D00E710B1 /* SentryUserTests.swift in Sources */, 7BA61CCF247EB59500C130A8 /* SentryCrashUUIDConversionTests.swift in Sources */, 7BBD188D2448453600427C76 /* SentryHttpDateParserTests.swift in Sources */, diff --git a/SentryTestUtils/Headers/SentryTestUtils-ObjC-BridgingHeader.h b/SentryTestUtils/Headers/SentryTestUtils-ObjC-BridgingHeader.h index cc2903f0a4e..bec6b1eb5b2 100644 --- a/SentryTestUtils/Headers/SentryTestUtils-ObjC-BridgingHeader.h +++ b/SentryTestUtils/Headers/SentryTestUtils-ObjC-BridgingHeader.h @@ -1,11 +1,5 @@ #import "SentryDefines.h" -#if TARGET_OS_IOS || TARGET_OS_TV -# define SENTRY_UIKIT_AVAILABLE 1 -#else -# define SENTRY_UIKIT_AVAILABLE 0 -#endif - #if SENTRY_HAS_UIKIT # import "SentryAppStartTracker.h" # import "SentryDefaultUIViewControllerPerformanceTracker.h" diff --git a/SentryTestUtils/Sources/TestDisplayLinkWrapper.swift b/SentryTestUtils/Sources/TestDisplayLinkWrapper.swift index 54f207e54e4..81f75a33f90 100644 --- a/SentryTestUtils/Sources/TestDisplayLinkWrapper.swift +++ b/SentryTestUtils/Sources/TestDisplayLinkWrapper.swift @@ -1,7 +1,7 @@ import Foundation @_spi(Private) @testable import Sentry -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst) public enum GPUFrame { case normal @@ -18,7 +18,7 @@ public enum FrameRate: UInt64 { } } -@_spi(Private) public class TestDisplayLinkWrapper: SentryDisplayLinkWrapper, SentryReplayDisplayLinkWrapper { +@_spi(Private) public class TestDisplayLinkWrapper: SentryDisplayLinkWrapper { public var target: AnyObject! public var selector: Selector! public var currentFrameRate: FrameRate = .low @@ -146,3 +146,7 @@ public enum FrameRate: UInt64 { } #endif + +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +extension TestDisplayLinkWrapper: SentryReplayDisplayLinkWrapper {} +#endif diff --git a/SentryTestUtils/Sources/TestSentryNSProcessInfoWrapper.swift b/SentryTestUtils/Sources/TestSentryNSProcessInfoWrapper.swift index 30d189c779e..c4e411cf49b 100644 --- a/SentryTestUtils/Sources/TestSentryNSProcessInfoWrapper.swift +++ b/SentryTestUtils/Sources/TestSentryNSProcessInfoWrapper.swift @@ -12,6 +12,7 @@ public var environment: [String: String]? public var isiOSAppOnMac: Bool? public var isMacCatalystApp: Bool? + public var isiOSAppOnVisionOS: Bool? } public var overrides = Override() @@ -45,4 +46,8 @@ public var isMacCatalystApp: Bool { return overrides.isMacCatalystApp ?? ProcessInfo.processInfo.isMacCatalystApp } + + public var isiOSAppOnVisionOS: Bool { + return overrides.isiOSAppOnVisionOS ?? ProcessInfo.processInfo.isiOSAppOnVisionOS + } } diff --git a/Sources/Swift/Helper/SentryProcessInfo.swift b/Sources/Swift/Helper/SentryProcessInfo.swift index 180b0b8f4f9..db48e1c882b 100644 --- a/Sources/Swift/Helper/SentryProcessInfo.swift +++ b/Sources/Swift/Helper/SentryProcessInfo.swift @@ -10,6 +10,8 @@ @available(macOS 12.0, *) var isMacCatalystApp: Bool { get } + + var isiOSAppOnVisionOS: Bool { get } } // This is needed because a file that only contains an @objc extension will get automatically stripped out @@ -26,4 +28,16 @@ public var processPath: String? { Bundle.main.executablePath } + + public var isiOSAppOnVisionOS: Bool { + if #available(iOS 26.1, visionOS 26.1, *) { + // Use official API when available + // https://developer.apple.com/documentation/foundation/processinfo/isiosapponvision + return self.isiOSAppOnVision + } else { + // Fallback for older versions: `UIWindowSceneGeometryPreferencesVision` is only available on visionOS + // https://developer.apple.com/documentation/uikit/uiwindowscene/geometrypreferences/vision?language=objc + return NSClassFromString("UIWindowSceneGeometryPreferencesVision") != nil + } + } } diff --git a/Sources/Swift/SentryCrash/SentryCrashWrapper.swift b/Sources/Swift/SentryCrash/SentryCrashWrapper.swift index 3f02c126d67..d09300732b7 100644 --- a/Sources/Swift/SentryCrash/SentryCrashWrapper.swift +++ b/Sources/Swift/SentryCrash/SentryCrashWrapper.swift @@ -170,6 +170,13 @@ public final class SentryCrashWrapper: NSObject { deviceData["locale"] = Locale.autoupdatingCurrent.identifier + if #available(macOS 12, *) { + deviceData["ios_app_on_macos"] = self.processInfoWrapper.isiOSAppOnMac + deviceData["mac_catalyst_app"] = self.processInfoWrapper.isMacCatalystApp + } + + deviceData["ios_app_on_visionos"] = self.processInfoWrapper.isiOSAppOnVisionOS + // Set screen dimensions if available setScreenDimensions(&deviceData) @@ -210,7 +217,7 @@ public final class SentryCrashWrapper: NSObject { if self.processInfoWrapper.isMacCatalystApp { runtimeContext["name"] = "Mac Catalyst App" - runtimeContext["raw_description"] = "raw_description" + runtimeContext["raw_description"] = "mac-catalyst-app" } } diff --git a/Tests/SentryTests/Helper/SentryDeviceTests.m b/Tests/SentryTests/Helper/SentryDeviceTests.m index e71c1528f29..cd69d2ad2f7 100644 --- a/Tests/SentryTests/Helper/SentryDeviceTests.m +++ b/Tests/SentryTests/Helper/SentryDeviceTests.m @@ -79,6 +79,18 @@ - (void)testCPUArchitecture // TODO: create a watch UI test target to test this branch as it cannot run on the watch // simulator SENTRY_ASSERT_CONTAINS(arch, @"arm"); // Real Watches +#elif TARGET_OS_VISION +# if TARGET_OS_SIMULATOR +# if TARGET_CPU_ARM64 + SENTRY_ASSERT_CONTAINS(arch, @"arm"); // Vision Pro simulator on M1 macs +# elif TARGET_CPU_X86_64 + SENTRY_ASSERT_CONTAINS(arch, @"x86"); // Vision Pro simulator on Intel macs +# else + XCTFail(@"Unexpected CPU type on test host."); +# endif // TARGET_CPU_ARM64 +# else + SENTRY_ASSERT_CONTAINS(arch, @"arm"); // Real Vision Pro devices +# endif #else XCTFail(@"Unexpected device OS"); #endif @@ -127,6 +139,8 @@ - (void)testOSName #elif TARGET_OS_WATCH // TODO: create a watch UI test target to test this branch SENTRY_ASSERT_EQUAL(osName, @"watchOS"); +#elif TARGET_OS_VISION + SENTRY_ASSERT_EQUAL(osName, @"visionOS"); #else XCTFail(@"Unexpected device OS"); #endif @@ -161,6 +175,8 @@ - (void)testDeviceModel // TODO: create a watch UI test target to test this branch as it cannot run on the watch // simulator SENTRY_ASSERT_CONTAINS(modelName, @"Watch"); +#elif TARGET_OS_VISION + SENTRY_ASSERT_CONTAINS(modelName, @"RealityDevice"); #else XCTFail(@"Unexpected target OS"); #endif @@ -205,6 +221,8 @@ - (void)testSimulatedDeviceModel // TODO: create a watch UI test target to test this branch as it cannot run on the watch // simulator SENTRY_ASSERT_CONTAINS(modelName, @"Watch"); +# elif TARGET_OS_VISION + SENTRY_ASSERT_CONTAINS(modelName, @"RealityDevice"); # else XCTFail(@"Unexpected device OS"); # endif diff --git a/Tests/SentryTests/Helper/SentryProcessInfoTests.swift b/Tests/SentryTests/Helper/SentryProcessInfoTests.swift new file mode 100644 index 00000000000..8dc580ee0ab --- /dev/null +++ b/Tests/SentryTests/Helper/SentryProcessInfoTests.swift @@ -0,0 +1,20 @@ +@_spi(Private) @testable import Sentry +import XCTest + +final class SentryProcessInfoTests: XCTestCase { + + func testIsiOSAppOnVisionOS() throws { + // -- Arrange -- + let processInfo = ProcessInfo.processInfo + + // -- Act -- + let result = processInfo.isiOSAppOnVisionOS + + // -- Assert -- + // This test only asserts that the property exists, as we can not adapt the process info in tests + // and a test running on visionOS is also not an iOS app. + // + // We asserted this manually by running iOS-Swift on visionOS, then exploring the data in `lldb` + XCTAssertFalse(result, "isiOSAppOnVisionOS should be false when not running on visionOS") + } +} diff --git a/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift b/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift index 5726042d5ba..a962d73061f 100644 --- a/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift @@ -78,7 +78,7 @@ class SentryANRTrackingIntegrationTests: SentrySDKIntegrationTestsBase { let tracker = try XCTUnwrap(Dynamic(sut).tracker.asAnyObject as? SentryANRTracker) -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst) XCTAssertTrue(tracker.helper is SentryANRTrackerV2, "Expected SentryANRTrackerV2, but got \(type(of: tracker))") #else XCTAssertTrue(tracker.helper is SentryANRTrackerV1, "Expected SentryANRTrackerV1 on macOS, but got \(type(of: tracker))") diff --git a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift index 81a9cc49ee8..61f74fd8314 100644 --- a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift +++ b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift @@ -3,10 +3,9 @@ import Foundation @_spi(Private) import SentryTestUtils import XCTest -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) - class TestDelayedWrapper: SentryDelayedFramesTracker {} +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) class SentryTimeToDisplayTrackerTest: XCTestCase { private class Fixture { @@ -323,7 +322,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { let expectedInvocations = invocationsBefore + 1 XCTAssertEqual(dispatchQueueWrapper.blockOnMainInvocations.count, expectedInvocations, "reportFullyDisplayed should be dispatched on the main queue. ") } - + func testNotWaitingForFullyDisplayed_AfterTracerTimesOut() throws { fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 9)) diff --git a/Tests/SentryTests/Integrations/Session/SentrySessionTrackerTests.swift b/Tests/SentryTests/Integrations/Session/SentrySessionTrackerTests.swift index c0078e64d21..0b64d5c8ee8 100644 --- a/Tests/SentryTests/Integrations/Session/SentrySessionTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Session/SentrySessionTrackerTests.swift @@ -13,7 +13,7 @@ class SentrySessionTrackerTests: XCTestCase { let client: TestClient! let sentryCrash: TestSentryCrashWrapper - #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) + #if (os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT let _application: TestSentryUIApplication #else let _application: TestSentryNSApplication @@ -38,7 +38,7 @@ class SentrySessionTrackerTests: XCTestCase { sentryCrash = TestSentryCrashWrapper(processInfoWrapper: ProcessInfo.processInfo) - #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) + #if (os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT _application = TestSentryUIApplication() _application.unsafeApplicationState = .inactive #else @@ -537,7 +537,7 @@ class SentrySessionTrackerTests: XCTestCase { // MARK: - Helpers private func startSutInAppDelegate() { - #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) + #if (os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT // The Sentry SDK should be initialized in the `UIAppDelegate.didFinishLaunchingWithOptions` // At this point the application state is `inactive`, because the app just launched but did not // become the active app yet. @@ -565,7 +565,7 @@ class SentrySessionTrackerTests: XCTestCase { // We remove the observers, to ensure future notifications are not handled. sut.removeObservers() - #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) + #if (os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT // When the app stops, the app state is `inactive`. // This can be observed by viewing the application state in `UIAppDelegate.applicationDidEnterBackground`. fixture._application.unsafeApplicationState = .inactive @@ -580,7 +580,7 @@ class SentrySessionTrackerTests: XCTestCase { private func crashSut() { sut.removeObservers() - #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) + #if (os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT // When the app stops, the app state is `inactive`. // // This can be observed by viewing the application state in `UIAppDelegate.applicationDidEnterBackground`. @@ -600,7 +600,7 @@ class SentrySessionTrackerTests: XCTestCase { } private func goToForeground() { - #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) + #if (os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT // When the app becomes active, the app state is `active`. // This can be observed by viewing the application state in `UIAppDelegate.applicationDidBecomeActive`. fixture._application.unsafeApplicationState = .active @@ -622,7 +622,7 @@ class SentrySessionTrackerTests: XCTestCase { private func goToBackground() { // Before an app goes to background, it is still active and will resign from being active. willResignActive() - #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) + #if (os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT // It is expected that the app state is background when the didEnterBackground is called fixture._application.unsafeApplicationState = .background fixture.notificationCenter @@ -642,7 +642,7 @@ class SentrySessionTrackerTests: XCTestCase { } private func willResignActive() { - #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) + #if (os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT // When the app is about to resign being active, it is still active. // // This can be observed by viewing the application state in `UIAppDelegate.applicationWillResignActive`. @@ -665,7 +665,7 @@ class SentrySessionTrackerTests: XCTestCase { private func hybridSdkDidBecomeActive() { // When an app did become active, it is in the active state. - #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) + #if (os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT fixture._application.unsafeApplicationState = .active #else fixture._application.setIsActive(true) @@ -687,7 +687,7 @@ class SentrySessionTrackerTests: XCTestCase { } private func willTerminate() { - #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) + #if (os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT // When terminating an app, it will first move to the background and then terminate. // // This can be observed by viewing the application state in `UIAppDelegate.applicationWillTerminate`. @@ -876,7 +876,7 @@ class SentrySessionTrackerTests: XCTestCase { private func postHybridSdkDidBecomeActiveNotification() { // When the hybrid SDK posts this notification, the app should be in active state - #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) + #if (os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT fixture._application.unsafeApplicationState = .active #else fixture._application.setIsActive(true) diff --git a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift index 3ea98fa28eb..5e7698b3040 100644 --- a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift +++ b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift @@ -126,7 +126,7 @@ class PrivateSentrySDKOnlyTests: XCTestCase { XCTAssertNil(PrivateSentrySDKOnly.envelope(with: itemData)) } - #if canImport(UIKit) + #if canImport(UIKit) && !os(visionOS) func testGetAppStartMeasurement() { let appStartMeasurement = TestData.getAppStartMeasurement(type: .warm, runtimeInitSystemTimestamp: 1) SentrySDKInternal.setAppStartMeasurement(appStartMeasurement) @@ -240,7 +240,7 @@ class PrivateSentrySDKOnlyTests: XCTestCase { XCTAssertEqual(PrivateSentrySDKOnly.options.enabled, true) } - #if !os(tvOS) && !os(watchOS) + #if !os(tvOS) && !os(watchOS) && !os(visionOS) /** * Smoke Tests profiling via PrivateSentrySDKOnly. Actual profiling unit tests are done elsewhere. */ @@ -349,6 +349,7 @@ class PrivateSentrySDKOnlyTests: XCTestCase { #endif #if canImport(UIKit) + #if SENTRY_TARGET_REPLAY_SUPPORTED func testCaptureReplayShouldCallReplayIntegration() throws { guard #available(iOS 16.0, tvOS 16.0, *) else { return } @@ -443,6 +444,11 @@ class PrivateSentrySDKOnlyTests: XCTestCase { XCTAssertTrue(redactBuilder.isRedactContainerClassTestOnly(RedactContainer.self)) } + private func getFirstIntegrationAsReplay() throws -> SentrySessionReplayIntegration { + return try XCTUnwrap(SentrySDKInternal.currentHub().installedIntegrations().first as? SentrySessionReplayIntegration) + } + #endif // SENTRY_TARGET_REPLAY_SUPPORTED + func testAddExtraSdkPackages() throws { PrivateSentrySDKOnly.addSdkPackage("package1", version: "version1") PrivateSentrySDKOnly.addSdkPackage("package2", version: "version2") @@ -462,10 +468,6 @@ class PrivateSentrySDKOnlyTests: XCTestCase { } } - private func getFirstIntegrationAsReplay() throws -> SentrySessionReplayIntegration { - return try XCTUnwrap(SentrySDKInternal.currentHub().installedIntegrations().first as? SentrySessionReplayIntegration) - } - private let VALID_REPLAY_ID = "0eac7ab503354dd5819b03e263627a29" #endif diff --git a/Tests/SentryTests/SentryClientTests.swift b/Tests/SentryTests/SentryClientTests.swift index 13a7e9f67a4..317190f4a88 100644 --- a/Tests/SentryTests/SentryClientTests.swift +++ b/Tests/SentryTests/SentryClientTests.swift @@ -1951,7 +1951,7 @@ class SentryClientTests: XCTestCase { if !SentryDependencyContainer.sharedInstance().crashWrapper.isBeingTraced { expectedIntegrations = ["ANRTracking"] + expectedIntegrations } -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst) expectedIntegrations.append("FramesTracking") #endif // os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) @@ -2503,7 +2503,7 @@ private extension SentryClientTests { } private func getSpan(operation: String, tracer: SentryTracer) -> Span { -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst) return SentrySpan(tracer: tracer, context: SpanContext(operation: operation), framesTracker: nil) #else return SentrySpan(tracer: tracer, context: SpanContext(operation: operation)) diff --git a/Tests/SentryTests/SentryCrash/SentryCrashWrapperTests.swift b/Tests/SentryTests/SentryCrash/SentryCrashWrapperTests.swift index a272e046918..e2fd10e178b 100644 --- a/Tests/SentryTests/SentryCrash/SentryCrashWrapperTests.swift +++ b/Tests/SentryTests/SentryCrash/SentryCrashWrapperTests.swift @@ -1,4 +1,5 @@ @_spi(Private) @testable import Sentry +@_spi(Private) import SentryTestUtils import XCTest final class SentryCrashWrapperTests: XCTestCase { @@ -129,7 +130,97 @@ final class SentryCrashWrapperTests: XCTestCase { let runtimeContext = try XCTUnwrap(scope.contextDictionary["runtime"] as? [String: Any]) XCTAssertEqual(runtimeContext["name"] as? String, "Mac Catalyst App") - XCTAssertEqual(runtimeContext["raw_description"] as? String, "raw_description") + XCTAssertEqual(runtimeContext["raw_description"] as? String, "mac-catalyst-app") #endif } + + @available(macOS 12.0, *) + func testEnrichScope_DeviceContext_iOSAppOnMac() throws { + let mockProcessInfo = MockSentryProcessInfo() + mockProcessInfo.overrides.isiOSAppOnMac = true + mockProcessInfo.overrides.isMacCatalystApp = false + + let testScope = Scope() + let crashWrapper = SentryCrashWrapper(processInfoWrapper: mockProcessInfo, systemInfo: [ + "osVersion": "23A344", + "kernelVersion": "23.0.0", + "isJailbroken": false, + "systemName": "iOS", + "cpuArchitecture": "arm64", + "machine": "iPhone14,2", + "model": "iPhone 13 Pro", + "freeMemorySize": UInt64(1_073_741_824), + "usableMemorySize": UInt64(4_294_967_296), + "memorySize": UInt64(6_442_450_944), + "appStartTime": "2023-01-01T12:00:00Z", + "deviceAppHash": "abc123", + "appID": "12345", + "buildType": "debug" + ]) + + crashWrapper.enrichScope(testScope) + + let deviceContext = try XCTUnwrap(testScope.contextDictionary["device"] as? [String: Any]) + XCTAssertEqual(deviceContext["ios_app_on_macos"] as? Bool, true) + XCTAssertEqual(deviceContext["mac_catalyst_app"] as? Bool, false) + } + + @available(macOS 12.0, *) + func testEnrichScope_DeviceContext_MacCatalyst() throws { + let mockProcessInfo = MockSentryProcessInfo() + mockProcessInfo.overrides.isiOSAppOnMac = false + mockProcessInfo.overrides.isMacCatalystApp = true + + let testScope = Scope() + let crashWrapper = SentryCrashWrapper(processInfoWrapper: mockProcessInfo, systemInfo: [ + "osVersion": "23A344", + "kernelVersion": "23.0.0", + "isJailbroken": false, + "systemName": "iOS", + "cpuArchitecture": "arm64", + "machine": "iPhone14,2", + "model": "iPhone 13 Pro", + "freeMemorySize": UInt64(1_073_741_824), + "usableMemorySize": UInt64(4_294_967_296), + "memorySize": UInt64(6_442_450_944), + "appStartTime": "2023-01-01T12:00:00Z", + "deviceAppHash": "abc123", + "appID": "12345", + "buildType": "debug" + ]) + + crashWrapper.enrichScope(testScope) + + let deviceContext = try XCTUnwrap(testScope.contextDictionary["device"] as? [String: Any]) + XCTAssertEqual(deviceContext["ios_app_on_macos"] as? Bool, false) + XCTAssertEqual(deviceContext["mac_catalyst_app"] as? Bool, true) + } + + func testEnrichScope_DeviceContext_iOSAppOnVisionOS() throws { + let mockProcessInfo = MockSentryProcessInfo() + mockProcessInfo.overrides.isiOSAppOnVisionOS = true + + let testScope = Scope() + let crashWrapper = SentryCrashWrapper(processInfoWrapper: mockProcessInfo, systemInfo: [ + "osVersion": "23A344", + "kernelVersion": "23.0.0", + "isJailbroken": false, + "systemName": "iOS", + "cpuArchitecture": "arm64", + "machine": "iPhone14,2", + "model": "iPhone 13 Pro", + "freeMemorySize": UInt64(1_073_741_824), + "usableMemorySize": UInt64(4_294_967_296), + "memorySize": UInt64(6_442_450_944), + "appStartTime": "2023-01-01T12:00:00Z", + "deviceAppHash": "abc123", + "appID": "12345", + "buildType": "debug" + ]) + + crashWrapper.enrichScope(testScope) + + let deviceContext = try XCTUnwrap(testScope.contextDictionary["device"] as? [String: Any]) + XCTAssertEqual(deviceContext["ios_app_on_visionos"] as? Bool, true) + } } diff --git a/Tests/SentryTests/SentryHubTests.swift b/Tests/SentryTests/SentryHubTests.swift index 08dd1a5b438..ee37030db23 100644 --- a/Tests/SentryTests/SentryHubTests.swift +++ b/Tests/SentryTests/SentryHubTests.swift @@ -238,7 +238,7 @@ class SentryHubTests: XCTestCase { // Assert let runtimeContext = try XCTUnwrap (hub.scope.contextDictionary["runtime"] as? [String: String]) XCTAssertEqual(runtimeContext["name"], "Mac Catalyst App") - XCTAssertEqual(runtimeContext["raw_description"], "raw_description") + XCTAssertEqual(runtimeContext["raw_description"], "mac-catalyst-app") } func testScopeNotEnriched_WhenScopeIsNil() { diff --git a/Tests/SentryTests/SentryOptionsTest.m b/Tests/SentryTests/SentryOptionsTest.m index 9ca5afb041e..8449d387e4c 100644 --- a/Tests/SentryTests/SentryOptionsTest.m +++ b/Tests/SentryTests/SentryOptionsTest.m @@ -713,7 +713,7 @@ - (void)assertDefaultValues:(SentryOptions *)options XCTAssertEqual(200 * 1024 * 1024, options.maxAttachmentSize); XCTAssertEqual(NO, options.sendDefaultPii); XCTAssertTrue(options.enableAutoPerformanceTracing); -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION XCTAssertTrue(options.enableUIViewControllerTracing); XCTAssertFalse(options.attachScreenshot); XCTAssertEqual(3.0, options.idleTimeout); @@ -893,6 +893,7 @@ - (void)testEnablePreWarmedAppStartTracking [self testBooleanField:@"enablePreWarmedAppStartTracing" defaultValue:YES]; } +# if !TARGET_OS_VISION - (void)testSessionReplaySettingsInit { if (@available(iOS 16.0, tvOS 16.0, *)) { @@ -912,7 +913,7 @@ - (void)testSessionReplaySettingsDefaults XCTAssertEqual(options.sessionReplay.onErrorSampleRate, 0); } } - +# endif #endif // SENTRY_HAS_UIKIT #if SENTRY_HAS_METRIC_KIT diff --git a/Tests/SentryTests/SentrySDKTests.swift b/Tests/SentryTests/SentrySDKTests.swift index 9ec693a716e..1a6c3648b15 100644 --- a/Tests/SentryTests/SentrySDKTests.swift +++ b/Tests/SentryTests/SentrySDKTests.swift @@ -141,7 +141,7 @@ class SentrySDKTests: XCTestCase { if !SentryDependencyContainer.sharedInstance().crashWrapper.isBeingTraced { expectedIntegrations.append("SentryANRTrackingIntegration") } -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst) expectedIntegrations.append("SentryFramesTrackingIntegration") #endif // os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index c9dec3767f6..8ff4a0b6c10 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -9,8 +9,10 @@ #if SENTRY_HAS_UIKIT # import "MockUIScene.h" # import "SentryDefaultUIViewControllerPerformanceTracker.h" -# import "SentrySessionReplayIntegration+Private.h" -# import "SentrySessionReplayIntegration+Test.h" +# if SENTRY_TARGET_REPLAY_SUPPORTED +# import "SentrySessionReplayIntegration+Private.h" +# import "SentrySessionReplayIntegration+Test.h" +# endif // SENTRY_TARGET_REPLAY_SUPPORTED # import "SentryUIEventTracker.h" # import "SentryUIEventTrackerTransactionMode.h" # import "SentryUIEventTrackingIntegration.h" diff --git a/Tests/SentryTests/TestSentryNSApplication.swift b/Tests/SentryTests/TestSentryNSApplication.swift index acf4e0ba5cf..9ccca0c5cbf 100644 --- a/Tests/SentryTests/TestSentryNSApplication.swift +++ b/Tests/SentryTests/TestSentryNSApplication.swift @@ -1,6 +1,6 @@ @_spi(Private) @testable import Sentry -#if !(os(iOS) || targetEnvironment(macCatalyst) || os(tvOS)) +#if !(os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) class TestSentryNSApplication: SentryApplication { private var _underlyingIsActive = true func setIsActive(_ isActive: Bool) { diff --git a/Tests/SentryTests/Transaction/SentrySpanTests.swift b/Tests/SentryTests/Transaction/SentrySpanTests.swift index 476b597e28d..bf812293084 100644 --- a/Tests/SentryTests/Transaction/SentrySpanTests.swift +++ b/Tests/SentryTests/Transaction/SentrySpanTests.swift @@ -15,7 +15,7 @@ class SentrySpanTests: XCTestCase { let options: Options let notificationCenter = TestNSNotificationCenterWrapper() let currentDateProvider = TestCurrentDateProvider() -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +#if (os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT let tracer = SentryTracer(context: SpanContext(operation: "TEST"), framesTracker: nil) #else let tracer = SentryTracer(context: SpanContext(operation: "TEST")) @@ -41,7 +41,7 @@ class SentrySpanTests: XCTestCase { } func getSutWithTracer() -> SentrySpan { -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +#if (os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT return SentrySpan(tracer: tracer, context: SpanContext(operation: someOperation, sampled: .undecided), framesTracker: nil) #else return SentrySpan(tracer: tracer, context: SpanContext(operation: someOperation, sampled: .undecided)) @@ -538,7 +538,7 @@ class SentrySpanTests: XCTestCase { // Span has a weak reference to tracer. If we don't keep a reference // to the tracer ARC will deallocate the tracer. let sutGenerator: () -> Span = { -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +#if (os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT let tracer = SentryTracer(context: SpanContext(operation: "TEST"), framesTracker: nil) return SentrySpan(tracer: tracer, context: SpanContext(operation: ""), framesTracker: nil) #else @@ -628,7 +628,7 @@ class SentrySpanTests: XCTestCase { XCTAssertEqual(expectedBaggage, sut.baggageHttpHeader()) } -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +#if (os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT func testAddSlowFrozenFramesToData() { let (displayLinkWrapper, framesTracker) = givenFramesTracker() @@ -706,5 +706,5 @@ class SentrySpanTests: XCTestCase { return (displayLinkWrapper, framesTracker) } -#endif // os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +#endif // (os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT } diff --git a/Tests/SentryTests/Transaction/SentryTracerTests.swift b/Tests/SentryTests/Transaction/SentryTracerTests.swift index bc001e564e8..2c06ea4a7d3 100644 --- a/Tests/SentryTests/Transaction/SentryTracerTests.swift +++ b/Tests/SentryTests/Transaction/SentryTracerTests.swift @@ -615,7 +615,7 @@ class SentryTracerTests: XCTestCase { } func testIdleTimeout_TracerDeallocated() throws { -#if !os(tvOS) && !os(watchOS) +#if !os(tvOS) && !os(watchOS) && !os(visionOS) if sentry_threadSanitizerIsPresent() { throw XCTSkip("doesn't currently work with TSAN enabled. the tracer instance remains retained by something in the TSAN dylib, and we cannot debug the memory graph with TSAN attached to see what is retaining it. it's likely out of our control.") } diff --git a/Tests/SentryTests/UIImageHelperTests.swift b/Tests/SentryTests/UIImageHelperTests.swift index 1b92c573f3d..f8292ac7ac3 100644 --- a/Tests/SentryTests/UIImageHelperTests.swift +++ b/Tests/SentryTests/UIImageHelperTests.swift @@ -1,4 +1,5 @@ #if canImport(UIKit) +#if os(iOS) || os(tvOS) import Foundation @testable import Sentry import XCTest @@ -61,4 +62,5 @@ class UIImageHelperTests: XCTestCase { } } -#endif +#endif // os(iOS) || os(tvOS) +#endif // canImport(UIKit) From ad1b8e0ac774618a3fd94302d8fa3639df6fa384 Mon Sep 17 00:00:00 2001 From: Philip Niedertscheider Date: Mon, 1 Dec 2025 15:56:29 +0100 Subject: [PATCH 2/3] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6de2fa864a0..f95532ce457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Feature - Add options to customize UserFeedback error messages (#6790) +- Add isiOSAppOnVisionOS, isiOSAppOnMac, isMacCatalystApp to device context #6939 ### Fixes From ad08e2681d82ab25ed8ddc2d649e5be579008f68 Mon Sep 17 00:00:00 2001 From: Philip Niedertscheider Date: Tue, 2 Dec 2025 11:46:16 +0100 Subject: [PATCH 3/3] WIP --- CHANGELOG.md | 7 ++++++- Samples/iOS-Swift/iOS-Swift/AppDelegate.swift | 2 +- Sources/Sentry/SentryBuildAppStartSpans.m | 2 +- Sources/Sentry/SentrySpan.m | 6 +++--- Sources/Sentry/SentryTracer.m | 20 +++++++++---------- Sources/Sentry/include/SentrySpan.h | 4 ++-- Sources/Swift/Helper/SentryProcessInfo.swift | 16 ++++++++++----- .../SentryANRTrackingIntegrationTests.swift | 2 +- .../SentryTimeToDisplayTrackerTest.swift | 2 +- .../PrivateSentrySDKOnlyTests.swift | 2 +- Tests/SentryTests/Protocol/TestData.swift | 6 +++--- Tests/SentryTests/SentryClientTests.swift | 4 ++-- Tests/SentryTests/SentryOptionsTest.m | 8 ++++---- Tests/SentryTests/SentrySDKTests.swift | 2 +- .../SentryTests/TestSentryNSApplication.swift | 2 +- .../SentryBuildAppStartSpansTests.swift | 2 +- .../Transaction/SentrySpanTests.swift | 10 +++++----- 17 files changed, 54 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8423f86044f..c0c046d7895 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- Add isiOSAppOnVisionOS, isiOSAppOnMac, isMacCatalystApp to device context #6939 + ## 9.0.0 This changelog lists every breaking change. For a high-level overview and upgrade guidance, see the [migration guide](https://docs.sentry.io/platforms/apple/migration/). @@ -62,7 +68,6 @@ This changelog lists every breaking change. For a high-level overview and upgrad - Add `sentry.replay_id` attribute to logs ([#6515](https://github.com/getsentry/sentry-cocoa/pull/6515)) - Structured Logs: Add log APIs to `Hub` and `Client` (#6737) - Add options to customize UserFeedback error messages (#6790) -- Add isiOSAppOnVisionOS, isiOSAppOnMac, isMacCatalystApp to device context #6939 ### Fixes diff --git a/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift b/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift index 17d87f9778b..b685e071d6a 100644 --- a/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift +++ b/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift @@ -26,7 +26,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { SampleAppDebugMenu.shared.display() metricKit.receiveReports() - + return true } diff --git a/Sources/Sentry/SentryBuildAppStartSpans.m b/Sources/Sentry/SentryBuildAppStartSpans.m index e3e0b5e60f8..38b6fb649de 100644 --- a/Sources/Sentry/SentryBuildAppStartSpans.m +++ b/Sources/Sentry/SentryBuildAppStartSpans.m @@ -7,7 +7,7 @@ #import "SentryTracer.h" #import -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION SentrySpan * sentryBuildAppStartSpan( diff --git a/Sources/Sentry/SentrySpan.m b/Sources/Sentry/SentrySpan.m index 1e5e782a8ca..55cc1e468b7 100644 --- a/Sources/Sentry/SentrySpan.m +++ b/Sources/Sentry/SentrySpan.m @@ -46,7 +46,7 @@ @implementation SentrySpan { } - (instancetype)initWithContext:(SentrySpanContext *)context -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION framesTracker:(nullable SentryFramesTracker *)framesTracker; #endif // SENTRY_HAS_UIKIT { @@ -65,7 +65,7 @@ - (instancetype)initWithContext:(SentrySpanContext *)context getThreadName:currentThread]; } -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION _framesTracker = framesTracker; if (_framesTracker.isRunning) { SentryScreenFrames *currentFrames = _framesTracker.currentFrames; @@ -137,7 +137,7 @@ - (void)stopObservingContinuousProfiling - (instancetype)initWithTracer:(SentryTracer *)tracer context:(SentrySpanContext *)context -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION framesTracker:(nullable SentryFramesTracker *)framesTracker { if (self = [self initWithContext:context framesTracker:framesTracker]) { diff --git a/Sources/Sentry/SentryTracer.m b/Sources/Sentry/SentryTracer.m index 5f41a8b5d4c..39e027c80ec 100644 --- a/Sources/Sentry/SentryTracer.m +++ b/Sources/Sentry/SentryTracer.m @@ -30,7 +30,7 @@ # import "SentryProfiledTracerConcurrency.h" #endif // SENTRY_TARGET_PROFILING_SUPPORTED -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION # import "SentryAppStartMeasurement.h" # import "SentryBuildAppStartSpans.h" #endif // SENTRY_HAS_UIKIT @@ -39,7 +39,7 @@ static const void *spanTimestampObserver = &spanTimestampObserver; -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION /** * The maximum amount of seconds the app start measurement end time and the start time of the * transaction are allowed to be apart. @@ -114,7 +114,7 @@ - (instancetype)initWithTransactionContext:(SentryTransactionContext *)transacti configuration:(SentryTracerConfiguration *)configuration; { if (!(self = [super initWithContext:transactionContext -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION framesTracker:SentryDependencyContainer.sharedInstance.framesTracker #endif // SENTRY_HAS_UIKIT ])) { @@ -390,7 +390,7 @@ - (BOOL)isAutoGeneratedTransaction SentrySpan *child = [[SentrySpan alloc] initWithTracer:self context:context -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION framesTracker:SentryDependencyContainer.sharedInstance.framesTracker #endif // SENTRY_HAS_UIKIT ]; @@ -615,13 +615,13 @@ - (BOOL)finishTracer:(SentrySpanStatus)unfinishedSpansFinishStatus shouldCleanUp } [super finishWithStatus:_finishStatus]; } -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION appStartMeasurement = [self getAppStartMeasurement]; if (appStartMeasurement != nil) { [self updateStartTime:appStartMeasurement.appStartTimestamp]; } -#endif // SENTRY_HAS_UIKIT +#endif // SENTRY_HAS_UIKIT && !TARGET_OS_VISION if (shouldCleanUp) { [self.delegate tracerDidFinish:self]; @@ -721,7 +721,7 @@ - (SentryTransaction *)toTransaction { NSUInteger capacity; -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION [self addFrameStatistics]; NSArray> *appStartSpans = sentryBuildAppStartSpans(self, appStartMeasurement); @@ -736,7 +736,7 @@ - (SentryTransaction *)toTransaction [spans addObjectsFromArray:_children]; } -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION [spans addObjectsFromArray:appStartSpans]; #endif // SENTRY_HAS_UIKIT @@ -763,7 +763,7 @@ - (SentryTransaction *)toTransaction [debugImageProvider getDebugImagesFromCacheForFrames:framesOfAllSpans]; } -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION [self addAppStartMeasurements:transaction]; if ([viewNames count] > 0) { @@ -774,7 +774,7 @@ - (SentryTransaction *)toTransaction return transaction; } -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION - (nullable SentryAppStartMeasurement *)getAppStartMeasurement SENTRY_DISABLE_THREAD_SANITIZER( "double-checked lock produce false alarms") diff --git a/Sources/Sentry/include/SentrySpan.h b/Sources/Sentry/include/SentrySpan.h index b15dbe69428..bcc9fba7459 100644 --- a/Sources/Sentry/include/SentrySpan.h +++ b/Sources/Sentry/include/SentrySpan.h @@ -96,7 +96,7 @@ SENTRY_NO_INIT */ - (instancetype)initWithTracer:(SentryTracer *)transaction context:(SentrySpanContext *)context -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION framesTracker:(nullable SentryFramesTracker *)framesTracker; #endif // SENTRY_HAS_UIKIT ; @@ -106,7 +106,7 @@ SENTRY_NO_INIT * @param context This span context information. */ - (instancetype)initWithContext:(SentrySpanContext *)context -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION framesTracker:(nullable SentryFramesTracker *)framesTracker; #endif // SENTRY_HAS_UIKIT ; diff --git a/Sources/Swift/Helper/SentryProcessInfo.swift b/Sources/Swift/Helper/SentryProcessInfo.swift index db48e1c882b..95e8057044f 100644 --- a/Sources/Swift/Helper/SentryProcessInfo.swift +++ b/Sources/Swift/Helper/SentryProcessInfo.swift @@ -33,11 +33,17 @@ if #available(iOS 26.1, visionOS 26.1, *) { // Use official API when available // https://developer.apple.com/documentation/foundation/processinfo/isiosapponvision - return self.isiOSAppOnVision - } else { - // Fallback for older versions: `UIWindowSceneGeometryPreferencesVision` is only available on visionOS - // https://developer.apple.com/documentation/uikit/uiwindowscene/geometrypreferences/vision?language=objc - return NSClassFromString("UIWindowSceneGeometryPreferencesVision") != nil + // + // For unknown reasons when running an iOS app "Designed for iPad" on visionOS 1.1, the simulator system + // version is simulator 17.4, but it still enters this block. + // + // Due to that it crashes with an uncaught exception 'NSInvalidArgumentException', reason: '-[NSProcessInfo isiOSAppOnVision]: unrecognized selector sent to instance 0x600001549230' + if self.responds(to: NSSelectorFromString("isiOSAppOnVision")) { + return self.isiOSAppOnVision + } } + // Fallback for older versions: `UIWindowSceneGeometryPreferencesVision` is only available on visionOS + // https://developer.apple.com/documentation/uikit/uiwindowscene/geometrypreferences/vision?language=objc + return NSClassFromString("UIWindowSceneGeometryPreferencesVision") != nil } } diff --git a/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift b/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift index a962d73061f..5726042d5ba 100644 --- a/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift @@ -78,7 +78,7 @@ class SentryANRTrackingIntegrationTests: SentrySDKIntegrationTestsBase { let tracker = try XCTUnwrap(Dynamic(sut).tracker.asAnyObject as? SentryANRTracker) -#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst) +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) XCTAssertTrue(tracker.helper is SentryANRTrackerV2, "Expected SentryANRTrackerV2, but got \(type(of: tracker))") #else XCTAssertTrue(tracker.helper is SentryANRTrackerV1, "Expected SentryANRTrackerV1 on macOS, but got \(type(of: tracker))") diff --git a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift index 61f74fd8314..50a5fe2c78a 100644 --- a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift +++ b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift @@ -322,7 +322,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { let expectedInvocations = invocationsBefore + 1 XCTAssertEqual(dispatchQueueWrapper.blockOnMainInvocations.count, expectedInvocations, "reportFullyDisplayed should be dispatched on the main queue. ") } - + func testNotWaitingForFullyDisplayed_AfterTracerTimesOut() throws { fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 9)) diff --git a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift index 5e7698b3040..208583dbaa5 100644 --- a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift +++ b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift @@ -126,7 +126,7 @@ class PrivateSentrySDKOnlyTests: XCTestCase { XCTAssertNil(PrivateSentrySDKOnly.envelope(with: itemData)) } - #if canImport(UIKit) && !os(visionOS) + #if canImport(UIKit) func testGetAppStartMeasurement() { let appStartMeasurement = TestData.getAppStartMeasurement(type: .warm, runtimeInitSystemTimestamp: 1) SentrySDKInternal.setAppStartMeasurement(appStartMeasurement) diff --git a/Tests/SentryTests/Protocol/TestData.swift b/Tests/SentryTests/Protocol/TestData.swift index 538c3d198de..6c9f8d6b3a7 100644 --- a/Tests/SentryTests/Protocol/TestData.swift +++ b/Tests/SentryTests/Protocol/TestData.swift @@ -2,7 +2,7 @@ import Sentry @_spi(Private) import Sentry @_spi(Private) import SentryTestUtils -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst) import UIKit #endif @@ -340,8 +340,8 @@ class TestData { return request } - #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) - + #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) || os(visionOS) + static func getAppStartMeasurement( type: SentryAppStartType, appStartTimestamp: Date = TestData.timestamp, diff --git a/Tests/SentryTests/SentryClientTests.swift b/Tests/SentryTests/SentryClientTests.swift index 317190f4a88..13a7e9f67a4 100644 --- a/Tests/SentryTests/SentryClientTests.swift +++ b/Tests/SentryTests/SentryClientTests.swift @@ -1951,7 +1951,7 @@ class SentryClientTests: XCTestCase { if !SentryDependencyContainer.sharedInstance().crashWrapper.isBeingTraced { expectedIntegrations = ["ANRTracking"] + expectedIntegrations } -#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst) +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) expectedIntegrations.append("FramesTracking") #endif // os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) @@ -2503,7 +2503,7 @@ private extension SentryClientTests { } private func getSpan(operation: String, tracer: SentryTracer) -> Span { -#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst) +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) return SentrySpan(tracer: tracer, context: SpanContext(operation: operation), framesTracker: nil) #else return SentrySpan(tracer: tracer, context: SpanContext(operation: operation)) diff --git a/Tests/SentryTests/SentryOptionsTest.m b/Tests/SentryTests/SentryOptionsTest.m index 8449d387e4c..bb837bc1f98 100644 --- a/Tests/SentryTests/SentryOptionsTest.m +++ b/Tests/SentryTests/SentryOptionsTest.m @@ -359,7 +359,7 @@ - (void)testBeforeSendSpan SentryOptions *options = [self getValidOptions:@{ @"beforeSendSpan" : callback }]; options.beforeSendSpan( [[SentrySpan alloc] initWithContext:[[SentrySpanContext alloc] initWithOperation:@""] -#if SENTRY_HAS_UIKIT +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION framesTracker:NULL #endif ]); @@ -713,7 +713,7 @@ - (void)assertDefaultValues:(SentryOptions *)options XCTAssertEqual(200 * 1024 * 1024, options.maxAttachmentSize); XCTAssertEqual(NO, options.sendDefaultPii); XCTAssertTrue(options.enableAutoPerformanceTracing); -#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION +#if SENTRY_HAS_UIKIT && SENTRY_TARGET_REPLAY_SUPPORTED XCTAssertTrue(options.enableUIViewControllerTracing); XCTAssertFalse(options.attachScreenshot); XCTAssertEqual(3.0, options.idleTimeout); @@ -893,7 +893,7 @@ - (void)testEnablePreWarmedAppStartTracking [self testBooleanField:@"enablePreWarmedAppStartTracing" defaultValue:YES]; } -# if !TARGET_OS_VISION +# if SENTRY_TARGET_REPLAY_SUPPORTED - (void)testSessionReplaySettingsInit { if (@available(iOS 16.0, tvOS 16.0, *)) { @@ -913,7 +913,7 @@ - (void)testSessionReplaySettingsDefaults XCTAssertEqual(options.sessionReplay.onErrorSampleRate, 0); } } -# endif +# endif // SENTRY_TARGET_REPLAY_SUPPORTED #endif // SENTRY_HAS_UIKIT #if SENTRY_HAS_METRIC_KIT diff --git a/Tests/SentryTests/SentrySDKTests.swift b/Tests/SentryTests/SentrySDKTests.swift index 1a6c3648b15..9ec693a716e 100644 --- a/Tests/SentryTests/SentrySDKTests.swift +++ b/Tests/SentryTests/SentrySDKTests.swift @@ -141,7 +141,7 @@ class SentrySDKTests: XCTestCase { if !SentryDependencyContainer.sharedInstance().crashWrapper.isBeingTraced { expectedIntegrations.append("SentryANRTrackingIntegration") } -#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst) +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) expectedIntegrations.append("SentryFramesTrackingIntegration") #endif // os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) diff --git a/Tests/SentryTests/TestSentryNSApplication.swift b/Tests/SentryTests/TestSentryNSApplication.swift index 9ccca0c5cbf..888bb80f5d4 100644 --- a/Tests/SentryTests/TestSentryNSApplication.swift +++ b/Tests/SentryTests/TestSentryNSApplication.swift @@ -1,6 +1,6 @@ @_spi(Private) @testable import Sentry -#if !(os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || (swift(>=5.9) && os(visionOS))) +#if !(os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || os(visionOS)) class TestSentryNSApplication: SentryApplication { private var _underlyingIsActive = true func setIsActive(_ isActive: Bool) { diff --git a/Tests/SentryTests/Transaction/SentryBuildAppStartSpansTests.swift b/Tests/SentryTests/Transaction/SentryBuildAppStartSpansTests.swift index b78bd129b3f..31bd88407c3 100644 --- a/Tests/SentryTests/Transaction/SentryBuildAppStartSpansTests.swift +++ b/Tests/SentryTests/Transaction/SentryBuildAppStartSpansTests.swift @@ -1,7 +1,7 @@ @testable import Sentry import XCTest -#if canImport(UIKit) +#if canImport(UIKit) && !os(visionOS) class SentryBuildAppStartSpansTests: XCTestCase { func testSentryBuildAppStartSpans_appStartMeasurementIsNil_shouldNotReturnAnySpans() { diff --git a/Tests/SentryTests/Transaction/SentrySpanTests.swift b/Tests/SentryTests/Transaction/SentrySpanTests.swift index bf812293084..476b597e28d 100644 --- a/Tests/SentryTests/Transaction/SentrySpanTests.swift +++ b/Tests/SentryTests/Transaction/SentrySpanTests.swift @@ -15,7 +15,7 @@ class SentrySpanTests: XCTestCase { let options: Options let notificationCenter = TestNSNotificationCenterWrapper() let currentDateProvider = TestCurrentDateProvider() -#if (os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) let tracer = SentryTracer(context: SpanContext(operation: "TEST"), framesTracker: nil) #else let tracer = SentryTracer(context: SpanContext(operation: "TEST")) @@ -41,7 +41,7 @@ class SentrySpanTests: XCTestCase { } func getSutWithTracer() -> SentrySpan { -#if (os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) return SentrySpan(tracer: tracer, context: SpanContext(operation: someOperation, sampled: .undecided), framesTracker: nil) #else return SentrySpan(tracer: tracer, context: SpanContext(operation: someOperation, sampled: .undecided)) @@ -538,7 +538,7 @@ class SentrySpanTests: XCTestCase { // Span has a weak reference to tracer. If we don't keep a reference // to the tracer ARC will deallocate the tracer. let sutGenerator: () -> Span = { -#if (os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) let tracer = SentryTracer(context: SpanContext(operation: "TEST"), framesTracker: nil) return SentrySpan(tracer: tracer, context: SpanContext(operation: ""), framesTracker: nil) #else @@ -628,7 +628,7 @@ class SentrySpanTests: XCTestCase { XCTAssertEqual(expectedBaggage, sut.baggageHttpHeader()) } -#if (os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) func testAddSlowFrozenFramesToData() { let (displayLinkWrapper, framesTracker) = givenFramesTracker() @@ -706,5 +706,5 @@ class SentrySpanTests: XCTestCase { return (displayLinkWrapper, framesTracker) } -#endif // (os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT +#endif // os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) }