From 489bc31f9508e49b4aa16526778ffed144f7b4c2 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 27 Nov 2024 08:45:25 -0300 Subject: [PATCH 01/37] WIP --- .../LocalhostMySegmentsStorageContainer.java | 5 + .../synchronizer/RolloutCacheManager.java | 8 ++ .../RolloutCacheManagerConfig.java | 29 +++++ .../synchronizer/RolloutCacheManagerImpl.java | 98 +++++++++++++++++ .../storage/RolloutDefinitionsCache.java | 6 + .../storage/mysegments/MySegmentsStorage.java | 8 +- .../MySegmentsStorageContainer.java | 4 +- .../MySegmentsStorageContainerImpl.java | 9 ++ .../client/storage/splits/SplitsStorage.java | 5 +- .../synchronizer/RolloutCacheManagerTest.kt | 103 ++++++++++++++++++ 10 files changed, 265 insertions(+), 10 deletions(-) create mode 100644 src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java create mode 100644 src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java create mode 100644 src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java create mode 100644 src/main/java/io/split/android/client/storage/RolloutDefinitionsCache.java create mode 100644 src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt diff --git a/src/main/java/io/split/android/client/localhost/LocalhostMySegmentsStorageContainer.java b/src/main/java/io/split/android/client/localhost/LocalhostMySegmentsStorageContainer.java index 32f37c6f0..64304916b 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostMySegmentsStorageContainer.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostMySegmentsStorageContainer.java @@ -17,4 +17,9 @@ public MySegmentsStorage getStorageForKey(String matchingKey) { public long getUniqueAmount() { return mEmptyMySegmentsStorage.getAll().size(); } + + @Override + public void clear() { + // No-op + } } diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java new file mode 100644 index 000000000..372b376d2 --- /dev/null +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java @@ -0,0 +1,8 @@ +package io.split.android.client.service.synchronizer; + +import io.split.android.client.service.executor.SplitTaskExecutionListener; + +interface RolloutCacheManager { + + void validateCache(SplitTaskExecutionListener listener); +} diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java new file mode 100644 index 000000000..9664a0af5 --- /dev/null +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java @@ -0,0 +1,29 @@ +package io.split.android.client.service.synchronizer; + +import androidx.annotation.VisibleForTesting; + +import io.split.android.client.SplitClientConfig; + +class RolloutCacheManagerConfig { + + private final long mCacheExpirationInDays; + private final boolean mClearOnInit; + + @VisibleForTesting + RolloutCacheManagerConfig(long cacheExpirationInDays, boolean clearOnInit) { + mCacheExpirationInDays = cacheExpirationInDays; + mClearOnInit = clearOnInit; + } + + public static RolloutCacheManagerConfig from(SplitClientConfig splitClientConfig) { + return new RolloutCacheManagerConfig(splitClientConfig.cacheExpirationInSeconds(), false); // TODO + } + + public long getCacheExpirationInDays() { + return mCacheExpirationInDays; + } + + public boolean isClearOnInit() { + return mClearOnInit; + } +} diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java new file mode 100644 index 000000000..a5205da4a --- /dev/null +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java @@ -0,0 +1,98 @@ +package io.split.android.client.service.synchronizer; + +import static io.split.android.client.utils.Utils.checkNotNull; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.annotation.WorkerThread; + +import java.util.concurrent.TimeUnit; + +import io.split.android.client.SplitClientConfig; +import io.split.android.client.service.executor.SplitTask; +import io.split.android.client.service.executor.SplitTaskExecutionInfo; +import io.split.android.client.service.executor.SplitTaskExecutionListener; +import io.split.android.client.service.executor.SplitTaskExecutor; +import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.storage.RolloutDefinitionsCache; +import io.split.android.client.storage.common.SplitStorageContainer; +import io.split.android.client.storage.general.GeneralInfoStorage; +import io.split.android.client.utils.logger.Logger; + +public class RolloutCacheManagerImpl implements RolloutCacheManager, SplitTask { + + public static final int MIN_CACHE_CLEAR_DAYS = 1; + @NonNull + private final GeneralInfoStorage mGeneralInfoStorage; + @NonNull + private final SplitTaskExecutor mTaskExecutor; + + @NonNull + private final RolloutCacheManagerConfig mConfig; + + @NonNull + private final RolloutDefinitionsCache[] mStorages; + + public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitTaskExecutor splitTaskExecutor, @NonNull SplitStorageContainer storageContainer) { + this(storageContainer.getGeneralInfoStorage(), + RolloutCacheManagerConfig.from(splitClientConfig), + splitTaskExecutor, + storageContainer.getSplitsStorage(), + storageContainer.getMySegmentsStorageContainer(), + storageContainer.getMyLargeSegmentsStorageContainer()); + } + + @VisibleForTesting + RolloutCacheManagerImpl(@NonNull GeneralInfoStorage generalInfoStorage, @NonNull RolloutCacheManagerConfig config, @NonNull SplitTaskExecutor splitTaskExecutor, @NonNull RolloutDefinitionsCache... storages) { + mGeneralInfoStorage = checkNotNull(generalInfoStorage); + mStorages = checkNotNull(storages); + mConfig = checkNotNull(config); + mTaskExecutor = checkNotNull(splitTaskExecutor); + } + + @WorkerThread + @Override + public void validateCache(SplitTaskExecutionListener listener) { + mTaskExecutor.submit(this, listener); + } + + @NonNull + @Override + public SplitTaskExecutionInfo execute() { + try { + validate(); + } catch (Exception e) { + Logger.e("Error occurred validating cache: " + e.getMessage()); + + return SplitTaskExecutionInfo.error(SplitTaskType.GENERIC_TASK); + } + return SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK); + } + + private void validate() { + // calculate elapsed time since last update + long lastUpdateTimestamp = mGeneralInfoStorage.getSplitsUpdateTimestamp(); + long daysSinceLastUpdate = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastUpdateTimestamp); + + if (daysSinceLastUpdate > mConfig.getCacheExpirationInDays()) { + clear(); + } else if (mConfig.isClearOnInit()) { + long lastCacheClearTimestamp = mGeneralInfoStorage.getRolloutCacheLastClearTimestamp(); + long daysSinceCacheClear = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastCacheClearTimestamp); + + // don't clear too soon + if (daysSinceCacheClear > MIN_CACHE_CLEAR_DAYS) { + clear(); + } + } + } + + private void clear() { + if (mStorages.length > 0) { + for (RolloutDefinitionsCache storage : mStorages) { + storage.clear(); + } + mGeneralInfoStorage.setRolloutCacheLastClearTimestamp(System.currentTimeMillis()); + } + } +} diff --git a/src/main/java/io/split/android/client/storage/RolloutDefinitionsCache.java b/src/main/java/io/split/android/client/storage/RolloutDefinitionsCache.java new file mode 100644 index 000000000..912fafa97 --- /dev/null +++ b/src/main/java/io/split/android/client/storage/RolloutDefinitionsCache.java @@ -0,0 +1,6 @@ +package io.split.android.client.storage; + +public interface RolloutDefinitionsCache { + + void clear(); +} diff --git a/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorage.java b/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorage.java index 351a82d79..2675fc5f1 100644 --- a/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorage.java +++ b/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorage.java @@ -1,12 +1,11 @@ package io.split.android.client.storage.mysegments; -import androidx.annotation.VisibleForTesting; - import java.util.Set; import io.split.android.client.dtos.SegmentsChange; +import io.split.android.client.storage.RolloutDefinitionsCache; -public interface MySegmentsStorage { +public interface MySegmentsStorage extends RolloutDefinitionsCache { void loadLocal(); Set getAll(); @@ -14,7 +13,4 @@ public interface MySegmentsStorage { void set(SegmentsChange segmentsChange); long getChangeNumber(); - - @VisibleForTesting - void clear(); } diff --git a/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorageContainer.java b/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorageContainer.java index 98e656071..9feeded60 100644 --- a/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorageContainer.java +++ b/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorageContainer.java @@ -1,6 +1,8 @@ package io.split.android.client.storage.mysegments; -public interface MySegmentsStorageContainer { +import io.split.android.client.storage.RolloutDefinitionsCache; + +public interface MySegmentsStorageContainer extends RolloutDefinitionsCache { MySegmentsStorage getStorageForKey(String matchingKey); diff --git a/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorageContainerImpl.java b/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorageContainerImpl.java index 8a1ee9187..0a8f51f45 100644 --- a/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorageContainerImpl.java +++ b/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorageContainerImpl.java @@ -41,4 +41,13 @@ public long getUniqueAmount() { return segments.size(); } + + @Override + public void clear() { + synchronized (lock) { + for (MySegmentsStorage mySegmentsStorage : mStorageMap.values()) { + mySegmentsStorage.clear(); + } + } + } } diff --git a/src/main/java/io/split/android/client/storage/splits/SplitsStorage.java b/src/main/java/io/split/android/client/storage/splits/SplitsStorage.java index 70d228726..81ff4e5a5 100644 --- a/src/main/java/io/split/android/client/storage/splits/SplitsStorage.java +++ b/src/main/java/io/split/android/client/storage/splits/SplitsStorage.java @@ -9,8 +9,9 @@ import java.util.Set; import io.split.android.client.dtos.Split; +import io.split.android.client.storage.RolloutDefinitionsCache; -public interface SplitsStorage { +public interface SplitsStorage extends RolloutDefinitionsCache { void loadLocal(); Split get(@NonNull String name); @@ -38,8 +39,6 @@ public interface SplitsStorage { void updateFlagsSpec(String flagsSpec); - void clear(); - @NonNull Set getNamesByFlagSets(Collection flagSets); } diff --git a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt new file mode 100644 index 000000000..2521efb7a --- /dev/null +++ b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt @@ -0,0 +1,103 @@ +package io.split.android.client.service.synchronizer + +import io.split.android.client.service.executor.SplitTaskExecutionListener +import io.split.android.client.service.executor.SplitTaskExecutor +import io.split.android.client.storage.RolloutDefinitionsCache +import io.split.android.client.storage.general.GeneralInfoStorage +import io.split.android.fake.SplitTaskExecutorStub +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import java.util.concurrent.TimeUnit + +class RolloutCacheManagerTest { + + private lateinit var mRolloutCacheManager: RolloutCacheManager + private lateinit var mGeneralInfoStorage: GeneralInfoStorage + private lateinit var mSplitTaskExecutor: SplitTaskExecutor + private lateinit var mSplitsCache: RolloutDefinitionsCache + private lateinit var mSegmentsCache: RolloutDefinitionsCache + + @Before + fun setup() { + mGeneralInfoStorage = mock(GeneralInfoStorage::class.java) + mSplitTaskExecutor = SplitTaskExecutorStub() + mSplitsCache = mock(RolloutDefinitionsCache::class.java) + mSegmentsCache = mock(RolloutDefinitionsCache::class.java) + } + + @Test + fun `validateCache calls listener`() { + mRolloutCacheManager = getCacheManager(10L, false) + + val listener = mock(SplitTaskExecutionListener::class.java) + mRolloutCacheManager.validateCache(listener) + + verify(listener).taskExecuted(any()) + } + + @Test + fun `validateCache calls clear on storages when expiration is surpassed`() { + val mockedTimestamp = createMockedTimestamp(10L) + `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) + mRolloutCacheManager = getCacheManager(9L, false) + + mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) + + verify(mSplitsCache).clear() + verify(mSegmentsCache).clear() + } + + @Test + fun `validateCache does not call clear on storages when expiration is not surpassed and clearOnInit is false`() { + val mockedTimestamp = createMockedTimestamp(1L) + `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) + mRolloutCacheManager = getCacheManager(10L, false) + + mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) + + verify(mSplitsCache, times(0)).clear() + verify(mSegmentsCache, times(0)).clear() + } + + @Test + fun `validateCache calls clear on storages when expiration is not surpassed and clearOnInit is true`() { + val mockedTimestamp = createMockedTimestamp(1L) + `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) + mRolloutCacheManager = getCacheManager(10L, true) + + mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) + + verify(mSplitsCache).clear() + verify(mSegmentsCache).clear() + } + + @Test + fun `validateCache calls clear on storage only once when executed consecutively`() { + val mockedTimestamp = createMockedTimestamp(1L) + `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) + `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) + mRolloutCacheManager = getCacheManager(10L, true) + + mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) + mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) + + verify(mSplitsCache, times(1)).clear() + verify(mSegmentsCache, times(1)).clear() + } + + private fun getCacheManager(expiration: Long, clearOnInit: Boolean): RolloutCacheManager { + return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheManagerConfig(expiration, clearOnInit), mSplitTaskExecutor, mSplitsCache, mSegmentsCache) + } + + private fun createMockedTimestamp(period: Long): Long { + val currentTimeMillis = System.currentTimeMillis() + val mockedTimestamp = + TimeUnit.DAYS.toMillis(TimeUnit.MILLISECONDS.toDays(currentTimeMillis) - period) + return mockedTimestamp + } +} From a5f7c5ee4b399a4f745addb40cb35c49288af4b4 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 27 Nov 2024 10:05:49 -0300 Subject: [PATCH 02/37] New tests --- .../android/client/SplitClientConfig.java | 4 ++ .../RolloutCacheManagerConfig.java | 2 +- .../synchronizer/RolloutCacheManagerImpl.java | 24 +++++++---- .../synchronizer/RolloutCacheManagerTest.kt | 42 +++++++++++++++++++ 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitClientConfig.java b/src/main/java/io/split/android/client/SplitClientConfig.java index 15872b80b..c2eff3e9b 100644 --- a/src/main/java/io/split/android/client/SplitClientConfig.java +++ b/src/main/java/io/split/android/client/SplitClientConfig.java @@ -486,6 +486,10 @@ public long impressionsDedupeTimeInterval() { return mImpressionsDedupeTimeInterval; } + public boolean clearOnInit() { + return false; // TODO: to be implemented in the future + } + public static final class Builder { static final int PROXY_PORT_DEFAULT = 80; diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java index 9664a0af5..f2e330938 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java @@ -16,7 +16,7 @@ class RolloutCacheManagerConfig { } public static RolloutCacheManagerConfig from(SplitClientConfig splitClientConfig) { - return new RolloutCacheManagerConfig(splitClientConfig.cacheExpirationInSeconds(), false); // TODO + return new RolloutCacheManagerConfig(splitClientConfig.cacheExpirationInSeconds(), splitClientConfig.clearOnInit()); } public long getCacheExpirationInDays() { diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java index a5205da4a..16437c2ac 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java @@ -60,7 +60,10 @@ public void validateCache(SplitTaskExecutionListener listener) { @Override public SplitTaskExecutionInfo execute() { try { - validate(); + boolean expired = validateExpiration(); + if (expired) { + clear(); + } } catch (Exception e) { Logger.e("Error occurred validating cache: " + e.getMessage()); @@ -69,30 +72,33 @@ public SplitTaskExecutionInfo execute() { return SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK); } - private void validate() { + private boolean validateExpiration() { // calculate elapsed time since last update long lastUpdateTimestamp = mGeneralInfoStorage.getSplitsUpdateTimestamp(); long daysSinceLastUpdate = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastUpdateTimestamp); if (daysSinceLastUpdate > mConfig.getCacheExpirationInDays()) { - clear(); + Logger.v("Clearing rollout definitions cache due to expiration"); + return true; } else if (mConfig.isClearOnInit()) { long lastCacheClearTimestamp = mGeneralInfoStorage.getRolloutCacheLastClearTimestamp(); long daysSinceCacheClear = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastCacheClearTimestamp); // don't clear too soon if (daysSinceCacheClear > MIN_CACHE_CLEAR_DAYS) { - clear(); + Logger.v("Forcing rollout definitions cache clear"); + return true; } } + + return false; } private void clear() { - if (mStorages.length > 0) { - for (RolloutDefinitionsCache storage : mStorages) { - storage.clear(); - } - mGeneralInfoStorage.setRolloutCacheLastClearTimestamp(System.currentTimeMillis()); + for (RolloutDefinitionsCache storage : mStorages) { + storage.clear(); } + mGeneralInfoStorage.setRolloutCacheLastClearTimestamp(System.currentTimeMillis()); + Logger.v("Rollout definitions cache cleared"); } } diff --git a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt index 2521efb7a..41dcc6422 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt +++ b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt @@ -8,6 +8,9 @@ import io.split.android.fake.SplitTaskExecutorStub import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.ArgumentMatchers.argThat +import org.mockito.Mockito.longThat import org.mockito.Mockito.mock import org.mockito.Mockito.times import org.mockito.Mockito.verify @@ -90,6 +93,45 @@ class RolloutCacheManagerTest { verify(mSegmentsCache, times(1)).clear() } + @Test + fun `exception during clear still calls listener`() { + val mockedTimestamp = createMockedTimestamp(1L) + `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) + `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) + mRolloutCacheManager = getCacheManager(10L, true) + + val listener = mock(SplitTaskExecutionListener::class.java) + `when`(mSplitsCache.clear()).thenThrow(RuntimeException("Exception during clear")) + + mRolloutCacheManager.validateCache(listener) + + verify(listener).taskExecuted(any()) + } + + @Test + fun `validateCache updates last clear timestamp when storages are cleared`() { + val mockedTimestamp = createMockedTimestamp(1L) + `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) + `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) + mRolloutCacheManager = getCacheManager(10L, true) + + mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) + + verify(mGeneralInfoStorage).setRolloutCacheLastClearTimestamp(longThat { it > 0 }) + } + + @Test + fun `validateCache does not update last clear timestamp when storages are not cleared`() { + val mockedTimestamp = createMockedTimestamp(1L) + `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) + `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) + mRolloutCacheManager = getCacheManager(10L, false) + + mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) + + verify(mGeneralInfoStorage, times(0)).setRolloutCacheLastClearTimestamp(anyLong()) + } + private fun getCacheManager(expiration: Long, clearOnInit: Boolean): RolloutCacheManager { return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheManagerConfig(expiration, clearOnInit), mSplitTaskExecutor, mSplitsCache, mSegmentsCache) } From a416b0ce50bcbe65f9680b246ffa51df6a17db0b Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 27 Nov 2024 10:57:33 -0300 Subject: [PATCH 03/37] Cleanup --- .../synchronizer/RolloutCacheManagerConfig.java | 6 +++--- .../service/synchronizer/RolloutCacheManagerImpl.java | 11 ++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java index f2e330938..1de13b297 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java @@ -15,15 +15,15 @@ class RolloutCacheManagerConfig { mClearOnInit = clearOnInit; } - public static RolloutCacheManagerConfig from(SplitClientConfig splitClientConfig) { + static RolloutCacheManagerConfig from(SplitClientConfig splitClientConfig) { return new RolloutCacheManagerConfig(splitClientConfig.cacheExpirationInSeconds(), splitClientConfig.clearOnInit()); } - public long getCacheExpirationInDays() { + long getCacheExpirationInDays() { return mCacheExpirationInDays; } - public boolean isClearOnInit() { + boolean isClearOnInit() { return mClearOnInit; } } diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java index 16437c2ac..254ace2cc 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java @@ -4,7 +4,6 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import androidx.annotation.WorkerThread; import java.util.concurrent.TimeUnit; @@ -21,15 +20,14 @@ public class RolloutCacheManagerImpl implements RolloutCacheManager, SplitTask { - public static final int MIN_CACHE_CLEAR_DAYS = 1; + public static final int MIN_CACHE_CLEAR_DAYS = 1; // TODO + @NonNull private final GeneralInfoStorage mGeneralInfoStorage; - @NonNull - private final SplitTaskExecutor mTaskExecutor; - @NonNull private final RolloutCacheManagerConfig mConfig; - + @NonNull + private final SplitTaskExecutor mTaskExecutor; @NonNull private final RolloutDefinitionsCache[] mStorages; @@ -50,7 +48,6 @@ public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @No mTaskExecutor = checkNotNull(splitTaskExecutor); } - @WorkerThread @Override public void validateCache(SplitTaskExecutionListener listener) { mTaskExecutor.submit(this, listener); From f5373dc281453fe5262ff0527d4103686cd09b06 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 29 Nov 2024 15:46:38 -0300 Subject: [PATCH 04/37] WIP integration --- .../android/client/SplitFactoryHelper.java | 18 +------- .../android/client/SplitFactoryImpl.java | 43 ++++++++++--------- .../storage/cipher/ApplyCipherTask.java | 3 +- .../android/client/SplitFactoryHelperTest.kt | 16 ------- 4 files changed, 25 insertions(+), 55 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 3757b633c..6889fcc79 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -27,7 +27,6 @@ import io.split.android.client.network.SplitHttpHeadersBuilder; import io.split.android.client.service.ServiceFactory; import io.split.android.client.service.SplitApiFacade; -import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskFactory; import io.split.android.client.service.http.mysegments.MySegmentsFetcherFactory; @@ -70,7 +69,6 @@ import io.split.android.client.shared.ClientComponentsRegisterImpl; import io.split.android.client.shared.UserConsent; import io.split.android.client.storage.attributes.PersistentAttributesStorage; -import io.split.android.client.storage.cipher.EncryptionMigrationTask; import io.split.android.client.storage.cipher.SplitCipher; import io.split.android.client.storage.cipher.SplitCipherFactory; import io.split.android.client.storage.cipher.SplitEncryptionLevel; @@ -399,21 +397,9 @@ public ProcessStrategy getImpressionStrategy(SplitTaskExecutor splitTaskExecutor .getStrategy(config.impressionsMode()); } - SplitCipher migrateEncryption(String apiKey, - SplitRoomDatabase splitDatabase, - SplitTaskExecutor splitTaskExecutor, - final boolean encryptionEnabled, - SplitTaskExecutionListener executionListener) { - - SplitCipher toCipher = SplitCipherFactory.create(apiKey, encryptionEnabled ? SplitEncryptionLevel.AES_128_CBC : + @Nullable SplitCipher getCipher(String apiKey, boolean encryptionEnabled) { + return SplitCipherFactory.create(apiKey, encryptionEnabled ? SplitEncryptionLevel.AES_128_CBC : SplitEncryptionLevel.NONE); - splitTaskExecutor.submit(new EncryptionMigrationTask(apiKey, - splitDatabase, - encryptionEnabled, - toCipher), - executionListener); - - return toCipher; } @Nullable diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index b5c0252e3..fb4cfaabe 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -29,8 +29,6 @@ import io.split.android.client.network.HttpClientImpl; import io.split.android.client.service.SplitApiFacade; import io.split.android.client.service.executor.SplitSingleThreadTaskExecutor; -import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskExecutorImpl; import io.split.android.client.service.executor.SplitTaskFactory; @@ -38,6 +36,7 @@ import io.split.android.client.service.impressions.ImpressionManager; import io.split.android.client.service.impressions.StrategyImpressionManager; import io.split.android.client.service.sseclient.sseclient.StreamingComponents; +import io.split.android.client.service.synchronizer.RolloutCacheManagerImpl; import io.split.android.client.service.synchronizer.SyncManager; import io.split.android.client.service.synchronizer.Synchronizer; import io.split.android.client.service.synchronizer.SynchronizerImpl; @@ -49,6 +48,7 @@ import io.split.android.client.shared.SplitClientContainer; import io.split.android.client.shared.SplitClientContainerImpl; import io.split.android.client.shared.UserConsent; +import io.split.android.client.storage.cipher.EncryptionMigrationTask; import io.split.android.client.storage.cipher.SplitCipher; import io.split.android.client.storage.common.SplitStorageContainer; import io.split.android.client.storage.db.SplitRoomDatabase; @@ -84,9 +84,6 @@ public class SplitFactoryImpl implements SplitFactory { private final SplitClientContainer mClientContainer; private final UserConsentManager mUserConsentManager; - @SuppressWarnings("FieldCanBeLocal") // keeping the reference on purpose - private final SplitTaskExecutionListener mMigrationExecutionListener; - public SplitFactoryImpl(@NonNull String apiToken, @NonNull Key key, @NonNull SplitClientConfig config, @NonNull Context context) throws URISyntaxException { this(apiToken, key, config, context, @@ -163,20 +160,11 @@ private SplitFactoryImpl(@NonNull String apiToken, @NonNull Key key, @NonNull Sp defaultHttpClient.addStreamingHeaders(factoryHelper.buildStreamingHeaders(apiToken)); SplitTaskExecutor splitTaskExecutor = new SplitTaskExecutorImpl(); + splitTaskExecutor.pause(); EventsManagerCoordinator mEventsManagerCoordinator = new EventsManagerCoordinator(); - mMigrationExecutionListener = new SplitTaskExecutionListener() { - @Override - public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { - mEventsManagerCoordinator.notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE); - } - }; - SplitCipher splitCipher = factoryHelper.migrateEncryption(mApiKey, - splitDatabase, - splitTaskExecutor, - config.encryptionEnabled(), - mMigrationExecutionListener); + SplitCipher splitCipher = factoryHelper.getCipher(apiToken, config.encryptionEnabled()); ScheduledThreadPoolExecutor impressionsObserverExecutor = new ScheduledThreadPoolExecutor(1, new ThreadPoolExecutor.CallerRunsPolicy()); @@ -200,6 +188,7 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { cleanUpDabase(splitTaskExecutor, splitTaskFactory); WorkManagerWrapper workManagerWrapper = factoryHelper.buildWorkManagerWrapper(context, config, apiToken, databaseName, filters); SplitSingleThreadTaskExecutor splitSingleThreadTaskExecutor = new SplitSingleThreadTaskExecutor(); + splitSingleThreadTaskExecutor.pause(); ImpressionManager impressionManager = new StrategyImpressionManager(factoryHelper.getImpressionStrategy(splitTaskExecutor, splitTaskFactory, mStorageContainer, config)); final RetryBackoffCounterTimerFactory retryBackoffCounterTimerFactory = new RetryBackoffCounterTimerFactory(); @@ -254,8 +243,6 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { mLifecycleManager = testLifecycleManager; } - mLifecycleManager.register(mSyncManager); - ExecutorService impressionsLoggingTaskExecutor = factoryHelper.getImpressionsLoggingTaskExecutor(); final ImpressionListener splitImpressionListener = new SyncImpressionListener(mSyncManager, impressionsLoggingTaskExecutor); @@ -335,14 +322,28 @@ public void run() { mStorageContainer.getSplitsStorage(), new SplitValidatorImpl(), mSplitParser); - mSyncManager.start(); + Runnable initializer = new Runnable() { + @Override + public void run() { + Logger.v("Running initialization thread"); + new RolloutCacheManagerImpl(config, splitTaskExecutor, mStorageContainer).execute(); + new EncryptionMigrationTask(apiToken, splitDatabase, config.encryptionEnabled(), splitCipher).execute(); + mEventsManagerCoordinator.notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE); + splitTaskExecutor.resume(); + splitSingleThreadTaskExecutor.resume(); + mSyncManager.start(); + mLifecycleManager.register(mSyncManager); + + Logger.i("Android SDK initialized!"); + } + }; + new Thread(initializer).start(); + if (config.shouldRecordTelemetry()) { int activeFactoriesCount = mFactoryMonitor.count(mApiKey); mStorageContainer.getTelemetryStorage().recordActiveFactories(activeFactoriesCount); mStorageContainer.getTelemetryStorage().recordRedundantFactories(activeFactoriesCount - 1); } - - Logger.i("Android SDK initialized!"); } private static String getFlagsSpec(@Nullable TestingConfig testingConfig) { diff --git a/src/main/java/io/split/android/client/storage/cipher/ApplyCipherTask.java b/src/main/java/io/split/android/client/storage/cipher/ApplyCipherTask.java index adb47d63a..e1e5928a1 100644 --- a/src/main/java/io/split/android/client/storage/cipher/ApplyCipherTask.java +++ b/src/main/java/io/split/android/client/storage/cipher/ApplyCipherTask.java @@ -49,6 +49,7 @@ public SplitTaskExecutionInfo execute() { mSplitDatabase.runInTransaction(new Runnable() { @Override public void run() { + updateAttributes(mSplitDatabase.attributesDao()); updateSplits(mSplitDatabase.splitDao()); updateSegments(mSplitDatabase.mySegmentDao()); updateLargeSegments(mSplitDatabase.myLargeSegmentDao()); @@ -56,7 +57,6 @@ public void run() { updateEvents(mSplitDatabase.eventDao()); updateImpressionsCount(mSplitDatabase.impressionsCountDao()); updateUniqueKeys(mSplitDatabase.uniqueKeysDao()); - updateAttributes(mSplitDatabase.attributesDao()); } }); @@ -87,7 +87,6 @@ private void updateAttributes(AttributesDao attributesDao) { private void updateUniqueKeys(UniqueKeysDao uniqueKeysDao) { List items = uniqueKeysDao.getAll(); - for (UniqueKeyEntity item : items) { String fromUserKey = mFromCipher.decrypt(item.getUserKey()); String fromFeatureList = mFromCipher.decrypt(item.getFeatureList()); diff --git a/src/test/java/io/split/android/client/SplitFactoryHelperTest.kt b/src/test/java/io/split/android/client/SplitFactoryHelperTest.kt index 7c123c8dd..acda1af2a 100644 --- a/src/test/java/io/split/android/client/SplitFactoryHelperTest.kt +++ b/src/test/java/io/split/android/client/SplitFactoryHelperTest.kt @@ -46,22 +46,6 @@ class SplitFactoryHelperTest { mocks.close() } - @Test - fun migrateEncryption() { - - helper.migrateEncryption( - "abcdedfghijklmnopqrstuvwxyz", - splitRoomDatabase, - splitTaskExecutor, - true, - taskListener, - ) - - verify(splitTaskExecutor).submit( - argThat { it is EncryptionMigrationTask }, - argThat { it?.equals(taskListener) == true }) - } - @Test fun generateDatabaseNameWithoutPrefixAndKeyLongerThan4() { val path = mock(File::class.java) From 4d0b415d9175be9852d8783473c8048e12ad0ff1 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 29 Nov 2024 18:57:17 -0300 Subject: [PATCH 05/37] Tests modifications --- .../database/DatabaseInitializationTest.java | 5 ++++ .../android/client/SplitFactoryImpl.java | 25 +++++++++++-------- .../synchronizer/RolloutCacheManagerImpl.java | 20 +++++++++------ .../synchronizer/RolloutCacheManagerTest.kt | 7 +++--- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/androidTest/java/tests/database/DatabaseInitializationTest.java b/src/androidTest/java/tests/database/DatabaseInitializationTest.java index 440dde5f5..a4201e1a9 100644 --- a/src/androidTest/java/tests/database/DatabaseInitializationTest.java +++ b/src/androidTest/java/tests/database/DatabaseInitializationTest.java @@ -182,6 +182,11 @@ public void usingNullPrefixResultsInIgnoredPrefix() { private static String[] getDbList(Context context) { // remove -journal dbs since we're not interested in them + try { + Thread.sleep(500); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } return Arrays.stream(context.databaseList()).filter(db -> !db.endsWith("-journal")).toArray(String[]::new); } } diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index fb4cfaabe..21524d2c2 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -29,6 +29,8 @@ import io.split.android.client.network.HttpClientImpl; import io.split.android.client.service.SplitApiFacade; import io.split.android.client.service.executor.SplitSingleThreadTaskExecutor; +import io.split.android.client.service.executor.SplitTaskExecutionInfo; +import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskExecutorImpl; import io.split.android.client.service.executor.SplitTaskFactory; @@ -153,7 +155,6 @@ private SplitFactoryImpl(@NonNull String apiToken, @NonNull Key key, @NonNull Sp } else { splitDatabase = testDatabase; Logger.d("Using test database"); - System.out.println("USING TEST DB: " + testDatabase); } defaultHttpClient.addHeaders(factoryHelper.buildHeaders(config, apiToken)); @@ -326,15 +327,19 @@ public void run() { @Override public void run() { Logger.v("Running initialization thread"); - new RolloutCacheManagerImpl(config, splitTaskExecutor, mStorageContainer).execute(); - new EncryptionMigrationTask(apiToken, splitDatabase, config.encryptionEnabled(), splitCipher).execute(); - mEventsManagerCoordinator.notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE); - splitTaskExecutor.resume(); - splitSingleThreadTaskExecutor.resume(); - mSyncManager.start(); - mLifecycleManager.register(mSyncManager); - - Logger.i("Android SDK initialized!"); + RolloutCacheManagerImpl rolloutCacheManager = new RolloutCacheManagerImpl(config, mStorageContainer, new EncryptionMigrationTask(apiToken, splitDatabase, config.encryptionEnabled(), splitCipher)); + rolloutCacheManager.validateCache(new SplitTaskExecutionListener() { + @Override + public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { + mEventsManagerCoordinator.notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE); + splitTaskExecutor.resume(); + splitSingleThreadTaskExecutor.resume(); + mSyncManager.start(); + mLifecycleManager.register(mSyncManager); + + Logger.i("Android SDK initialized!"); + } + }); } }; new Thread(initializer).start(); diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java index 254ace2cc..3e5b00b08 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java @@ -4,6 +4,7 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import androidx.annotation.WorkerThread; import java.util.concurrent.TimeUnit; @@ -11,9 +12,9 @@ import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionListener; -import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskType; import io.split.android.client.storage.RolloutDefinitionsCache; +import io.split.android.client.storage.cipher.EncryptionMigrationTask; import io.split.android.client.storage.common.SplitStorageContainer; import io.split.android.client.storage.general.GeneralInfoStorage; import io.split.android.client.utils.logger.Logger; @@ -27,30 +28,33 @@ public class RolloutCacheManagerImpl implements RolloutCacheManager, SplitTask { @NonNull private final RolloutCacheManagerConfig mConfig; @NonNull - private final SplitTaskExecutor mTaskExecutor; - @NonNull private final RolloutDefinitionsCache[] mStorages; + @NonNull + private final EncryptionMigrationTask mEncryptionMigrationTask; - public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitTaskExecutor splitTaskExecutor, @NonNull SplitStorageContainer storageContainer) { + public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitStorageContainer storageContainer, @NonNull EncryptionMigrationTask encryptionMigrationTask) { this(storageContainer.getGeneralInfoStorage(), RolloutCacheManagerConfig.from(splitClientConfig), - splitTaskExecutor, + encryptionMigrationTask, storageContainer.getSplitsStorage(), storageContainer.getMySegmentsStorageContainer(), storageContainer.getMyLargeSegmentsStorageContainer()); } @VisibleForTesting - RolloutCacheManagerImpl(@NonNull GeneralInfoStorage generalInfoStorage, @NonNull RolloutCacheManagerConfig config, @NonNull SplitTaskExecutor splitTaskExecutor, @NonNull RolloutDefinitionsCache... storages) { + RolloutCacheManagerImpl(@NonNull GeneralInfoStorage generalInfoStorage, @NonNull RolloutCacheManagerConfig config, @NonNull EncryptionMigrationTask encryptionMigrationTask, @NonNull RolloutDefinitionsCache... storages) { mGeneralInfoStorage = checkNotNull(generalInfoStorage); + mEncryptionMigrationTask = checkNotNull(encryptionMigrationTask); mStorages = checkNotNull(storages); mConfig = checkNotNull(config); - mTaskExecutor = checkNotNull(splitTaskExecutor); } + @WorkerThread @Override public void validateCache(SplitTaskExecutionListener listener) { - mTaskExecutor.submit(this, listener); + execute(); + mEncryptionMigrationTask.execute(); + listener.taskExecuted(SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK)); } @NonNull diff --git a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt index 41dcc6422..7bb93671b 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt +++ b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt @@ -3,6 +3,7 @@ package io.split.android.client.service.synchronizer import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.service.executor.SplitTaskExecutor import io.split.android.client.storage.RolloutDefinitionsCache +import io.split.android.client.storage.cipher.EncryptionMigrationTask import io.split.android.client.storage.general.GeneralInfoStorage import io.split.android.fake.SplitTaskExecutorStub import org.junit.Before @@ -21,14 +22,14 @@ class RolloutCacheManagerTest { private lateinit var mRolloutCacheManager: RolloutCacheManager private lateinit var mGeneralInfoStorage: GeneralInfoStorage - private lateinit var mSplitTaskExecutor: SplitTaskExecutor private lateinit var mSplitsCache: RolloutDefinitionsCache private lateinit var mSegmentsCache: RolloutDefinitionsCache + private lateinit var mEncryptionMigrationTask: EncryptionMigrationTask @Before fun setup() { mGeneralInfoStorage = mock(GeneralInfoStorage::class.java) - mSplitTaskExecutor = SplitTaskExecutorStub() + mEncryptionMigrationTask = mock(EncryptionMigrationTask::class.java); mSplitsCache = mock(RolloutDefinitionsCache::class.java) mSegmentsCache = mock(RolloutDefinitionsCache::class.java) } @@ -133,7 +134,7 @@ class RolloutCacheManagerTest { } private fun getCacheManager(expiration: Long, clearOnInit: Boolean): RolloutCacheManager { - return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheManagerConfig(expiration, clearOnInit), mSplitTaskExecutor, mSplitsCache, mSegmentsCache) + return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheManagerConfig(expiration, clearOnInit), mEncryptionMigrationTask, mSplitsCache, mSegmentsCache) } private fun createMockedTimestamp(period: Long): Long { From 428bb5b4a4145d19f74ff7ed2a9f71168e7952df Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Dec 2024 13:07:15 -0300 Subject: [PATCH 06/37] WIP --- .../android/client/SplitFactoryImpl.java | 11 ++++--- .../service/executor/SplitTaskFactory.java | 5 +++ .../executor/SplitTaskFactoryImpl.java | 8 +++++ .../synchronizer/RolloutCacheManagerImpl.java | 31 ++++++++++++++++--- .../synchronizer/RolloutCacheManagerTest.kt | 28 ++++++++++++++--- 5 files changed, 69 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index 21524d2c2..b401b36a0 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -50,7 +50,6 @@ import io.split.android.client.shared.SplitClientContainer; import io.split.android.client.shared.SplitClientContainerImpl; import io.split.android.client.shared.UserConsent; -import io.split.android.client.storage.cipher.EncryptionMigrationTask; import io.split.android.client.storage.cipher.SplitCipher; import io.split.android.client.storage.common.SplitStorageContainer; import io.split.android.client.storage.db.SplitRoomDatabase; @@ -186,7 +185,6 @@ private SplitFactoryImpl(@NonNull String apiToken, @NonNull Key key, @NonNull Sp config, splitApiFacade, mStorageContainer, splitsFilterQueryStringFromConfig, getFlagsSpec(testingConfig), mEventsManagerCoordinator, filters, flagSetsFilter, testingConfig); - cleanUpDabase(splitTaskExecutor, splitTaskFactory); WorkManagerWrapper workManagerWrapper = factoryHelper.buildWorkManagerWrapper(context, config, apiToken, databaseName, filters); SplitSingleThreadTaskExecutor splitSingleThreadTaskExecutor = new SplitSingleThreadTaskExecutor(); splitSingleThreadTaskExecutor.pause(); @@ -326,14 +324,19 @@ public void run() { Runnable initializer = new Runnable() { @Override public void run() { - Logger.v("Running initialization thread"); - RolloutCacheManagerImpl rolloutCacheManager = new RolloutCacheManagerImpl(config, mStorageContainer, new EncryptionMigrationTask(apiToken, splitDatabase, config.encryptionEnabled(), splitCipher)); + RolloutCacheManagerImpl rolloutCacheManager = new RolloutCacheManagerImpl(config, + mStorageContainer, + splitTaskFactory.createCleanUpDatabaseTask(System.currentTimeMillis() / 1000), + splitTaskFactory.createEncryptionMigrationTask(apiToken, splitDatabase, config.encryptionEnabled(), splitCipher)); + rolloutCacheManager.validateCache(new SplitTaskExecutionListener() { @Override public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { mEventsManagerCoordinator.notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE); + splitTaskExecutor.resume(); splitSingleThreadTaskExecutor.resume(); + mSyncManager.start(); mLifecycleManager.register(mSyncManager); diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java index e23407f55..989557cd9 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java @@ -11,6 +11,9 @@ import io.split.android.client.service.splits.SplitsSyncTask; import io.split.android.client.service.splits.SplitsUpdateTask; import io.split.android.client.service.telemetry.TelemetryTaskFactory; +import io.split.android.client.storage.cipher.EncryptionMigrationTask; +import io.split.android.client.storage.cipher.SplitCipher; +import io.split.android.client.storage.db.SplitRoomDatabase; public interface SplitTaskFactory extends TelemetryTaskFactory, ImpressionsTaskFactory { @@ -29,4 +32,6 @@ public interface SplitTaskFactory extends TelemetryTaskFactory, ImpressionsTaskF FilterSplitsInCacheTask createFilterSplitsInCacheTask(); CleanUpDatabaseTask createCleanUpDatabaseTask(long maxTimestamp); + + EncryptionMigrationTask createEncryptionMigrationTask(String sdkKey, SplitRoomDatabase splitRoomDatabase, boolean encryptionEnabled, SplitCipher splitCipher); } diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index 2c167cda0..75ecc45fc 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -44,7 +44,10 @@ import io.split.android.client.service.telemetry.TelemetryStatsRecorderTask; import io.split.android.client.service.telemetry.TelemetryTaskFactory; import io.split.android.client.service.telemetry.TelemetryTaskFactoryImpl; +import io.split.android.client.storage.cipher.EncryptionMigrationTask; +import io.split.android.client.storage.cipher.SplitCipher; import io.split.android.client.storage.common.SplitStorageContainer; +import io.split.android.client.storage.db.SplitRoomDatabase; import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; import io.split.android.client.telemetry.storage.TelemetryStorage; @@ -203,6 +206,11 @@ public SplitInPlaceUpdateTask createSplitsUpdateTask(Split featureFlag, long sin return new SplitInPlaceUpdateTask(mSplitsStorageContainer.getSplitsStorage(), mSplitChangeProcessor, mEventsManager, mTelemetryRuntimeProducer, featureFlag, since); } + @Override + public EncryptionMigrationTask createEncryptionMigrationTask(String sdkKey, SplitRoomDatabase splitRoomDatabase, boolean encryptionEnabled, SplitCipher splitCipher) { + return new EncryptionMigrationTask(sdkKey, splitRoomDatabase, encryptionEnabled, splitCipher); + } + @NonNull private TelemetryTaskFactory initializeTelemetryTaskFactory(@NonNull SplitClientConfig splitClientConfig, @Nullable Map filters, TelemetryStorage telemetryStorage) { final TelemetryTaskFactory mTelemetryTaskFactory; diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java index 3e5b00b08..f211ae62b 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java @@ -9,6 +9,7 @@ import java.util.concurrent.TimeUnit; import io.split.android.client.SplitClientConfig; +import io.split.android.client.service.CleanUpDatabaseTask; import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionListener; @@ -30,11 +31,16 @@ public class RolloutCacheManagerImpl implements RolloutCacheManager, SplitTask { @NonNull private final RolloutDefinitionsCache[] mStorages; @NonNull + private final CleanUpDatabaseTask mCleanUpDatabaseTask; + @NonNull private final EncryptionMigrationTask mEncryptionMigrationTask; - public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitStorageContainer storageContainer, @NonNull EncryptionMigrationTask encryptionMigrationTask) { + public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitStorageContainer storageContainer, + @NonNull CleanUpDatabaseTask cleanUpDatabaseTask, + @NonNull EncryptionMigrationTask encryptionMigrationTask) { this(storageContainer.getGeneralInfoStorage(), RolloutCacheManagerConfig.from(splitClientConfig), + cleanUpDatabaseTask, encryptionMigrationTask, storageContainer.getSplitsStorage(), storageContainer.getMySegmentsStorageContainer(), @@ -42,8 +48,13 @@ public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @No } @VisibleForTesting - RolloutCacheManagerImpl(@NonNull GeneralInfoStorage generalInfoStorage, @NonNull RolloutCacheManagerConfig config, @NonNull EncryptionMigrationTask encryptionMigrationTask, @NonNull RolloutDefinitionsCache... storages) { + RolloutCacheManagerImpl(@NonNull GeneralInfoStorage generalInfoStorage, + @NonNull RolloutCacheManagerConfig config, + @NonNull CleanUpDatabaseTask clean, + @NonNull EncryptionMigrationTask encryptionMigrationTask, + @NonNull RolloutDefinitionsCache... storages) { mGeneralInfoStorage = checkNotNull(generalInfoStorage); + mCleanUpDatabaseTask = checkNotNull(clean); mEncryptionMigrationTask = checkNotNull(encryptionMigrationTask); mStorages = checkNotNull(storages); mConfig = checkNotNull(config); @@ -52,9 +63,19 @@ public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @No @WorkerThread @Override public void validateCache(SplitTaskExecutionListener listener) { - execute(); - mEncryptionMigrationTask.execute(); - listener.taskExecuted(SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK)); + try { + Logger.v("Rollout cache manager: Executing clearing task"); + mCleanUpDatabaseTask.execute(); + Logger.v("Rollout cache manager: Validating cache"); + execute(); + Logger.v("Rollout cache manager: Migrating encryption"); + mEncryptionMigrationTask.execute(); + Logger.v("Rollout cache manager: validation finished"); + listener.taskExecuted(SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK)); + } catch (Exception ex) { + Logger.e("Error occurred validating cache: " + ex.getMessage()); + listener.taskExecuted(SplitTaskExecutionInfo.error(SplitTaskType.GENERIC_TASK)); + } } @NonNull diff --git a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt index 7bb93671b..20e1b2c24 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt +++ b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt @@ -1,16 +1,14 @@ package io.split.android.client.service.synchronizer +import io.split.android.client.service.CleanUpDatabaseTask import io.split.android.client.service.executor.SplitTaskExecutionListener -import io.split.android.client.service.executor.SplitTaskExecutor import io.split.android.client.storage.RolloutDefinitionsCache import io.split.android.client.storage.cipher.EncryptionMigrationTask import io.split.android.client.storage.general.GeneralInfoStorage -import io.split.android.fake.SplitTaskExecutorStub import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyLong -import org.mockito.ArgumentMatchers.argThat import org.mockito.Mockito.longThat import org.mockito.Mockito.mock import org.mockito.Mockito.times @@ -25,11 +23,13 @@ class RolloutCacheManagerTest { private lateinit var mSplitsCache: RolloutDefinitionsCache private lateinit var mSegmentsCache: RolloutDefinitionsCache private lateinit var mEncryptionMigrationTask: EncryptionMigrationTask + private lateinit var mCleanUpDatabaseTask: CleanUpDatabaseTask @Before fun setup() { mGeneralInfoStorage = mock(GeneralInfoStorage::class.java) - mEncryptionMigrationTask = mock(EncryptionMigrationTask::class.java); + mEncryptionMigrationTask = mock(EncryptionMigrationTask::class.java) + mCleanUpDatabaseTask = mock(CleanUpDatabaseTask::class.java) mSplitsCache = mock(RolloutDefinitionsCache::class.java) mSegmentsCache = mock(RolloutDefinitionsCache::class.java) } @@ -133,8 +133,26 @@ class RolloutCacheManagerTest { verify(mGeneralInfoStorage, times(0)).setRolloutCacheLastClearTimestamp(anyLong()) } + @Test + fun `validateCache executes cleanUpDatabaseTask`() { + mRolloutCacheManager = getCacheManager(10L, false) + + mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) + + verify(mCleanUpDatabaseTask).execute() + } + + @Test + fun `validateCache executes encryptionMigrationTask`() { + mRolloutCacheManager = getCacheManager(10L, false) + + mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) + + verify(mEncryptionMigrationTask).execute() + } + private fun getCacheManager(expiration: Long, clearOnInit: Boolean): RolloutCacheManager { - return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheManagerConfig(expiration, clearOnInit), mEncryptionMigrationTask, mSplitsCache, mSegmentsCache) + return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheManagerConfig(expiration, clearOnInit), mCleanUpDatabaseTask, mEncryptionMigrationTask, mSplitsCache, mSegmentsCache) } private fun createMockedTimestamp(period: Long): Long { From 4532d4a65dc77a20ffbec6f9f676865643676fdd Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Dec 2024 17:43:28 -0300 Subject: [PATCH 07/37] Refactor --- .../android/client/SplitFactoryHelper.java | 83 ++++++++++++++++++- .../android/client/SplitFactoryImpl.java | 46 ++++------ .../attributes/AttributesManagerImpl.java | 4 +- .../synchronizer/RolloutCacheManager.java | 2 +- .../android/client/SplitFactoryHelperTest.kt | 58 +++++++++++-- 5 files changed, 150 insertions(+), 43 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 6889fcc79..bfb9b6bde 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -4,6 +4,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.core.util.Pair; import androidx.work.WorkManager; @@ -22,11 +23,16 @@ import io.split.android.client.common.CompressionUtilProvider; import io.split.android.client.events.EventsManagerCoordinator; +import io.split.android.client.events.SplitInternalEvent; +import io.split.android.client.lifecycle.SplitLifecycleManager; import io.split.android.client.network.HttpClient; import io.split.android.client.network.SdkTargetPath; import io.split.android.client.network.SplitHttpHeadersBuilder; import io.split.android.client.service.ServiceFactory; import io.split.android.client.service.SplitApiFacade; +import io.split.android.client.service.executor.SplitSingleThreadTaskExecutor; +import io.split.android.client.service.executor.SplitTaskExecutionInfo; +import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskFactory; import io.split.android.client.service.http.mysegments.MySegmentsFetcherFactory; @@ -55,6 +61,8 @@ import io.split.android.client.service.sseclient.sseclient.SseHandler; import io.split.android.client.service.sseclient.sseclient.SseRefreshTokenTimer; import io.split.android.client.service.sseclient.sseclient.StreamingComponents; +import io.split.android.client.service.synchronizer.RolloutCacheManager; +import io.split.android.client.service.synchronizer.RolloutCacheManagerImpl; import io.split.android.client.service.synchronizer.SyncGuardian; import io.split.android.client.service.synchronizer.SyncGuardianImpl; import io.split.android.client.service.synchronizer.SyncManager; @@ -84,6 +92,7 @@ import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; import io.split.android.client.telemetry.storage.TelemetryStorage; import io.split.android.client.utils.Utils; +import io.split.android.client.utils.logger.Logger; class SplitFactoryHelper { private static final int DB_MAGIC_CHARS_COUNT = 4; @@ -397,7 +406,8 @@ public ProcessStrategy getImpressionStrategy(SplitTaskExecutor splitTaskExecutor .getStrategy(config.impressionsMode()); } - @Nullable SplitCipher getCipher(String apiKey, boolean encryptionEnabled) { + @Nullable + SplitCipher getCipher(String apiKey, boolean encryptionEnabled) { return SplitCipherFactory.create(apiKey, encryptionEnabled ? SplitEncryptionLevel.AES_128_CBC : SplitEncryptionLevel.NONE); } @@ -472,4 +482,75 @@ public URI build(String matchingKey) throws URISyntaxException { return SdkTargetPath.mySegments(mEndpoint, matchingKey); } } + + static class Initializer implements Runnable { + + private final RolloutCacheManager mRolloutCacheManager; + private final SplitTaskExecutionListener mListener; + + Initializer( + String apiToken, + SplitClientConfig config, + SplitTaskFactory splitTaskFactory, + SplitRoomDatabase splitDatabase, + SplitCipher splitCipher, + EventsManagerCoordinator eventsManagerCoordinator, + SplitTaskExecutor splitTaskExecutor, + SplitSingleThreadTaskExecutor splitSingleThreadTaskExecutor, + SplitStorageContainer storageContainer, + SyncManager syncManager, + SplitLifecycleManager lifecycleManager) { + + this(new RolloutCacheManagerImpl(config, + storageContainer, + splitTaskFactory.createCleanUpDatabaseTask(System.currentTimeMillis() / 1000), + splitTaskFactory.createEncryptionMigrationTask(apiToken, splitDatabase, config.encryptionEnabled(), splitCipher)), + new Listener(eventsManagerCoordinator, splitTaskExecutor, splitSingleThreadTaskExecutor, syncManager, lifecycleManager)); + } + + @VisibleForTesting + Initializer(RolloutCacheManager rolloutCacheManager, SplitTaskExecutionListener listener) { + mRolloutCacheManager = rolloutCacheManager; + mListener = listener; + } + + @Override + public void run() { + mRolloutCacheManager.validateCache(mListener); + } + + static class Listener implements SplitTaskExecutionListener { + + private final EventsManagerCoordinator mEventsManagerCoordinator; + private final SplitTaskExecutor mSplitTaskExecutor; + private final SplitSingleThreadTaskExecutor mSplitSingleThreadTaskExecutor; + private final SyncManager mSyncManager; + private final SplitLifecycleManager mLifecycleManager; + + Listener(EventsManagerCoordinator eventsManagerCoordinator, + SplitTaskExecutor splitTaskExecutor, + SplitSingleThreadTaskExecutor splitSingleThreadTaskExecutor, + SyncManager syncManager, + SplitLifecycleManager lifecycleManager) { + mEventsManagerCoordinator = eventsManagerCoordinator; + mSplitTaskExecutor = splitTaskExecutor; + mSplitSingleThreadTaskExecutor = splitSingleThreadTaskExecutor; + mSyncManager = syncManager; + mLifecycleManager = lifecycleManager; + } + + @Override + public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { + mEventsManagerCoordinator.notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE); + + mSplitTaskExecutor.resume(); + mSplitSingleThreadTaskExecutor.resume(); + + mSyncManager.start(); + mLifecycleManager.register(mSyncManager); + + Logger.i("Android SDK initialized!"); + } + } + } } diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index b401b36a0..6ac3a3825 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -18,7 +18,6 @@ import io.split.android.client.api.Key; import io.split.android.client.common.CompressionUtilProvider; import io.split.android.client.events.EventsManagerCoordinator; -import io.split.android.client.events.SplitInternalEvent; import io.split.android.client.factory.FactoryMonitor; import io.split.android.client.factory.FactoryMonitorImpl; import io.split.android.client.impressions.ImpressionListener; @@ -29,8 +28,6 @@ import io.split.android.client.network.HttpClientImpl; import io.split.android.client.service.SplitApiFacade; import io.split.android.client.service.executor.SplitSingleThreadTaskExecutor; -import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskExecutorImpl; import io.split.android.client.service.executor.SplitTaskFactory; @@ -38,7 +35,6 @@ import io.split.android.client.service.impressions.ImpressionManager; import io.split.android.client.service.impressions.StrategyImpressionManager; import io.split.android.client.service.sseclient.sseclient.StreamingComponents; -import io.split.android.client.service.synchronizer.RolloutCacheManagerImpl; import io.split.android.client.service.synchronizer.SyncManager; import io.split.android.client.service.synchronizer.Synchronizer; import io.split.android.client.service.synchronizer.SynchronizerImpl; @@ -314,6 +310,19 @@ public void run() { } }); + // Set up async initialization + final SplitFactoryHelper.Initializer initializer = new SplitFactoryHelper.Initializer(apiToken, + config, + splitTaskFactory, + splitDatabase, + splitCipher, + mEventsManagerCoordinator, + splitTaskExecutor, + splitSingleThreadTaskExecutor, + mStorageContainer, + mSyncManager, + mLifecycleManager); + // Initialize default client client(); SplitParser mSplitParser = new SplitParser(mStorageContainer.getMySegmentsStorageContainer(), mStorageContainer.getMyLargeSegmentsStorageContainer()); @@ -321,37 +330,14 @@ public void run() { mStorageContainer.getSplitsStorage(), new SplitValidatorImpl(), mSplitParser); - Runnable initializer = new Runnable() { - @Override - public void run() { - RolloutCacheManagerImpl rolloutCacheManager = new RolloutCacheManagerImpl(config, - mStorageContainer, - splitTaskFactory.createCleanUpDatabaseTask(System.currentTimeMillis() / 1000), - splitTaskFactory.createEncryptionMigrationTask(apiToken, splitDatabase, config.encryptionEnabled(), splitCipher)); - - rolloutCacheManager.validateCache(new SplitTaskExecutionListener() { - @Override - public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { - mEventsManagerCoordinator.notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE); - - splitTaskExecutor.resume(); - splitSingleThreadTaskExecutor.resume(); - - mSyncManager.start(); - mLifecycleManager.register(mSyncManager); - - Logger.i("Android SDK initialized!"); - } - }); - } - }; - new Thread(initializer).start(); - if (config.shouldRecordTelemetry()) { int activeFactoriesCount = mFactoryMonitor.count(mApiKey); mStorageContainer.getTelemetryStorage().recordActiveFactories(activeFactoriesCount); mStorageContainer.getTelemetryStorage().recordRedundantFactories(activeFactoriesCount - 1); } + + // Run initializer + new Thread(initializer).start(); } private static String getFlagsSpec(@Nullable TestingConfig testingConfig) { diff --git a/src/main/java/io/split/android/client/attributes/AttributesManagerImpl.java b/src/main/java/io/split/android/client/attributes/AttributesManagerImpl.java index 24169f66b..5b2327883 100644 --- a/src/main/java/io/split/android/client/attributes/AttributesManagerImpl.java +++ b/src/main/java/io/split/android/client/attributes/AttributesManagerImpl.java @@ -118,13 +118,13 @@ public boolean clearAttributes() { private void submitUpdateTask(PersistentAttributesStorage persistentStorage, Map mInMemoryAttributes) { if (persistentStorage != null && mSplitTaskExecutor != null && mAttributeTaskFactory != null) { - mSplitTaskExecutor.submit(mAttributeTaskFactory.createAttributeUpdateTask(persistentStorage, mInMemoryAttributes), null); + mSplitTaskExecutor.schedule(mAttributeTaskFactory.createAttributeUpdateTask(persistentStorage, mInMemoryAttributes), 5L, null); } } private void submitClearTask(PersistentAttributesStorage persistentStorage) { if (persistentStorage != null && mSplitTaskExecutor != null && mAttributeTaskFactory != null) { - mSplitTaskExecutor.submit(mAttributeTaskFactory.createAttributeClearTask(persistentStorage), null); + mSplitTaskExecutor.schedule(mAttributeTaskFactory.createAttributeClearTask(persistentStorage), 5L, null); } } } diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java index 372b376d2..72cf4477e 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java @@ -2,7 +2,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionListener; -interface RolloutCacheManager { +public interface RolloutCacheManager { void validateCache(SplitTaskExecutionListener listener); } diff --git a/src/test/java/io/split/android/client/SplitFactoryHelperTest.kt b/src/test/java/io/split/android/client/SplitFactoryHelperTest.kt index acda1af2a..7ff5e004b 100644 --- a/src/test/java/io/split/android/client/SplitFactoryHelperTest.kt +++ b/src/test/java/io/split/android/client/SplitFactoryHelperTest.kt @@ -1,10 +1,17 @@ package io.split.android.client import android.content.Context +import io.split.android.client.SplitFactoryHelper.Initializer.Listener +import io.split.android.client.events.EventsManagerCoordinator +import io.split.android.client.events.SplitInternalEvent +import io.split.android.client.lifecycle.SplitLifecycleManager +import io.split.android.client.service.executor.SplitSingleThreadTaskExecutor +import io.split.android.client.service.executor.SplitTaskExecutionInfo import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.service.executor.SplitTaskExecutor -import io.split.android.client.storage.cipher.EncryptionMigrationTask -import io.split.android.client.storage.db.SplitRoomDatabase +import io.split.android.client.service.executor.SplitTaskType +import io.split.android.client.service.synchronizer.RolloutCacheManager +import io.split.android.client.service.synchronizer.SyncManager import junit.framework.TestCase.assertEquals import org.junit.After import org.junit.Before @@ -12,7 +19,6 @@ import org.junit.Test import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.any -import org.mockito.Mockito.argThat import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @@ -24,12 +30,6 @@ class SplitFactoryHelperTest { private lateinit var mocks: AutoCloseable - @Mock - private lateinit var splitRoomDatabase: SplitRoomDatabase - @Mock - private lateinit var splitTaskExecutor: SplitTaskExecutor - @Mock - private lateinit var taskListener: SplitTaskExecutionListener @Mock private lateinit var context: Context @@ -136,4 +136,44 @@ class SplitFactoryHelperTest { verify(existingPath).renameTo(nonExistingPath) assertEquals("abcdwxyz", databaseName) } + + @Test + fun `Initializer test`() { + val rolloutCacheManager = mock(RolloutCacheManager::class.java) + val splitTaskExecutionListener = mock(SplitTaskExecutionListener::class.java) + + val initializer = SplitFactoryHelper.Initializer( + rolloutCacheManager, + splitTaskExecutionListener + ) + + initializer.run() + + verify(rolloutCacheManager).validateCache(splitTaskExecutionListener) + } + + @Test + fun `Initializer Listener test`() { + val eventsManagerCoordinator = mock(EventsManagerCoordinator::class.java) + val taskExecutor = mock(SplitTaskExecutor::class.java) + val singleThreadTaskExecutor = mock(SplitSingleThreadTaskExecutor::class.java) + val syncManager = mock(SyncManager::class.java) + val lifecycleManager = mock(SplitLifecycleManager::class.java) + + val listener = Listener( + eventsManagerCoordinator, + taskExecutor, + singleThreadTaskExecutor, + syncManager, + lifecycleManager + ) + + listener.taskExecuted(SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK)) + + verify(eventsManagerCoordinator).notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE) + verify(taskExecutor).resume() + verify(singleThreadTaskExecutor).resume() + verify(syncManager).start() + verify(lifecycleManager).register(syncManager) + } } From fb3d56f1dd775f9d2a3f8a792198839a93b33927 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Dec 2024 18:01:34 -0300 Subject: [PATCH 08/37] Fix tests --- .../client/attributes/AttributesManagerImplTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/io/split/android/client/attributes/AttributesManagerImplTest.java b/src/test/java/io/split/android/client/attributes/AttributesManagerImplTest.java index 1e2bb618e..afcd377ed 100644 --- a/src/test/java/io/split/android/client/attributes/AttributesManagerImplTest.java +++ b/src/test/java/io/split/android/client/attributes/AttributesManagerImplTest.java @@ -92,7 +92,7 @@ public void setAttributeLaunchesAttributeUpdateTaskIfValueIsValid() { attributeClient.setAttribute(name, attribute); verify(attributeTaskFactory).createAttributeUpdateTask(persistentAttributesStorage, attributeMap); - verify(splitTaskExecutor).submit(updateAttributesInPersistentStorageTask, null); + verify(splitTaskExecutor).schedule(updateAttributesInPersistentStorageTask, 5L, null); } @Test @@ -170,7 +170,7 @@ public void setAttributesLaunchesAttributeUpdateTaskIfValuesAreValid() { attributeClient.setAttributes(attributeMap); verify(attributeTaskFactory).createAttributeUpdateTask(persistentAttributesStorage, attributeMap); - verify(splitTaskExecutor).submit(updateAttributesInPersistentStorageTask, null); + verify(splitTaskExecutor).schedule(updateAttributesInPersistentStorageTask, 5L, null); } @Test @@ -225,7 +225,7 @@ public void clearLaunchesAttributeClearTask() { attributeClient.clearAttributes(); verify(attributeTaskFactory).createAttributeClearTask(persistentAttributesStorage); - verify(splitTaskExecutor).submit(clearAttributesInPersistentStorageTask, null); + verify(splitTaskExecutor).schedule(clearAttributesInPersistentStorageTask, 5L, null); } @Test @@ -250,7 +250,7 @@ public void removeLaunchesAttributeUpdateTask() { attributeClient.removeAttribute("key"); verify(attributeTaskFactory).createAttributeUpdateTask(persistentAttributesStorage, attributeMap); - verify(splitTaskExecutor).submit(updateAttributesInPersistentStorageTask, null); + verify(splitTaskExecutor).schedule(updateAttributesInPersistentStorageTask, 5L, null); } private Map getDefaultValues() { From ff03a856b60ecb833bfee948cad1f20122bffe51 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Dec 2024 20:33:35 -0300 Subject: [PATCH 09/37] Update tests --- .../attributes/AttributesIntegrationTest.java | 6 +++--- .../integration/encryption/EncryptionTest.java | 2 +- .../largesegments/LargeSegmentsStreamingTest.java | 4 ++-- .../shared/SharedClientsIntegrationTest.java | 4 ++-- .../telemetry/TelemetryOccupancyTest.java | 15 +++++++++++---- .../io/split/android/client/SplitFactoryImpl.java | 15 ++++++++------- 6 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/androidTest/java/tests/integration/attributes/AttributesIntegrationTest.java b/src/androidTest/java/tests/integration/attributes/AttributesIntegrationTest.java index 9b9a47401..085d99cb3 100644 --- a/src/androidTest/java/tests/integration/attributes/AttributesIntegrationTest.java +++ b/src/androidTest/java/tests/integration/attributes/AttributesIntegrationTest.java @@ -108,7 +108,7 @@ public void testPersistentAttributes2() throws InterruptedException { // 3. Perform clear and verify there are no attributes on DB client.clearAttributes(); - countDownLatch.await(1, TimeUnit.SECONDS); + countDownLatch.await(7, TimeUnit.SECONDS); Assert.assertNull(mRoomDb.attributesDao().getByUserKey(userKey)); } @@ -153,7 +153,7 @@ public void testPersistentAttributesWithMultiClient2() throws InterruptedExcepti // 2. Clear second client's attributes and check DB entry has been cleared client2.clearAttributes(); - countDownLatch.await(1, TimeUnit.SECONDS); // waiting since DB operations are async + countDownLatch.await(7, TimeUnit.SECONDS); // waiting since DB operations are async Assert.assertNull(mRoomDb.attributesDao().getByUserKey("new_key")); // 3. Verify evaluation with first client uses attribute @@ -162,7 +162,7 @@ public void testPersistentAttributesWithMultiClient2() throws InterruptedExcepti // 4. Perform clear and verify there are no attributes on DB client.clearAttributes(); - countDownLatch.await(1, TimeUnit.SECONDS); + countDownLatch.await(7, TimeUnit.SECONDS); Assert.assertNull(mRoomDb.attributesDao().getByUserKey(matchingKey)); } diff --git a/src/androidTest/java/tests/integration/encryption/EncryptionTest.java b/src/androidTest/java/tests/integration/encryption/EncryptionTest.java index b8ef6be27..6a8561825 100644 --- a/src/androidTest/java/tests/integration/encryption/EncryptionTest.java +++ b/src/androidTest/java/tests/integration/encryption/EncryptionTest.java @@ -127,7 +127,7 @@ public void onPostExecutionView(SplitClient client) { } }); - assertTrue(latch.await(2, TimeUnit.SECONDS)); + assertTrue(latch.await(5, TimeUnit.SECONDS)); mLifecycleManager.simulateOnPause(); Thread.sleep(200); mLifecycleManager.simulateOnResume(); diff --git a/src/androidTest/java/tests/integration/largesegments/LargeSegmentsStreamingTest.java b/src/androidTest/java/tests/integration/largesegments/LargeSegmentsStreamingTest.java index 2b95f70ca..f5cfe3cdc 100644 --- a/src/androidTest/java/tests/integration/largesegments/LargeSegmentsStreamingTest.java +++ b/src/androidTest/java/tests/integration/largesegments/LargeSegmentsStreamingTest.java @@ -67,8 +67,8 @@ public void setUp() throws IOException, InterruptedException { public void unboundedLargeSegmentsUpdateTriggersSdkUpdate() throws IOException, InterruptedException { TestSetup testSetup = getTestSetup(); - boolean mySegmentsAwait = mLatches.get(MY_SEGMENTS).await(10, TimeUnit.SECONDS); - boolean splitsAwait = mLatches.get(SPLIT_CHANGES).await(10, TimeUnit.SECONDS); + boolean mySegmentsAwait = mLatches.get(MY_SEGMENTS).await(15, TimeUnit.SECONDS); + boolean splitsAwait = mLatches.get(SPLIT_CHANGES).await(15, TimeUnit.SECONDS); String initialSegmentList = testSetup.database.myLargeSegmentDao().getByUserKey(IntegrationHelper.dummyUserKey().matchingKey()).getSegmentList(); mRandomizeMyLargeSegments.set(true); diff --git a/src/androidTest/java/tests/integration/shared/SharedClientsIntegrationTest.java b/src/androidTest/java/tests/integration/shared/SharedClientsIntegrationTest.java index feecae9a8..a12642bee 100644 --- a/src/androidTest/java/tests/integration/shared/SharedClientsIntegrationTest.java +++ b/src/androidTest/java/tests/integration/shared/SharedClientsIntegrationTest.java @@ -159,8 +159,8 @@ public void onPostExecutionView(SplitClient client) { } }); insertSplitsIntoDb(); - readyLatch.await(10, TimeUnit.SECONDS); - readyLatch2.await(10, TimeUnit.SECONDS); + readyLatch.await(15, TimeUnit.SECONDS); + readyLatch2.await(15, TimeUnit.SECONDS); assertEquals(1, readyCount.get()); assertEquals(1, readyCount2.get()); diff --git a/src/androidTest/java/tests/integration/telemetry/TelemetryOccupancyTest.java b/src/androidTest/java/tests/integration/telemetry/TelemetryOccupancyTest.java index 87cf5669b..472fcdbea 100644 --- a/src/androidTest/java/tests/integration/telemetry/TelemetryOccupancyTest.java +++ b/src/androidTest/java/tests/integration/telemetry/TelemetryOccupancyTest.java @@ -6,16 +6,16 @@ import org.junit.Test; import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CountDownLatch; import helper.TestableSplitConfigBuilder; import io.split.android.client.SplitClientConfig; -import io.split.android.client.storage.db.StorageFactory; import io.split.android.client.telemetry.model.streaming.OccupancyPriStreamingEvent; import io.split.android.client.telemetry.model.streaming.OccupancySecStreamingEvent; import io.split.android.client.telemetry.model.streaming.StreamingEvent; import io.split.android.client.telemetry.model.streaming.TokenRefreshStreamingEvent; -import io.split.android.client.telemetry.storage.TelemetryStorage; import tests.integration.streaming.OccupancyBaseTest; public class TelemetryOccupancyTest extends OccupancyBaseTest { @@ -35,12 +35,19 @@ public class TelemetryOccupancyTest extends OccupancyBaseTest { @Test public void telemetryOccupancyPriStreamingEvent() throws InterruptedException, IOException { + new CountDownLatch(1); getSplitFactory(mTelemetryEnabledConfig); pushOccupancy(PRIMARY_CHANNEL, 1); - sleep(2000); - List streamingEvents = mTelemetryStorage.popStreamingEvents(); + long startTime = System.currentTimeMillis(); + List streamingEvents = new ArrayList<>(); + streamingEvents = mTelemetryStorage.popStreamingEvents(); + while (System.currentTimeMillis() - startTime < 5000 && + !streamingEvents.stream().anyMatch(event -> event instanceof OccupancyPriStreamingEvent)) { + Thread.sleep(100); + streamingEvents = mTelemetryStorage.popStreamingEvents(); + } assertTrue(streamingEvents.stream().anyMatch(event -> event instanceof OccupancyPriStreamingEvent)); } diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index 6ac3a3825..34d3abd63 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -323,13 +323,6 @@ public void run() { mSyncManager, mLifecycleManager); - // Initialize default client - client(); - SplitParser mSplitParser = new SplitParser(mStorageContainer.getMySegmentsStorageContainer(), mStorageContainer.getMyLargeSegmentsStorageContainer()); - mManager = new SplitManagerImpl( - mStorageContainer.getSplitsStorage(), - new SplitValidatorImpl(), mSplitParser); - if (config.shouldRecordTelemetry()) { int activeFactoriesCount = mFactoryMonitor.count(mApiKey); mStorageContainer.getTelemetryStorage().recordActiveFactories(activeFactoriesCount); @@ -338,6 +331,14 @@ public void run() { // Run initializer new Thread(initializer).start(); + + // Initialize default client + client(); + SplitParser mSplitParser = new SplitParser(mStorageContainer.getMySegmentsStorageContainer(), mStorageContainer.getMyLargeSegmentsStorageContainer()); + mManager = new SplitManagerImpl( + mStorageContainer.getSplitsStorage(), + new SplitValidatorImpl(), mSplitParser); + } private static String getFlagsSpec(@Nullable TestingConfig testingConfig) { From 015180d88eac2e28cd66d71868083e29d15a13b9 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 3 Dec 2024 11:37:08 -0300 Subject: [PATCH 10/37] Track segments sync by Key --- .../java/fake/SynchronizerSpyImpl.java | 9 +++-- .../telemetry/TelemetryOccupancyTest.java | 9 ++++- .../synchronizer/SynchronizerImpl.java | 9 +++-- .../MySegmentsSynchronizerRegistry.java | 6 ++- .../MySegmentsSynchronizerRegistryImpl.java | 13 ++++--- .../shared/ClientComponentsRegisterImpl.java | 4 +- .../client/service/SynchronizerTest.java | 9 +++-- ...ySegmentsSynchronizerRegistryImplTest.java | 37 ++++++++++--------- .../ClientComponentsRegisterImplTest.java | 4 +- 9 files changed, 57 insertions(+), 43 deletions(-) diff --git a/src/androidTest/java/fake/SynchronizerSpyImpl.java b/src/androidTest/java/fake/SynchronizerSpyImpl.java index 6971258eb..2203f3913 100644 --- a/src/androidTest/java/fake/SynchronizerSpyImpl.java +++ b/src/androidTest/java/fake/SynchronizerSpyImpl.java @@ -3,6 +3,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; +import io.split.android.client.api.Key; import io.split.android.client.dtos.Event; import io.split.android.client.impressions.Impression; import io.split.android.client.service.synchronizer.Synchronizer; @@ -122,12 +123,12 @@ public void unregisterAttributesSynchronizer(String userKey) { } @Override - public void registerMySegmentsSynchronizer(String userKey, MySegmentsSynchronizer mySegmentsSynchronizer) { - ((MySegmentsSynchronizerRegistry) mSynchronizer).registerMySegmentsSynchronizer(userKey, mySegmentsSynchronizer); + public void registerMySegmentsSynchronizer(Key key, MySegmentsSynchronizer mySegmentsSynchronizer) { + ((MySegmentsSynchronizerRegistry) mSynchronizer).registerMySegmentsSynchronizer(key, mySegmentsSynchronizer); } @Override - public void unregisterMySegmentsSynchronizer(String userKey) { - ((MySegmentsSynchronizerRegistry) mSynchronizer).unregisterMySegmentsSynchronizer(userKey); + public void unregisterMySegmentsSynchronizer(Key key) { + ((MySegmentsSynchronizerRegistry) mSynchronizer).unregisterMySegmentsSynchronizer(key); } } diff --git a/src/androidTest/java/tests/integration/telemetry/TelemetryOccupancyTest.java b/src/androidTest/java/tests/integration/telemetry/TelemetryOccupancyTest.java index 472fcdbea..e13080d3a 100644 --- a/src/androidTest/java/tests/integration/telemetry/TelemetryOccupancyTest.java +++ b/src/androidTest/java/tests/integration/telemetry/TelemetryOccupancyTest.java @@ -58,7 +58,14 @@ public void telemetryOccupancySecStreamingEvent() throws InterruptedException, I pushOccupancy(SECONDARY_CHANNEL, 1); sleep(2000); - List streamingEvents = mTelemetryStorage.popStreamingEvents(); + long startTime = System.currentTimeMillis(); + List streamingEvents = new ArrayList<>(); + streamingEvents = mTelemetryStorage.popStreamingEvents(); + while (System.currentTimeMillis() - startTime < 5000 && + !streamingEvents.stream().anyMatch(event -> event instanceof OccupancySecStreamingEvent)) { + Thread.sleep(100); + streamingEvents = mTelemetryStorage.popStreamingEvents(); + } assertTrue(streamingEvents.stream().anyMatch(event -> event instanceof OccupancySecStreamingEvent)); } diff --git a/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java index 5f069bee9..b18fd0b50 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java @@ -10,6 +10,7 @@ import io.split.android.client.RetryBackoffCounterTimerFactory; import io.split.android.client.SplitClientConfig; +import io.split.android.client.api.Key; import io.split.android.client.dtos.Event; import io.split.android.client.events.ISplitEventsManager; import io.split.android.client.impressions.Impression; @@ -259,13 +260,13 @@ public void pushImpression(Impression impression) { } @Override - public void registerMySegmentsSynchronizer(String userKey, MySegmentsSynchronizer mySegmentsSynchronizer) { - mMySegmentsSynchronizerRegistry.registerMySegmentsSynchronizer(userKey, mySegmentsSynchronizer); + public void registerMySegmentsSynchronizer(Key key, MySegmentsSynchronizer mySegmentsSynchronizer) { + mMySegmentsSynchronizerRegistry.registerMySegmentsSynchronizer(key, mySegmentsSynchronizer); } @Override - public void unregisterMySegmentsSynchronizer(String userKey) { - mMySegmentsSynchronizerRegistry.unregisterMySegmentsSynchronizer(userKey); + public void unregisterMySegmentsSynchronizer(Key key) { + mMySegmentsSynchronizerRegistry.unregisterMySegmentsSynchronizer(key); } @Override diff --git a/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistry.java b/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistry.java index a6cd8c763..d91c47f06 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistry.java +++ b/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistry.java @@ -1,8 +1,10 @@ package io.split.android.client.service.synchronizer.mysegments; +import io.split.android.client.api.Key; + public interface MySegmentsSynchronizerRegistry { - void registerMySegmentsSynchronizer(String userKey, MySegmentsSynchronizer mySegmentsSynchronizer); + void registerMySegmentsSynchronizer(Key key, MySegmentsSynchronizer mySegmentsSynchronizer); - void unregisterMySegmentsSynchronizer(String userKey); + void unregisterMySegmentsSynchronizer(Key key); } diff --git a/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistryImpl.java b/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistryImpl.java index 5aba4ce82..8007e570e 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistryImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistryImpl.java @@ -6,6 +6,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; +import io.split.android.client.api.Key; import io.split.android.client.service.mysegments.MySegmentUpdateParams; public class MySegmentsSynchronizerRegistryImpl implements MySegmentsSynchronizerRegistry, @@ -15,23 +16,23 @@ public class MySegmentsSynchronizerRegistryImpl implements MySegmentsSynchronize private final AtomicBoolean mSynchronizedSegments = new AtomicBoolean(false); private final AtomicBoolean mScheduledSegmentsSyncTask = new AtomicBoolean(false); private final AtomicBoolean mStoppedPeriodicFetching = new AtomicBoolean(false); - private final ConcurrentMap mMySegmentsSynchronizers = new ConcurrentHashMap<>(); + private final ConcurrentMap mMySegmentsSynchronizers = new ConcurrentHashMap<>(); @Override - public synchronized void registerMySegmentsSynchronizer(String userKey, MySegmentsSynchronizer mySegmentsSynchronizer) { - mMySegmentsSynchronizers.put(userKey, mySegmentsSynchronizer); + public synchronized void registerMySegmentsSynchronizer(Key key, MySegmentsSynchronizer mySegmentsSynchronizer) { + mMySegmentsSynchronizers.put(key, mySegmentsSynchronizer); triggerPendingActions(mySegmentsSynchronizer); } @Override - public synchronized void unregisterMySegmentsSynchronizer(String userKey) { - MySegmentsSynchronizer mySegmentsSynchronizer = mMySegmentsSynchronizers.get(userKey); + public synchronized void unregisterMySegmentsSynchronizer(Key key) { + MySegmentsSynchronizer mySegmentsSynchronizer = mMySegmentsSynchronizers.get(key); if (mySegmentsSynchronizer != null) { mySegmentsSynchronizer.stopPeriodicFetching(); mySegmentsSynchronizer.destroy(); } - mMySegmentsSynchronizers.remove(userKey); + mMySegmentsSynchronizers.remove(key); } @Override diff --git a/src/main/java/io/split/android/client/shared/ClientComponentsRegisterImpl.java b/src/main/java/io/split/android/client/shared/ClientComponentsRegisterImpl.java index de1d53414..af0dc3449 100644 --- a/src/main/java/io/split/android/client/shared/ClientComponentsRegisterImpl.java +++ b/src/main/java/io/split/android/client/shared/ClientComponentsRegisterImpl.java @@ -95,7 +95,7 @@ public void registerComponents(Key key, SplitEventsManager eventsManager, MySegm @Override public void unregisterComponentsForKey(Key key) { mAttributesSynchronizerRegistry.unregisterAttributesSynchronizer(key.matchingKey()); - mMySegmentsSynchronizerRegistry.unregisterMySegmentsSynchronizer(key.matchingKey()); + mMySegmentsSynchronizerRegistry.unregisterMySegmentsSynchronizer(key); mEventsManagerRegistry.unregisterEventsManager(key); if (isSyncEnabled()) { @@ -116,7 +116,7 @@ private void registerAttributesSynchronizer(Key key, SplitEventsManager eventsMa } private void registerMySegmentsSynchronizer(Key key, MySegmentsSynchronizer mySegmentsSynchronizer) { - mMySegmentsSynchronizerRegistry.registerMySegmentsSynchronizer(key.matchingKey(), + mMySegmentsSynchronizerRegistry.registerMySegmentsSynchronizer(key, mySegmentsSynchronizer); } diff --git a/src/test/java/io/split/android/client/service/SynchronizerTest.java b/src/test/java/io/split/android/client/service/SynchronizerTest.java index c0c32eabb..58b19db94 100644 --- a/src/test/java/io/split/android/client/service/SynchronizerTest.java +++ b/src/test/java/io/split/android/client/service/SynchronizerTest.java @@ -39,6 +39,7 @@ import io.split.android.client.RetryBackoffCounterTimerFactory; import io.split.android.client.SplitClientConfig; +import io.split.android.client.api.Key; import io.split.android.client.dtos.Event; import io.split.android.client.dtos.KeyImpression; import io.split.android.client.dtos.SplitChange; @@ -243,7 +244,7 @@ public void splitExecutorSchedule() { verify(mWorkManagerWrapper).removeWork(); verify(mWorkManagerWrapper, never()).scheduleWork(); - mSynchronizer.unregisterMySegmentsSynchronizer("userKey"); + mSynchronizer.unregisterMySegmentsSynchronizer(new Key("userKey")); } @Test @@ -544,7 +545,7 @@ public void loadLocalData() { when(loadMySegmentsTask.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.LOAD_LOCAL_MY_SEGMENTS)); when(mMySegmentsTaskFactory.createLoadMySegmentsTask()).thenReturn(loadMySegmentsTask); - ((MySegmentsSynchronizerRegistry) mSynchronizer).registerMySegmentsSynchronizer("", mMySegmentsSynchronizer); + ((MySegmentsSynchronizerRegistry) mSynchronizer).registerMySegmentsSynchronizer(new Key(""), mMySegmentsSynchronizer); mSynchronizer.loadAndSynchronizeSplits(); mSynchronizer.loadMySegmentsFromCache(); @@ -775,9 +776,9 @@ public void reschedulingEventsTaskCancelsPreviousWhenCallingSequentially() { public void registerMySegmentsSynchronizerDelegatesToRegistry() { setup(SplitClientConfig.builder().synchronizeInBackground(false).build()); - mSynchronizer.registerMySegmentsSynchronizer("userKey", mMySegmentsSynchronizer); + mSynchronizer.registerMySegmentsSynchronizer(new Key("userKey"), mMySegmentsSynchronizer); - verify(mMySegmentsSynchronizerRegistry).registerMySegmentsSynchronizer("userKey", mMySegmentsSynchronizer); + verify(mMySegmentsSynchronizerRegistry).registerMySegmentsSynchronizer(new Key("userKey"), mMySegmentsSynchronizer); } @Test diff --git a/src/test/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistryImplTest.java b/src/test/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistryImplTest.java index 9052de36a..a24909553 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistryImplTest.java +++ b/src/test/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistryImplTest.java @@ -6,6 +6,7 @@ import org.junit.Before; import org.junit.Test; +import io.split.android.client.api.Key; import io.split.android.client.service.mysegments.MySegmentUpdateParams; public class MySegmentsSynchronizerRegistryImplTest { @@ -21,7 +22,7 @@ public void setUp() { public void loadMySegmentsFromCacheGetCalledInEveryRegisteredSync() { MySegmentsSynchronizer syncMock = mock(MySegmentsSynchronizer.class); - mRegistry.registerMySegmentsSynchronizer("key", syncMock); + mRegistry.registerMySegmentsSynchronizer(new Key("key"), syncMock); mRegistry.loadMySegmentsFromCache(); verify(syncMock).loadMySegmentsFromCache(); @@ -31,7 +32,7 @@ public void loadMySegmentsFromCacheGetCalledInEveryRegisteredSync() { public void synchronizeMySegmentsGetCalledInEveryRegisteredSync() { MySegmentsSynchronizer syncMock = mock(MySegmentsSynchronizer.class); - mRegistry.registerMySegmentsSynchronizer("key", syncMock); + mRegistry.registerMySegmentsSynchronizer(new Key("key"), syncMock); mRegistry.synchronizeMySegments(); verify(syncMock).synchronizeMySegments(); @@ -42,7 +43,7 @@ public void forceMySegmentsSyncGetCalledInEveryRegisteredSync() { MySegmentsSynchronizer syncMock = mock(MySegmentsSynchronizer.class); MySegmentUpdateParams params = new MySegmentUpdateParams(4L, 1L, 2L); - mRegistry.registerMySegmentsSynchronizer("key", syncMock); + mRegistry.registerMySegmentsSynchronizer(new Key("key"), syncMock); mRegistry.forceMySegmentsSync(params); verify(syncMock).forceMySegmentsSync(params); @@ -52,7 +53,7 @@ public void forceMySegmentsSyncGetCalledInEveryRegisteredSync() { public void destroyGetCalledInEveryRegisteredSync() { MySegmentsSynchronizer syncMock = mock(MySegmentsSynchronizer.class); - mRegistry.registerMySegmentsSynchronizer("key", syncMock); + mRegistry.registerMySegmentsSynchronizer(new Key("key"), syncMock); mRegistry.destroy(); verify(syncMock).destroy(); @@ -62,7 +63,7 @@ public void destroyGetCalledInEveryRegisteredSync() { public void scheduleSegmentsSyncTaskGetCalledInEveryRegisteredSync() { MySegmentsSynchronizer syncMock = mock(MySegmentsSynchronizer.class); - mRegistry.registerMySegmentsSynchronizer("key", syncMock); + mRegistry.registerMySegmentsSynchronizer(new Key("key"), syncMock); mRegistry.scheduleSegmentsSyncTask(); verify(syncMock).scheduleSegmentsSyncTask(); @@ -72,7 +73,7 @@ public void scheduleSegmentsSyncTaskGetCalledInEveryRegisteredSync() { public void submitMySegmentsLoadingTaskGetCalledInEveryRegisteredSync() { MySegmentsSynchronizer syncMock = mock(MySegmentsSynchronizer.class); - mRegistry.registerMySegmentsSynchronizer("key", syncMock); + mRegistry.registerMySegmentsSynchronizer(new Key("key"), syncMock); mRegistry.submitMySegmentsLoadingTask(); verify(syncMock).submitMySegmentsLoadingTask(); @@ -82,7 +83,7 @@ public void submitMySegmentsLoadingTaskGetCalledInEveryRegisteredSync() { public void stopPeriodicFetchingGetCalledInEveryRegisteredSync() { MySegmentsSynchronizer syncMock = mock(MySegmentsSynchronizer.class); - mRegistry.registerMySegmentsSynchronizer("key", syncMock); + mRegistry.registerMySegmentsSynchronizer(new Key("key"), syncMock); mRegistry.stopPeriodicFetching(); verify(syncMock).stopPeriodicFetching(); @@ -92,8 +93,8 @@ public void stopPeriodicFetchingGetCalledInEveryRegisteredSync() { public void unregisterStopsTasksBeforeRemovingSync() { MySegmentsSynchronizer syncMock = mock(MySegmentsSynchronizer.class); - mRegistry.registerMySegmentsSynchronizer("key", syncMock); - mRegistry.unregisterMySegmentsSynchronizer("key"); + mRegistry.registerMySegmentsSynchronizer(new Key("key"), syncMock); + mRegistry.unregisterMySegmentsSynchronizer(new Key("key")); verify(syncMock).stopPeriodicFetching(); verify(syncMock).destroy(); @@ -105,10 +106,10 @@ public void callLoadSegmentsFromCacheForNewlyRegisteredSyncIfNecessary() { MySegmentsSynchronizer syncMock2 = mock(MySegmentsSynchronizer.class); MySegmentsSynchronizer syncMock3 = mock(MySegmentsSynchronizer.class); - mRegistry.registerMySegmentsSynchronizer("key", syncMock); + mRegistry.registerMySegmentsSynchronizer(new Key("key"), syncMock); mRegistry.loadMySegmentsFromCache(); - mRegistry.registerMySegmentsSynchronizer("new_key", syncMock2); - mRegistry.registerMySegmentsSynchronizer("new_key", syncMock3); + mRegistry.registerMySegmentsSynchronizer(new Key("new_key"), syncMock2); + mRegistry.registerMySegmentsSynchronizer(new Key("new_key"), syncMock3); verify(syncMock2).loadMySegmentsFromCache(); verify(syncMock3).loadMySegmentsFromCache(); @@ -120,10 +121,10 @@ public void callSynchronizeMySegmentsForNewlyRegisteredSyncIfNecessary() { MySegmentsSynchronizer syncMock2 = mock(MySegmentsSynchronizer.class); MySegmentsSynchronizer syncMock3 = mock(MySegmentsSynchronizer.class); - mRegistry.registerMySegmentsSynchronizer("key", syncMock); + mRegistry.registerMySegmentsSynchronizer(new Key("key"), syncMock); mRegistry.synchronizeMySegments(); - mRegistry.registerMySegmentsSynchronizer("new_key", syncMock2); - mRegistry.registerMySegmentsSynchronizer("new_key", syncMock3); + mRegistry.registerMySegmentsSynchronizer(new Key("new_key"), syncMock2); + mRegistry.registerMySegmentsSynchronizer(new Key("new_key"), syncMock3); verify(syncMock2).synchronizeMySegments(); verify(syncMock3).synchronizeMySegments(); @@ -135,10 +136,10 @@ public void callScheduleSegmentsSyncTaskForNewlyRegisteredSyncIfNecessary() { MySegmentsSynchronizer syncMock2 = mock(MySegmentsSynchronizer.class); MySegmentsSynchronizer syncMock3 = mock(MySegmentsSynchronizer.class); - mRegistry.registerMySegmentsSynchronizer("key", syncMock); + mRegistry.registerMySegmentsSynchronizer(new Key("key"), syncMock); mRegistry.scheduleSegmentsSyncTask(); - mRegistry.registerMySegmentsSynchronizer("new_key", syncMock2); - mRegistry.registerMySegmentsSynchronizer("new_key", syncMock3); + mRegistry.registerMySegmentsSynchronizer(new Key("new_key"), syncMock2); + mRegistry.registerMySegmentsSynchronizer(new Key("new_key"), syncMock3); verify(syncMock2).scheduleSegmentsSyncTask(); verify(syncMock3).scheduleSegmentsSyncTask(); diff --git a/src/test/java/io/split/android/client/shared/ClientComponentsRegisterImplTest.java b/src/test/java/io/split/android/client/shared/ClientComponentsRegisterImplTest.java index fac2fbd93..b78c13cd1 100644 --- a/src/test/java/io/split/android/client/shared/ClientComponentsRegisterImplTest.java +++ b/src/test/java/io/split/android/client/shared/ClientComponentsRegisterImplTest.java @@ -91,7 +91,7 @@ public void attributesSynchronizerIsRegistered() { public void mySegmentsSynchronizerIsRegistered() { register.registerComponents(mMatchingKey, mSplitEventsManager, mMySegmentsTaskFactory); - verify(mMySegmentsSynchronizerRegistry).registerMySegmentsSynchronizer("matching_key", mMySegmentsSynchronizer); + verify(mMySegmentsSynchronizerRegistry).registerMySegmentsSynchronizer(new Key("matching_key", "bucketing_key"), mMySegmentsSynchronizer); } @Test @@ -120,7 +120,7 @@ public void componentsAreCorrectlyUnregistered() { register.unregisterComponentsForKey(mMatchingKey); verify(mAttributesSynchronizerRegistry).unregisterAttributesSynchronizer("matching_key"); - verify(mMySegmentsSynchronizerRegistry).unregisterMySegmentsSynchronizer("matching_key"); + verify(mMySegmentsSynchronizerRegistry).unregisterMySegmentsSynchronizer(new Key("matching_key", "bucketing_key")); verify(mMySegmentsUpdateWorkerRegistry).unregisterMySegmentsUpdateWorker("matching_key"); verify(mMySegmentsNotificationProcessorRegistry).unregisterMembershipsProcessor("matching_key"); verify(mEventsManagerRegistry).unregisterEventsManager(mMatchingKey); From f4ea3265cda293548077129839546ec1e04c017c Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 3 Dec 2024 12:02:01 -0300 Subject: [PATCH 11/37] Rollout config --- .../helper/TestableSplitConfigBuilder.java | 10 ++- .../client/RolloutCacheConfiguration.java | 69 +++++++++++++++++++ .../android/client/SplitClientConfig.java | 31 +++++++-- .../client/service/ServiceConstants.java | 1 + .../RolloutCacheManagerConfig.java | 29 -------- .../synchronizer/RolloutCacheManagerImpl.java | 14 ++-- .../client/RolloutCacheConfigurationTest.java | 41 +++++++++++ .../android/client/SplitClientConfigTest.java | 32 +++++++++ .../synchronizer/RolloutCacheManagerTest.kt | 27 ++++---- 9 files changed, 201 insertions(+), 53 deletions(-) create mode 100644 src/main/java/io/split/android/client/RolloutCacheConfiguration.java delete mode 100644 src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java create mode 100644 src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java diff --git a/src/androidTest/java/helper/TestableSplitConfigBuilder.java b/src/androidTest/java/helper/TestableSplitConfigBuilder.java index 9c577162c..34449f445 100644 --- a/src/androidTest/java/helper/TestableSplitConfigBuilder.java +++ b/src/androidTest/java/helper/TestableSplitConfigBuilder.java @@ -2,6 +2,7 @@ import java.lang.reflect.Constructor; +import io.split.android.client.RolloutCacheConfiguration; import io.split.android.client.ServiceEndpoints; import io.split.android.client.SplitClientConfig; import io.split.android.client.SyncConfig; @@ -64,6 +65,7 @@ public class TestableSplitConfigBuilder { private String mPrefix = ""; private CertificatePinningConfiguration mCertificatePinningConfiguration; private long mImpressionsDedupeTimeInterval = ServiceConstants.DEFAULT_IMPRESSIONS_DEDUPE_TIME_INTERVAL; + private RolloutCacheConfiguration mRolloutCacheConfiguration = RolloutCacheConfiguration.builder().build(); public TestableSplitConfigBuilder() { mServiceEndpoints = ServiceEndpoints.builder().build(); @@ -274,6 +276,11 @@ public TestableSplitConfigBuilder impressionsDedupeTimeInterval(long impressions return this; } + public TestableSplitConfigBuilder rolloutCacheConfiguration(RolloutCacheConfiguration rolloutCacheConfiguration) { + this.mRolloutCacheConfiguration = rolloutCacheConfiguration; + return this; + } + public SplitClientConfig build() { Constructor constructor = SplitClientConfig.class.getDeclaredConstructors()[0]; constructor.setAccessible(true); @@ -329,7 +336,8 @@ public SplitClientConfig build() { mPrefix, mObserverCacheExpirationPeriod, mCertificatePinningConfiguration, - mImpressionsDedupeTimeInterval); + mImpressionsDedupeTimeInterval, + mRolloutCacheConfiguration); Logger.instance().setLevel(mLogLevel); return config; diff --git a/src/main/java/io/split/android/client/RolloutCacheConfiguration.java b/src/main/java/io/split/android/client/RolloutCacheConfiguration.java new file mode 100644 index 000000000..f1cd03cbd --- /dev/null +++ b/src/main/java/io/split/android/client/RolloutCacheConfiguration.java @@ -0,0 +1,69 @@ +package io.split.android.client; + +import io.split.android.client.service.ServiceConstants; +import io.split.android.client.utils.logger.Logger; + +public class RolloutCacheConfiguration { + + private final int mExpiration; + private final boolean mClearOnInit; + + private RolloutCacheConfiguration(int expiration, boolean clearOnInit) { + mExpiration = expiration; + mClearOnInit = clearOnInit; + } + + public int getExpiration() { + return mExpiration; + } + + public boolean clearOnInit() { + return mClearOnInit; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private static final int MIN_EXPIRATION_DAYS = 1; + + private int mExpiration = ServiceConstants.DEFAULT_ROLLOUT_CACHE_EXPIRATION; + private boolean mClearOnInit = false; + + private Builder() { + + } + + /** + * Set the expiration time for the rollout definitions cache, in days. Default is 10 days. + * @param expiration in days + * @return This builder + */ + public Builder expiration(int expiration) { + if (expiration < MIN_EXPIRATION_DAYS) { + Logger.w("Cache expiration must be at least 1 day. Using default value."); + mExpiration = ServiceConstants.DEFAULT_ROLLOUT_CACHE_EXPIRATION; + } else { + mExpiration = expiration; + } + + return this; + } + + /** + * Set if the rollout definitions cache should be cleared on initialization. Default is false. + * @param clearOnInit whether to clear cache on initialization. + * @return This builder + */ + public Builder clearOnInit(boolean clearOnInit) { + mClearOnInit = clearOnInit; + return this; + } + + public RolloutCacheConfiguration build() { + return new RolloutCacheConfiguration(mExpiration, mClearOnInit); + } + } +} diff --git a/src/main/java/io/split/android/client/SplitClientConfig.java b/src/main/java/io/split/android/client/SplitClientConfig.java index c2eff3e9b..b33567a32 100644 --- a/src/main/java/io/split/android/client/SplitClientConfig.java +++ b/src/main/java/io/split/android/client/SplitClientConfig.java @@ -131,6 +131,8 @@ public class SplitClientConfig { private final long mObserverCacheExpirationPeriod; private final CertificatePinningConfiguration mCertificatePinningConfiguration; private final long mImpressionsDedupeTimeInterval; + @NonNull + private final RolloutCacheConfiguration mRolloutCacheConfiguration; public static Builder builder() { return new Builder(); @@ -185,7 +187,8 @@ private SplitClientConfig(String endpoint, String prefix, long observerCacheExpirationPeriod, CertificatePinningConfiguration certificatePinningConfiguration, - long impressionsDedupeTimeInterval) { + long impressionsDedupeTimeInterval, + RolloutCacheConfiguration rolloutCacheConfiguration) { mEndpoint = endpoint; mEventsEndpoint = eventsEndpoint; mTelemetryEndpoint = telemetryEndpoint; @@ -243,6 +246,7 @@ private SplitClientConfig(String endpoint, mObserverCacheExpirationPeriod = observerCacheExpirationPeriod; mCertificatePinningConfiguration = certificatePinningConfiguration; mImpressionsDedupeTimeInterval = impressionsDedupeTimeInterval; + mRolloutCacheConfiguration = rolloutCacheConfiguration; } public String trafficType() { @@ -486,8 +490,8 @@ public long impressionsDedupeTimeInterval() { return mImpressionsDedupeTimeInterval; } - public boolean clearOnInit() { - return false; // TODO: to be implemented in the future + public RolloutCacheConfiguration rolloutCacheConfiguration() { + return mRolloutCacheConfiguration; } public static final class Builder { @@ -566,6 +570,8 @@ public static final class Builder { private long mImpressionsDedupeTimeInterval = ServiceConstants.DEFAULT_IMPRESSIONS_DEDUPE_TIME_INTERVAL; + private RolloutCacheConfiguration mRolloutCacheConfiguration = RolloutCacheConfiguration.builder().build(); + public Builder() { mServiceEndpoints = ServiceEndpoints.builder().build(); } @@ -1106,6 +1112,22 @@ public Builder impressionsDedupeTimeInterval(long impressionsDedupeTimeInterval) return this; } + /** + * Configuration for rollout definitions cache. + * + * @param rolloutCacheConfiguration Configuration object + * @return This builder + */ + public Builder rolloutCacheConfiguration(@NonNull RolloutCacheConfiguration rolloutCacheConfiguration) { + if (rolloutCacheConfiguration == null) { + Logger.w("Rollout cache configuration is null. Setting to default value."); + mRolloutCacheConfiguration = RolloutCacheConfiguration.builder().build(); + } else { + mRolloutCacheConfiguration = rolloutCacheConfiguration; + } + return this; + } + public SplitClientConfig build() { Logger.instance().setLevel(mLogLevel); @@ -1237,7 +1259,8 @@ public SplitClientConfig build() { mPrefix, mObserverCacheExpirationPeriod, mCertificatePinningConfiguration, - mImpressionsDedupeTimeInterval); + mImpressionsDedupeTimeInterval, + mRolloutCacheConfiguration); } private HttpProxy parseProxyHost(String proxyUri) { diff --git a/src/main/java/io/split/android/client/service/ServiceConstants.java b/src/main/java/io/split/android/client/service/ServiceConstants.java index 097b043b8..6f5c9f00c 100644 --- a/src/main/java/io/split/android/client/service/ServiceConstants.java +++ b/src/main/java/io/split/android/client/service/ServiceConstants.java @@ -11,6 +11,7 @@ public class ServiceConstants { public static final long MIN_INITIAL_DELAY = 5L; public static final int DEFAULT_RECORDS_PER_PUSH = 100; public static final long DEFAULT_SPLITS_CACHE_EXPIRATION_IN_SECONDS = TimeUnit.DAYS.toSeconds(10); // 10 days + public static final int DEFAULT_ROLLOUT_CACHE_EXPIRATION = 10; // 10 days public static final int MAX_ROWS_PER_QUERY = 100; diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java deleted file mode 100644 index 1de13b297..000000000 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.split.android.client.service.synchronizer; - -import androidx.annotation.VisibleForTesting; - -import io.split.android.client.SplitClientConfig; - -class RolloutCacheManagerConfig { - - private final long mCacheExpirationInDays; - private final boolean mClearOnInit; - - @VisibleForTesting - RolloutCacheManagerConfig(long cacheExpirationInDays, boolean clearOnInit) { - mCacheExpirationInDays = cacheExpirationInDays; - mClearOnInit = clearOnInit; - } - - static RolloutCacheManagerConfig from(SplitClientConfig splitClientConfig) { - return new RolloutCacheManagerConfig(splitClientConfig.cacheExpirationInSeconds(), splitClientConfig.clearOnInit()); - } - - long getCacheExpirationInDays() { - return mCacheExpirationInDays; - } - - boolean isClearOnInit() { - return mClearOnInit; - } -} diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java index f211ae62b..cc025db81 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java @@ -8,6 +8,7 @@ import java.util.concurrent.TimeUnit; +import io.split.android.client.RolloutCacheConfiguration; import io.split.android.client.SplitClientConfig; import io.split.android.client.service.CleanUpDatabaseTask; import io.split.android.client.service.executor.SplitTask; @@ -27,7 +28,7 @@ public class RolloutCacheManagerImpl implements RolloutCacheManager, SplitTask { @NonNull private final GeneralInfoStorage mGeneralInfoStorage; @NonNull - private final RolloutCacheManagerConfig mConfig; + private final RolloutCacheConfiguration mConfig; @NonNull private final RolloutDefinitionsCache[] mStorages; @NonNull @@ -35,11 +36,12 @@ public class RolloutCacheManagerImpl implements RolloutCacheManager, SplitTask { @NonNull private final EncryptionMigrationTask mEncryptionMigrationTask; - public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitStorageContainer storageContainer, + public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, + @NonNull SplitStorageContainer storageContainer, @NonNull CleanUpDatabaseTask cleanUpDatabaseTask, @NonNull EncryptionMigrationTask encryptionMigrationTask) { this(storageContainer.getGeneralInfoStorage(), - RolloutCacheManagerConfig.from(splitClientConfig), + splitClientConfig.rolloutCacheConfiguration(), cleanUpDatabaseTask, encryptionMigrationTask, storageContainer.getSplitsStorage(), @@ -49,7 +51,7 @@ public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @No @VisibleForTesting RolloutCacheManagerImpl(@NonNull GeneralInfoStorage generalInfoStorage, - @NonNull RolloutCacheManagerConfig config, + @NonNull RolloutCacheConfiguration config, @NonNull CleanUpDatabaseTask clean, @NonNull EncryptionMigrationTask encryptionMigrationTask, @NonNull RolloutDefinitionsCache... storages) { @@ -99,10 +101,10 @@ private boolean validateExpiration() { long lastUpdateTimestamp = mGeneralInfoStorage.getSplitsUpdateTimestamp(); long daysSinceLastUpdate = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastUpdateTimestamp); - if (daysSinceLastUpdate > mConfig.getCacheExpirationInDays()) { + if (daysSinceLastUpdate > mConfig.getExpiration()) { Logger.v("Clearing rollout definitions cache due to expiration"); return true; - } else if (mConfig.isClearOnInit()) { + } else if (mConfig.clearOnInit()) { long lastCacheClearTimestamp = mGeneralInfoStorage.getRolloutCacheLastClearTimestamp(); long daysSinceCacheClear = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastCacheClearTimestamp); diff --git a/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java b/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java new file mode 100644 index 000000000..ad1760fe8 --- /dev/null +++ b/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java @@ -0,0 +1,41 @@ +package io.split.android.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class RolloutCacheConfigurationTest { + + @Test + public void defaultValues() { + RolloutCacheConfiguration config = RolloutCacheConfiguration.builder().build(); + assertEquals(10, config.getExpiration()); + assertFalse(config.clearOnInit()); + } + + @Test + public void expirationIsCorrectlySet() { + RolloutCacheConfiguration.Builder builder = RolloutCacheConfiguration.builder(); + builder.expiration(1); + RolloutCacheConfiguration config = builder.build(); + assertEquals(1, config.getExpiration()); + } + + @Test + public void clearOnInitIsCorrectlySet() { + RolloutCacheConfiguration.Builder builder = RolloutCacheConfiguration.builder(); + builder.clearOnInit(true); + RolloutCacheConfiguration config = builder.build(); + assertTrue(config.clearOnInit()); + } + + @Test + public void negativeExpirationIsSetToDefault() { + RolloutCacheConfiguration.Builder builder = RolloutCacheConfiguration.builder(); + builder.expiration(-1); + RolloutCacheConfiguration config = builder.build(); + assertEquals(10, config.getExpiration()); + } +} diff --git a/src/test/java/io/split/android/client/SplitClientConfigTest.java b/src/test/java/io/split/android/client/SplitClientConfigTest.java index 8f3351ccb..83564c640 100644 --- a/src/test/java/io/split/android/client/SplitClientConfigTest.java +++ b/src/test/java/io/split/android/client/SplitClientConfigTest.java @@ -3,6 +3,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; import androidx.annotation.NonNull; @@ -224,6 +225,37 @@ public void observerCacheExpirationPeriodMatchesDedupeTimeIntervalWhenDedupeTime assertEquals(TimeUnit.HOURS.toMillis(4), config3.observerCacheExpirationPeriod()); } + @Test + public void rolloutCacheConfigurationDefaults() { + RolloutCacheConfiguration config = SplitClientConfig.builder().build().rolloutCacheConfiguration(); + + assertEquals(10, config.getExpiration()); + assertFalse(config.clearOnInit()); + } + + @Test + public void rolloutCacheConfigurationExpirationIsCorrectlySet() { + RolloutCacheConfiguration config = SplitClientConfig.builder() + .rolloutCacheConfiguration(RolloutCacheConfiguration.builder().expiration(1).clearOnInit(true).build()) + .build().rolloutCacheConfiguration(); + + assertEquals(1, config.getExpiration()); + assertTrue(config.clearOnInit()); + } + + @Test + public void nullRolloutCacheConfigurationSetsDefault() { + Queue logMessages = getLogMessagesQueue(); + RolloutCacheConfiguration config = SplitClientConfig.builder() + .logLevel(SplitLogLevel.WARNING) + .rolloutCacheConfiguration(null) + .build().rolloutCacheConfiguration(); + + assertEquals(10, config.getExpiration()); + assertFalse(config.clearOnInit()); + assertEquals(1, logMessages.size()); + } + @NonNull private static Queue getLogMessagesQueue() { Queue logMessages = new LinkedList<>(); diff --git a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt index 20e1b2c24..a473ed775 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt +++ b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt @@ -1,5 +1,6 @@ package io.split.android.client.service.synchronizer +import io.split.android.client.RolloutCacheConfiguration import io.split.android.client.service.CleanUpDatabaseTask import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.storage.RolloutDefinitionsCache @@ -36,7 +37,7 @@ class RolloutCacheManagerTest { @Test fun `validateCache calls listener`() { - mRolloutCacheManager = getCacheManager(10L, false) + mRolloutCacheManager = getCacheManager(10, false) val listener = mock(SplitTaskExecutionListener::class.java) mRolloutCacheManager.validateCache(listener) @@ -46,9 +47,9 @@ class RolloutCacheManagerTest { @Test fun `validateCache calls clear on storages when expiration is surpassed`() { - val mockedTimestamp = createMockedTimestamp(10L) + val mockedTimestamp = createMockedTimestamp(10) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) - mRolloutCacheManager = getCacheManager(9L, false) + mRolloutCacheManager = getCacheManager(9, false) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -60,7 +61,7 @@ class RolloutCacheManagerTest { fun `validateCache does not call clear on storages when expiration is not surpassed and clearOnInit is false`() { val mockedTimestamp = createMockedTimestamp(1L) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) - mRolloutCacheManager = getCacheManager(10L, false) + mRolloutCacheManager = getCacheManager(10, false) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -72,7 +73,7 @@ class RolloutCacheManagerTest { fun `validateCache calls clear on storages when expiration is not surpassed and clearOnInit is true`() { val mockedTimestamp = createMockedTimestamp(1L) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) - mRolloutCacheManager = getCacheManager(10L, true) + mRolloutCacheManager = getCacheManager(10, true) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -85,7 +86,7 @@ class RolloutCacheManagerTest { val mockedTimestamp = createMockedTimestamp(1L) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) - mRolloutCacheManager = getCacheManager(10L, true) + mRolloutCacheManager = getCacheManager(10, true) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -99,7 +100,7 @@ class RolloutCacheManagerTest { val mockedTimestamp = createMockedTimestamp(1L) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) - mRolloutCacheManager = getCacheManager(10L, true) + mRolloutCacheManager = getCacheManager(10, true) val listener = mock(SplitTaskExecutionListener::class.java) `when`(mSplitsCache.clear()).thenThrow(RuntimeException("Exception during clear")) @@ -114,7 +115,7 @@ class RolloutCacheManagerTest { val mockedTimestamp = createMockedTimestamp(1L) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) - mRolloutCacheManager = getCacheManager(10L, true) + mRolloutCacheManager = getCacheManager(10, true) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -126,7 +127,7 @@ class RolloutCacheManagerTest { val mockedTimestamp = createMockedTimestamp(1L) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) - mRolloutCacheManager = getCacheManager(10L, false) + mRolloutCacheManager = getCacheManager(10, false) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -135,7 +136,7 @@ class RolloutCacheManagerTest { @Test fun `validateCache executes cleanUpDatabaseTask`() { - mRolloutCacheManager = getCacheManager(10L, false) + mRolloutCacheManager = getCacheManager(10, false) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -144,15 +145,15 @@ class RolloutCacheManagerTest { @Test fun `validateCache executes encryptionMigrationTask`() { - mRolloutCacheManager = getCacheManager(10L, false) + mRolloutCacheManager = getCacheManager(10, false) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) verify(mEncryptionMigrationTask).execute() } - private fun getCacheManager(expiration: Long, clearOnInit: Boolean): RolloutCacheManager { - return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheManagerConfig(expiration, clearOnInit), mCleanUpDatabaseTask, mEncryptionMigrationTask, mSplitsCache, mSegmentsCache) + private fun getCacheManager(expiration: Int, clearOnInit: Boolean): RolloutCacheManager { + return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheConfiguration.builder().expiration(expiration).clearOnInit(clearOnInit).build(), mCleanUpDatabaseTask, mEncryptionMigrationTask, mSplitsCache, mSegmentsCache) } private fun createMockedTimestamp(period: Long): Long { From a9d47fef051446c6053fb1a4f1ff7d65c234005c Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 3 Dec 2024 15:13:15 -0300 Subject: [PATCH 12/37] Fixes --- .../attributes/AttributesIntegrationTest.java | 33 ++++++++++++++++++- .../synchronizer/RolloutCacheManagerImpl.java | 9 +++-- .../synchronizer/RolloutCacheManagerTest.kt | 24 ++++++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/androidTest/java/tests/integration/attributes/AttributesIntegrationTest.java b/src/androidTest/java/tests/integration/attributes/AttributesIntegrationTest.java index 085d99cb3..949d415aa 100644 --- a/src/androidTest/java/tests/integration/attributes/AttributesIntegrationTest.java +++ b/src/androidTest/java/tests/integration/attributes/AttributesIntegrationTest.java @@ -20,6 +20,7 @@ import helper.IntegrationHelper; import helper.SplitEventTaskHelper; import helper.TestableSplitConfigBuilder; +import io.split.android.client.ServiceEndpoints; import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFactory; @@ -32,15 +33,21 @@ import io.split.android.client.storage.db.SplitRoomDatabase; import io.split.android.client.storage.db.attributes.AttributesEntity; import io.split.android.client.utils.Json; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; public class AttributesIntegrationTest { private Context mContext; private SplitRoomDatabase mRoomDb; private SplitFactory mSplitFactory; + private MockWebServer mWebServer; @Before public void setup() { + setupServer(); mContext = InstrumentationRegistry.getInstrumentation().getContext(); mRoomDb = DatabaseHelper.getTestDatabase(mContext); mRoomDb.clearAllTables(); @@ -51,7 +58,7 @@ public void testPersistentAttributes() throws InterruptedException { insertSplitsFromFileIntoDB(); CountDownLatch readyLatch = new CountDownLatch(1); SplitClient client = getSplitClient(readyLatch, true, null); - readyLatch.await(5, TimeUnit.SECONDS); + readyLatch.await(10, TimeUnit.SECONDS); // 1. Evaluate without attrs Assert.assertEquals("on", client.getTreatment("workm")); @@ -224,8 +231,12 @@ private void setAttributesInClientAndEvaluate(SplitClient client) { private SplitClient getSplitClient(CountDownLatch readyLatch, boolean persistenceEnabled, String matchingKey) { if (mSplitFactory == null) { + final String url = mWebServer.url("/").url().toString(); + ServiceEndpoints endpoints = ServiceEndpoints.builder() + .apiEndpoint(url).eventsEndpoint(url).build(); SplitClientConfig config = new TestableSplitConfigBuilder() .enableDebug() + .serviceEndpoints(endpoints) .featuresRefreshRate(9999) .segmentsRefreshRate(9999) .impressionsRefreshRate(9999) @@ -272,4 +283,24 @@ private List getSplitListFromJson() { return changes.splits; } + + private void setupServer() { + mWebServer = new MockWebServer(); + + final Dispatcher dispatcher = new Dispatcher() { + + @Override + public MockResponse dispatch(RecordedRequest request) throws InterruptedException { + if (request.getPath().contains("/" + IntegrationHelper.ServicePath.MEMBERSHIPS)) { + return new MockResponse().setResponseCode(200).setBody(IntegrationHelper.dummyAllSegments()); + } else if (request.getPath().contains("/splitChanges")) { + return new MockResponse().setResponseCode(200) + .setBody(IntegrationHelper.emptySplitChanges(-1, 10000)); + } else { + return new MockResponse().setResponseCode(404); + } + } + }; + mWebServer.setDispatcher(dispatcher); + } } diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java index cc025db81..2659ef8c4 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java @@ -72,7 +72,7 @@ public void validateCache(SplitTaskExecutionListener listener) { execute(); Logger.v("Rollout cache manager: Migrating encryption"); mEncryptionMigrationTask.execute(); - Logger.v("Rollout cache manager: validation finished"); + Logger.v("Rollout cache manager: Validation finished"); listener.taskExecuted(SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK)); } catch (Exception ex) { Logger.e("Error occurred validating cache: " + ex.getMessage()); @@ -97,15 +97,18 @@ public SplitTaskExecutionInfo execute() { } private boolean validateExpiration() { - // calculate elapsed time since last update long lastUpdateTimestamp = mGeneralInfoStorage.getSplitsUpdateTimestamp(); + // calculate elapsed time since last update long daysSinceLastUpdate = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastUpdateTimestamp); - if (daysSinceLastUpdate > mConfig.getExpiration()) { + if (lastUpdateTimestamp > 0 && daysSinceLastUpdate > mConfig.getExpiration()) { Logger.v("Clearing rollout definitions cache due to expiration"); return true; } else if (mConfig.clearOnInit()) { long lastCacheClearTimestamp = mGeneralInfoStorage.getRolloutCacheLastClearTimestamp(); + if (lastCacheClearTimestamp < 1) { + return true; + } long daysSinceCacheClear = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastCacheClearTimestamp); // don't clear too soon diff --git a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt index a473ed775..aa710c378 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt +++ b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt @@ -152,6 +152,30 @@ class RolloutCacheManagerTest { verify(mEncryptionMigrationTask).execute() } + @Test + fun `default value for update timestamp does not clear cache`() { + `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(0L) + `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L) + mRolloutCacheManager = getCacheManager(10, false) + + mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) + + verify(mSplitsCache, times(0)).clear() + verify(mSegmentsCache, times(0)).clear() + } + + @Test + fun `default value for last clear timestamp clears cache when clearOnInit is true`() { + `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(createMockedTimestamp(System.currentTimeMillis())) + `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L) + mRolloutCacheManager = getCacheManager(10, true) + + mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) + + verify(mSplitsCache).clear() + verify(mSegmentsCache).clear() + } + private fun getCacheManager(expiration: Int, clearOnInit: Boolean): RolloutCacheManager { return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheConfiguration.builder().expiration(expiration).clearOnInit(clearOnInit).build(), mCleanUpDatabaseTask, mEncryptionMigrationTask, mSplitsCache, mSegmentsCache) } From c8eefc8f7f9b9a86205e35771c94e2581095af6a Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 4 Dec 2024 13:41:43 -0300 Subject: [PATCH 13/37] WIP int tests --- .../java/helper/IntegrationHelper.java | 61 +++--- .../observer/DedupeIntegrationTest.java | 3 +- .../integration/FlagsSpecInRequestTest.java | 2 +- .../integration/InitialChangeNumberTest.java | 2 +- .../SplitsTwoDifferentApiKeyTest.java | 2 +- .../matcher/SemverMatcherTest.java | 2 +- .../matcher/UnsupportedMatcherTest.java | 2 +- .../RolloutCacheManagerIntegrationTest.java | 194 ++++++++++++++++++ .../sets/FlagSetsEvaluationTest.java | 2 +- .../sets/FlagSetsMultipleFactoryTest.java | 2 +- .../integration/sets/FlagSetsPollingTest.java | 2 +- .../streaming/SplitsKillProcessTest.java | 2 +- .../streaming/SplitsSyncProcessTest.java | 2 +- .../synchronizer/RolloutCacheManagerImpl.java | 4 +- 14 files changed, 242 insertions(+), 40 deletions(-) create mode 100644 src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java diff --git a/src/androidTest/java/helper/IntegrationHelper.java b/src/androidTest/java/helper/IntegrationHelper.java index 09a17e8b0..528f730fa 100644 --- a/src/androidTest/java/helper/IntegrationHelper.java +++ b/src/androidTest/java/helper/IntegrationHelper.java @@ -27,6 +27,7 @@ import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import fake.HttpClientMock; import fake.HttpResponseMock; @@ -390,40 +391,45 @@ public static Set asSet(T... elements) { return result; } - /** - * A simple interface to allow us to define the response for a given path - */ - public interface ResponseClosure { - HttpResponseMock onResponse(URI uri, - HttpMethod httpMethod, - String body); + public static long getTimestampDaysAgo(int days) { + return System.currentTimeMillis() - TimeUnit.DAYS.toMillis(days); + } - static String getSinceFromUri(URI uri) { - try { - return parse(uri.getQuery()).get("since"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + + public static String getSinceFromUri(URI uri) { + try { + return parse(uri.getQuery()).get("since"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); } + } - static Map parse(String query) throws UnsupportedEncodingException { - Map queryPairs = new HashMap<>(); - String[] pairs = query.split("&"); + static Map parse(String query) throws UnsupportedEncodingException { + Map queryPairs = new HashMap<>(); + String[] pairs = query.split("&"); - for (String pair : pairs) { - int idx = pair.indexOf("="); - try { - String key = URLDecoder.decode(pair.substring(0, idx), "UTF-8"); - String value = URLDecoder.decode(pair.substring(idx + 1), "UTF-8"); + for (String pair : pairs) { + int idx = pair.indexOf("="); + try { + String key = URLDecoder.decode(pair.substring(0, idx), "UTF-8"); + String value = URLDecoder.decode(pair.substring(idx + 1), "UTF-8"); - queryPairs.put(key, value); - } catch (Exception e) { - e.printStackTrace(); - } + queryPairs.put(key, value); + } catch (Exception e) { + e.printStackTrace(); } - - return queryPairs; } + + return queryPairs; + } + + /** + * A simple interface to allow us to define the response for a given path + */ + public interface ResponseClosure { + HttpResponseMock onResponse(URI uri, + HttpMethod httpMethod, + String body); } /** @@ -435,5 +441,6 @@ public interface StreamingResponseClosure { public static class ServicePath { public static final String MEMBERSHIPS = "memberships"; + public static final String SPLIT_CHANGES = "splitChanges"; } } diff --git a/src/androidTest/java/io/split/android/client/service/impressions/observer/DedupeIntegrationTest.java b/src/androidTest/java/io/split/android/client/service/impressions/observer/DedupeIntegrationTest.java index 105d47d3d..d3f9152f4 100644 --- a/src/androidTest/java/io/split/android/client/service/impressions/observer/DedupeIntegrationTest.java +++ b/src/androidTest/java/io/split/android/client/service/impressions/observer/DedupeIntegrationTest.java @@ -3,7 +3,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; + +import static helper.IntegrationHelper.getSinceFromUri; import android.content.Context; diff --git a/src/androidTest/java/tests/integration/FlagsSpecInRequestTest.java b/src/androidTest/java/tests/integration/FlagsSpecInRequestTest.java index 12ecce898..c289ef64e 100644 --- a/src/androidTest/java/tests/integration/FlagsSpecInRequestTest.java +++ b/src/androidTest/java/tests/integration/FlagsSpecInRequestTest.java @@ -3,7 +3,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; -import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; +import static helper.IntegrationHelper.getSinceFromUri; import android.content.Context; diff --git a/src/androidTest/java/tests/integration/InitialChangeNumberTest.java b/src/androidTest/java/tests/integration/InitialChangeNumberTest.java index 434fd63f9..a8b64a13e 100644 --- a/src/androidTest/java/tests/integration/InitialChangeNumberTest.java +++ b/src/androidTest/java/tests/integration/InitialChangeNumberTest.java @@ -1,6 +1,6 @@ package tests.integration; -import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; +import static helper.IntegrationHelper.getSinceFromUri; import android.content.Context; diff --git a/src/androidTest/java/tests/integration/SplitsTwoDifferentApiKeyTest.java b/src/androidTest/java/tests/integration/SplitsTwoDifferentApiKeyTest.java index 1183f7d7a..548daad4d 100644 --- a/src/androidTest/java/tests/integration/SplitsTwoDifferentApiKeyTest.java +++ b/src/androidTest/java/tests/integration/SplitsTwoDifferentApiKeyTest.java @@ -2,7 +2,7 @@ import static java.lang.Thread.sleep; -import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; +import static helper.IntegrationHelper.getSinceFromUri; import android.content.Context; diff --git a/src/androidTest/java/tests/integration/matcher/SemverMatcherTest.java b/src/androidTest/java/tests/integration/matcher/SemverMatcherTest.java index d0e63c4e8..14fa837ae 100644 --- a/src/androidTest/java/tests/integration/matcher/SemverMatcherTest.java +++ b/src/androidTest/java/tests/integration/matcher/SemverMatcherTest.java @@ -2,7 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; +import static helper.IntegrationHelper.getSinceFromUri; import android.content.Context; diff --git a/src/androidTest/java/tests/integration/matcher/UnsupportedMatcherTest.java b/src/androidTest/java/tests/integration/matcher/UnsupportedMatcherTest.java index 928d1d93a..e11af1561 100644 --- a/src/androidTest/java/tests/integration/matcher/UnsupportedMatcherTest.java +++ b/src/androidTest/java/tests/integration/matcher/UnsupportedMatcherTest.java @@ -4,7 +4,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; +import static helper.IntegrationHelper.getSinceFromUri; import android.content.Context; diff --git a/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java b/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java new file mode 100644 index 000000000..a1c0a2e2a --- /dev/null +++ b/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java @@ -0,0 +1,194 @@ +package tests.integration.rollout; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static helper.IntegrationHelper.buildFactory; +import static helper.IntegrationHelper.dummyApiKey; +import static helper.IntegrationHelper.dummyUserKey; +import static helper.IntegrationHelper.emptySplitChanges; +import static helper.IntegrationHelper.getTimestampDaysAgo; +import static helper.IntegrationHelper.randomizedAllSegments; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import helper.DatabaseHelper; +import helper.FileHelper; +import helper.IntegrationHelper; +import io.split.android.client.RolloutCacheConfiguration; +import io.split.android.client.ServiceEndpoints; +import io.split.android.client.SplitClientConfig; +import io.split.android.client.SplitFactory; +import io.split.android.client.dtos.Split; +import io.split.android.client.dtos.SplitChange; +import io.split.android.client.events.SplitEvent; +import io.split.android.client.storage.db.GeneralInfoEntity; +import io.split.android.client.storage.db.MyLargeSegmentEntity; +import io.split.android.client.storage.db.MySegmentEntity; +import io.split.android.client.storage.db.SplitEntity; +import io.split.android.client.storage.db.SplitRoomDatabase; +import io.split.android.client.utils.Json; +import io.split.android.client.utils.logger.SplitLogLevel; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import tests.integration.shared.TestingHelper; + +public class RolloutCacheManagerIntegrationTest { + + private final AtomicReference mSinceFromUri = new AtomicReference<>(null); + private MockWebServer mWebServer; + private SplitRoomDatabase mRoomDb; + private Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + + @Before + public void setUp() { + mSinceFromUri.set(null); + setupServer(); + mRoomDb = DatabaseHelper.getTestDatabase(mContext); + mRoomDb.clearAllTables(); + } + + @Test + public void expirationPeriodIsUsed() throws InterruptedException { + + +// +// Verify persistent storage of flags & segments is cleared + + // Preload DB with update timestamp of 1 day ago + long oldTimestamp = getTimestampDaysAgo(1); + preloadDb(oldTimestamp, 0L, 18000L); + + // Track initial values + List initialFlags = mRoomDb.splitDao().getAll(); + List initialSegments = mRoomDb.mySegmentDao().getAll(); + List initialLargeSegments = mRoomDb.myLargeSegmentDao().getAll(); + long initialChangeNumber = mRoomDb.generalInfoDao().getByName(GeneralInfoEntity.CHANGE_NUMBER_INFO).getLongValue(); + + // Initialize SDK with an expiration of 1 day + CountDownLatch readyLatch = new CountDownLatch(1); + SplitFactory factory = getSplitFactory(RolloutCacheConfiguration.builder().expiration(1).build()); + + // Track final values + List finalFlags = mRoomDb.splitDao().getAll(); + List finalSegments = mRoomDb.mySegmentDao().getAll(); + List finalLargeSegments = mRoomDb.myLargeSegmentDao().getAll(); + long finalChangeNumber = mRoomDb.generalInfoDao().getByName(GeneralInfoEntity.CHANGE_NUMBER_INFO).getLongValue(); + + // Wait for ready + factory.client().on(SplitEvent.SDK_READY, TestingHelper.testTask(readyLatch)); + boolean readyAwait = readyLatch.await(10, TimeUnit.SECONDS); + + assertTrue(readyAwait); + assertEquals(2, initialFlags.size()); + assertEquals(1, initialSegments.size()); + assertEquals(1, initialLargeSegments.size()); + assertEquals(18000L, initialChangeNumber); + assertEquals(0, finalFlags.size()); + assertEquals(0, finalSegments.size()); + assertEquals(0, finalLargeSegments.size()); + assertEquals(-1, finalChangeNumber); + assertTrue(0L < mRoomDb.generalInfoDao() + .getByName("rolloutCacheLastClearTimestamp").getLongValue()); + assertEquals("-1", mSinceFromUri.get()); + } + + private SplitFactory getSplitFactory(RolloutCacheConfiguration rolloutCacheConfiguration) { + final String url = mWebServer.url("/").url().toString(); + ServiceEndpoints endpoints = ServiceEndpoints.builder() + .apiEndpoint(url).eventsEndpoint(url).build(); + SplitClientConfig.Builder builder = new SplitClientConfig.Builder() + .serviceEndpoints(endpoints) + .streamingEnabled(false) + .featuresRefreshRate(9999) + .segmentsRefreshRate(9999) + .impressionsRefreshRate(9999) + .logLevel(SplitLogLevel.VERBOSE) + .streamingEnabled(false); + + if (rolloutCacheConfiguration != null) { + builder.rolloutCacheConfiguration(rolloutCacheConfiguration); + } + + SplitClientConfig config = builder + .build(); + + return buildFactory( + dummyApiKey(), dummyUserKey(), + config, mContext, null, mRoomDb); + } + + private void setupServer() { + mWebServer = new MockWebServer(); + + final Dispatcher dispatcher = new Dispatcher() { + + @Override + public MockResponse dispatch(RecordedRequest request) throws InterruptedException { + Thread.sleep(1000); + if (request.getPath().contains("/" + IntegrationHelper.ServicePath.MEMBERSHIPS)) { + return new MockResponse().setResponseCode(200).setBody(randomizedAllSegments()); + } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.SPLIT_CHANGES)) { + mSinceFromUri.compareAndSet(null, IntegrationHelper.getSinceFromUri(request.getRequestUrl().uri())); + return new MockResponse().setResponseCode(200) + .setBody(emptySplitChanges(-1, 10000)); + } else { + return new MockResponse().setResponseCode(404); + } + } + }; + mWebServer.setDispatcher(dispatcher); + } + + private void preloadDb(long updateTimestamp, long lastClearTimestamp, long changeNumber) { + List splitListFromJson = getSplitListFromJson(); + List entities = splitListFromJson.stream() + .filter(split -> split.name != null) + .map(split -> { + SplitEntity result = new SplitEntity(); + result.setName(split.name); + result.setBody(Json.toJson(split)); + + return result; + }).collect(Collectors.toList()); + + mRoomDb.generalInfoDao().update(new GeneralInfoEntity(GeneralInfoEntity.CHANGE_NUMBER_INFO, 1)); + mRoomDb.generalInfoDao().update(new GeneralInfoEntity(GeneralInfoEntity.SPLITS_UPDATE_TIMESTAMP, updateTimestamp)); + mRoomDb.generalInfoDao().update(new GeneralInfoEntity("rolloutCacheLastClearTimestamp", lastClearTimestamp)); + mRoomDb.generalInfoDao().update(new GeneralInfoEntity(GeneralInfoEntity.CHANGE_NUMBER_INFO, changeNumber)); + + MyLargeSegmentEntity largeSegment = new MyLargeSegmentEntity(); + largeSegment.setSegmentList("{\"m1\":1,\"m2\":1}"); + largeSegment.setUserKey(dummyUserKey().matchingKey()); + largeSegment.setUpdatedAt(System.currentTimeMillis()); + mRoomDb.myLargeSegmentDao().update(largeSegment); + + MySegmentEntity segment = new MySegmentEntity(); + segment.setSegmentList("m1,m2"); + segment.setUserKey(dummyUserKey().matchingKey()); + segment.setUpdatedAt(System.currentTimeMillis()); + mRoomDb.mySegmentDao().update(segment); + mRoomDb.splitDao().insert(entities); + } + + private List getSplitListFromJson() { + FileHelper fileHelper = new FileHelper(); + String s = fileHelper.loadFileContent(mContext, "attributes_test_split_change.json"); + + SplitChange changes = Json.fromJson(s, SplitChange.class); + + return changes.splits; + } +} diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsEvaluationTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsEvaluationTest.java index 86f42034f..5507e432b 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsEvaluationTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsEvaluationTest.java @@ -2,7 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; +import static helper.IntegrationHelper.getSinceFromUri; import android.content.Context; diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsMultipleFactoryTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsMultipleFactoryTest.java index 059d7c97d..da3d18250 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsMultipleFactoryTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsMultipleFactoryTest.java @@ -2,7 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; +import static helper.IntegrationHelper.getSinceFromUri; import android.content.Context; import android.database.Cursor; diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java index 6faaab513..06146259a 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java @@ -3,7 +3,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; +import static helper.IntegrationHelper.getSinceFromUri; import android.content.Context; diff --git a/src/androidTest/java/tests/integration/streaming/SplitsKillProcessTest.java b/src/androidTest/java/tests/integration/streaming/SplitsKillProcessTest.java index bf7fea927..45b0369d5 100644 --- a/src/androidTest/java/tests/integration/streaming/SplitsKillProcessTest.java +++ b/src/androidTest/java/tests/integration/streaming/SplitsKillProcessTest.java @@ -1,7 +1,7 @@ package tests.integration.streaming; import static java.lang.Thread.sleep; -import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; +import static helper.IntegrationHelper.getSinceFromUri; import android.content.Context; diff --git a/src/androidTest/java/tests/integration/streaming/SplitsSyncProcessTest.java b/src/androidTest/java/tests/integration/streaming/SplitsSyncProcessTest.java index 536a9c7d4..3675b4612 100644 --- a/src/androidTest/java/tests/integration/streaming/SplitsSyncProcessTest.java +++ b/src/androidTest/java/tests/integration/streaming/SplitsSyncProcessTest.java @@ -1,7 +1,7 @@ package tests.integration.streaming; import static java.lang.Thread.sleep; -import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; +import static helper.IntegrationHelper.getSinceFromUri; import android.content.Context; diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java index 2659ef8c4..c852e4a85 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java @@ -101,7 +101,7 @@ private boolean validateExpiration() { // calculate elapsed time since last update long daysSinceLastUpdate = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastUpdateTimestamp); - if (lastUpdateTimestamp > 0 && daysSinceLastUpdate > mConfig.getExpiration()) { + if (lastUpdateTimestamp > 0 && daysSinceLastUpdate >= mConfig.getExpiration()) { Logger.v("Clearing rollout definitions cache due to expiration"); return true; } else if (mConfig.clearOnInit()) { @@ -112,7 +112,7 @@ private boolean validateExpiration() { long daysSinceCacheClear = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastCacheClearTimestamp); // don't clear too soon - if (daysSinceCacheClear > MIN_CACHE_CLEAR_DAYS) { + if (daysSinceCacheClear >= MIN_CACHE_CLEAR_DAYS) { Logger.v("Forcing rollout definitions cache clear"); return true; } From f65f0ae0664674cda2ad3cedf3fc9b3c04edec09 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 4 Dec 2024 17:27:27 -0300 Subject: [PATCH 14/37] WIP --- .../RolloutCacheManagerIntegrationTest.java | 135 +++++++++++++++--- .../client/RolloutCacheConfiguration.java | 2 +- .../android/client/SplitClientConfig.java | 4 +- .../client/service/ServiceConstants.java | 2 +- .../synchronizer/RolloutCacheManagerImpl.java | 2 +- .../client/RolloutCacheConfigurationTest.java | 4 +- .../android/client/SplitClientConfigTest.java | 6 +- 7 files changed, 124 insertions(+), 31 deletions(-) diff --git a/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java b/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java index a1c0a2e2a..e922053ec 100644 --- a/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java +++ b/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java @@ -1,11 +1,11 @@ package tests.integration.rollout; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static helper.IntegrationHelper.buildFactory; import static helper.IntegrationHelper.dummyApiKey; import static helper.IntegrationHelper.dummyUserKey; -import static helper.IntegrationHelper.emptySplitChanges; import static helper.IntegrationHelper.getTimestampDaysAgo; import static helper.IntegrationHelper.randomizedAllSegments; @@ -29,6 +29,7 @@ import io.split.android.client.ServiceEndpoints; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFactory; +import io.split.android.client.dtos.SegmentsChange; import io.split.android.client.dtos.Split; import io.split.android.client.dtos.SplitChange; import io.split.android.client.events.SplitEvent; @@ -50,7 +51,8 @@ public class RolloutCacheManagerIntegrationTest { private final AtomicReference mSinceFromUri = new AtomicReference<>(null); private MockWebServer mWebServer; private SplitRoomDatabase mRoomDb; - private Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + private CountDownLatch mRequestCountdownLatch; @Before public void setUp() { @@ -58,18 +60,91 @@ public void setUp() { setupServer(); mRoomDb = DatabaseHelper.getTestDatabase(mContext); mRoomDb.clearAllTables(); + mRequestCountdownLatch = new CountDownLatch(1); } @Test public void expirationPeriodIsUsed() throws InterruptedException { + test(getTimestampDaysAgo(1), RolloutCacheConfiguration.builder().expiration(1)); + } + + @Test + public void clearOnInitClearsCacheOnStartup() throws InterruptedException { + test(System.currentTimeMillis(), RolloutCacheConfiguration.builder().clearOnInit(true)); + } + + @Test + public void repeatedInitWithClearOnInitSetToTrueDoesNotClearIfMinDaysHasNotElapsed() throws InterruptedException { + // Preload DB with update timestamp of 1 day ago + long oldTimestamp = System.currentTimeMillis(); + preloadDb(oldTimestamp, 0L, 8000L); + + // Track initial values + List initialFlags = mRoomDb.splitDao().getAll(); + List initialSegments = mRoomDb.mySegmentDao().getAll(); + List initialLargeSegments = mRoomDb.myLargeSegmentDao().getAll(); + long initialChangeNumber = mRoomDb.generalInfoDao().getByName(GeneralInfoEntity.CHANGE_NUMBER_INFO).getLongValue(); + + CountDownLatch readyLatch = new CountDownLatch(1); + SplitFactory factory = getSplitFactory(RolloutCacheConfiguration.builder().clearOnInit(true).build()); + Thread.sleep(1000); + + // Track intermediate values + List intermediateFlags = mRoomDb.splitDao().getAll(); + List intermediateSegments = mRoomDb.mySegmentDao().getAll(); + List intermediateLargeSegments = mRoomDb.myLargeSegmentDao().getAll(); + long intermediateChangeNumber = mRoomDb.generalInfoDao().getByName(GeneralInfoEntity.CHANGE_NUMBER_INFO).getLongValue(); + + // Resume server responses after tracking DB values + mRequestCountdownLatch.countDown(); + + // Wait for ready + factory.client().on(SplitEvent.SDK_READY, TestingHelper.testTask(readyLatch)); + boolean readyAwait = readyLatch.await(10, TimeUnit.SECONDS); + + factory.destroy(); + mRequestCountdownLatch = new CountDownLatch(1); + + preloadDb(null, null, null); + SplitFactory factory2 = getSplitFactory(RolloutCacheConfiguration.builder().clearOnInit(true).build()); + Thread.sleep(1000); + // Track intermediate values + List factory2Flags = mRoomDb.splitDao().getAll(); + List factory2Segments = mRoomDb.mySegmentDao().getAll(); + List factory2LargeSegments = mRoomDb.myLargeSegmentDao().getAll(); + long factory2ChangeNumber = mRoomDb.generalInfoDao().getByName(GeneralInfoEntity.CHANGE_NUMBER_INFO).getLongValue(); -// -// Verify persistent storage of flags & segments is cleared + // initial values + assertTrue(readyAwait); + assertEquals(2, initialFlags.size()); + assertEquals(1, initialSegments.size()); + assertFalse(Json.fromJson(initialSegments.get(0).getSegmentList(), SegmentsChange.class).getSegments().isEmpty()); + assertFalse(Json.fromJson(initialLargeSegments.get(0).getSegmentList(), SegmentsChange.class).getSegments().isEmpty()); + assertEquals(8000L, initialChangeNumber); + + // values after clear + assertEquals(1, intermediateSegments.size()); + assertTrue(Json.fromJson(intermediateSegments.get(0).getSegmentList(), SegmentsChange.class).getSegments().isEmpty()); + assertEquals(1, intermediateLargeSegments.size()); + assertEquals(0, intermediateFlags.size()); + assertTrue(Json.fromJson(intermediateLargeSegments.get(0).getSegmentList(), SegmentsChange.class).getSegments().isEmpty()); + assertEquals(-1, intermediateChangeNumber); + + // values after second init (values were reinserted into DB); no clear + assertEquals(2, factory2Flags.size()); + assertEquals(1, factory2Segments.size()); + assertFalse(Json.fromJson(factory2Segments.get(0).getSegmentList(), SegmentsChange.class).getSegments().isEmpty()); + assertFalse(Json.fromJson(factory2LargeSegments.get(0).getSegmentList(), SegmentsChange.class).getSegments().isEmpty()); + assertEquals(10000L, factory2ChangeNumber); + assertTrue(0L < mRoomDb.generalInfoDao() + .getByName("rolloutCacheLastClearTimestamp").getLongValue()); + } + private void test(long timestampDaysAgo, RolloutCacheConfiguration.Builder configBuilder) throws InterruptedException { // Preload DB with update timestamp of 1 day ago - long oldTimestamp = getTimestampDaysAgo(1); - preloadDb(oldTimestamp, 0L, 18000L); + long oldTimestamp = timestampDaysAgo; + preloadDb(oldTimestamp, 0L, 8000L); // Track initial values List initialFlags = mRoomDb.splitDao().getAll(); @@ -77,28 +152,41 @@ public void expirationPeriodIsUsed() throws InterruptedException { List initialLargeSegments = mRoomDb.myLargeSegmentDao().getAll(); long initialChangeNumber = mRoomDb.generalInfoDao().getByName(GeneralInfoEntity.CHANGE_NUMBER_INFO).getLongValue(); - // Initialize SDK with an expiration of 1 day + // Initialize SDK CountDownLatch readyLatch = new CountDownLatch(1); - SplitFactory factory = getSplitFactory(RolloutCacheConfiguration.builder().expiration(1).build()); + SplitFactory factory = getSplitFactory(configBuilder.build()); + Thread.sleep(1000); + + // Track final values + verify(factory, readyLatch, initialFlags, initialSegments, initialLargeSegments, initialChangeNumber); + } + private void verify(SplitFactory factory, CountDownLatch readyLatch, List initialFlags, List initialSegments, List initialLargeSegments, long initialChangeNumber) throws InterruptedException { // Track final values List finalFlags = mRoomDb.splitDao().getAll(); List finalSegments = mRoomDb.mySegmentDao().getAll(); List finalLargeSegments = mRoomDb.myLargeSegmentDao().getAll(); long finalChangeNumber = mRoomDb.generalInfoDao().getByName(GeneralInfoEntity.CHANGE_NUMBER_INFO).getLongValue(); + // Resume server responses after tracking DB values + mRequestCountdownLatch.countDown(); + // Wait for ready factory.client().on(SplitEvent.SDK_READY, TestingHelper.testTask(readyLatch)); boolean readyAwait = readyLatch.await(10, TimeUnit.SECONDS); + // Verify assertTrue(readyAwait); assertEquals(2, initialFlags.size()); assertEquals(1, initialSegments.size()); - assertEquals(1, initialLargeSegments.size()); - assertEquals(18000L, initialChangeNumber); + assertFalse(Json.fromJson(initialSegments.get(0).getSegmentList(), SegmentsChange.class).getSegments().isEmpty()); + assertFalse(Json.fromJson(initialLargeSegments.get(0).getSegmentList(), SegmentsChange.class).getSegments().isEmpty()); + assertEquals(8000L, initialChangeNumber); + assertEquals(1, finalSegments.size()); + assertTrue(Json.fromJson(finalSegments.get(0).getSegmentList(), SegmentsChange.class).getSegments().isEmpty()); assertEquals(0, finalFlags.size()); - assertEquals(0, finalSegments.size()); - assertEquals(0, finalLargeSegments.size()); + assertEquals(1, finalLargeSegments.size()); + assertTrue(Json.fromJson(finalLargeSegments.get(0).getSegmentList(), SegmentsChange.class).getSegments().isEmpty()); assertEquals(-1, finalChangeNumber); assertTrue(0L < mRoomDb.generalInfoDao() .getByName("rolloutCacheLastClearTimestamp").getLongValue()); @@ -137,13 +225,13 @@ private void setupServer() { @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { - Thread.sleep(1000); + mRequestCountdownLatch.await(); if (request.getPath().contains("/" + IntegrationHelper.ServicePath.MEMBERSHIPS)) { return new MockResponse().setResponseCode(200).setBody(randomizedAllSegments()); } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.SPLIT_CHANGES)) { mSinceFromUri.compareAndSet(null, IntegrationHelper.getSinceFromUri(request.getRequestUrl().uri())); return new MockResponse().setResponseCode(200) - .setBody(emptySplitChanges(-1, 10000)); + .setBody(IntegrationHelper.emptySplitChanges(-1, 10000L)); } else { return new MockResponse().setResponseCode(404); } @@ -152,7 +240,7 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio mWebServer.setDispatcher(dispatcher); } - private void preloadDb(long updateTimestamp, long lastClearTimestamp, long changeNumber) { + private void preloadDb(Long updateTimestamp, Long lastClearTimestamp, Long changeNumber) { List splitListFromJson = getSplitListFromJson(); List entities = splitListFromJson.stream() .filter(split -> split.name != null) @@ -164,19 +252,24 @@ private void preloadDb(long updateTimestamp, long lastClearTimestamp, long chang return result; }).collect(Collectors.toList()); - mRoomDb.generalInfoDao().update(new GeneralInfoEntity(GeneralInfoEntity.CHANGE_NUMBER_INFO, 1)); - mRoomDb.generalInfoDao().update(new GeneralInfoEntity(GeneralInfoEntity.SPLITS_UPDATE_TIMESTAMP, updateTimestamp)); - mRoomDb.generalInfoDao().update(new GeneralInfoEntity("rolloutCacheLastClearTimestamp", lastClearTimestamp)); - mRoomDb.generalInfoDao().update(new GeneralInfoEntity(GeneralInfoEntity.CHANGE_NUMBER_INFO, changeNumber)); + if (updateTimestamp != null) { + mRoomDb.generalInfoDao().update(new GeneralInfoEntity(GeneralInfoEntity.SPLITS_UPDATE_TIMESTAMP, updateTimestamp)); + } + if (lastClearTimestamp != null) { + mRoomDb.generalInfoDao().update(new GeneralInfoEntity("rolloutCacheLastClearTimestamp", lastClearTimestamp)); + } + if (changeNumber != null) { + mRoomDb.generalInfoDao().update(new GeneralInfoEntity(GeneralInfoEntity.CHANGE_NUMBER_INFO, changeNumber)); + } MyLargeSegmentEntity largeSegment = new MyLargeSegmentEntity(); - largeSegment.setSegmentList("{\"m1\":1,\"m2\":1}"); + largeSegment.setSegmentList("{\"k\":[{\"n\":\"ls1\"},{\"n\":\"ls2\"}],\"cn\":null}"); largeSegment.setUserKey(dummyUserKey().matchingKey()); largeSegment.setUpdatedAt(System.currentTimeMillis()); mRoomDb.myLargeSegmentDao().update(largeSegment); MySegmentEntity segment = new MySegmentEntity(); - segment.setSegmentList("m1,m2"); + segment.setSegmentList("{\"k\":[{\"n\":\"s1\"},{\"n\":\"s2\"}],\"cn\":null}"); segment.setUserKey(dummyUserKey().matchingKey()); segment.setUpdatedAt(System.currentTimeMillis()); mRoomDb.mySegmentDao().update(segment); diff --git a/src/main/java/io/split/android/client/RolloutCacheConfiguration.java b/src/main/java/io/split/android/client/RolloutCacheConfiguration.java index f1cd03cbd..fde7d6acf 100644 --- a/src/main/java/io/split/android/client/RolloutCacheConfiguration.java +++ b/src/main/java/io/split/android/client/RolloutCacheConfiguration.java @@ -17,7 +17,7 @@ public int getExpiration() { return mExpiration; } - public boolean clearOnInit() { + public boolean isClearOnInit() { return mClearOnInit; } diff --git a/src/main/java/io/split/android/client/SplitClientConfig.java b/src/main/java/io/split/android/client/SplitClientConfig.java index b33567a32..94631c1d0 100644 --- a/src/main/java/io/split/android/client/SplitClientConfig.java +++ b/src/main/java/io/split/android/client/SplitClientConfig.java @@ -65,7 +65,6 @@ public class SplitClientConfig { // Data folder private static final String DEFAULT_DATA_FOLDER = "split_data"; - private static final long SPLITS_CACHE_EXPIRATION_IN_SECONDS = ServiceConstants.DEFAULT_SPLITS_CACHE_EXPIRATION_IN_SECONDS; private static final long OBSERVER_CACHE_EXPIRATION_PERIOD = ServiceConstants.DEFAULT_OBSERVER_CACHE_EXPIRATION_PERIOD_MS; private final String mEndpoint; @@ -253,8 +252,9 @@ public String trafficType() { return mTrafficType; } + @Deprecated public long cacheExpirationInSeconds() { - return SPLITS_CACHE_EXPIRATION_IN_SECONDS; + return TimeUnit.DAYS.toSeconds(rolloutCacheConfiguration().getExpiration()); } public long eventFlushInterval() { diff --git a/src/main/java/io/split/android/client/service/ServiceConstants.java b/src/main/java/io/split/android/client/service/ServiceConstants.java index 6f5c9f00c..07cf4d1b8 100644 --- a/src/main/java/io/split/android/client/service/ServiceConstants.java +++ b/src/main/java/io/split/android/client/service/ServiceConstants.java @@ -10,8 +10,8 @@ public class ServiceConstants { public static final long DEFAULT_INITIAL_DELAY = 15L; public static final long MIN_INITIAL_DELAY = 5L; public static final int DEFAULT_RECORDS_PER_PUSH = 100; - public static final long DEFAULT_SPLITS_CACHE_EXPIRATION_IN_SECONDS = TimeUnit.DAYS.toSeconds(10); // 10 days public static final int DEFAULT_ROLLOUT_CACHE_EXPIRATION = 10; // 10 days + public static final long DEFAULT_SPLITS_CACHE_EXPIRATION_IN_SECONDS = TimeUnit.DAYS.toSeconds(DEFAULT_ROLLOUT_CACHE_EXPIRATION); // 10 days public static final int MAX_ROWS_PER_QUERY = 100; diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java index c852e4a85..da2ff2e42 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java @@ -104,7 +104,7 @@ private boolean validateExpiration() { if (lastUpdateTimestamp > 0 && daysSinceLastUpdate >= mConfig.getExpiration()) { Logger.v("Clearing rollout definitions cache due to expiration"); return true; - } else if (mConfig.clearOnInit()) { + } else if (mConfig.isClearOnInit()) { long lastCacheClearTimestamp = mGeneralInfoStorage.getRolloutCacheLastClearTimestamp(); if (lastCacheClearTimestamp < 1) { return true; diff --git a/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java b/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java index ad1760fe8..8dd533451 100644 --- a/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java +++ b/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java @@ -12,7 +12,7 @@ public class RolloutCacheConfigurationTest { public void defaultValues() { RolloutCacheConfiguration config = RolloutCacheConfiguration.builder().build(); assertEquals(10, config.getExpiration()); - assertFalse(config.clearOnInit()); + assertFalse(config.isClearOnInit()); } @Test @@ -28,7 +28,7 @@ public void clearOnInitIsCorrectlySet() { RolloutCacheConfiguration.Builder builder = RolloutCacheConfiguration.builder(); builder.clearOnInit(true); RolloutCacheConfiguration config = builder.build(); - assertTrue(config.clearOnInit()); + assertTrue(config.isClearOnInit()); } @Test diff --git a/src/test/java/io/split/android/client/SplitClientConfigTest.java b/src/test/java/io/split/android/client/SplitClientConfigTest.java index 83564c640..690455513 100644 --- a/src/test/java/io/split/android/client/SplitClientConfigTest.java +++ b/src/test/java/io/split/android/client/SplitClientConfigTest.java @@ -230,7 +230,7 @@ public void rolloutCacheConfigurationDefaults() { RolloutCacheConfiguration config = SplitClientConfig.builder().build().rolloutCacheConfiguration(); assertEquals(10, config.getExpiration()); - assertFalse(config.clearOnInit()); + assertFalse(config.isClearOnInit()); } @Test @@ -240,7 +240,7 @@ public void rolloutCacheConfigurationExpirationIsCorrectlySet() { .build().rolloutCacheConfiguration(); assertEquals(1, config.getExpiration()); - assertTrue(config.clearOnInit()); + assertTrue(config.isClearOnInit()); } @Test @@ -252,7 +252,7 @@ public void nullRolloutCacheConfigurationSetsDefault() { .build().rolloutCacheConfiguration(); assertEquals(10, config.getExpiration()); - assertFalse(config.clearOnInit()); + assertFalse(config.isClearOnInit()); assertEquals(1, logMessages.size()); } From 6234fa70087cbc6a394a2e80a0489a8d5544d91d Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 5 Dec 2024 12:55:45 -0300 Subject: [PATCH 15/37] WIP --- .../RolloutCacheManagerIntegrationTest.java | 3 +- .../workmanager/WorkManagerWrapperTest.java | 2 - .../client/service/ServiceConstants.java | 2 - .../executor/SplitTaskFactoryImpl.java | 4 +- .../service/splits/SplitsSyncHelper.java | 7 +-- .../client/service/splits/SplitsSyncTask.java | 24 ++------- .../synchronizer/WorkManagerWrapper.java | 1 - .../service/workmanager/SplitWorker.java | 7 --- .../workmanager/splits/SplitsSyncWorker.java | 1 - .../splits/SplitsSyncWorkerTaskBuilder.java | 5 -- .../client/service/SplitSyncTaskTest.java | 50 ++++--------------- .../client/service/SplitsSyncHelperTest.java | 41 --------------- .../SplitsSyncWorkerTaskBuilderTest.java | 8 +-- 13 files changed, 21 insertions(+), 134 deletions(-) diff --git a/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java b/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java index e922053ec..62e1653c9 100644 --- a/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java +++ b/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java @@ -75,7 +75,7 @@ public void clearOnInitClearsCacheOnStartup() throws InterruptedException { @Test public void repeatedInitWithClearOnInitSetToTrueDoesNotClearIfMinDaysHasNotElapsed() throws InterruptedException { - // Preload DB with update timestamp of 1 day ago + // Preload DB with update timestamp of now long oldTimestamp = System.currentTimeMillis(); preloadDb(oldTimestamp, 0L, 8000L); @@ -102,6 +102,7 @@ public void repeatedInitWithClearOnInitSetToTrueDoesNotClearIfMinDaysHasNotElaps factory.client().on(SplitEvent.SDK_READY, TestingHelper.testTask(readyLatch)); boolean readyAwait = readyLatch.await(10, TimeUnit.SECONDS); + // Destroy factory factory.destroy(); mRequestCountdownLatch = new CountDownLatch(1); diff --git a/src/androidTest/java/tests/workmanager/WorkManagerWrapperTest.java b/src/androidTest/java/tests/workmanager/WorkManagerWrapperTest.java index bc9867946..bf593bbf9 100644 --- a/src/androidTest/java/tests/workmanager/WorkManagerWrapperTest.java +++ b/src/androidTest/java/tests/workmanager/WorkManagerWrapperTest.java @@ -124,7 +124,6 @@ public void scheduleWorkSchedulesSplitsJob() { mWrapper.scheduleWork(); Data inputData = new Data.Builder() - .putLong("splitCacheExpiration", 864000) .putString("endpoint", "https://test.split.io/api") .putBoolean("shouldRecordTelemetry", true) .putStringArray("configuredFilterValues", new String[]{"set_1", "set_2"}) @@ -252,7 +251,6 @@ public void schedulingWithoutCertificatePinning() { mWrapper.scheduleWork(); Data inputData = new Data.Builder() - .putLong("splitCacheExpiration", 864000) .putString("endpoint", "https://test.split.io/api") .putBoolean("shouldRecordTelemetry", true) .putStringArray("configuredFilterValues", new String[]{"set_1", "set_2"}) diff --git a/src/main/java/io/split/android/client/service/ServiceConstants.java b/src/main/java/io/split/android/client/service/ServiceConstants.java index 07cf4d1b8..6865fa5b7 100644 --- a/src/main/java/io/split/android/client/service/ServiceConstants.java +++ b/src/main/java/io/split/android/client/service/ServiceConstants.java @@ -11,7 +11,6 @@ public class ServiceConstants { public static final long MIN_INITIAL_DELAY = 5L; public static final int DEFAULT_RECORDS_PER_PUSH = 100; public static final int DEFAULT_ROLLOUT_CACHE_EXPIRATION = 10; // 10 days - public static final long DEFAULT_SPLITS_CACHE_EXPIRATION_IN_SECONDS = TimeUnit.DAYS.toSeconds(DEFAULT_ROLLOUT_CACHE_EXPIRATION); // 10 days public static final int MAX_ROWS_PER_QUERY = 100; @@ -28,7 +27,6 @@ public class ServiceConstants { public final static String WORKER_PARAM_ENDPOINT = "endpoint"; public final static String WORKER_PARAM_IMPRESSIONS_PER_PUSH = "impressionsPerPush"; public final static String WORKER_PARAM_EVENTS_PER_PUSH = "eventsPerPush"; - public final static String WORKER_PARAM_SPLIT_CACHE_EXPIRATION = "splitCacheExpiration"; public static final String WORKER_PARAM_UNIQUE_KEYS_PER_PUSH = "unique_keys_per_push"; public static final String WORKER_PARAM_UNIQUE_KEYS_ESTIMATED_SIZE_IN_BYTES = "unique_keys_estimated_size_in_bytes"; public static final String WORKER_PARAM_ENCRYPTION_ENABLED = "encryptionEnabled"; diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index 75ecc45fc..934f9ac75 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -128,8 +128,8 @@ public ImpressionsRecorderTask createImpressionsRecorderTask() { @Override public SplitsSyncTask createSplitsSyncTask(boolean checkCacheExpiration) { - return SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorageContainer.getSplitsStorage(), checkCacheExpiration, - mSplitClientConfig.cacheExpirationInSeconds(), mSplitsFilterQueryStringFromConfig, mEventsManager, mSplitsStorageContainer.getTelemetryStorage()); + return SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorageContainer.getSplitsStorage(), + mSplitsFilterQueryStringFromConfig, mEventsManager, mSplitsStorageContainer.getTelemetryStorage()); } @Override diff --git a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java index f65adb969..7396323f3 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java +++ b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java @@ -185,11 +185,8 @@ private void updateStorage(boolean clearBeforeUpdate, SplitChange splitChange) { mSplitsStorage.update(mSplitChangeProcessor.process(splitChange)); } - public boolean cacheHasExpired(long storedChangeNumber, long updateTimestamp, long cacheExpirationInSeconds) { - long elapsed = now() - TimeUnit.MILLISECONDS.toSeconds(updateTimestamp); - return storedChangeNumber > -1 - && updateTimestamp > 0 - && (elapsed > cacheExpirationInSeconds); + public boolean cacheHasExpired() { + return false; } private long now() { diff --git a/src/main/java/io/split/android/client/service/splits/SplitsSyncTask.java b/src/main/java/io/split/android/client/service/splits/SplitsSyncTask.java index 1fe6d6cec..8a7a7bc6f 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitsSyncTask.java +++ b/src/main/java/io/split/android/client/service/splits/SplitsSyncTask.java @@ -21,8 +21,6 @@ public class SplitsSyncTask implements SplitTask { private final String mSplitsFilterQueryStringFromConfig; private final SplitsStorage mSplitsStorage; - private final boolean mCheckCacheExpiration; - private final long mCacheExpirationInSeconds; private final SplitsSyncHelper mSplitsSyncHelper; @Nullable private final ISplitEventsManager mEventsManager; // Should only be null on background sync @@ -32,27 +30,21 @@ public class SplitsSyncTask implements SplitTask { public static SplitsSyncTask build(@NonNull SplitsSyncHelper splitsSyncHelper, @NonNull SplitsStorage splitsStorage, - boolean checkCacheExpiration, - long cacheExpirationInSeconds, String splitsFilterQueryString, @NonNull ISplitEventsManager eventsManager, @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer) { - return new SplitsSyncTask(splitsSyncHelper, splitsStorage, checkCacheExpiration, cacheExpirationInSeconds, splitsFilterQueryString, telemetryRuntimeProducer, eventsManager, ServiceConstants.ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES); + return new SplitsSyncTask(splitsSyncHelper, splitsStorage, splitsFilterQueryString, telemetryRuntimeProducer, eventsManager, ServiceConstants.ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES); } public static SplitTask buildForBackground(@NonNull SplitsSyncHelper splitsSyncHelper, @NonNull SplitsStorage splitsStorage, - boolean checkCacheExpiration, - long cacheExpirationInSeconds, - String splitsFilterQueryString, + String splitsFilterQueryString, @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer) { - return new SplitsSyncTask(splitsSyncHelper, splitsStorage, checkCacheExpiration, cacheExpirationInSeconds, splitsFilterQueryString, telemetryRuntimeProducer, null, 1); + return new SplitsSyncTask(splitsSyncHelper, splitsStorage, splitsFilterQueryString, telemetryRuntimeProducer, null, 1); } private SplitsSyncTask(@NonNull SplitsSyncHelper splitsSyncHelper, @NonNull SplitsStorage splitsStorage, - boolean checkCacheExpiration, - long cacheExpirationInSeconds, String splitsFilterQueryString, @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer, @Nullable ISplitEventsManager eventsManager, @@ -60,8 +52,6 @@ private SplitsSyncTask(@NonNull SplitsSyncHelper splitsSyncHelper, mSplitsStorage = checkNotNull(splitsStorage); mSplitsSyncHelper = checkNotNull(splitsSyncHelper); - mCacheExpirationInSeconds = cacheExpirationInSeconds; - mCheckCacheExpiration = checkCacheExpiration; mSplitsFilterQueryStringFromConfig = splitsFilterQueryString; mEventsManager = eventsManager; mChangeChecker = new SplitsChangeChecker(); @@ -73,10 +63,6 @@ private SplitsSyncTask(@NonNull SplitsSyncHelper splitsSyncHelper, @NonNull public SplitTaskExecutionInfo execute() { long storedChangeNumber = mSplitsStorage.getTill(); - long updateTimestamp = mSplitsStorage.getUpdateTimestamp(); - - boolean shouldClearExpiredCache = mCheckCacheExpiration && - mSplitsSyncHelper.cacheHasExpired(storedChangeNumber, updateTimestamp, mCacheExpirationInSeconds); boolean splitsFilterHasChanged = splitsFilterHasChanged(mSplitsStorage.getSplitsFilterQueryString()); @@ -87,8 +73,8 @@ public SplitTaskExecutionInfo execute() { long startTime = System.currentTimeMillis(); SplitTaskExecutionInfo result = mSplitsSyncHelper.sync(storedChangeNumber, - splitsFilterHasChanged || shouldClearExpiredCache, - splitsFilterHasChanged || shouldClearExpiredCache, mOnDemandFetchBackoffMaxRetries); + splitsFilterHasChanged, + splitsFilterHasChanged, mOnDemandFetchBackoffMaxRetries); mTelemetryRuntimeProducer.recordSyncLatency(OperationType.SPLITS, System.currentTimeMillis() - startTime); if (result.getStatus() == SplitTaskExecutionStatus.SUCCESS) { diff --git a/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java b/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java index c342f1cbc..c65042116 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java +++ b/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java @@ -199,7 +199,6 @@ private SplitTaskType taskTypeFromTags(Set tags) { private Data buildSplitSyncInputData() { Data.Builder dataBuilder = new Data.Builder(); - dataBuilder.putLong(ServiceConstants.WORKER_PARAM_SPLIT_CACHE_EXPIRATION, mSplitClientConfig.cacheExpirationInSeconds()); dataBuilder.putString(ServiceConstants.WORKER_PARAM_ENDPOINT, mSplitClientConfig.endpoint()); dataBuilder.putBoolean(ServiceConstants.SHOULD_RECORD_TELEMETRY, mSplitClientConfig.shouldRecordTelemetry()); dataBuilder.putString(ServiceConstants.WORKER_PARAM_CONFIGURED_FILTER_TYPE, (mFilter != null) ? mFilter.getType().queryStringField() : null); diff --git a/src/main/java/io/split/android/client/service/workmanager/SplitWorker.java b/src/main/java/io/split/android/client/service/workmanager/SplitWorker.java index 1b51bdfa9..b24b231ce 100644 --- a/src/main/java/io/split/android/client/service/workmanager/SplitWorker.java +++ b/src/main/java/io/split/android/client/service/workmanager/SplitWorker.java @@ -23,7 +23,6 @@ public abstract class SplitWorker extends Worker { private final SplitRoomDatabase mDatabase; private final HttpClient mHttpClient; private final String mEndpoint; - private final long mCacheExpirationInSeconds; protected SplitTask mSplitTask; @@ -36,8 +35,6 @@ public SplitWorker(@NonNull Context context, String apiKey = inputData.getString(ServiceConstants.WORKER_PARAM_API_KEY); mEndpoint = inputData.getString(ServiceConstants.WORKER_PARAM_ENDPOINT); mDatabase = SplitRoomDatabase.getDatabase(context, databaseName); - mCacheExpirationInSeconds = inputData.getLong(ServiceConstants.WORKER_PARAM_SPLIT_CACHE_EXPIRATION, - ServiceConstants.DEFAULT_SPLITS_CACHE_EXPIRATION_IN_SECONDS); mHttpClient = buildHttpClient(apiKey, buildCertPinningConfig(inputData.getString(ServiceConstants.WORKER_PARAM_CERTIFICATE_PINS))); } @@ -64,10 +61,6 @@ public String getEndPoint() { return mEndpoint; } - public long getCacheExpirationInSeconds() { - return mCacheExpirationInSeconds; - } - private static HttpClient buildHttpClient(String apiKey, @Nullable CertificatePinningConfiguration certificatePinningConfiguration) { HttpClientImpl.Builder builder = new HttpClientImpl.Builder(); diff --git a/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorker.java b/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorker.java index 6724aafe0..d5f3f6d27 100644 --- a/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorker.java +++ b/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorker.java @@ -22,7 +22,6 @@ public SplitsSyncWorker(@NonNull Context context, new FetcherProvider(getHttpClient(), getEndPoint()), new SplitChangeProcessorProvider().provideSplitChangeProcessor(params.configuredFilterType(), params.configuredFilterValues()), new SyncHelperProvider(), - getCacheExpirationInSeconds(), params.flagsSpec()); mSplitTask = builder.getTask(); diff --git a/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilder.java b/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilder.java index d1fa25c7a..9d0a8a026 100644 --- a/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilder.java +++ b/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilder.java @@ -15,7 +15,6 @@ */ class SplitsSyncWorkerTaskBuilder { - private final long mCacheExpirationInSeconds; private final StorageProvider mStorageProvider; private final FetcherProvider mFetcherProvider; private final SplitChangeProcessor mSplitChangeProcessor; @@ -26,13 +25,11 @@ class SplitsSyncWorkerTaskBuilder { FetcherProvider fetcherProvider, SplitChangeProcessor splitChangeProcessor, SyncHelperProvider splitsSyncHelperProvider, - long cacheExpirationInSeconds, String flagsSpec) { mStorageProvider = storageProvider; mFetcherProvider = fetcherProvider; mSplitsSyncHelperProvider = splitsSyncHelperProvider; mSplitChangeProcessor = splitChangeProcessor; - mCacheExpirationInSeconds = cacheExpirationInSeconds; mFlagsSpec = flagsSpec; } @@ -51,8 +48,6 @@ SplitTask getTask() { return SplitsSyncTask.buildForBackground(splitsSyncHelper, splitsStorage, - false, - mCacheExpirationInSeconds, splitsFilterQueryString, telemetryStorage); } catch (URISyntaxException e) { diff --git a/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java b/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java index 9c354f744..0278ffb0f 100644 --- a/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java +++ b/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java @@ -67,7 +67,7 @@ public void correctExecution() throws HttpFetcherException { // And updateTimestamp is 0 // Retry is off, so splitSyncHelper.sync should be called mTask = SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorage, - false, 1000, mQueryString, mEventsManager, mTelemetryRuntimeProducer); + mQueryString, mEventsManager, mTelemetryRuntimeProducer); when(mSplitsStorage.getTill()).thenReturn(-1L); when(mSplitsStorage.getUpdateTimestamp()).thenReturn(0L); when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(mQueryString); @@ -77,38 +77,6 @@ public void correctExecution() throws HttpFetcherException { verify(mSplitsSyncHelper, times(1)).sync(-1, false, false, ServiceConstants.ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES); } - @Test - public void cleanOldCacheDisabled() throws HttpFetcherException { - // Cache should not be cleared when cache expired - mTask = SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorage, - false, 100L, mQueryString, mEventsManager, mTelemetryRuntimeProducer); - when(mSplitsStorage.getTill()).thenReturn(300L); - when(mSplitsStorage.getUpdateTimestamp()).thenReturn(100L); - when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(mQueryString); - // This value function checks that cache is expired, here we simulate cache expired - when(mSplitsSyncHelper.cacheHasExpired(anyLong(), anyLong(), anyLong())).thenReturn(true); - - mTask.execute(); - - verify(mSplitsStorage, never()).clear(); - } - - @Test - public void cleanOldCacheEnabled() throws HttpFetcherException { - - // Cache should be cleared when cache expired - mTask = SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorage, - true, 100L, mQueryString, mEventsManager, mTelemetryRuntimeProducer); - when(mSplitsStorage.getTill()).thenReturn(100L); - when(mSplitsStorage.getUpdateTimestamp()).thenReturn(100L); // Dummy value clearing depends on cacheHasExpired function value - when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(mQueryString); - when(mSplitsSyncHelper.cacheHasExpired(anyLong(), anyLong(), anyLong())).thenReturn(true); - - mTask.execute(); - - verify(mSplitsSyncHelper, times(1)).sync(100L, true, true, ServiceConstants.ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES); - } - @Test public void cleanSplitsWhenQueryStringHasChanged() throws HttpFetcherException { // Splits have to be cleared when query string on db is != than current one on current sdk client instance @@ -119,11 +87,11 @@ public void cleanSplitsWhenQueryStringHasChanged() throws HttpFetcherException { Map params = new HashMap<>(); params.put("since", 100L); mTask = SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorage, - true, 100L, otherQs, mEventsManager, mTelemetryRuntimeProducer); + otherQs, mEventsManager, mTelemetryRuntimeProducer); when(mSplitsStorage.getTill()).thenReturn(100L); when(mSplitsStorage.getUpdateTimestamp()).thenReturn(1111L); when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(mQueryString); - when(mSplitsSyncHelper.cacheHasExpired(anyLong(), anyLong(), anyLong())).thenReturn(false); + when(mSplitsSyncHelper.cacheHasExpired()).thenReturn(false); mTask.execute(); @@ -137,11 +105,11 @@ public void noClearSplitsWhenQueryStringHasNotChanged() throws HttpFetcherExcept // Setting up cache not expired mTask = SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorage, - true,100L, mQueryString, mEventsManager, mTelemetryRuntimeProducer); + mQueryString, mEventsManager, mTelemetryRuntimeProducer); when(mSplitsStorage.getTill()).thenReturn(100L); when(mSplitsStorage.getUpdateTimestamp()).thenReturn(1111L); when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(mQueryString); - when(mSplitsSyncHelper.cacheHasExpired(anyLong(), anyLong(), anyLong())).thenReturn(false); + when(mSplitsSyncHelper.cacheHasExpired()).thenReturn(false); mTask.execute(); @@ -156,7 +124,7 @@ public void splitUpdatedNotified() throws HttpFetcherException { // And updateTimestamp is 0 // Retry is off, so splitSyncHelper.sync should be called mTask = SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorage, - false, 1000, mQueryString, mEventsManager, mTelemetryRuntimeProducer); + mQueryString, mEventsManager, mTelemetryRuntimeProducer); when(mSplitsStorage.getTill()).thenReturn(-1L).thenReturn(100L); when(mSplitsStorage.getUpdateTimestamp()).thenReturn(0L); when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(mQueryString); @@ -174,7 +142,7 @@ public void splitFetchdNotified() throws HttpFetcherException { // And updateTimestamp is 0 // Retry is off, so splitSyncHelper.sync should be called mTask = SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorage, - false, 1000, mQueryString, mEventsManager, mTelemetryRuntimeProducer); + mQueryString, mEventsManager, mTelemetryRuntimeProducer); when(mSplitsStorage.getTill()).thenReturn(100L); when(mSplitsStorage.getUpdateTimestamp()).thenReturn(0L); when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(mQueryString); @@ -188,7 +156,7 @@ public void splitFetchdNotified() throws HttpFetcherException { @Test public void syncIsTrackedInTelemetry() { mTask = SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorage, - false, 1000, mQueryString, mEventsManager, mTelemetryRuntimeProducer); + mQueryString, mEventsManager, mTelemetryRuntimeProducer); when(mSplitsStorage.getTill()).thenReturn(100L); when(mSplitsStorage.getUpdateTimestamp()).thenReturn(0L); when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(mQueryString); @@ -202,7 +170,7 @@ public void syncIsTrackedInTelemetry() { @Test public void recordSuccessInTelemetry() { mTask = SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorage, - false, 1000, mQueryString, mEventsManager, mTelemetryRuntimeProducer); + mQueryString, mEventsManager, mTelemetryRuntimeProducer); when(mSplitsStorage.getTill()).thenReturn(-1L); when(mSplitsStorage.getUpdateTimestamp()).thenReturn(0L); when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(mQueryString); diff --git a/src/test/java/io/split/android/client/service/SplitsSyncHelperTest.java b/src/test/java/io/split/android/client/service/SplitsSyncHelperTest.java index 1b2d9e104..2ff74c234 100644 --- a/src/test/java/io/split/android/client/service/SplitsSyncHelperTest.java +++ b/src/test/java/io/split/android/client/service/SplitsSyncHelperTest.java @@ -14,7 +14,6 @@ import static org.mockito.Mockito.when; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatcher; @@ -174,46 +173,6 @@ public void shouldClearStorageAfterFetch() throws HttpFetcherException { assertEquals(SplitTaskExecutionStatus.SUCCESS, result.getStatus()); } - @Test - public void cacheExpired() throws HttpFetcherException { - - // change number > -1 should clear cache - // when cache expired - - long cacheExpInSeconds = 10000; - long updateTimestamp = System.currentTimeMillis() / 1000 - cacheExpInSeconds - 1; - boolean expired = mSplitsSyncHelper.cacheHasExpired(100, updateTimestamp, cacheExpInSeconds); - - Assert.assertTrue(expired); - } - - @Test - public void cacheNotExpired() throws HttpFetcherException { - - // change number > -1 should clear cache - // only when cache expired - - long cacheExpInSeconds = 10000; - long updateTimestamp = System.currentTimeMillis() - cacheExpInSeconds * 1000 + 1000; - boolean expired = mSplitsSyncHelper.cacheHasExpired(100, updateTimestamp, cacheExpInSeconds); - - Assert.assertFalse(expired); - } - - @Test - public void cacheExpiredButChangeNumber() throws HttpFetcherException { - - // change number = -1 means no previous cache available - // so, should no clear cache - // even if it's expired - - long cacheExpInSeconds = 10000; - long updateTimestamp = System.currentTimeMillis() / 1000 - cacheExpInSeconds - 1000; - boolean expired = mSplitsSyncHelper.cacheHasExpired(-1, updateTimestamp, cacheExpInSeconds); - - Assert.assertFalse(expired); - } - @Test public void errorIsRecordedInTelemetry() throws HttpFetcherException { when(mSplitsFetcher.execute(mDefaultParams, null)) diff --git a/src/test/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilderTest.java b/src/test/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilderTest.java index 47f83e41f..1b950b365 100644 --- a/src/test/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilderTest.java +++ b/src/test/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilderTest.java @@ -56,7 +56,6 @@ public void getTaskUsesStorageProviderForSplitsStorage() { mFetcherProvider, mSplitChangeProcessor, mSplitsSyncHelperProvider, - 0, null); builder.getTask(); @@ -71,7 +70,6 @@ public void getTaskUsesFetcherProviderForFetcher() throws URISyntaxException { mFetcherProvider, mSplitChangeProcessor, mSplitsSyncHelperProvider, - 0, null); builder.getTask(); @@ -86,7 +84,6 @@ public void getTaskUsesStorageProviderForTelemetryStorage() { mFetcherProvider, mSplitChangeProcessor, mSplitsSyncHelperProvider, - 0, null); builder.getTask(); @@ -104,7 +101,6 @@ public void getTaskUsesSplitsSyncHelperProviderForSplitsSyncHelper() throws URIS mFetcherProvider, mSplitChangeProcessor, mSplitsSyncHelperProvider, - 0, "1.5"); builder.getTask(); @@ -127,7 +123,6 @@ public void getTaskReturnsNullWhenURISyntaxExceptionIsThrown() throws URISyntaxE mFetcherProvider, mSplitChangeProcessor, mSplitsSyncHelperProvider, - 0, null); SplitTask task = builder.getTask(); @@ -145,12 +140,11 @@ public void getTaskUsesSplitSyncTaskStaticMethod() { mFetcherProvider, mSplitChangeProcessor, mSplitsSyncHelperProvider, - 250, "2.5"); builder.getTask(); - mockedStatic.verify(() -> SplitsSyncTask.buildForBackground(splitsSyncHelper, mSplitsStorage, false, 250, "filterQueryString", mTelemetryStorage)); + mockedStatic.verify(() -> SplitsSyncTask.buildForBackground(splitsSyncHelper, mSplitsStorage, "filterQueryString", mTelemetryStorage)); } } } From f645892fabc919d4198346f0e9dd18b8162293ba Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 5 Dec 2024 15:06:39 -0300 Subject: [PATCH 16/37] Remove unused method --- .../split/android/client/service/splits/SplitsSyncHelper.java | 4 ---- .../io/split/android/client/service/SplitSyncTaskTest.java | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java index 7396323f3..50ecf6d94 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java +++ b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java @@ -185,10 +185,6 @@ private void updateStorage(boolean clearBeforeUpdate, SplitChange splitChange) { mSplitsStorage.update(mSplitChangeProcessor.process(splitChange)); } - public boolean cacheHasExpired() { - return false; - } - private long now() { return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); } diff --git a/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java b/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java index 0278ffb0f..9bbe08901 100644 --- a/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java +++ b/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java @@ -91,7 +91,6 @@ public void cleanSplitsWhenQueryStringHasChanged() throws HttpFetcherException { when(mSplitsStorage.getTill()).thenReturn(100L); when(mSplitsStorage.getUpdateTimestamp()).thenReturn(1111L); when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(mQueryString); - when(mSplitsSyncHelper.cacheHasExpired()).thenReturn(false); mTask.execute(); @@ -109,7 +108,6 @@ public void noClearSplitsWhenQueryStringHasNotChanged() throws HttpFetcherExcept when(mSplitsStorage.getTill()).thenReturn(100L); when(mSplitsStorage.getUpdateTimestamp()).thenReturn(1111L); when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(mQueryString); - when(mSplitsSyncHelper.cacheHasExpired()).thenReturn(false); mTask.execute(); From b0b52aa7061801e149c60e1810d69e02908cf7e3 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 5 Dec 2024 15:56:49 -0300 Subject: [PATCH 17/37] Remove unused method --- .../split/android/client/service/splits/SplitsSyncHelper.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java index 50ecf6d94..17e136fb7 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java +++ b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java @@ -185,10 +185,6 @@ private void updateStorage(boolean clearBeforeUpdate, SplitChange splitChange) { mSplitsStorage.update(mSplitChangeProcessor.process(splitChange)); } - private long now() { - return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); - } - private void logError(String message) { Logger.e("Error while executing splits sync/update task: " + message); } From 55d3cbbcc32710aab800c6e8123a47bc3866dd5b Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 5 Dec 2024 16:17:43 -0300 Subject: [PATCH 18/37] Rename config --- .../RolloutCacheManagerIntegrationTest.java | 2 +- .../client/RolloutCacheConfiguration.java | 16 ++++++++-------- .../split/android/client/SplitClientConfig.java | 2 +- .../synchronizer/RolloutCacheManagerImpl.java | 2 +- .../client/RolloutCacheConfigurationTest.java | 10 +++++----- .../android/client/SplitClientConfigTest.java | 8 ++++---- .../synchronizer/RolloutCacheManagerTest.kt | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java b/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java index 62e1653c9..8aa1b84a9 100644 --- a/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java +++ b/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java @@ -65,7 +65,7 @@ public void setUp() { @Test public void expirationPeriodIsUsed() throws InterruptedException { - test(getTimestampDaysAgo(1), RolloutCacheConfiguration.builder().expiration(1)); + test(getTimestampDaysAgo(1), RolloutCacheConfiguration.builder().expirationDays(1)); } @Test diff --git a/src/main/java/io/split/android/client/RolloutCacheConfiguration.java b/src/main/java/io/split/android/client/RolloutCacheConfiguration.java index fde7d6acf..413034e26 100644 --- a/src/main/java/io/split/android/client/RolloutCacheConfiguration.java +++ b/src/main/java/io/split/android/client/RolloutCacheConfiguration.java @@ -5,16 +5,16 @@ public class RolloutCacheConfiguration { - private final int mExpiration; + private final int mExpirationDays; private final boolean mClearOnInit; private RolloutCacheConfiguration(int expiration, boolean clearOnInit) { - mExpiration = expiration; + mExpirationDays = expiration; mClearOnInit = clearOnInit; } - public int getExpiration() { - return mExpiration; + public int getExpirationDays() { + return mExpirationDays; } public boolean isClearOnInit() { @@ -38,15 +38,15 @@ private Builder() { /** * Set the expiration time for the rollout definitions cache, in days. Default is 10 days. - * @param expiration in days + * @param expirationDays in days * @return This builder */ - public Builder expiration(int expiration) { - if (expiration < MIN_EXPIRATION_DAYS) { + public Builder expirationDays(int expirationDays) { + if (expirationDays < MIN_EXPIRATION_DAYS) { Logger.w("Cache expiration must be at least 1 day. Using default value."); mExpiration = ServiceConstants.DEFAULT_ROLLOUT_CACHE_EXPIRATION; } else { - mExpiration = expiration; + mExpiration = expirationDays; } return this; diff --git a/src/main/java/io/split/android/client/SplitClientConfig.java b/src/main/java/io/split/android/client/SplitClientConfig.java index 94631c1d0..00af53115 100644 --- a/src/main/java/io/split/android/client/SplitClientConfig.java +++ b/src/main/java/io/split/android/client/SplitClientConfig.java @@ -254,7 +254,7 @@ public String trafficType() { @Deprecated public long cacheExpirationInSeconds() { - return TimeUnit.DAYS.toSeconds(rolloutCacheConfiguration().getExpiration()); + return TimeUnit.DAYS.toSeconds(rolloutCacheConfiguration().getExpirationDays()); } public long eventFlushInterval() { diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java index c195c2f5b..2c9b9db04 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java @@ -101,7 +101,7 @@ private boolean validateExpiration() { // calculate elapsed time since last update long daysSinceLastUpdate = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastUpdateTimestamp); - if (lastUpdateTimestamp > 0 && daysSinceLastUpdate >= mConfig.getExpiration()) { + if (lastUpdateTimestamp > 0 && daysSinceLastUpdate >= mConfig.getExpirationDays()) { Logger.v("Clearing rollout definitions cache due to expiration"); return true; } else if (mConfig.isClearOnInit()) { diff --git a/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java b/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java index 8dd533451..ba342d1c8 100644 --- a/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java +++ b/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java @@ -11,16 +11,16 @@ public class RolloutCacheConfigurationTest { @Test public void defaultValues() { RolloutCacheConfiguration config = RolloutCacheConfiguration.builder().build(); - assertEquals(10, config.getExpiration()); + assertEquals(10, config.getExpirationDays()); assertFalse(config.isClearOnInit()); } @Test public void expirationIsCorrectlySet() { RolloutCacheConfiguration.Builder builder = RolloutCacheConfiguration.builder(); - builder.expiration(1); + builder.expirationDays(1); RolloutCacheConfiguration config = builder.build(); - assertEquals(1, config.getExpiration()); + assertEquals(1, config.getExpirationDays()); } @Test @@ -34,8 +34,8 @@ public void clearOnInitIsCorrectlySet() { @Test public void negativeExpirationIsSetToDefault() { RolloutCacheConfiguration.Builder builder = RolloutCacheConfiguration.builder(); - builder.expiration(-1); + builder.expirationDays(-1); RolloutCacheConfiguration config = builder.build(); - assertEquals(10, config.getExpiration()); + assertEquals(10, config.getExpirationDays()); } } diff --git a/src/test/java/io/split/android/client/SplitClientConfigTest.java b/src/test/java/io/split/android/client/SplitClientConfigTest.java index 690455513..1e69b9c12 100644 --- a/src/test/java/io/split/android/client/SplitClientConfigTest.java +++ b/src/test/java/io/split/android/client/SplitClientConfigTest.java @@ -229,17 +229,17 @@ public void observerCacheExpirationPeriodMatchesDedupeTimeIntervalWhenDedupeTime public void rolloutCacheConfigurationDefaults() { RolloutCacheConfiguration config = SplitClientConfig.builder().build().rolloutCacheConfiguration(); - assertEquals(10, config.getExpiration()); + assertEquals(10, config.getExpirationDays()); assertFalse(config.isClearOnInit()); } @Test public void rolloutCacheConfigurationExpirationIsCorrectlySet() { RolloutCacheConfiguration config = SplitClientConfig.builder() - .rolloutCacheConfiguration(RolloutCacheConfiguration.builder().expiration(1).clearOnInit(true).build()) + .rolloutCacheConfiguration(RolloutCacheConfiguration.builder().expirationDays(1).clearOnInit(true).build()) .build().rolloutCacheConfiguration(); - assertEquals(1, config.getExpiration()); + assertEquals(1, config.getExpirationDays()); assertTrue(config.isClearOnInit()); } @@ -251,7 +251,7 @@ public void nullRolloutCacheConfigurationSetsDefault() { .rolloutCacheConfiguration(null) .build().rolloutCacheConfiguration(); - assertEquals(10, config.getExpiration()); + assertEquals(10, config.getExpirationDays()); assertFalse(config.isClearOnInit()); assertEquals(1, logMessages.size()); } diff --git a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt index aa710c378..b89da1649 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt +++ b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt @@ -177,7 +177,7 @@ class RolloutCacheManagerTest { } private fun getCacheManager(expiration: Int, clearOnInit: Boolean): RolloutCacheManager { - return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheConfiguration.builder().expiration(expiration).clearOnInit(clearOnInit).build(), mCleanUpDatabaseTask, mEncryptionMigrationTask, mSplitsCache, mSegmentsCache) + return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheConfiguration.builder().expirationDays(expiration).clearOnInit(clearOnInit).build(), mCleanUpDatabaseTask, mEncryptionMigrationTask, mSplitsCache, mSegmentsCache) } private fun createMockedTimestamp(period: Long): Long { From 06ce624e881d2b1c2666f0577a4d7aaed654fb10 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 5 Dec 2024 17:16:58 -0300 Subject: [PATCH 19/37] Emit SDK_READY_FROM_CACHE alongside SDK_READY, if not previously emitted --- .../RolloutCacheManagerIntegrationTest.java | 17 +++++++++++++++++ .../client/events/SplitEventsManager.java | 3 +++ .../client/events/EventsManagerTest.java | 1 + 3 files changed, 21 insertions(+) diff --git a/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java b/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java index 8aa1b84a9..2efb3d933 100644 --- a/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java +++ b/src/androidTest/java/tests/integration/rollout/RolloutCacheManagerIntegrationTest.java @@ -142,6 +142,23 @@ public void repeatedInitWithClearOnInitSetToTrueDoesNotClearIfMinDaysHasNotElaps .getByName("rolloutCacheLastClearTimestamp").getLongValue()); } + @Test + public void sdkReadyFromCacheIsEmittedOnFreshInit() throws InterruptedException { + SplitFactory splitFactory = getSplitFactory(RolloutCacheConfiguration.builder().build()); + + CountDownLatch latch1 = new CountDownLatch(1); + CountDownLatch latch2 = new CountDownLatch(1); + splitFactory.client().on(SplitEvent.SDK_READY_FROM_CACHE, TestingHelper.testTask(latch1)); + splitFactory.client("two").on(SplitEvent.SDK_READY_FROM_CACHE, TestingHelper.testTask(latch2)); + mRequestCountdownLatch.countDown(); + + boolean await1 = latch1.await(10, TimeUnit.SECONDS); + boolean await2 = latch2.await(10, TimeUnit.SECONDS); + + assertTrue(await1); + assertTrue(await2); + } + private void test(long timestampDaysAgo, RolloutCacheConfiguration.Builder configBuilder) throws InterruptedException { // Preload DB with update timestamp of 1 day ago long oldTimestamp = timestampDaysAgo; diff --git a/src/main/java/io/split/android/client/events/SplitEventsManager.java b/src/main/java/io/split/android/client/events/SplitEventsManager.java index f6150157e..c443aa8e2 100644 --- a/src/main/java/io/split/android/client/events/SplitEventsManager.java +++ b/src/main/java/io/split/android/client/events/SplitEventsManager.java @@ -191,6 +191,9 @@ private void triggerSdkReadyIfNeeded() { if ((wasTriggered(SplitInternalEvent.MY_SEGMENTS_UPDATED) || wasTriggered(SplitInternalEvent.MY_SEGMENTS_FETCHED) || wasTriggered(SplitInternalEvent.MY_LARGE_SEGMENTS_UPDATED)) && (wasTriggered(SplitInternalEvent.SPLITS_UPDATED) || wasTriggered(SplitInternalEvent.SPLITS_FETCHED)) && !isTriggered(SplitEvent.SDK_READY)) { + if (!isTriggered(SplitEvent.SDK_READY_FROM_CACHE)) { + trigger(SplitEvent.SDK_READY_FROM_CACHE); + } trigger(SplitEvent.SDK_READY); } } diff --git a/src/test/java/io/split/android/client/events/EventsManagerTest.java b/src/test/java/io/split/android/client/events/EventsManagerTest.java index c9cca29c6..1f896825d 100644 --- a/src/test/java/io/split/android/client/events/EventsManagerTest.java +++ b/src/test/java/io/split/android/client/events/EventsManagerTest.java @@ -51,6 +51,7 @@ public void eventOnReady() { execute(shouldStop, intervalExecutionTime, maxExecutionTime, eventManager, SplitEvent.SDK_READY); assertTrue(eventManager.eventAlreadyTriggered(SplitEvent.SDK_READY)); + assertTrue(eventManager.eventAlreadyTriggered(SplitEvent.SDK_READY_FROM_CACHE)); assertFalse(eventManager.eventAlreadyTriggered(SplitEvent.SDK_READY_TIMED_OUT)); } From 910a82eed13d46a78925ef838f8d55749b9558d2 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 5 Dec 2024 19:02:11 -0300 Subject: [PATCH 20/37] Fix test --- src/androidTest/java/tests/integration/IntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/androidTest/java/tests/integration/IntegrationTest.java b/src/androidTest/java/tests/integration/IntegrationTest.java index 57327373b..07f8d8c37 100644 --- a/src/androidTest/java/tests/integration/IntegrationTest.java +++ b/src/androidTest/java/tests/integration/IntegrationTest.java @@ -286,7 +286,7 @@ public void testNoReadyFromCache() throws Exception { latch.await(40, TimeUnit.SECONDS); Assert.assertTrue(client.isReady()); - Assert.assertFalse(readyFromCacheTask.isOnPostExecutionCalled); + Assert.assertTrue(readyFromCacheTask.isOnPostExecutionCalled); Assert.assertTrue(readyTask.isOnPostExecutionCalled); Assert.assertFalse(readyTimeOutTask.isOnPostExecutionCalled); From 18779a15f7f9ec846fc0d968e093220cd3a4f573 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 6 Dec 2024 14:11:49 -0300 Subject: [PATCH 21/37] Update version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9e32031ee..9e880ea12 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ apply plugin: 'kotlin-android' apply from: 'spec.gradle' ext { - splitVersion = '5.1.0-alpha.1' + splitVersion = '5.1.0-alpha.2' } android { From 034f0cde82a75ad05593b22a3622b5db9a76e78e Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 9 Dec 2024 15:26:11 -0300 Subject: [PATCH 22/37] Fix test --- .../java/tests/integration/MySegmentsServerErrorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/androidTest/java/tests/integration/MySegmentsServerErrorTest.java b/src/androidTest/java/tests/integration/MySegmentsServerErrorTest.java index fe10d2bb4..ab1dc376b 100644 --- a/src/androidTest/java/tests/integration/MySegmentsServerErrorTest.java +++ b/src/androidTest/java/tests/integration/MySegmentsServerErrorTest.java @@ -195,7 +195,7 @@ public void test() throws Exception { Assert.fail("Impressions request timeout"); } - Assert.assertFalse(readyFromCacheTask.isOnPostExecutionCalled); + Assert.assertTrue(readyFromCacheTask.isOnPostExecutionCalled); Assert.assertEquals("on_s1", treatments.get(0)); Assert.assertEquals("on_s1", treatments.get(1)); Assert.assertEquals("on_s1", treatments.get(2)); From 9f371a0aff1e49b407d2265c8b426b9c97201fd3 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 9 Dec 2024 17:17:10 -0300 Subject: [PATCH 23/37] WIP --- .../android/client/SplitFactoryHelper.java | 12 +- .../android/client/SplitFactoryImpl.java | 8 +- .../impressions/ImpressionManager.java | 5 +- .../StrategyImpressionManager.java | 54 ++++---- .../impressions/strategy/DebugStrategy.java | 47 +------ .../impressions/strategy/DebugTracker.java | 7 +- .../strategy/ImpressionStrategyProvider.java | 124 +++++++++++------- .../impressions/strategy/NoneStrategy.java | 53 -------- .../strategy/OptimizedStrategy.java | 69 +--------- .../strategy/OptimizedTracker.java | 46 +------ .../impressions/strategy/ProcessStrategy.java | 2 +- .../synchronizer/SynchronizerImpl.java | 8 +- .../impressions/strategy/NoneStrategyTest.kt | 3 +- 13 files changed, 140 insertions(+), 298 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index bfb9b6bde..a0649aba1 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -39,7 +39,6 @@ import io.split.android.client.service.http.mysegments.MySegmentsFetcherFactoryImpl; import io.split.android.client.service.impressions.strategy.ImpressionStrategyConfig; import io.split.android.client.service.impressions.strategy.ImpressionStrategyProvider; -import io.split.android.client.service.impressions.strategy.ProcessStrategy; import io.split.android.client.service.mysegments.AllSegmentsResponseParser; import io.split.android.client.service.sseclient.EventStreamParser; import io.split.android.client.service.sseclient.ReconnectBackoffCounter; @@ -387,10 +386,10 @@ public StreamingComponents buildStreamingComponents(@NonNull SplitTaskExecutor s syncGuardian); } - public ProcessStrategy getImpressionStrategy(SplitTaskExecutor splitTaskExecutor, - SplitTaskFactory splitTaskFactory, - SplitStorageContainer splitStorageContainer, - SplitClientConfig config) { + public ImpressionStrategyProvider getImpressionStrategyProvider(SplitTaskExecutor splitTaskExecutor, + SplitTaskFactory splitTaskFactory, + SplitStorageContainer splitStorageContainer, + SplitClientConfig config) { return new ImpressionStrategyProvider(splitTaskExecutor, splitStorageContainer, splitTaskFactory, @@ -402,8 +401,7 @@ public ProcessStrategy getImpressionStrategy(SplitTaskExecutor splitTaskExecutor config.impressionsCounterRefreshRate(), config.mtkRefreshRate(), config.userConsent() == UserConsent.GRANTED, - config.impressionsDedupeTimeInterval())) - .getStrategy(config.impressionsMode()); + config.impressionsDedupeTimeInterval())); } @Nullable diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index 34d3abd63..260af4d04 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -32,8 +32,10 @@ import io.split.android.client.service.executor.SplitTaskExecutorImpl; import io.split.android.client.service.executor.SplitTaskFactory; import io.split.android.client.service.executor.SplitTaskFactoryImpl; -import io.split.android.client.service.impressions.ImpressionManager; import io.split.android.client.service.impressions.StrategyImpressionManager; +import io.split.android.client.service.impressions.strategy.ImpressionStrategyProvider; +import io.split.android.client.service.impressions.strategy.PeriodicTracker; +import io.split.android.client.service.impressions.strategy.ProcessStrategy; import io.split.android.client.service.sseclient.sseclient.StreamingComponents; import io.split.android.client.service.synchronizer.SyncManager; import io.split.android.client.service.synchronizer.Synchronizer; @@ -185,7 +187,9 @@ private SplitFactoryImpl(@NonNull String apiToken, @NonNull Key key, @NonNull Sp SplitSingleThreadTaskExecutor splitSingleThreadTaskExecutor = new SplitSingleThreadTaskExecutor(); splitSingleThreadTaskExecutor.pause(); - ImpressionManager impressionManager = new StrategyImpressionManager(factoryHelper.getImpressionStrategy(splitTaskExecutor, splitTaskFactory, mStorageContainer, config)); + ImpressionStrategyProvider impressionStrategyProvider = factoryHelper.getImpressionStrategyProvider(splitTaskExecutor, splitTaskFactory, mStorageContainer, config); + Pair noneComponents = impressionStrategyProvider.getNoneComponents(); + StrategyImpressionManager impressionManager = new StrategyImpressionManager(noneComponents, impressionStrategyProvider.getStrategy(config.impressionsMode())); final RetryBackoffCounterTimerFactory retryBackoffCounterTimerFactory = new RetryBackoffCounterTimerFactory(); StreamingComponents streamingComponents = factoryHelper.buildStreamingComponents(splitTaskExecutor, diff --git a/src/main/java/io/split/android/client/service/impressions/ImpressionManager.java b/src/main/java/io/split/android/client/service/impressions/ImpressionManager.java index 11598f02f..ec990e2a6 100644 --- a/src/main/java/io/split/android/client/service/impressions/ImpressionManager.java +++ b/src/main/java/io/split/android/client/service/impressions/ImpressionManager.java @@ -1,9 +1,10 @@ package io.split.android.client.service.impressions; import io.split.android.client.impressions.Impression; -import io.split.android.client.service.impressions.strategy.PeriodicTracker; -public interface ImpressionManager extends PeriodicTracker { +public interface ImpressionManager { + + void enableTracking(boolean enable); void pushImpression(Impression impression); } diff --git a/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java b/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java index 3512a2049..545f48c18 100644 --- a/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java +++ b/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java @@ -1,9 +1,6 @@ package io.split.android.client.service.impressions; -import static io.split.android.client.utils.Utils.checkNotNull; - -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; +import androidx.core.util.Pair; import java.util.concurrent.atomic.AtomicBoolean; @@ -12,25 +9,17 @@ import io.split.android.client.service.impressions.strategy.ProcessStrategy; import io.split.android.client.utils.logger.Logger; -public class StrategyImpressionManager implements ImpressionManager { +public class StrategyImpressionManager implements ImpressionManager, PeriodicTracker { private final AtomicBoolean isTrackingEnabled = new AtomicBoolean(true); private final ProcessStrategy mProcessStrategy; - private final PeriodicTracker mPeriodicTracker; - - public StrategyImpressionManager(@NonNull ProcessStrategy processStrategy) { - this(processStrategy, processStrategy); - } - - @VisibleForTesting - StrategyImpressionManager(@NonNull ProcessStrategy processStrategy, @NonNull PeriodicTracker periodicTracker) { - mProcessStrategy = checkNotNull(processStrategy); - mPeriodicTracker = checkNotNull(periodicTracker); - } + private final ProcessStrategy mNoneStrategy; + private final PeriodicTracker[] mPeriodicTracker; - @Override - public void enableTracking(boolean enable) { - isTrackingEnabled.set(enable); + public StrategyImpressionManager(Pair noneComponents, Pair strategy) { + mProcessStrategy = strategy.first; + mNoneStrategy = noneComponents.first; + mPeriodicTracker = new PeriodicTracker[]{noneComponents.second, strategy.second}; } @Override @@ -40,21 +29,40 @@ public void pushImpression(Impression impression) { return; } - mProcessStrategy.apply(impression); + if (track(impression)) { + mProcessStrategy.apply(impression); + } else { + mNoneStrategy.apply(impression); + } + } + + @Override + public void enableTracking(boolean enable) { + isTrackingEnabled.set(enable); } @Override public void flush() { - mPeriodicTracker.flush(); + for (PeriodicTracker tracker : mPeriodicTracker) { + tracker.flush(); + } } @Override public void startPeriodicRecording() { - mPeriodicTracker.startPeriodicRecording(); + for (PeriodicTracker tracker : mPeriodicTracker) { + tracker.startPeriodicRecording(); + } } @Override public void stopPeriodicRecording() { - mPeriodicTracker.stopPeriodicRecording(); + for (PeriodicTracker tracker : mPeriodicTracker) { + tracker.stopPeriodicRecording(); + } + } + + private static boolean track(Impression impression) { + return true; // TODO: Placeholder method } } diff --git a/src/main/java/io/split/android/client/service/impressions/strategy/DebugStrategy.java b/src/main/java/io/split/android/client/service/impressions/strategy/DebugStrategy.java index ffeeaa60f..1a0c0855d 100644 --- a/src/main/java/io/split/android/client/service/impressions/strategy/DebugStrategy.java +++ b/src/main/java/io/split/android/client/service/impressions/strategy/DebugStrategy.java @@ -4,7 +4,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import java.util.concurrent.atomic.AtomicBoolean; @@ -16,7 +15,6 @@ import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.impressions.ImpressionsTaskFactory; import io.split.android.client.service.impressions.observer.ImpressionsObserver; -import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer; import io.split.android.client.service.synchronizer.RecorderSyncHelper; import io.split.android.client.telemetry.model.ImpressionsDataType; import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; @@ -31,7 +29,6 @@ class DebugStrategy implements ProcessStrategy { private final SplitTaskExecutor mTaskExecutor; private final TelemetryRuntimeProducer mTelemetryRuntimeProducer; private final ImpressionsTaskFactory mImpressionsTaskFactory; - private final PeriodicTracker mDebugTracker; private final AtomicBoolean mIsSynchronizing = new AtomicBoolean(true); /** @noinspection FieldCanBeLocal*/ private final SplitTaskExecutionListener mTaskExecutionListener = new SplitTaskExecutionListener() { @@ -41,7 +38,6 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { if (taskInfo.getStatus() == SplitTaskExecutionStatus.ERROR) { if (Boolean.TRUE.equals(taskInfo.getBoolValue(SplitTaskExecutionInfo.DO_NOT_RETRY))) { mIsSynchronizing.compareAndSet(true, false); - mDebugTracker.stopPeriodicRecording(); } } } @@ -51,24 +47,7 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { @NonNull RecorderSyncHelper impressionsSyncHelper, @NonNull SplitTaskExecutor taskExecutor, @NonNull ImpressionsTaskFactory taskFactory, - @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer, - @NonNull RetryBackoffCounterTimer retryTimer, - int impressionsRefreshRate) { - this(impressionsObserver, - impressionsSyncHelper, - taskExecutor, - taskFactory, - telemetryRuntimeProducer, - new DebugTracker(impressionsSyncHelper, taskExecutor, taskFactory, retryTimer, impressionsRefreshRate)); - } - - @VisibleForTesting - DebugStrategy(@NonNull ImpressionsObserver impressionsObserver, - @NonNull RecorderSyncHelper impressionsSyncHelper, - @NonNull SplitTaskExecutor taskExecutor, - @NonNull ImpressionsTaskFactory taskFactory, - @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer, - @NonNull PeriodicTracker tracker) { + @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer) { mImpressionsObserver = checkNotNull(impressionsObserver); RecorderSyncHelper syncHelper = checkNotNull(impressionsSyncHelper); syncHelper.addListener(mTaskExecutionListener); @@ -76,7 +55,6 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { mTaskExecutor = checkNotNull(taskExecutor); mImpressionsTaskFactory = checkNotNull(taskFactory); mTelemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer); - mDebugTracker = checkNotNull(tracker); } @Override @@ -92,27 +70,4 @@ public void apply(@NonNull Impression impression) { mTelemetryRuntimeProducer.recordImpressionStats(ImpressionsDataType.IMPRESSIONS_QUEUED, 1); } - - @Override - public void flush() { - mDebugTracker.flush(); - } - - @Override - public void startPeriodicRecording() { - if (mIsSynchronizing.get()) { - mDebugTracker.startPeriodicRecording(); - } - } - - @Override - public void stopPeriodicRecording() { - mDebugTracker.stopPeriodicRecording(); - mImpressionsObserver.persist(); - } - - @Override - public void enableTracking(boolean enable) { - mDebugTracker.enableTracking(enable); - } } diff --git a/src/main/java/io/split/android/client/service/impressions/strategy/DebugTracker.java b/src/main/java/io/split/android/client/service/impressions/strategy/DebugTracker.java index 05ac1c29f..d17019865 100644 --- a/src/main/java/io/split/android/client/service/impressions/strategy/DebugTracker.java +++ b/src/main/java/io/split/android/client/service/impressions/strategy/DebugTracker.java @@ -8,6 +8,7 @@ import io.split.android.client.service.ServiceConstants; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.impressions.ImpressionsTaskFactory; +import io.split.android.client.service.impressions.observer.ImpressionsObserver; import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer; import io.split.android.client.service.synchronizer.RecorderSyncHelper; @@ -18,13 +19,16 @@ class DebugTracker implements PeriodicTracker { private final ImpressionsTaskFactory mImpressionsTaskFactory; private final RetryBackoffCounterTimer mRetryTimer; private final int mImpressionsRefreshRate; + private final ImpressionsObserver mImpressionsObserver; private String mImpressionsRecorderTaskId; - DebugTracker(@NonNull RecorderSyncHelper impressionsSyncHelper, + DebugTracker(@NonNull ImpressionsObserver impressionsObserver, + @NonNull RecorderSyncHelper impressionsSyncHelper, @NonNull SplitTaskExecutor taskExecutor, @NonNull ImpressionsTaskFactory taskFactory, @NonNull RetryBackoffCounterTimer retryTimer, int impressionsRefreshRate) { + mImpressionsObserver = checkNotNull(impressionsObserver); mImpressionsSyncHelper = checkNotNull(impressionsSyncHelper); mTaskExecutor = checkNotNull(taskExecutor); mImpressionsTaskFactory = checkNotNull(taskFactory); @@ -63,6 +67,7 @@ private void scheduleImpressionsRecorderTask() { @Override public void stopPeriodicRecording() { mTaskExecutor.stopTask(mImpressionsRecorderTaskId); + mImpressionsObserver.persist(); } @Override diff --git a/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java b/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java index 787647587..17307f526 100644 --- a/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java +++ b/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java @@ -1,5 +1,8 @@ package io.split.android.client.service.impressions.strategy; +import androidx.core.util.Pair; + +import io.split.android.client.dtos.KeyImpression; import io.split.android.client.service.ServiceConstants; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskType; @@ -8,6 +11,7 @@ import io.split.android.client.service.impressions.ImpressionsMode; import io.split.android.client.service.impressions.ImpressionsTaskFactory; import io.split.android.client.service.impressions.observer.ImpressionsObserverImpl; +import io.split.android.client.service.impressions.unique.UniqueKeysTracker; import io.split.android.client.service.impressions.unique.UniqueKeysTrackerImpl; import io.split.android.client.service.synchronizer.RecorderSyncHelperImpl; import io.split.android.client.storage.common.SplitStorageContainer; @@ -20,6 +24,8 @@ public class ImpressionStrategyProvider { private final ImpressionsTaskFactory mSplitTaskFactory; private final TelemetryRuntimeProducer mTelemetryStorage; private final ImpressionStrategyConfig mImpressionStrategyConfig; + private final ImpressionsCounter mImpressionsCounter; + private final ImpressionManagerRetryTimerProviderImpl mImpressionManagerRetryTimerProvider; public ImpressionStrategyProvider(SplitTaskExecutor splitTaskExecutor, SplitStorageContainer storageContainer, @@ -31,58 +37,76 @@ public ImpressionStrategyProvider(SplitTaskExecutor splitTaskExecutor, mSplitTaskFactory = splitTaskFactory; mTelemetryStorage = telemetryStorage; mImpressionStrategyConfig = config; + mImpressionsCounter = new ImpressionsCounter(mImpressionStrategyConfig.getDedupeTimeIntervalInMs()); + mImpressionManagerRetryTimerProvider = new ImpressionManagerRetryTimerProviderImpl(mSplitTaskExecutor); } - public ProcessStrategy getStrategy(ImpressionsMode mode) { - ImpressionManagerRetryTimerProviderImpl impressionManagerRetryTimerProvider = new ImpressionManagerRetryTimerProviderImpl(mSplitTaskExecutor); - switch (mode) { - case DEBUG: - return new DebugStrategy( - new ImpressionsObserverImpl(mStorageContainer.getImpressionsObserverCachePersistentStorage(), ServiceConstants.LAST_SEEN_IMPRESSION_CACHE_SIZE), - new RecorderSyncHelperImpl<>( - SplitTaskType.IMPRESSIONS_RECORDER, - mStorageContainer.getImpressionsStorage(), - mImpressionStrategyConfig.getImpressionsQueueSize(), - mImpressionStrategyConfig.getImpressionsChunkSize(), - mSplitTaskExecutor), - mSplitTaskExecutor, - mSplitTaskFactory, - mTelemetryStorage, - impressionManagerRetryTimerProvider.getImpressionsTimer(), - mImpressionStrategyConfig.getImpressionsRefreshRate() - ); - case NONE: - return new NoneStrategy( - mSplitTaskExecutor, - mSplitTaskFactory, - new ImpressionsCounter(mImpressionStrategyConfig.getDedupeTimeIntervalInMs()), - new UniqueKeysTrackerImpl(), - impressionManagerRetryTimerProvider.getImpressionsCountTimer(), - impressionManagerRetryTimerProvider.getUniqueKeysTimer(), - mImpressionStrategyConfig.getImpressionsCounterRefreshRate(), - mImpressionStrategyConfig.getUniqueKeysRefreshRate(), - mImpressionStrategyConfig.isUserConsentGranted() - ); - default: - return new OptimizedStrategy( - new ImpressionsObserverImpl(mStorageContainer.getImpressionsObserverCachePersistentStorage(), ServiceConstants.LAST_SEEN_IMPRESSION_CACHE_SIZE), - new ImpressionsCounter(mImpressionStrategyConfig.getDedupeTimeIntervalInMs()), - new RecorderSyncHelperImpl<>( - SplitTaskType.IMPRESSIONS_RECORDER, - mStorageContainer.getImpressionsStorage(), - mImpressionStrategyConfig.getImpressionsQueueSize(), - mImpressionStrategyConfig.getImpressionsChunkSize(), - mSplitTaskExecutor), - mSplitTaskExecutor, - mSplitTaskFactory, - mTelemetryStorage, - impressionManagerRetryTimerProvider.getImpressionsTimer(), - impressionManagerRetryTimerProvider.getImpressionsCountTimer(), - mImpressionStrategyConfig.getImpressionsRefreshRate(), - mImpressionStrategyConfig.getImpressionsCounterRefreshRate(), - mImpressionStrategyConfig.isUserConsentGranted(), - mImpressionStrategyConfig.getDedupeTimeIntervalInMs() - ); + public Pair getStrategy(ImpressionsMode mode) { + ImpressionsObserverImpl impressionsObserver = new ImpressionsObserverImpl(mStorageContainer.getImpressionsObserverCachePersistentStorage(), ServiceConstants.LAST_SEEN_IMPRESSION_CACHE_SIZE); + RecorderSyncHelperImpl impressionsSyncHelper = new RecorderSyncHelperImpl<>( + SplitTaskType.IMPRESSIONS_RECORDER, + mStorageContainer.getImpressionsStorage(), + mImpressionStrategyConfig.getImpressionsQueueSize(), + mImpressionStrategyConfig.getImpressionsChunkSize(), + mSplitTaskExecutor); + if (mode == ImpressionsMode.DEBUG) { + DebugTracker tracker = new DebugTracker( + impressionsObserver, + impressionsSyncHelper, + mSplitTaskExecutor, + mSplitTaskFactory, + mImpressionManagerRetryTimerProvider.getImpressionsTimer(), + mImpressionStrategyConfig.getImpressionsRefreshRate()); + DebugStrategy debugStrategy = new DebugStrategy( + impressionsObserver, + impressionsSyncHelper, + mSplitTaskExecutor, + mSplitTaskFactory, + mTelemetryStorage); + return new Pair<>(debugStrategy, tracker); + } else if (mode == ImpressionsMode.NONE) { + return new Pair<>(null, null); + } else { + OptimizedStrategy optimizedStrategy = new OptimizedStrategy( + impressionsObserver, + mImpressionsCounter, + impressionsSyncHelper, + mSplitTaskExecutor, + mSplitTaskFactory, + mTelemetryStorage, + mImpressionStrategyConfig.getDedupeTimeIntervalInMs()); + OptimizedTracker optimizedTracker = new OptimizedTracker( + impressionsObserver, + impressionsSyncHelper, + mSplitTaskExecutor, + mSplitTaskFactory, + mImpressionManagerRetryTimerProvider.getImpressionsTimer(), + mImpressionStrategyConfig.getImpressionsRefreshRate(), + mImpressionStrategyConfig.isUserConsentGranted() + ); + return new Pair<>(optimizedStrategy, optimizedTracker); } } + + public Pair getNoneComponents() { + UniqueKeysTracker uniqueKeysTracker = new UniqueKeysTrackerImpl(); + NoneStrategy noneStrategy = new NoneStrategy( + mSplitTaskExecutor, + mSplitTaskFactory, + mImpressionsCounter, + uniqueKeysTracker, + mImpressionStrategyConfig.isUserConsentGranted() + ); + NoneTracker noneTracker = new NoneTracker( + mSplitTaskExecutor, + mSplitTaskFactory, + mImpressionsCounter, + uniqueKeysTracker, + mImpressionManagerRetryTimerProvider.getImpressionsCountTimer(), + mImpressionManagerRetryTimerProvider.getUniqueKeysTimer(), + mImpressionStrategyConfig.getImpressionsCounterRefreshRate(), + mImpressionStrategyConfig.getUniqueKeysRefreshRate(), + mImpressionStrategyConfig.isUserConsentGranted()); + return new Pair<>(noneStrategy, noneTracker); // TODO + } } diff --git a/src/main/java/io/split/android/client/service/impressions/strategy/NoneStrategy.java b/src/main/java/io/split/android/client/service/impressions/strategy/NoneStrategy.java index 243477f06..064ef1dfa 100644 --- a/src/main/java/io/split/android/client/service/impressions/strategy/NoneStrategy.java +++ b/src/main/java/io/split/android/client/service/impressions/strategy/NoneStrategy.java @@ -3,7 +3,6 @@ import static io.split.android.client.utils.Utils.checkNotNull; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import java.util.concurrent.atomic.AtomicBoolean; @@ -12,7 +11,6 @@ import io.split.android.client.service.impressions.ImpressionsCounter; import io.split.android.client.service.impressions.ImpressionsTaskFactory; import io.split.android.client.service.impressions.unique.UniqueKeysTracker; -import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer; /** * {@link ProcessStrategy} that corresponds to NONE Impressions mode. @@ -24,47 +22,17 @@ class NoneStrategy implements ProcessStrategy { private final ImpressionsCounter mImpressionsCounter; private final UniqueKeysTracker mUniqueKeysTracker; private final AtomicBoolean mTrackingIsEnabled; - private final PeriodicTracker mNoneTracker; NoneStrategy(@NonNull SplitTaskExecutor taskExecutor, @NonNull ImpressionsTaskFactory taskFactory, @NonNull ImpressionsCounter impressionsCounter, @NonNull UniqueKeysTracker uniqueKeysTracker, - @NonNull RetryBackoffCounterTimer impressionsCountRetryTimer, - @NonNull RetryBackoffCounterTimer uniqueKeysRetryTimer, - int impressionsCounterRefreshRate, - int uniqueKeysRefreshRate, boolean trackingIsEnabled) { - this(taskExecutor, - taskFactory, - impressionsCounter, - uniqueKeysTracker, - trackingIsEnabled, - new NoneTracker( - taskExecutor, - taskFactory, - impressionsCounter, - uniqueKeysTracker, - impressionsCountRetryTimer, - uniqueKeysRetryTimer, - impressionsCounterRefreshRate, - uniqueKeysRefreshRate, - trackingIsEnabled)); - } - - @VisibleForTesting - NoneStrategy(@NonNull SplitTaskExecutor taskExecutor, - @NonNull ImpressionsTaskFactory taskFactory, - @NonNull ImpressionsCounter impressionsCounter, - @NonNull UniqueKeysTracker uniqueKeysTracker, - boolean trackingIsEnabled, - @NonNull PeriodicTracker tracker) { mTaskExecutor = checkNotNull(taskExecutor); mTaskFactory = checkNotNull(taskFactory); mImpressionsCounter = checkNotNull(impressionsCounter); mUniqueKeysTracker = checkNotNull(uniqueKeysTracker); mTrackingIsEnabled = new AtomicBoolean(trackingIsEnabled); - mNoneTracker = checkNotNull(tracker); } @Override @@ -77,27 +45,6 @@ public void apply(@NonNull Impression impression) { } } - @Override - public void flush() { - mNoneTracker.flush(); - } - - @Override - public void startPeriodicRecording() { - mNoneTracker.startPeriodicRecording(); - } - - @Override - public void stopPeriodicRecording() { - mNoneTracker.stopPeriodicRecording(); - } - - @Override - public void enableTracking(boolean enable) { - mTrackingIsEnabled.set(enable); - mNoneTracker.enableTracking(enable); - } - private void saveUniqueKeys() { if (mTrackingIsEnabled.get()) { mTaskExecutor.submit( diff --git a/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedStrategy.java b/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedStrategy.java index e232e5ff6..6adcd40bf 100644 --- a/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedStrategy.java +++ b/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedStrategy.java @@ -3,7 +3,6 @@ import static io.split.android.client.utils.Utils.checkNotNull; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import java.util.concurrent.atomic.AtomicBoolean; @@ -17,7 +16,6 @@ import io.split.android.client.service.impressions.ImpressionsCounter; import io.split.android.client.service.impressions.ImpressionsTaskFactory; import io.split.android.client.service.impressions.observer.ImpressionsObserver; -import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer; import io.split.android.client.service.synchronizer.RecorderSyncHelper; import io.split.android.client.telemetry.model.ImpressionsDataType; import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; @@ -33,11 +31,11 @@ class OptimizedStrategy implements ProcessStrategy { private final SplitTaskExecutor mTaskExecutor; private final ImpressionsTaskFactory mImpressionsTaskFactory; private final TelemetryRuntimeProducer mTelemetryRuntimeProducer; - private final AtomicBoolean mTrackingIsEnabled; - private final PeriodicTracker mOptimizedTracker; private final AtomicBoolean mIsSynchronizing = new AtomicBoolean(true); private final long mImpressionsDedupeTimeInterval; - /** @noinspection FieldCanBeLocal*/ + /** + * @noinspection FieldCanBeLocal + */ private final SplitTaskExecutionListener mTaskExecutionListener = new SplitTaskExecutionListener() { @Override public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { @@ -45,7 +43,6 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { if (taskInfo.getStatus() == SplitTaskExecutionStatus.ERROR) { if (Boolean.TRUE.equals(taskInfo.getBoolValue(SplitTaskExecutionInfo.DO_NOT_RETRY))) { mIsSynchronizing.compareAndSet(true, false); - mOptimizedTracker.stopPeriodicRecording(); } } } @@ -57,40 +54,6 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { @NonNull SplitTaskExecutor taskExecutor, @NonNull ImpressionsTaskFactory taskFactory, @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer, - @NonNull RetryBackoffCounterTimer impressionsRetryTimer, - @NonNull RetryBackoffCounterTimer impressionsCountRetryTimer, - int impressionsRefreshRate, - int impressionsCounterRefreshRate, - boolean isTrackingEnabled, - long impressionsDedupeTimeInterval) { - this(impressionsObserver, - impressionsCounter, - impressionsSyncHelper, - taskExecutor, - taskFactory, - telemetryRuntimeProducer, - isTrackingEnabled, - new OptimizedTracker(impressionsCounter, - impressionsSyncHelper, - taskExecutor, - taskFactory, - impressionsRetryTimer, - impressionsCountRetryTimer, - impressionsRefreshRate, - impressionsCounterRefreshRate, - isTrackingEnabled), - impressionsDedupeTimeInterval); - } - - @VisibleForTesting - OptimizedStrategy(@NonNull ImpressionsObserver impressionsObserver, - @NonNull ImpressionsCounter impressionsCounter, - @NonNull RecorderSyncHelper impressionsSyncHelper, - @NonNull SplitTaskExecutor taskExecutor, - @NonNull ImpressionsTaskFactory taskFactory, - @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer, - boolean isTrackingEnabled, - @NonNull PeriodicTracker tracker, long impressionsDedupeTimeInterval) { mImpressionsObserver = checkNotNull(impressionsObserver); mImpressionsCounter = checkNotNull(impressionsCounter); @@ -100,8 +63,6 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { mTaskExecutor = checkNotNull(taskExecutor); mImpressionsTaskFactory = checkNotNull(taskFactory); mTelemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer); - mTrackingIsEnabled = new AtomicBoolean(isTrackingEnabled); - mOptimizedTracker = checkNotNull(tracker); mImpressionsDedupeTimeInterval = impressionsDedupeTimeInterval; } @@ -136,28 +97,4 @@ private boolean shouldPushImpression(KeyImpression impression) { private static boolean previousTimeIsValid(Long previousTime) { return previousTime != null && previousTime != 0; } - - @Override - public void flush() { - mOptimizedTracker.flush(); - } - - @Override - public void startPeriodicRecording() { - if (mIsSynchronizing.get()) { - mOptimizedTracker.startPeriodicRecording(); - } - } - - @Override - public void stopPeriodicRecording() { - mOptimizedTracker.stopPeriodicRecording(); - mImpressionsObserver.persist(); - } - - @Override - public void enableTracking(boolean enable) { - mTrackingIsEnabled.set(enable); - mOptimizedTracker.enableTracking(enable); - } } diff --git a/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedTracker.java b/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedTracker.java index 05a42cf87..0ba6f4c25 100644 --- a/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedTracker.java +++ b/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedTracker.java @@ -9,66 +9,55 @@ import io.split.android.client.dtos.KeyImpression; import io.split.android.client.service.ServiceConstants; import io.split.android.client.service.executor.SplitTaskExecutor; -import io.split.android.client.service.executor.SplitTaskSerialWrapper; -import io.split.android.client.service.impressions.ImpressionsCounter; import io.split.android.client.service.impressions.ImpressionsTaskFactory; +import io.split.android.client.service.impressions.observer.ImpressionsObserver; import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer; import io.split.android.client.service.synchronizer.RecorderSyncHelper; class OptimizedTracker implements PeriodicTracker { - private final ImpressionsCounter mImpressionsCounter; + private final ImpressionsObserver mImpressionsObserver; private final RecorderSyncHelper mImpressionsSyncHelper; private final SplitTaskExecutor mTaskExecutor; private final ImpressionsTaskFactory mImpressionsTaskFactory; private final RetryBackoffCounterTimer mRetryTimer; - private final RetryBackoffCounterTimer mImpressionsCountRetryTimer; private final int mImpressionsRefreshRate; - private final int mImpressionsCounterRefreshRate; private String mImpressionsRecorderTaskId; - private String mImpressionsRecorderCountTaskId; private final AtomicBoolean mTrackingIsEnabled; - OptimizedTracker(@NonNull ImpressionsCounter impressionsCounter, + OptimizedTracker(@NonNull ImpressionsObserver impressionsObserver, @NonNull RecorderSyncHelper impressionsSyncHelper, @NonNull SplitTaskExecutor taskExecutor, @NonNull ImpressionsTaskFactory taskFactory, @NonNull RetryBackoffCounterTimer impressionsRetryTimer, - @NonNull RetryBackoffCounterTimer impressionsCountRetryTimer, int impressionsRefreshRate, - int impressionsCounterRefreshRate, boolean isTrackingEnabled) { - mImpressionsCounter = checkNotNull(impressionsCounter); + mImpressionsObserver = checkNotNull(impressionsObserver); mImpressionsSyncHelper = checkNotNull(impressionsSyncHelper); mTaskExecutor = checkNotNull(taskExecutor); mImpressionsTaskFactory = checkNotNull(taskFactory); mRetryTimer = checkNotNull(impressionsRetryTimer); - mImpressionsCountRetryTimer = checkNotNull(impressionsCountRetryTimer); mImpressionsRefreshRate = impressionsRefreshRate; - mImpressionsCounterRefreshRate = impressionsCounterRefreshRate; mTrackingIsEnabled = new AtomicBoolean(isTrackingEnabled); } @Override public void flush() { flushImpressions(); - flushImpressionsCount(); } @Override public void startPeriodicRecording() { scheduleImpressionsRecorderTask(); - scheduleImpressionsCountRecorderTask(); } @Override public void stopPeriodicRecording() { - saveImpressionsCount(); mTaskExecutor.stopTask(mImpressionsRecorderTaskId); - mTaskExecutor.stopTask(mImpressionsRecorderCountTaskId); + mImpressionsObserver.persist(); } @Override @@ -83,13 +72,6 @@ private void flushImpressions() { mRetryTimer.start(); } - private void flushImpressionsCount() { - mImpressionsCountRetryTimer.setTask(new SplitTaskSerialWrapper( - mImpressionsTaskFactory.createSaveImpressionsCountTask(mImpressionsCounter.popAll()), - mImpressionsTaskFactory.createImpressionsCountRecorderTask())); - mImpressionsCountRetryTimer.start(); - } - private void scheduleImpressionsRecorderTask() { if (mImpressionsRecorderTaskId != null) { mTaskExecutor.stopTask(mImpressionsRecorderTaskId); @@ -100,22 +82,4 @@ private void scheduleImpressionsRecorderTask() { mImpressionsRefreshRate, mImpressionsSyncHelper); } - - private void scheduleImpressionsCountRecorderTask() { - if (mImpressionsRecorderCountTaskId != null) { - mTaskExecutor.stopTask(mImpressionsRecorderCountTaskId); - } - mImpressionsRecorderCountTaskId = mTaskExecutor.schedule( - mImpressionsTaskFactory.createImpressionsCountRecorderTask(), - ServiceConstants.NO_INITIAL_DELAY, - mImpressionsCounterRefreshRate, - null); - } - - private void saveImpressionsCount() { - if (mTrackingIsEnabled.get()) { - mTaskExecutor.submit( - mImpressionsTaskFactory.createSaveImpressionsCountTask(mImpressionsCounter.popAll()), null); - } - } } diff --git a/src/main/java/io/split/android/client/service/impressions/strategy/ProcessStrategy.java b/src/main/java/io/split/android/client/service/impressions/strategy/ProcessStrategy.java index d43786741..202a33835 100644 --- a/src/main/java/io/split/android/client/service/impressions/strategy/ProcessStrategy.java +++ b/src/main/java/io/split/android/client/service/impressions/strategy/ProcessStrategy.java @@ -4,7 +4,7 @@ import io.split.android.client.impressions.Impression; -public interface ProcessStrategy extends PeriodicTracker { +public interface ProcessStrategy { void apply(@NonNull Impression impression); } diff --git a/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java index b18fd0b50..0cdfe304e 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java @@ -20,7 +20,7 @@ import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskFactory; import io.split.android.client.service.executor.SplitTaskType; -import io.split.android.client.service.impressions.ImpressionManager; +import io.split.android.client.service.impressions.StrategyImpressionManager; import io.split.android.client.service.sseclient.feedbackchannel.PushManagerEventBroadcaster; import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer; import io.split.android.client.service.synchronizer.attributes.AttributesSynchronizer; @@ -44,7 +44,7 @@ public class SynchronizerImpl implements Synchronizer, SplitTaskExecutionListene private final StoragePusher mEventsStorage; private final SplitClientConfig mSplitClientConfig; private final SplitTaskFactory mSplitTaskFactory; - private final ImpressionManager mImpressionManager; + private final StrategyImpressionManager mImpressionManager; private final FeatureFlagsSynchronizer mFeatureFlagsSynchronizer; private RecorderSyncHelper mEventsSyncHelper; @@ -66,7 +66,7 @@ public SynchronizerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer, @NonNull AttributesSynchronizerRegistryImpl attributesSynchronizerRegistry, @NonNull MySegmentsSynchronizerRegistryImpl mySegmentsSynchronizerRegistry, - @NonNull ImpressionManager impressionManager, + @NonNull StrategyImpressionManager impressionManager, @NonNull StoragePusher eventsStorage, @NonNull ISplitEventsManager eventsManagerCoordinator, @Nullable PushManagerEventBroadcaster pushManagerEventBroadcaster) { @@ -100,7 +100,7 @@ public SynchronizerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer, @NonNull AttributesSynchronizerRegistryImpl attributesSynchronizerRegistry, @NonNull MySegmentsSynchronizerRegistryImpl mySegmentsSynchronizerRegistry, - @NonNull ImpressionManager impressionManager, + @NonNull StrategyImpressionManager impressionManager, @NonNull FeatureFlagsSynchronizer featureFlagsSynchronizer, @NonNull StoragePusher eventsStorage) { diff --git a/src/test/java/io/split/android/client/service/impressions/strategy/NoneStrategyTest.kt b/src/test/java/io/split/android/client/service/impressions/strategy/NoneStrategyTest.kt index 316ced052..44257b813 100644 --- a/src/test/java/io/split/android/client/service/impressions/strategy/NoneStrategyTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/strategy/NoneStrategyTest.kt @@ -50,8 +50,7 @@ class NoneStrategyTest { taskFactory, impressionsCounter, uniqueKeysTracker, - true, - tracker + true ) } From ed204a7924fd220ac51a0635383a8c4a5f509782 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 10 Dec 2024 10:38:33 -0300 Subject: [PATCH 24/37] WIP --- .../StrategyImpressionManager.java | 10 +- .../strategy/ImpressionStrategyProvider.java | 5 +- .../client/service/SynchronizerTest.java | 8 +- .../StrategyImpressionManagerTest.kt | 8 +- .../impressions/strategy/DebugStrategyTest.kt | 121 ++++++----------- .../impressions/strategy/DebugTrackerTest.kt | 11 ++ .../impressions/strategy/NoneStrategyTest.kt | 28 ---- .../strategy/OptimizedStrategyTest.kt | 128 ++++++------------ .../strategy/OptimizedTrackerTest.kt | 65 ++------- .../SynchronizerImplTelemetryTest.java | 4 +- 10 files changed, 126 insertions(+), 262 deletions(-) diff --git a/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java b/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java index 545f48c18..c970f0182 100644 --- a/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java +++ b/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java @@ -17,9 +17,13 @@ public class StrategyImpressionManager implements ImpressionManager, PeriodicTra private final PeriodicTracker[] mPeriodicTracker; public StrategyImpressionManager(Pair noneComponents, Pair strategy) { - mProcessStrategy = strategy.first; - mNoneStrategy = noneComponents.first; - mPeriodicTracker = new PeriodicTracker[]{noneComponents.second, strategy.second}; + this(noneComponents.first, noneComponents.second, strategy.first, strategy.second); + } + + StrategyImpressionManager(ProcessStrategy noneStrategy, PeriodicTracker noneTracker, ProcessStrategy strategy, PeriodicTracker strategyTracker) { + mProcessStrategy = strategy; + mNoneStrategy = noneStrategy; + mPeriodicTracker = new PeriodicTracker[]{noneTracker, strategyTracker}; } @Override diff --git a/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java b/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java index 17307f526..691209838 100644 --- a/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java +++ b/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java @@ -95,8 +95,7 @@ public Pair getNoneComponents() { mSplitTaskFactory, mImpressionsCounter, uniqueKeysTracker, - mImpressionStrategyConfig.isUserConsentGranted() - ); + mImpressionStrategyConfig.isUserConsentGranted()); NoneTracker noneTracker = new NoneTracker( mSplitTaskExecutor, mSplitTaskFactory, @@ -107,6 +106,6 @@ public Pair getNoneComponents() { mImpressionStrategyConfig.getImpressionsCounterRefreshRate(), mImpressionStrategyConfig.getUniqueKeysRefreshRate(), mImpressionStrategyConfig.isUserConsentGranted()); - return new Pair<>(noneStrategy, noneTracker); // TODO + return new Pair<>(noneStrategy, noneTracker); } } diff --git a/src/test/java/io/split/android/client/service/SynchronizerTest.java b/src/test/java/io/split/android/client/service/SynchronizerTest.java index 58b19db94..ebe456370 100644 --- a/src/test/java/io/split/android/client/service/SynchronizerTest.java +++ b/src/test/java/io/split/android/client/service/SynchronizerTest.java @@ -55,12 +55,12 @@ import io.split.android.client.service.executor.SplitTaskType; import io.split.android.client.service.http.HttpFetcher; import io.split.android.client.service.http.HttpRecorder; -import io.split.android.client.service.impressions.ImpressionManager; import io.split.android.client.service.impressions.ImpressionManagerConfig; import io.split.android.client.service.impressions.ImpressionsCountRecorderTask; import io.split.android.client.service.impressions.ImpressionsMode; import io.split.android.client.service.impressions.ImpressionsRecorderTask; import io.split.android.client.service.impressions.SaveImpressionsCountTask; +import io.split.android.client.service.impressions.StrategyImpressionManager; import io.split.android.client.service.mysegments.LoadMySegmentsTask; import io.split.android.client.service.mysegments.MySegmentsSyncTask; import io.split.android.client.service.mysegments.MySegmentsTaskFactory; @@ -141,7 +141,7 @@ public class SynchronizerTest { private AttributesSynchronizerRegistryImpl mAttributesSynchronizerRegistry; @Mock private FeatureFlagsSynchronizer mFeatureFlagsSynchronizer; - ImpressionManager mImpressionManager; + private StrategyImpressionManager mImpressionManager; private final String mUserKey = "user_key"; @@ -195,7 +195,7 @@ public void setup(SplitClientConfig splitClientConfig, ImpressionManagerConfig.M .thenReturn(mRetryTimerSplitsUpdate); when(mRetryBackoffFactory.createWithFixedInterval(any(), eq(1), eq(3))) .thenReturn(mRetryTimerEventsRecorder); - mImpressionManager = Mockito.mock(ImpressionManager.class); + mImpressionManager = Mockito.mock(StrategyImpressionManager.class); mSynchronizer = new SynchronizerImpl(splitClientConfig, mTaskExecutor, mSingleThreadedTaskExecutor, mTaskFactory, mWorkManagerWrapper, mRetryBackoffFactory, mTelemetryRuntimeProducer, mAttributesSynchronizerRegistry, mMySegmentsSynchronizerRegistry, mImpressionManager, mFeatureFlagsSynchronizer, mSplitStorageContainer.getEventsStorage()); @@ -587,7 +587,7 @@ public void destroy() { .synchronizeInBackground(false) .build(); setup(config); - ImpressionManager impressionManager = mock(ImpressionManager.class); + StrategyImpressionManager impressionManager = mock(StrategyImpressionManager.class); when(mRetryBackoffFactory.create(any(), anyInt())) .thenReturn(mRetryTimerSplitsSync) .thenReturn(mRetryTimerSplitsUpdate); diff --git a/src/test/java/io/split/android/client/service/impressions/StrategyImpressionManagerTest.kt b/src/test/java/io/split/android/client/service/impressions/StrategyImpressionManagerTest.kt index b9128505e..563802099 100644 --- a/src/test/java/io/split/android/client/service/impressions/StrategyImpressionManagerTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/StrategyImpressionManagerTest.kt @@ -18,12 +18,18 @@ class StrategyImpressionManagerTest { @Mock private lateinit var strategy: ProcessStrategy + @Mock + private lateinit var noneStrategy: ProcessStrategy + + @Mock + private lateinit var noneTracker: PeriodicTracker + private lateinit var impressionManager: StrategyImpressionManager @Before fun setUp() { MockitoAnnotations.openMocks(this) - impressionManager = StrategyImpressionManager(strategy, tracker) + impressionManager = StrategyImpressionManager(noneStrategy, noneTracker, strategy, tracker) } @Test diff --git a/src/test/java/io/split/android/client/service/impressions/strategy/DebugStrategyTest.kt b/src/test/java/io/split/android/client/service/impressions/strategy/DebugStrategyTest.kt index d7f4a8607..5e395ba9f 100644 --- a/src/test/java/io/split/android/client/service/impressions/strategy/DebugStrategyTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/strategy/DebugStrategyTest.kt @@ -35,9 +35,6 @@ class DebugStrategyTest { @Mock private lateinit var telemetryRuntimeProducer: TelemetryRuntimeProducer - @Mock - private lateinit var tracker: PeriodicTracker - @Mock private lateinit var impressionsObserver: ImpressionsObserver @@ -52,7 +49,6 @@ class DebugStrategyTest { taskExecutor, taskFactory, telemetryRuntimeProducer, - tracker ) } @@ -88,41 +84,6 @@ class DebugStrategyTest { verify(taskExecutor, never()).submit(impressionsRecorderTask, impressionsSyncHelper) } - @Test - fun `flush calls flush on tracker`() { - strategy.flush() - - verify(tracker).flush() - } - - @Test - fun `startPeriodicRecording calls startPeriodicRecording on tracker`() { - strategy.startPeriodicRecording() - - verify(tracker).startPeriodicRecording() - } - - @Test - fun `stopPeriodicRecording calls stopPeriodicRecording on tracker`() { - strategy.stopPeriodicRecording() - - verify(tracker).stopPeriodicRecording() - } - - @Test - fun `stopPeriodicRecording calls persist on observer`() { - strategy.stopPeriodicRecording() - - verify(impressionsObserver).persist() - } - - @Test - fun `enableTracking calls enableTracking on tracker`() { - strategy.enableTracking(true) - - verify(tracker).enableTracking(true) - } - @Test fun `apply calls testAndSet on observer`() { val impression = createUniqueImpression() @@ -140,47 +101,46 @@ class DebugStrategyTest { spy(impression).withPreviousTime(20421) } - @Test - fun `call stop periodic tracking when sync listener returns do not retry`() { - val listenerCaptor = ArgumentCaptor.forClass(SplitTaskExecutionListener::class.java) - - `when`(impressionsSyncHelper.addListener(listenerCaptor.capture())).thenAnswer { it } - `when`(impressionsSyncHelper.taskExecuted(argThat { - it.taskType == SplitTaskType.IMPRESSIONS_RECORDER - })).thenAnswer { - listenerCaptor.value.taskExecuted( - SplitTaskExecutionInfo.error( - SplitTaskType.IMPRESSIONS_RECORDER, - mapOf(SplitTaskExecutionInfo.DO_NOT_RETRY to true) - ) - ) - it - } - - strategy = DebugStrategy( - impressionsObserver, - impressionsSyncHelper, - taskExecutor, - taskFactory, - telemetryRuntimeProducer, - tracker - ) - - strategy.startPeriodicRecording() - // simulate sync helper trigger - impressionsSyncHelper.taskExecuted( - SplitTaskExecutionInfo.error( - SplitTaskType.IMPRESSIONS_RECORDER, - mapOf(SplitTaskExecutionInfo.DO_NOT_RETRY to true) - ) - ) - - // start periodic recording again to verify it is not working anymore - strategy.startPeriodicRecording() - - verify(tracker, times(1)).startPeriodicRecording() - verify(tracker).stopPeriodicRecording() - } +// @Test +// fun `call stop periodic tracking when sync listener returns do not retry`() { +// val listenerCaptor = ArgumentCaptor.forClass(SplitTaskExecutionListener::class.java) +// +// `when`(impressionsSyncHelper.addListener(listenerCaptor.capture())).thenAnswer { it } +// `when`(impressionsSyncHelper.taskExecuted(argThat { +// it.taskType == SplitTaskType.IMPRESSIONS_RECORDER +// })).thenAnswer { +// listenerCaptor.value.taskExecuted( +// SplitTaskExecutionInfo.error( +// SplitTaskType.IMPRESSIONS_RECORDER, +// mapOf(SplitTaskExecutionInfo.DO_NOT_RETRY to true) +// ) +// ) +// it +// } +// +// strategy = DebugStrategy( +// impressionsObserver, +// impressionsSyncHelper, +// taskExecutor, +// taskFactory, +// telemetryRuntimeProducer, +// ) +// +// strategy.startPeriodicRecording() +// // simulate sync helper trigger +// impressionsSyncHelper.taskExecuted( +// SplitTaskExecutionInfo.error( +// SplitTaskType.IMPRESSIONS_RECORDER, +// mapOf(SplitTaskExecutionInfo.DO_NOT_RETRY to true) +// ) +// ) +// +// // start periodic recording again to verify it is not working anymore +// strategy.startPeriodicRecording() +// +// verify(tracker, times(1)).startPeriodicRecording() +// verify(tracker).stopPeriodicRecording() +// } @Test fun `do not submit recording task when push fails with do not retry`() { @@ -221,7 +181,6 @@ class DebugStrategyTest { taskExecutor, taskFactory, telemetryRuntimeProducer, - tracker ) // call apply two times; first one will trigger the recording task and second one should not diff --git a/src/test/java/io/split/android/client/service/impressions/strategy/DebugTrackerTest.kt b/src/test/java/io/split/android/client/service/impressions/strategy/DebugTrackerTest.kt index 70d2ffd4e..426a670eb 100644 --- a/src/test/java/io/split/android/client/service/impressions/strategy/DebugTrackerTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/strategy/DebugTrackerTest.kt @@ -4,6 +4,7 @@ import io.split.android.client.dtos.KeyImpression import io.split.android.client.service.executor.SplitTaskExecutor import io.split.android.client.service.impressions.ImpressionsRecorderTask import io.split.android.client.service.impressions.ImpressionsTaskFactory +import io.split.android.client.service.impressions.observer.ImpressionsObserver import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer import io.split.android.client.service.synchronizer.RecorderSyncHelper import org.junit.Before @@ -14,6 +15,8 @@ import org.mockito.MockitoAnnotations class DebugTrackerTest { + @Mock + private lateinit var impressionsObserver: ImpressionsObserver @Mock private lateinit var syncHelper: RecorderSyncHelper @Mock @@ -29,6 +32,7 @@ class DebugTrackerTest { fun setUp() { MockitoAnnotations.openMocks(this) tracker = DebugTracker( + impressionsObserver, syncHelper, taskExecutor, taskFactory, @@ -89,4 +93,11 @@ class DebugTrackerTest { verify(taskExecutor).stopTask("250") verify(taskExecutor).schedule(task2, 0L, 30L, syncHelper) } + + @Test + fun `stopPeriodicRecording calls persist on observer`() { + tracker.stopPeriodicRecording() + + verify(impressionsObserver).persist() + } } diff --git a/src/test/java/io/split/android/client/service/impressions/strategy/NoneStrategyTest.kt b/src/test/java/io/split/android/client/service/impressions/strategy/NoneStrategyTest.kt index 44257b813..96c9fd5fa 100644 --- a/src/test/java/io/split/android/client/service/impressions/strategy/NoneStrategyTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/strategy/NoneStrategyTest.kt @@ -89,34 +89,6 @@ class NoneStrategyTest { 1 ) } - - @Test - fun `flush calls flush on tracker`() { - strategy.flush() - - verify(tracker).flush() - } - - @Test - fun `startPeriodicRecording calls startPeriodicRecording on tracker`() { - strategy.startPeriodicRecording() - - verify(tracker).startPeriodicRecording() - } - - @Test - fun `stopPeriodicRecording calls stopPeriodicRecording on tracker`() { - strategy.stopPeriodicRecording() - - verify(tracker).stopPeriodicRecording() - } - - @Test - fun `enableTracking calls enableTracking on tracker`() { - strategy.enableTracking(true) - - verify(tracker).enableTracking(true) - } } fun createUniqueImpression( diff --git a/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedStrategyTest.kt b/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedStrategyTest.kt index f404d9296..3d0472667 100644 --- a/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedStrategyTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedStrategyTest.kt @@ -52,9 +52,6 @@ class OptimizedStrategyTest { @Mock private lateinit var telemetryRuntimeProducer: TelemetryRuntimeProducer - @Mock - private lateinit var tracker: PeriodicTracker - private lateinit var strategy: OptimizedStrategy private val dedupeTimeInterval = ServiceConstants.DEFAULT_IMPRESSIONS_DEDUPE_TIME_INTERVAL @@ -69,8 +66,6 @@ class OptimizedStrategyTest { taskExecutor, taskFactory, telemetryRuntimeProducer, - true, - tracker, dedupeTimeInterval ) } @@ -155,85 +150,48 @@ class OptimizedStrategyTest { ) } - @Test - fun `flush calls flush on tracker`() { - strategy.flush() - - verify(tracker).flush() - } - - @Test - fun `startPeriodicRecording calls startPeriodicRecording on tracker`() { - strategy.startPeriodicRecording() - - verify(tracker).startPeriodicRecording() - } - - @Test - fun `stopPeriodicRecording calls stopPeriodicRecording on tracker`() { - strategy.stopPeriodicRecording() - - verify(tracker).stopPeriodicRecording() - } - - @Test - fun `stopPeriodicRecording calls persist on ImpressionsObserver`() { - strategy.stopPeriodicRecording() - - verify(impressionsObserver).persist() - } - - @Test - fun `enableTracking calls enableTracking on tracker`() { - strategy.enableTracking(true) - - verify(tracker).enableTracking(true) - } - - @Test - fun `call stop periodic tracking when sync listener returns do not retry`() { - val listenerCaptor = ArgumentCaptor.forClass(SplitTaskExecutionListener::class.java) - - `when`(impressionsSyncHelper.addListener(listenerCaptor.capture())).thenAnswer { it } - `when`(impressionsSyncHelper.taskExecuted(argThat { - it.taskType == SplitTaskType.IMPRESSIONS_RECORDER - })).thenAnswer { - listenerCaptor.value.taskExecuted( - SplitTaskExecutionInfo.error( - SplitTaskType.IMPRESSIONS_RECORDER, - mapOf(DO_NOT_RETRY to true) - ) - ) - it - } - - strategy = OptimizedStrategy( - impressionsObserver, - impressionsCounter, - impressionsSyncHelper, - taskExecutor, - taskFactory, - telemetryRuntimeProducer, - true, - tracker, - dedupeTimeInterval - ) - - strategy.startPeriodicRecording() - // simulate sync helper trigger - impressionsSyncHelper.taskExecuted( - SplitTaskExecutionInfo.error( - SplitTaskType.IMPRESSIONS_RECORDER, - mapOf(DO_NOT_RETRY to true) - ) - ) - - // start periodic recording again to verify it is not working anymore - strategy.startPeriodicRecording() - - verify(tracker, times(1)).startPeriodicRecording() - verify(tracker).stopPeriodicRecording() - } +// @Test +// fun `call stop periodic tracking when sync listener returns do not retry`() { +// val listenerCaptor = ArgumentCaptor.forClass(SplitTaskExecutionListener::class.java) +// +// `when`(impressionsSyncHelper.addListener(listenerCaptor.capture())).thenAnswer { it } +// `when`(impressionsSyncHelper.taskExecuted(argThat { +// it.taskType == SplitTaskType.IMPRESSIONS_RECORDER +// })).thenAnswer { +// listenerCaptor.value.taskExecuted( +// SplitTaskExecutionInfo.error( +// SplitTaskType.IMPRESSIONS_RECORDER, +// mapOf(DO_NOT_RETRY to true) +// ) +// ) +// it +// } +// +// strategy = OptimizedStrategy( +// impressionsObserver, +// impressionsCounter, +// impressionsSyncHelper, +// taskExecutor, +// taskFactory, +// telemetryRuntimeProducer, +// dedupeTimeInterval +// ) +// +// strategy.startPeriodicRecording() +// // simulate sync helper trigger +// impressionsSyncHelper.taskExecuted( +// SplitTaskExecutionInfo.error( +// SplitTaskType.IMPRESSIONS_RECORDER, +// mapOf(DO_NOT_RETRY to true) +// ) +// ) +// +// // start periodic recording again to verify it is not working anymore +// strategy.startPeriodicRecording() +// +// verify(tracker, times(1)).startPeriodicRecording() +// verify(tracker).stopPeriodicRecording() +// } @Test fun `do not submit recording task when push fails with do not retry`() { @@ -275,8 +233,6 @@ class OptimizedStrategyTest { taskExecutor, taskFactory, telemetryRuntimeProducer, - true, - tracker, dedupeTimeInterval ) diff --git a/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedTrackerTest.kt b/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedTrackerTest.kt index 51f7d6a4b..4e543f8aa 100644 --- a/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedTrackerTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedTrackerTest.kt @@ -5,6 +5,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.service.executor.SplitTaskExecutor import io.split.android.client.service.executor.SplitTaskSerialWrapper import io.split.android.client.service.impressions.* +import io.split.android.client.service.impressions.observer.ImpressionsObserver import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer import io.split.android.client.service.synchronizer.RecorderSyncHelper import org.junit.Before @@ -25,13 +26,10 @@ class OptimizedTrackerTest { private lateinit var impressionTimer: RetryBackoffCounterTimer @Mock - private lateinit var impressionCountTimer: RetryBackoffCounterTimer + private lateinit var syncHelper: RecorderSyncHelper @Mock - private lateinit var impressionCounter: ImpressionsCounter - - @Mock - private lateinit var syncHelper: RecorderSyncHelper + private lateinit var impressionsObserver: ImpressionsObserver private lateinit var tracker: OptimizedTracker @@ -39,36 +37,16 @@ class OptimizedTrackerTest { fun setUp() { MockitoAnnotations.openMocks(this) tracker = OptimizedTracker( - impressionCounter, + impressionsObserver, syncHelper, taskExecutor, taskFactory, impressionTimer, - impressionCountTimer, 30, - 40, true ) } - @Test - fun `flush flushes impression count`() { - val saveTask = mock(SaveImpressionsCountTask::class.java) - val recorderTask = mock(ImpressionsCountRecorderTask::class.java) - `when`(taskFactory.createSaveImpressionsCountTask(any())) - .thenReturn(saveTask) - `when`(taskFactory.createImpressionsCountRecorderTask()).thenReturn(recorderTask) - - tracker.flush() - - verify(impressionCountTimer) - .setTask(argThat { argument -> - val taskList = argument.taskList - taskList.size == 2 && taskList[0] == saveTask && taskList[1] == recorderTask - }) - verify(impressionCountTimer).start() - } - @Test fun `flush flushes impressions`() { val task = mock(ImpressionsRecorderTask::class.java) @@ -90,16 +68,6 @@ class OptimizedTrackerTest { verify(taskExecutor).schedule(task, 0L, 30L, syncHelper) } - @Test - fun `start periodic recording schedules impression count recorder task`() { - val task = mock(ImpressionsCountRecorderTask::class.java) - `when`(taskFactory.createImpressionsCountRecorderTask()).thenReturn(task) - - tracker.startPeriodicRecording() - - verify(taskExecutor).schedule(task, 0L, 40L, null) - } - @Test fun `stop periodic recording stops impression recording`() { val task = mock(ImpressionsRecorderTask::class.java) @@ -114,24 +82,6 @@ class OptimizedTrackerTest { verify(taskExecutor).stopTask("250") } - @Test - fun `stop periodic recording stops impression count recording`() { - val countTask = mock(ImpressionsCountRecorderTask::class.java) - `when`(taskFactory.createImpressionsCountRecorderTask()).thenReturn(countTask) - `when`( - taskExecutor.schedule( - any(ImpressionsCountRecorderTask::class.java), - eq(0L), - eq(40L), - eq(null) - ) - ).thenReturn("id_1") - tracker.startPeriodicRecording() - tracker.stopPeriodicRecording() - - verify(taskExecutor).stopTask("id_1") - } - @Test fun `stop periodic recording does not save impression count when tracking is disabled`() { val countTask = mock(SaveImpressionsCountTask::class.java) @@ -149,4 +99,11 @@ class OptimizedTrackerTest { verify(taskExecutor, never()).submit(eq(countTask), any()) } + + @Test + fun `stopPeriodicRecording calls persist on ImpressionsObserver`() { + tracker.stopPeriodicRecording() + + verify(impressionsObserver).persist() + } } diff --git a/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java b/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java index 367282052..2c83e3ada 100644 --- a/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java +++ b/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java @@ -23,7 +23,7 @@ import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskFactory; import io.split.android.client.service.executor.SplitTaskType; -import io.split.android.client.service.impressions.ImpressionManager; +import io.split.android.client.service.impressions.StrategyImpressionManager; import io.split.android.client.service.splits.SplitsSyncTask; import io.split.android.client.service.sseclient.feedbackchannel.PushManagerEventBroadcaster; import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer; @@ -55,7 +55,7 @@ public class SynchronizerImplTelemetryTest { @Mock MySegmentsSynchronizerRegistryImpl mMySegmentsSynchronizerRegistry; @Mock - ImpressionManager mImpressionManager; + StrategyImpressionManager mImpressionManager; @Mock PushManagerEventBroadcaster mPushManagerEventBroadcaster; From a041c526a458bd06541fafff4be9674ce3ecb85b Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 10 Dec 2024 11:04:49 -0300 Subject: [PATCH 25/37] WIP --- .../userconsent/UserConsentModeNoneTest.kt | 5 +- .../StrategyImpressionManager.java | 20 ++-- .../strategy/ImpressionStrategyProvider.java | 108 +++++++++--------- 3 files changed, 72 insertions(+), 61 deletions(-) diff --git a/src/androidTest/java/tests/integration/userconsent/UserConsentModeNoneTest.kt b/src/androidTest/java/tests/integration/userconsent/UserConsentModeNoneTest.kt index eac6083a6..ee8ab250a 100644 --- a/src/androidTest/java/tests/integration/userconsent/UserConsentModeNoneTest.kt +++ b/src/androidTest/java/tests/integration/userconsent/UserConsentModeNoneTest.kt @@ -72,8 +72,8 @@ class UserConsentModeNoneTest { mKeysLatch?.await(10, TimeUnit.SECONDS) mCountLatch?.await(10, TimeUnit.SECONDS) - Assert.assertTrue(mKeysPosted) Assert.assertTrue(mCountPosted) + Assert.assertTrue(mKeysPosted) } @Test @@ -207,6 +207,7 @@ class UserConsentModeNoneTest { .impressionsRefreshRate(3) .eventFlushInterval(3) .mtkRefreshRate(3) + .enableDebug() .userConsent(userConsent) .build() @@ -228,7 +229,7 @@ class UserConsentModeNoneTest { return HttpStreamResponseMock(200, null) } - override fun getResponse(uri: URI, method: HttpMethod, body: String): HttpResponseMock { + override fun getResponse(uri: URI, method: HttpMethod, body: String?): HttpResponseMock { println(uri.path) return if (uri.path.contains("/" + IntegrationHelper.ServicePath.MEMBERSHIPS)) { HttpResponseMock(200, IntegrationHelper.emptyAllSegments()) diff --git a/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java b/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java index c970f0182..5eb2a104e 100644 --- a/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java +++ b/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java @@ -1,7 +1,11 @@ package io.split.android.client.service.impressions; +import static io.split.android.client.utils.Utils.checkNotNull; + import androidx.core.util.Pair; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import io.split.android.client.impressions.Impression; @@ -14,16 +18,18 @@ public class StrategyImpressionManager implements ImpressionManager, PeriodicTra private final AtomicBoolean isTrackingEnabled = new AtomicBoolean(true); private final ProcessStrategy mProcessStrategy; private final ProcessStrategy mNoneStrategy; - private final PeriodicTracker[] mPeriodicTracker; + private final Set mPeriodicTrackers; public StrategyImpressionManager(Pair noneComponents, Pair strategy) { this(noneComponents.first, noneComponents.second, strategy.first, strategy.second); } StrategyImpressionManager(ProcessStrategy noneStrategy, PeriodicTracker noneTracker, ProcessStrategy strategy, PeriodicTracker strategyTracker) { - mProcessStrategy = strategy; - mNoneStrategy = noneStrategy; - mPeriodicTracker = new PeriodicTracker[]{noneTracker, strategyTracker}; + mProcessStrategy = checkNotNull(strategy); + mNoneStrategy = checkNotNull(noneStrategy); + mPeriodicTrackers = new HashSet<>(); + mPeriodicTrackers.add(noneTracker); + mPeriodicTrackers.add(strategyTracker); } @Override @@ -47,21 +53,21 @@ public void enableTracking(boolean enable) { @Override public void flush() { - for (PeriodicTracker tracker : mPeriodicTracker) { + for (PeriodicTracker tracker : mPeriodicTrackers) { tracker.flush(); } } @Override public void startPeriodicRecording() { - for (PeriodicTracker tracker : mPeriodicTracker) { + for (PeriodicTracker tracker : mPeriodicTrackers) { tracker.startPeriodicRecording(); } } @Override public void stopPeriodicRecording() { - for (PeriodicTracker tracker : mPeriodicTracker) { + for (PeriodicTracker tracker : mPeriodicTrackers) { tracker.stopPeriodicRecording(); } } diff --git a/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java b/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java index 691209838..c331ee61f 100644 --- a/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java +++ b/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java @@ -26,6 +26,8 @@ public class ImpressionStrategyProvider { private final ImpressionStrategyConfig mImpressionStrategyConfig; private final ImpressionsCounter mImpressionsCounter; private final ImpressionManagerRetryTimerProviderImpl mImpressionManagerRetryTimerProvider; + private final NoneStrategy mNoneStrategy; + private final NoneTracker mNoneTracker; public ImpressionStrategyProvider(SplitTaskExecutor splitTaskExecutor, SplitStorageContainer storageContainer, @@ -39,64 +41,15 @@ public ImpressionStrategyProvider(SplitTaskExecutor splitTaskExecutor, mImpressionStrategyConfig = config; mImpressionsCounter = new ImpressionsCounter(mImpressionStrategyConfig.getDedupeTimeIntervalInMs()); mImpressionManagerRetryTimerProvider = new ImpressionManagerRetryTimerProviderImpl(mSplitTaskExecutor); - } - - public Pair getStrategy(ImpressionsMode mode) { - ImpressionsObserverImpl impressionsObserver = new ImpressionsObserverImpl(mStorageContainer.getImpressionsObserverCachePersistentStorage(), ServiceConstants.LAST_SEEN_IMPRESSION_CACHE_SIZE); - RecorderSyncHelperImpl impressionsSyncHelper = new RecorderSyncHelperImpl<>( - SplitTaskType.IMPRESSIONS_RECORDER, - mStorageContainer.getImpressionsStorage(), - mImpressionStrategyConfig.getImpressionsQueueSize(), - mImpressionStrategyConfig.getImpressionsChunkSize(), - mSplitTaskExecutor); - if (mode == ImpressionsMode.DEBUG) { - DebugTracker tracker = new DebugTracker( - impressionsObserver, - impressionsSyncHelper, - mSplitTaskExecutor, - mSplitTaskFactory, - mImpressionManagerRetryTimerProvider.getImpressionsTimer(), - mImpressionStrategyConfig.getImpressionsRefreshRate()); - DebugStrategy debugStrategy = new DebugStrategy( - impressionsObserver, - impressionsSyncHelper, - mSplitTaskExecutor, - mSplitTaskFactory, - mTelemetryStorage); - return new Pair<>(debugStrategy, tracker); - } else if (mode == ImpressionsMode.NONE) { - return new Pair<>(null, null); - } else { - OptimizedStrategy optimizedStrategy = new OptimizedStrategy( - impressionsObserver, - mImpressionsCounter, - impressionsSyncHelper, - mSplitTaskExecutor, - mSplitTaskFactory, - mTelemetryStorage, - mImpressionStrategyConfig.getDedupeTimeIntervalInMs()); - OptimizedTracker optimizedTracker = new OptimizedTracker( - impressionsObserver, - impressionsSyncHelper, - mSplitTaskExecutor, - mSplitTaskFactory, - mImpressionManagerRetryTimerProvider.getImpressionsTimer(), - mImpressionStrategyConfig.getImpressionsRefreshRate(), - mImpressionStrategyConfig.isUserConsentGranted() - ); - return new Pair<>(optimizedStrategy, optimizedTracker); - } - } - public Pair getNoneComponents() { UniqueKeysTracker uniqueKeysTracker = new UniqueKeysTrackerImpl(); - NoneStrategy noneStrategy = new NoneStrategy( + mNoneStrategy = new NoneStrategy( mSplitTaskExecutor, mSplitTaskFactory, mImpressionsCounter, uniqueKeysTracker, mImpressionStrategyConfig.isUserConsentGranted()); - NoneTracker noneTracker = new NoneTracker( + mNoneTracker = new NoneTracker( mSplitTaskExecutor, mSplitTaskFactory, mImpressionsCounter, @@ -106,6 +59,57 @@ public Pair getNoneComponents() { mImpressionStrategyConfig.getImpressionsCounterRefreshRate(), mImpressionStrategyConfig.getUniqueKeysRefreshRate(), mImpressionStrategyConfig.isUserConsentGranted()); - return new Pair<>(noneStrategy, noneTracker); + } + + public Pair getStrategy(ImpressionsMode mode) { + ImpressionsObserverImpl impressionsObserver = new ImpressionsObserverImpl(mStorageContainer.getImpressionsObserverCachePersistentStorage(), ServiceConstants.LAST_SEEN_IMPRESSION_CACHE_SIZE); + RecorderSyncHelperImpl impressionsSyncHelper = new RecorderSyncHelperImpl<>( + SplitTaskType.IMPRESSIONS_RECORDER, + mStorageContainer.getImpressionsStorage(), + mImpressionStrategyConfig.getImpressionsQueueSize(), + mImpressionStrategyConfig.getImpressionsChunkSize(), + mSplitTaskExecutor); + switch (mode) { + case DEBUG: + DebugTracker tracker = new DebugTracker( + impressionsObserver, + impressionsSyncHelper, + mSplitTaskExecutor, + mSplitTaskFactory, + mImpressionManagerRetryTimerProvider.getImpressionsTimer(), + mImpressionStrategyConfig.getImpressionsRefreshRate()); + DebugStrategy debugStrategy = new DebugStrategy( + impressionsObserver, + impressionsSyncHelper, + mSplitTaskExecutor, + mSplitTaskFactory, + mTelemetryStorage); + return new Pair<>(debugStrategy, tracker); + case NONE: + return getNoneComponents(); + default: + OptimizedStrategy optimizedStrategy = new OptimizedStrategy( + impressionsObserver, + mImpressionsCounter, + impressionsSyncHelper, + mSplitTaskExecutor, + mSplitTaskFactory, + mTelemetryStorage, + mImpressionStrategyConfig.getDedupeTimeIntervalInMs()); + OptimizedTracker optimizedTracker = new OptimizedTracker( + impressionsObserver, + impressionsSyncHelper, + mSplitTaskExecutor, + mSplitTaskFactory, + mImpressionManagerRetryTimerProvider.getImpressionsTimer(), + mImpressionStrategyConfig.getImpressionsRefreshRate(), + mImpressionStrategyConfig.isUserConsentGranted() + ); + return new Pair<>(optimizedStrategy, optimizedTracker); + } + } + + public Pair getNoneComponents() { + return new Pair<>(mNoneStrategy, mNoneTracker); } } From 552b9ecbca35e370e5e79b2f1934dc29880700dd Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 10 Dec 2024 13:33:54 -0300 Subject: [PATCH 26/37] Minor changes to UC tests --- .../integration/userconsent/UserConsentModeDebugTest.kt | 5 +++-- .../tests/integration/userconsent/UserConsentModeNoneTest.kt | 2 +- .../integration/userconsent/UserConsentModeOptimizedTest.kt | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/androidTest/java/tests/integration/userconsent/UserConsentModeDebugTest.kt b/src/androidTest/java/tests/integration/userconsent/UserConsentModeDebugTest.kt index c86df4698..16b224947 100644 --- a/src/androidTest/java/tests/integration/userconsent/UserConsentModeDebugTest.kt +++ b/src/androidTest/java/tests/integration/userconsent/UserConsentModeDebugTest.kt @@ -199,7 +199,8 @@ class UserConsentModeDebugTest { mEventDao = splitRoomDatabase.eventDao() val dispatcher: HttpResponseMockDispatcher = buildDispatcher() val config = TestableSplitConfigBuilder().ready(30000) - .trafficType("client") + .trafficType("account") + .enableDebug() .impressionsMode(ImpressionsMode.DEBUG) .impressionsRefreshRate(3) .eventFlushInterval(3) @@ -224,7 +225,7 @@ class UserConsentModeDebugTest { return HttpStreamResponseMock(200, null) } - override fun getResponse(uri: URI, method: HttpMethod, body: String): HttpResponseMock { + override fun getResponse(uri: URI, method: HttpMethod, body: String?): HttpResponseMock { println(uri.path) return if (uri.path.contains("/" + IntegrationHelper.ServicePath.MEMBERSHIPS)) { HttpResponseMock(200, IntegrationHelper.emptyAllSegments()) diff --git a/src/androidTest/java/tests/integration/userconsent/UserConsentModeNoneTest.kt b/src/androidTest/java/tests/integration/userconsent/UserConsentModeNoneTest.kt index ee8ab250a..fd8e7b4cc 100644 --- a/src/androidTest/java/tests/integration/userconsent/UserConsentModeNoneTest.kt +++ b/src/androidTest/java/tests/integration/userconsent/UserConsentModeNoneTest.kt @@ -202,7 +202,7 @@ class UserConsentModeNoneTest { mCountDao = splitRoomDatabase.impressionsCountDao() val dispatcher: HttpResponseMockDispatcher = buildDispatcher() val config = TestableSplitConfigBuilder().ready(30000) - .trafficType("client") + .trafficType("account") .impressionsMode(ImpressionsMode.NONE) .impressionsRefreshRate(3) .eventFlushInterval(3) diff --git a/src/androidTest/java/tests/integration/userconsent/UserConsentModeOptimizedTest.kt b/src/androidTest/java/tests/integration/userconsent/UserConsentModeOptimizedTest.kt index 686b3436e..ea7580626 100644 --- a/src/androidTest/java/tests/integration/userconsent/UserConsentModeOptimizedTest.kt +++ b/src/androidTest/java/tests/integration/userconsent/UserConsentModeOptimizedTest.kt @@ -209,11 +209,12 @@ class UserConsentModeOptimizedTest { mCountDao = splitRoomDatabase.impressionsCountDao() val dispatcher: HttpResponseMockDispatcher = buildDispatcher() val config = TestableSplitConfigBuilder().ready(30000) - .trafficType("client") + .trafficType("account") .impressionsMode(ImpressionsMode.OPTIMIZED) .impressionsRefreshRate(3) .impressionsCountersRefreshRate(3) .mtkRefreshRate(3) + .enableDebug() .eventFlushInterval(3) .userConsent(userConsent) .build() @@ -236,7 +237,7 @@ class UserConsentModeOptimizedTest { return HttpStreamResponseMock(200, null) } - override fun getResponse(uri: URI, method: HttpMethod, body: String): HttpResponseMock { + override fun getResponse(uri: URI, method: HttpMethod, body: String?): HttpResponseMock { println(uri.path) return if (uri.path.contains("/" + IntegrationHelper.ServicePath.MEMBERSHIPS)) { HttpResponseMock(200, IntegrationHelper.emptyAllSegments()) From 9f7f97ad6c0b8592955c80d3b8d3c2432134a315 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 10 Dec 2024 14:53:26 -0300 Subject: [PATCH 27/37] Wip test changes --- .../impressions/strategy/DebugTracker.java | 24 +++++- .../impressions/strategy/NoneTracker.java | 3 - .../strategy/OptimizedTracker.java | 24 +++++- .../impressions/strategy/DebugStrategyTest.kt | 51 ++----------- .../impressions/strategy/DebugTrackerTest.kt | 50 +++++++++++++ .../impressions/strategy/NoneStrategyTest.kt | 10 --- .../impressions/strategy/NoneTrackerTest.kt | 1 - .../strategy/OptimizedStrategyTest.kt | 43 ----------- .../strategy/OptimizedTrackerTest.kt | 73 +++++++++++++++---- 9 files changed, 162 insertions(+), 117 deletions(-) diff --git a/src/main/java/io/split/android/client/service/impressions/strategy/DebugTracker.java b/src/main/java/io/split/android/client/service/impressions/strategy/DebugTracker.java index d17019865..36d86ff7b 100644 --- a/src/main/java/io/split/android/client/service/impressions/strategy/DebugTracker.java +++ b/src/main/java/io/split/android/client/service/impressions/strategy/DebugTracker.java @@ -4,8 +4,13 @@ import androidx.annotation.NonNull; +import java.util.concurrent.atomic.AtomicBoolean; + import io.split.android.client.dtos.KeyImpression; import io.split.android.client.service.ServiceConstants; +import io.split.android.client.service.executor.SplitTaskExecutionInfo; +import io.split.android.client.service.executor.SplitTaskExecutionListener; +import io.split.android.client.service.executor.SplitTaskExecutionStatus; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.impressions.ImpressionsTaskFactory; import io.split.android.client.service.impressions.observer.ImpressionsObserver; @@ -19,7 +24,21 @@ class DebugTracker implements PeriodicTracker { private final ImpressionsTaskFactory mImpressionsTaskFactory; private final RetryBackoffCounterTimer mRetryTimer; private final int mImpressionsRefreshRate; + private final AtomicBoolean mIsSynchronizing = new AtomicBoolean(true); private final ImpressionsObserver mImpressionsObserver; + /** @noinspection FieldCanBeLocal*/ + private final SplitTaskExecutionListener mTaskExecutionListener = new SplitTaskExecutionListener() { + @Override + public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { + // this listener intercepts impressions recording task + if (taskInfo.getStatus() == SplitTaskExecutionStatus.ERROR) { + if (Boolean.TRUE.equals(taskInfo.getBoolValue(SplitTaskExecutionInfo.DO_NOT_RETRY))) { + mIsSynchronizing.compareAndSet(true, false); + stopPeriodicRecording(); + } + } + } + }; private String mImpressionsRecorderTaskId; DebugTracker(@NonNull ImpressionsObserver impressionsObserver, @@ -30,6 +49,7 @@ class DebugTracker implements PeriodicTracker { int impressionsRefreshRate) { mImpressionsObserver = checkNotNull(impressionsObserver); mImpressionsSyncHelper = checkNotNull(impressionsSyncHelper); + mImpressionsSyncHelper.addListener(mTaskExecutionListener); mTaskExecutor = checkNotNull(taskExecutor); mImpressionsTaskFactory = checkNotNull(taskFactory); mRetryTimer = retryTimer; @@ -50,7 +70,9 @@ private void flushImpressions() { @Override public void startPeriodicRecording() { - scheduleImpressionsRecorderTask(); + if (mIsSynchronizing.get()) { + scheduleImpressionsRecorderTask(); + } } private void scheduleImpressionsRecorderTask() { diff --git a/src/main/java/io/split/android/client/service/impressions/strategy/NoneTracker.java b/src/main/java/io/split/android/client/service/impressions/strategy/NoneTracker.java index 566272c02..de9b5e06c 100644 --- a/src/main/java/io/split/android/client/service/impressions/strategy/NoneTracker.java +++ b/src/main/java/io/split/android/client/service/impressions/strategy/NoneTracker.java @@ -7,13 +7,10 @@ import java.util.concurrent.atomic.AtomicBoolean; import io.split.android.client.service.ServiceConstants; -import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionListener; -import io.split.android.client.service.executor.SplitTaskExecutionStatus; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskSerialWrapper; -import io.split.android.client.service.executor.SplitTaskType; import io.split.android.client.service.impressions.ImpressionsCounter; import io.split.android.client.service.impressions.ImpressionsTaskFactory; import io.split.android.client.service.impressions.unique.UniqueKeysTracker; diff --git a/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedTracker.java b/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedTracker.java index 0ba6f4c25..5f441dfaf 100644 --- a/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedTracker.java +++ b/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedTracker.java @@ -8,6 +8,9 @@ import io.split.android.client.dtos.KeyImpression; import io.split.android.client.service.ServiceConstants; +import io.split.android.client.service.executor.SplitTaskExecutionInfo; +import io.split.android.client.service.executor.SplitTaskExecutionListener; +import io.split.android.client.service.executor.SplitTaskExecutionStatus; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.impressions.ImpressionsTaskFactory; import io.split.android.client.service.impressions.observer.ImpressionsObserver; @@ -25,6 +28,22 @@ class OptimizedTracker implements PeriodicTracker { private final int mImpressionsRefreshRate; private String mImpressionsRecorderTaskId; private final AtomicBoolean mTrackingIsEnabled; + private final AtomicBoolean mIsSynchronizing = new AtomicBoolean(true); + /** + * @noinspection FieldCanBeLocal + */ + private final SplitTaskExecutionListener mTaskExecutionListener = new SplitTaskExecutionListener() { + @Override + public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { + // this listener intercepts impressions recording task + if (taskInfo.getStatus() == SplitTaskExecutionStatus.ERROR) { + if (Boolean.TRUE.equals(taskInfo.getBoolValue(SplitTaskExecutionInfo.DO_NOT_RETRY))) { + mIsSynchronizing.compareAndSet(true, false); + stopPeriodicRecording(); + } + } + } + }; OptimizedTracker(@NonNull ImpressionsObserver impressionsObserver, @NonNull RecorderSyncHelper impressionsSyncHelper, @@ -36,6 +55,7 @@ class OptimizedTracker implements PeriodicTracker { boolean isTrackingEnabled) { mImpressionsObserver = checkNotNull(impressionsObserver); mImpressionsSyncHelper = checkNotNull(impressionsSyncHelper); + mImpressionsSyncHelper.addListener(mTaskExecutionListener); mTaskExecutor = checkNotNull(taskExecutor); mImpressionsTaskFactory = checkNotNull(taskFactory); @@ -51,7 +71,9 @@ public void flush() { @Override public void startPeriodicRecording() { - scheduleImpressionsRecorderTask(); + if (mIsSynchronizing.get()) { + scheduleImpressionsRecorderTask(); + } } @Override diff --git a/src/test/java/io/split/android/client/service/impressions/strategy/DebugStrategyTest.kt b/src/test/java/io/split/android/client/service/impressions/strategy/DebugStrategyTest.kt index 5e395ba9f..25c9919d9 100644 --- a/src/test/java/io/split/android/client/service/impressions/strategy/DebugStrategyTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/strategy/DebugStrategyTest.kt @@ -1,7 +1,6 @@ package io.split.android.client.service.impressions.strategy import io.split.android.client.dtos.KeyImpression -import io.split.android.client.impressions.Impression import io.split.android.client.service.executor.SplitTaskExecutionInfo import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.service.executor.SplitTaskExecutor @@ -17,7 +16,14 @@ import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.Mock -import org.mockito.Mockito.* +import org.mockito.Mockito.argThat +import org.mockito.Mockito.eq +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.invocation.InvocationOnMock @@ -101,47 +107,6 @@ class DebugStrategyTest { spy(impression).withPreviousTime(20421) } -// @Test -// fun `call stop periodic tracking when sync listener returns do not retry`() { -// val listenerCaptor = ArgumentCaptor.forClass(SplitTaskExecutionListener::class.java) -// -// `when`(impressionsSyncHelper.addListener(listenerCaptor.capture())).thenAnswer { it } -// `when`(impressionsSyncHelper.taskExecuted(argThat { -// it.taskType == SplitTaskType.IMPRESSIONS_RECORDER -// })).thenAnswer { -// listenerCaptor.value.taskExecuted( -// SplitTaskExecutionInfo.error( -// SplitTaskType.IMPRESSIONS_RECORDER, -// mapOf(SplitTaskExecutionInfo.DO_NOT_RETRY to true) -// ) -// ) -// it -// } -// -// strategy = DebugStrategy( -// impressionsObserver, -// impressionsSyncHelper, -// taskExecutor, -// taskFactory, -// telemetryRuntimeProducer, -// ) -// -// strategy.startPeriodicRecording() -// // simulate sync helper trigger -// impressionsSyncHelper.taskExecuted( -// SplitTaskExecutionInfo.error( -// SplitTaskType.IMPRESSIONS_RECORDER, -// mapOf(SplitTaskExecutionInfo.DO_NOT_RETRY to true) -// ) -// ) -// -// // start periodic recording again to verify it is not working anymore -// strategy.startPeriodicRecording() -// -// verify(tracker, times(1)).startPeriodicRecording() -// verify(tracker).stopPeriodicRecording() -// } - @Test fun `do not submit recording task when push fails with do not retry`() { val listenerCaptor = ArgumentCaptor.forClass(SplitTaskExecutionListener::class.java) diff --git a/src/test/java/io/split/android/client/service/impressions/strategy/DebugTrackerTest.kt b/src/test/java/io/split/android/client/service/impressions/strategy/DebugTrackerTest.kt index 426a670eb..6ddca42f7 100644 --- a/src/test/java/io/split/android/client/service/impressions/strategy/DebugTrackerTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/strategy/DebugTrackerTest.kt @@ -1,7 +1,10 @@ package io.split.android.client.service.impressions.strategy import io.split.android.client.dtos.KeyImpression +import io.split.android.client.service.executor.SplitTaskExecutionInfo +import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.service.executor.SplitTaskExecutor +import io.split.android.client.service.executor.SplitTaskType import io.split.android.client.service.impressions.ImpressionsRecorderTask import io.split.android.client.service.impressions.ImpressionsTaskFactory import io.split.android.client.service.impressions.observer.ImpressionsObserver @@ -9,6 +12,7 @@ import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTi import io.split.android.client.service.synchronizer.RecorderSyncHelper import org.junit.Before import org.junit.Test +import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.MockitoAnnotations @@ -100,4 +104,50 @@ class DebugTrackerTest { verify(impressionsObserver).persist() } + + @Test + fun `call stop periodic tracking when sync listener returns do not retry`() { + val listenerCaptor = ArgumentCaptor.forClass(SplitTaskExecutionListener::class.java) + + val impressionsRecorderTask = mock(ImpressionsRecorderTask::class.java) + `when`(taskFactory.createImpressionsRecorderTask()).thenReturn(impressionsRecorderTask) + `when`(taskExecutor.schedule(eq(impressionsRecorderTask), eq(0L), eq(30L), any())).thenReturn("250") + `when`(syncHelper.addListener(listenerCaptor.capture())).thenAnswer { it } + `when`(syncHelper.taskExecuted(argThat { + it.taskType == SplitTaskType.IMPRESSIONS_RECORDER + })).thenAnswer { + listenerCaptor.value.taskExecuted( + SplitTaskExecutionInfo.error( + SplitTaskType.IMPRESSIONS_RECORDER, + mapOf(SplitTaskExecutionInfo.DO_NOT_RETRY to true) + ) + ) + it + } + + tracker = DebugTracker( + impressionsObserver, + syncHelper, + taskExecutor, + taskFactory, + retryBackoffCounterTimer, + 30 + ) + + tracker.startPeriodicRecording() + val spy = spy(tracker) + // simulate sync helper trigger + syncHelper.taskExecuted( + SplitTaskExecutionInfo.error( + SplitTaskType.IMPRESSIONS_RECORDER, + mapOf(SplitTaskExecutionInfo.DO_NOT_RETRY to true) + ) + ) + + // start periodic recording again to verify it is not working anymore + spy.startPeriodicRecording() + + verify(taskExecutor).stopTask("250") + verify(impressionsObserver).persist() + } } diff --git a/src/test/java/io/split/android/client/service/impressions/strategy/NoneStrategyTest.kt b/src/test/java/io/split/android/client/service/impressions/strategy/NoneStrategyTest.kt index 96c9fd5fa..daaeb89aa 100644 --- a/src/test/java/io/split/android/client/service/impressions/strategy/NoneStrategyTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/strategy/NoneStrategyTest.kt @@ -7,7 +7,6 @@ import io.split.android.client.service.impressions.ImpressionsCounter import io.split.android.client.service.impressions.ImpressionsTaskFactory import io.split.android.client.service.impressions.unique.SaveUniqueImpressionsTask import io.split.android.client.service.impressions.unique.UniqueKeysTracker -import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers.any @@ -31,15 +30,6 @@ class NoneStrategyTest { @Mock private lateinit var uniqueKeysTracker: UniqueKeysTracker - @Mock - private lateinit var countRetryTimer: RetryBackoffCounterTimer - - @Mock - private lateinit var uniqueKeysRetryTimer: RetryBackoffCounterTimer - - @Mock - private lateinit var tracker: PeriodicTracker - private lateinit var strategy: NoneStrategy @Before diff --git a/src/test/java/io/split/android/client/service/impressions/strategy/NoneTrackerTest.kt b/src/test/java/io/split/android/client/service/impressions/strategy/NoneTrackerTest.kt index 00028caa0..8d1ebe1f9 100644 --- a/src/test/java/io/split/android/client/service/impressions/strategy/NoneTrackerTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/strategy/NoneTrackerTest.kt @@ -186,7 +186,6 @@ class NoneTrackerTest { verify(taskExecutor, never()).submit(eq(countTask), any()) } - @Test fun `stop periodic recording does not save unique keys when tracking is disabled`() { val countTask = mock(SaveUniqueImpressionsTask::class.java) diff --git a/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedStrategyTest.kt b/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedStrategyTest.kt index 3d0472667..7e05fae92 100644 --- a/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedStrategyTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedStrategyTest.kt @@ -150,49 +150,6 @@ class OptimizedStrategyTest { ) } -// @Test -// fun `call stop periodic tracking when sync listener returns do not retry`() { -// val listenerCaptor = ArgumentCaptor.forClass(SplitTaskExecutionListener::class.java) -// -// `when`(impressionsSyncHelper.addListener(listenerCaptor.capture())).thenAnswer { it } -// `when`(impressionsSyncHelper.taskExecuted(argThat { -// it.taskType == SplitTaskType.IMPRESSIONS_RECORDER -// })).thenAnswer { -// listenerCaptor.value.taskExecuted( -// SplitTaskExecutionInfo.error( -// SplitTaskType.IMPRESSIONS_RECORDER, -// mapOf(DO_NOT_RETRY to true) -// ) -// ) -// it -// } -// -// strategy = OptimizedStrategy( -// impressionsObserver, -// impressionsCounter, -// impressionsSyncHelper, -// taskExecutor, -// taskFactory, -// telemetryRuntimeProducer, -// dedupeTimeInterval -// ) -// -// strategy.startPeriodicRecording() -// // simulate sync helper trigger -// impressionsSyncHelper.taskExecuted( -// SplitTaskExecutionInfo.error( -// SplitTaskType.IMPRESSIONS_RECORDER, -// mapOf(DO_NOT_RETRY to true) -// ) -// ) -// -// // start periodic recording again to verify it is not working anymore -// strategy.startPeriodicRecording() -// -// verify(tracker, times(1)).startPeriodicRecording() -// verify(tracker).stopPeriodicRecording() -// } - @Test fun `do not submit recording task when push fails with do not retry`() { val listenerCaptor = ArgumentCaptor.forClass(SplitTaskExecutionListener::class.java) diff --git a/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedTrackerTest.kt b/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedTrackerTest.kt index 4e543f8aa..71915b9ca 100644 --- a/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedTrackerTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedTrackerTest.kt @@ -1,15 +1,19 @@ package io.split.android.client.service.impressions.strategy import io.split.android.client.dtos.KeyImpression +import io.split.android.client.service.executor.SplitTaskExecutionInfo +import io.split.android.client.service.executor.SplitTaskExecutionInfo.DO_NOT_RETRY import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.service.executor.SplitTaskExecutor import io.split.android.client.service.executor.SplitTaskSerialWrapper +import io.split.android.client.service.executor.SplitTaskType import io.split.android.client.service.impressions.* import io.split.android.client.service.impressions.observer.ImpressionsObserver import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer import io.split.android.client.service.synchronizer.RecorderSyncHelper import org.junit.Before import org.junit.Test +import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.MockitoAnnotations @@ -47,6 +51,16 @@ class OptimizedTrackerTest { ) } + @Test + fun `start periodic recording schedules impression recorder task`() { + val task = mock(ImpressionsRecorderTask::class.java) + `when`(taskFactory.createImpressionsRecorderTask()).thenReturn(task) + + tracker.startPeriodicRecording() + + verify(taskExecutor).schedule(task, 0L, 30L, syncHelper) + } + @Test fun `flush flushes impressions`() { val task = mock(ImpressionsRecorderTask::class.java) @@ -83,27 +97,56 @@ class OptimizedTrackerTest { } @Test - fun `stop periodic recording does not save impression count when tracking is disabled`() { - val countTask = mock(SaveImpressionsCountTask::class.java) - `when`(taskFactory.createSaveImpressionsCountTask(any())).thenReturn(countTask) - `when`( - taskExecutor.schedule( - any(SaveImpressionsCountTask::class.java), - eq(0L), - eq(40L), - eq(null) - ) - ).thenReturn("id_1") - tracker.enableTracking(false) + fun `stopPeriodicRecording calls persist on ImpressionsObserver`() { tracker.stopPeriodicRecording() - verify(taskExecutor, never()).submit(eq(countTask), any()) + verify(impressionsObserver).persist() } @Test - fun `stopPeriodicRecording calls persist on ImpressionsObserver`() { - tracker.stopPeriodicRecording() + fun `call stop periodic tracking when sync listener returns do not retry`() { + val listenerCaptor = ArgumentCaptor.forClass(SplitTaskExecutionListener::class.java) + + val impressionsRecorderTask = mock(ImpressionsRecorderTask::class.java) + `when`(taskFactory.createImpressionsRecorderTask()).thenReturn(impressionsRecorderTask) + `when`(taskExecutor.schedule(eq(impressionsRecorderTask), eq(0L), eq(30L), any())).thenReturn("250") + `when`(syncHelper.addListener(listenerCaptor.capture())).thenAnswer { it } + `when`(syncHelper.taskExecuted(argThat { + it.taskType == SplitTaskType.IMPRESSIONS_RECORDER + })).thenAnswer { + listenerCaptor.value.taskExecuted( + SplitTaskExecutionInfo.error( + SplitTaskType.IMPRESSIONS_RECORDER, + mapOf(DO_NOT_RETRY to true) + ) + ) + it + } + tracker = OptimizedTracker( + impressionsObserver, + syncHelper, + taskExecutor, + taskFactory, + impressionTimer, + 30, + true + ) + + val spy = spy(tracker) + tracker.startPeriodicRecording() + // simulate sync helper trigger + syncHelper.taskExecuted( + SplitTaskExecutionInfo.error( + SplitTaskType.IMPRESSIONS_RECORDER, + mapOf(DO_NOT_RETRY to true) + ) + ) + + // start periodic recording again to verify it is not working anymore + spy.startPeriodicRecording() + + verify(taskExecutor).stopTask("250") verify(impressionsObserver).persist() } } From 4c3b592fb09d3b8acaa2fe4222d3f6a2fdeb8cd7 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 10 Dec 2024 17:34:40 -0300 Subject: [PATCH 28/37] Update recorder sync helper to handle multiple listeners --- .../synchronizer/RecorderSyncHelperImpl.java | 23 +++++++++++----- .../RecorderSyncHelperImplTest.java | 27 +++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImpl.java b/src/main/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImpl.java index a2582584a..4351930fb 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImpl.java @@ -5,6 +5,8 @@ import androidx.annotation.NonNull; import java.lang.ref.WeakReference; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -26,7 +28,7 @@ public class RecorderSyncHelperImpl implements Recorde private final int mMaxQueueSize; private final long mMaxQueueSizeInBytes; private final SplitTaskType mTaskType; - private WeakReference mTaskExecutionListener; + private final Set> mTaskExecutionListener; public RecorderSyncHelperImpl(SplitTaskType taskType, StoragePusher storage, @@ -40,7 +42,7 @@ public RecorderSyncHelperImpl(SplitTaskType taskType, mTotalPushedSizeInBytes = new AtomicLong(0); mMaxQueueSize = maxQueueSize; mMaxQueueSizeInBytes = maxQueueSizeInBytes; - mTaskExecutionListener = new WeakReference<>(null); + mTaskExecutionListener = new HashSet<>(); } @Override @@ -67,19 +69,28 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { SplitTaskExecutionInfo.NON_SENT_BYTES)); } - if (mTaskExecutionListener.get() != null) { - mTaskExecutionListener.get().taskExecuted(taskInfo); + for (WeakReference reference : mTaskExecutionListener) { + SplitTaskExecutionListener listener = reference.get(); + if (listener != null) { + listener.taskExecuted(taskInfo); + } } } @Override public void addListener(SplitTaskExecutionListener listener) { - mTaskExecutionListener = new WeakReference<>(listener); + mTaskExecutionListener.add(new WeakReference<>(listener)); } @Override public void removeListener(SplitTaskExecutionListener listener) { - mTaskExecutionListener = new WeakReference<>(null); + for (WeakReference reference : mTaskExecutionListener) { + SplitTaskExecutionListener listenerRef = reference.get(); + if (listenerRef != null && listenerRef.equals(listener)) { + mTaskExecutionListener.remove(reference); + break; + } + } } private void pushAsync(T entity) { diff --git a/src/test/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImplTest.java b/src/test/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImplTest.java index dcd5a45ad..1888bf831 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImplTest.java +++ b/src/test/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImplTest.java @@ -52,13 +52,40 @@ public void listenerIsNotCalledOnceRemoved() { mTaskExecutor); SplitTaskExecutionListener listener = mock(SplitTaskExecutionListener.class); + SplitTaskExecutionListener listener2 = mock(SplitTaskExecutionListener.class); SplitTaskExecutionInfo executionInfo = SplitTaskExecutionInfo.success(SplitTaskType.IMPRESSIONS_RECORDER); syncHelper.addListener(listener); + syncHelper.addListener(listener2); syncHelper.taskExecuted(executionInfo); syncHelper.removeListener(listener); syncHelper.taskExecuted(executionInfo); verify(listener, times(1)).taskExecuted(executionInfo); + verify(listener2, times(2)).taskExecuted(executionInfo); + } + + @Test + public void multipleListenersCanBeAdded() { + mStoragePusher = mock(StoragePusher.class); + mTaskExecutor = mock(SplitTaskExecutor.class); + + RecorderSyncHelperImpl syncHelper = new RecorderSyncHelperImpl<>( + SplitTaskType.IMPRESSIONS_RECORDER, + mStoragePusher, + 1, + 1, + mTaskExecutor); + + SplitTaskExecutionListener listener1 = mock(SplitTaskExecutionListener.class); + SplitTaskExecutionListener listener2 = mock(SplitTaskExecutionListener.class); + SplitTaskExecutionInfo executionInfo = SplitTaskExecutionInfo.success(SplitTaskType.IMPRESSIONS_RECORDER); + + syncHelper.addListener(listener1); + syncHelper.addListener(listener2); + syncHelper.taskExecuted(executionInfo); + + verify(listener1).taskExecuted(executionInfo); + verify(listener2).taskExecuted(executionInfo); } } From 8d8e1614ef35049529cce71eee93a0876d1f4c45 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 12 Dec 2024 14:36:15 -0300 Subject: [PATCH 29/37] Fix test --- .../streaming/CleanUpDatabaseTest.java | 16 ++++++---------- .../split/android/client/SplitFactoryImpl.java | 5 ----- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/androidTest/java/tests/integration/streaming/CleanUpDatabaseTest.java b/src/androidTest/java/tests/integration/streaming/CleanUpDatabaseTest.java index f7f9c6127..30b748d0d 100644 --- a/src/androidTest/java/tests/integration/streaming/CleanUpDatabaseTest.java +++ b/src/androidTest/java/tests/integration/streaming/CleanUpDatabaseTest.java @@ -1,7 +1,5 @@ package tests.integration.streaming; -import static java.lang.Thread.sleep; - import android.content.Context; import androidx.core.util.Pair; @@ -140,7 +138,7 @@ public void testCleanUp() throws IOException, InterruptedException { .streamingEnabled(true) .impressionsRefreshRate(999999999) .eventFlushInterval(99999999) - .logLevel(SplitLogLevel.DEBUG) + .logLevel(SplitLogLevel.VERBOSE) .trafficType("account") .build(); @@ -155,11 +153,12 @@ public void testCleanUp() throws IOException, InterruptedException { mClient.on(SplitEvent.SDK_READY, readyTask); latch.await(40, TimeUnit.SECONDS); - - // wait to allow cleanup to run - sleep(5000); + Thread.sleep(1000); // Load all records again after cleanup + List remainingKeys = mUniqueKeysDao.getBy(0, StorageRecordStatus.ACTIVE, 10); + remainingKeys.addAll(mUniqueKeysDao.getBy(0, StorageRecordStatus.DELETED, 10)); + List remainingEvents = mEventDao.getBy(0, StorageRecordStatus.ACTIVE, 10); remainingEvents.addAll(mEventDao.getBy(0, StorageRecordStatus.DELETED, 10)); @@ -169,8 +168,6 @@ public void testCleanUp() throws IOException, InterruptedException { List remainingCounts = mImpressionsCountDao.getBy(0, StorageRecordStatus.ACTIVE, 10); remainingCounts.addAll(mImpressionsCountDao.getBy(0, StorageRecordStatus.DELETED, 10)); - List remainingKeys = mUniqueKeysDao.getBy(0, StorageRecordStatus.ACTIVE, 10); - remainingKeys.addAll(mUniqueKeysDao.getBy(0, StorageRecordStatus.DELETED, 10)); List remainingImpressionsObserverCacheEntities = mImpressionsObserverCacheDao.getAll(3); @@ -184,7 +181,6 @@ public void testCleanUp() throws IOException, InterruptedException { Assert.assertEquals(1, remainingCounts.size()); Assert.assertEquals(1, remainingKeys.size()); Assert.assertEquals(1, remainingImpressionsObserverCacheEntities.size()); - } private EventEntity createEventEntity(long createdAt, int status, String name) { @@ -258,7 +254,7 @@ public HttpResponseMock getResponse(URI uri, HttpMethod method, String body) { } else if (uri.getPath().contains("/bulk")) { return createResponse(500, ""); } else { - return new HttpResponseMock(200); + return new HttpResponseMock(404); } } diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index 260af4d04..058c1139d 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -413,11 +413,6 @@ private void setupValidations(SplitClientConfig splitClientConfig) { ValidationConfig.getInstance().setTrackEventNamePattern(splitClientConfig.trackEventNamePattern()); } - private void cleanUpDabase(SplitTaskExecutor splitTaskExecutor, - SplitTaskFactory splitTaskFactory) { - splitTaskExecutor.submit(splitTaskFactory.createCleanUpDatabaseTask(System.currentTimeMillis() / 1000), null); - } - private EventsTracker buildEventsTracker() { EventValidator eventsValidator = new EventValidatorImpl(new KeyValidatorImpl(), mStorageContainer.getSplitsStorage()); return new EventsTrackerImpl(eventsValidator, new ValidationMessageLoggerImpl(), mStorageContainer.getTelemetryStorage(), From 872f01e8048e9e16da3f9759804c6f2d24e0f08e Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 11 Dec 2024 11:44:42 -0300 Subject: [PATCH 30/37] Add trackImpressions to Split DTO --- .../io/split/android/client/dtos/Split.java | 3 + .../engine/experiments/ParsedSplit.java | 15 ++- .../engine/experiments/SplitParser.java | 3 +- .../client/dtos/SplitDeserializationTest.java | 127 ++++++++++++++++++ .../engine/experiments/SplitParserTest.java | 19 +++ .../io/split/android/helpers/SplitHelper.java | 3 +- 6 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 src/test/java/io/split/android/client/dtos/SplitDeserializationTest.java diff --git a/src/main/java/io/split/android/client/dtos/Split.java b/src/main/java/io/split/android/client/dtos/Split.java index 395a4c3a3..be1b235f1 100644 --- a/src/main/java/io/split/android/client/dtos/Split.java +++ b/src/main/java/io/split/android/client/dtos/Split.java @@ -49,4 +49,7 @@ public class Split { @Nullable @SerializedName("sets") public Set sets; + + @SerializedName("trackImpressions") + public boolean trackImpressions = true; } diff --git a/src/main/java/io/split/android/engine/experiments/ParsedSplit.java b/src/main/java/io/split/android/engine/experiments/ParsedSplit.java index c00b17f8b..a9ae822f7 100644 --- a/src/main/java/io/split/android/engine/experiments/ParsedSplit.java +++ b/src/main/java/io/split/android/engine/experiments/ParsedSplit.java @@ -23,6 +23,7 @@ public class ParsedSplit { private final int mAlgo; private final Map mConfigurations; private final Set mSets; + private final boolean mTrackImpressions; public ParsedSplit( String feature, @@ -36,7 +37,8 @@ public ParsedSplit( int trafficAllocationSeed, int algo, Map configurations, - Set sets + Set sets, + boolean trackImpressions ) { mSplit = feature; mSeed = seed; @@ -47,6 +49,7 @@ public ParsedSplit( mChangeNumber = changeNumber; mAlgo = algo; mConfigurations = configurations; + mTrackImpressions = trackImpressions; if (mDefaultTreatment == null) { throw new IllegalArgumentException("DefaultTreatment is null"); @@ -104,6 +107,10 @@ public Set sets() { return mSets; } + public boolean trackImpression() { + return mTrackImpressions; + } + @Override public int hashCode() { int result = 17; @@ -116,6 +123,7 @@ public int hashCode() { result = 31 * result + (int) (mChangeNumber ^ (mChangeNumber >>> 32)); result = 31 * result + (mAlgo ^ (mAlgo >>> 32)); result = 31 * result + ((mSets != null) ? mSets.hashCode() : 0); + result = 31 * result + (mTrackImpressions ? 1 : 0); return result; } @@ -135,7 +143,8 @@ public boolean equals(Object obj) { && mChangeNumber == other.mChangeNumber && mAlgo == other.mAlgo && (Objects.equals(mConfigurations, other.mConfigurations)) - && (Objects.equals(mSets, other.mSets)); + && (Objects.equals(mSets, other.mSets) + && mTrackImpressions == other.mTrackImpressions); } @@ -146,7 +155,7 @@ public String toString() { ", default treatment:" + mDefaultTreatment + ", parsedConditions:" + mParsedCondition + ", trafficTypeName:" + mTrafficTypeName + ", changeNumber:" + mChangeNumber + - ", algo:" + mAlgo + ", config:" + mConfigurations + ", sets:" + mSets; + ", algo:" + mAlgo + ", config:" + mConfigurations + ", sets:" + mSets + ", trackImpression:" + mTrackImpressions; } } diff --git a/src/main/java/io/split/android/engine/experiments/SplitParser.java b/src/main/java/io/split/android/engine/experiments/SplitParser.java index 2763ecca7..5684afd88 100644 --- a/src/main/java/io/split/android/engine/experiments/SplitParser.java +++ b/src/main/java/io/split/android/engine/experiments/SplitParser.java @@ -124,7 +124,8 @@ private ParsedSplit parseWithoutExceptionHandling(Split split, String matchingKe split.trafficAllocationSeed, split.algo, split.configurations, - split.sets); + split.sets, + split.trackImpressions); } private CombiningMatcher toMatcher(MatcherGroup matcherGroup, String matchingKey) throws UnsupportedMatcherException { diff --git a/src/test/java/io/split/android/client/dtos/SplitDeserializationTest.java b/src/test/java/io/split/android/client/dtos/SplitDeserializationTest.java new file mode 100644 index 000000000..0fa373f9d --- /dev/null +++ b/src/test/java/io/split/android/client/dtos/SplitDeserializationTest.java @@ -0,0 +1,127 @@ +package io.split.android.client.dtos; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import io.split.android.client.utils.Json; + +public class SplitDeserializationTest { + + @Test + public void trackImpressionsDefaultsToTrueWhenNotPresentInSplit() { + assertTrue(Json.fromJson(getTestSplit(null), Split.class).trackImpressions); + } + + @Test + public void trackImpressionsValueIsParsedCorrectly() { + assertTrue(Json.fromJson(getTestSplit(true), Split.class).trackImpressions); + assertFalse(Json.fromJson(getTestSplit(false), Split.class).trackImpressions); + } + + private String getTestSplit(Boolean trackImpressions) { + return "{\n" + + ((trackImpressions != null) ? "\"trackImpressions\": " + trackImpressions + ",\n" : "") + + " \"trafficTypeName\": \"client\",\n" + + " \"name\": \"workm\",\n" + + " \"trafficAllocation\": 100,\n" + + " \"trafficAllocationSeed\": 147392224,\n" + + " \"seed\": 524417105,\n" + + " \"status\": \"ACTIVE\",\n" + + " \"killed\": false,\n" + + " \"defaultTreatment\": \"on\",\n" + + " \"changeNumber\": 1602796638344,\n" + + " \"algo\": 2,\n" + + " \"configurations\": {},\n" + + " \"conditions\": [\n" + + " {\n" + + " \"conditionType\": \"ROLLOUT\",\n" + + " \"matcherGroup\": {\n" + + " \"combiner\": \"AND\",\n" + + " \"matchers\": [\n" + + " {\n" + + " \"keySelector\": {\n" + + " \"trafficType\": \"client\",\n" + + " \"attribute\": null\n" + + " },\n" + + " \"matcherType\": \"IN_SEGMENT\",\n" + + " \"negate\": false,\n" + + " \"userDefinedSegmentMatcherData\": {\n" + + " \"segmentName\": \"new_segment\"\n" + + " },\n" + + " \"whitelistMatcherData\": null,\n" + + " \"unaryNumericMatcherData\": null,\n" + + " \"betweenMatcherData\": null,\n" + + " \"booleanMatcherData\": null,\n" + + " \"dependencyMatcherData\": null,\n" + + " \"stringMatcherData\": null\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"partitions\": [\n" + + " {\n" + + " \"treatment\": \"on\",\n" + + " \"size\": 0\n" + + " },\n" + + " {\n" + + " \"treatment\": \"off\",\n" + + " \"size\": 0\n" + + " },\n" + + " {\n" + + " \"treatment\": \"free\",\n" + + " \"size\": 100\n" + + " },\n" + + " {\n" + + " \"treatment\": \"conta\",\n" + + " \"size\": 0\n" + + " }\n" + + " ],\n" + + " \"label\": \"in segment new_segment\"\n" + + " },\n" + + " {\n" + + " \"conditionType\": \"ROLLOUT\",\n" + + " \"matcherGroup\": {\n" + + " \"combiner\": \"AND\",\n" + + " \"matchers\": [\n" + + " {\n" + + " \"keySelector\": {\n" + + " \"trafficType\": \"client\",\n" + + " \"attribute\": null\n" + + " },\n" + + " \"matcherType\": \"ALL_KEYS\",\n" + + " \"negate\": false,\n" + + " \"userDefinedSegmentMatcherData\": null,\n" + + " \"whitelistMatcherData\": null,\n" + + " \"unaryNumericMatcherData\": null,\n" + + " \"betweenMatcherData\": null,\n" + + " \"booleanMatcherData\": null,\n" + + " \"dependencyMatcherData\": null,\n" + + " \"stringMatcherData\": null\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"partitions\": [\n" + + " {\n" + + " \"treatment\": \"on\",\n" + + " \"size\": 100\n" + + " },\n" + + " {\n" + + " \"treatment\": \"off\",\n" + + " \"size\": 0\n" + + " },\n" + + " {\n" + + " \"treatment\": \"free\",\n" + + " \"size\": 0\n" + + " },\n" + + " {\n" + + " \"treatment\": \"conta\",\n" + + " \"size\": 0\n" + + " }\n" + + " ],\n" + + " \"label\": \"default rule\"\n" + + " }\n" + + " ]\n" + + " }"; + } +} diff --git a/src/test/java/io/split/android/engine/experiments/SplitParserTest.java b/src/test/java/io/split/android/engine/experiments/SplitParserTest.java index e6c1d6ca6..cf24758e2 100644 --- a/src/test/java/io/split/android/engine/experiments/SplitParserTest.java +++ b/src/test/java/io/split/android/engine/experiments/SplitParserTest.java @@ -3,7 +3,9 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -495,6 +497,22 @@ public void inLargeSegmentMatcherParsingTest() { verify(mMySegmentsStorageContainer, never()).getStorageForKey("matching_key"); } + @Test + public void trackImpressionsParsingTest(){ + SplitParser parser = createParser(); + + Split split = makeSplit("splitName", Collections.emptyList()); + split.trackImpressions = false; + Split split2 = makeSplit("splitName", Collections.emptyList()); + split2.trackImpressions = true; + + ParsedSplit actual = parser.parse(split); + ParsedSplit actual2 = parser.parse(split2); + + assertFalse(actual.trackImpression()); + assertTrue(actual2.trackImpression()); + } + private void set_matcher_test(Condition c, io.split.android.engine.matchers.Matcher m) { SplitParser parser = createParser(); @@ -536,6 +554,7 @@ private Split makeSplit(String name, List conditions, long changeNumb split.algo = 1; split.configurations = configurations; split.sets = Collections.emptySet(); + split.trackImpressions = true; return split; } diff --git a/src/test/java/io/split/android/helpers/SplitHelper.java b/src/test/java/io/split/android/helpers/SplitHelper.java index 0aec872e8..c0f4792c0 100644 --- a/src/test/java/io/split/android/helpers/SplitHelper.java +++ b/src/test/java/io/split/android/helpers/SplitHelper.java @@ -122,7 +122,8 @@ public static ParsedSplit createParsedSplit( seed, algo, configurations, - Collections.emptySet() + Collections.emptySet(), + true ); } From 3845f39c38308331423f29a1fbe2d15f7e9fa1c7 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 12 Dec 2024 15:09:06 -0300 Subject: [PATCH 31/37] Add trackImpressions to SplitView --- .../android/client/SplitManagerImpl.java | 19 ++++++++++--------- .../split/android/client/api/SplitView.java | 1 + .../engine/experiments/ParsedSplit.java | 4 ++-- .../android/client/SplitManagerImplTest.java | 14 ++++++++++++++ .../engine/experiments/SplitParserTest.java | 4 ++-- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitManagerImpl.java b/src/main/java/io/split/android/client/SplitManagerImpl.java index 5fe90cd85..4f7bc30ae 100644 --- a/src/main/java/io/split/android/client/SplitManagerImpl.java +++ b/src/main/java/io/split/android/client/SplitManagerImpl.java @@ -1,5 +1,14 @@ package io.split.android.client; +import static io.split.android.client.utils.Utils.checkNotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + import io.split.android.client.api.SplitView; import io.split.android.client.dtos.Partition; import io.split.android.client.dtos.Split; @@ -13,15 +22,6 @@ import io.split.android.engine.experiments.ParsedSplit; import io.split.android.engine.experiments.SplitParser; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static io.split.android.client.utils.Utils.checkNotNull; - public class SplitManagerImpl implements SplitManager { private final SplitsStorage _splitsStorage; @@ -144,6 +144,7 @@ private SplitView toSplitView(ParsedSplit parsedSplit) { splitView.configs = parsedSplit.configurations(); splitView.sets = new ArrayList<>(parsedSplit.sets() == null ? new HashSet<>() : parsedSplit.sets()); splitView.defaultTreatment = parsedSplit.defaultTreatment(); + splitView.trackImpressions = parsedSplit.trackImpressions(); Set treatments = new HashSet<>(); for (ParsedCondition condition : parsedSplit.parsedConditions()) { diff --git a/src/main/java/io/split/android/client/api/SplitView.java b/src/main/java/io/split/android/client/api/SplitView.java index 9cbc7192e..a8a416037 100644 --- a/src/main/java/io/split/android/client/api/SplitView.java +++ b/src/main/java/io/split/android/client/api/SplitView.java @@ -21,4 +21,5 @@ public class SplitView { @NonNull public List sets = new ArrayList<>(); public String defaultTreatment; + public boolean trackImpressions; } diff --git a/src/main/java/io/split/android/engine/experiments/ParsedSplit.java b/src/main/java/io/split/android/engine/experiments/ParsedSplit.java index a9ae822f7..eec6ed718 100644 --- a/src/main/java/io/split/android/engine/experiments/ParsedSplit.java +++ b/src/main/java/io/split/android/engine/experiments/ParsedSplit.java @@ -107,7 +107,7 @@ public Set sets() { return mSets; } - public boolean trackImpression() { + public boolean trackImpressions() { return mTrackImpressions; } @@ -155,7 +155,7 @@ public String toString() { ", default treatment:" + mDefaultTreatment + ", parsedConditions:" + mParsedCondition + ", trafficTypeName:" + mTrafficTypeName + ", changeNumber:" + mChangeNumber + - ", algo:" + mAlgo + ", config:" + mConfigurations + ", sets:" + mSets + ", trackImpression:" + mTrackImpressions; + ", algo:" + mAlgo + ", config:" + mConfigurations + ", sets:" + mSets + ", trackImpressions:" + mTrackImpressions; } } diff --git a/src/test/java/io/split/android/client/SplitManagerImplTest.java b/src/test/java/io/split/android/client/SplitManagerImplTest.java index 07adcfc84..02ff0b7f3 100644 --- a/src/test/java/io/split/android/client/SplitManagerImplTest.java +++ b/src/test/java/io/split/android/client/SplitManagerImplTest.java @@ -6,6 +6,7 @@ import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.when; @@ -186,6 +187,19 @@ public void defaultTreatmentIsPresentWhenFetchingMultipleSplits() { assertEquals("some_treatment", splitNames.get(0).defaultTreatment); } + @Test + public void trackImpressionsIsPresent() { + Split split = SplitHelper.createSplit("FeatureName", 123, true, + "some_treatment", Arrays.asList(getTestCondition()), + "traffic", 456L, 1, null); + split.trackImpressions = false; + when(mSplitsStorage.get("FeatureName")).thenReturn(split); + + SplitView featureFlag = mSplitManager.split("FeatureName"); + + assertFalse(featureFlag.trackImpressions); + } + private Condition getTestCondition() { return SplitHelper.createCondition(CombiningMatcher.of(new AllKeysMatcher()), Arrays.asList(ConditionsTestUtil.partition("off", 10))); } diff --git a/src/test/java/io/split/android/engine/experiments/SplitParserTest.java b/src/test/java/io/split/android/engine/experiments/SplitParserTest.java index cf24758e2..ae7024f8d 100644 --- a/src/test/java/io/split/android/engine/experiments/SplitParserTest.java +++ b/src/test/java/io/split/android/engine/experiments/SplitParserTest.java @@ -509,8 +509,8 @@ public void trackImpressionsParsingTest(){ ParsedSplit actual = parser.parse(split); ParsedSplit actual2 = parser.parse(split2); - assertFalse(actual.trackImpression()); - assertTrue(actual2.trackImpression()); + assertFalse(actual.trackImpressions()); + assertTrue(actual2.trackImpressions()); } private void set_matcher_test(Condition c, io.split.android.engine.matchers.Matcher m) { From 23d075886369f87228aba094be2bbd35bb45593d Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 11 Dec 2024 16:56:47 -0300 Subject: [PATCH 32/37] Decorated impression & evaluation result changes --- .../android/client/EvaluationResult.java | 21 +++++-- .../split/android/client/EvaluatorImpl.java | 14 ++--- .../impressions/DecoratedImpression.java | 17 ++++++ .../StrategyImpressionManager.java | 12 +++- .../validators/TreatmentManagerImpl.java | 14 +++-- .../android/client/TreatmentManagerTest.java | 16 ++++++ .../StrategyImpressionManagerTest.kt | 56 ++++++++++++++++++- 7 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 src/main/java/io/split/android/client/impressions/DecoratedImpression.java diff --git a/src/main/java/io/split/android/client/EvaluationResult.java b/src/main/java/io/split/android/client/EvaluationResult.java index f5ecebc80..d7f16ec4c 100644 --- a/src/main/java/io/split/android/client/EvaluationResult.java +++ b/src/main/java/io/split/android/client/EvaluationResult.java @@ -1,24 +1,33 @@ package io.split.android.client; +import androidx.annotation.VisibleForTesting; + public final class EvaluationResult { private final String mTreatment; private final String mLabel; private final Long mChangeNumber; private final String mConfigurations; + private final boolean mTrackImpression; + @VisibleForTesting public EvaluationResult(String treatment, String label) { - this(treatment, label, null); + this(treatment, label, null, null, true); + } + + public EvaluationResult(String treatment, String label, boolean trackImpression) { + this(treatment, label, null, null, trackImpression); } - EvaluationResult(String treatment, String label, Long changeNumber) { - this(treatment, label, changeNumber, null); + EvaluationResult(String treatment, String label, Long changeNumber, boolean trackImpression) { + this(treatment, label, changeNumber, null, trackImpression); } - public EvaluationResult(String treatment, String label, Long changeNumber, String configurations) { + public EvaluationResult(String treatment, String label, Long changeNumber, String configurations, boolean trackImpression) { mTreatment = treatment; mLabel = label; mChangeNumber = changeNumber; mConfigurations = configurations; + mTrackImpression = trackImpression; } public String getTreatment() { @@ -36,4 +45,8 @@ public Long getChangeNumber() { public String getConfigurations() { return mConfigurations; } + + public boolean getTrackImpression() { + return mTrackImpression; + } } diff --git a/src/main/java/io/split/android/client/EvaluatorImpl.java b/src/main/java/io/split/android/client/EvaluatorImpl.java index ea15ab796..b845bf6d0 100644 --- a/src/main/java/io/split/android/client/EvaluatorImpl.java +++ b/src/main/java/io/split/android/client/EvaluatorImpl.java @@ -28,16 +28,16 @@ public EvaluationResult getTreatment(String matchingKey, String bucketingKey, St try { ParsedSplit parsedSplit = mSplitParser.parse(mSplitsStorage.get(splitName), matchingKey); if (parsedSplit == null) { - return new EvaluationResult(Treatments.CONTROL, TreatmentLabels.DEFINITION_NOT_FOUND); + return new EvaluationResult(Treatments.CONTROL, TreatmentLabels.DEFINITION_NOT_FOUND, true); } return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper ex) { Logger.e(ex, "Catch Change Number Exception"); - return new EvaluationResult(Treatments.CONTROL, TreatmentLabels.EXCEPTION, ex.changeNumber()); + return new EvaluationResult(Treatments.CONTROL, TreatmentLabels.EXCEPTION, ex.changeNumber(), true); } catch (Exception e) { Logger.e(e, "Catch All Exception"); - return new EvaluationResult(Treatments.CONTROL, TreatmentLabels.EXCEPTION); + return new EvaluationResult(Treatments.CONTROL, TreatmentLabels.EXCEPTION, true); } } @@ -52,7 +52,7 @@ public EvaluationResult getTreatment(String matchingKey, String bucketingKey, St private EvaluationResult getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit, Map attributes) throws ChangeNumberExceptionWrapper { try { if (parsedSplit.killed()) { - return new EvaluationResult(parsedSplit.defaultTreatment(), TreatmentLabels.KILLED, parsedSplit.changeNumber(), configForTreatment(parsedSplit, parsedSplit.defaultTreatment())); + return new EvaluationResult(parsedSplit.defaultTreatment(), TreatmentLabels.KILLED, parsedSplit.changeNumber(), configForTreatment(parsedSplit, parsedSplit.defaultTreatment()), parsedSplit.trackImpression()); } /* @@ -75,7 +75,7 @@ private EvaluationResult getTreatment(String matchingKey, String bucketingKey, P if (bucket > parsedSplit.trafficAllocation()) { // out of split - return new EvaluationResult(parsedSplit.defaultTreatment(), TreatmentLabels.NOT_IN_SPLIT, parsedSplit.changeNumber(), configForTreatment(parsedSplit, parsedSplit.defaultTreatment())); + return new EvaluationResult(parsedSplit.defaultTreatment(), TreatmentLabels.NOT_IN_SPLIT, parsedSplit.changeNumber(), configForTreatment(parsedSplit, parsedSplit.defaultTreatment()), parsedSplit.trackImpression()); } } @@ -84,11 +84,11 @@ private EvaluationResult getTreatment(String matchingKey, String bucketingKey, P if (parsedCondition.matcher().match(matchingKey, bucketingKey, attributes, this)) { String treatment = Splitter.getTreatment(bk, parsedSplit.seed(), parsedCondition.partitions(), parsedSplit.algo()); - return new EvaluationResult(treatment, parsedCondition.label(), parsedSplit.changeNumber(), configForTreatment(parsedSplit, treatment)); + return new EvaluationResult(treatment, parsedCondition.label(), parsedSplit.changeNumber(), configForTreatment(parsedSplit, treatment), parsedSplit.trackImpression()); } } - return new EvaluationResult(parsedSplit.defaultTreatment(), TreatmentLabels.DEFAULT_RULE, parsedSplit.changeNumber(), configForTreatment(parsedSplit, parsedSplit.defaultTreatment())); + return new EvaluationResult(parsedSplit.defaultTreatment(), TreatmentLabels.DEFAULT_RULE, parsedSplit.changeNumber(), configForTreatment(parsedSplit, parsedSplit.defaultTreatment()), parsedSplit.trackImpression()); } catch (Exception e) { throw new ChangeNumberExceptionWrapper(e, parsedSplit.changeNumber()); } diff --git a/src/main/java/io/split/android/client/impressions/DecoratedImpression.java b/src/main/java/io/split/android/client/impressions/DecoratedImpression.java new file mode 100644 index 000000000..d1114a28a --- /dev/null +++ b/src/main/java/io/split/android/client/impressions/DecoratedImpression.java @@ -0,0 +1,17 @@ +package io.split.android.client.impressions; + +import java.util.Map; + +public class DecoratedImpression extends Impression { + + private final boolean mTrackImpressions; + + public DecoratedImpression(String key, String bucketingKey, String split, String treatment, long time, String appliedRule, Long changeNumber, Map atributes, boolean trackImpressions) { + super(key, bucketingKey, split, treatment, time, appliedRule, changeNumber, atributes); + mTrackImpressions = trackImpressions; + } + + public boolean getTrackImpressions() { + return mTrackImpressions; + } +} diff --git a/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java b/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java index 5eb2a104e..35cdc8e57 100644 --- a/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java +++ b/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java @@ -8,6 +8,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import io.split.android.client.impressions.DecoratedImpression; import io.split.android.client.impressions.Impression; import io.split.android.client.service.impressions.strategy.PeriodicTracker; import io.split.android.client.service.impressions.strategy.ProcessStrategy; @@ -39,7 +40,7 @@ public void pushImpression(Impression impression) { return; } - if (track(impression)) { + if (shouldTrack(impression)) { mProcessStrategy.apply(impression); } else { mNoneStrategy.apply(impression); @@ -72,7 +73,12 @@ public void stopPeriodicRecording() { } } - private static boolean track(Impression impression) { - return true; // TODO: Placeholder method + private static boolean shouldTrack(Impression impression) { + if (impression instanceof DecoratedImpression) { + return ((DecoratedImpression) impression).getTrackImpressions(); + } else { + // default behaviour; will never get here. + return true; + } } } diff --git a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java index a88e81747..16c4f2708 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -21,7 +21,7 @@ import io.split.android.client.attributes.AttributesMerger; import io.split.android.client.events.ListenableEventsManager; import io.split.android.client.events.SplitEvent; -import io.split.android.client.impressions.Impression; +import io.split.android.client.impressions.DecoratedImpression; import io.split.android.client.impressions.ImpressionListener; import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.model.Method; @@ -294,7 +294,8 @@ private TreatmentResult getTreatmentWithConfigWithoutMetrics(String split, Map attributes) { + private void logImpression(String matchingKey, String bucketingKey, String splitName, String result, String label, Long changeNumber, Map attributes, boolean trackImpression) { try { - mImpressionListener.log(new Impression(matchingKey, bucketingKey, splitName, result, System.currentTimeMillis(), label, changeNumber, attributes)); + mImpressionListener.log(new DecoratedImpression(matchingKey, bucketingKey, splitName, result, System.currentTimeMillis(), label, changeNumber, attributes, trackImpression)); } catch (Throwable t) { Logger.e("An error occurred logging impression: " + t.getLocalizedMessage()); } @@ -339,7 +341,7 @@ private EvaluationResult evaluateIfReady(String featureFlagName, mValidationLogger.w("the SDK is not ready, results may be incorrect for feature flag " + featureFlagName + ". Make sure to wait for SDK readiness before using this method", validationTag); mTelemetryStorageProducer.recordNonReadyUsage(); - return new EvaluationResult(Treatments.CONTROL, TreatmentLabels.NOT_READY, null, null); + return new EvaluationResult(Treatments.CONTROL, TreatmentLabels.NOT_READY, null, null, true); } return mEvaluator.getTreatment(mMatchingKey, mBucketingKey, featureFlagName, attributes); } diff --git a/src/test/java/io/split/android/client/TreatmentManagerTest.java b/src/test/java/io/split/android/client/TreatmentManagerTest.java index a13f3e287..c1280161c 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTest.java @@ -2,6 +2,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -27,6 +28,7 @@ import io.split.android.client.dtos.Split; import io.split.android.client.events.ListenableEventsManager; import io.split.android.client.events.SplitEvent; +import io.split.android.client.impressions.DecoratedImpression; import io.split.android.client.impressions.ImpressionListener; import io.split.android.client.storage.mysegments.MySegmentsStorage; import io.split.android.client.storage.mysegments.MySegmentsStorageContainer; @@ -316,6 +318,20 @@ public void evaluationWhenNotReadyLogsCorrectMessage() { verify(validationMessageLogger).w(eq("the SDK is not ready, results may be incorrect for feature flag test_split. Make sure to wait for SDK readiness before using this method"), any()); } + @Test + public void trackValueFromEvaluationResultGetsPassedInToImpression() { + Evaluator evaluatorMock = mock(Evaluator.class); + when(evaluatorMock.getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_split"), anyMap())) + .thenReturn(new EvaluationResult("test", "test")); + TreatmentManagerImpl tManager = initializeTreatmentManager(evaluatorMock); + + tManager.getTreatment("test_split", null, false); + + verify(impressionListener).log(argThat(argument -> { + return ((DecoratedImpression) argument).getTrackImpressions(); + })); + } + private void assertControl(List splitList, String treatment, Map treatmentList, SplitResult splitResult, Map splitResultList) { Assert.assertNotNull(treatment); Assert.assertEquals(Treatments.CONTROL, treatment); diff --git a/src/test/java/io/split/android/client/service/impressions/StrategyImpressionManagerTest.kt b/src/test/java/io/split/android/client/service/impressions/StrategyImpressionManagerTest.kt index 563802099..d69c4e78c 100644 --- a/src/test/java/io/split/android/client/service/impressions/StrategyImpressionManagerTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/StrategyImpressionManagerTest.kt @@ -1,6 +1,6 @@ package io.split.android.client.service.impressions -import io.split.android.client.impressions.Impression +import io.split.android.client.impressions.DecoratedImpression import io.split.android.client.service.impressions.strategy.PeriodicTracker import io.split.android.client.service.impressions.strategy.ProcessStrategy import org.junit.Before @@ -8,6 +8,8 @@ import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoInteractions +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations class StrategyImpressionManagerTest { @@ -37,6 +39,7 @@ class StrategyImpressionManagerTest { impressionManager.flush() verify(tracker).flush() + verify(noneTracker).flush() } @Test @@ -44,6 +47,7 @@ class StrategyImpressionManagerTest { impressionManager.startPeriodicRecording() verify(tracker).startPeriodicRecording() + verify(noneTracker).startPeriodicRecording() } @Test @@ -51,13 +55,61 @@ class StrategyImpressionManagerTest { impressionManager.stopPeriodicRecording() verify(tracker).stopPeriodicRecording() + verify(noneTracker).stopPeriodicRecording() } @Test fun `pushImpression calls apply on strategy`() { - val impression = mock(Impression::class.java) + val impression = mock(DecoratedImpression::class.java) + `when`(impression.trackImpressions).thenReturn(true) impressionManager.pushImpression(impression) verify(strategy).apply(impression) + verifyNoInteractions(noneStrategy) + } + + @Test + fun `pushImpression calls apply on noneStrategy when trackImpressions is false`() { + val impression = mock(DecoratedImpression::class.java) + `when`(impression.trackImpressions).thenReturn(false) + impressionManager.pushImpression(impression) + + verify(noneStrategy).apply(impression) + verifyNoInteractions(strategy) + } + + @Test + fun `pushImpression when it is decorated uses value from trackImpression to track`() { + val impression = mock(DecoratedImpression::class.java) + val impression2 = mock(DecoratedImpression::class.java) + `when`(impression.trackImpressions).thenReturn(false) + `when`(impression2.trackImpressions).thenReturn(true) + impressionManager.pushImpression(impression) + impressionManager.pushImpression(impression2) + + verify(strategy).apply(impression2) + verify(noneStrategy).apply(impression) + } + + @Test + fun `enableTracking set to true causes impressions to be sent to strategy`() { + impressionManager.enableTracking(true) + val impression = mock(DecoratedImpression::class.java) + `when`(impression.trackImpressions).thenReturn(true) + impressionManager.pushImpression(impression) + + verify(strategy).apply(impression) + verifyNoInteractions(noneStrategy) + } + + @Test + fun `enableTracking set to false causes impressions to not be tracked`() { + impressionManager.enableTracking(false) + val impression = mock(DecoratedImpression::class.java) + `when`(impression.trackImpressions).thenReturn(true) + impressionManager.pushImpression(impression) + + verifyNoInteractions(noneStrategy) + verifyNoInteractions(strategy) } } From ad32409a413538da2ea259f55de6affc301b9ac3 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 12 Dec 2024 13:35:24 -0300 Subject: [PATCH 33/37] WIP --- .../java/fake/SyncManagerStub.java | 4 +- .../java/fake/SynchronizerSpyImpl.java | 4 +- .../client/SplitClientFactoryImpl.java | 6 +-- .../android/client/SplitFactoryImpl.java | 12 +++--- .../impressions/DecoratedImpression.java | 13 +++--- .../DecoratedImpressionListener.java | 6 +++ .../impressions/ImpressionListener.java | 20 ++++++++-- .../impressions/ImpressionLoggingTask.java | 4 +- .../impressions/SyncImpressionListener.java | 8 +--- .../LocalhostImpressionsListener.java | 8 +++- .../localhost/LocalhostSplitClient.java | 6 +-- .../impressions/ImpressionManager.java | 4 +- .../StrategyImpressionManager.java | 18 ++------- .../service/synchronizer/SyncManager.java | 4 +- .../service/synchronizer/SyncManagerImpl.java | 3 +- .../service/synchronizer/Synchronizer.java | 4 +- .../synchronizer/SynchronizerImpl.java | 3 +- .../shared/SplitClientContainerImpl.java | 2 +- .../TreatmentManagerFactoryImpl.java | 4 +- .../validators/TreatmentManagerImpl.java | 10 +++-- .../TreatmentManagerExceptionsTest.java | 2 +- .../client/TreatmentManagerTelemetryTest.java | 2 +- .../android/client/TreatmentManagerTest.java | 15 +++---- .../TreatmentManagerWithFlagSetsTest.java | 2 +- .../ImpressionLoggingTaskTest.java | 12 +++--- .../SyncImpressionListenerTest.java | 8 ++-- .../client/service/SynchronizerTest.java | 40 ++++++++++--------- .../StrategyImpressionManagerTest.kt | 37 ++++++++--------- .../client/utils/SplitClientImplFactory.java | 3 +- .../android/fake/ImpressionListenerMock.java | 8 +++- 30 files changed, 149 insertions(+), 123 deletions(-) create mode 100644 src/main/java/io/split/android/client/impressions/DecoratedImpressionListener.java diff --git a/src/androidTest/java/fake/SyncManagerStub.java b/src/androidTest/java/fake/SyncManagerStub.java index de24ad9cb..179a8461f 100644 --- a/src/androidTest/java/fake/SyncManagerStub.java +++ b/src/androidTest/java/fake/SyncManagerStub.java @@ -1,7 +1,7 @@ package fake; import io.split.android.client.dtos.Event; -import io.split.android.client.impressions.Impression; +import io.split.android.client.impressions.DecoratedImpression; import io.split.android.client.service.synchronizer.SyncManager; import io.split.android.client.shared.UserConsent; @@ -42,6 +42,6 @@ public void pushEvent(Event event) { } @Override - public void pushImpression(Impression impression) { + public void pushImpression(DecoratedImpression impression) { } } diff --git a/src/androidTest/java/fake/SynchronizerSpyImpl.java b/src/androidTest/java/fake/SynchronizerSpyImpl.java index 2203f3913..81a1c8fdd 100644 --- a/src/androidTest/java/fake/SynchronizerSpyImpl.java +++ b/src/androidTest/java/fake/SynchronizerSpyImpl.java @@ -5,7 +5,7 @@ import io.split.android.client.api.Key; import io.split.android.client.dtos.Event; -import io.split.android.client.impressions.Impression; +import io.split.android.client.impressions.DecoratedImpression; import io.split.android.client.service.synchronizer.Synchronizer; import io.split.android.client.service.synchronizer.SynchronizerSpy; import io.split.android.client.service.synchronizer.attributes.AttributesSynchronizer; @@ -88,7 +88,7 @@ public void pushEvent(Event event) { } @Override - public void pushImpression(Impression impression) { + public void pushImpression(DecoratedImpression impression) { mSynchronizer.pushImpression(impression); } diff --git a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java index 0956c1788..544aab48b 100644 --- a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java @@ -17,9 +17,9 @@ import io.split.android.client.service.mysegments.MySegmentsTaskFactory; import io.split.android.client.service.synchronizer.SyncManager; import io.split.android.client.shared.SplitClientContainer; -import io.split.android.client.storage.common.SplitStorageContainer; import io.split.android.client.storage.attributes.AttributesStorage; import io.split.android.client.storage.attributes.PersistentAttributesStorage; +import io.split.android.client.storage.common.SplitStorageContainer; import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.TelemetrySynchronizer; import io.split.android.client.telemetry.storage.TelemetryInitProducer; @@ -43,7 +43,7 @@ public class SplitClientFactoryImpl implements SplitClientFactory { private final SplitParser mSplitParser; private final AttributesManagerFactory mAttributesManagerFactory; private final TreatmentManagerFactory mTreatmentManagerFactory; - private final ImpressionListener mCustomerImpressionListener; + private final ImpressionListener.FederatedImpressionListener mCustomerImpressionListener; private final SplitValidatorImpl mSplitValidator; private final EventsTracker mEventsTracker; @@ -57,7 +57,7 @@ public SplitClientFactoryImpl(@NonNull SplitFactory splitFactory, @NonNull ValidationMessageLogger validationLogger, @NonNull KeyValidator keyValidator, @NonNull EventsTracker eventsTracker, - @NonNull ImpressionListener customerImpressionListener, + @NonNull ImpressionListener.FederatedImpressionListener customerImpressionListener, @Nullable FlagSetsFilter flagSetsFilter) { mSplitFactory = checkNotNull(splitFactory); mClientContainer = checkNotNull(clientContainer); diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index 058c1139d..32ea3963c 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -20,6 +20,7 @@ import io.split.android.client.events.EventsManagerCoordinator; import io.split.android.client.factory.FactoryMonitor; import io.split.android.client.factory.FactoryMonitorImpl; +import io.split.android.client.impressions.DecoratedImpressionListener; import io.split.android.client.impressions.ImpressionListener; import io.split.android.client.impressions.SyncImpressionListener; import io.split.android.client.lifecycle.SplitLifecycleManager; @@ -243,17 +244,16 @@ private SplitFactoryImpl(@NonNull String apiToken, @NonNull Key key, @NonNull Sp } ExecutorService impressionsLoggingTaskExecutor = factoryHelper.getImpressionsLoggingTaskExecutor(); - final ImpressionListener splitImpressionListener + final DecoratedImpressionListener splitImpressionListener = new SyncImpressionListener(mSyncManager, impressionsLoggingTaskExecutor); - final ImpressionListener customerImpressionListener; + final ImpressionListener.FederatedImpressionListener customerImpressionListener; + List impressionListeners = new ArrayList<>(); if (config.impressionListener() != null) { - List impressionListeners = new ArrayList<>(); - impressionListeners.add(splitImpressionListener); impressionListeners.add(config.impressionListener()); - customerImpressionListener = new ImpressionListener.FederatedImpressionListener(impressionListeners); + customerImpressionListener = new ImpressionListener.FederatedImpressionListener(splitImpressionListener, impressionListeners); } else { - customerImpressionListener = splitImpressionListener; + customerImpressionListener = new ImpressionListener.FederatedImpressionListener(splitImpressionListener, impressionListeners); } EventsTracker eventsTracker = buildEventsTracker(); mUserConsentManager = new UserConsentManagerImpl(config, diff --git a/src/main/java/io/split/android/client/impressions/DecoratedImpression.java b/src/main/java/io/split/android/client/impressions/DecoratedImpression.java index d1114a28a..ecfb0e03f 100644 --- a/src/main/java/io/split/android/client/impressions/DecoratedImpression.java +++ b/src/main/java/io/split/android/client/impressions/DecoratedImpression.java @@ -1,16 +1,19 @@ package io.split.android.client.impressions; -import java.util.Map; +public class DecoratedImpression { -public class DecoratedImpression extends Impression { - + private final Impression mImpression; private final boolean mTrackImpressions; - public DecoratedImpression(String key, String bucketingKey, String split, String treatment, long time, String appliedRule, Long changeNumber, Map atributes, boolean trackImpressions) { - super(key, bucketingKey, split, treatment, time, appliedRule, changeNumber, atributes); + public DecoratedImpression(Impression impression, boolean trackImpressions) { + mImpression = impression; mTrackImpressions = trackImpressions; } + public Impression getImpression() { + return mImpression; + } + public boolean getTrackImpressions() { return mTrackImpressions; } diff --git a/src/main/java/io/split/android/client/impressions/DecoratedImpressionListener.java b/src/main/java/io/split/android/client/impressions/DecoratedImpressionListener.java new file mode 100644 index 000000000..e063128c0 --- /dev/null +++ b/src/main/java/io/split/android/client/impressions/DecoratedImpressionListener.java @@ -0,0 +1,6 @@ +package io.split.android.client.impressions; + +public interface DecoratedImpressionListener { + + void log(DecoratedImpression impression); +} diff --git a/src/main/java/io/split/android/client/impressions/ImpressionListener.java b/src/main/java/io/split/android/client/impressions/ImpressionListener.java index 7db48a401..7f87e8d95 100644 --- a/src/main/java/io/split/android/client/impressions/ImpressionListener.java +++ b/src/main/java/io/split/android/client/impressions/ImpressionListener.java @@ -19,22 +19,29 @@ public interface ImpressionListener { */ void close(); - final class NoopImpressionListener implements ImpressionListener { + final class NoopImpressionListener implements ImpressionListener, DecoratedImpressionListener { @Override public void log(Impression impression) { // noop } + @Override + public void log(DecoratedImpression impression) { + + } + @Override public void close() { // noop } } - final class FederatedImpressionListener implements ImpressionListener { - private List _delegates; + final class FederatedImpressionListener implements ImpressionListener, DecoratedImpressionListener { + private final DecoratedImpressionListener mDecoratedImpressionListener; + private final List _delegates; - public FederatedImpressionListener(List delegates) { + public FederatedImpressionListener(DecoratedImpressionListener decoratedImpressionListener, List delegates) { + mDecoratedImpressionListener = decoratedImpressionListener; _delegates = delegates; } @@ -45,6 +52,11 @@ public void log(Impression impression) { } } + @Override + public void log(DecoratedImpression impression) { + mDecoratedImpressionListener.log(impression); + } + @Override public void close() { for (ImpressionListener listener : _delegates) { diff --git a/src/main/java/io/split/android/client/impressions/ImpressionLoggingTask.java b/src/main/java/io/split/android/client/impressions/ImpressionLoggingTask.java index ff80c8530..8ca6bc3b4 100644 --- a/src/main/java/io/split/android/client/impressions/ImpressionLoggingTask.java +++ b/src/main/java/io/split/android/client/impressions/ImpressionLoggingTask.java @@ -10,10 +10,10 @@ class ImpressionLoggingTask implements Runnable { private final SyncManager mSyncManager; - private final Impression mImpression; + private final DecoratedImpression mImpression; ImpressionLoggingTask(@NonNull SyncManager syncManager, - Impression impression) { + DecoratedImpression impression) { mSyncManager = checkNotNull(syncManager); mImpression = impression; } diff --git a/src/main/java/io/split/android/client/impressions/SyncImpressionListener.java b/src/main/java/io/split/android/client/impressions/SyncImpressionListener.java index 6bdf4fa14..147e940d2 100644 --- a/src/main/java/io/split/android/client/impressions/SyncImpressionListener.java +++ b/src/main/java/io/split/android/client/impressions/SyncImpressionListener.java @@ -9,7 +9,7 @@ import io.split.android.client.service.synchronizer.SyncManager; import io.split.android.client.utils.logger.Logger; -public class SyncImpressionListener implements ImpressionListener { +public class SyncImpressionListener implements DecoratedImpressionListener { private final SyncManager mSyncManager; private final ExecutorService mExecutorService; @@ -21,15 +21,11 @@ public SyncImpressionListener(@NonNull SyncManager syncManager, } @Override - public void log(Impression impression) { + public void log(DecoratedImpression impression) { try { mExecutorService.submit(new ImpressionLoggingTask(mSyncManager, impression)); } catch (Exception ex) { Logger.w("Error submitting impression logging task: " + ex.getLocalizedMessage()); } } - - @Override - public void close() { - } } diff --git a/src/main/java/io/split/android/client/localhost/LocalhostImpressionsListener.java b/src/main/java/io/split/android/client/localhost/LocalhostImpressionsListener.java index af7e053ca..846dd6a83 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostImpressionsListener.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostImpressionsListener.java @@ -1,13 +1,19 @@ package io.split.android.client.localhost; +import io.split.android.client.impressions.DecoratedImpression; +import io.split.android.client.impressions.DecoratedImpressionListener; import io.split.android.client.impressions.Impression; import io.split.android.client.impressions.ImpressionListener; -public class LocalhostImpressionsListener implements ImpressionListener { +public class LocalhostImpressionsListener implements ImpressionListener, DecoratedImpressionListener { @Override public void log(Impression impression) { } + @Override + public void log(DecoratedImpression impression) { + } + @Override public void close() { } diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java index 5c3d31e64..77c394308 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java @@ -263,11 +263,11 @@ public boolean track(String eventType, double value, Map propert return false; } - private ImpressionListener getImpressionsListener(SplitClientConfig config) { + private ImpressionListener.FederatedImpressionListener getImpressionsListener(SplitClientConfig config) { if (config.impressionListener() != null) { - return config.impressionListener(); + return new ImpressionListener.FederatedImpressionListener(new ImpressionListener.NoopImpressionListener(), Collections.singletonList(config.impressionListener())); } else { - return new LocalhostImpressionsListener(); + return new ImpressionListener.FederatedImpressionListener(new ImpressionListener.NoopImpressionListener(), Collections.emptyList()); } } diff --git a/src/main/java/io/split/android/client/service/impressions/ImpressionManager.java b/src/main/java/io/split/android/client/service/impressions/ImpressionManager.java index ec990e2a6..511536d06 100644 --- a/src/main/java/io/split/android/client/service/impressions/ImpressionManager.java +++ b/src/main/java/io/split/android/client/service/impressions/ImpressionManager.java @@ -1,10 +1,10 @@ package io.split.android.client.service.impressions; -import io.split.android.client.impressions.Impression; +import io.split.android.client.impressions.DecoratedImpression; public interface ImpressionManager { void enableTracking(boolean enable); - void pushImpression(Impression impression); + void pushImpression(DecoratedImpression impression); } diff --git a/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java b/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java index 35cdc8e57..d291cc875 100644 --- a/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java +++ b/src/main/java/io/split/android/client/service/impressions/StrategyImpressionManager.java @@ -9,7 +9,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import io.split.android.client.impressions.DecoratedImpression; -import io.split.android.client.impressions.Impression; import io.split.android.client.service.impressions.strategy.PeriodicTracker; import io.split.android.client.service.impressions.strategy.ProcessStrategy; import io.split.android.client.utils.logger.Logger; @@ -34,16 +33,16 @@ public StrategyImpressionManager(Pair noneComp } @Override - public void pushImpression(Impression impression) { + public void pushImpression(DecoratedImpression impression) { if (!isTrackingEnabled.get()) { Logger.v("Impression not tracked because tracking is disabled"); return; } - if (shouldTrack(impression)) { - mProcessStrategy.apply(impression); + if (impression.getTrackImpressions()) { + mProcessStrategy.apply(impression.getImpression()); } else { - mNoneStrategy.apply(impression); + mNoneStrategy.apply(impression.getImpression()); } } @@ -72,13 +71,4 @@ public void stopPeriodicRecording() { tracker.stopPeriodicRecording(); } } - - private static boolean shouldTrack(Impression impression) { - if (impression instanceof DecoratedImpression) { - return ((DecoratedImpression) impression).getTrackImpressions(); - } else { - // default behaviour; will never get here. - return true; - } - } } diff --git a/src/main/java/io/split/android/client/service/synchronizer/SyncManager.java b/src/main/java/io/split/android/client/service/synchronizer/SyncManager.java index 200862b3f..bb5904220 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/SyncManager.java +++ b/src/main/java/io/split/android/client/service/synchronizer/SyncManager.java @@ -1,7 +1,7 @@ package io.split.android.client.service.synchronizer; import io.split.android.client.dtos.Event; -import io.split.android.client.impressions.Impression; +import io.split.android.client.impressions.DecoratedImpression; import io.split.android.client.lifecycle.SplitLifecycleAware; import io.split.android.client.shared.UserConsent; @@ -12,7 +12,7 @@ public interface SyncManager extends SplitLifecycleAware { void pushEvent(Event event); - void pushImpression(Impression impression); + void pushImpression(DecoratedImpression impression); void stop(); diff --git a/src/main/java/io/split/android/client/service/synchronizer/SyncManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/SyncManagerImpl.java index 1a96ebff2..e96693539 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/SyncManagerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/SyncManagerImpl.java @@ -9,6 +9,7 @@ import io.split.android.client.SplitClientConfig; import io.split.android.client.dtos.Event; +import io.split.android.client.impressions.DecoratedImpression; import io.split.android.client.impressions.Impression; import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; @@ -163,7 +164,7 @@ public void pushEvent(Event event) { } @Override - public void pushImpression(Impression impression) { + public void pushImpression(DecoratedImpression impression) { mSynchronizer.pushImpression(impression); } diff --git a/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java b/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java index 27adf8d38..b16331b83 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java +++ b/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java @@ -1,7 +1,7 @@ package io.split.android.client.service.synchronizer; import io.split.android.client.dtos.Event; -import io.split.android.client.impressions.Impression; +import io.split.android.client.impressions.DecoratedImpression; import io.split.android.client.lifecycle.SplitLifecycleAware; public interface Synchronizer extends SplitLifecycleAware { @@ -28,7 +28,7 @@ public interface Synchronizer extends SplitLifecycleAware { void pushEvent(Event event); - void pushImpression(Impression impression); + void pushImpression(DecoratedImpression impression); void flush(); diff --git a/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java index 0cdfe304e..930f5bd9b 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java @@ -13,6 +13,7 @@ import io.split.android.client.api.Key; import io.split.android.client.dtos.Event; import io.split.android.client.events.ISplitEventsManager; +import io.split.android.client.impressions.DecoratedImpression; import io.split.android.client.impressions.Impression; import io.split.android.client.service.ServiceConstants; import io.split.android.client.service.executor.SplitTaskExecutionInfo; @@ -255,7 +256,7 @@ public void pushEvent(Event event) { } @Override - public void pushImpression(Impression impression) { + public void pushImpression(DecoratedImpression impression) { mImpressionManager.pushImpression(impression); } diff --git a/src/main/java/io/split/android/client/shared/SplitClientContainerImpl.java b/src/main/java/io/split/android/client/shared/SplitClientContainerImpl.java index 9d50f645d..a49788bf5 100644 --- a/src/main/java/io/split/android/client/shared/SplitClientContainerImpl.java +++ b/src/main/java/io/split/android/client/shared/SplitClientContainerImpl.java @@ -67,7 +67,7 @@ public SplitClientContainerImpl(@NonNull String defaultMatchingKey, @NonNull SplitApiFacade splitApiFacade, @NonNull ValidationMessageLogger validationLogger, @NonNull KeyValidator keyValidator, - @NonNull ImpressionListener customerImpressionListener, + @NonNull ImpressionListener.FederatedImpressionListener customerImpressionListener, @Nullable PushNotificationManager pushNotificationManager, @NonNull ClientComponentsRegister clientComponentsRegister, @NonNull MySegmentsWorkManagerWrapper workManagerWrapper, diff --git a/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java b/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java index 770461b09..3fce796d9 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java @@ -21,7 +21,7 @@ public class TreatmentManagerFactoryImpl implements TreatmentManagerFactory { private final KeyValidator mKeyValidator; private final SplitValidator mSplitValidator; - private final ImpressionListener mCustomerImpressionListener; + private final ImpressionListener.FederatedImpressionListener mCustomerImpressionListener; private final boolean mLabelsEnabled; private final AttributesMerger mAttributesMerger; private final TelemetryStorageProducer mTelemetryStorageProducer; @@ -33,7 +33,7 @@ public class TreatmentManagerFactoryImpl implements TreatmentManagerFactory { public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, @NonNull SplitValidator splitValidator, - @NonNull ImpressionListener customerImpressionListener, + @NonNull ImpressionListener.FederatedImpressionListener customerImpressionListener, boolean labelsEnabled, @NonNull AttributesMerger attributesMerger, @NonNull TelemetryStorageProducer telemetryStorageProducer, diff --git a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java index 16c4f2708..2adc8ace8 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -22,6 +22,7 @@ import io.split.android.client.events.ListenableEventsManager; import io.split.android.client.events.SplitEvent; import io.split.android.client.impressions.DecoratedImpression; +import io.split.android.client.impressions.Impression; import io.split.android.client.impressions.ImpressionListener; import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.model.Method; @@ -34,7 +35,7 @@ public class TreatmentManagerImpl implements TreatmentManager { private final Evaluator mEvaluator; private final KeyValidator mKeyValidator; private final SplitValidator mSplitValidator; - private final ImpressionListener mImpressionListener; + private final ImpressionListener.FederatedImpressionListener mImpressionListener; private final String mMatchingKey; private final String mBucketingKey; private final boolean mLabelsEnabled; @@ -54,7 +55,7 @@ public TreatmentManagerImpl(String matchingKey, Evaluator evaluator, KeyValidator keyValidator, SplitValidator splitValidator, - ImpressionListener impressionListener, + ImpressionListener.FederatedImpressionListener impressionListener, boolean labelsEnabled, ListenableEventsManager eventsManager, @NonNull AttributesManager attributesManager, @@ -318,7 +319,10 @@ private TreatmentResult getTreatmentWithConfigWithoutMetrics(String split, Map attributes, boolean trackImpression) { try { - mImpressionListener.log(new DecoratedImpression(matchingKey, bucketingKey, splitName, result, System.currentTimeMillis(), label, changeNumber, attributes, trackImpression)); + Impression impression = new Impression(matchingKey, bucketingKey, splitName, result, System.currentTimeMillis(), label, changeNumber, attributes); + DecoratedImpression decoratedImpression = new DecoratedImpression(impression, trackImpression); + mImpressionListener.log(decoratedImpression); + mImpressionListener.log(impression); } catch (Throwable t) { Logger.e("An error occurred logging impression: " + t.getLocalizedMessage()); } diff --git a/src/test/java/io/split/android/client/TreatmentManagerExceptionsTest.java b/src/test/java/io/split/android/client/TreatmentManagerExceptionsTest.java index 74593a436..e130f233c 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerExceptionsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerExceptionsTest.java @@ -45,7 +45,7 @@ public class TreatmentManagerExceptionsTest { @Mock SplitValidator splitValidator; @Mock - ImpressionListener impressionListener; + ImpressionListener.FederatedImpressionListener impressionListener; @Mock ListenableEventsManager eventsManager; @Mock diff --git a/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java b/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java index dfe84c02e..d4b917084 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java @@ -41,7 +41,7 @@ public class TreatmentManagerTelemetryTest { @Mock SplitValidator splitValidator; @Mock - ImpressionListener impressionListener; + ImpressionListener.FederatedImpressionListener impressionListener; @Mock ListenableEventsManager eventsManager; @Mock diff --git a/src/test/java/io/split/android/client/TreatmentManagerTest.java b/src/test/java/io/split/android/client/TreatmentManagerTest.java index c1280161c..1fb4d5307 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTest.java @@ -45,7 +45,6 @@ import io.split.android.client.validators.ValidationMessageLogger; import io.split.android.client.validators.ValidationMessageLoggerImpl; import io.split.android.engine.experiments.SplitParser; -import io.split.android.fake.ImpressionListenerMock; import io.split.android.fake.SplitEventsManagerStub; import io.split.android.grammar.Treatments; import io.split.android.helpers.FileHelper; @@ -54,7 +53,7 @@ public class TreatmentManagerTest { Evaluator evaluator; - ImpressionListener impressionListener; + ImpressionListener.FederatedImpressionListener impressionListener; ListenableEventsManager eventsManagerStub; AttributesManager attributesManager = mock(AttributesManager.class); TelemetryStorageProducer telemetryStorageProducer = mock(TelemetryStorageProducer.class); @@ -91,7 +90,7 @@ public void loadSplitsFromFile() { evaluator = new EvaluatorImpl(splitsStorage, splitParser); } - impressionListener = mock(ImpressionListener.class); + impressionListener = mock(ImpressionListener.FederatedImpressionListener.class); eventsManagerStub = new SplitEventsManagerStub(); } @@ -322,14 +321,12 @@ public void evaluationWhenNotReadyLogsCorrectMessage() { public void trackValueFromEvaluationResultGetsPassedInToImpression() { Evaluator evaluatorMock = mock(Evaluator.class); when(evaluatorMock.getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_split"), anyMap())) - .thenReturn(new EvaluationResult("test", "test")); + .thenReturn(new EvaluationResult("test", "test", true)); TreatmentManagerImpl tManager = initializeTreatmentManager(evaluatorMock); tManager.getTreatment("test_split", null, false); - verify(impressionListener).log(argThat(argument -> { - return ((DecoratedImpression) argument).getTrackImpressions(); - })); + verify(impressionListener).log(argThat(DecoratedImpression::getTrackImpressions)); } private void assertControl(List splitList, String treatment, Map treatmentList, SplitResult splitResult, Map splitResultList) { @@ -366,7 +363,7 @@ private TreatmentManager createTreatmentManager(String matchingKey, String bucke return new TreatmentManagerImpl( matchingKey, bucketingKey, evaluator, new KeyValidatorImpl(), splitValidator, - new ImpressionListenerMock(), config.labelsEnabled(), eventsManager, + mock(ImpressionListener.FederatedImpressionListener.class), config.labelsEnabled(), eventsManager, mock(AttributesManager.class), mock(AttributesMerger.class), mock(TelemetryStorageProducer.class), mFlagSetsFilter, mSplitsStorage, validationLogger, new FlagSetsValidatorImpl()); } @@ -390,7 +387,7 @@ private TreatmentManagerImpl initializeTreatmentManager(Evaluator evaluator) { evaluator, mock(KeyValidator.class), mock(SplitValidator.class), - mock(ImpressionListener.class), + impressionListener, SplitClientConfig.builder().build().labelsEnabled(), eventsManager, attributesManager, diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java index adb7d46fd..bdcbec7db 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -45,7 +45,7 @@ public class TreatmentManagerWithFlagSetsTest { @Mock private SplitValidator mSplitValidator; @Mock - private ImpressionListener mImpressionListener; + private ImpressionListener.FederatedImpressionListener mImpressionListener; @Mock private ListenableEventsManager mEventsManager; @Mock diff --git a/src/test/java/io/split/android/client/impressions/ImpressionLoggingTaskTest.java b/src/test/java/io/split/android/client/impressions/ImpressionLoggingTaskTest.java index 135d21544..713f38c13 100644 --- a/src/test/java/io/split/android/client/impressions/ImpressionLoggingTaskTest.java +++ b/src/test/java/io/split/android/client/impressions/ImpressionLoggingTaskTest.java @@ -24,7 +24,7 @@ public void setUp() { @Test public void executeLogsImpressionInListener() { - Impression impression = createImpression(); + DecoratedImpression impression = createImpression(); mImpressionsLoggingTask = new ImpressionLoggingTask(mSyncManager, impression); mImpressionsLoggingTask.run(); @@ -34,7 +34,7 @@ public void executeLogsImpressionInListener() { @Test public void successfulExecutionReturnsSuccessInfo() { - Impression impression = createImpression(); + DecoratedImpression impression = createImpression(); mImpressionsLoggingTask = new ImpressionLoggingTask(mSyncManager, impression); mImpressionsLoggingTask.run(); @@ -42,14 +42,14 @@ public void successfulExecutionReturnsSuccessInfo() { @Test public void unsuccessfulExecutionDoesNotCrash() { - doThrow(new RuntimeException("test")).when(mSyncManager).pushImpression(any(Impression.class)); - Impression impression = createImpression(); + doThrow(new RuntimeException("test")).when(mSyncManager).pushImpression(any(DecoratedImpression.class)); + DecoratedImpression impression = createImpression(); mImpressionsLoggingTask = new ImpressionLoggingTask(mSyncManager, impression); mImpressionsLoggingTask.run(); } - private static Impression createImpression() { - return new Impression("key", "feature", "treatment", "on", 1402040204L, "label", 123123L, new HashMap<>()); + private static DecoratedImpression createImpression() { + return new DecoratedImpression(new Impression("key", "feature", "treatment", "on", 1402040204L, "label", 123123L, new HashMap<>()), true); } } diff --git a/src/test/java/io/split/android/client/impressions/SyncImpressionListenerTest.java b/src/test/java/io/split/android/client/impressions/SyncImpressionListenerTest.java index 3c6632098..4131e445f 100644 --- a/src/test/java/io/split/android/client/impressions/SyncImpressionListenerTest.java +++ b/src/test/java/io/split/android/client/impressions/SyncImpressionListenerTest.java @@ -27,7 +27,7 @@ public void setUp() { @Test public void logSubmitsImpressionLoggingTaskInExecutor() { SyncImpressionListener syncImpressionListener = new SyncImpressionListener(mSyncManager, mExecutorService); - Impression impression = createImpression(); + DecoratedImpression impression = createImpression(); syncImpressionListener.log(impression); @@ -37,13 +37,13 @@ public void logSubmitsImpressionLoggingTaskInExecutor() { @Test public void errorWhileSubmittingTaskIsHandled() { SyncImpressionListener syncImpressionListener = new SyncImpressionListener(mSyncManager, mExecutorService); - Impression impression = createImpression(); + DecoratedImpression impression = createImpression(); doThrow(new RuntimeException("test")).when(mExecutorService).submit(any(ImpressionLoggingTask.class), any()); syncImpressionListener.log(impression); } - private static Impression createImpression() { - return new Impression("key", "feature", "treatment", "on", 1402040204L, "label", 123123L, new HashMap<>()); + private static DecoratedImpression createImpression() { + return new DecoratedImpression(new Impression("key", "feature", "treatment", "on", 1402040204L, "label", 123123L, new HashMap<>()), true); } } diff --git a/src/test/java/io/split/android/client/service/SynchronizerTest.java b/src/test/java/io/split/android/client/service/SynchronizerTest.java index ebe456370..79347551f 100644 --- a/src/test/java/io/split/android/client/service/SynchronizerTest.java +++ b/src/test/java/io/split/android/client/service/SynchronizerTest.java @@ -44,6 +44,7 @@ import io.split.android.client.dtos.KeyImpression; import io.split.android.client.dtos.SplitChange; import io.split.android.client.events.SplitEventsManager; +import io.split.android.client.impressions.DecoratedImpression; import io.split.android.client.impressions.Impression; import io.split.android.client.service.events.EventsRecorderTask; import io.split.android.client.service.executor.SplitTask; @@ -433,8 +434,8 @@ public void pushImpression() throws InterruptedException { .impressionsQueueSize(3) .build(); setup(config); - Impression impression = createImpression(); - ArgumentCaptor impressionCaptor = ArgumentCaptor.forClass(Impression.class); + DecoratedImpression impression = createImpression(); + ArgumentCaptor impressionCaptor = ArgumentCaptor.forClass(DecoratedImpression.class); mSynchronizer.startPeriodicRecording(); mSynchronizer.pushImpression(impression); Thread.sleep(200); @@ -442,13 +443,14 @@ public void pushImpression() throws InterruptedException { any(ImpressionsRecorderTask.class), any(SplitTaskExecutionListener.class)); verify(mImpressionManager).pushImpression(impressionCaptor.capture()); - Assert.assertEquals("key", impressionCaptor.getValue().key()); - Assert.assertEquals("bkey", impressionCaptor.getValue().bucketingKey()); - Assert.assertEquals("split", impressionCaptor.getValue().split()); - Assert.assertEquals("on", impressionCaptor.getValue().treatment()); - Assert.assertEquals(100L, impressionCaptor.getValue().time()); - Assert.assertEquals("default rule", impressionCaptor.getValue().appliedRule()); - Assert.assertEquals(999, impressionCaptor.getValue().changeNumber().longValue()); + Impression capturedImpression = impressionCaptor.getValue().getImpression(); + Assert.assertEquals("key", capturedImpression.key()); + Assert.assertEquals("bkey", capturedImpression.bucketingKey()); + Assert.assertEquals("split", capturedImpression.split()); + Assert.assertEquals("on", capturedImpression.treatment()); + Assert.assertEquals(100L, capturedImpression.time()); + Assert.assertEquals("default rule", capturedImpression.appliedRule()); + Assert.assertEquals(999, capturedImpression.changeNumber().longValue()); } @Test @@ -465,7 +467,7 @@ public void pushImpressionReachQueueSizeImpDebug() throws InterruptedException { mSynchronizer.pushImpression(createImpression()); } Thread.sleep(200); - verify(mImpressionManager, times(8)).pushImpression(any(Impression.class)); + verify(mImpressionManager, times(8)).pushImpression(any(DecoratedImpression.class)); } @Test @@ -482,7 +484,7 @@ public void pushImpressionReachQueueSizeImpOptimized() throws InterruptedExcepti mSynchronizer.pushImpression(createUniqueImpression()); } Thread.sleep(200); - verify(mImpressionManager, times(8)).pushImpression(any(Impression.class)); + verify(mImpressionManager, times(8)).pushImpression(any(DecoratedImpression.class)); } @Test @@ -500,7 +502,7 @@ public void pushImpressionBytesLimitImpDebug() throws InterruptedException { mSynchronizer.pushImpression(createImpression()); } Thread.sleep(200); - verify(mImpressionManager, times(10)).pushImpression(any(Impression.class)); + verify(mImpressionManager, times(10)).pushImpression(any(DecoratedImpression.class)); } @Test @@ -518,7 +520,7 @@ public void pushImpressionBytesLimitImpOptimized() throws InterruptedException { mSynchronizer.pushImpression(createUniqueImpression()); } Thread.sleep(200); - verify(mImpressionManager, times(10)).pushImpression(any(Impression.class)); + verify(mImpressionManager, times(10)).pushImpression(any(DecoratedImpression.class)); } @Test @@ -794,14 +796,14 @@ public void synchronizeSplitsDelegatesToFeatureFlagsSynchronizer() { public void tearDown() { } - private Impression createImpression() { - return new Impression("key", "bkey", "split", "on", - 100L, "default rule", 999L, null); + private DecoratedImpression createImpression() { + return new DecoratedImpression(new Impression("key", "bkey", "split", "on", + 100L, "default rule", 999L, null), true); } - private Impression createUniqueImpression() { - return new Impression("key", "bkey", UUID.randomUUID().toString(), "on", - 100L, "default rule", 999L, null); + private DecoratedImpression createUniqueImpression() { + return new DecoratedImpression(new Impression("key", "bkey", UUID.randomUUID().toString(), "on", + 100L, "default rule", 999L, null), true); } private KeyImpression keyImpression(Impression impression) { diff --git a/src/test/java/io/split/android/client/service/impressions/StrategyImpressionManagerTest.kt b/src/test/java/io/split/android/client/service/impressions/StrategyImpressionManagerTest.kt index d69c4e78c..d72103443 100644 --- a/src/test/java/io/split/android/client/service/impressions/StrategyImpressionManagerTest.kt +++ b/src/test/java/io/split/android/client/service/impressions/StrategyImpressionManagerTest.kt @@ -1,6 +1,7 @@ package io.split.android.client.service.impressions import io.split.android.client.impressions.DecoratedImpression +import io.split.android.client.impressions.Impression import io.split.android.client.service.impressions.strategy.PeriodicTracker import io.split.android.client.service.impressions.strategy.ProcessStrategy import org.junit.Before @@ -60,9 +61,9 @@ class StrategyImpressionManagerTest { @Test fun `pushImpression calls apply on strategy`() { - val impression = mock(DecoratedImpression::class.java) - `when`(impression.trackImpressions).thenReturn(true) - impressionManager.pushImpression(impression) + val impression = mock(Impression::class.java) + val decoratedImpression = DecoratedImpression(impression, true) + impressionManager.pushImpression(decoratedImpression) verify(strategy).apply(impression) verifyNoInteractions(noneStrategy) @@ -70,9 +71,9 @@ class StrategyImpressionManagerTest { @Test fun `pushImpression calls apply on noneStrategy when trackImpressions is false`() { - val impression = mock(DecoratedImpression::class.java) - `when`(impression.trackImpressions).thenReturn(false) - impressionManager.pushImpression(impression) + val impression = mock(Impression::class.java) + val decoratedImpression = DecoratedImpression(impression, false) + impressionManager.pushImpression(decoratedImpression) verify(noneStrategy).apply(impression) verifyNoInteractions(strategy) @@ -80,12 +81,12 @@ class StrategyImpressionManagerTest { @Test fun `pushImpression when it is decorated uses value from trackImpression to track`() { - val impression = mock(DecoratedImpression::class.java) - val impression2 = mock(DecoratedImpression::class.java) - `when`(impression.trackImpressions).thenReturn(false) - `when`(impression2.trackImpressions).thenReturn(true) - impressionManager.pushImpression(impression) - impressionManager.pushImpression(impression2) + val impression = mock(Impression::class.java) + val impression2 = mock(Impression::class.java) + val decoratedImpression = DecoratedImpression(impression, false) + val decoratedImpression2 = DecoratedImpression(impression2, true) + impressionManager.pushImpression(decoratedImpression) + impressionManager.pushImpression(decoratedImpression2) verify(strategy).apply(impression2) verify(noneStrategy).apply(impression) @@ -94,9 +95,9 @@ class StrategyImpressionManagerTest { @Test fun `enableTracking set to true causes impressions to be sent to strategy`() { impressionManager.enableTracking(true) - val impression = mock(DecoratedImpression::class.java) - `when`(impression.trackImpressions).thenReturn(true) - impressionManager.pushImpression(impression) + val impression = mock(Impression::class.java) + val decoratedImpression = DecoratedImpression(impression, true) + impressionManager.pushImpression(decoratedImpression) verify(strategy).apply(impression) verifyNoInteractions(noneStrategy) @@ -105,9 +106,9 @@ class StrategyImpressionManagerTest { @Test fun `enableTracking set to false causes impressions to not be tracked`() { impressionManager.enableTracking(false) - val impression = mock(DecoratedImpression::class.java) - `when`(impression.trackImpressions).thenReturn(true) - impressionManager.pushImpression(impression) + val impression = mock(Impression::class.java) + val decoratedImpression = DecoratedImpression(impression, true) + impressionManager.pushImpression(decoratedImpression) verifyNoInteractions(noneStrategy) verifyNoInteractions(strategy) diff --git a/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java b/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java index 73f254230..36b8728a5 100644 --- a/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java +++ b/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java @@ -14,6 +14,7 @@ import io.split.android.client.attributes.AttributesMergerImpl; import io.split.android.client.events.SplitEventsManager; import io.split.android.client.events.SplitInternalEvent; +import io.split.android.client.impressions.DecoratedImpressionListener; import io.split.android.client.impressions.ImpressionListener; import io.split.android.client.shared.SplitClientContainer; import io.split.android.client.storage.mysegments.MySegmentsStorageContainer; @@ -37,7 +38,7 @@ public static SplitClientImpl get(Key key, SplitsStorage splitsStorage) { SplitParser splitParser = new SplitParser(mock(MySegmentsStorageContainer.class), mock(MySegmentsStorageContainer.class)); TelemetryStorage telemetryStorage = mock(TelemetryStorage.class); TreatmentManagerFactory treatmentManagerFactory = new TreatmentManagerFactoryImpl( - new KeyValidatorImpl(), new SplitValidatorImpl(), new ImpressionListener.NoopImpressionListener(), + new KeyValidatorImpl(), new SplitValidatorImpl(), new ImpressionListener.FederatedImpressionListener(mock(DecoratedImpressionListener.class), Collections.emptyList()), false, new AttributesMergerImpl(), telemetryStorage, splitParser, new FlagSetsFilterImpl(Collections.emptySet()), splitsStorage); diff --git a/src/test/java/io/split/android/fake/ImpressionListenerMock.java b/src/test/java/io/split/android/fake/ImpressionListenerMock.java index ce2ff7bd1..e9d53773f 100644 --- a/src/test/java/io/split/android/fake/ImpressionListenerMock.java +++ b/src/test/java/io/split/android/fake/ImpressionListenerMock.java @@ -1,13 +1,19 @@ package io.split.android.fake; +import io.split.android.client.impressions.DecoratedImpression; +import io.split.android.client.impressions.DecoratedImpressionListener; import io.split.android.client.impressions.Impression; import io.split.android.client.impressions.ImpressionListener; -public class ImpressionListenerMock implements ImpressionListener { +public class ImpressionListenerMock implements ImpressionListener, DecoratedImpressionListener { @Override public void log(Impression impression) { } + @Override + public void log(DecoratedImpression impression) { + } + @Override public void close() { } From b0b321b4a7e3abcf12e2d3817bcea371975a3748 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 12 Dec 2024 15:33:33 -0300 Subject: [PATCH 34/37] Fix tests comp issue --- src/main/java/io/split/android/client/EvaluatorImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/split/android/client/EvaluatorImpl.java b/src/main/java/io/split/android/client/EvaluatorImpl.java index b845bf6d0..33c0eac9b 100644 --- a/src/main/java/io/split/android/client/EvaluatorImpl.java +++ b/src/main/java/io/split/android/client/EvaluatorImpl.java @@ -52,7 +52,7 @@ public EvaluationResult getTreatment(String matchingKey, String bucketingKey, St private EvaluationResult getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit, Map attributes) throws ChangeNumberExceptionWrapper { try { if (parsedSplit.killed()) { - return new EvaluationResult(parsedSplit.defaultTreatment(), TreatmentLabels.KILLED, parsedSplit.changeNumber(), configForTreatment(parsedSplit, parsedSplit.defaultTreatment()), parsedSplit.trackImpression()); + return new EvaluationResult(parsedSplit.defaultTreatment(), TreatmentLabels.KILLED, parsedSplit.changeNumber(), configForTreatment(parsedSplit, parsedSplit.defaultTreatment()), parsedSplit.trackImpressions()); } /* @@ -75,7 +75,7 @@ private EvaluationResult getTreatment(String matchingKey, String bucketingKey, P if (bucket > parsedSplit.trafficAllocation()) { // out of split - return new EvaluationResult(parsedSplit.defaultTreatment(), TreatmentLabels.NOT_IN_SPLIT, parsedSplit.changeNumber(), configForTreatment(parsedSplit, parsedSplit.defaultTreatment()), parsedSplit.trackImpression()); + return new EvaluationResult(parsedSplit.defaultTreatment(), TreatmentLabels.NOT_IN_SPLIT, parsedSplit.changeNumber(), configForTreatment(parsedSplit, parsedSplit.defaultTreatment()), parsedSplit.trackImpressions()); } } @@ -84,11 +84,11 @@ private EvaluationResult getTreatment(String matchingKey, String bucketingKey, P if (parsedCondition.matcher().match(matchingKey, bucketingKey, attributes, this)) { String treatment = Splitter.getTreatment(bk, parsedSplit.seed(), parsedCondition.partitions(), parsedSplit.algo()); - return new EvaluationResult(treatment, parsedCondition.label(), parsedSplit.changeNumber(), configForTreatment(parsedSplit, treatment), parsedSplit.trackImpression()); + return new EvaluationResult(treatment, parsedCondition.label(), parsedSplit.changeNumber(), configForTreatment(parsedSplit, treatment), parsedSplit.trackImpressions()); } } - return new EvaluationResult(parsedSplit.defaultTreatment(), TreatmentLabels.DEFAULT_RULE, parsedSplit.changeNumber(), configForTreatment(parsedSplit, parsedSplit.defaultTreatment()), parsedSplit.trackImpression()); + return new EvaluationResult(parsedSplit.defaultTreatment(), TreatmentLabels.DEFAULT_RULE, parsedSplit.changeNumber(), configForTreatment(parsedSplit, parsedSplit.defaultTreatment()), parsedSplit.trackImpressions()); } catch (Exception e) { throw new ChangeNumberExceptionWrapper(e, parsedSplit.changeNumber()); } From 02be21e08fdf5d77f89ec5d0111d44159cb25bc0 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 13 Dec 2024 11:12:49 -0300 Subject: [PATCH 35/37] WIP tests --- .../assets/split_changes_imp_toggle.json | 124 ++++++++++++ .../java/helper/IntegrationHelper.java | 4 + .../toggle/ImpressionsToggleTest.java | 190 ++++++++++++++++++ 3 files changed, 318 insertions(+) create mode 100644 src/androidTest/assets/split_changes_imp_toggle.json create mode 100644 src/androidTest/java/tests/integration/toggle/ImpressionsToggleTest.java diff --git a/src/androidTest/assets/split_changes_imp_toggle.json b/src/androidTest/assets/split_changes_imp_toggle.json new file mode 100644 index 000000000..d1f7196ce --- /dev/null +++ b/src/androidTest/assets/split_changes_imp_toggle.json @@ -0,0 +1,124 @@ +{ + "splits": [ + { + "trafficTypeName": "user", + "name": "tracked", + "trafficAllocation": 100, + "trafficAllocationSeed": -285565213, + "seed": -1992295819, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1506703262916, + "algo": 2, + "trackImpressions": true, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "client", + "attribute": null + }, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "new_segment" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "free", + "size": 100 + }, + { + "treatment": "conta", + "size": 0 + } + ], + "label": "in segment new_segment" + } + ] + }, + { + "trafficTypeName": "user", + "name": "not_tracked", + "trafficAllocation": 100, + "trafficAllocationSeed": -285565213, + "seed": -1992295819, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1506703262916, + "algo": 2, + "trackImpressions": false, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "client", + "attribute": null + }, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "new_segment" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "free", + "size": 100 + }, + { + "treatment": "conta", + "size": 0 + } + ], + "label": "in segment new_segment" + } + ] + } + ], + "since": 1506703262916, + "till": 1506703262916 +} diff --git a/src/androidTest/java/helper/IntegrationHelper.java b/src/androidTest/java/helper/IntegrationHelper.java index 528f730fa..6144c6a7b 100644 --- a/src/androidTest/java/helper/IntegrationHelper.java +++ b/src/androidTest/java/helper/IntegrationHelper.java @@ -442,5 +442,9 @@ public interface StreamingResponseClosure { public static class ServicePath { public static final String MEMBERSHIPS = "memberships"; public static final String SPLIT_CHANGES = "splitChanges"; + public static final String EVENTS = "events"; + public static final String UNIQUE_KEYS = "keys/cs"; + public static final String COUNT = "testImpressions/count"; + public static final String IMPRESSIONS = "testImpressions/bulk"; } } diff --git a/src/androidTest/java/tests/integration/toggle/ImpressionsToggleTest.java b/src/androidTest/java/tests/integration/toggle/ImpressionsToggleTest.java new file mode 100644 index 000000000..9c2a7bb69 --- /dev/null +++ b/src/androidTest/java/tests/integration/toggle/ImpressionsToggleTest.java @@ -0,0 +1,190 @@ +package tests.integration.toggle; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static helper.IntegrationHelper.buildFactory; +import static helper.IntegrationHelper.emptyAllSegments; +import static helper.IntegrationHelper.getSinceFromUri; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import helper.DatabaseHelper; +import helper.FileHelper; +import helper.IntegrationHelper; +import io.split.android.client.ServiceEndpoints; +import io.split.android.client.SplitClient; +import io.split.android.client.SplitClientConfig; +import io.split.android.client.SplitFactory; +import io.split.android.client.SplitManager; +import io.split.android.client.api.SplitView; +import io.split.android.client.dtos.SplitChange; +import io.split.android.client.events.SplitEvent; +import io.split.android.client.service.impressions.ImpressionsMode; +import io.split.android.client.utils.Json; +import io.split.android.client.utils.logger.SplitLogLevel; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import tests.integration.shared.TestingHelper; + +public class ImpressionsToggleTest { + + private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + private CountDownLatch mRequestCountdownLatch; + private MockWebServer mWebServer; + + private final AtomicReference mCountBody = new AtomicReference<>(null); + private final AtomicReference mImpressionsBody = new AtomicReference<>(null); + private final AtomicReference mUniqueKeysBody = new AtomicReference<>(null); + + @Before + public void setUp() { + setupServer(); + mRequestCountdownLatch = new CountDownLatch(1); + mCountBody.set(null); + mImpressionsBody.set(null); + mUniqueKeysBody.set(null); + } + + @Test + public void managerContainsProperty() throws InterruptedException { + SplitFactory splitFactory = getReadyFactory(ImpressionsMode.OPTIMIZED); + + SplitManager manager = splitFactory.manager(); + List splits = manager.splits(); + + assertTrue(manager.split("tracked").trackImpressions); + assertFalse(manager.split("not_tracked").trackImpressions); + assertEquals(2, splits.size()); + } + + @Test + public void test() throws InterruptedException { + // 1. Initialize SDK in impressions NONE mode + SplitFactory splitFactory = getReadyFactory(ImpressionsMode.NONE); + + // 2. Fetch splitChanges with both flags with trackImpressions true & false + SplitClient client = splitFactory.client(); + client.getTreatment("tracked"); + client.getTreatment("not_tracked"); + + // 3. Verify all counts & mtks are tracked + client.flush(); + + Thread.sleep(2000); + assertNotNull(mCountBody.get()); + assertNotNull(mUniqueKeysBody.get()); + assertNull(mImpressionsBody.get()); + } + + @Test + public void test2() { + // 1. Initialize SDK in impressions DEBUG mode + // 2. Fetch splitChanges with both flags with trackImpressions true & false + // 3. Verify counts & MTKs are tracked only for trackImpressions false + // 4. Verify impressions are tracked for trackImpressions true + } + + @Test + public void test3() { + // 1. Initialize SDK in impressions OPTIMIZED mode + // 2. Fetch splitChanges with both flags with trackImpressions true & false + // 3. Verify counts & MTKs are tracked only for trackImpressions false + // 4. Verify impressions are tracked for trackImpressions true + } + + private SplitFactory getReadyFactory(ImpressionsMode impressionsMode) throws InterruptedException { + SplitFactory splitFactory = getSplitFactory(impressionsMode); + CountDownLatch latch = new CountDownLatch(1); + splitFactory.client().on(SplitEvent.SDK_READY, new TestingHelper.TestEventTask(latch)); + mRequestCountdownLatch.countDown(); + + boolean await = latch.await(5, TimeUnit.SECONDS); + + if (!await) { + fail("Client was not ready"); + } + + return splitFactory; + } + + private SplitFactory getSplitFactory(ImpressionsMode impressionsMode) { + final String url = mWebServer.url("/").url().toString(); + ServiceEndpoints endpoints = ServiceEndpoints.builder() + .apiEndpoint(url) + .eventsEndpoint(url) + .telemetryServiceEndpoint(url) + .build(); + SplitClientConfig config = new SplitClientConfig.Builder() + .serviceEndpoints(endpoints) + .impressionsMode(impressionsMode) + .streamingEnabled(false) + .featuresRefreshRate(9999) + .segmentsRefreshRate(9999) + .impressionsRefreshRate(9999) + .logLevel(SplitLogLevel.VERBOSE) + .streamingEnabled(false) + .build(); + + return buildFactory(IntegrationHelper.dummyApiKey(), IntegrationHelper.dummyUserKey(), config, + mContext, null, DatabaseHelper.getTestDatabase(mContext)); + } + + private void setupServer() { + mWebServer = new MockWebServer(); + + final Dispatcher dispatcher = new Dispatcher() { + + @Override + public MockResponse dispatch(RecordedRequest request) throws InterruptedException { + mRequestCountdownLatch.await(); + if (request.getPath().contains("/" + IntegrationHelper.ServicePath.MEMBERSHIPS)) { + return new MockResponse().setResponseCode(200).setBody(emptyAllSegments()); + } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.SPLIT_CHANGES)) { + String sinceFromUri = getSinceFromUri(request.getRequestUrl().uri()); + if (sinceFromUri.equals("-1")) { + return new MockResponse().setResponseCode(200).setBody(loadSplitChanges()); + } else { + return new MockResponse().setResponseCode(200) + .setBody(IntegrationHelper.emptySplitChanges(1506703262916L, 1506703262916L)); + } + } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.COUNT)) { + mCountBody.set(request.getBody().readUtf8()); + return new MockResponse().setResponseCode(200); + } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.IMPRESSIONS)) { + mImpressionsBody.set(request.getBody().readUtf8()); + return new MockResponse().setResponseCode(200); + } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.UNIQUE_KEYS)) { + mUniqueKeysBody.set(request.getBody().readUtf8()); + return new MockResponse().setResponseCode(200); + } else { + return new MockResponse().setResponseCode(404); + } + } + }; + mWebServer.setDispatcher(dispatcher); + } + + private String loadSplitChanges() { + FileHelper fileHelper = new FileHelper(); + String change = fileHelper.loadFileContent(mContext, "split_changes_imp_toggle.json"); + SplitChange parsedChange = Json.fromJson(change, SplitChange.class); + parsedChange.since = parsedChange.till; + return Json.toJson(parsedChange); + } +} From a79d535624fcd55ae83e8650f9fc8b8ffe12716b Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 13 Dec 2024 11:42:48 -0300 Subject: [PATCH 36/37] WIP test --- .../java/helper/DatabaseHelper.java | 5 +++- .../toggle/ImpressionsToggleTest.java | 24 +++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/androidTest/java/helper/DatabaseHelper.java b/src/androidTest/java/helper/DatabaseHelper.java index e71be3479..57646fcd2 100644 --- a/src/androidTest/java/helper/DatabaseHelper.java +++ b/src/androidTest/java/helper/DatabaseHelper.java @@ -17,9 +17,12 @@ public static boolean removeDatabaseFile(String name) { } public static SplitRoomDatabase getTestDatabase(Context context) { - return Room.inMemoryDatabaseBuilder(context, SplitRoomDatabase.class) + SplitRoomDatabase database = Room.inMemoryDatabaseBuilder(context, SplitRoomDatabase.class) .fallbackToDestructiveMigration() .allowMainThreadQueries() .build(); + + database.clearAllTables(); + return database; } } diff --git a/src/androidTest/java/tests/integration/toggle/ImpressionsToggleTest.java b/src/androidTest/java/tests/integration/toggle/ImpressionsToggleTest.java index 9c2a7bb69..f08e65c76 100644 --- a/src/androidTest/java/tests/integration/toggle/ImpressionsToggleTest.java +++ b/src/androidTest/java/tests/integration/toggle/ImpressionsToggleTest.java @@ -35,6 +35,7 @@ import io.split.android.client.events.SplitEvent; import io.split.android.client.service.impressions.ImpressionsMode; import io.split.android.client.utils.Json; +import io.split.android.client.utils.logger.Logger; import io.split.android.client.utils.logger.SplitLogLevel; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; @@ -52,6 +53,10 @@ public class ImpressionsToggleTest { private final AtomicReference mImpressionsBody = new AtomicReference<>(null); private final AtomicReference mUniqueKeysBody = new AtomicReference<>(null); + private CountDownLatch mCountLatch; + private CountDownLatch mImpressionsLatch; + private CountDownLatch mUniqueKeysLatch; + @Before public void setUp() { setupServer(); @@ -59,6 +64,10 @@ public void setUp() { mCountBody.set(null); mImpressionsBody.set(null); mUniqueKeysBody.set(null); + + mCountLatch = new CountDownLatch(1); + mImpressionsLatch = new CountDownLatch(1); + mUniqueKeysLatch = new CountDownLatch(1); } @Test @@ -74,19 +83,22 @@ public void managerContainsProperty() throws InterruptedException { } @Test - public void test() throws InterruptedException { + public void testNoneMode() throws InterruptedException { // 1. Initialize SDK in impressions NONE mode SplitFactory splitFactory = getReadyFactory(ImpressionsMode.NONE); // 2. Fetch splitChanges with both flags with trackImpressions true & false SplitClient client = splitFactory.client(); - client.getTreatment("tracked"); - client.getTreatment("not_tracked"); + String trackedTreatment = client.getTreatment("tracked"); + String notTrackedTreatment = client.getTreatment("not_tracked"); + Thread.sleep(200); // 3. Verify all counts & mtks are tracked client.flush(); - Thread.sleep(2000); + mUniqueKeysLatch.await(5, TimeUnit.SECONDS); + mCountLatch.await(5, TimeUnit.SECONDS); + assertNotNull(mCountBody.get()); assertNotNull(mUniqueKeysBody.get()); assertNull(mImpressionsBody.get()); @@ -153,6 +165,7 @@ private void setupServer() { @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { mRequestCountdownLatch.await(); + Logger.e("Path is: " + request.getPath()); if (request.getPath().contains("/" + IntegrationHelper.ServicePath.MEMBERSHIPS)) { return new MockResponse().setResponseCode(200).setBody(emptyAllSegments()); } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.SPLIT_CHANGES)) { @@ -164,12 +177,15 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio .setBody(IntegrationHelper.emptySplitChanges(1506703262916L, 1506703262916L)); } } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.COUNT)) { + mCountLatch.countDown(); mCountBody.set(request.getBody().readUtf8()); return new MockResponse().setResponseCode(200); } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.IMPRESSIONS)) { + mImpressionsLatch.countDown(); mImpressionsBody.set(request.getBody().readUtf8()); return new MockResponse().setResponseCode(200); } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.UNIQUE_KEYS)) { + mUniqueKeysLatch.countDown(); mUniqueKeysBody.set(request.getBody().readUtf8()); return new MockResponse().setResponseCode(200); } else { From f547b3afa09caf78110b10d0a15cbbf0ceb655da Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 13 Dec 2024 15:42:04 -0300 Subject: [PATCH 37/37] Better tests --- .../toggle/ImpressionsToggleTest.java | 111 +++++++++++++++--- 1 file changed, 96 insertions(+), 15 deletions(-) diff --git a/src/androidTest/java/tests/integration/toggle/ImpressionsToggleTest.java b/src/androidTest/java/tests/integration/toggle/ImpressionsToggleTest.java index f08e65c76..8d3f4c352 100644 --- a/src/androidTest/java/tests/integration/toggle/ImpressionsToggleTest.java +++ b/src/androidTest/java/tests/integration/toggle/ImpressionsToggleTest.java @@ -14,9 +14,13 @@ import androidx.test.platform.app.InstrumentationRegistry; +import com.google.common.reflect.TypeToken; + import org.junit.Before; import org.junit.Test; +import java.lang.reflect.Type; +import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -32,8 +36,11 @@ import io.split.android.client.SplitManager; import io.split.android.client.api.SplitView; import io.split.android.client.dtos.SplitChange; +import io.split.android.client.dtos.TestImpressions; import io.split.android.client.events.SplitEvent; +import io.split.android.client.service.impressions.ImpressionsCount; import io.split.android.client.service.impressions.ImpressionsMode; +import io.split.android.client.service.impressions.unique.MTK; import io.split.android.client.utils.Json; import io.split.android.client.utils.logger.Logger; import io.split.android.client.utils.logger.SplitLogLevel; @@ -89,35 +96,109 @@ public void testNoneMode() throws InterruptedException { // 2. Fetch splitChanges with both flags with trackImpressions true & false SplitClient client = splitFactory.client(); - String trackedTreatment = client.getTreatment("tracked"); - String notTrackedTreatment = client.getTreatment("not_tracked"); + client.getTreatment("tracked"); + client.getTreatment("not_tracked"); Thread.sleep(200); - // 3. Verify all counts & mtks are tracked + // 3. Flush client.flush(); + // 4. Wait for requests to be sent mUniqueKeysLatch.await(5, TimeUnit.SECONDS); mCountLatch.await(5, TimeUnit.SECONDS); + boolean impressionsAwait = mImpressionsLatch.await(3, TimeUnit.SECONDS); - assertNotNull(mCountBody.get()); - assertNotNull(mUniqueKeysBody.get()); - assertNull(mImpressionsBody.get()); + // 5. Verify request bodies + verifyOnlyNone(impressionsAwait); } @Test - public void test2() { + public void testDebugMode() throws InterruptedException { // 1. Initialize SDK in impressions DEBUG mode + SplitFactory splitFactory = getReadyFactory(ImpressionsMode.DEBUG); + // 2. Fetch splitChanges with both flags with trackImpressions true & false - // 3. Verify counts & MTKs are tracked only for trackImpressions false - // 4. Verify impressions are tracked for trackImpressions true + SplitClient client = splitFactory.client(); + client.getTreatment("tracked"); + client.getTreatment("not_tracked"); + Thread.sleep(500); + + // 3. Flush + client.flush(); + + // 4. Wait for requests to be sent + mUniqueKeysLatch.await(5, TimeUnit.SECONDS); + mCountLatch.await(5, TimeUnit.SECONDS); + mImpressionsLatch.await(5, TimeUnit.SECONDS); + + // 5. Verify request bodies + verify(); } @Test - public void test3() { + public void testOptimizedMode() throws InterruptedException { // 1. Initialize SDK in impressions OPTIMIZED mode + SplitFactory splitFactory = getReadyFactory(ImpressionsMode.OPTIMIZED); + // 2. Fetch splitChanges with both flags with trackImpressions true & false - // 3. Verify counts & MTKs are tracked only for trackImpressions false - // 4. Verify impressions are tracked for trackImpressions true + SplitClient client = splitFactory.client(); + client.getTreatment("tracked"); + client.getTreatment("not_tracked"); + Thread.sleep(200); + + // 3. Flush + client.flush(); + + // 4. Wait for requests to be sent + mUniqueKeysLatch.await(5, TimeUnit.SECONDS); + mCountLatch.await(5, TimeUnit.SECONDS); + mImpressionsLatch.await(5, TimeUnit.SECONDS); + + // 5. Verify request bodies + verify(); + } + + private void verifyOnlyNone(boolean impressionsAwait) { + // a. Counts + String countBody = mCountBody.get(); + ImpressionsCount impressionsCount = Json.fromJson(countBody, ImpressionsCount.class); + assertEquals(2, impressionsCount.perFeature.size()); + assertTrue(impressionsCount.perFeature.stream().anyMatch(c -> c.feature.equals("tracked") && c.count == 1)); + assertTrue(impressionsCount.perFeature.stream().anyMatch(c -> c.feature.equals("not_tracked") && c.count == 1)); + + // b. MTKs + String uniqueBody = mUniqueKeysBody.get(); + MTK mtk = Json.fromJson(uniqueBody, MTK.class); + assertEquals(1, mtk.getKeys().size()); + assertTrue(mtk.getKeys().get(0).getFeatures().containsAll(Arrays.asList("not_tracked", "tracked"))); + assertEquals("CUSTOMER_ID", mtk.getKeys().get(0).getKey()); + + // c. Impressions (no impressions) + assertNull(mImpressionsBody.get()); + assertFalse(impressionsAwait); + } + + private void verify() { + // a. Counts + String countBody = mCountBody.get(); + ImpressionsCount impressionsCount = Json.fromJson(countBody, ImpressionsCount.class); + assertEquals(1, impressionsCount.perFeature.size()); + assertEquals("not_tracked", impressionsCount.perFeature.get(0).feature); + + // b. MTKs + String uniqueBody = mUniqueKeysBody.get(); + MTK mtk = Json.fromJson(uniqueBody, MTK.class); + assertEquals(1, mtk.getKeys().size()); + assertTrue(mtk.getKeys().get(0).getFeatures().containsAll(Arrays.asList("not_tracked"))); + assertEquals(1, mtk.getKeys().get(0).getFeatures().size()); + assertNotNull(uniqueBody); + + // c. Impressions + String impressionsBody = mImpressionsBody.get(); + Type listTypeToken = new TypeToken>(){}.getType(); + List impressions = Json.fromJson(impressionsBody, listTypeToken); + assertEquals(1, impressions.size()); + assertEquals("tracked", impressions.get(0).testName); } private SplitFactory getReadyFactory(ImpressionsMode impressionsMode) throws InterruptedException { @@ -177,16 +258,16 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio .setBody(IntegrationHelper.emptySplitChanges(1506703262916L, 1506703262916L)); } } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.COUNT)) { - mCountLatch.countDown(); mCountBody.set(request.getBody().readUtf8()); + mCountLatch.countDown(); return new MockResponse().setResponseCode(200); } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.IMPRESSIONS)) { - mImpressionsLatch.countDown(); mImpressionsBody.set(request.getBody().readUtf8()); + mImpressionsLatch.countDown(); return new MockResponse().setResponseCode(200); } else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.UNIQUE_KEYS)) { - mUniqueKeysLatch.countDown(); mUniqueKeysBody.set(request.getBody().readUtf8()); + mUniqueKeysLatch.countDown(); return new MockResponse().setResponseCode(200); } else { return new MockResponse().setResponseCode(404);