diff --git a/.changeset/calm-guards-install.md b/.changeset/calm-guards-install.md new file mode 100644 index 00000000..e551ffde --- /dev/null +++ b/.changeset/calm-guards-install.md @@ -0,0 +1,6 @@ +--- +'posthog': patch +'posthog-android': patch +--- + +Prevent duplicate integration installation during concurrent SDK setup. diff --git a/posthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.kt b/posthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.kt index ec085cc5..4de7b7f5 100644 --- a/posthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.kt +++ b/posthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.kt @@ -7,6 +7,7 @@ import android.os.Bundle import com.posthog.PostHogIntegration import com.posthog.PostHogInterface import com.posthog.android.PostHogAndroidConfig +import java.util.concurrent.atomic.AtomicBoolean /** * Captures deep link and screen view events @@ -18,10 +19,10 @@ internal class PostHogActivityLifecycleCallbackIntegration( private val config: PostHogAndroidConfig, ) : ActivityLifecycleCallbacks, PostHogIntegration { private var postHog: PostHogInterface? = null + private var ownsInstallation = false private companion object { - @Volatile - private var integrationInstalled = false + private val integrationInstalled = AtomicBoolean(false) } override fun onActivityCreated( @@ -83,19 +84,28 @@ internal class PostHogActivityLifecycleCallbackIntegration( override fun onActivityDestroyed(activity: Activity) { } + @Synchronized override fun install(postHog: PostHogInterface) { - if (integrationInstalled) { + if (!integrationInstalled.compareAndSet(false, true)) { return } - integrationInstalled = true + ownsInstallation = true this.postHog = postHog application.registerActivityLifecycleCallbacks(this) } + @Synchronized override fun uninstall() { - this.postHog = null - integrationInstalled = false - application.unregisterActivityLifecycleCallbacks(this) + if (!ownsInstallation) { + return + } + try { + this.postHog = null + application.unregisterActivityLifecycleCallbacks(this) + } finally { + ownsInstallation = false + integrationInstalled.set(false) + } } } diff --git a/posthog-android/src/main/java/com/posthog/android/internal/PostHogAppInstallIntegration.kt b/posthog-android/src/main/java/com/posthog/android/internal/PostHogAppInstallIntegration.kt index b89dad3d..03ec187d 100644 --- a/posthog-android/src/main/java/com/posthog/android/internal/PostHogAppInstallIntegration.kt +++ b/posthog-android/src/main/java/com/posthog/android/internal/PostHogAppInstallIntegration.kt @@ -6,6 +6,7 @@ import com.posthog.PostHogInterface import com.posthog.android.PostHogAndroidConfig import com.posthog.internal.PostHogPreferences.Companion.BUILD import com.posthog.internal.PostHogPreferences.Companion.VERSION +import java.util.concurrent.atomic.AtomicBoolean /** * Captures app installed and updated events @@ -16,15 +17,14 @@ internal class PostHogAppInstallIntegration( private val context: Context, private val config: PostHogAndroidConfig, ) : PostHogIntegration { + private var ownsInstallation = false + private companion object { - @Volatile - private var integrationInstalled = false + private val integrationInstalled = AtomicBoolean(false) } + @Synchronized override fun install(postHog: PostHogInterface) { - if (integrationInstalled) { - return - } // While the store is unreadable (Direct Boot) VERSION/BUILD read as absent, which would // fire a spurious "Application Installed" for an existing install and overwrite the // persisted previous build on unlock. Stay uninstalled so the correct event can still be @@ -32,7 +32,10 @@ internal class PostHogAppInstallIntegration( if (config.cachePreferences?.isAvailable() == false) { return } - integrationInstalled = true + if (!integrationInstalled.compareAndSet(false, true)) { + return + } + ownsInstallation = true getPackageInfo(context, config)?.let { packageInfo -> config.cachePreferences?.let { preferences -> @@ -74,7 +77,12 @@ internal class PostHogAppInstallIntegration( } } + @Synchronized override fun uninstall() { - integrationInstalled = false + if (!ownsInstallation) { + return + } + ownsInstallation = false + integrationInstalled.set(false) } } diff --git a/posthog-android/src/main/java/com/posthog/android/internal/PostHogLifecycleObserverIntegration.kt b/posthog-android/src/main/java/com/posthog/android/internal/PostHogLifecycleObserverIntegration.kt index 5a8045c5..1c648c0e 100644 --- a/posthog-android/src/main/java/com/posthog/android/internal/PostHogLifecycleObserverIntegration.kt +++ b/posthog-android/src/main/java/com/posthog/android/internal/PostHogLifecycleObserverIntegration.kt @@ -11,6 +11,7 @@ import com.posthog.android.PostHogAndroidConfig import com.posthog.internal.PostHogSessionManager import java.util.Timer import java.util.TimerTask +import java.util.concurrent.atomic.AtomicBoolean /** * Captures app opened and backgrounded events @@ -32,6 +33,7 @@ internal class PostHogLifecycleObserverIntegration( private val bgEndSessionDelayMs = (1000 * 60 * 30).toLong() // 30 minutes private var postHog: PostHogInterface? = null + private var ownsInstallation = false private companion object { // in case there are multiple instances or the SDK is closed/setup again @@ -40,8 +42,7 @@ internal class PostHogLifecycleObserverIntegration( @Volatile private var fromBackground = false - @Volatile - private var integrationInstalled = false + private val integrationInstalled = AtomicBoolean(false) } override fun onStart(owner: LifecycleOwner) { @@ -124,11 +125,12 @@ internal class PostHogLifecycleObserverIntegration( lifecycle.addObserver(this) } + @Synchronized override fun install(postHog: PostHogInterface) { - if (integrationInstalled) { + if (!integrationInstalled.compareAndSet(false, true)) { return } - integrationInstalled = true + ownsInstallation = true try { this.postHog = postHog @@ -148,9 +150,12 @@ internal class PostHogLifecycleObserverIntegration( lifecycle.removeObserver(this) } + @Synchronized override fun uninstall() { + if (!ownsInstallation) { + return + } try { - integrationInstalled = false this.postHog = null if (isMainThread(mainHandler)) { remove() @@ -161,6 +166,9 @@ internal class PostHogLifecycleObserverIntegration( } } catch (e: Throwable) { config.logger.log("Failed to uninstall PostHogLifecycleObserverIntegration: $e") + } finally { + ownsInstallation = false + integrationInstalled.set(false) } } } diff --git a/posthog-android/src/main/java/com/posthog/android/internal/PostHogTouchActivityIntegration.kt b/posthog-android/src/main/java/com/posthog/android/internal/PostHogTouchActivityIntegration.kt index 459b9985..c1483064 100644 --- a/posthog-android/src/main/java/com/posthog/android/internal/PostHogTouchActivityIntegration.kt +++ b/posthog-android/src/main/java/com/posthog/android/internal/PostHogTouchActivityIntegration.kt @@ -10,6 +10,7 @@ import curtains.OnRootViewsChangedListener import curtains.TouchEventInterceptor import curtains.phoneWindow import curtains.touchEventInterceptors +import java.util.concurrent.atomic.AtomicBoolean /** * Marks user touches as session activity by calling [PostHogSessionManager.touchSession] @@ -22,9 +23,10 @@ import curtains.touchEventInterceptors internal class PostHogTouchActivityIntegration( private val config: PostHogAndroidConfig, ) : PostHogIntegration { + private var ownsInstallation = false + private companion object { - @Volatile - private var integrationInstalled = false + private val integrationInstalled = AtomicBoolean(false) } private val touchInterceptor = @@ -53,11 +55,12 @@ internal class PostHogTouchActivityIntegration( } } + @Synchronized override fun install(postHog: PostHogInterface) { - if (integrationInstalled || !isSupported()) { + if (!isSupported() || !integrationInstalled.compareAndSet(false, true)) { return } - integrationInstalled = true + ownsInstallation = true try { Curtains.rootViews.forEach { view -> view.phoneWindow?.let { window -> @@ -72,7 +75,11 @@ internal class PostHogTouchActivityIntegration( } } + @Synchronized override fun uninstall() { + if (!ownsInstallation) { + return + } try { Curtains.onRootViewsChangedListeners -= onRootViewsChangedListener Curtains.rootViews.forEach { view -> @@ -83,7 +90,8 @@ internal class PostHogTouchActivityIntegration( } catch (e: Throwable) { config.logger.log("PostHogTouchActivityIntegration uninstall failed: $e.") } finally { - integrationInstalled = false + ownsInstallation = false + integrationInstalled.set(false) } } diff --git a/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt b/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt index ef35d54b..17352c62 100644 --- a/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt +++ b/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt @@ -101,6 +101,7 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean public class PostHogReplayIntegration( private val context: Context, @@ -191,6 +192,7 @@ public class PostHogReplayIntegration( private var postHog: PostHogInterface? = null private var replayQueue: PostHogReplayQueue? = null + private var ownsInstallation = false @Volatile private var replaySessionId: String? = null @@ -524,11 +526,12 @@ public class PostHogReplayIntegration( decorViews.remove(view) } + @Synchronized override fun install(postHog: PostHogInterface) { - if (integrationInstalled || !isSupported()) { + if (!isSupported() || !integrationInstalled.compareAndSet(false, true)) { return } - integrationInstalled = true + ownsInstallation = true this.postHog = postHog // Wire up as buffer delegate for the replay queue @@ -558,9 +561,12 @@ public class PostHogReplayIntegration( } } + @Synchronized override fun uninstall() { + if (!ownsInstallation) { + return + } try { - integrationInstalled = false this.postHog = null // Clear buffer delegate @@ -589,6 +595,9 @@ public class PostHogReplayIntegration( decorViews.clear() } catch (e: Throwable) { config.logger.log("Session Replay uninstall failed: $e.") + } finally { + ownsInstallation = false + integrationInstalled.set(false) } } @@ -2335,7 +2344,6 @@ public class PostHogReplayIntegration( const val ANDROID_COMPOSE_VIEW_CLASS_NAME: String = "androidx.compose.ui.platform.AndroidComposeView" const val ANDROID_COMPOSE_VIEW: String = "AndroidComposeView" - @Volatile - private var integrationInstalled = false + private val integrationInstalled = AtomicBoolean(false) } } diff --git a/posthog-android/src/main/java/com/posthog/android/replay/internal/PostHogLogCatIntegration.kt b/posthog-android/src/main/java/com/posthog/android/replay/internal/PostHogLogCatIntegration.kt index 3895da4b..ec0a02ff 100644 --- a/posthog-android/src/main/java/com/posthog/android/replay/internal/PostHogLogCatIntegration.kt +++ b/posthog-android/src/main/java/com/posthog/android/replay/internal/PostHogLogCatIntegration.kt @@ -9,6 +9,7 @@ import com.posthog.internal.replay.RRPluginEvent import com.posthog.internal.replay.capture import java.text.SimpleDateFormat import java.util.Locale +import java.util.concurrent.atomic.AtomicBoolean internal class PostHogLogCatIntegration(private val config: PostHogAndroidConfig) : PostHogIntegration { @Volatile @@ -20,22 +21,23 @@ internal class PostHogLogCatIntegration(private val config: PostHogAndroidConfig get() = postHog?.isSessionReplayActive() ?: false private var postHog: PostHogInterface? = null + private var ownsInstallation = false private companion object { - @Volatile - private var integrationInstalled = false + private val integrationInstalled = AtomicBoolean(false) } + @Synchronized override fun install(postHog: PostHogInterface) { this.postHog = postHog - if (integrationInstalled) { - return - } val captureLogcat = config.remoteConfigHolder?.isConsoleLogRecordingEnabled() ?: true if (!config.sessionReplayConfig.captureLogcat || !captureLogcat) { return } - integrationInstalled = true + if (!integrationInstalled.compareAndSet(false, true)) { + return + } + ownsInstallation = true val cmd = mutableListOf("logcat", "-v", "threadtime", "*:E") val sdf = SimpleDateFormat("MM-dd HH:mm:ss.mmm", Locale.ROOT) cmd.add("-T") @@ -107,11 +109,19 @@ internal class PostHogLogCatIntegration(private val config: PostHogAndroidConfig } @PostHogVisibleForTesting - internal fun isInstalled(): Boolean = integrationInstalled + internal fun isInstalled(): Boolean = integrationInstalled.get() + @Synchronized override fun uninstall() { - integrationInstalled = false - logcatInProgress = false - logcatThread?.interruptSafely() + if (!ownsInstallation) { + return + } + try { + logcatInProgress = false + logcatThread?.interruptSafely() + } finally { + ownsInstallation = false + integrationInstalled.set(false) + } } } diff --git a/posthog-android/src/test/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegrationTest.kt b/posthog-android/src/test/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegrationTest.kt index a9387521..ffee9621 100644 --- a/posthog-android/src/test/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegrationTest.kt +++ b/posthog-android/src/test/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegrationTest.kt @@ -13,7 +13,9 @@ import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.never +import org.mockito.kotlin.times import org.mockito.kotlin.verify +import java.util.concurrent.CountDownLatch import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -40,6 +42,55 @@ internal class PostHogActivityLifecycleCallbackIntegrationTest { PostHog.resetSharedInstance() } + @Test + fun `concurrent installs only register one lifecycle callback`() { + val threadCount = 32 + val integrations = List(threadCount) { getSut() } + val fake = createPostHogFake() + val ready = CountDownLatch(threadCount) + val start = CountDownLatch(1) + val threads = + integrations.map { integration -> + Thread { + ready.countDown() + start.await() + integration.install(fake) + }.apply { start() } + } + + ready.await() + start.countDown() + threads.forEach { it.join() } + + try { + verify(application, times(1)).registerActivityLifecycleCallbacks(any()) + } finally { + integrations.forEach { it.uninstall() } + } + } + + @Test + fun `uninstall from a non-owner keeps the winning installation`() { + val owner = getSut() + val nonOwner = getSut() + val laterInstance = getSut() + val fake = createPostHogFake() + + try { + owner.install(fake) + nonOwner.install(fake) + nonOwner.uninstall() + laterInstance.install(fake) + + verify(application, times(1)).registerActivityLifecycleCallbacks(any()) + verify(application, never()).unregisterActivityLifecycleCallbacks(any()) + } finally { + owner.uninstall() + nonOwner.uninstall() + laterInstance.uninstall() + } + } + @Test fun `install registers the lifecycle callback`() { val sut = getSut() @@ -56,7 +107,9 @@ internal class PostHogActivityLifecycleCallbackIntegrationTest { @Test fun `uninstall unregisters the lifecycle callback`() { val sut = getSut() + val fake = createPostHogFake() + sut.install(fake) sut.uninstall() verify(application).unregisterActivityLifecycleCallbacks(any()) diff --git a/posthog-android/src/test/java/com/posthog/android/replay/PostHogReplayIntegrationTest.kt b/posthog-android/src/test/java/com/posthog/android/replay/PostHogReplayIntegrationTest.kt index 294d750b..2b78605c 100644 --- a/posthog-android/src/test/java/com/posthog/android/replay/PostHogReplayIntegrationTest.kt +++ b/posthog-android/src/test/java/com/posthog/android/replay/PostHogReplayIntegrationTest.kt @@ -1156,6 +1156,7 @@ internal class PostHogReplayIntegrationTest { // modification — so an unsynchronized map can corrupt under this race (lost entries or an // infinite bucket-chain loop; the timeout catches the latter). val sut = getSut() + sut.install(mock()) val appContext = ApplicationProvider.getApplicationContext() val listener = mock() val probe = View(appContext) diff --git a/posthog/src/main/java/com/posthog/errortracking/PostHogErrorTrackingAutoCaptureIntegration.kt b/posthog/src/main/java/com/posthog/errortracking/PostHogErrorTrackingAutoCaptureIntegration.kt index 8e59687d..e201b086 100644 --- a/posthog/src/main/java/com/posthog/errortracking/PostHogErrorTrackingAutoCaptureIntegration.kt +++ b/posthog/src/main/java/com/posthog/errortracking/PostHogErrorTrackingAutoCaptureIntegration.kt @@ -5,12 +5,14 @@ import com.posthog.PostHogIntegration import com.posthog.PostHogInterface import com.posthog.internal.errortracking.PostHogThrowable import com.posthog.internal.errortracking.UncaughtExceptionHandlerAdapter +import java.util.concurrent.atomic.AtomicBoolean public class PostHogErrorTrackingAutoCaptureIntegration : PostHogIntegration, Thread.UncaughtExceptionHandler { private val config: PostHogConfig private val adapterExceptionHandler: UncaughtExceptionHandlerAdapter private var defaultExceptionHandler: Thread.UncaughtExceptionHandler? = null private var postHog: PostHogInterface? = null + private var ownsInstallation = false public constructor(config: PostHogConfig) { this.config = config @@ -23,14 +25,14 @@ public class PostHogErrorTrackingAutoCaptureIntegration : PostHogIntegration, Th } private companion object { - @Volatile - private var integrationInstalled = false + private val integrationInstalled = AtomicBoolean(false) } + @Synchronized override fun install(postHog: PostHogInterface) { this.postHog = postHog - if (integrationInstalled) { + if (integrationInstalled.get()) { return } @@ -54,18 +56,26 @@ public class PostHogErrorTrackingAutoCaptureIntegration : PostHogIntegration, Th } private fun installHandler() { + if (!integrationInstalled.compareAndSet(false, true)) { + return + } + ownsInstallation = true adapterExceptionHandler.setDefaultUncaughtExceptionHandler(this) - integrationInstalled = true config.logger.log("Exception autocapture is enabled.") } + @Synchronized override fun uninstall() { - if (!integrationInstalled) { + if (!ownsInstallation) { return } - adapterExceptionHandler.setDefaultUncaughtExceptionHandler(defaultExceptionHandler) - integrationInstalled = false - config.logger.log("Exception autocapture is disabled.") + try { + adapterExceptionHandler.setDefaultUncaughtExceptionHandler(defaultExceptionHandler) + config.logger.log("Exception autocapture is disabled.") + } finally { + ownsInstallation = false + integrationInstalled.set(false) + } } override fun onRemoteConfig(loaded: Boolean) { diff --git a/posthog/src/main/java/com/posthog/internal/PostHogSendCachedEventsIntegration.kt b/posthog/src/main/java/com/posthog/internal/PostHogSendCachedEventsIntegration.kt index 0bed41d5..009b7368 100644 --- a/posthog/src/main/java/com/posthog/internal/PostHogSendCachedEventsIntegration.kt +++ b/posthog/src/main/java/com/posthog/internal/PostHogSendCachedEventsIntegration.kt @@ -4,9 +4,11 @@ import com.posthog.PostHogConfig import com.posthog.PostHogEvent import com.posthog.PostHogIntegration import com.posthog.PostHogInterface +import com.posthog.PostHogVisibleForTesting import java.io.File import java.io.IOException import java.util.concurrent.ExecutorService +import java.util.concurrent.atomic.AtomicBoolean /** * The integration that sends all the cached legacy events, triggered once the SDK is setup @@ -19,16 +21,24 @@ internal class PostHogSendCachedEventsIntegration( private val api: PostHogApi, private val executor: ExecutorService, ) : PostHogIntegration { - private companion object { - @Volatile - private var integrationInstalled = false + private var ownsInstallation = false + + internal companion object { + private val integrationInstalled = AtomicBoolean(false) + + @PostHogVisibleForTesting + internal fun resetInstallationForTesting() { + integrationInstalled.set(false) + } } + @Synchronized override fun install(postHog: PostHogInterface) { - if (integrationInstalled) { + if (!integrationInstalled.compareAndSet(false, true)) { + executor.shutdown() return } - integrationInstalled = true + ownsInstallation = true executor.executeSafely { if (config.networkStatus?.isConnected() == false) { @@ -138,7 +148,12 @@ internal class PostHogSendCachedEventsIntegration( iterator.remove() } + @Synchronized override fun uninstall() { - integrationInstalled = false + if (!ownsInstallation) { + return + } + ownsInstallation = false + integrationInstalled.set(false) } } diff --git a/posthog/src/test/java/com/posthog/internal/PostHogSendCachedEventsIntegrationTest.kt b/posthog/src/test/java/com/posthog/internal/PostHogSendCachedEventsIntegrationTest.kt index 11867efa..530fa0e9 100644 --- a/posthog/src/test/java/com/posthog/internal/PostHogSendCachedEventsIntegrationTest.kt +++ b/posthog/src/test/java/com/posthog/internal/PostHogSendCachedEventsIntegrationTest.kt @@ -2,15 +2,22 @@ package com.posthog.internal import com.posthog.API_KEY import com.posthog.PostHogConfig +import com.posthog.PostHogInterface import com.posthog.shutdownAndAwaitTermination import com.posthog.vendor.uuid.TimeBasedEpochGenerator import org.junit.Rule import org.junit.rules.TemporaryFolder import org.mockito.Mockito.mock import java.io.File +import java.util.concurrent.AbstractExecutorService +import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger import kotlin.test.AfterTest +import kotlin.test.BeforeTest import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertFalse internal class PostHogSendCachedEventsIntegrationTest { @@ -36,8 +43,14 @@ internal class PostHogSendCachedEventsIntegrationTest { return PostHogSendCachedEventsIntegration(config, api, executor = executor) } + @BeforeTest + fun `set up`() { + PostHogSendCachedEventsIntegration.resetInstallationForTesting() + } + @AfterTest fun `set down`() { + PostHogSendCachedEventsIntegration.resetInstallationForTesting() tmpDir.root.deleteRecursively() } @@ -55,6 +68,61 @@ internal class PostHogSendCachedEventsIntegrationTest { return storagePrefix } + @Test + fun `concurrent installs only schedule one legacy flush`() { + val threadCount = 32 + val scheduledFlushes = AtomicInteger() + val shutdownExecutors = AtomicInteger() + val start = CountDownLatch(1) + val ready = CountDownLatch(threadCount) + val integrations = + List(threadCount) { + val config = PostHogConfig(API_KEY, host = "host") + val countingExecutor = + object : AbstractExecutorService() { + override fun execute(command: Runnable) { + scheduledFlushes.incrementAndGet() + } + + override fun shutdown() { + shutdownExecutors.incrementAndGet() + } + + override fun shutdownNow(): List = emptyList() + + override fun isShutdown(): Boolean = false + + override fun isTerminated(): Boolean = false + + override fun awaitTermination( + timeout: Long, + unit: TimeUnit, + ): Boolean = true + } + PostHogSendCachedEventsIntegration(config, PostHogApi(config), countingExecutor) + } + val postHog = mock() + val threads = + integrations.map { integration -> + Thread { + ready.countDown() + start.await() + integration.install(postHog) + }.apply { start() } + } + + ready.await() + start.countDown() + threads.forEach { it.join() } + + try { + assertEquals(1, scheduledFlushes.get()) + assertEquals(threadCount, shutdownExecutors.get()) + } finally { + integrations.forEach { it.uninstall() } + } + } + @Test fun `install bails out if not connected`() { val storagePrefix = writeFile(listOf(event)) diff --git a/posthog/src/test/java/com/posthog/internal/PostHogSendCachedLegacyEventsIntegrationTest.kt b/posthog/src/test/java/com/posthog/internal/PostHogSendCachedLegacyEventsIntegrationTest.kt index 68038735..7f562616 100644 --- a/posthog/src/test/java/com/posthog/internal/PostHogSendCachedLegacyEventsIntegrationTest.kt +++ b/posthog/src/test/java/com/posthog/internal/PostHogSendCachedLegacyEventsIntegrationTest.kt @@ -12,6 +12,7 @@ import org.mockito.Mockito.mock import java.io.File import java.util.concurrent.Executors import kotlin.test.AfterTest +import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -42,8 +43,14 @@ internal class PostHogSendCachedLegacyEventsIntegrationTest { return PostHogSendCachedEventsIntegration(config, api, executor = executor) } + @BeforeTest + fun `set up`() { + PostHogSendCachedEventsIntegration.resetInstallationForTesting() + } + @AfterTest fun `set down`() { + PostHogSendCachedEventsIntegration.resetInstallationForTesting() tmpDir.root.deleteRecursively() }