Skip to content

Commit 5c5238a

Browse files
committed
Add tests
1 parent 5b32662 commit 5c5238a

File tree

8 files changed

+393
-103
lines changed

8 files changed

+393
-103
lines changed

sentry-android-core/api/sentry-android-core.api

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,17 @@ public final class io/sentry/android/core/AppLifecycleIntegration : io/sentry/In
166166
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
167167
}
168168

169-
public final class io/sentry/android/core/AppState {
169+
public final class io/sentry/android/core/AppState : java/io/Closeable {
170+
public fun close ()V
170171
public static fun getInstance ()Lio/sentry/android/core/AppState;
171172
public fun isInBackground ()Ljava/lang/Boolean;
172173
}
173174

175+
public abstract interface class io/sentry/android/core/AppState$AppStateListener {
176+
public abstract fun onBackground ()V
177+
public abstract fun onForeground ()V
178+
}
179+
174180
public final class io/sentry/android/core/BuildConfig {
175181
public static final field BUILD_TYPE Ljava/lang/String;
176182
public static final field DEBUG Z
@@ -420,11 +426,13 @@ public class io/sentry/android/core/SpanFrameMetricsCollector : io/sentry/IPerfo
420426
public fun onSpanStarted (Lio/sentry/ISpan;)V
421427
}
422428

423-
public final class io/sentry/android/core/SystemEventsBreadcrumbsIntegration : io/sentry/Integration, java/io/Closeable {
429+
public final class io/sentry/android/core/SystemEventsBreadcrumbsIntegration : io/sentry/Integration, io/sentry/android/core/AppState$AppStateListener, java/io/Closeable {
424430
public fun <init> (Landroid/content/Context;)V
425431
public fun <init> (Landroid/content/Context;Ljava/util/List;)V
426432
public fun close ()V
427433
public static fun getDefaultActions ()Ljava/util/List;
434+
public fun onBackground ()V
435+
public fun onForeground ()V
428436
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
429437
}
430438

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ static void loadDefaultAndMetadataOptions(
128128
options.setCacheDirPath(getCacheDir(context).getAbsolutePath());
129129

130130
readDefaultOptionValues(options, context, buildInfoProvider);
131-
AppState.getInstance().addLifecycleObserver(options);
131+
AppState.getInstance().registerLifecycleObserver(options);
132132
}
133133

134134
@TestOnly

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22

33
import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion;
44

5-
import androidx.lifecycle.ProcessLifecycleOwner;
65
import io.sentry.IScopes;
76
import io.sentry.ISentryLifecycleToken;
87
import io.sentry.Integration;
98
import io.sentry.SentryLevel;
109
import io.sentry.SentryOptions;
11-
import io.sentry.android.core.internal.util.AndroidThreadChecker;
1210
import io.sentry.util.AutoClosableReentrantLock;
1311
import io.sentry.util.Objects;
1412
import java.io.Closeable;
@@ -89,6 +87,6 @@ public void close() throws IOException {
8987
// TODO: probably should move it to Scopes.close(), but that'd require a new interface and
9088
// different implementations for Java and Android. This is probably fine like this too, because
9189
// integrations are closed in the same place
92-
AppState.getInstance().removeLifecycleObserver();
90+
AppState.getInstance().unregisterLifecycleObserver();
9391
}
9492
}

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

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import androidx.annotation.NonNull;
44
import androidx.lifecycle.DefaultLifecycleObserver;
5-
import androidx.lifecycle.Lifecycle;
65
import androidx.lifecycle.LifecycleOwner;
76
import androidx.lifecycle.ProcessLifecycleOwner;
87
import io.sentry.ILogger;
@@ -65,7 +64,7 @@ void removeAppStateListener(final @NotNull AppStateListener listener) {
6564
}
6665
}
6766

