Skip to content

Commit 32134b8

Browse files
committed
added LazyEvaluator to evaluate a function lazily
AndroidOptionsInitializer.installDefaultIntegrations now evaluate cache dir lazily
1 parent 3f5a679 commit 32134b8

File tree

7 files changed

+123
-8
lines changed

7 files changed

+123
-8
lines changed

sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.sentry.internal.gestures.GestureTargetLocator;
2727
import io.sentry.internal.viewhierarchy.ViewHierarchyExporter;
2828
import io.sentry.transport.NoOpEnvelopeCache;
29+
import io.sentry.util.LazyEvaluator;
2930
import io.sentry.util.Objects;
3031
import java.io.File;
3132
import java.util.ArrayList;
@@ -195,12 +196,13 @@ static void installDefaultIntegrations(
195196

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

200202
options.addIntegration(
201203
new SendCachedEnvelopeIntegration(
202204
new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath()),
203-
hasStartupCrashMarker));
205+
startupCrashMarkerEvaluator));
204206

205207
// Integrations are registered in the same order. NDK before adding Watch outbox,
206208
// because sentry-native move files around and we don't want to watch that.
@@ -220,7 +222,7 @@ static void installDefaultIntegrations(
220222
options.addIntegration(
221223
new SendCachedEnvelopeIntegration(
222224
new SendFireAndForgetOutboxSender(() -> options.getOutboxPath()),
223-
hasStartupCrashMarker));
225+
startupCrashMarkerEvaluator));
224226

225227
// AppLifecycleIntegration has to be installed before AnrIntegration, because AnrIntegration
226228
// relies on AppState set by it

sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration;
66
import io.sentry.SentryLevel;
77
import io.sentry.SentryOptions;
8+
import io.sentry.util.LazyEvaluator;
89
import io.sentry.util.Objects;
910
import java.util.concurrent.Future;
1011
import java.util.concurrent.RejectedExecutionException;
@@ -16,13 +17,13 @@ final class SendCachedEnvelopeIntegration implements Integration {
1617

1718
private final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory
1819
factory;
19-
private final boolean hasStartupCrashMarker;
20+
private final @NotNull LazyEvaluator<Boolean> startupCrashMarkerEvaluator;
2021

2122
public SendCachedEnvelopeIntegration(
2223
final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory factory,
23-
final boolean hasStartupCrashMarker) {
24+
final @NotNull LazyEvaluator<Boolean> startupCrashMarkerEvaluator) {
2425
this.factory = Objects.requireNonNull(factory, "SendFireAndForgetFactory is required");
25-
this.hasStartupCrashMarker = hasStartupCrashMarker;
26+
this.startupCrashMarkerEvaluator = startupCrashMarkerEvaluator;
2627
}
2728

2829
@Override
@@ -62,7 +63,7 @@ public void register(@NotNull IHub hub, @NotNull SentryOptions options) {
6263
}
6364
});
6465

65-
if (hasStartupCrashMarker) {
66+
if (startupCrashMarkerEvaluator.getValue()) {
6667
androidOptions
6768
.getLogger()
6869
.log(SentryLevel.DEBUG, "Startup Crash marker exists, blocking flush.");

sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import org.junit.runner.RunWith
2222
import org.mockito.kotlin.any
2323
import org.mockito.kotlin.eq
2424
import org.mockito.kotlin.mock
25+
import org.mockito.kotlin.never
26+
import org.mockito.kotlin.spy
27+
import org.mockito.kotlin.verify
2528
import org.mockito.kotlin.whenever
2629
import org.robolectric.annotation.Config
2730
import java.io.File
@@ -599,6 +602,22 @@ class AndroidOptionsInitializerTest {
599602
assertFalse(fixture.sentryOptions.scopeObservers.any { it is PersistingScopeObserver })
600603
}
601604

605+
@Test
606+
fun `installDefaultIntegrations does not evaluate cacheDir or outboxPath when called`() {
607+
val mockOptions = spy(fixture.sentryOptions)
608+
AndroidOptionsInitializer.installDefaultIntegrations(
609+
fixture.context,
610+
mockOptions,
611+
mock(),
612+
mock(),
613+
mock(),
614+
false,
615+
false
616+
)
617+
verify(mockOptions, never()).outboxPath
618+
verify(mockOptions, never()).cacheDirPath
619+
}
620+
602621
@Config(sdk = [30])
603622
@Test
604623
fun `AnrV2Integration added to integrations list for API 30 and above`() {

sentry-android-core/src/test/java/io/sentry/android/core/SendCachedEnvelopeIntegrationTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import io.sentry.ILogger
55
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForget
66
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory
77
import io.sentry.SentryLevel.DEBUG
8+
import io.sentry.util.LazyEvaluator
89
import org.awaitility.kotlin.await
910
import org.mockito.kotlin.any
1011
import org.mockito.kotlin.eq
@@ -53,7 +54,7 @@ class SendCachedEnvelopeIntegrationTest {
5354
}
5455
)
5556

56-
return SendCachedEnvelopeIntegration(factory, hasStartupCrashMarker)
57+
return SendCachedEnvelopeIntegration(factory, LazyEvaluator { hasStartupCrashMarker })
5758
}
5859
}
5960

