Skip to content
This repository has been archived by the owner on Aug 30, 2023. It is now read-only.

Commit

Permalink
removed Thread.sleep from LifecycleWatcher tests, using awaitility an…
Browse files Browse the repository at this point in the history
…d DateProvider (#392)
  • Loading branch information
marandaneto authored May 7, 2020
1 parent 92c416e commit 9979618
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 79 deletions.
3 changes: 2 additions & 1 deletion buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ object Config {
val leakCanary = "com.squareup.leakcanary:leakcanary-android:2.1"

val lifecycleVersion = "2.2.0"
val lifecycleProcessor = "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
val lifecycleProcess = "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
val lifecycleCommonJava8 = "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
}

Expand All @@ -38,6 +38,7 @@ object Config {
val androidxJunit = "androidx.test.ext:junit:1.1.1"
val robolectric = "org.robolectric:robolectric:4.3.1"
val mockitoKotlin = "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
val awaitility = "org.awaitility:awaitility-kotlin:4.0.2"
}

object QualityPlugins {
Expand Down
3 changes: 2 additions & 1 deletion sentry-android-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ dependencies {
implementation(Config.Libs.gson)

// lifecycle processor, session tracking
implementation(Config.Libs.lifecycleProcessor)
implementation(Config.Libs.lifecycleProcess)
implementation(Config.Libs.lifecycleCommonJava8)

compileOnly(Config.CompileOnly.nopen)
Expand All @@ -96,6 +96,7 @@ dependencies {
testImplementation(Config.TestLibs.androidxRunner)
testImplementation(Config.TestLibs.androidxJunit)
testImplementation(Config.TestLibs.mockitoKotlin)
testImplementation(Config.TestLibs.awaitility)
}

//TODO: move thse blocks to parent gradle file, DRY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
import io.sentry.core.Breadcrumb;
import io.sentry.core.IHub;
import io.sentry.core.SentryLevel;
import io.sentry.core.transport.CurrentDateProvider;
import io.sentry.core.transport.ICurrentDateProvider;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

final class LifecycleWatcher implements DefaultLifecycleObserver {

Expand All @@ -21,16 +25,34 @@ final class LifecycleWatcher implements DefaultLifecycleObserver {
private final @NotNull IHub hub;
private final boolean enableSessionTracking;
private final boolean enableAppLifecycleBreadcrumbs;
private final @NotNull AtomicBoolean runningSession = new AtomicBoolean();

private final @NotNull ICurrentDateProvider currentDateProvider;

LifecycleWatcher(
final @NotNull IHub hub,
final long sessionIntervalMillis,
final boolean enableSessionTracking,
final boolean enableAppLifecycleBreadcrumbs) {
this(
hub,
sessionIntervalMillis,
enableSessionTracking,
enableAppLifecycleBreadcrumbs,
CurrentDateProvider.getInstance());
}

LifecycleWatcher(
final @NotNull IHub hub,
final long sessionIntervalMillis,
final boolean enableSessionTracking,
final boolean enableAppLifecycleBreadcrumbs,
final @NotNull ICurrentDateProvider currentDateProvider) {
this.sessionIntervalMillis = sessionIntervalMillis;
this.enableSessionTracking = enableSessionTracking;
this.enableAppLifecycleBreadcrumbs = enableAppLifecycleBreadcrumbs;
this.hub = hub;
this.currentDateProvider = currentDateProvider;
}

// App goes to foreground
Expand All @@ -42,12 +64,13 @@ public void onStart(final @NotNull LifecycleOwner owner) {

private void startSession() {
if (enableSessionTracking) {
final long currentTimeMillis = System.currentTimeMillis();
final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis();
cancelTask();
if (lastStartedSession == 0L
|| (lastStartedSession + sessionIntervalMillis) <= currentTimeMillis) {
addSessionBreadcrumb("start");
hub.startSession();
runningSession.set(true);
}
lastStartedSession = currentTimeMillis;
}
Expand All @@ -72,6 +95,7 @@ private void scheduleEndSession() {
public void run() {
addSessionBreadcrumb("end");
hub.endSession();
runningSession.set(false);
}
};

Expand All @@ -81,6 +105,7 @@ public void run() {
private void cancelTask() {
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
}
}

Expand All @@ -103,4 +128,16 @@ private void addSessionBreadcrumb(final @NotNull String state) {
breadcrumb.setLevel(SentryLevel.INFO);
hub.addBreadcrumb(breadcrumb);
}

@TestOnly
@NotNull
AtomicBoolean isRunningSession() {
return runningSession;
}

@TestOnly
@Nullable
TimerTask getTimerTask() {
return timerTask;
}
}
Original file line number Diff line number Diff line change
@@ -1,93 +1,105 @@
package io.sentry.android.core

import androidx.lifecycle.LifecycleOwner
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.check
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import io.sentry.core.Breadcrumb
import io.sentry.core.IHub
import io.sentry.core.SentryLevel
import kotlin.test.Ignore
import io.sentry.core.transport.ICurrentDateProvider
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import org.awaitility.kotlin.await

class LifecycleWatcherTest {

private class Fixture {
val ownerMock = mock<LifecycleOwner>()
val hub = mock<IHub>()
val dateProvider = mock<ICurrentDateProvider>()

fun getSUT(sessionIntervalMillis: Long = 0L, enableSessionTracking: Boolean = true, enableAppLifecycleBreadcrumbs: Boolean = true): LifecycleWatcher {
return LifecycleWatcher(hub, sessionIntervalMillis, enableSessionTracking, enableAppLifecycleBreadcrumbs, dateProvider)
}
}

private val fixture = Fixture()

@Test
fun `if last started session is 0, start new session`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 100L, true, false)
watcher.onStart(mock())
verify(hub).startSession()
val watcher = fixture.getSUT(enableAppLifecycleBreadcrumbs = false)
watcher.onStart(fixture.ownerMock)
verify(fixture.hub).startSession()
}

@Test
fun `if last started session is after interval, start new session`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 100L, true, false)
watcher.onStart(mock())
Thread.sleep(150L)
watcher.onStart(mock())
verify(hub, times(2)).startSession()
val watcher = fixture.getSUT(enableAppLifecycleBreadcrumbs = false)
whenever(fixture.dateProvider.currentTimeMillis).thenReturn(1L, 2L)
watcher.onStart(fixture.ownerMock)
watcher.onStart(fixture.ownerMock)
verify(fixture.hub, times(2)).startSession()
}

@Test
fun `if last started session is before interval, it should not start a new session`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 1000L, true, false)
watcher.onStart(mock())
Thread.sleep(100)
watcher.onStart(mock())
verify(hub).startSession()
val watcher = fixture.getSUT(enableAppLifecycleBreadcrumbs = false)
whenever(fixture.dateProvider.currentTimeMillis).thenReturn(2L, 1L)
watcher.onStart(fixture.ownerMock)
watcher.onStart(fixture.ownerMock)
verify(fixture.hub).startSession()
}

@Ignore("for some reason this is flaky only on appveyor")
@Test
fun `if app goes to background, end session after interval`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 100L, true, false)
watcher.onStart(mock())
watcher.onStop(mock())
Thread.sleep(500L)
verify(hub).endSession()
val watcher = fixture.getSUT(enableAppLifecycleBreadcrumbs = false)
watcher.onStart(fixture.ownerMock)
watcher.onStop(fixture.ownerMock)
await.untilFalse(watcher.isRunningSession)
verify(fixture.hub).endSession()
}