68-
void addLifecycleObserver(final @Nullable SentryAndroidOptions options) {
67+
void registerLifecycleObserver(final @Nullable SentryAndroidOptions options) {
6968
if (lifecycleObserver != null) {
7069
return;
7170
}
@@ -81,7 +80,8 @@ private void ensureLifecycleObserver(final @NotNull ILogger logger) {
8180
}
8281
try {
8382
Class.forName("androidx.lifecycle.ProcessLifecycleOwner");
84-
// create it right away, so it's available in addAppStateListener in case it's posted to main thread
83+
// create it right away, so it's available in addAppStateListener in case it's posted to main
84+
// thread
8585
lifecycleObserver = new LifecycleObserver();
8686

8787
if (AndroidThreadChecker.getInstance().isMainThread()) {
@@ -92,17 +92,12 @@ private void ensureLifecycleObserver(final @NotNull ILogger logger) {
9292
handler.post(() -> addObserverInternal(logger));
9393
}
9494
} catch (ClassNotFoundException e) {
95-
logger
96-
.log(
97-
SentryLevel.WARNING,
98-
"androidx.lifecycle is not available, some features might not be properly working,"
99-
+ "e.g. Session Tracking, Network and System Events breadcrumbs, etc.");
95+
logger.log(
96+
SentryLevel.WARNING,
97+
"androidx.lifecycle is not available, some features might not be properly working,"
98+
+ "e.g. Session Tracking, Network and System Events breadcrumbs, etc.");
10099
} catch (Throwable e) {
101-
logger
102-
.log(
103-
SentryLevel.ERROR,
104-
"AppState could not register lifecycle observer",
105-
e);
100+
logger.log(SentryLevel.ERROR, "AppState could not register lifecycle observer", e);
106101
}
107102
}
108103

@@ -118,15 +113,14 @@ private void addObserverInternal(final @NotNull ILogger logger) {
118113
// connection with conflicting dependencies of the androidx.lifecycle.
119114
// //See the issue here: https://github.com/getsentry/sentry-java/pull/2228
120115
lifecycleObserver = null;
121-
logger
122-
.log(
123-
SentryLevel.ERROR,
124-
"AppState failed to get Lifecycle and could not install lifecycle observer.",
125-
e);
116+
logger.log(
117+
SentryLevel.ERROR,
118+
"AppState failed to get Lifecycle and could not install lifecycle observer.",
119+
e);
126120
}
127121
}
128122

129-
void removeLifecycleObserver() {
123+
void unregisterLifecycleObserver() {
130124
if (lifecycleObserver == null) {
131125
return;
132126
}
@@ -157,38 +151,39 @@ private void removeObserverInternal(final @Nullable LifecycleObserver ref) {
157151

158152
@Override
159153
public void close() throws IOException {
160-
removeLifecycleObserver();
154+
unregisterLifecycleObserver();
161155
}
162156

163-
static final class LifecycleObserver implements DefaultLifecycleObserver {
164-
final List<AppStateListener> listeners = new CopyOnWriteArrayList<AppStateListener>() {
165-
@Override
166-
public boolean add(AppStateListener appStateListener) {
167-
// notify the listeners immediately to let them "catch up" with the current state (mimics the behavior of androidx.lifecycle)
168-
Lifecycle.State currentState = ProcessLifecycleOwner.get().getLifecycle().getCurrentState();
169-
if (currentState.isAtLeast(Lifecycle.State.STARTED)) {
170-
appStateListener.onForeground();
171-
} else {
172-
appStateListener.onBackground();
173-
}
174-
return super.add(appStateListener);
175-
}
176-
};
157+
final class LifecycleObserver implements DefaultLifecycleObserver {
158+
final List<AppStateListener> listeners =
159+
new CopyOnWriteArrayList<AppStateListener>() {
160+
@Override
161+
public boolean add(AppStateListener appStateListener) {
162+
// notify the listeners immediately to let them "catch up" with the current state
163+
// (mimics the behavior of androidx.lifecycle)
164+
if (Boolean.FALSE.equals(inBackground)) {
165+
appStateListener.onForeground();
166+
} else if (Boolean.TRUE.equals(inBackground)) {
167+
appStateListener.onBackground();
168+
}
169+
return super.add(appStateListener);
170+
}
171+
};
177172

178173
@Override
179174
public void onStart(@NonNull LifecycleOwner owner) {
180175
for (AppStateListener listener : listeners) {
181176
listener.onForeground();
182177
}
183-
AppState.getInstance().setInBackground(false);
178+
setInBackground(false);
184179
}
185180

186181
@Override
187182
public void onStop(@NonNull LifecycleOwner owner) {
188183
for (AppStateListener listener : listeners) {
189184
listener.onBackground();
190185
}
191-
AppState.getInstance().setInBackground(true);
186+
setInBackground(true);
192187
}
193188
}
194189

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

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import android.content.Intent;
2626
import android.content.IntentFilter;
2727
import android.os.Bundle;
28-
import androidx.lifecycle.ProcessLifecycleOwner;
2928
import io.sentry.Breadcrumb;
3029
import io.sentry.Hint;
3130
import io.sentry.IScopes;
@@ -34,7 +33,6 @@
3433
import io.sentry.SentryLevel;
3534
import io.sentry.SentryOptions;
3635
import io.sentry.android.core.internal.util.AndroidCurrentDateProvider;
37-
import io.sentry.android.core.internal.util.AndroidThreadChecker;
3836
import io.sentry.android.core.internal.util.Debouncer;
3937
import io.sentry.util.AutoClosableReentrantLock;
4038
import io.sentry.util.Objects;
@@ -49,8 +47,8 @@
4947
import org.jetbrains.annotations.Nullable;
5048
import org.jetbrains.annotations.TestOnly;
5149

52-
public final class SystemEventsBreadcrumbsIntegration implements Integration, Closeable,
53-
AppState.AppStateListener {
50+
public final class SystemEventsBreadcrumbsIntegration
51+
implements Integration, Closeable, AppState.AppStateListener {
5452

5553
private final @NotNull Context context;
5654

@@ -170,18 +168,21 @@ private void unregisterReceiver() {
170168
return;
171169
}
172170

173-
options.getExecutorService().submit(() -> {
174-
final @Nullable SystemEventsBroadcastReceiver receiverRef;
175-
try (final @NotNull ISentryLifecycleToken ignored = receiverLock.acquire()) {
176-
isStopped = true;
177-
receiverRef = receiver;
178-
receiver = null;
179-
}
171+
options
172+
.getExecutorService()
173+
.submit(
174+
() -> {
175+
final @Nullable SystemEventsBroadcastReceiver receiverRef;
176+
try (final @NotNull ISentryLifecycleToken ignored = receiverLock.acquire()) {
177+
isStopped = true;
178+
receiverRef = receiver;
179+
receiver = null;
180+
}
180181

181-
if (receiverRef != null) {
182-
context.unregisterReceiver(receiverRef);
183-
}
184-
});
182+
if (receiverRef != null) {
183+
context.unregisterReceiver(receiverRef);
184+
}
185+
});
185186
}
186187

187188
@Override

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

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,17 @@ import kotlin.test.Test
88
import kotlin.test.assertNotNull
99
import kotlin.test.assertNull
1010
import org.junit.runner.RunWith
11-
import org.mockito.kotlin.any
1211
import org.mockito.kotlin.mock
13-
import org.mockito.kotlin.verify
1412
import org.robolectric.Shadows.shadowOf
1513

1614
@RunWith(AndroidJUnit4::class)
1715
class AppLifecycleIntegrationTest {
1816
private class Fixture {
1917
val scopes = mock<IScopes>()
20-
lateinit var handler: MainLooperHandler
2118
val options = SentryAndroidOptions()
2219

23-
fun getSut(mockHandler: Boolean = true): AppLifecycleIntegration {
24-
handler = if (mockHandler) mock() else MainLooperHandler()
25-
return AppLifecycleIntegration(handler)
20+
fun getSut(): AppLifecycleIntegration {
21+
return AppLifecycleIntegration()
2622
}
2723
}
2824

@@ -64,23 +60,7 @@ class AppLifecycleIntegrationTest {
6460
}
6561

6662
@Test
67-
fun `When AppLifecycleIntegration is registered from a background thread, post on the main thread`() {
68-
val sut = fixture.getSut()
69-
val latch = CountDownLatch(1)
70-
71-
Thread {
72-
sut.register(fixture.scopes, fixture.options)
73-
latch.countDown()
74-
}
75-
.start()
76-
77-
latch.await()
78-
79-
verify(fixture.handler).post(any())
80-
}
81-
82-
@Test
83-
fun `When AppLifecycleIntegration is closed from a background thread, post on the main thread`() {
63+
fun `When AppLifecycleIntegration is closed from a background thread, watcher is set to null`() {
8464
val sut = fixture.getSut()
8565
val latch = CountDownLatch(1)
8666

@@ -96,29 +76,25 @@ class AppLifecycleIntegrationTest {
9676

9777
latch.await()
9878

99-
verify(fixture.handler).post(any())
79+
// ensure all messages on main looper got processed
80+
shadowOf(Looper.getMainLooper()).idle()
81+
82+
assertNull(sut.watcher)
10083
}
10184

10285
@Test
103-
fun `When AppLifecycleIntegration is closed from a background thread, watcher is set to null`() {
104-
val sut = fixture.getSut(mockHandler = false)
105-
val latch = CountDownLatch(1)
86+
fun `When AppLifecycleIntegration is closed, AppState unregisterLifecycleObserver is called`() {
87+
val sut = fixture.getSut()
88+
val appState = AppState.getInstance()
10689

10790
sut.register(fixture.scopes, fixture.options)
10891

109-
assertNotNull(sut.watcher)
92+
// Verify that lifecycleObserver is not null after registration
93+
assertNotNull(appState.lifecycleObserver)
11094

111-
Thread {
112-
sut.close()
113-
latch.countDown()
114-
}
115-
.start()
116-
117-
latch.await()
118-
119-
// ensure all messages on main looper got processed
120-
shadowOf(Looper.getMainLooper()).idle()
95+
sut.close()
12196

122-
assertNull(sut.watcher)
97+
// Verify that lifecycleObserver is null after unregistering
98+
assertNull(appState.lifecycleObserver)
12399
}
124100
}

0 commit comments

Comments
 (0)