From f65f0ae0664674cda2ad3cedf3fc9b3c04edec09 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 4 Dec 2024 17:27:27 -0300 Subject: [PATCH] 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()); }