@Test
fun `if app goes to background and foreground again, dont end the session`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 1000L, true, false)
watcher.onStart(mock())
watcher.onStop(mock())
Thread.sleep(150)
watcher.onStart(mock())
verify(hub, never()).endSession()
val watcher = fixture.getSUT(sessionIntervalMillis = 30000L, enableAppLifecycleBreadcrumbs = false)
watcher.onStart(fixture.ownerMock)

watcher.onStop(fixture.ownerMock)
assertNotNull(watcher.timerTask)

watcher.onStart(fixture.ownerMock)
assertNull(watcher.timerTask)

verify(fixture.hub, never()).endSession()
}

@Test
fun `When session tracking is disabled, do not start session`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 1000L, false, false)
watcher.onStart(mock())
verify(hub, never()).startSession()
val watcher = fixture.getSUT(enableSessionTracking = false, enableAppLifecycleBreadcrumbs = false)
watcher.onStart(fixture.ownerMock)
verify(fixture.hub, never()).startSession()
}

@Test
fun `When session tracking is disabled, do not end session`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 0L, false, false)
watcher.onStart(mock())
verify(hub, never()).endSession()
val watcher = fixture.getSUT(enableSessionTracking = false, enableAppLifecycleBreadcrumbs = false)
watcher.onStop(fixture.ownerMock)
assertNull(watcher.timerTask)
verify(fixture.hub, never()).endSession()
}

