diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/crash/CompositeCrashService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/crash/CompositeCrashService.kt deleted file mode 100644 index 9722a75d4..000000000 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/crash/CompositeCrashService.kt +++ /dev/null @@ -1,47 +0,0 @@ -package io.embrace.android.embracesdk.capture.crash - -import io.embrace.android.embracesdk.config.ConfigService -import io.embrace.android.embracesdk.internal.ApkToolsConfig -import io.embrace.android.embracesdk.internal.utils.Provider -import io.embrace.android.embracesdk.logging.EmbLogger -import io.embrace.android.embracesdk.payload.JsException - -internal class CompositeCrashService( - private val crashServiceProvider: Provider, - private val crashDataSourceProvider: Provider, - private val configService: ConfigService, - private val logger: EmbLogger, -) : CrashService { - - private val useCrashDataSource: Boolean - get() = configService.oTelBehavior.isBetaEnabled() - - private val baseCrashService: CrashService - get() = if (useCrashDataSource) crashDataSourceProvider() else crashServiceProvider() - - init { - if (configService.autoDataCaptureBehavior.isUncaughtExceptionHandlerEnabled() && - !ApkToolsConfig.IS_EXCEPTION_CAPTURE_DISABLED - ) { - registerExceptionHandler() - } - } - - override fun handleCrash(exception: Throwable) { - baseCrashService.handleCrash(exception) - } - - override fun logUnhandledJsException(exception: JsException) { - baseCrashService.logUnhandledJsException(exception) - } - - /** - * Registers the Embrace [java.lang.Thread.UncaughtExceptionHandler] to intercept uncaught - * exceptions and forward them to the Embrace API as crashes. - */ - private fun registerExceptionHandler() { - val defaultHandler = Thread.getDefaultUncaughtExceptionHandler() - val embraceHandler = EmbraceUncaughtExceptionHandler(defaultHandler, this, logger) - Thread.setDefaultUncaughtExceptionHandler(embraceHandler) - } -} diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/crash/CrashDataSourceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/crash/CrashDataSourceImpl.kt index dfa017508..7af57127f 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/crash/CrashDataSourceImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/crash/CrashDataSourceImpl.kt @@ -11,6 +11,7 @@ import io.embrace.android.embracesdk.arch.schema.EmbType.System.ReactNativeCrash import io.embrace.android.embracesdk.arch.schema.SchemaType import io.embrace.android.embracesdk.arch.schema.TelemetryAttributes import io.embrace.android.embracesdk.config.ConfigService +import io.embrace.android.embracesdk.internal.ApkToolsConfig import io.embrace.android.embracesdk.internal.crash.CrashFileMarker import io.embrace.android.embracesdk.internal.logs.LogOrchestrator import io.embrace.android.embracesdk.internal.serialization.EmbraceSerializer @@ -45,7 +46,7 @@ internal class CrashDataSourceImpl( private val logWriter: LogWriter, private val configService: ConfigService, private val serializer: EmbraceSerializer, - logger: EmbLogger, + private val logger: EmbLogger, ) : CrashDataSource, LogDataSourceImpl( destination = logWriter, @@ -56,6 +57,14 @@ internal class CrashDataSourceImpl( private var mainCrashHandled = false private var jsException: JsException? = null + init { + if (configService.autoDataCaptureBehavior.isUncaughtExceptionHandlerEnabled() && + !ApkToolsConfig.IS_EXCEPTION_CAPTURE_DISABLED + ) { + registerExceptionHandler() + } + } + /** * Handles a crash caught by the [EmbraceUncaughtExceptionHandler] by constructing a * JSON message containing a description of the crash, device, and context, and then sending @@ -171,4 +180,14 @@ internal class CrashDataSourceImpl( private fun encodeToUTF8String(source: String): String { return source.toByteArray().toUTF8String() } + + /** + * Registers the Embrace [java.lang.Thread.UncaughtExceptionHandler] to intercept uncaught + * exceptions and forward them to the Embrace API as crashes. + */ + private fun registerExceptionHandler() { + val defaultHandler = Thread.getDefaultUncaughtExceptionHandler() + val embraceHandler = EmbraceUncaughtExceptionHandler(defaultHandler, this, logger) + Thread.setDefaultUncaughtExceptionHandler(embraceHandler) + } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/crash/EmbraceCrashService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/crash/EmbraceCrashService.kt deleted file mode 100644 index 11dc311d2..000000000 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/crash/EmbraceCrashService.kt +++ /dev/null @@ -1,140 +0,0 @@ -package io.embrace.android.embracesdk.capture.crash - -import io.embrace.android.embracesdk.EventType -import io.embrace.android.embracesdk.anr.AnrService -import io.embrace.android.embracesdk.capture.metadata.MetadataService -import io.embrace.android.embracesdk.capture.user.UserService -import io.embrace.android.embracesdk.comms.api.ApiClient -import io.embrace.android.embracesdk.comms.delivery.DeliveryService -import io.embrace.android.embracesdk.event.EventService -import io.embrace.android.embracesdk.gating.GatingService -import io.embrace.android.embracesdk.internal.clock.Clock -import io.embrace.android.embracesdk.internal.crash.CrashFileMarker -import io.embrace.android.embracesdk.internal.logs.LogOrchestrator -import io.embrace.android.embracesdk.internal.utils.Uuid.getEmbUuid -import io.embrace.android.embracesdk.logging.EmbLogger -import io.embrace.android.embracesdk.ndk.NdkService -import io.embrace.android.embracesdk.payload.Event -import io.embrace.android.embracesdk.payload.EventMessage -import io.embrace.android.embracesdk.payload.JsException -import io.embrace.android.embracesdk.payload.extensions.CrashFactory -import io.embrace.android.embracesdk.prefs.PreferencesService -import io.embrace.android.embracesdk.session.id.SessionIdTracker -import io.embrace.android.embracesdk.session.orchestrator.SessionOrchestrator -import io.embrace.android.embracesdk.session.properties.SessionPropertiesService - -/** - * Intercepts uncaught Java exceptions and forwards them to the Embrace API. - */ -internal class EmbraceCrashService( - private val logOrchestrator: LogOrchestrator, - private val sessionOrchestrator: SessionOrchestrator, - private val sessionPropertiesService: SessionPropertiesService, - private val metadataService: MetadataService, - private val sessionIdTracker: SessionIdTracker, - private val deliveryService: DeliveryService, - private val userService: UserService, - private val eventService: EventService, - private val anrService: AnrService?, - private val ndkService: NdkService, - private val gatingService: GatingService, - private val preferencesService: PreferencesService, - private val crashMarker: CrashFileMarker, - private val clock: Clock, - private val logger: EmbLogger -) : CrashService { - - private var mainCrashHandled = false - private var jsException: JsException? = null - - /** - * Handles a crash caught by the [EmbraceUncaughtExceptionHandler] by constructing a - * JSON message containing a description of the crash, device, and context, and then sending - * it to the Embrace API. - * - * @param exception the exception thrown by the thread - */ - override fun handleCrash(exception: Throwable) { - if (!mainCrashHandled) { - mainCrashHandled = true - - // Stop ANR tracking first to avoid capture ANR when crash message is being sent - anrService?.forceAnrTrackingStopOnCrash() - - // Check if the unity crash id exists. If so, means that the native crash capture - // is enabled for an Unity build. When a native crash occurs and the NDK sends an - // uncaught exception the SDK assign the unity crash id as the java crash id. - val unityCrashId = ndkService.getUnityCrashId() - val crashNumber = preferencesService.incrementAndGetCrashNumber() - val crash = if (unityCrashId != null) { - CrashFactory.ofThrowable(logger, exception, jsException, crashNumber, unityCrashId) - } else { - CrashFactory.ofThrowable(logger, exception, jsException, crashNumber) - } - - val sessionId = sessionIdTracker.getActiveSessionId() - - val event = Event( - CRASH_REPORT_EVENT_NAME, - null, - getEmbUuid(), - sessionId, - EventType.CRASH, - clock.now(), - null, - false, - null, - metadataService.getAppState(), - null, - sessionPropertiesService.getProperties(), - eventService.getActiveEventIds(), - null, - null, - null, - null - ) - val versionedEvent = EventMessage( - event, - crash, - metadataService.getDeviceInfo(), - metadataService.getAppInfo(), - userService.getUserInfo(), - null, - null, - ApiClient.MESSAGE_VERSION, - null - ) - - // Sanitize crash event - val crashEvent = gatingService.gateEventMessage(versionedEvent) - - // Send the crash. This is not guaranteed to succeed since the process is terminating - // and the request is made on a background executor, but data analysis shows that - // a surprising % of crashes make it through based on the receive time. Therefore we - // attempt to send the crash and if it fails, we will send it again on the next launch. - deliveryService.sendCrash(crashEvent, true) - - // Attempt to send any logs that are still waiting in the sink - logOrchestrator.flush(true) - - // End, cache and send the session - sessionOrchestrator.endSessionWithCrash(crash.crashId) - - // Indicate that a crash happened so we can know that in the next launch - crashMarker.mark() - } - } - - /** - * Associates an unhandled JS exception with a crash - * - * @param exception the unhandled JS exception - */ - override fun logUnhandledJsException(exception: JsException) { - this.jsException = exception - } - - companion object { - private const val CRASH_REPORT_EVENT_NAME = "_crash_report" - } -} diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/CrashModule.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/CrashModule.kt index 724de3f03..d30c2576a 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/CrashModule.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/CrashModule.kt @@ -1,10 +1,7 @@ package io.embrace.android.embracesdk.injection -import io.embrace.android.embracesdk.capture.crash.CompositeCrashService -import io.embrace.android.embracesdk.capture.crash.CrashDataSource import io.embrace.android.embracesdk.capture.crash.CrashDataSourceImpl import io.embrace.android.embracesdk.capture.crash.CrashService -import io.embrace.android.embracesdk.capture.crash.EmbraceCrashService import io.embrace.android.embracesdk.internal.crash.CrashFileMarker import io.embrace.android.embracesdk.internal.crash.CrashFileMarkerImpl import io.embrace.android.embracesdk.internal.crash.LastRunCrashVerifier @@ -28,11 +25,9 @@ internal class CrashModuleImpl( initModule: InitModule, storageModule: StorageModule, essentialServiceModule: EssentialServiceModule, - deliveryModule: DeliveryModule, nativeModule: NativeModule, sessionModule: SessionModule, anrModule: AnrModule, - dataContainerModule: DataContainerModule, androidServicesModule: AndroidServicesModule, logModule: CustomerLogModule ) : CrashModule { @@ -44,27 +39,7 @@ internal class CrashModuleImpl( CrashFileMarkerImpl(markerFile, initModule.logger) } - private val legacyCrashService: CrashService by singleton { - EmbraceCrashService( - logModule.logOrchestrator, - sessionModule.sessionOrchestrator, - sessionModule.sessionPropertiesService, - essentialServiceModule.metadataService, - essentialServiceModule.sessionIdTracker, - deliveryModule.deliveryService, - essentialServiceModule.userService, - dataContainerModule.eventService, - anrModule.anrService, - nativeModule.ndkService, - essentialServiceModule.gatingService, - androidServicesModule.preferencesService, - crashMarker, - initModule.clock, - initModule.logger - ) - } - - private val crashDataSource: CrashDataSource by singleton { + override val crashService: CrashService by singleton { CrashDataSourceImpl( logModule.logOrchestrator, sessionModule.sessionOrchestrator, @@ -80,15 +55,6 @@ internal class CrashModuleImpl( ) } - override val crashService: CrashService by singleton { - CompositeCrashService( - { legacyCrashService }, - { crashDataSource }, - essentialServiceModule.configService, - initModule.logger - ) - } - override val lastRunCrashVerifier: LastRunCrashVerifier by singleton { LastRunCrashVerifier(crashMarker, initModule.logger) } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/ModuleInitBootstrapper.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/ModuleInitBootstrapper.kt index 647b6b06e..82c7296e6 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/ModuleInitBootstrapper.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/ModuleInitBootstrapper.kt @@ -419,11 +419,9 @@ internal class ModuleInitBootstrapper( initModule, storageModule, essentialServiceModule, - deliveryModule, nativeModule, sessionModule, anrModule, - dataContainerModule, androidServicesModule, customerLogModule, ) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/utils/Types.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/utils/Types.kt index 21925982f..ff5121e02 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/utils/Types.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/utils/Types.kt @@ -211,11 +211,9 @@ internal typealias CrashModuleSupplier = ( initModule: InitModule, storageModule: StorageModule, essentialServiceModule: EssentialServiceModule, - deliveryModule: DeliveryModule, nativeModule: NativeModule, sessionModule: SessionModule, anrModule: AnrModule, - dataContainerModule: DataContainerModule, androidServicesModule: AndroidServicesModule, logModule: CustomerLogModule, ) -> CrashModule diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceCrashServiceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceCrashServiceTest.kt deleted file mode 100644 index 510a639b1..000000000 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceCrashServiceTest.kt +++ /dev/null @@ -1,208 +0,0 @@ -package io.embrace.android.embracesdk - -import io.embrace.android.embracesdk.capture.crash.EmbraceCrashService -import io.embrace.android.embracesdk.fakes.FakeAnrService -import io.embrace.android.embracesdk.fakes.FakeClock -import io.embrace.android.embracesdk.fakes.FakeConfigService -import io.embrace.android.embracesdk.fakes.FakeEventService -import io.embrace.android.embracesdk.fakes.FakeLogMessageService -import io.embrace.android.embracesdk.fakes.FakeLogOrchestrator -import io.embrace.android.embracesdk.fakes.FakeMetadataService -import io.embrace.android.embracesdk.fakes.FakePreferenceService -import io.embrace.android.embracesdk.fakes.FakeSessionIdTracker -import io.embrace.android.embracesdk.fakes.FakeSessionOrchestrator -import io.embrace.android.embracesdk.fakes.FakeUserService -import io.embrace.android.embracesdk.gating.EmbraceGatingService -import io.embrace.android.embracesdk.internal.crash.CrashFileMarkerImpl -import io.embrace.android.embracesdk.logging.EmbLogger -import io.embrace.android.embracesdk.logging.EmbLoggerImpl -import io.embrace.android.embracesdk.payload.Crash -import io.embrace.android.embracesdk.payload.JsException -import io.embrace.android.embracesdk.payload.LegacyExceptionInfo -import io.embrace.android.embracesdk.payload.ThreadInfo -import io.embrace.android.embracesdk.payload.extensions.CrashFactory -import io.embrace.android.embracesdk.session.properties.SessionPropertiesService -import io.embrace.android.embracesdk.utils.at -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.mockkStatic -import io.mockk.unmockkAll -import io.mockk.verify -import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertSame -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test - -internal class EmbraceCrashServiceTest { - - private lateinit var embraceCrashService: EmbraceCrashService - private lateinit var logOrchestrator: FakeLogOrchestrator - private lateinit var sessionOrchestrator: FakeSessionOrchestrator - private lateinit var sessionPropertiesService: SessionPropertiesService - private lateinit var metadataService: FakeMetadataService - private lateinit var sessionIdTracker: FakeSessionIdTracker - private lateinit var deliveryService: FakeDeliveryService - private lateinit var userService: FakeUserService - private lateinit var eventService: FakeEventService - private lateinit var anrService: FakeAnrService - private lateinit var ndkService: FakeNdkService - private lateinit var preferencesService: FakePreferenceService - private lateinit var logger: EmbLogger - - private lateinit var crash: Crash - private lateinit var localJsException: JsException - private lateinit var crashMarker: CrashFileMarkerImpl - private val testException = RuntimeException("Test exception") - private val fakeClock = FakeClock(1000L) - - @Before - fun setup() { - mockkStatic(Crash::class) - mockkObject(CrashFactory) - - logOrchestrator = FakeLogOrchestrator() - sessionOrchestrator = FakeSessionOrchestrator() - sessionPropertiesService = FakeSessionPropertiesService() - metadataService = FakeMetadataService() - sessionIdTracker = FakeSessionIdTracker() - deliveryService = FakeDeliveryService() - userService = FakeUserService() - eventService = FakeEventService() - anrService = FakeAnrService() - ndkService = FakeNdkService() - preferencesService = FakePreferenceService() - crashMarker = mockk(relaxUnitFun = true) - logger = EmbLoggerImpl() - - localJsException = JsException("jsException", "Error", "Error", "") - } - - private fun setupForHandleCrash() { - val gatingService = EmbraceGatingService(FakeConfigService(), FakeLogMessageService(), logger) - - embraceCrashService = EmbraceCrashService( - logOrchestrator, - sessionOrchestrator, - sessionPropertiesService, - metadataService, - sessionIdTracker, - deliveryService, - userService, - eventService, - anrService, - ndkService, - gatingService, - preferencesService, - crashMarker, - fakeClock, - logger - ) - - metadataService.setAppForeground() - } - - @Test - fun `test SessionOrchestrator and LogOrchestrator are called when handleCrash is called`() { - crash = CrashFactory.ofThrowable(logger, testException, null, 1) - setupForHandleCrash() - - embraceCrashService.handleCrash(testException) - - assertEquals(1, anrService.forceAnrTrackingStopOnCrashCount) - assertNotNull(deliveryService.lastSentCrash) - assertTrue(logOrchestrator.flushCalled) - assertNotNull(sessionOrchestrator.crashId) - } - - @Test - fun `test ApiClient and SessionService are called when handleCrash is called with JSException`() { - setupForHandleCrash() - embraceCrashService.handleCrash(testException) - - assertEquals(1, anrService.forceAnrTrackingStopOnCrashCount) - val lastSentCrash = deliveryService.lastSentCrash - assertNotNull(lastSentCrash) - assertEquals(lastSentCrash?.crash?.crashId, sessionOrchestrator.crashId) - - /* - * Verify mainCrashHandled is true after the first execution - * by testing that a second execution of handleCrash wont run anything - */ - embraceCrashService.handleCrash(testException) - assertEquals(1, anrService.forceAnrTrackingStopOnCrashCount) - assertNotNull(deliveryService.lastSentCrash) - assertSame(lastSentCrash, deliveryService.lastSentCrash) - } - - @Test - fun `test ApiClient and SessionService are called when handleCrash is called with unityId`() { - crash = CrashFactory.ofThrowable(logger, testException, localJsException, 1, "Unity123") - setupForHandleCrash() - ndkService.lastUnityCrashId = "Unity123" - - embraceCrashService.handleCrash(testException) - - verify { CrashFactory.ofThrowable(logger, testException, localJsException, 1, "Unity123") } - assertEquals(1, anrService.forceAnrTrackingStopOnCrashCount) - assertNotNull(deliveryService.lastSentCrash) - assertEquals(crash.crashId, sessionOrchestrator.crashId) - } - - @Test - fun `test handleCrash calls mark() method when capture_last_run config is enabled`() { - crash = CrashFactory.ofThrowable(logger, testException, localJsException, 1, "Unity123") - setupForHandleCrash() - - embraceCrashService.handleCrash(testException) - - verify(exactly = 1) { crashMarker.mark() } - } - - @After - fun tearDown() { - unmockkAll() - } - - @Test - fun testSerialization() { - val crash = Crash( - "123", - listOf( - LegacyExceptionInfo( - "java.lang.RuntimeException", - "ExceptionMessage", - listOf("stacktrace.line") - ) - ), - listOf("js_exception"), - listOf( - ThreadInfo( - 123, - Thread.State.RUNNABLE, - "ReferenceHandler", - 1, - listOf("stacktrace.line.thread") - ) - ) - ) - assertJsonMatchesGoldenFile("crash_expected.json", crash) - } - - @Test - fun testDeserialization() { - val obj = deserializeJsonFromResource("crash_expected.json") - assertEquals("123", obj.crashId) - assertEquals("java.lang.RuntimeException", obj.exceptions?.at(0)?.name) - assertEquals("ExceptionMessage", obj.exceptions?.at(0)?.message) - assertEquals("stacktrace.line", obj.exceptions?.at(0)?.lines?.at(0)) - assertEquals("js_exception", obj.jsExceptions?.at(0)) - assertEquals(123L, obj.threads?.at(0)?.threadId) - assertEquals(Thread.State.RUNNABLE, obj.threads?.at(0)?.state) - assertEquals("ReferenceHandler", obj.threads?.at(0)?.name) - assertEquals(1, obj.threads?.at(0)?.priority) - assertEquals("stacktrace.line.thread", obj.threads?.at(0)?.lines?.at(0)) - } -} diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crash/CompositeCrashServiceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crash/CompositeCrashServiceTest.kt deleted file mode 100644 index c2f650270..000000000 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crash/CompositeCrashServiceTest.kt +++ /dev/null @@ -1,121 +0,0 @@ -package io.embrace.android.embracesdk.capture.crash - -import io.embrace.android.embracesdk.config.local.CrashHandlerLocalConfig -import io.embrace.android.embracesdk.config.local.LocalConfig -import io.embrace.android.embracesdk.config.local.SdkLocalConfig -import io.embrace.android.embracesdk.config.remote.OTelRemoteConfig -import io.embrace.android.embracesdk.config.remote.RemoteConfig -import io.embrace.android.embracesdk.fakes.FakeConfigService -import io.embrace.android.embracesdk.fakes.FakeCrashDataSource -import io.embrace.android.embracesdk.fakes.FakeCrashService -import io.embrace.android.embracesdk.fakes.fakeAutoDataCaptureBehavior -import io.embrace.android.embracesdk.fakes.fakeOTelBehavior -import io.embrace.android.embracesdk.logging.EmbLogger -import io.embrace.android.embracesdk.logging.EmbLoggerImpl -import io.embrace.android.embracesdk.payload.JsException -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test - -internal class CompositeCrashServiceTest { - - private lateinit var compositeCrashService: CompositeCrashService - private lateinit var configService: FakeConfigService - private lateinit var logger: EmbLogger - private lateinit var crashServiceV1: FakeCrashService - private lateinit var crashServiceV2: FakeCrashDataSource - private lateinit var oTelConfig: OTelRemoteConfig - - @Before - fun setUp() { - logger = EmbLoggerImpl() - crashServiceV1 = FakeCrashService() - crashServiceV2 = FakeCrashDataSource() - oTelConfig = OTelRemoteConfig(isBetaEnabled = false) - } - - @Test - fun `test exception handler is registered with config option enabled`() { - setupForHandleCrash(true) - assert(Thread.getDefaultUncaughtExceptionHandler() is EmbraceUncaughtExceptionHandler) - } - - @Test - fun `test exception handler is not registered with config option disabled`() { - setupForHandleCrash(false) - assert(Thread.getDefaultUncaughtExceptionHandler() !is EmbraceUncaughtExceptionHandler) - } - - @Test - fun `test handleCrash is called on crashServiceV1 if OTelConfig is false`() { - setupForHandleCrash(true) - val exception = RuntimeException("Test exception") - compositeCrashService.handleCrash(exception) - assertEquals(null, crashServiceV2.exception) - assertEquals(exception, crashServiceV1.exception) - } - - @Test - fun `test handleCrash is called on crashServiceV2 if OTelConfig is true`() { - oTelConfig = OTelRemoteConfig(isBetaEnabled = true) - setupForHandleCrash(true) - val exception = RuntimeException("Test exception") - compositeCrashService.handleCrash(exception) - assertEquals(exception, crashServiceV2.exception) - assertEquals(null, crashServiceV1.exception) - } - - @Test - fun `test logUnhandledJsException is called on crashServiceV1 if OTelConfig is false`() { - setupForHandleCrash(true) - val exception = JsException( - name = "Exception name", - message = "Exception message", - type = "Exception type", - stacktrace = "Exception stacktrace", - ) - compositeCrashService.logUnhandledJsException(exception) - assertEquals(null, crashServiceV2.jsException) - assertEquals(exception, crashServiceV1.jsException) - } - - @Test - fun `test logUnhandledJsException is called on crashServiceV2 if OTelConfig is true`() { - oTelConfig = OTelRemoteConfig(isBetaEnabled = true) - setupForHandleCrash(true) - val exception = JsException( - name = "Exception name", - message = "Exception message", - type = "Exception type", - stacktrace = "Exception stacktrace", - ) - compositeCrashService.logUnhandledJsException(exception) - assertEquals(exception, crashServiceV2.jsException) - assertEquals(null, crashServiceV1.jsException) - } - - private fun setupForHandleCrash(crashHandlerEnabled: Boolean) { - configService = FakeConfigService( - autoDataCaptureBehavior = fakeAutoDataCaptureBehavior( - localCfg = { - LocalConfig( - "", - false, - SdkLocalConfig(crashHandler = CrashHandlerLocalConfig(crashHandlerEnabled)) - ) - } - ), - oTelBehavior = fakeOTelBehavior( - remoteCfg = { - RemoteConfig(oTelConfig = oTelConfig) - } - ) - ) - compositeCrashService = CompositeCrashService( - { crashServiceV1 }, - { crashServiceV2 }, - configService, - logger - ) - } -} diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crash/CrashDataSourceImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crash/CrashDataSourceImplTest.kt index 01fb8447b..c0421483a 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crash/CrashDataSourceImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crash/CrashDataSourceImplTest.kt @@ -3,6 +3,9 @@ package io.embrace.android.embracesdk.capture.crash import io.embrace.android.embracesdk.FakeNdkService import io.embrace.android.embracesdk.arch.assertIsType import io.embrace.android.embracesdk.arch.schema.EmbType +import io.embrace.android.embracesdk.config.local.CrashHandlerLocalConfig +import io.embrace.android.embracesdk.config.local.LocalConfig +import io.embrace.android.embracesdk.config.local.SdkLocalConfig import io.embrace.android.embracesdk.fakes.FakeAnrService import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeCrashFileMarker @@ -10,6 +13,7 @@ import io.embrace.android.embracesdk.fakes.FakeLogOrchestrator import io.embrace.android.embracesdk.fakes.FakeLogWriter import io.embrace.android.embracesdk.fakes.FakePreferenceService import io.embrace.android.embracesdk.fakes.FakeSessionOrchestrator +import io.embrace.android.embracesdk.fakes.fakeAutoDataCaptureBehavior import io.embrace.android.embracesdk.fakes.fakeEmbraceSessionProperties import io.embrace.android.embracesdk.internal.serialization.EmbraceSerializer import io.embrace.android.embracesdk.logging.EmbLogger @@ -62,7 +66,18 @@ internal class CrashDataSourceImplTest { testException = RuntimeException("Test exception") } - private fun setupForHandleCrash() { + private fun setupForHandleCrash(crashHandlerEnabled: Boolean = false) { + configService = FakeConfigService( + autoDataCaptureBehavior = fakeAutoDataCaptureBehavior( + localCfg = { + LocalConfig( + "", + false, + SdkLocalConfig(crashHandler = CrashHandlerLocalConfig(crashHandlerEnabled)) + ) + } + ) + ) crashDataSource = CrashDataSourceImpl( logOrchestrator, sessionOrchestrator, @@ -98,7 +113,10 @@ internal class CrashDataSourceImplTest { assertEquals(1, anrService.forceAnrTrackingStopOnCrashCount) assertEquals(1, logWriter.logEvents.size) val lastSentCrash = logWriter.logEvents.single() - assertEquals(logWriter.logEvents.single().schemaType.attributes()["log.record.uid"], sessionOrchestrator.crashId) + assertEquals( + logWriter.logEvents.single().schemaType.attributes()["log.record.uid"], + sessionOrchestrator.crashId + ) /* * Verify mainCrashHandled is true after the first execution @@ -119,7 +137,10 @@ internal class CrashDataSourceImplTest { assertEquals(1, anrService.forceAnrTrackingStopOnCrashCount) assertEquals(1, logWriter.logEvents.size) - assertEquals(logWriter.logEvents.single().schemaType.attributes()["log.record.uid"], sessionOrchestrator.crashId) + assertEquals( + logWriter.logEvents.single().schemaType.attributes()["log.record.uid"], + sessionOrchestrator.crashId + ) assertEquals(ndkService.lastUnityCrashId, sessionOrchestrator.crashId) } @@ -153,4 +174,16 @@ internal class CrashDataSourceImplTest { lastSentCrashAttributes["emb.android.react_native_crash.js_exception"] ) } + + @Test + fun `test exception handler is registered with config option enabled`() { + setupForHandleCrash(true) + assert(Thread.getDefaultUncaughtExceptionHandler() is EmbraceUncaughtExceptionHandler) + } + + @Test + fun `test exception handler is not registered with config option disabled`() { + setupForHandleCrash(false) + assert(Thread.getDefaultUncaughtExceptionHandler() !is EmbraceUncaughtExceptionHandler) + } } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeModuleInitBootstrapper.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeModuleInitBootstrapper.kt index ce247d973..d85a55f84 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeModuleInitBootstrapper.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeModuleInitBootstrapper.kt @@ -52,7 +52,7 @@ internal fun fakeModuleInitBootstrapper( nativeModuleSupplier: NativeModuleSupplier = { _, _, _, _, _, _, _ -> FakeNativeModule() }, dataContainerModuleSupplier: DataContainerModuleSupplier = { _, _, _, _, _ -> FakeDataContainerModule() }, sessionModuleSupplier: SessionModuleSupplier = { _, _, _, _, _, _, _, _, _, _, _, _ -> FakeSessionModule() }, - crashModuleSupplier: CrashModuleSupplier = { _, _, _, _, _, _, _, _, _, _ -> FakeCrashModule() }, + crashModuleSupplier: CrashModuleSupplier = { _, _, _, _, _, _, _, _ -> FakeCrashModule() }, payloadModuleSupplier: PayloadModuleSupplier = { _, _, _, _, _, _, _, _, _, _, _ -> FakePayloadModule() } ) = ModuleInitBootstrapper( logger = fakeEmbLogger, diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/injection/CrashModuleImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/injection/CrashModuleImplTest.kt index 697134367..1b35d146a 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/injection/CrashModuleImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/injection/CrashModuleImplTest.kt @@ -10,8 +10,6 @@ import io.embrace.android.embracesdk.fakes.fakeOTelBehavior import io.embrace.android.embracesdk.fakes.injection.FakeAndroidServicesModule import io.embrace.android.embracesdk.fakes.injection.FakeAnrModule import io.embrace.android.embracesdk.fakes.injection.FakeCustomerLogModule -import io.embrace.android.embracesdk.fakes.injection.FakeDataContainerModule -import io.embrace.android.embracesdk.fakes.injection.FakeDeliveryModule import io.embrace.android.embracesdk.fakes.injection.FakeEssentialServiceModule import io.embrace.android.embracesdk.fakes.injection.FakeNativeModule import io.embrace.android.embracesdk.fakes.injection.FakeSessionModule @@ -43,11 +41,9 @@ internal class CrashModuleImplTest { InitModuleImpl(), FakeStorageModule(), FakeEssentialServiceModule(), - FakeDeliveryModule(), FakeNativeModule(), FakeSessionModule(), FakeAnrModule(), - FakeDataContainerModule(), FakeAndroidServicesModule(), FakeCustomerLogModule(), ) @@ -68,11 +64,9 @@ internal class CrashModuleImplTest { oTelBehavior = oTelBehaviorWithBetaFeatureDisabled ) ), - FakeDeliveryModule(), FakeNativeModule(), FakeSessionModule(), FakeAnrModule(), - FakeDataContainerModule(), FakeAndroidServicesModule(), FakeCustomerLogModule(), ) @@ -92,11 +86,9 @@ internal class CrashModuleImplTest { autoDataCaptureBehavior = autoDataCaptureBehaviorWithNdkEnabled ) ), - FakeDeliveryModule(), FakeNativeModule(), FakeSessionModule(), FakeAnrModule(), - FakeDataContainerModule(), FakeAndroidServicesModule(), FakeCustomerLogModule(), )