From 5f2084d59d43661251a8d8f349cbf75c0ec71ff0 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 4 Sep 2025 12:31:14 -0700 Subject: [PATCH 1/6] Updated evaluator --- .../io/split/client/SplitFactoryImpl.java | 7 +- .../java/io/split/client/utils/Utils.java | 28 +++++ .../split/engine/evaluator/EvaluatorImp.java | 19 ++-- .../io/split/client/SplitClientImplTest.java | 106 +++++++++--------- .../evaluator/EvaluatorIntegrationTest.java | 2 +- .../split/engine/evaluator/EvaluatorTest.java | 75 ++++++++++++- 6 files changed, 169 insertions(+), 68 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index cca655612..bcc1a9679 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -257,7 +257,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn config.getThreadFactory()); // Evaluator - _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache); + _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, null); // SplitClient _client = new SplitClientImpl(this, @@ -348,7 +348,8 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor _telemetrySynchronizer = new TelemetryConsumerSubmitter(customStorageWrapper, _sdkMetadata); UserCustomRuleBasedSegmentAdapterConsumer userCustomRuleBasedSegmentAdapterConsumer = new UserCustomRuleBasedSegmentAdapterConsumer(customStorageWrapper); - _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer, userCustomRuleBasedSegmentAdapterConsumer); + _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer, + userCustomRuleBasedSegmentAdapterConsumer, null); _impressionsSender = PluggableImpressionSender.create(customStorageWrapper); _uniqueKeysTracker = createUniqueKeysTracker(config); _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer, @@ -446,7 +447,7 @@ protected SplitFactoryImpl(SplitClientConfig config) { _impressionsManager, null, null, null); // Evaluator - _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache); + _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, null); EventsStorage eventsStorage = new NoopEventsStorageImp(); diff --git a/client/src/main/java/io/split/client/utils/Utils.java b/client/src/main/java/io/split/client/utils/Utils.java index 9a386db55..74e17ee14 100644 --- a/client/src/main/java/io/split/client/utils/Utils.java +++ b/client/src/main/java/io/split/client/utils/Utils.java @@ -1,6 +1,8 @@ package io.split.client.utils; import io.split.client.dtos.ChangeDto; +import io.split.client.dtos.FallbackTreatmentsConfiguration; +import io.split.engine.evaluator.EvaluatorImp.TreatmentLabelAndChangeNumber; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; @@ -45,4 +47,30 @@ public static URI appendPath(URI root, String pathToAppend) throws URISyntaxExce public static boolean checkExitConditions(ChangeDto change, long cn) { return change.t < cn && change.t != -1; } + + public static TreatmentLabelAndChangeNumber checkFallbackTreatments(String treatment, String label, + String feature_name, Long changeNumber, + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration) { + if (fallbackTreatmentsConfiguration != null) { + if (fallbackTreatmentsConfiguration.getByFlagFallbackTreatment() != null + && fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name) != null + && !fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name).getTreatment().isEmpty()) { + return new TreatmentLabelAndChangeNumber( + fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name).getTreatment(), + fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name).getLabel() + label, + changeNumber); + } + + if (fallbackTreatmentsConfiguration.getGlobalFallbackTreatment() != null + && !fallbackTreatmentsConfiguration.getGlobalFallbackTreatment().getTreatment().isEmpty()) { + return new TreatmentLabelAndChangeNumber(fallbackTreatmentsConfiguration.getGlobalFallbackTreatment().getTreatment(), + fallbackTreatmentsConfiguration.getGlobalFallbackTreatment().getLabel() + label, + changeNumber); + } + } + + return new TreatmentLabelAndChangeNumber(treatment, + label, + changeNumber); + } } \ No newline at end of file diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index 6d31952c3..9dd718194 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -1,6 +1,7 @@ package io.split.engine.evaluator; import io.split.client.dtos.ConditionType; +import io.split.client.dtos.FallbackTreatmentsConfiguration; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; @@ -19,6 +20,7 @@ import java.util.Map; import static com.google.common.base.Preconditions.checkNotNull; +import static io.split.client.utils.Utils.checkFallbackTreatments; public class EvaluatorImp implements Evaluator { private static final Logger _log = LoggerFactory.getLogger(EvaluatorImp.class); @@ -26,19 +28,22 @@ public class EvaluatorImp implements Evaluator { private final SegmentCacheConsumer _segmentCacheConsumer; private final EvaluationContext _evaluationContext; private final SplitCacheConsumer _splitCacheConsumer; + private final FallbackTreatmentsConfiguration _fallbackTreatmentsConfiguration; public EvaluatorImp(SplitCacheConsumer splitCacheConsumer, SegmentCacheConsumer segmentCache, - RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer) { + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer, + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration) { _splitCacheConsumer = checkNotNull(splitCacheConsumer); _segmentCacheConsumer = checkNotNull(segmentCache); _evaluationContext = new EvaluationContext(this, _segmentCacheConsumer, ruleBasedSegmentCacheConsumer); + _fallbackTreatmentsConfiguration = fallbackTreatmentsConfiguration; } @Override public TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String featureFlag, Map attributes) { ParsedSplit parsedSplit = _splitCacheConsumer.get(featureFlag); - return evaluateParsedSplit(matchingKey, bucketingKey, attributes, parsedSplit); + return evaluateParsedSplit(matchingKey, bucketingKey, attributes, parsedSplit, featureFlag); } @Override @@ -49,7 +54,7 @@ public Map evaluateFeatures(String matchi if (parsedSplits == null) { return results; } - featureFlags.forEach(s -> results.put(s, evaluateParsedSplit(matchingKey, bucketingKey, attributes, parsedSplits.get(s)))); + featureFlags.forEach(s -> results.put(s, evaluateParsedSplit(matchingKey, bucketingKey, attributes, parsedSplits.get(s), s))); return results; } @@ -172,18 +177,18 @@ private String getConfig(ParsedSplit parsedSplit, String returnedTreatment) { } private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, String bucketingKey, Map attributes, - ParsedSplit parsedSplit) { + ParsedSplit parsedSplit, String feature_name) { try { if (parsedSplit == null) { - return new TreatmentLabelAndChangeNumber(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND); + return checkFallbackTreatments(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND, feature_name, null, _fallbackTreatmentsConfiguration); } return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { _log.error("Evaluator Exception", e.wrappedException()); - return new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, Labels.EXCEPTION, e.changeNumber()); + return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, feature_name, e.changeNumber(), _fallbackTreatmentsConfiguration); } catch (Exception e) { _log.error("Evaluator Exception", e); - return new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, Labels.EXCEPTION); + return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, feature_name, null, _fallbackTreatmentsConfiguration); } } diff --git a/client/src/test/java/io/split/client/SplitClientImplTest.java b/client/src/test/java/io/split/client/SplitClientImplTest.java index 5b56708d9..a5b55ed78 100644 --- a/client/src/test/java/io/split/client/SplitClientImplTest.java +++ b/client/src/test/java/io/split/client/SplitClientImplTest.java @@ -99,7 +99,7 @@ public void nullKeyResultsInControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(Treatments.CONTROL, client.getTreatment(null, "test1")); @@ -129,7 +129,7 @@ public void nullTestResultsInControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(Treatments.CONTROL, client.getTreatment("adil@relateiq.com", null)); @@ -152,7 +152,7 @@ public void exceptionsResultInControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(Treatments.CONTROL, client.getTreatment("adil@relateiq.com", "test1")); @@ -184,7 +184,7 @@ public void works() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -222,7 +222,7 @@ public void worksNullConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); String randomKey = RandomStringUtils.random(10); @@ -258,7 +258,7 @@ public void worksAndHasConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -295,7 +295,7 @@ public void lastConditionIsAlwaysDefault() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -335,7 +335,7 @@ public void lastConditionIsAlwaysDefaultButWithTreatment() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -371,7 +371,7 @@ public void multipleConditionsWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -405,7 +405,7 @@ public void killedTestAlwaysGoesToDefault() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -445,7 +445,7 @@ public void killedTestAlwaysGoesToDefaultHasConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -483,7 +483,7 @@ public void dependencyMatcherOn() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -518,7 +518,7 @@ public void dependencyMatcherOff() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -547,7 +547,7 @@ public void dependencyMatcherControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -577,7 +577,7 @@ public void attributesWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -612,7 +612,7 @@ public void attributesWork2() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -648,7 +648,7 @@ public void attributesGreaterThanNegativeNumber() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -686,7 +686,7 @@ public void attributesForSets() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer ,segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer ,segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -731,7 +731,7 @@ public void labelsArePopulated() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -834,7 +834,7 @@ private void trafficAllocation(String key, int trafficAllocation, int trafficAll NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -888,7 +888,7 @@ public void notInTrafficAllocationDefaultConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -932,7 +932,7 @@ public void matchingBucketingKeysWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -975,7 +975,7 @@ public void matchingBucketingKeysByFlagSetWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1016,7 +1016,7 @@ public void matchingBucketingKeysByFlagSetsWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1054,7 +1054,7 @@ public void impressionMetadataIsPropagated() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1097,7 +1097,7 @@ public void blockUntilReadyDoesNotTimeWhenSdkIsReady() throws TimeoutException, NoopEventsStorageImp.create(), config, ready, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1119,7 +1119,7 @@ public void blockUntilReadyTimesWhenSdkIsNotReady() throws TimeoutException, Int NoopEventsStorageImp.create(), config, ready, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1140,7 +1140,7 @@ public void trackWithValidParameters() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1166,7 +1166,7 @@ public void trackWithInvalidEventTypeIds() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Assert.assertFalse(client.track("validKey", "valid_traffic_type", "")); @@ -1191,7 +1191,7 @@ public void trackWithInvalidTrafficTypeNames() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1213,7 +1213,7 @@ public void trackWithInvalidKeys() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1245,7 +1245,7 @@ public void getTreatmentWithInvalidKeys() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Assert.assertNotEquals(Treatments.CONTROL, client.getTreatment("valid", "split")); @@ -1296,7 +1296,7 @@ public void trackWithProperties() { eventClientMock, config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1420,7 +1420,7 @@ public void clientCannotPerformActionsWhenDestroyed() throws InterruptedExceptio NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1461,7 +1461,7 @@ public void worksAndHasConfigTryKetTreatmentWithKey() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1512,7 +1512,7 @@ public void worksAndHasConfigByFlagSetTryKetTreatmentWithKey() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1561,7 +1561,7 @@ public void worksAndHasConfigByFlagSetsTryKetTreatmentWithKey() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1599,7 +1599,7 @@ public void blockUntilReadyException() throws TimeoutException, InterruptedExcep NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1629,7 +1629,7 @@ public void nullKeyResultsInControlGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(Treatments.CONTROL, client.getTreatments(null, Collections.singletonList("test1")).get("test1")); @@ -1660,7 +1660,7 @@ public void nullSplitsResultsInEmptyGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(0, client.getTreatments("key", null).size()); @@ -1683,7 +1683,7 @@ public void exceptionsResultInControlGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("adil@relateiq.com", Arrays.asList("test1", "test2")); @@ -1717,7 +1717,7 @@ public void getTreatmentsWorks() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("randomKey", Arrays.asList(test, "test2")); @@ -1748,7 +1748,7 @@ public void emptySplitsResultsInNullGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("key", new ArrayList<>()); @@ -1773,7 +1773,7 @@ public void exceptionsResultInControlTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("adil@relateiq.com", Arrays.asList("test1")); @@ -1811,7 +1811,7 @@ public void worksTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("anyKey", Arrays.asList(test, test2)); @@ -1849,7 +1849,7 @@ public void worksOneControlTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1894,7 +1894,7 @@ public void treatmentsWorksAndHasConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map attributes = new HashMap<>(); @@ -1937,7 +1937,7 @@ public void testTreatmentsByFlagSet() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1978,7 +1978,7 @@ public void testTreatmentsByFlagSetInvalid() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertTrue(client.getTreatmentsByFlagSet(RandomStringUtils.random(10), "", new HashMap<>()).isEmpty()); @@ -2022,7 +2022,7 @@ public void testTreatmentsByFlagSets() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); int numKeys = 5; @@ -2078,7 +2078,7 @@ public void treatmentsWorksAndHasConfigFlagSet() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map attributes = new HashMap<>(); @@ -2135,7 +2135,7 @@ public void treatmentsWorksAndHasConfigFlagSets() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map attributes = new HashMap<>(); @@ -2181,7 +2181,7 @@ public void impressionPropertiesTest() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, new FlagSetsFilterImpl(new HashSet<>()) ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java index 5cc6d01d9..e686f5308 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java @@ -174,7 +174,7 @@ private Evaluator buildEvaluatorAndLoadCache(boolean killed, int trafficAllocati SplitCache splitCache = new InMemoryCacheImp(flagSetsFilter); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); RuleBasedSegmentCache ruleBasedSegmentCache = new RuleBasedSegmentCacheInMemoryImp(); - Evaluator evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache); + Evaluator evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, null); Partition partition = new Partition(); partition.treatment = ON_TREATMENT; diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index cf166bd2b..c30209a9e 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -1,8 +1,6 @@ package io.split.engine.evaluator; -import io.split.client.dtos.ConditionType; -import io.split.client.dtos.Partition; -import io.split.client.dtos.Prerequisites; +import io.split.client.dtos.*; import io.split.client.utils.Json; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; @@ -50,7 +48,7 @@ public void before() { _splitCacheConsumer = Mockito.mock(SplitCacheConsumer.class); _segmentCacheConsumer = Mockito.mock(SegmentCacheConsumer.class); _ruleBasedSegmentCacheConsumer = Mockito.mock(RuleBasedSegmentCacheConsumer.class); - _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, null); _matcher = Mockito.mock(CombiningMatcher.class); _evaluationContext = Mockito.mock(EvaluationContext.class); @@ -226,4 +224,73 @@ public void evaluateWithPrerequisites() { assertEquals(Labels.KILLED, result.label); assertEquals(CHANGE_NUMBER, result.changeNumber); } + + @Test + public void evaluateFallbackTreatmentWorks() { + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(null); + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), null); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentsConfiguration); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals("on", result.treatment); + assertEquals("fallback - definition not found", result.label); + + ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, null, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), false, null); + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals("on", result.treatment); + assertEquals("fallback - exception", result.label); + + // using byflag only + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(null); + Mockito.when(_splitCacheConsumer.get("another_name")).thenReturn(null); + fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(null, new HashMap() {{ put(SPLIT_NAME, new FallbackTreatment("off")); }} ); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentsConfiguration); + + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals("off", result.treatment); + assertEquals("fallback - definition not found", result.label); + + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, "another_name", null); + assertEquals("control", result.treatment); + assertEquals("definition not found", result.label); + + split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, null, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), false, null); + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals("off", result.treatment); + assertEquals("fallback - exception", result.label); + + split = new ParsedSplit("another_name", 0, false, DEFAULT_TREATMENT_VALUE, _conditions, null, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), false, null); + Mockito.when(_splitCacheConsumer.get("another_name")).thenReturn(split); + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, "another_name", null); + assertEquals("control", result.treatment); + assertEquals("exception", result.label); + + // with byflag + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(null); + Mockito.when(_splitCacheConsumer.get("another_name")).thenReturn(null); + fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), new HashMap() {{ put(SPLIT_NAME, new FallbackTreatment("off")); }} ); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentsConfiguration); + + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals("off", result.treatment); + assertEquals("fallback - definition not found", result.label); + + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, "another_name", null); + assertEquals("on", result.treatment); + assertEquals("fallback - definition not found", result.label); + + split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, null, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), false, null); + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals("off", result.treatment); + assertEquals("fallback - exception", result.label); + + split = new ParsedSplit("another_name", 0, false, DEFAULT_TREATMENT_VALUE, _conditions, null, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), false, null); + Mockito.when(_splitCacheConsumer.get("another_name")).thenReturn(split); + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, "another_name", null); + assertEquals("on", result.treatment); + assertEquals("fallback - exception", result.label); + } } \ No newline at end of file From 455cf279d4475808f0b188572352f919944b5a72 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:46:09 -0700 Subject: [PATCH 2/6] Update client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java Co-authored-by: nmayorsplit <104373752+nmayorsplit@users.noreply.github.com> --- .../src/main/java/io/split/engine/evaluator/EvaluatorImp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index 9dd718194..e08848eb2 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -177,7 +177,7 @@ private String getConfig(ParsedSplit parsedSplit, String returnedTreatment) { } private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, String bucketingKey, Map attributes, - ParsedSplit parsedSplit, String feature_name) { + ParsedSplit parsedSplit, String featureName) { try { if (parsedSplit == null) { return checkFallbackTreatments(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND, feature_name, null, _fallbackTreatmentsConfiguration); From 1f3e613ad767c4e2136514a91f5707ad02226aa8 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:46:15 -0700 Subject: [PATCH 3/6] Update client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java Co-authored-by: nmayorsplit <104373752+nmayorsplit@users.noreply.github.com> --- .../src/main/java/io/split/engine/evaluator/EvaluatorImp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index e08848eb2..4e192944b 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -180,7 +180,7 @@ private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, St ParsedSplit parsedSplit, String featureName) { try { if (parsedSplit == null) { - return checkFallbackTreatments(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND, feature_name, null, _fallbackTreatmentsConfiguration); + return checkFallbackTreatments(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND, featureName, null, _fallbackTreatmentsConfiguration); } return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { From 6004e5508eb5c460b9a70167cb8e1f7006216fdf Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:46:22 -0700 Subject: [PATCH 4/6] Update client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java Co-authored-by: nmayorsplit <104373752+nmayorsplit@users.noreply.github.com> --- .../src/main/java/io/split/engine/evaluator/EvaluatorImp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index 4e192944b..5dc28e6ec 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -185,7 +185,7 @@ private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, St return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { _log.error("Evaluator Exception", e.wrappedException()); - return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, feature_name, e.changeNumber(), _fallbackTreatmentsConfiguration); + return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, featureName, e.changeNumber(), _fallbackTreatmentsConfiguration); } catch (Exception e) { _log.error("Evaluator Exception", e); return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, feature_name, null, _fallbackTreatmentsConfiguration); From cda0248787a019af7a64b78fd8d2d8e5e3c1374e Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:46:28 -0700 Subject: [PATCH 5/6] Update client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java Co-authored-by: nmayorsplit <104373752+nmayorsplit@users.noreply.github.com> --- .../src/main/java/io/split/engine/evaluator/EvaluatorImp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index 5dc28e6ec..84445cd4a 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -188,7 +188,7 @@ private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, St return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, featureName, e.changeNumber(), _fallbackTreatmentsConfiguration); } catch (Exception e) { _log.error("Evaluator Exception", e); - return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, feature_name, null, _fallbackTreatmentsConfiguration); + return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, featureName, null, _fallbackTreatmentsConfiguration); } } From be872d251fe199cbc0240e85e7209a3c3276f754 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 5 Sep 2025 10:21:07 -0700 Subject: [PATCH 6/6] added fallback calculator --- .../split/client/dtos/FallbackTreatment.java | 10 ++++- .../dtos/FallbackTreatmentCalculator.java | 6 +++ .../dtos/FallbackTreatmentCalculatorImp.java | 40 +++++++++++++++++++ .../java/io/split/client/utils/Utils.java | 28 ------------- .../split/engine/evaluator/EvaluatorImp.java | 19 +++++---- .../FallbackTreatmentCalculationImpTest.java | 40 +++++++++++++++++++ .../split/engine/evaluator/EvaluatorTest.java | 9 +++-- 7 files changed, 112 insertions(+), 40 deletions(-) create mode 100644 client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculator.java create mode 100644 client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculatorImp.java create mode 100644 client/src/test/java/io/split/client/dtos/FallbackTreatmentCalculationImpTest.java diff --git a/client/src/main/java/io/split/client/dtos/FallbackTreatment.java b/client/src/main/java/io/split/client/dtos/FallbackTreatment.java index c4f406e4a..542f90157 100644 --- a/client/src/main/java/io/split/client/dtos/FallbackTreatment.java +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatment.java @@ -10,13 +10,19 @@ public class FallbackTreatment { public FallbackTreatment(String treatment, Map config) { _treatment = treatment; _config = config; - _label = "fallback - "; + _label = null; } public FallbackTreatment(String treatment) { _treatment = treatment; _config = null; - _label = "fallback - "; + _label = null; + } + + public FallbackTreatment(String treatment, Map config, String label) { + _treatment = treatment; + _config = config; + _label = label; } public Map getConfig() { diff --git a/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculator.java b/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculator.java new file mode 100644 index 000000000..b172a1cb2 --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculator.java @@ -0,0 +1,6 @@ +package io.split.client.dtos; + +public interface FallbackTreatmentCalculator +{ + FallbackTreatment resolve(String flagName, String label); +} diff --git a/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculatorImp.java b/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculatorImp.java new file mode 100644 index 000000000..c9854d320 --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculatorImp.java @@ -0,0 +1,40 @@ +package io.split.client.dtos; + +import io.split.grammar.Treatments; + +public class FallbackTreatmentCalculatorImp implements FallbackTreatmentCalculator +{ + private final FallbackTreatmentsConfiguration _fallbackTreatmentsConfiguration; + private final String labelPrefix = "fallback - "; + + public FallbackTreatmentCalculatorImp(FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration) { + _fallbackTreatmentsConfiguration = fallbackTreatmentsConfiguration; + } + + public FallbackTreatment resolve(String flagName, String label) { + if (_fallbackTreatmentsConfiguration != null) { + if (_fallbackTreatmentsConfiguration.getByFlagFallbackTreatment() != null + && _fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(flagName) != null) { + return copyWithLabel(_fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(flagName), + resolveLabel(label)); + } + if (_fallbackTreatmentsConfiguration.getGlobalFallbackTreatment() != null) { + return copyWithLabel(_fallbackTreatmentsConfiguration.getGlobalFallbackTreatment(), + resolveLabel(label)); + } + } + + return new FallbackTreatment(Treatments.CONTROL, null, label); + } + + private String resolveLabel(String label) { + if (label == null) { + return null; + } + return labelPrefix + label; + } + + private FallbackTreatment copyWithLabel(FallbackTreatment fallbackTreatment, String label) { + return new FallbackTreatment(fallbackTreatment.getTreatment(), fallbackTreatment.getConfig(), label); + } +} diff --git a/client/src/main/java/io/split/client/utils/Utils.java b/client/src/main/java/io/split/client/utils/Utils.java index 74e17ee14..9a386db55 100644 --- a/client/src/main/java/io/split/client/utils/Utils.java +++ b/client/src/main/java/io/split/client/utils/Utils.java @@ -1,8 +1,6 @@ package io.split.client.utils; import io.split.client.dtos.ChangeDto; -import io.split.client.dtos.FallbackTreatmentsConfiguration; -import io.split.engine.evaluator.EvaluatorImp.TreatmentLabelAndChangeNumber; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; @@ -47,30 +45,4 @@ public static URI appendPath(URI root, String pathToAppend) throws URISyntaxExce public static boolean checkExitConditions(ChangeDto change, long cn) { return change.t < cn && change.t != -1; } - - public static TreatmentLabelAndChangeNumber checkFallbackTreatments(String treatment, String label, - String feature_name, Long changeNumber, - FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration) { - if (fallbackTreatmentsConfiguration != null) { - if (fallbackTreatmentsConfiguration.getByFlagFallbackTreatment() != null - && fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name) != null - && !fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name).getTreatment().isEmpty()) { - return new TreatmentLabelAndChangeNumber( - fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name).getTreatment(), - fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name).getLabel() + label, - changeNumber); - } - - if (fallbackTreatmentsConfiguration.getGlobalFallbackTreatment() != null - && !fallbackTreatmentsConfiguration.getGlobalFallbackTreatment().getTreatment().isEmpty()) { - return new TreatmentLabelAndChangeNumber(fallbackTreatmentsConfiguration.getGlobalFallbackTreatment().getTreatment(), - fallbackTreatmentsConfiguration.getGlobalFallbackTreatment().getLabel() + label, - changeNumber); - } - } - - return new TreatmentLabelAndChangeNumber(treatment, - label, - changeNumber); - } } \ No newline at end of file diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index 84445cd4a..773413b2b 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -1,6 +1,8 @@ package io.split.engine.evaluator; import io.split.client.dtos.ConditionType; +import io.split.client.dtos.FallbackTreatment; +import io.split.client.dtos.FallbackTreatmentCalculator; import io.split.client.dtos.FallbackTreatmentsConfiguration; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.engine.experiments.ParsedCondition; @@ -20,7 +22,6 @@ import java.util.Map; import static com.google.common.base.Preconditions.checkNotNull; -import static io.split.client.utils.Utils.checkFallbackTreatments; public class EvaluatorImp implements Evaluator { private static final Logger _log = LoggerFactory.getLogger(EvaluatorImp.class); @@ -28,15 +29,15 @@ public class EvaluatorImp implements Evaluator { private final SegmentCacheConsumer _segmentCacheConsumer; private final EvaluationContext _evaluationContext; private final SplitCacheConsumer _splitCacheConsumer; - private final FallbackTreatmentsConfiguration _fallbackTreatmentsConfiguration; + private final FallbackTreatmentCalculator _fallbackTreatmentCalculator; public EvaluatorImp(SplitCacheConsumer splitCacheConsumer, SegmentCacheConsumer segmentCache, RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer, - FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration) { + FallbackTreatmentCalculator fallbackTreatmentCalculator) { _splitCacheConsumer = checkNotNull(splitCacheConsumer); _segmentCacheConsumer = checkNotNull(segmentCache); _evaluationContext = new EvaluationContext(this, _segmentCacheConsumer, ruleBasedSegmentCacheConsumer); - _fallbackTreatmentsConfiguration = fallbackTreatmentsConfiguration; + _fallbackTreatmentCalculator = fallbackTreatmentCalculator; } @Override @@ -179,16 +180,20 @@ private String getConfig(ParsedSplit parsedSplit, String returnedTreatment) { private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, String bucketingKey, Map attributes, ParsedSplit parsedSplit, String featureName) { try { + if (parsedSplit == null) { - return checkFallbackTreatments(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND, featureName, null, _fallbackTreatmentsConfiguration); + FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, Labels.DEFINITION_NOT_FOUND); + return new TreatmentLabelAndChangeNumber(fallbackTreatment.getTreatment(), fallbackTreatment.getLabel()); } return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { _log.error("Evaluator Exception", e.wrappedException()); - return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, featureName, e.changeNumber(), _fallbackTreatmentsConfiguration); + FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, Labels.EXCEPTION); + return new TreatmentLabelAndChangeNumber(fallbackTreatment.getTreatment(), fallbackTreatment.getLabel(), e.changeNumber()); } catch (Exception e) { _log.error("Evaluator Exception", e); - return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, featureName, null, _fallbackTreatmentsConfiguration); + FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, Labels.EXCEPTION); + return new TreatmentLabelAndChangeNumber(fallbackTreatment.getTreatment(), fallbackTreatment.getLabel()); } } diff --git a/client/src/test/java/io/split/client/dtos/FallbackTreatmentCalculationImpTest.java b/client/src/test/java/io/split/client/dtos/FallbackTreatmentCalculationImpTest.java new file mode 100644 index 000000000..4e082e007 --- /dev/null +++ b/client/src/test/java/io/split/client/dtos/FallbackTreatmentCalculationImpTest.java @@ -0,0 +1,40 @@ +package io.split.client.dtos; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class FallbackTreatmentCalculationImpTest { + + @Test + public void TestWorks() { + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), null); + FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + assertEquals("on", fallbackTreatmentCalculator.resolve("anyflag", "exception").getTreatment()); + assertEquals("fallback - exception", fallbackTreatmentCalculator.resolve("anyflag", "exception").getLabel()); + + fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), + new HashMap() {{ put("flag", new FallbackTreatment("off")); }} ); + fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + assertEquals("on", fallbackTreatmentCalculator.resolve("anyflag", "exception").getTreatment()); + assertEquals("fallback - exception", fallbackTreatmentCalculator.resolve("anyflag", "exception").getLabel()); + assertEquals("off", fallbackTreatmentCalculator.resolve("flag", "exception").getTreatment()); + assertEquals("fallback - exception", fallbackTreatmentCalculator.resolve("flag", "exception").getLabel()); + + fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(null, + new HashMap() {{ put("flag", new FallbackTreatment("off")); }} ); + fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + assertEquals("control", fallbackTreatmentCalculator.resolve("anyflag", "exception").getTreatment()); + assertEquals("exception", fallbackTreatmentCalculator.resolve("anyflag", "exception").getLabel()); + assertEquals("off", fallbackTreatmentCalculator.resolve("flag", "exception").getTreatment()); + assertEquals("fallback - exception", fallbackTreatmentCalculator.resolve("flag", "exception").getLabel()); + } +} diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index c30209a9e..9ef7343aa 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -229,7 +229,8 @@ public void evaluateWithPrerequisites() { public void evaluateFallbackTreatmentWorks() { Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(null); FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), null); - _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentsConfiguration); + FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculator); EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); assertEquals("on", result.treatment); @@ -245,7 +246,8 @@ public void evaluateFallbackTreatmentWorks() { Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(null); Mockito.when(_splitCacheConsumer.get("another_name")).thenReturn(null); fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(null, new HashMap() {{ put(SPLIT_NAME, new FallbackTreatment("off")); }} ); - _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentsConfiguration); + fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculator); result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); assertEquals("off", result.treatment); @@ -271,7 +273,8 @@ public void evaluateFallbackTreatmentWorks() { Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(null); Mockito.when(_splitCacheConsumer.get("another_name")).thenReturn(null); fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), new HashMap() {{ put(SPLIT_NAME, new FallbackTreatment("off")); }} ); - _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentsConfiguration); + fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculator); result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); assertEquals("off", result.treatment);