diff --git a/CHANGELOG.md b/CHANGELOG.md index e9bd1989f4..1ac33edc5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +* Feat: Automatically enable `Timber` and `Fragment` integrations if they are present on the classpath (#1936) + ## 5.6.3 * Fix: If transaction or span is finished, do not allow to mutate (#1940) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 689c85e35a..7a12eff25c 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -1,5 +1,6 @@ public final class io/sentry/android/core/ActivityFramesTracker { public fun (Lio/sentry/android/core/LoadClass;)V + public fun (Lio/sentry/android/core/LoadClass;Lio/sentry/ILogger;)V public fun addActivity (Landroid/app/Activity;)V public fun setMetrics (Landroid/app/Activity;Lio/sentry/protocol/SentryId;)V public fun stop ()V @@ -82,7 +83,9 @@ public abstract interface class io/sentry/android/core/IDebugImagesLoader { public final class io/sentry/android/core/LoadClass { public fun ()V - public fun loadClass (Ljava/lang/String;)Ljava/lang/Class; + public fun isClassAvailable (Ljava/lang/String;Lio/sentry/ILogger;)Z + public fun isClassAvailable (Ljava/lang/String;Lio/sentry/SentryOptions;)Z + public fun loadClass (Ljava/lang/String;Lio/sentry/ILogger;)Ljava/lang/Class; } public final class io/sentry/android/core/NdkIntegration : io/sentry/Integration, java/io/Closeable { diff --git a/sentry-android-core/build.gradle.kts b/sentry-android-core/build.gradle.kts index c92c80743a..7937655aad 100644 --- a/sentry-android-core/build.gradle.kts +++ b/sentry-android-core/build.gradle.kts @@ -49,6 +49,7 @@ android { // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks. checkReleaseBuilds = false + disable += "LogNotTimber" } // needed because of Kotlin 1.4.x @@ -78,6 +79,8 @@ tasks.withType().configureEach { dependencies { api(projects.sentry) + compileOnly(projects.sentryAndroidFragment) + compileOnly(projects.sentryAndroidTimber) // lifecycle processor, session tracking implementation(Config.Libs.lifecycleProcess) @@ -102,4 +105,6 @@ dependencies { testImplementation(Config.TestLibs.mockitoInline) testImplementation(Config.TestLibs.awaitility) testImplementation(projects.sentryTestSupport) + testImplementation(projects.sentryAndroidFragment) + testImplementation(projects.sentryAndroidTimber) } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java index fda5b25e6a..d61a33e119 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.util.SparseIntArray; import androidx.core.app.FrameMetricsAggregator; +import io.sentry.ILogger; import io.sentry.protocol.MeasurementValue; import io.sentry.protocol.SentryId; import java.util.HashMap; @@ -25,28 +26,23 @@ public final class ActivityFramesTracker { private final @NotNull Map> activityMeasurements = new ConcurrentHashMap<>(); - public ActivityFramesTracker(final @NotNull LoadClass loadClass) { - androidXAvailable = checkAndroidXAvailability(loadClass); + public ActivityFramesTracker(final @NotNull LoadClass loadClass, final @Nullable ILogger logger) { + androidXAvailable = + loadClass.isClassAvailable("androidx.core.app.FrameMetricsAggregator", logger); if (androidXAvailable) { frameMetricsAggregator = new FrameMetricsAggregator(); } } + public ActivityFramesTracker(final @NotNull LoadClass loadClass) { + this(loadClass, null); + } + @TestOnly ActivityFramesTracker(final @Nullable FrameMetricsAggregator frameMetricsAggregator) { this.frameMetricsAggregator = frameMetricsAggregator; } - private static boolean checkAndroidXAvailability(final @NotNull LoadClass loadClass) { - try { - loadClass.loadClass("androidx.core.app.FrameMetricsAggregator"); - return true; - } catch (ClassNotFoundException ignored) { - // androidx.core isn't available. - return false; - } - } - private boolean isFrameMetricsAggregatorAvailable() { return androidXAvailable && frameMetricsAggregator != null; } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index d69c8890c2..8a032095a2 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -13,6 +13,8 @@ import io.sentry.SendFireAndForgetOutboxSender; import io.sentry.SentryLevel; import io.sentry.SentryOptions; +import io.sentry.android.fragment.FragmentLifecycleIntegration; +import io.sentry.android.timber.SentryTimberIntegration; import io.sentry.util.Objects; import java.io.BufferedInputStream; import java.io.File; @@ -43,7 +45,7 @@ static void init(final @NotNull SentryAndroidOptions options, final @NotNull Con Objects.requireNonNull(context, "The application context is required."); Objects.requireNonNull(options, "The options object is required."); - init(options, context, new AndroidLogger()); + init(options, context, new AndroidLogger(), false, false); } /** @@ -52,12 +54,16 @@ static void init(final @NotNull SentryAndroidOptions options, final @NotNull Con * @param options the SentryOptions * @param context the Application context * @param logger the ILogger interface + * @param isFragmentAvailable whether the Fragment integration is available on the classpath + * @param isTimberAvailable whether the Timber integration is available on the classpath */ static void init( final @NotNull SentryAndroidOptions options, @NotNull Context context, - final @NotNull ILogger logger) { - init(options, context, logger, new BuildInfoProvider()); + final @NotNull ILogger logger, + final boolean isFragmentAvailable, + final boolean isTimberAvailable) { + init(options, context, logger, new BuildInfoProvider(), isFragmentAvailable, isTimberAvailable); } /** @@ -67,13 +73,24 @@ static void init( * @param context the Application context * @param logger the ILogger interface * @param buildInfoProvider the IBuildInfoProvider interface + * @param isFragmentAvailable whether the Fragment integration is available on the classpath + * @param isTimberAvailable whether the Timber integration is available on the classpath */ static void init( final @NotNull SentryAndroidOptions options, @NotNull Context context, final @NotNull ILogger logger, - final @NotNull IBuildInfoProvider buildInfoProvider) { - init(options, context, logger, buildInfoProvider, new LoadClass()); + final @NotNull IBuildInfoProvider buildInfoProvider, + final boolean isFragmentAvailable, + final boolean isTimberAvailable) { + init( + options, + context, + logger, + buildInfoProvider, + new LoadClass(), + isFragmentAvailable, + isTimberAvailable); } /** @@ -84,13 +101,17 @@ static void init( * @param logger the ILogger interface * @param buildInfoProvider the IBuildInfoProvider interface * @param loadClass the LoadClass wrapper + * @param isFragmentAvailable whether the Fragment integration is available on the classpath + * @param isTimberAvailable whether the Timber integration is available on the classpath */ static void init( final @NotNull SentryAndroidOptions options, @NotNull Context context, final @NotNull ILogger logger, final @NotNull IBuildInfoProvider buildInfoProvider, - final @NotNull LoadClass loadClass) { + final @NotNull LoadClass loadClass, + final boolean isFragmentAvailable, + final boolean isTimberAvailable) { Objects.requireNonNull(context, "The context is required."); // it returns null if ContextImpl, so let's check for nullability @@ -107,9 +128,16 @@ static void init( ManifestMetadataReader.applyMetadata(context, options); initializeCacheDirs(context, options); - final ActivityFramesTracker activityFramesTracker = new ActivityFramesTracker(loadClass); + final ActivityFramesTracker activityFramesTracker = + new ActivityFramesTracker(loadClass, options.getLogger()); installDefaultIntegrations( - context, options, buildInfoProvider, loadClass, activityFramesTracker); + context, + options, + buildInfoProvider, + loadClass, + activityFramesTracker, + isFragmentAvailable, + isTimberAvailable); readDefaultOptionValues(options, context); @@ -124,7 +152,9 @@ private static void installDefaultIntegrations( final @NotNull SentryOptions options, final @NotNull IBuildInfoProvider buildInfoProvider, final @NotNull LoadClass loadClass, - final @NotNull ActivityFramesTracker activityFramesTracker) { + final @NotNull ActivityFramesTracker activityFramesTracker, + final boolean isFragmentAvailable, + final boolean isTimberAvailable) { options.addIntegration( new SendCachedEnvelopeFireAndForgetIntegration( @@ -132,7 +162,10 @@ private static void installDefaultIntegrations( // Integrations are registered in the same order. NDK before adding Watch outbox, // because sentry-native move files around and we don't want to watch that. - final Class sentryNdkClass = loadNdkIfAvailable(options, buildInfoProvider, loadClass); + final Class sentryNdkClass = + isNdkAvailable(buildInfoProvider) + ? loadClass.loadClass(SENTRY_NDK_CLASS_NAME, options.getLogger()) + : null; options.addIntegration(new NdkIntegration(sentryNdkClass)); // this integration uses android.os.FileObserver, we can't move to sentry @@ -155,12 +188,18 @@ private static void installDefaultIntegrations( new ActivityLifecycleIntegration( (Application) context, buildInfoProvider, activityFramesTracker)); options.addIntegration(new UserInteractionIntegration((Application) context, loadClass)); + if (isFragmentAvailable) { + options.addIntegration(new FragmentLifecycleIntegration((Application) context, true, true)); + } } else { options .getLogger() .log( SentryLevel.WARNING, - "ActivityLifecycle and UserInteraction Integrations need an Application class to be installed."); + "ActivityLifecycle, FragmentLifecycle and UserInteraction Integrations need an Application class to be installed."); + } + if (isTimberAvailable) { + options.addIntegration(new SentryTimberIntegration()); } options.addIntegration(new AppComponentsBreadcrumbsIntegration(context)); options.addIntegration(new SystemEventsBreadcrumbsIntegration(context)); @@ -257,24 +296,4 @@ private static void initializeCacheDirs( private static boolean isNdkAvailable(final @NotNull IBuildInfoProvider buildInfoProvider) { return buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN; } - - private static @Nullable Class loadNdkIfAvailable( - final @NotNull SentryOptions options, - final @NotNull IBuildInfoProvider buildInfoProvider, - final @NotNull LoadClass loadClass) { - if (isNdkAvailable(buildInfoProvider)) { - try { - return loadClass.loadClass(SENTRY_NDK_CLASS_NAME); - } catch (ClassNotFoundException e) { - options.getLogger().log(SentryLevel.ERROR, "Failed to load SentryNdk.", e); - } catch (UnsatisfiedLinkError e) { - options - .getLogger() - .log(SentryLevel.ERROR, "Failed to load (UnsatisfiedLinkError) SentryNdk.", e); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Failed to initialize SentryNdk.", e); - } - } - return null; - } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/LoadClass.java b/sentry-android-core/src/main/java/io/sentry/android/core/LoadClass.java index 9e1db9b0e1..6401945cab 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/LoadClass.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/LoadClass.java @@ -1,6 +1,10 @@ package io.sentry.android.core; +import io.sentry.ILogger; +import io.sentry.SentryLevel; +import io.sentry.SentryOptions; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** An Adapter for making Class.forName testable */ public final class LoadClass { @@ -9,10 +13,34 @@ public final class LoadClass { * Try to load a class via reflection * * @param clazz the full class name - * @return a Class - * @throws ClassNotFoundException if class is not found + * @param logger an instance of ILogger + * @return a Class if it's available, or null */ - public @NotNull Class loadClass(@NotNull String clazz) throws ClassNotFoundException { - return Class.forName(clazz); + public @Nullable Class loadClass(final @NotNull String clazz, final @Nullable ILogger logger) { + try { + return Class.forName(clazz); + } catch (ClassNotFoundException e) { + if (logger != null) { + logger.log(SentryLevel.DEBUG, "Class not available:" + clazz, e); + } + } catch (UnsatisfiedLinkError e) { + if (logger != null) { + logger.log(SentryLevel.ERROR, "Failed to load (UnsatisfiedLinkError) " + clazz, e); + } + } catch (Throwable e) { + if (logger != null) { + logger.log(SentryLevel.ERROR, "Failed to initialize " + clazz, e); + } + } + return null; + } + + public boolean isClassAvailable(final @NotNull String clazz, final @Nullable ILogger logger) { + return loadClass(clazz, logger) != null; + } + + public boolean isClassAvailable( + final @NotNull String clazz, final @Nullable SentryOptions options) { + return isClassAvailable(clazz, options != null ? options.getLogger() : null); } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java index a90229e370..dc65b632db 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java @@ -4,11 +4,17 @@ import android.os.SystemClock; import io.sentry.DateUtils; import io.sentry.ILogger; +import io.sentry.Integration; import io.sentry.OptionsContainer; import io.sentry.Sentry; import io.sentry.SentryLevel; +import io.sentry.SentryOptions; +import io.sentry.android.fragment.FragmentLifecycleIntegration; +import io.sentry.android.timber.SentryTimberIntegration; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import org.jetbrains.annotations.NotNull; /** Sentry initialization class */ @@ -19,6 +25,12 @@ public final class SentryAndroid { // SystemClock.uptimeMillis() isn't affected by phone provider or clock changes. private static final long appStart = SystemClock.uptimeMillis(); + static final String SENTRY_FRAGMENT_INTEGRATION_CLASS_NAME = + "io.sentry.android.fragment.FragmentLifecycleIntegration"; + + static final String SENTRY_TIMBER_INTEGRATION_CLASS_NAME = + "io.sentry.android.timber.SentryTimberIntegration"; + private SentryAndroid() {} /** @@ -71,8 +83,16 @@ public static synchronized void init( Sentry.init( OptionsContainer.create(SentryAndroidOptions.class), options -> { - AndroidOptionsInitializer.init(options, context, logger); + final LoadClass classLoader = new LoadClass(); + final boolean isFragmentAvailable = + classLoader.isClassAvailable(SENTRY_FRAGMENT_INTEGRATION_CLASS_NAME, options); + final boolean isTimberAvailable = + classLoader.isClassAvailable(SENTRY_TIMBER_INTEGRATION_CLASS_NAME, options); + + AndroidOptionsInitializer.init( + options, context, logger, isFragmentAvailable, isTimberAvailable); configuration.configure(options); + deduplicateIntegrations(options, isFragmentAvailable, isTimberAvailable); }, true); } catch (IllegalAccessException e) { @@ -95,4 +115,46 @@ public static synchronized void init( throw new RuntimeException("Failed to initialize Sentry's SDK", e); } } + + /** + * Deduplicate potentially duplicated Fragment and Timber integrations, which can be added + * automatically by our SDK as well as by the user. The user's ones win over ours. + * + * @param options SentryOptions to retrieve integrations from + */ + private static void deduplicateIntegrations( + final @NotNull SentryOptions options, + final boolean isFragmentAvailable, + final boolean isTimberAvailable) { + + final List timberIntegrations = new ArrayList<>(); + final List fragmentIntegrations = new ArrayList<>(); + + for (final Integration integration : options.getIntegrations()) { + if (isFragmentAvailable) { + if (integration instanceof FragmentLifecycleIntegration) { + fragmentIntegrations.add(integration); + } + } + if (isTimberAvailable) { + if (integration instanceof SentryTimberIntegration) { + timberIntegrations.add(integration); + } + } + } + + if (fragmentIntegrations.size() > 1) { + for (int i = 0; i < fragmentIntegrations.size() - 1; i++) { + final Integration integration = fragmentIntegrations.get(i); + options.getIntegrations().remove(integration); + } + } + + if (timberIntegrations.size() > 1) { + for (int i = 0; i < timberIntegrations.size() - 1; i++) { + final Integration integration = timberIntegrations.get(i); + options.getIntegrations().remove(integration); + } + } + } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java index 7a851b74bd..4f917f86eb 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java @@ -33,26 +33,10 @@ public UserInteractionIntegration( final @NotNull Application application, final @NotNull LoadClass classLoader) { this.application = Objects.requireNonNull(application, "Application is required"); - isAndroidXAvailable = checkAndroidXAvailability(classLoader); - isAndroidXScrollViewAvailable = checkAndroidXScrollViewAvailability(classLoader); - } - - private static boolean checkAndroidXAvailability(final @NotNull LoadClass loadClass) { - try { - loadClass.loadClass("androidx.core.view.GestureDetectorCompat"); - return true; - } catch (ClassNotFoundException ignored) { - return false; - } - } - - private static boolean checkAndroidXScrollViewAvailability(final @NotNull LoadClass loadClass) { - try { - loadClass.loadClass("androidx.core.view.ScrollingView"); - return true; - } catch (ClassNotFoundException ignored) { - return false; - } + isAndroidXAvailable = + classLoader.isClassAvailable("androidx.core.view.GestureDetectorCompat", options); + isAndroidXScrollViewAvailable = + classLoader.isClassAvailable("androidx.core.view.ScrollingView", options); } private void startTracking(final @Nullable Window window, final @NotNull Context context) { diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt index a7cc4e1d20..1a8d2b5c2c 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt @@ -7,6 +7,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever +import io.sentry.ILogger import io.sentry.protocol.SentryId import org.junit.runner.RunWith import kotlin.test.Test @@ -116,7 +117,7 @@ class ActivityFramesTrackerTest { @Test fun `addActivity does not throw if no AndroidX`() { - whenever(fixture.loadClass.loadClass(any())).thenThrow(ClassNotFoundException()) + whenever(fixture.loadClass.isClassAvailable(any(), any())).thenReturn(false) val sut = ActivityFramesTracker(fixture.loadClass) sut.addActivity(fixture.activity) @@ -124,7 +125,7 @@ class ActivityFramesTrackerTest { @Test fun `setMetrics does not throw if no AndroidX`() { - whenever(fixture.loadClass.loadClass(any())).thenThrow(ClassNotFoundException()) + whenever(fixture.loadClass.isClassAvailable(any(), any())).thenReturn(false) val sut = ActivityFramesTracker(fixture.loadClass) sut.setMetrics(fixture.activity, fixture.sentryId) @@ -140,7 +141,7 @@ class ActivityFramesTrackerTest { @Test fun `stop does not throw if no AndroidX`() { - whenever(fixture.loadClass.loadClass(any())).thenThrow(ClassNotFoundException()) + whenever(fixture.loadClass.isClassAvailable(any(), any())).thenReturn(false) val sut = ActivityFramesTracker(fixture.loadClass) sut.stop() @@ -148,7 +149,7 @@ class ActivityFramesTrackerTest { @Test fun `takeMetrics returns null if no AndroidX`() { - whenever(fixture.loadClass.loadClass(any())).thenThrow(ClassNotFoundException()) + whenever(fixture.loadClass.isClassAvailable(any(), any())).thenReturn(false) val sut = ActivityFramesTracker(fixture.loadClass) assertNull(sut.takeMetrics(fixture.sentryId)) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt index f0a0de175e..ff23d95fd4 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt @@ -1,25 +1,20 @@ package io.sentry.android.core -import android.app.Application import android.content.Context import android.os.Bundle import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.nhaarman.mockitokotlin2.any -import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.never -import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import io.sentry.ILogger import io.sentry.MainEventProcessor import io.sentry.SendCachedEnvelopeFireAndForgetIntegration -import io.sentry.SentryLevel import io.sentry.SentryOptions -import io.sentry.android.core.NdkIntegration.SENTRY_NDK_CLASS_NAME +import io.sentry.android.fragment.FragmentLifecycleIntegration +import io.sentry.android.timber.SentryTimberIntegration import org.junit.runner.RunWith import java.io.File -import java.lang.RuntimeException import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -30,24 +25,85 @@ import kotlin.test.assertTrue @RunWith(AndroidJUnit4::class) class AndroidOptionsInitializerTest { - private lateinit var context: Context - private lateinit var file: File + + class Fixture(val context: Context, private val file: File) { + val sentryOptions = SentryAndroidOptions() + lateinit var mockContext: Context + val logger = mock() + + fun initSut( + metadata: Bundle? = null, + hasAppContext: Boolean = true, + useRealContext: Boolean = false, + configureOptions: SentryAndroidOptions.() -> Unit = {}, + configureContext: Context.() -> Unit = {} + ) { + mockContext = if (metadata != null) { + ContextUtilsTest.mockMetaData( + mockContext = ContextUtilsTest.createMockContext(hasAppContext), + metaData = metadata + ) + } else { + ContextUtilsTest.createMockContext(hasAppContext) + } + whenever(mockContext.cacheDir).thenReturn(file) + if (mockContext.applicationContext != null) { + whenever(mockContext.applicationContext.cacheDir).thenReturn(file) + } + mockContext.configureContext() + sentryOptions.configureOptions() + AndroidOptionsInitializer.init( + sentryOptions, if (useRealContext) context else mockContext + ) + } + + fun initSutWithClassLoader( + minApi: Int = 16, + classToLoad: Class<*>? = null, + isFragmentAvailable: Boolean = false, + isTimberAvailable: Boolean = false + ) { + mockContext = ContextUtilsTest.mockMetaData( + mockContext = ContextUtilsTest.createMockContext(hasAppContext = true), + metaData = Bundle().apply { + putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") + } + ) + sentryOptions.setDebug(true) + AndroidOptionsInitializer.init( + sentryOptions, mockContext, logger, createBuildInfo(minApi), + createClassMock(classToLoad), isFragmentAvailable, isTimberAvailable + ) + } + + private fun createBuildInfo(minApi: Int = 16): IBuildInfoProvider { + val buildInfo = mock() + whenever(buildInfo.sdkInfoVersion).thenReturn(minApi) + return buildInfo + } + + private fun createClassMock(clazz: Class<*>?): LoadClass { + val loadClassMock = mock() + whenever(loadClassMock.loadClass(any(), any())).thenReturn(clazz) + whenever(loadClassMock.isClassAvailable(any(), any())).thenReturn(clazz != null) + return loadClassMock + } + } + + private lateinit var fixture: Fixture @BeforeTest fun `set up`() { - context = ApplicationProvider.getApplicationContext() - file = context.cacheDir + val appContext = ApplicationProvider.getApplicationContext() + fixture = Fixture(appContext, appContext.cacheDir) } @Test fun `logger set to AndroidLogger`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = createMockContext() - - AndroidOptionsInitializer.init(sentryOptions, mockContext) + fixture.initSut() val logger = SentryOptions::class.java.declaredFields.first { it.name == "logger" } logger.isAccessible = true - val loggerField = logger.get(sentryOptions) + val loggerField = logger.get(fixture.sentryOptions) val innerLogger = loggerField.javaClass.declaredFields.first { it.name == "logger" } innerLogger.isAccessible = true assertTrue(innerLogger.get(loggerField) is AndroidLogger) @@ -55,320 +111,229 @@ class AndroidOptionsInitializerTest { @Test fun `AndroidEventProcessor added to processors list`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = createMockContext() - - AndroidOptionsInitializer.init(sentryOptions, mockContext) - val actual = sentryOptions.eventProcessors.any { it is DefaultAndroidEventProcessor } + fixture.initSut() + val actual = + fixture.sentryOptions.eventProcessors.any { it is DefaultAndroidEventProcessor } assertNotNull(actual) } @Test fun `PerformanceAndroidEventProcessor added to processors list`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = createMockContext() - - AndroidOptionsInitializer.init(sentryOptions, mockContext) - val actual = sentryOptions.eventProcessors.any { it is PerformanceAndroidEventProcessor } + fixture.initSut() + val actual = + fixture.sentryOptions.eventProcessors.any { it is PerformanceAndroidEventProcessor } assertNotNull(actual) } @Test fun `MainEventProcessor added to processors list and its the 1st`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = createMockContext() - - AndroidOptionsInitializer.init(sentryOptions, mockContext) - val actual = sentryOptions.eventProcessors.firstOrNull { it is MainEventProcessor } + fixture.initSut() + val actual = fixture.sentryOptions.eventProcessors.firstOrNull { it is MainEventProcessor } assertNotNull(actual) } @Test fun `envelopesDir should be set at initialization`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = createMockContext() - - AndroidOptionsInitializer.init(sentryOptions, mockContext) + fixture.initSut() - assertTrue(sentryOptions.cacheDirPath?.endsWith("${File.separator}cache${File.separator}sentry")!!) + assertTrue( + fixture.sentryOptions.cacheDirPath?.endsWith( + "${File.separator}cache${File.separator}sentry" + )!! + ) } @Test fun `outboxDir should be set at initialization`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = createMockContext() - - AndroidOptionsInitializer.init(sentryOptions, mockContext) + fixture.initSut() - assertTrue(sentryOptions.outboxPath?.endsWith("${File.separator}cache${File.separator}sentry${File.separator}outbox")!!) + assertTrue( + fixture.sentryOptions.outboxPath?.endsWith( + "${File.separator}cache${File.separator}sentry${File.separator}outbox" + )!! + ) } @Test fun `init should set context package name as appInclude`() { - val sentryOptions = SentryAndroidOptions() - - AndroidOptionsInitializer.init(sentryOptions, context) + fixture.initSut(useRealContext = true) // cant mock PackageInfo, its buggy - assertTrue(sentryOptions.inAppIncludes.contains("io.sentry.android.core.test")) + assertTrue(fixture.sentryOptions.inAppIncludes.contains("io.sentry.android.core.test")) } @Test fun `init should set release if empty`() { - val sentryOptions = SentryAndroidOptions() - - AndroidOptionsInitializer.init(sentryOptions, context) + fixture.initSut(useRealContext = true) // cant mock PackageInfo, its buggy - assertTrue(sentryOptions.release!!.startsWith("io.sentry.android.core.test@")) + assertTrue(fixture.sentryOptions.release!!.startsWith("io.sentry.android.core.test@")) } @Test fun `init should not replace options if set on manifest`() { - val sentryOptions = SentryAndroidOptions().apply { - release = "release" - } - - AndroidOptionsInitializer.init(sentryOptions, context) + fixture.initSut(configureOptions = { release = "release" }) - assertEquals("release", sentryOptions.release) + assertEquals("release", fixture.sentryOptions.release) } @Test fun `init should not set context package name if it starts with android package`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = ContextUtilsTest.createMockContext() - whenever(mockContext.packageName).thenReturn("android.context") + fixture.initSut(configureContext = { + whenever(packageName).thenReturn("android.context") + }) - AndroidOptionsInitializer.init(sentryOptions, mockContext) - - assertFalse(sentryOptions.inAppIncludes.contains("android.context")) + assertFalse(fixture.sentryOptions.inAppIncludes.contains("android.context")) } @Test fun `init should set distinct id on start`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = ContextUtilsTest.createMockContext() + fixture.initSut() - AndroidOptionsInitializer.init(sentryOptions, mockContext) - - assertNotNull(sentryOptions.distinctId) { + assertNotNull(fixture.sentryOptions.distinctId) { assertTrue(it.isNotEmpty()) } - val installation = File(context.filesDir, Installation.INSTALLATION) + val installation = File(fixture.context.filesDir, Installation.INSTALLATION) installation.deleteOnExit() } @Test fun `init should set proguard uuid id on start`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = ContextUtilsTest.mockMetaData(metaData = createBundleWithProguardUuid()) - - AndroidOptionsInitializer.init(sentryOptions, mockContext) + fixture.initSut( + Bundle().apply { + putString(ManifestMetadataReader.PROGUARD_UUID, "proguard-uuid") + }, + hasAppContext = false + ) - assertEquals("proguard-uuid", sentryOptions.proguardUuid) + assertEquals("proguard-uuid", fixture.sentryOptions.proguardUuid) } @Test fun `init should set Android transport gate`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = createMockContext() - - AndroidOptionsInitializer.init(sentryOptions, mockContext) + fixture.initSut() - assertNotNull(sentryOptions.transportGate) - assertTrue(sentryOptions.transportGate is AndroidTransportGate) + assertNotNull(fixture.sentryOptions.transportGate) + assertTrue(fixture.sentryOptions.transportGate is AndroidTransportGate) } @Test fun `NdkIntegration will load SentryNdk class and add to the integration list`() { - val mockContext = ContextUtilsTest.mockMetaData(metaData = createBundleWithDsn()) - val logger = mock() - val sentryOptions = SentryAndroidOptions().apply { - setDebug(true) - } - - AndroidOptionsInitializer.init(sentryOptions, mockContext, logger, createBuildInfo(), createClassMock()) + fixture.initSutWithClassLoader(classToLoad = SentryNdk::class.java) - val actual = sentryOptions.integrations.firstOrNull { it is NdkIntegration } + val actual = fixture.sentryOptions.integrations.firstOrNull { it is NdkIntegration } assertNotNull((actual as NdkIntegration).sentryNdkClass) - - verify(logger, never()).log(eq(SentryLevel.ERROR), any(), any()) - verify(logger, never()).log(eq(SentryLevel.FATAL), any(), any()) } @Test fun `NdkIntegration won't be enabled because API is lower than 16`() { - val mockContext = ContextUtilsTest.mockMetaData(metaData = createBundleWithDsn()) - val logger = mock() - val sentryOptions = SentryAndroidOptions().apply { - setDebug(true) - } + fixture.initSutWithClassLoader(minApi = 14, classToLoad = SentryNdk::class.java) - AndroidOptionsInitializer.init(sentryOptions, mockContext, logger, createBuildInfo(14), createClassMock()) - - val actual = sentryOptions.integrations.firstOrNull { it is NdkIntegration } + val actual = fixture.sentryOptions.integrations.firstOrNull { it is NdkIntegration } assertNull((actual as NdkIntegration).sentryNdkClass) - - verify(logger, never()).log(eq(SentryLevel.ERROR), any(), any()) - verify(logger, never()).log(eq(SentryLevel.FATAL), any(), any()) } @Test - fun `NdkIntegration won't be enabled, it throws linkage error`() { - val mockContext = ContextUtilsTest.mockMetaData(metaData = createBundleWithDsn()) - val logger = mock() - val sentryOptions = SentryAndroidOptions().apply { - setDebug(true) - } - - AndroidOptionsInitializer.init(sentryOptions, mockContext, logger, createBuildInfo(), createClassMockThrows(UnsatisfiedLinkError())) + fun `NdkIntegration won't be enabled, if class not found`() { + fixture.initSutWithClassLoader(classToLoad = null) - val actual = sentryOptions.integrations.firstOrNull { it is NdkIntegration } + val actual = fixture.sentryOptions.integrations.firstOrNull { it is NdkIntegration } assertNull((actual as NdkIntegration).sentryNdkClass) - - verify(logger).log(eq(SentryLevel.ERROR), any(), any()) - } - - @Test - fun `NdkIntegration won't be enabled, it throws class not found`() { - val mockContext = ContextUtilsTest.mockMetaData(metaData = createBundleWithDsn()) - val logger = mock() - val sentryOptions = SentryAndroidOptions().apply { - setDebug(true) - } - - AndroidOptionsInitializer.init(sentryOptions, mockContext, logger, createBuildInfo(), createClassMockThrows(ClassNotFoundException())) - - val actual = sentryOptions.integrations.firstOrNull { it is NdkIntegration } - assertNull((actual as NdkIntegration).sentryNdkClass) - - verify(logger).log(eq(SentryLevel.ERROR), any(), any()) - } - - @Test - fun `NdkIntegration won't be enabled, it throws unknown error`() { - val mockContext = ContextUtilsTest.mockMetaData(metaData = createBundleWithDsn()) - val logger = mock() - val sentryOptions = SentryAndroidOptions().apply { - setDebug(true) - } - - AndroidOptionsInitializer.init(sentryOptions, mockContext, logger, createBuildInfo(), createClassMockThrows(RuntimeException())) - - val actual = sentryOptions.integrations.firstOrNull { it is NdkIntegration } - assertNull((actual as NdkIntegration).sentryNdkClass) - - verify(logger).log(eq(SentryLevel.ERROR), any(), any()) } @Test fun `AnrIntegration added to integration list`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = createMockContext() + fixture.initSut() - AndroidOptionsInitializer.init(sentryOptions, mockContext) - val actual = sentryOptions.integrations.firstOrNull { it is AnrIntegration } + val actual = fixture.sentryOptions.integrations.firstOrNull { it is AnrIntegration } assertNotNull(actual) } @Test fun `EnvelopeFileObserverIntegration added to integration list`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = createMockContext() + fixture.initSut() - AndroidOptionsInitializer.init(sentryOptions, mockContext) - val actual = sentryOptions.integrations.firstOrNull { it is EnvelopeFileObserverIntegration } + val actual = + fixture.sentryOptions.integrations.firstOrNull { it is EnvelopeFileObserverIntegration } assertNotNull(actual) } @Test fun `SendCachedEnvelopeFireAndForgetIntegration added to integration list`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = createMockContext() + fixture.initSut() - AndroidOptionsInitializer.init(sentryOptions, mockContext) - val actual = sentryOptions.integrations.firstOrNull { it is SendCachedEnvelopeFireAndForgetIntegration } + val actual = + fixture.sentryOptions.integrations + .firstOrNull { it is SendCachedEnvelopeFireAndForgetIntegration } assertNotNull(actual) } @Test fun `When given Context returns a non null ApplicationContext, uses it`() { - val sentryOptions = SentryAndroidOptions() - val mockApp = mock() - val mockContext = mock() - whenever(mockContext.applicationContext).thenReturn(mockApp) + fixture.initSut() - AndroidOptionsInitializer.init(sentryOptions, mockContext) - assertNotNull(mockContext) + assertNotNull(fixture.mockContext) } @Test fun `When given Context returns a null ApplicationContext is null, keep given Context`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = mock() - whenever(mockContext.applicationContext).thenReturn(null) + fixture.initSut(hasAppContext = false) - AndroidOptionsInitializer.init(sentryOptions, mockContext) - assertNotNull(mockContext) + assertNotNull(fixture.mockContext) } @Test fun `When given Context is not an Application class, do not add ActivityLifecycleIntegration`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = mock() - whenever(mockContext.applicationContext).thenReturn(null) + fixture.initSut(hasAppContext = false) - AndroidOptionsInitializer.init(sentryOptions, mockContext) - val actual = sentryOptions.integrations.firstOrNull { it is ActivityLifecycleIntegration } + val actual = fixture.sentryOptions.integrations + .firstOrNull { it is ActivityLifecycleIntegration } assertNull(actual) } @Test fun `When given Context is not an Application class, do not add UserInteractionIntegration`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = mock() - whenever(mockContext.applicationContext).thenReturn(null) + fixture.initSut(hasAppContext = false) - AndroidOptionsInitializer.init(sentryOptions, mockContext) - val actual = sentryOptions.integrations.firstOrNull { it is UserInteractionIntegration } + val actual = fixture.sentryOptions.integrations + .firstOrNull { it is UserInteractionIntegration } assertNull(actual) } - private fun createMockContext(): Context { - val mockContext = ContextUtilsTest.createMockContext() - whenever(mockContext.cacheDir).thenReturn(file) - return mockContext - } + @Test + fun `FragmentLifecycleIntegration added to the integration list if available on classpath`() { + fixture.initSutWithClassLoader(isFragmentAvailable = true) - private fun createBundleWithDsn(): Bundle { - return Bundle().apply { - putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") - } + val actual = + fixture.sentryOptions.integrations.firstOrNull { it is FragmentLifecycleIntegration } + assertNotNull(actual) } - private fun createBundleWithProguardUuid(): Bundle { - return Bundle().apply { - putString(ManifestMetadataReader.PROGUARD_UUID, "proguard-uuid") - } - } + @Test + fun `FragmentLifecycleIntegration won't be enabled, it throws class not found`() { + fixture.initSutWithClassLoader(isFragmentAvailable = false) - private fun createBuildInfo(minApi: Int = 16): IBuildInfoProvider { - val buildInfo = mock() - whenever(buildInfo.sdkInfoVersion).thenReturn(minApi) - return buildInfo + val actual = + fixture.sentryOptions.integrations.firstOrNull { it is FragmentLifecycleIntegration } + assertNull(actual) } - private fun createClassMock(clazz: Class<*> = SentryNdk::class.java): LoadClass { - val loadClassMock = mock() - whenever(loadClassMock.loadClass(any())).thenReturn(clazz) - return loadClassMock + @Test + fun `SentryTimberIntegration added to the integration list if available on classpath`() { + fixture.initSutWithClassLoader(isTimberAvailable = true) + + val actual = + fixture.sentryOptions.integrations.firstOrNull { it is SentryTimberIntegration } + assertNotNull(actual) } - private fun createClassMockThrows(ex: Throwable): LoadClass { - val loadClassMock = mock() - whenever(loadClassMock.loadClass(eq(SENTRY_NDK_CLASS_NAME))).thenThrow(ex) - return loadClassMock + @Test + fun `SentryTimberIntegration won't be enabled, it throws class not found`() { + fixture.initSutWithClassLoader(isTimberAvailable = false) + + val actual = + fixture.sentryOptions.integrations.firstOrNull { it is SentryTimberIntegration } + assertNull(actual) } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsTest.kt index e536ed255f..6c1d3fc824 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsTest.kt @@ -9,11 +9,10 @@ import android.os.Bundle import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import java.io.File import java.io.FileNotFoundException object ContextUtilsTest { - fun mockMetaData(mockContext: Context = createMockContext(), metaData: Bundle): Context { + fun mockMetaData(mockContext: Context = createMockContext(hasAppContext = false), metaData: Bundle): Context { val mockPackageManager = mock() val mockApplicationInfo = mock() val assets = mock() @@ -29,10 +28,9 @@ object ContextUtilsTest { return mockContext } - fun createMockContext(): Context { - val mockApp = mock() - whenever(mockApp.applicationContext).thenReturn(mockApp) - whenever(mockApp.cacheDir).thenReturn(File("")) + fun createMockContext(hasAppContext: Boolean = true): Context { + val mockApp = mock() + whenever(mockApp.applicationContext).thenReturn(if (hasAppContext) mock() else null) return mockApp } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt index 1f280b86df..3da78a530c 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt @@ -1,6 +1,7 @@ package io.sentry.android.core import android.os.Bundle +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.eq @@ -10,6 +11,10 @@ import com.nhaarman.mockitokotlin2.verify import io.sentry.ILogger import io.sentry.Sentry import io.sentry.SentryLevel +import io.sentry.SentryLevel.DEBUG +import io.sentry.SentryLevel.FATAL +import io.sentry.android.fragment.FragmentLifecycleIntegration +import io.sentry.android.timber.SentryTimberIntegration import org.junit.runner.RunWith import kotlin.test.BeforeTest import kotlin.test.Test @@ -21,6 +26,28 @@ import kotlin.test.assertTrue @RunWith(AndroidJUnit4::class) class SentryAndroidTest { + class Fixture { + + fun initSut( + autoInit: Boolean = false, + logger: ILogger? = null, + options: Sentry.OptionsConfiguration? = null + ) { + val metadata = Bundle().apply { + putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") + putBoolean(ManifestMetadataReader.AUTO_INIT, autoInit) + } + val mockContext = ContextUtilsTest.mockMetaData(metaData = metadata) + when { + logger != null -> SentryAndroid.init(mockContext, logger) + options != null -> SentryAndroid.init(mockContext, options) + else -> SentryAndroid.init(mockContext) + } + } + } + + private val fixture = Fixture() + @BeforeTest fun `set up`() { Sentry.close() @@ -31,13 +58,7 @@ class SentryAndroidTest { fun `when auto-init is disabled and user calls init manually, SDK initializes`() { assertFalse(Sentry.isEnabled()) - val metaData = Bundle() - val mockContext = ContextUtilsTest.mockMetaData(metaData = metaData) - - metaData.putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") - metaData.putBoolean(ManifestMetadataReader.AUTO_INIT, false) - - SentryAndroid.init(mockContext) + fixture.initSut() assertTrue(Sentry.isEnabled()) } @@ -46,15 +67,7 @@ class SentryAndroidTest { fun `when auto-init is disabled and user calls init manually with a logger, SDK initializes`() { assertFalse(Sentry.isEnabled()) - val metaData = Bundle() - val mockContext = ContextUtilsTest.mockMetaData(metaData = metaData) - - metaData.putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") - metaData.putBoolean(ManifestMetadataReader.AUTO_INIT, false) - - val logger = mock() - - SentryAndroid.init(mockContext, logger) + fixture.initSut(logger = mock()) assertTrue(Sentry.isEnabled()) } @@ -63,41 +76,28 @@ class SentryAndroidTest { fun `when auto-init is disabled and user calls init manually with configuration handler, options should be set`() { assertFalse(Sentry.isEnabled()) - val metaData = Bundle() - val mockContext = ContextUtilsTest.mockMetaData(metaData = metaData) - - metaData.putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") - metaData.putBoolean(ManifestMetadataReader.AUTO_INIT, false) - var refOptions: SentryAndroidOptions? = null - SentryAndroid.init(mockContext) { options -> - options.anrTimeoutIntervalMillis = 3000 - refOptions = options + fixture.initSut { + it.anrTimeoutIntervalMillis = 3000 + refOptions = it } assertEquals(3000, refOptions!!.anrTimeoutIntervalMillis) - assertTrue(Sentry.isEnabled()) } @Test fun `init won't throw exception`() { - val metaData = Bundle() - val mockContext = ContextUtilsTest.mockMetaData(metaData = metaData) - metaData.putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") - val logger = mock() - SentryAndroid.init(mockContext, logger) + + fixture.initSut(autoInit = true, logger = logger) + verify(logger, never()).log(eq(SentryLevel.FATAL), any(), any()) } @Test fun `set app start if provider is disabled`() { - val metaData = Bundle() - val mockContext = ContextUtilsTest.mockMetaData(metaData = metaData) - metaData.putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") - - SentryAndroid.init(mockContext, mock()) + fixture.initSut(autoInit = true) // done by ActivityLifecycleIntegration so forcing it here AppStartState.getInstance().setAppStartEnd() @@ -105,4 +105,30 @@ class SentryAndroidTest { assertNotNull(AppStartState.getInstance().appStartInterval) } + + @Test + fun `deduplicates fragment and timber integrations`() { + var refOptions: SentryAndroidOptions? = null + + fixture.initSut(autoInit = true) { + it.addIntegration( + FragmentLifecycleIntegration(ApplicationProvider.getApplicationContext()) + ) + + it.addIntegration( + SentryTimberIntegration(minEventLevel = FATAL, minBreadcrumbLevel = DEBUG) + ) + refOptions = it + } + + assertEquals(refOptions!!.integrations.filterIsInstance().size, 1) + val timberIntegration = + refOptions!!.integrations.find { it is SentryTimberIntegration } as SentryTimberIntegration + assertEquals(timberIntegration.minEventLevel, FATAL) + assertEquals(timberIntegration.minBreadcrumbLevel, DEBUG) + + // fragment integration is not auto-installed in the test, since the context is not Application + // but we just verify here that the single integration is preserved + assertEquals(refOptions!!.integrations.filterIsInstance().size, 1) + } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryInitProviderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryInitProviderTest.kt index 59a665b97b..a54f39e568 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryInitProviderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryInitProviderTest.kt @@ -4,8 +4,6 @@ import android.content.Context import android.content.pm.ProviderInfo import android.os.Bundle import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.nhaarman.mockitokotlin2.mock -import io.sentry.ILogger import io.sentry.Sentry import io.sentry.test.callMethod import org.junit.runner.RunWith @@ -129,13 +127,12 @@ class SentryInitProviderTest { @Test fun `when applicationId is defined, ndk in meta-data is set to false, NDK doesnt initialize`() { val sentryOptions = SentryAndroidOptions() - val mockLogger = mock() val metaData = Bundle() val mockContext = ContextUtilsTest.mockMetaData(metaData = metaData) metaData.putBoolean(ManifestMetadataReader.NDK_ENABLE, false) - AndroidOptionsInitializer.init(sentryOptions, mockContext, mockLogger) + AndroidOptionsInitializer.init(sentryOptions, mockContext) assertFalse(sentryOptions.isEnableNdk) } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/UserInteractionIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/UserInteractionIntegrationTest.kt index 1f4e993695..83c6c0b83b 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/UserInteractionIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/UserInteractionIntegrationTest.kt @@ -8,6 +8,7 @@ import android.util.DisplayMetrics import android.view.Window import androidx.test.ext.junit.runners.AndroidJUnit4 import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.anyOrNull import com.nhaarman.mockitokotlin2.argumentCaptor import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never @@ -33,7 +34,11 @@ class UserInteractionIntegrationTest { val window = mock() val loadClass = mock() - fun getSut(callback: Window.Callback? = null): UserInteractionIntegration { + fun getSut( + callback: Window.Callback? = null, + isAndroidXAvailable: Boolean = true + ): UserInteractionIntegration { + whenever(loadClass.isClassAvailable(any(), anyOrNull())).thenReturn(isAndroidXAvailable) whenever(hub.options).thenReturn(options) whenever(window.callback).thenReturn(callback) whenever(activity.window).thenReturn(window) @@ -87,8 +92,7 @@ class UserInteractionIntegrationTest { @Test fun `when androidx is unavailable doesn't register a callback`() { - whenever(fixture.loadClass.loadClass(any())).thenThrow(ClassNotFoundException()) - val sut = fixture.getSut() + val sut = fixture.getSut(isAndroidXAvailable = false) sut.register(fixture.hub, fixture.options) diff --git a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml index c6f42d48dc..74276baa9d 100644 --- a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml +++ b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml @@ -64,7 +64,7 @@ - + diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java index 301435206b..a4a1c5397a 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java @@ -2,9 +2,6 @@ import android.app.Application; import android.os.StrictMode; -import io.sentry.android.core.SentryAndroid; -import io.sentry.android.fragment.FragmentLifecycleIntegration; -import io.sentry.android.timber.SentryTimberIntegration; /** Apps. main Application. */ public class MyApplication extends Application { @@ -16,12 +13,17 @@ public void onCreate() { // Example how to initialize the SDK manually which allows access to SentryOptions callbacks. // Make sure you disable the auto init via manifest meta-data: io.sentry.auto-init=false - SentryAndroid.init( - this, - options -> { - options.addIntegration(new FragmentLifecycleIntegration(MyApplication.this, true, true)); - options.addIntegration(new SentryTimberIntegration()); - }); + // SentryAndroid.init( + // this, + // options -> { + // /* + // use options, for example, to add a beforeSend callback: + // + // options.setBeforeSend((event, hint) -> { + // process event + // }); + // */ + // }); } private void strictMode() {