diff --git a/CHANGELOG.md b/CHANGELOG.md index 338dbe88e97..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/). diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 46c011026f2..c2fc9f4f04e 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/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 180b0b8f4f9..95e8057044f 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,22 @@ 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 + // + // 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/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/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift index 81a9cc49ee8..50a5fe2c78a 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 { 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..208583dbaa5 100644 --- a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift +++ b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift @@ -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/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/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..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 +#if SENTRY_HAS_UIKIT && SENTRY_TARGET_REPLAY_SUPPORTED XCTAssertTrue(options.enableUIViewControllerTracing); XCTAssertFalse(options.attachScreenshot); XCTAssertEqual(3.0, options.idleTimeout); @@ -893,6 +893,7 @@ - (void)testEnablePreWarmedAppStartTracking [self testBooleanField:@"enablePreWarmedAppStartTracing" defaultValue:YES]; } +# if SENTRY_TARGET_REPLAY_SUPPORTED - (void)testSessionReplaySettingsInit { if (@available(iOS 16.0, tvOS 16.0, *)) { @@ -912,7 +913,7 @@ - (void)testSessionReplaySettingsDefaults XCTAssertEqual(options.sessionReplay.onErrorSampleRate, 0); } } - +# endif // SENTRY_TARGET_REPLAY_SUPPORTED #endif // SENTRY_HAS_UIKIT #if SENTRY_HAS_METRIC_KIT 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..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)) +#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/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)