sentry/api/sentry.api

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4151,6 +4151,15 @@ public final class io/sentry/util/JsonSerializationUtils {
41514151
public static fun calendarToMap (Ljava/util/Calendar;)Ljava/util/Map;
41524152
}
41534153

4154+
public final class io/sentry/util/LazyEvaluator {
4155+
public fun <init> (Lio/sentry/util/LazyEvaluator$Evaluator;)V
4156+
public fun getValue ()Ljava/lang/Object;
4157+
}
4158+
4159+
public abstract interface class io/sentry/util/LazyEvaluator$Evaluator {
4160+
public abstract fun evaluate ()Ljava/lang/Object;
4161+
}
4162+
41544163
public final class io/sentry/util/LogUtils {
41554164
public fun <init> ()V
41564165
public static fun logNotInstanceOf (Ljava/lang/Class;Ljava/lang/Object;Lio/sentry/ILogger;)V
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.sentry.util;
2+
3+
import org.jetbrains.annotations.ApiStatus;
4+
import org.jetbrains.annotations.NotNull;
5+
import org.jetbrains.annotations.Nullable;
6+
7+
/**
8+
* Class that evaluates a function lazily. It means the evaluator function is called only when
9+
* getValue is called, and it's cached.
10+
*/
11+
@ApiStatus.Internal
12+
public final class LazyEvaluator<T> {
13+
private @Nullable T value = null;
14+
private final @NotNull Evaluator<T> evaluator;
15+
16+
/**
17+
* Class that evaluates a function lazily. It means the evaluator function is called only when
18+
* getValue is called, and it's cached.
19+
*
20+
* @param evaluator The function to evaluate.
21+
*/
22+
public LazyEvaluator(final @NotNull Evaluator<T> evaluator) {
23+
this.evaluator = evaluator;
24+
}
25+
26+
/**
27+
* Executes the evaluator function and caches its result, so that it's called only once.
28+
*
29+
* @return The result of the evaluator function.
30+
*/
31+
public synchronized @NotNull T getValue() {
32+
if (value == null) {
33+
value = evaluator.evaluate();
34+
}
35+
return value;
36+
}
37+
38+
public interface Evaluator<T> {
39+
@NotNull
40+
T evaluate();
41+
}
42+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.sentry.util
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
6+
class LazyEvaluatorTest {
7+
8+
class Fixture {
9+
var count = 0
10+
11+
fun getSut(): LazyEvaluator<Int> {
12+
count = 0
13+
return LazyEvaluator<Int> { ++count }
14+
}
15+
}
16+
17+
private val fixture = Fixture()
18+
19+
@Test
20+
fun `does not evaluate on instantiation`() {
21+
fixture.getSut()
22+
assertEquals(0, fixture.count)
23+
}
24+
25+
@Test
26+
fun `evaluator is called on getValue`() {
27+
val evaluator = fixture.getSut()
28+
assertEquals(0, fixture.count)
29+
assertEquals(1, evaluator.value)
30+
assertEquals(1, fixture.count)
31+
}
32+
33+
@Test
34+
fun `evaluates only once`() {
35+
val evaluator = fixture.getSut()
36+
assertEquals(0, fixture.count)
37+
assertEquals(1, evaluator.value)
38+
assertEquals(1, evaluator.value)
39+
assertEquals(1, fixture.count)
40+
}
41+
}

0 commit comments

Comments
 (0)