Skip to content

Commit

Permalink
added LazyEvaluator to evaluate a function lazily
Browse files Browse the repository at this point in the history
AndroidOptionsInitializer.installDefaultIntegrations now evaluate cache dir lazily
  • Loading branch information
stefanosiano committed Jul 11, 2023
1 parent 3f5a679 commit 32134b8
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import io.sentry.internal.gestures.GestureTargetLocator;
import io.sentry.internal.viewhierarchy.ViewHierarchyExporter;
import io.sentry.transport.NoOpEnvelopeCache;
import io.sentry.util.LazyEvaluator;
import io.sentry.util.Objects;
import java.io.File;
import java.util.ArrayList;
Expand Down Expand Up @@ -195,12 +196,13 @@ static void installDefaultIntegrations(

// read the startup crash marker here to avoid doing double-IO for the SendCachedEnvelope
// integrations below
final boolean hasStartupCrashMarker = AndroidEnvelopeCache.hasStartupCrashMarker(options);
LazyEvaluator<Boolean> startupCrashMarkerEvaluator =
new LazyEvaluator<>(() -> AndroidEnvelopeCache.hasStartupCrashMarker(options));

options.addIntegration(
new SendCachedEnvelopeIntegration(
new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath()),
hasStartupCrashMarker));
startupCrashMarkerEvaluator));

// 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.
Expand All @@ -220,7 +222,7 @@ static void installDefaultIntegrations(
options.addIntegration(
new SendCachedEnvelopeIntegration(
new SendFireAndForgetOutboxSender(() -> options.getOutboxPath()),
hasStartupCrashMarker));
startupCrashMarkerEvaluator));

// AppLifecycleIntegration has to be installed before AnrIntegration, because AnrIntegration
// relies on AppState set by it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.util.LazyEvaluator;
import io.sentry.util.Objects;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
Expand All @@ -16,13 +17,13 @@ final class SendCachedEnvelopeIntegration implements Integration {

private final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory
factory;
private final boolean hasStartupCrashMarker;
private final @NotNull LazyEvaluator<Boolean> startupCrashMarkerEvaluator;

public SendCachedEnvelopeIntegration(
final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory factory,
final boolean hasStartupCrashMarker) {
final @NotNull LazyEvaluator<Boolean> startupCrashMarkerEvaluator) {
this.factory = Objects.requireNonNull(factory, "SendFireAndForgetFactory is required");
this.hasStartupCrashMarker = hasStartupCrashMarker;
this.startupCrashMarkerEvaluator = startupCrashMarkerEvaluator;
}

@Override
Expand Down Expand Up @@ -62,7 +63,7 @@ public void register(@NotNull IHub hub, @NotNull SentryOptions options) {
}
});

if (hasStartupCrashMarker) {
if (startupCrashMarkerEvaluator.getValue()) {
androidOptions
.getLogger()
.log(SentryLevel.DEBUG, "Startup Crash marker exists, blocking flush.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.robolectric.annotation.Config
import java.io.File
Expand Down Expand Up @@ -599,6 +602,22 @@ class AndroidOptionsInitializerTest {
assertFalse(fixture.sentryOptions.scopeObservers.any { it is PersistingScopeObserver })
}

@Test
fun `installDefaultIntegrations does not evaluate cacheDir or outboxPath when called`() {
val mockOptions = spy(fixture.sentryOptions)
AndroidOptionsInitializer.installDefaultIntegrations(
fixture.context,
mockOptions,
mock(),
mock(),
mock(),
false,
false
)
verify(mockOptions, never()).outboxPath
verify(mockOptions, never()).cacheDirPath
}

@Config(sdk = [30])
@Test
fun `AnrV2Integration added to integrations list for API 30 and above`() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.sentry.ILogger
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForget
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory
import io.sentry.SentryLevel.DEBUG
import io.sentry.util.LazyEvaluator
import org.awaitility.kotlin.await
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
Expand Down Expand Up @@ -53,7 +54,7 @@ class SendCachedEnvelopeIntegrationTest {
}
)

return SendCachedEnvelopeIntegration(factory, hasStartupCrashMarker)
return SendCachedEnvelopeIntegration(factory, LazyEvaluator { hasStartupCrashMarker })
}
}

Expand Down
9 changes: 9 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -4151,6 +4151,15 @@ public final class io/sentry/util/JsonSerializationUtils {
public static fun calendarToMap (Ljava/util/Calendar;)Ljava/util/Map;
}

public final class io/sentry/util/LazyEvaluator {
public fun <init> (Lio/sentry/util/LazyEvaluator$Evaluator;)V
public fun getValue ()Ljava/lang/Object;
}

public abstract interface class io/sentry/util/LazyEvaluator$Evaluator {
public abstract fun evaluate ()Ljava/lang/Object;
}

public final class io/sentry/util/LogUtils {
public fun <init> ()V
public static fun logNotInstanceOf (Ljava/lang/Class;Ljava/lang/Object;Lio/sentry/ILogger;)V
Expand Down
42 changes: 42 additions & 0 deletions sentry/src/main/java/io/sentry/util/LazyEvaluator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.sentry.util;

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Class that evaluates a function lazily. It means the evaluator function is called only when
* getValue is called, and it's cached.
*/
@ApiStatus.Internal
public final class LazyEvaluator<T> {
private @Nullable T value = null;
private final @NotNull Evaluator<T> evaluator;

/**
* Class that evaluates a function lazily. It means the evaluator function is called only when
* getValue is called, and it's cached.
*
* @param evaluator The function to evaluate.
*/
public LazyEvaluator(final @NotNull Evaluator<T> evaluator) {
this.evaluator = evaluator;
}

/**
* Executes the evaluator function and caches its result, so that it's called only once.
*
* @return The result of the evaluator function.
*/
public synchronized @NotNull T getValue() {
if (value == null) {
value = evaluator.evaluate();
}
return value;
}

public interface Evaluator<T> {
@NotNull
T evaluate();
}
}
41 changes: 41 additions & 0 deletions sentry/src/test/java/io/sentry/util/LazyEvaluatorTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.sentry.util

import kotlin.test.Test
import kotlin.test.assertEquals

class LazyEvaluatorTest {

class Fixture {
var count = 0

fun getSut(): LazyEvaluator<Int> {
count = 0
return LazyEvaluator<Int> { ++count }
}
}

private val fixture = Fixture()

@Test
fun `does not evaluate on instantiation`() {
fixture.getSut()
assertEquals(0, fixture.count)
}

@Test
fun `evaluator is called on getValue`() {
val evaluator = fixture.getSut()
assertEquals(0, fixture.count)
assertEquals(1, evaluator.value)
assertEquals(1, fixture.count)
}

@Test
fun `evaluates only once`() {
val evaluator = fixture.getSut()
assertEquals(0, fixture.count)
assertEquals(1, evaluator.value)
assertEquals(1, evaluator.value)
assertEquals(1, fixture.count)
}
}

0 comments on commit 32134b8

Please sign in to comment.