From 8ef9ed18f1dd123d089888c2c2b5306f82b21d95 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 4 Sep 2023 16:12:39 -0300 Subject: [PATCH] Tests --- .../client/telemetry/model/Method.java | 3 + .../validators/TreatmentManagerImpl.java | 141 ++++++------ .../TreatmentManagerWithFlagSetsTest.java | 204 +++++++++++++++++- 3 files changed, 273 insertions(+), 75 deletions(-) diff --git a/src/main/java/io/split/android/client/telemetry/model/Method.java b/src/main/java/io/split/android/client/telemetry/model/Method.java index 3f4ae0626..2303325d0 100644 --- a/src/main/java/io/split/android/client/telemetry/model/Method.java +++ b/src/main/java/io/split/android/client/telemetry/model/Method.java @@ -6,6 +6,9 @@ public enum Method { TREATMENT_WITH_CONFIG("getTreatmentWithConfig"), TREATMENTS_WITH_CONFIG("getTreatmentsWithConfig"), TREATMENTS_BY_FLAG_SET("getTreatmentsByFlagSet"), + TREATMENTS_BY_FLAG_SETS("getTreatmentsByFlagSets"), + TREATMENTS_WITH_CONFIG_BY_FLAG_SET("getTreatmentsWithConfigByFlagSet"), + TREATMENTS_WITH_CONFIG_BY_FLAG_SETS("getTreatmentsWithConfigByFlagSets"), TRACK("track"); private final String _method; 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 ed8821442..930802f6b 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -37,7 +37,7 @@ private static class ValidationTag { public static final String GET_TREATMENT_WITH_CONFIG = "getTreatmentWithConfig"; public static final String GET_TREATMENTS_WITH_CONFIG = "getTreatmentsWithConfig"; public static final String GET_TREATMENTS_BY_FLAG_SET = "getTreatmentsByFlagSet"; - public static final String GET_TREATMENTS_BY_FLAG_SETS = "getTreatmentsByFlagSets"; + public static final String GET_TREATMENTS_BY_FLAG_SETS = "getFeatureFlagNamesToEvaluate"; public static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = "getTreatmentsWithConfigByFlagSet"; public static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = "getTreatmentsWithConfigByFlagSets"; } @@ -182,90 +182,42 @@ public Map getTreatmentsWithConfig(List splits, Map @Override public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { - String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SET; - if (isClientDestroyed) { - mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); - return new HashMap<>(); - } - - if (!mFlagSetsValidator.isValid(flagSet)) { - mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); - return new HashMap<>(); - } - - if (!mConfiguredFlagSets.isEmpty() && !mConfiguredFlagSets.contains(flagSet)) { - mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); - return new HashMap<>(); - } - long start = System.currentTimeMillis(); - - Map result = new HashMap<>(); - Set featureFlagNamesInSet = mSplitsStorage.getNamesByFlagSets(Collections.singletonList(flagSet)); - for (String featureFlagName : featureFlagNamesInSet) { - result.put(featureFlagName, getTreatmentWithConfigWithoutMetrics(featureFlagName, attributes, validationTag).treatment()); + try { + return evaluateFeaturesGeneric(getFeatureFlagNamesToEvaluate(ValidationTag.GET_TREATMENTS_BY_FLAG_SET, Collections.singletonList(flagSet), isClientDestroyed), attributes, ValidationTag.GET_TREATMENTS_BY_FLAG_SET, SplitResult::treatment); + } finally { + recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); } - - recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); - - return result; } @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { - String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SETS; - if (isClientDestroyed) { - mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); - return new HashMap<>(); - } - - if (flagSets == null) { - return new HashMap<>(); - } - long start = System.currentTimeMillis(); - - List setsToEvaluate = new ArrayList<>(); - for (String flagSet : flagSets) { - if (setsToEvaluate.contains(flagSet)) { - continue; - } - - boolean isValid = mFlagSetsValidator.isValid(flagSet); - boolean isConfigured = mConfiguredFlagSets.isEmpty() || mConfiguredFlagSets.contains(flagSet); - - if (!isValid) { - mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); - } else if (!isConfigured) { - mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); - } else { - setsToEvaluate.add(flagSet); - } - } - - if (setsToEvaluate.isEmpty()) { - return new HashMap<>(); - } - - Map result = new HashMap<>(); - Set featureFlagNamesInSet = mSplitsStorage.getNamesByFlagSets(setsToEvaluate); - for (String featureFlagName : featureFlagNamesInSet) { - result.put(featureFlagName, getTreatmentWithConfigWithoutMetrics(featureFlagName, attributes, validationTag).treatment()); + try { + return evaluateFeaturesGeneric(getFeatureFlagNamesToEvaluate(ValidationTag.GET_TREATMENTS_BY_FLAG_SETS, flagSets, isClientDestroyed), attributes, ValidationTag.GET_TREATMENTS_BY_FLAG_SETS, SplitResult::treatment); + } finally { + recordLatency(Method.TREATMENTS_BY_FLAG_SETS, start); } - - recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); - - return result; } @Override public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { - return null; + long start = System.currentTimeMillis(); + try { + return evaluateFeaturesGeneric(getFeatureFlagNamesToEvaluate(ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, Collections.singletonList(flagSet), isClientDestroyed), attributes, ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, splitResult -> splitResult); + } finally { + recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, start); + } } @Override public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { - return null; + long start = System.currentTimeMillis(); + try { + return evaluateFeaturesGeneric(getFeatureFlagNamesToEvaluate(ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flagSets, isClientDestroyed), attributes, ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, splitResult -> splitResult); + } finally { + recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, start); + } } private SplitResult getTreatmentWithConfigWithoutMetrics(String split, Map attributes, String validationTag) { @@ -387,4 +339,55 @@ private EvaluationResult evaluateIfReady(String splitName, private void recordLatency(Method treatment, long startTime) { mTelemetryStorageProducer.recordLatency(treatment, System.currentTimeMillis() - startTime); } + + @NonNull + private Set getFeatureFlagNamesToEvaluate(String validationTag, + @NonNull List flagSets, + boolean isClientDestroyed) { + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return new HashSet<>(); + } + + if (flagSets == null) { + return new HashSet<>(); + } + + List setsToEvaluate = new ArrayList<>(); + for (String flagSet : flagSets) { + if (setsToEvaluate.contains(flagSet)) { + continue; + } + + boolean isValid = mFlagSetsValidator.isValid(flagSet); + boolean isConfigured = mConfiguredFlagSets.isEmpty() || mConfiguredFlagSets.contains(flagSet); + + if (!isValid) { + mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); + } else if (!isConfigured) { + mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); + } else { + setsToEvaluate.add(flagSet); + } + } + + if (setsToEvaluate.isEmpty()) { + return new HashSet<>(); + } + + return mSplitsStorage.getNamesByFlagSets(setsToEvaluate); + } + + private Map evaluateFeaturesGeneric(Set names, @Nullable Map attributes, String validationTag, ResultTransformer transformer) { + Map result = new HashMap<>(); + for (String featureFlagName : names) { + SplitResult splitResult = getTreatmentWithConfigWithoutMetrics(featureFlagName, attributes, validationTag); + result.put(featureFlagName, transformer.transform(splitResult)); + } + return result; + } + + private interface ResultTransformer { + T transform(SplitResult splitResult); + } } diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java index 34b46e183..7e4cca25a 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -27,6 +28,7 @@ import io.split.android.client.events.SplitEvent; import io.split.android.client.impressions.ImpressionListener; import io.split.android.client.storage.splits.SplitsStorage; +import io.split.android.client.telemetry.model.Method; import io.split.android.client.telemetry.storage.TelemetryStorageProducer; import io.split.android.client.validators.KeyValidator; import io.split.android.client.validators.SplitValidator; @@ -96,7 +98,7 @@ public void tearDown() { } @Test - public void getTreatmentByFlagSetDestroyedDoesNotQueryStorageOrUseEvaluator() { + public void getTreatmentsByFlagSetDestroyedDoesNotQueryStorageOrUseEvaluator() { mTreatmentManager.getTreatmentsByFlagSet("set_1", null, true); verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); @@ -104,7 +106,7 @@ public void getTreatmentByFlagSetDestroyedDoesNotQueryStorageOrUseEvaluator() { } @Test - public void getTreatmentByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { + public void getTreatmentsByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); @@ -115,7 +117,7 @@ public void getTreatmentByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvalua } @Test - public void getTreatmentByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStorageNorUseEvaluator() { + public void getTreatmentsByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStorageNorUseEvaluator() { when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); @@ -126,7 +128,7 @@ public void getTreatmentByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStora } @Test - public void getTreatmentByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { + public void getTreatmentsByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { mConfiguredFlagSets.add("set_1"); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); @@ -138,7 +140,7 @@ public void getTreatmentByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndU } @Test - public void getTreatmentByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { + public void getTreatmentsByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { mConfiguredFlagSets.add("set_1"); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); @@ -150,7 +152,7 @@ public void getTreatmentByFlagSetWithConfiguredSetsNonExistingSetDoesNotQuerySto } @Test - public void getTreatmentByFlagSetReturnsCorrectFormat() { + public void getTreatmentsByFlagSetReturnsCorrectFormat() { Set mockNames = new HashSet<>(); mockNames.add("test_1"); mockNames.add("test_2"); @@ -164,6 +166,15 @@ public void getTreatmentByFlagSetReturnsCorrectFormat() { assertEquals("result_2", result.get("test_2")); } + @Test + public void getTreatmentsByFlagSetRecordsTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + + mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); + + verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_BY_FLAG_SET), anyLong()); + } + /// @Test public void getTreatmentsByFlagSetsDestroyedDoesNotQueryStorageOrUseEvaluator() { @@ -247,4 +258,185 @@ public void getTreatmentsByFlagSetsWithNullSetListReturnsEmpty() { verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); assertEquals(0, result.size()); } + + @Test + public void getTreatmentsByFlagSetsRecordsTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_BY_FLAG_SETS), anyLong()); + } + + /// + @Test + public void getTreatmentsWithConfigByFlagSetDestroyedDoesNotQueryStorageOrUseEvaluator() { + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, true); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStorageNorUseEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("SET!", null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_2", null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetReturnsCorrectFormat() { + Set mockNames = new HashSet<>(); + mockNames.add("test_1"); + mockNames.add("test_2"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(mockNames); + mConfiguredFlagSets.add("set_1"); + + Map result = mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + assertEquals(2, result.size()); + assertEquals("result_1", result.get("test_1").treatment()); + assertEquals("result_2", result.get("test_2").treatment()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetRecordsTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET), anyLong()); + } + + /// + @Test + public void getTreatmentsWithConfigByFlagSetsDestroyedDoesNotQueryStorageOrUseEvaluator() { + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Collections.singletonList("set_1"), null, true); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))) + .thenReturn(new HashSet<>(Arrays.asList("test_1", "test_2"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Arrays.asList("set_1", "set_2")); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_2"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithNoConfiguredSetsInvalidSetDoesNotQueryStorageForInvalidSet() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "SET!"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(any(), any(), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithConfiguredSetsExistingSetQueriesStorageForConfiguredSetOnlyAndUsesEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { + mConfiguredFlagSets.add("set_1"); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_2", "set_3"), null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsReturnsCorrectFormat() { + Set mockNames = new HashSet<>(); + mockNames.add("test_1"); + mockNames.add("test_2"); + when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))).thenReturn(mockNames); + + Map result = mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + assertEquals(2, result.size()); + assertEquals("result_1", result.get("test_1").treatment()); + assertEquals("result_2", result.get("test_2").treatment()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithDuplicatedSetDeduplicates() { + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_1"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithNullSetListReturnsEmpty() { + Map result = mTreatmentManager.getTreatmentsWithConfigByFlagSets(null, null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + assertEquals(0, result.size()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsRecordsTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS), anyLong()); + } }