@Test
fun `When session tracking is enabled, add breadcrumb on start`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 0L, true, false)
watcher.onStart(mock())
Thread.sleep(150)
verify(hub).addBreadcrumb(check<Breadcrumb> {
val watcher = fixture.getSUT(enableAppLifecycleBreadcrumbs = false)
watcher.onStart(fixture.ownerMock)
verify(fixture.hub).addBreadcrumb(check<Breadcrumb> {
assertEquals("app.lifecycle", it.category)
assertEquals("session", it.type)
assertEquals(SentryLevel.INFO, it.level)
Expand All @@ -97,11 +109,11 @@ class LifecycleWatcherTest {

@Test
fun `When session tracking is enabled, add breadcrumb on stop`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 0L, true, false)
watcher.onStop(mock())
Thread.sleep(150)
verify(hub).addBreadcrumb(check<Breadcrumb> {
val watcher = fixture.getSUT(enableAppLifecycleBreadcrumbs = false)
watcher.isRunningSession.set(true)
watcher.onStop(fixture.ownerMock)
await.untilFalse(watcher.isRunningSession)
verify(fixture.hub).addBreadcrumb(check<Breadcrumb> {
assertEquals("app.lifecycle", it.category)
assertEquals("session", it.type)
assertEquals(SentryLevel.INFO, it.level)
Expand All @@ -111,26 +123,24 @@ class LifecycleWatcherTest {

@Test
fun `When session tracking is disabled, do not add breadcrumb on start`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 0L, false, false)
watcher.onStart(mock())
verify(hub, never()).addBreadcrumb(any<Breadcrumb>())
val watcher = fixture.getSUT(enableSessionTracking = false, enableAppLifecycleBreadcrumbs = false)
watcher.onStart(fixture.ownerMock)
verify(fixture.hub, never()).addBreadcrumb(any<Breadcrumb>())
}

@Test
fun `When session tracking is disabled, do not add breadcrumb on stop`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 0L, false, false)
watcher.onStop(mock())
verify(hub, never()).addBreadcrumb(any<Breadcrumb>())
val watcher = fixture.getSUT(enableSessionTracking = false, enableAppLifecycleBreadcrumbs = false)
watcher.onStop(fixture.ownerMock)
assertNull(watcher.timerTask)
verify(fixture.hub, never()).addBreadcrumb(any<Breadcrumb>())
}

@Test
fun `When app lifecycle breadcrumbs is enabled, add breadcrumb on start`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 0L, false, true)
watcher.onStart(mock())
verify(hub).addBreadcrumb(check<Breadcrumb> {
val watcher = fixture.getSUT(enableSessionTracking = false)
watcher.onStart(fixture.ownerMock)
verify(fixture.hub).addBreadcrumb(check<Breadcrumb> {
assertEquals("app.lifecycle", it.category)
assertEquals("navigation", it.type)
assertEquals(SentryLevel.INFO, it.level)
Expand All @@ -140,18 +150,16 @@ class LifecycleWatcherTest {

@Test
fun `When app lifecycle breadcrumbs is disabled, do not add breadcrumb on start`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 0L, false, false)
watcher.onStart(mock())
verify(hub, never()).addBreadcrumb(any<Breadcrumb>())
val watcher = fixture.getSUT(enableSessionTracking = false, enableAppLifecycleBreadcrumbs = false)
watcher.onStart(fixture.ownerMock)
verify(fixture.hub, never()).addBreadcrumb(any<Breadcrumb>())
}

@Test
fun `When app lifecycle breadcrumbs is enabled, add breadcrumb on stop`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 0L, false, true)
watcher.onStop(mock())
verify(hub).addBreadcrumb(check<Breadcrumb> {
val watcher = fixture.getSUT(enableSessionTracking = false)
watcher.onStop(fixture.ownerMock)
verify(fixture.hub).addBreadcrumb(check<Breadcrumb> {
assertEquals("app.lifecycle", it.category)
assertEquals("navigation", it.type)
assertEquals(SentryLevel.INFO, it.level)
Expand All @@ -161,9 +169,8 @@ class LifecycleWatcherTest {

@Test
fun `When app lifecycle breadcrumbs is disabled, do not add breadcrumb on stop`() {
val hub = mock<IHub>()
val watcher = LifecycleWatcher(hub, 0L, false, false)
watcher.onStop(mock())
verify(hub, never()).addBreadcrumb(any<Breadcrumb>())
val watcher = fixture.getSUT(enableSessionTracking = false, enableAppLifecycleBreadcrumbs = false)
watcher.onStop(fixture.ownerMock)
verify(fixture.hub, never()).addBreadcrumb(any<Breadcrumb>())
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
package io.sentry.core.transport;

final class CurrentDateProvider implements ICurrentDateProvider {
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
public final class CurrentDateProvider implements ICurrentDateProvider {

private static final ICurrentDateProvider instance = new CurrentDateProvider();

public static ICurrentDateProvider getInstance() {
return instance;
}

private CurrentDateProvider() {}

@Override
public final long getCurrentTimeMillis() {
Expand Down
Loading

0 comments on commit 9979618

Please sign in to comment.