diff --git a/client/pom.xml b/client/pom.xml index 3c1162ee..f3506732 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -168,7 +168,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.4.3 + 5.4.4 com.google.code.gson diff --git a/client/src/main/java/io/split/client/CacheUpdaterService.java b/client/src/main/java/io/split/client/CacheUpdaterService.java index 6d5f8a06..63b42663 100644 --- a/client/src/main/java/io/split/client/CacheUpdaterService.java +++ b/client/src/main/java/io/split/client/CacheUpdaterService.java @@ -51,7 +51,7 @@ public void updateCache(Map map) { String treatment = conditions.size() > 0 ? Treatments.CONTROL : localhostSplit.treatment; configurations.put(localhostSplit.treatment, localhostSplit.config); - split = new ParsedSplit(splitName, 0, false, treatment,conditions, LOCALHOST, 0, 100, 0, 0, configurations, new HashSet<>(), true); + split = new ParsedSplit(splitName, 0, false, treatment,conditions, LOCALHOST, 0, 100, 0, 0, configurations, new HashSet<>(), true, null); parsedSplits.removeIf(parsedSplit -> parsedSplit.feature().equals(splitName)); parsedSplits.add(split); } diff --git a/client/src/main/java/io/split/client/api/SplitView.java b/client/src/main/java/io/split/client/api/SplitView.java index a7701327..cc217fe1 100644 --- a/client/src/main/java/io/split/client/api/SplitView.java +++ b/client/src/main/java/io/split/client/api/SplitView.java @@ -1,6 +1,7 @@ package io.split.client.api; import io.split.client.dtos.Partition; +import io.split.client.dtos.Prerequisites; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; @@ -27,6 +28,7 @@ public class SplitView { public List sets; public String defaultTreatment; public boolean impressionsDisabled; + public List prerequisites; public static SplitView fromParsedSplit(ParsedSplit parsedSplit) { SplitView splitView = new SplitView(); @@ -48,6 +50,8 @@ public static SplitView fromParsedSplit(ParsedSplit parsedSplit) { splitView.treatments = new ArrayList(treatments); splitView.configs = parsedSplit.configurations() == null? Collections.emptyMap() : parsedSplit.configurations() ; splitView.impressionsDisabled = parsedSplit.impressionsDisabled(); + splitView.prerequisites = parsedSplit.prerequisitesMatcher() != null ? + parsedSplit.prerequisitesMatcher().getPrerequisites(): new ArrayList<>(); return splitView; } diff --git a/client/src/main/java/io/split/client/dtos/Prerequisites.java b/client/src/main/java/io/split/client/dtos/Prerequisites.java new file mode 100644 index 00000000..644cb5fc --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/Prerequisites.java @@ -0,0 +1,12 @@ +package io.split.client.dtos; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +public class Prerequisites { + @SerializedName("n") + public String featureFlagName; + @SerializedName("ts") + public List treatments; +} diff --git a/client/src/main/java/io/split/client/dtos/Split.java b/client/src/main/java/io/split/client/dtos/Split.java index 866300d3..1b9a01e3 100644 --- a/client/src/main/java/io/split/client/dtos/Split.java +++ b/client/src/main/java/io/split/client/dtos/Split.java @@ -19,6 +19,7 @@ public class Split { public Map configurations; public HashSet sets; public Boolean impressionsDisabled = null; + public List prerequisites; @Override public String toString() { 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 a97902eb..6d31952c 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -86,8 +86,8 @@ private List getFeatureFlagNamesByFlagSets(List flagSets) { private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit, Map attributes) throws ChangeNumberExceptionWrapper { try { + String config = getConfig(parsedSplit, parsedSplit.defaultTreatment()); if (parsedSplit.killed()) { - String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; return new TreatmentLabelAndChangeNumber( parsedSplit.defaultTreatment(), Labels.KILLED, @@ -96,6 +96,17 @@ private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bu parsedSplit.impressionsDisabled()); } + String bk = getBucketingKey(bucketingKey, matchingKey); + + if (!parsedSplit.prerequisitesMatcher().match(matchingKey, bk, attributes, _evaluationContext)) { + return new TreatmentLabelAndChangeNumber( + parsedSplit.defaultTreatment(), + Labels.PREREQUISITES_NOT_MET, + parsedSplit.changeNumber(), + config, + parsedSplit.impressionsDisabled()); + } + /* * There are three parts to a single Feature flag: 1) Whitelists 2) Traffic Allocation * 3) Rollout. The flag inRollout is there to understand when we move into the Rollout @@ -104,11 +115,9 @@ private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bu */ boolean inRollout = false; - String bk = (bucketingKey == null) ? matchingKey : bucketingKey; - for (ParsedCondition parsedCondition : parsedSplit.parsedConditions()) { - if (!inRollout && parsedCondition.conditionType() == ConditionType.ROLLOUT) { + if (checkRollout(inRollout, parsedCondition)) { if (parsedSplit.trafficAllocation() < 100) { // if the traffic allocation is 100%, no need to do anything special. @@ -116,8 +125,7 @@ private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bu if (bucket > parsedSplit.trafficAllocation()) { // out of split - String config = parsedSplit.configurations() != null ? - parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; + config = getConfig(parsedSplit, parsedSplit.defaultTreatment()); return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), Labels.NOT_IN_SPLIT, parsedSplit.changeNumber(), config, parsedSplit.impressionsDisabled()); } @@ -128,7 +136,7 @@ private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bu if (parsedCondition.matcher().match(matchingKey, bucketingKey, attributes, _evaluationContext)) { String treatment = Splitter.getTreatment(bk, parsedSplit.seed(), parsedCondition.partitions(), parsedSplit.algo()); - String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(treatment) : null; + config = getConfig(parsedSplit, treatment); return new TreatmentLabelAndChangeNumber( treatment, parsedCondition.label(), @@ -138,7 +146,8 @@ private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bu } } - String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; + config = getConfig(parsedSplit, parsedSplit.defaultTreatment()); + return new TreatmentLabelAndChangeNumber( parsedSplit.defaultTreatment(), Labels.DEFAULT_RULE, @@ -150,13 +159,24 @@ private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bu } } + private boolean checkRollout(boolean inRollout, ParsedCondition parsedCondition) { + return (!inRollout && parsedCondition.conditionType() == ConditionType.ROLLOUT); + } + + private String getBucketingKey(String bucketingKey, String matchingKey) { + return (bucketingKey == null) ? matchingKey : bucketingKey; + } + + private String getConfig(ParsedSplit parsedSplit, String returnedTreatment) { + return parsedSplit.configurations() != null ? parsedSplit.configurations().get(returnedTreatment) : null; + } + private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, String bucketingKey, Map attributes, ParsedSplit parsedSplit) { try { if (parsedSplit == null) { return new TreatmentLabelAndChangeNumber(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND); } - return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { _log.error("Evaluator Exception", e.wrappedException()); diff --git a/client/src/main/java/io/split/engine/evaluator/Labels.java b/client/src/main/java/io/split/engine/evaluator/Labels.java index ac5a465a..9bda16a8 100644 --- a/client/src/main/java/io/split/engine/evaluator/Labels.java +++ b/client/src/main/java/io/split/engine/evaluator/Labels.java @@ -7,4 +7,5 @@ public class Labels { public static final String DEFINITION_NOT_FOUND = "definition not found"; public static final String EXCEPTION = "exception"; public static final String UNSUPPORTED_MATCHER = "targeting rule type unsupported by sdk"; + public static final String PREREQUISITES_NOT_MET = "prerequisites not met"; } \ No newline at end of file diff --git a/client/src/main/java/io/split/engine/experiments/ParsedSplit.java b/client/src/main/java/io/split/engine/experiments/ParsedSplit.java index a4d52d6a..e202474f 100644 --- a/client/src/main/java/io/split/engine/experiments/ParsedSplit.java +++ b/client/src/main/java/io/split/engine/experiments/ParsedSplit.java @@ -2,6 +2,7 @@ import com.google.common.collect.ImmutableList; import io.split.engine.matchers.AttributeMatcher; +import io.split.engine.matchers.PrerequisitesMatcher; import io.split.engine.matchers.RuleBasedSegmentMatcher; import io.split.engine.matchers.UserDefinedSegmentMatcher; @@ -34,6 +35,7 @@ public class ParsedSplit { private final Map _configurations; private final HashSet _flagSets; private final boolean _impressionsDisabled; + private PrerequisitesMatcher _prerequisitesMatcher; public static ParsedSplit createParsedSplitForTests( String feature, @@ -45,7 +47,8 @@ public static ParsedSplit createParsedSplitForTests( long changeNumber, int algo, HashSet flagSets, - boolean impressionsDisabled + boolean impressionsDisabled, + PrerequisitesMatcher prerequisitesMatcher ) { return new ParsedSplit( feature, @@ -60,7 +63,8 @@ public static ParsedSplit createParsedSplitForTests( algo, null, flagSets, - impressionsDisabled + impressionsDisabled, + prerequisitesMatcher ); } @@ -75,7 +79,8 @@ public static ParsedSplit createParsedSplitForTests( int algo, Map configurations, HashSet flagSets, - boolean impressionsDisabled + boolean impressionsDisabled, + PrerequisitesMatcher prerequisitesMatcher ) { return new ParsedSplit( feature, @@ -90,7 +95,8 @@ public static ParsedSplit createParsedSplitForTests( algo, configurations, flagSets, - impressionsDisabled + impressionsDisabled, + prerequisitesMatcher ); } @@ -107,7 +113,8 @@ public ParsedSplit( int algo, Map configurations, HashSet flagSets, - boolean impressionsDisabled + boolean impressionsDisabled, + PrerequisitesMatcher prerequisitesMatcher ) { _split = feature; _seed = seed; @@ -125,6 +132,7 @@ public ParsedSplit( _configurations = configurations; _flagSets = flagSets; _impressionsDisabled = impressionsDisabled; + _prerequisitesMatcher = prerequisitesMatcher; } public String feature() { @@ -171,6 +179,7 @@ public Map configurations() { public boolean impressionsDisabled() { return _impressionsDisabled; } + public PrerequisitesMatcher prerequisitesMatcher() { return _prerequisitesMatcher; } @Override public int hashCode() { @@ -195,17 +204,20 @@ public boolean equals(Object obj) { if (!(obj instanceof ParsedSplit)) return false; ParsedSplit other = (ParsedSplit) obj; + boolean trafficTypeCond = _trafficTypeName == null ? other._trafficTypeName == null : _trafficTypeName.equals(other._trafficTypeName); + boolean configCond = _configurations == null ? other._configurations == null : _configurations.equals(other._configurations); return _split.equals(other._split) && _seed == other._seed && _killed == other._killed && _defaultTreatment.equals(other._defaultTreatment) && _parsedCondition.equals(other._parsedCondition) - && _trafficTypeName == null ? other._trafficTypeName == null : _trafficTypeName.equals(other._trafficTypeName) + && trafficTypeCond && _changeNumber == other._changeNumber && _algo == other._algo - && _configurations == null ? other._configurations == null : _configurations.equals(other._configurations) - && _impressionsDisabled == other._impressionsDisabled; + && configCond + && _impressionsDisabled == other._impressionsDisabled + && _prerequisitesMatcher == other._prerequisitesMatcher; } @Override @@ -231,6 +243,9 @@ public String toString() { bldr.append(_configurations); bldr.append(", impressionsDisabled:"); bldr.append(_impressionsDisabled); + bldr.append(", prerequisites:"); + bldr.append(_prerequisitesMatcher); + return bldr.toString(); } diff --git a/client/src/main/java/io/split/engine/experiments/SplitParser.java b/client/src/main/java/io/split/engine/experiments/SplitParser.java index 414e5dfe..5771c9ae 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitParser.java +++ b/client/src/main/java/io/split/engine/experiments/SplitParser.java @@ -6,6 +6,7 @@ import io.split.client.dtos.Partition; import io.split.client.dtos.Split; import io.split.engine.matchers.CombiningMatcher; +import io.split.engine.matchers.PrerequisitesMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,6 +69,7 @@ private ParsedSplit parseWithoutExceptionHandling(Split split) { split.algo, split.configurations, split.sets, - split.impressionsDisabled); + split.impressionsDisabled, + new PrerequisitesMatcher(split.prerequisites)); } } \ No newline at end of file diff --git a/client/src/main/java/io/split/engine/matchers/PrerequisitesMatcher.java b/client/src/main/java/io/split/engine/matchers/PrerequisitesMatcher.java new file mode 100644 index 00000000..12278449 --- /dev/null +++ b/client/src/main/java/io/split/engine/matchers/PrerequisitesMatcher.java @@ -0,0 +1,71 @@ +package io.split.engine.matchers; + +import io.split.client.dtos.Prerequisites; +import io.split.engine.evaluator.EvaluationContext; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +public class PrerequisitesMatcher implements Matcher { + private List _prerequisites; + + public PrerequisitesMatcher(List prerequisites) { + _prerequisites = prerequisites; + } + + public List getPrerequisites() { return _prerequisites; } + + @Override + public boolean match(Object matchValue, String bucketingKey, Map attributes, EvaluationContext evaluationContext) { + if (matchValue == null) { + return false; + } + + if (!(matchValue instanceof String)) { + return false; + } + + if (_prerequisites == null) { + return true; + } + + for (Prerequisites prerequisites : _prerequisites) { + String treatment = evaluationContext.getEvaluator().evaluateFeature((String) matchValue, bucketingKey, + prerequisites.featureFlagName, attributes). treatment; + if (!prerequisites.treatments.contains(treatment)) { + return false; + } + } + return true; + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append("prerequisites: "); + if (this._prerequisites != null) { + bldr.append(this._prerequisites.stream().map(pr -> pr.featureFlagName + " " + + pr.treatments.toString()).map(Object::toString).collect(Collectors.joining(", "))); + } + return bldr.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PrerequisitesMatcher that = (PrerequisitesMatcher) o; + + return Objects.equals(_prerequisites, that._prerequisites); + } + + @Override + public int hashCode() { + int result = _prerequisites != null ? _prerequisites.hashCode() : 0; + result = 31 * result + (_prerequisites != null ? _prerequisites.hashCode() : 0); + return result; + } +} diff --git a/client/src/main/java/io/split/storages/memory/InMemoryCacheImp.java b/client/src/main/java/io/split/storages/memory/InMemoryCacheImp.java index 57c63b99..83e9f3b7 100644 --- a/client/src/main/java/io/split/storages/memory/InMemoryCacheImp.java +++ b/client/src/main/java/io/split/storages/memory/InMemoryCacheImp.java @@ -131,7 +131,8 @@ public void kill(String splitName, String defaultTreatment, long changeNumber) { parsedSplit.algo(), parsedSplit.configurations(), parsedSplit.flagSets(), - parsedSplit.impressionsDisabled() + parsedSplit.impressionsDisabled(), + parsedSplit.prerequisitesMatcher() ); _concurrentMap.put(splitName, updatedSplit); diff --git a/client/src/test/java/io/split/client/SplitClientImplTest.java b/client/src/test/java/io/split/client/SplitClientImplTest.java index 1e27e96e..5b56708d 100644 --- a/client/src/test/java/io/split/client/SplitClientImplTest.java +++ b/client/src/test/java/io/split/client/SplitClientImplTest.java @@ -11,6 +11,12 @@ import io.split.client.impressions.ImpressionsManager; import io.split.client.interceptors.FlagSetsFilter; import io.split.client.interceptors.FlagSetsFilterImpl; +import io.split.engine.matchers.PrerequisitesMatcher; +import io.split.engine.matchers.CombiningMatcher; +import io.split.engine.matchers.EqualToMatcher; +import io.split.engine.matchers.GreaterThanOrEqualToMatcher; +import io.split.engine.matchers.AllKeysMatcher; +import io.split.engine.matchers.DependencyMatcher; import io.split.storages.RuleBasedSegmentCacheConsumer; import io.split.storages.SegmentCacheConsumer; import io.split.storages.SplitCacheConsumer; @@ -18,11 +24,6 @@ import io.split.engine.SDKReadinessGates; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; -import io.split.engine.matchers.AllKeysMatcher; -import io.split.engine.matchers.CombiningMatcher; -import io.split.engine.matchers.DependencyMatcher; -import io.split.engine.matchers.EqualToMatcher; -import io.split.engine.matchers.GreaterThanOrEqualToMatcher; import io.split.engine.matchers.collections.ContainsAnyOfSetMatcher; import io.split.engine.matchers.strings.WhitelistMatcher; import io.split.grammar.Treatments; @@ -83,7 +84,7 @@ public void nullKeyResultsInControl() { Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, new HashSet<>(), true); + null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -113,7 +114,7 @@ public void nullTestResultsInControl() { Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, new HashSet<>(), true); + null, 1, 1, new HashSet<>(), true, null); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -167,7 +168,7 @@ public void works() { Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, new HashSet<>(), true); + null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -206,7 +207,7 @@ public void worksNullConfig() { ParsedCondition rollOutToEveryone = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -242,7 +243,7 @@ public void worksAndHasConfig() { Map configurations = new HashMap<>(); configurations.put(Treatments.ON, "{\"size\" : 30}"); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, configurations, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, configurations, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -279,7 +280,7 @@ public void lastConditionIsAlwaysDefault() { ParsedCondition rollOutToEveryone = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new WhitelistMatcher(Lists.newArrayList("adil@codigo.com"))), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -319,7 +320,7 @@ public void lastConditionIsAlwaysDefaultButWithTreatment() { configurations.put(Treatments.OFF, "{\"size\" : 30}"); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - "user", 1, 1, configurations, new HashSet<>(), true); + "user", 1, 1, configurations, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -354,7 +355,7 @@ public void multipleConditionsWork() { ParsedCondition trevor_is_always_shown = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new WhitelistMatcher(Lists.newArrayList("trevor@codigo.com"))), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(adil_is_always_on, pato_is_never_shown, trevor_is_always_shown); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -389,7 +390,7 @@ public void killedTestAlwaysGoesToDefault() { ParsedCondition rollOutToEveryone = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new WhitelistMatcher(Lists.newArrayList("adil@codigo.com"))), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, true, Treatments.OFF, conditions, "user", 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, true, Treatments.OFF, conditions, "user", 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -429,7 +430,7 @@ public void killedTestAlwaysGoesToDefaultHasConfig() { configurations.put(Treatments.OFF, "{\"size\" : 30}"); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, true, Treatments.OFF, conditions, - "user", 1, 1, configurations, new HashSet<>(), true); + "user", 1, 1, configurations, new HashSet<>(), true, null); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -462,11 +463,11 @@ public void dependencyMatcherOn() { ParsedCondition parent_is_on = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(partition(Treatments.ON, 100))); List parent_conditions = Lists.newArrayList(parent_is_on); - ParsedSplit parentSplit = ParsedSplit.createParsedSplitForTests(parent, 123, false, Treatments.OFF, parent_conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parentSplit = ParsedSplit.createParsedSplitForTests(parent, 123, false, Treatments.OFF, parent_conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); ParsedCondition dependent_needs_parent = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new DependencyMatcher(parent, Lists.newArrayList(Treatments.ON))), Lists.newArrayList(partition(Treatments.ON, 100))); List dependent_conditions = Lists.newArrayList(dependent_needs_parent); - ParsedSplit dependentSplit = ParsedSplit.createParsedSplitForTests(dependent, 123, false, Treatments.OFF, dependent_conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit dependentSplit = ParsedSplit.createParsedSplitForTests(dependent, 123, false, Treatments.OFF, dependent_conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -497,11 +498,11 @@ public void dependencyMatcherOff() { ParsedCondition parent_is_on = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(partition(Treatments.ON, 100))); List parent_conditions = Lists.newArrayList(parent_is_on); - ParsedSplit parentSplit = ParsedSplit.createParsedSplitForTests(parent, 123, false, Treatments.OFF, parent_conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parentSplit = ParsedSplit.createParsedSplitForTests(parent, 123, false, Treatments.OFF, parent_conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); ParsedCondition dependent_needs_parent = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new DependencyMatcher(parent, Lists.newArrayList(Treatments.OFF))), Lists.newArrayList(partition(Treatments.ON, 100))); List dependent_conditions = Lists.newArrayList(dependent_needs_parent); - ParsedSplit dependentSplit = ParsedSplit.createParsedSplitForTests(dependent, 123, false, Treatments.OFF, dependent_conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit dependentSplit = ParsedSplit.createParsedSplitForTests(dependent, 123, false, Treatments.OFF, dependent_conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -531,7 +532,7 @@ public void dependencyMatcherControl() { ParsedCondition dependent_needs_parent = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new DependencyMatcher("not-exists", Lists.newArrayList(Treatments.OFF))), Lists.newArrayList(partition(Treatments.OFF, 100))); List dependent_conditions = Lists.newArrayList(dependent_needs_parent); - ParsedSplit dependentSplit = ParsedSplit.createParsedSplitForTests(dependent, 123, false, Treatments.ON, dependent_conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit dependentSplit = ParsedSplit.createParsedSplitForTests(dependent, 123, false, Treatments.ON, dependent_conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -561,7 +562,7 @@ public void attributesWork() { ParsedCondition users_with_age_greater_than_10_are_on = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of("age", new GreaterThanOrEqualToMatcher(10, DataType.NUMBER)), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(adil_is_always_on, users_with_age_greater_than_10_are_on); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -596,7 +597,7 @@ public void attributesWork2() { ParsedCondition age_equal_to_0_should_be_on = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of("age", new EqualToMatcher(0, DataType.NUMBER)), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(age_equal_to_0_should_be_on); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -632,7 +633,7 @@ public void attributesGreaterThanNegativeNumber() { ParsedCondition age_equal_to_0_should_be_on = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of("age", new EqualToMatcher(-20, DataType.NUMBER)), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(age_equal_to_0_should_be_on); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -670,7 +671,7 @@ public void attributesForSets() { ParsedCondition any_of_set = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of("products", new ContainsAnyOfSetMatcher(Lists.newArrayList("sms", "video"))), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(any_of_set); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -714,7 +715,7 @@ public void labelsArePopulated() { ); List conditions = Lists.newArrayList(age_equal_to_0_should_be_on); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); @@ -817,7 +818,7 @@ private void trafficAllocation(String key, int trafficAllocation, int trafficAll List conditions = Lists.newArrayList(whitelistCondition, rollOutToEveryone); ParsedSplit parsedSplit = new ParsedSplit(test, 123, false, Treatments.OFF, conditions, null, 1, - trafficAllocation, trafficAllocationSeed, 1, null, new HashSet<>(), true); + trafficAllocation, trafficAllocationSeed, 1, null, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -869,7 +870,7 @@ public void notInTrafficAllocationDefaultConfig() { List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = new ParsedSplit(test, 123, false, Treatments.OFF, conditions, null, - 1, trafficAllocation, trafficAllocationSeed, 1, configurations, new HashSet<>(), true); + 1, trafficAllocation, trafficAllocationSeed, 1, configurations, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -916,7 +917,7 @@ public void matchingBucketingKeysWork() { ParsedCondition aijaz_should_match = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new WhitelistMatcher(whitelist)), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(aijaz_should_match); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -953,7 +954,7 @@ public void matchingBucketingKeysByFlagSetWork() { ParsedCondition aijaz_should_match = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new WhitelistMatcher(whitelist)), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(aijaz_should_match); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1, new HashSet<>(Arrays.asList("set1")), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1, new HashSet<>(Arrays.asList("set1")), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -994,7 +995,7 @@ public void matchingBucketingKeysByFlagSetsWork() { ParsedCondition aijaz_should_match = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new WhitelistMatcher(whitelist)), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(aijaz_should_match); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1, new HashSet<>(Arrays.asList("set1")), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1, new HashSet<>(Arrays.asList("set1")), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -1037,7 +1038,7 @@ public void impressionMetadataIsPropagated() { ); List conditions = Lists.newArrayList(age_equal_to_0_should_be_on); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -1229,7 +1230,7 @@ public void getTreatmentWithInvalidKeys() { ParsedCondition rollOutToEveryone = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -1388,7 +1389,7 @@ public void clientCannotPerformActionsWhenDestroyed() throws InterruptedExceptio ParsedCondition rollOutToEveryone = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -1445,7 +1446,7 @@ public void worksAndHasConfigTryKetTreatmentWithKey() { configurations.put(Treatments.ON, "{\"size\" : 30}"); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, configurations, new HashSet<>(), true); + null, 1, 1, configurations, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -1490,7 +1491,7 @@ public void worksAndHasConfigByFlagSetTryKetTreatmentWithKey() { configurations.put(Treatments.ON, "{\"size\" : 30}"); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, configurations, new HashSet<>(Arrays.asList("set1")), true); + null, 1, 1, configurations, new HashSet<>(Arrays.asList("set1")), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -1539,7 +1540,7 @@ public void worksAndHasConfigByFlagSetsTryKetTreatmentWithKey() { configurations.put(Treatments.ON, "{\"size\" : 30}"); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, configurations, new HashSet<>(Arrays.asList("set1")), true); + null, 1, 1, configurations, new HashSet<>(Arrays.asList("set1")), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -1582,7 +1583,7 @@ public void blockUntilReadyException() throws TimeoutException, InterruptedExcep Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, new HashSet<>(), true); + null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -1612,7 +1613,7 @@ public void nullKeyResultsInControlGetTreatments() { Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, new HashSet<>(), true); + null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); Map splits = new HashMap<>(); splits.put(test, parsedSplit); SDKReadinessGates gates = mock(SDKReadinessGates.class); @@ -1643,7 +1644,7 @@ public void nullSplitsResultsInEmptyGetTreatments() { Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, new HashSet<>(), true); + null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); Map splits = new HashMap<>(); splits.put(test, parsedSplit); SDKReadinessGates gates = mock(SDKReadinessGates.class); @@ -1699,7 +1700,7 @@ public void getTreatmentsWorks() { ParsedCondition rollOutToEveryone = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); Map splits = new HashMap<>(); splits.put(test, parsedSplit); SDKReadinessGates gates = mock(SDKReadinessGates.class); @@ -1731,7 +1732,7 @@ public void emptySplitsResultsInNullGetTreatments() { String test = "test1"; ParsedCondition rollOutToEveryone = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); Map splits = new HashMap<>(); splits.put(test, parsedSplit); SDKReadinessGates gates = mock(SDKReadinessGates.class); @@ -1789,9 +1790,9 @@ public void worksTreatments() { Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, new HashSet<>(), true); + null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); ParsedSplit parsedSplit2 = ParsedSplit.createParsedSplitForTests(test2, 123, false, Treatments.OFF, conditions, - null, 1, 1, new HashSet<>(), true); + null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); Map parsedSplits = new HashMap<>(); parsedSplits.put(test, parsedSplit); parsedSplits.put(test2, parsedSplit2); @@ -1830,7 +1831,7 @@ public void worksOneControlTreatments() { ParsedCondition rollOutToEveryone = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(), true, new PrerequisitesMatcher(null)); Map parsedSplits = new HashMap<>(); parsedSplits.put(test, parsedSplit); @@ -1877,7 +1878,7 @@ public void treatmentsWorksAndHasConfig() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, configurations, new HashSet<>(), true); + null, 1, 1, configurations, new HashSet<>(), true, new PrerequisitesMatcher(null)); Map parsedSplits = new HashMap<>(); parsedSplits.put(test, parsedSplit); SDKReadinessGates gates = mock(SDKReadinessGates.class); @@ -1914,7 +1915,7 @@ public void testTreatmentsByFlagSet() { Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, new HashSet<>(Arrays.asList("set1", "set2")), true); + null, 1, 1, new HashSet<>(Arrays.asList("set1", "set2")), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -1961,7 +1962,7 @@ public void testTreatmentsByFlagSetInvalid() { Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, new HashSet<>(Arrays.asList("set1", "set2")), true); + null, 1, 1, new HashSet<>(Arrays.asList("set1", "set2")), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -1992,9 +1993,9 @@ public void testTreatmentsByFlagSets() { Lists.newArrayList(partition("on", 100))); List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, new HashSet<>(Arrays.asList("set1", "set2")), true); + null, 1, 1, new HashSet<>(Arrays.asList("set1", "set2")), true, new PrerequisitesMatcher(null)); ParsedSplit parsedSplit2 = ParsedSplit.createParsedSplitForTests(test2, 123, false, Treatments.OFF, conditions, - null, 1, 1, new HashSet<>(Arrays.asList("set3", "set4")), true); + null, 1, 1, new HashSet<>(Arrays.asList("set3", "set4")), true, new PrerequisitesMatcher(null)); SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); @@ -2054,7 +2055,7 @@ public void treatmentsWorksAndHasConfigFlagSet() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, configurations, new HashSet<>(Arrays.asList("set1")), true); + null, 1, 1, configurations, new HashSet<>(Arrays.asList("set1")), true, new PrerequisitesMatcher(null)); Map parsedSplits = new HashMap<>(); parsedSplits.put(test, parsedSplit); @@ -2111,7 +2112,7 @@ public void treatmentsWorksAndHasConfigFlagSets() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, - null, 1, 1, configurations, new HashSet<>(Arrays.asList("set1")), true); + null, 1, 1, configurations, new HashSet<>(Arrays.asList("set1")), true, new PrerequisitesMatcher(null)); Map parsedSplits = new HashMap<>(); parsedSplits.put(test, parsedSplit); @@ -2158,7 +2159,7 @@ public void impressionPropertiesTest() { ); List conditions = Lists.newArrayList(age_equal_to_0_should_be_on); - ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(Arrays.asList("set")), true); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(Arrays.asList("set")), true, new PrerequisitesMatcher(null)); Map parsedSplits = new HashMap<>(); parsedSplits.put(test, parsedSplit); diff --git a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java index 82f65602..bba82452 100644 --- a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java +++ b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java @@ -528,7 +528,7 @@ public void splitClientMultiFactory() throws Exception { .until(() -> "on_whitelist".equals(client2.getTreatment("admin", "push_test"))); Awaitility.await() - .atMost(50L, TimeUnit.SECONDS) + .atMost(100L, TimeUnit.SECONDS) .until(() -> "on_whitelist".equals(client3.getTreatment("admin", "push_test"))); Awaitility.await() @@ -1101,6 +1101,80 @@ public MockResponse dispatch(RecordedRequest request) { Assert.assertTrue(check2); } + @Test + public void getTreatmentWithPrerequisites() throws Exception { + String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/splits_prereq.json")), StandardCharsets.UTF_8); + List allRequests = new ArrayList<>(); + Dispatcher dispatcher = new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + allRequests.add(request); + switch (request.getPath()) { + case "/api/splitChanges?s=1.3&since=-1&rbSince=-1": + return new MockResponse().setResponseCode(200).setBody(splits); + case "/api/splitChanges?s=1.3&since=1585948850109&rbSince=1585948850109": + return new MockResponse().setResponseCode(200).setBody("{\"ff\":{\"d\": [], \"s\":1585948850109, \"t\":1585948850109},\"rbs\":{\"d\":[],\"t\":1585948850109,\"s\":1585948850109}}"); + case "/api/segmentChanges/segment-test?since=-1": + return new MockResponse().setResponseCode(200).setBody("{\"name\":\"segment-test\",\"added\":[\"user-1\"],\"removed\":[],\"since\":-1,\"till\":-1}"); + case "/api/testImpressions/bulk": + return new MockResponse().setResponseCode(200); + case "/api/testImpressions/count": + return new MockResponse().setResponseCode(200); + case "/v1/keys/ss": + return new MockResponse().setResponseCode(200); + case "/v1/metrics/usage": + return new MockResponse().setResponseCode(200); + case "/v1/metrics/config": + return new MockResponse().setResponseCode(200); + } + return new MockResponse().setResponseCode(404); + } + }; + + MockWebServer splitServer = new MockWebServer(); + splitServer.setDispatcher(dispatcher); + splitServer.start(); + String serverURL = String.format("http://%s:%s", splitServer.getHostName(), splitServer.getPort()); + + SplitClientConfig config = SplitClientConfig.builder() + .setBlockUntilReadyTimeout(10000) + .endpoint(serverURL, serverURL) + .telemetryURL(serverURL + "/v1") + .authServiceURL(String.format("%s/api/auth/enabled", serverURL)) + .streamingEnabled(false) + .featuresRefreshRate(5) + .impressionsMode(ImpressionsManager.Mode.DEBUG) + .build(); + + SplitFactory factory = SplitFactoryBuilder.build("fake-api-token", config); + SplitClient client = factory.client(); + client.blockUntilReady(); + + Assert.assertEquals("on", client.getTreatment("bilal@split.io", "test_prereq", new HashMap() {{ + put("email", "bilal@@split.io"); + }})); + Assert.assertEquals("def_treatment", client.getTreatment("bilal@split.io", "test_prereq")); + Assert.assertEquals("def_treatment", client.getTreatment("mauro@split.io", "test_prereq", new HashMap() {{ + put("email", "mauro@@split.io"); + }})); + Assert.assertEquals("on", client.getTreatment("pato@split.io", "test_prereq", new HashMap() {{ + put("email", "pato@@split.io"); + }})); + + Assert.assertEquals("on_whitelist", client.getTreatment("bilal@split.io", "prereq_chain", new HashMap() {{ + put("email", "bilal@@split.io"); + }})); + Assert.assertEquals("on", client.getTreatment("pato@split.io", "prereq_chain", new HashMap() {{ + put("email", "pato@@split.io"); + }})); + Assert.assertEquals("on_default", client.getTreatment("mauro@split.io", "prereq_chain", new HashMap() {{ + put("email", "mauro@@split.io"); + }})); + + client.destroy(); + splitServer.shutdown(); + } + private SSEMockServer buildSSEMockServer(SSEMockServer.SseEventQueue eventQueue) { return new SSEMockServer(eventQueue, (token, version, channel) -> { if (!"1.1".equals(version)) { diff --git a/client/src/test/java/io/split/client/SplitManagerImplTest.java b/client/src/test/java/io/split/client/SplitManagerImplTest.java index 4843bd81..f3c04454 100644 --- a/client/src/test/java/io/split/client/SplitManagerImplTest.java +++ b/client/src/test/java/io/split/client/SplitManagerImplTest.java @@ -3,6 +3,7 @@ import com.google.common.collect.Lists; import io.split.client.api.SplitView; +import io.split.client.dtos.Prerequisites; import io.split.client.dtos.Split; import io.split.client.dtos.SplitChange; import io.split.client.utils.Json; @@ -13,6 +14,7 @@ import io.split.engine.experiments.SplitParser; import io.split.engine.matchers.AllKeysMatcher; import io.split.engine.matchers.CombiningMatcher; +import io.split.engine.matchers.PrerequisitesMatcher; import io.split.grammar.Treatments; import io.split.storages.SplitCacheConsumer; import io.split.telemetry.storage.InMemoryTelemetryStorage; @@ -66,7 +68,11 @@ public void splitCallWithExistentSplit() { String existent = "existent"; SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); - ParsedSplit response = ParsedSplit.createParsedSplitForTests("FeatureName", 123, true, "off", Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1, new HashSet<>(), false); + Prerequisites prereq = new Prerequisites(); + prereq.featureFlagName = "feature1"; + prereq.treatments = Lists.newArrayList("on"); + ParsedSplit response = ParsedSplit.createParsedSplitForTests("FeatureName", 123, true, "off", Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1, new HashSet<>(), false, + new PrerequisitesMatcher(Lists.newArrayList(prereq))); when(splitCacheConsumer.get(existent)).thenReturn(response); SplitManagerImpl splitManager = new SplitManagerImpl(splitCacheConsumer, @@ -81,6 +87,7 @@ public void splitCallWithExistentSplit() { Assert.assertEquals("off", theOne.treatments.get(0)); Assert.assertEquals(0, theOne.configs.size()); Assert.assertEquals("off", theOne.defaultTreatment); + Assert.assertEquals(Lists.newArrayList(prereq), theOne.prerequisites); } @Test @@ -92,7 +99,7 @@ public void splitCallWithExistentSplitAndConfigs() { Map configurations = new HashMap<>(); configurations.put(Treatments.OFF, "{\"size\" : 30}"); - ParsedSplit response = ParsedSplit.createParsedSplitForTests("FeatureName", 123, true, "off", Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1, configurations, new HashSet<>(), false); + ParsedSplit response = ParsedSplit.createParsedSplitForTests("FeatureName", 123, true, "off", Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1, configurations, new HashSet<>(), false, null); when(splitCacheConsumer.get(existent)).thenReturn(response); SplitManagerImpl splitManager = new SplitManagerImpl(splitCacheConsumer, @@ -128,7 +135,7 @@ public void splitsCallWithSplit() { List parsedSplits = Lists.newArrayList(); SDKReadinessGates gates = mock(SDKReadinessGates.class); when(gates.isSDKReady()).thenReturn(false); - ParsedSplit response = ParsedSplit.createParsedSplitForTests("FeatureName", 123, true, "off", Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1, new HashSet<>(), false); + ParsedSplit response = ParsedSplit.createParsedSplitForTests("FeatureName", 123, true, "off", Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1, new HashSet<>(), false, null); parsedSplits.add(response); when(splitCacheConsumer.getAll()).thenReturn(parsedSplits); @@ -203,7 +210,7 @@ public void splitCallWithExistentSets() { String existent = "existent"; SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); ParsedSplit response = ParsedSplit.createParsedSplitForTests("FeatureName", 123, true, "off", - Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1, new HashSet<>(Arrays.asList("set1", "set2", "set3")), false); + Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1, new HashSet<>(Arrays.asList("set1", "set2", "set3")), false, null); when(splitCacheConsumer.get(existent)).thenReturn(response); SplitManagerImpl splitManager = new SplitManagerImpl(splitCacheConsumer, @@ -218,7 +225,7 @@ public void splitCallWithEmptySets() { String existent = "existent"; SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); ParsedSplit response = ParsedSplit.createParsedSplitForTests("FeatureName", 123, true, "off", - Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1, null, false); + Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1, null, false, null); when(splitCacheConsumer.get(existent)).thenReturn(response); SplitManagerImpl splitManager = new SplitManagerImpl(splitCacheConsumer, 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 cb92bc17..5cc6d01d 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java @@ -11,6 +11,7 @@ import io.split.engine.experiments.ParsedSplit; import io.split.engine.matchers.AttributeMatcher; import io.split.engine.matchers.CombiningMatcher; +import io.split.engine.matchers.PrerequisitesMatcher; import io.split.engine.matchers.RuleBasedSegmentMatcher; import io.split.engine.matchers.strings.EndsWithAnyOfMatcher; import io.split.engine.matchers.strings.WhitelistMatcher; @@ -200,11 +201,11 @@ private Evaluator buildEvaluatorAndLoadCache(boolean killed, int trafficAllocati List conditions = Lists.newArrayList(whitelistCondition, rollOutCondition); List conditionsForRBS = Lists.newArrayList(ruleBasedSegmentCondition, rollOutCondition); - ParsedSplit parsedSplit1 = new ParsedSplit("split_1", 0, false, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366551, 100, 0, 2, null, new HashSet<>(), true); - ParsedSplit parsedSplit2 = new ParsedSplit("split_2", 0, true, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366552, 100, 0, 2, null, new HashSet<>(), true); - ParsedSplit parsedSplit3 = new ParsedSplit("split_3", 0, false, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366554, 100, 0, 2, null, new HashSet<>(), true); - ParsedSplit parsedSplit4 = new ParsedSplit("split_test", 0, killed, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366555, trafficAllocation, 0, 2, null, new HashSet<>(), true); - ParsedSplit parsedSplit5 = new ParsedSplit("split_5", 0, false, DEFAULT_TREATMENT_VALUE, conditionsForRBS, TRAFFIC_TYPE_VALUE, 223366554, 100, 0, 2, null, new HashSet<>(), true); + ParsedSplit parsedSplit1 = new ParsedSplit("split_1", 0, false, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366551, 100, 0, 2, null, new HashSet<>(), true, new PrerequisitesMatcher(null)); + ParsedSplit parsedSplit2 = new ParsedSplit("split_2", 0, true, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366552, 100, 0, 2, null, new HashSet<>(), true, new PrerequisitesMatcher(null)); + ParsedSplit parsedSplit3 = new ParsedSplit("split_3", 0, false, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366554, 100, 0, 2, null, new HashSet<>(), true, new PrerequisitesMatcher(null)); + ParsedSplit parsedSplit4 = new ParsedSplit("split_test", 0, killed, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366555, trafficAllocation, 0, 2, null, new HashSet<>(), true, new PrerequisitesMatcher(null)); + ParsedSplit parsedSplit5 = new ParsedSplit("split_5", 0, false, DEFAULT_TREATMENT_VALUE, conditionsForRBS, TRAFFIC_TYPE_VALUE, 223366554, 100, 0, 2, null, new HashSet<>(), true, new PrerequisitesMatcher(null)); splitCache.putMany(Stream.of(parsedSplit1, parsedSplit2, parsedSplit3, parsedSplit4, parsedSplit5).collect(Collectors.toList())); ParsedRuleBasedSegment parsedRuleBasedSegment = new ParsedRuleBasedSegment("sample_rule_based_segment", 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 2fe2d83e..cf166bd2 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -2,9 +2,12 @@ import io.split.client.dtos.ConditionType; import io.split.client.dtos.Partition; +import io.split.client.dtos.Prerequisites; +import io.split.client.utils.Json; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; import io.split.engine.matchers.CombiningMatcher; +import io.split.engine.matchers.PrerequisitesMatcher; import io.split.storages.RuleBasedSegmentCacheConsumer; import io.split.storages.SegmentCacheConsumer; import io.split.storages.SplitCacheConsumer; @@ -68,7 +71,7 @@ public void evaluateWhenSplitNameDoesNotExistReturnControl() { @Test public void evaluateWhenSplitIsKilledReturnDefaultTreatment() { - ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, true, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2, new HashSet<>(), true); + ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, true, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2, new HashSet<>(), true, new PrerequisitesMatcher(null)); Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); @@ -80,7 +83,7 @@ public void evaluateWhenSplitIsKilledReturnDefaultTreatment() { @Test public void evaluateWithoutConditionsReturnDefaultTreatment() { - ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2, new HashSet<>(), true); + ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2, new HashSet<>(), true, new PrerequisitesMatcher(null)); Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); @@ -99,7 +102,7 @@ public void evaluateWithRollOutConditionBucketIsBiggerTrafficAllocationReturnDef ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher,_partitions, TEST_LABEL_VALUE); _conditions.add(condition); - ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 10, 12, 2, _configurations, new HashSet<>(), true); + ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 10, 12, 2, _configurations, new HashSet<>(), true, new PrerequisitesMatcher(null)); Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); Mockito.when(condition.matcher().match(MATCHING_KEY, BUCKETING_KEY, null, _evaluationContext)).thenReturn(true); @@ -120,7 +123,7 @@ public void evaluateWithRollOutConditionTrafficAllocationIsBiggerBucketReturnTre ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher, _partitions, TEST_LABEL_VALUE); _conditions.add(condition); - ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), true); + ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), true, new PrerequisitesMatcher(null)); Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); Mockito.when(condition.matcher().match(Mockito.anyString(), Mockito.anyString(), Mockito.anyObject(), Mockito.anyObject())).thenReturn(true); @@ -141,7 +144,7 @@ public void evaluateWithWhitelistConditionReturnTreatment() { ParsedCondition condition = new ParsedCondition(ConditionType.WHITELIST, _matcher, _partitions, "test whitelist label"); _conditions.add(condition); - ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), true); + ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), true, new PrerequisitesMatcher(null)); Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); Mockito.when(condition.matcher().match(Mockito.anyString(), Mockito.anyString(), Mockito.anyObject(), Mockito.anyObject())).thenReturn(true); @@ -155,7 +158,7 @@ public void evaluateWithWhitelistConditionReturnTreatment() { @Test public void evaluateWithSets() { - ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2, new HashSet<>(Arrays.asList("set1", "set2")), true); + ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2, new HashSet<>(Arrays.asList("set1", "set2")), true, new PrerequisitesMatcher(null)); List sets = new ArrayList<>(Arrays.asList("set1", "empty_set")); Map> flagSets = new HashMap<>(); flagSets.put("set1", new HashSet<>(Arrays.asList(SPLIT_NAME))); @@ -176,7 +179,7 @@ public void evaluateWithSets() { @Test public void evaluateWithSetsNotHaveFlags() { - ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2, new HashSet<>(Arrays.asList("set1", "set2")), true); + ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2, new HashSet<>(Arrays.asList("set1", "set2")), true, new PrerequisitesMatcher(null)); List sets = new ArrayList<>(Arrays.asList("set2")); Map> flagSets = new HashMap<>(); Mockito.when(_splitCacheConsumer.getNamesByFlagSets(sets)).thenReturn(flagSets); @@ -186,4 +189,41 @@ public void evaluateWithSetsNotHaveFlags() { Map result = _evaluator.evaluateFeaturesByFlagSets(MATCHING_KEY, BUCKETING_KEY, sets, null); Assert.assertTrue(result.isEmpty()); } + + @Test + public void evaluateWithPrerequisites() { + Partition partition = new Partition(); + partition.treatment = TREATMENT_VALUE; + partition.size = 100; + _partitions.add(partition); + ParsedCondition condition = new ParsedCondition(ConditionType.WHITELIST, _matcher, _partitions, "test whitelist label"); + _conditions.add(condition); + List prerequisites = Arrays.asList(Json.fromJson("{\"n\": \"split1\", \"ts\": [\"" + TREATMENT_VALUE + "\"]}", Prerequisites.class)); + + ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), true, new PrerequisitesMatcher(prerequisites)); + ParsedSplit split1 = new ParsedSplit("split1", 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), true, new PrerequisitesMatcher(null)); + + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); + Mockito.when(_splitCacheConsumer.get("split1")).thenReturn(split1); + Mockito.when(condition.matcher().match(Mockito.anyString(), Mockito.anyString(), Mockito.anyObject(), Mockito.anyObject())).thenReturn(true); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals(TREATMENT_VALUE, result.treatment); + assertEquals("test whitelist label", result.label); + assertEquals(CHANGE_NUMBER, result.changeNumber); + + Mockito.when(condition.matcher().match(Mockito.anyString(), Mockito.anyString(), Mockito.anyObject(), Mockito.anyObject())).thenReturn(false); + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals(DEFAULT_TREATMENT_VALUE, result.treatment); + assertEquals(Labels.PREREQUISITES_NOT_MET, result.label); + assertEquals(CHANGE_NUMBER, result.changeNumber); + + // if split is killed, label should be killed. + split = new ParsedSplit(SPLIT_NAME, 0, true, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), true, new PrerequisitesMatcher(prerequisites)); + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals(DEFAULT_TREATMENT_VALUE, result.treatment); + assertEquals(Labels.KILLED, result.label); + assertEquals(CHANGE_NUMBER, result.changeNumber); + } } \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java b/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java index f2cc0cc4..a6c2468a 100644 --- a/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java @@ -88,7 +88,7 @@ private void works(long startingChangeNumber) throws InterruptedException { ParsedCondition expectedParsedCondition = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(ConditionsTestUtil.partition("on", 10))); List expectedListOfMatcherAndSplits = Lists.newArrayList(expectedParsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("" + cache.getChangeNumber(), (int) cache.getChangeNumber(), false, Treatments.OFF, expectedListOfMatcherAndSplits, null, cache.getChangeNumber(), 1, new HashSet<>(), true); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("" + cache.getChangeNumber(), (int) cache.getChangeNumber(), false, Treatments.OFF, expectedListOfMatcherAndSplits, null, cache.getChangeNumber(), 1, new HashSet<>(), true, null); ParsedSplit actual = cache.get("" + cache.getChangeNumber()); Thread.sleep(1000); diff --git a/client/src/test/java/io/split/engine/experiments/SplitParserTest.java b/client/src/test/java/io/split/engine/experiments/SplitParserTest.java index 5b581983..4676a8c3 100644 --- a/client/src/test/java/io/split/engine/experiments/SplitParserTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitParserTest.java @@ -11,11 +11,7 @@ import io.split.client.dtos.Split; import io.split.client.dtos.SplitChange; import io.split.client.dtos.Status; -import io.split.storages.SegmentCache; -import io.split.storages.memory.SegmentCacheInMemoryImpl; -import io.split.client.utils.Json; -import io.split.engine.evaluator.Labels; -import io.split.engine.ConditionsTestUtil; +import io.split.engine.matchers.PrerequisitesMatcher; import io.split.engine.matchers.AttributeMatcher; import io.split.engine.matchers.BetweenMatcher; import io.split.engine.matchers.CombiningMatcher; @@ -23,6 +19,11 @@ import io.split.engine.matchers.GreaterThanOrEqualToMatcher; import io.split.engine.matchers.LessThanOrEqualToMatcher; import io.split.engine.matchers.UserDefinedSegmentMatcher; +import io.split.storages.SegmentCache; +import io.split.storages.memory.SegmentCacheInMemoryImpl; +import io.split.client.utils.Json; +import io.split.engine.evaluator.Labels; +import io.split.engine.ConditionsTestUtil; import io.split.engine.matchers.collections.ContainsAllOfSetMatcher; import io.split.engine.matchers.collections.ContainsAnyOfSetMatcher; import io.split.engine.matchers.collections.EqualToSetMatcher; @@ -94,11 +95,12 @@ public void works() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>(), false); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); - Assert.assertEquals(actual, expected); + compareParsed(actual, expected); assertTrue(expected.hashCode() != 0); assertTrue(expected.equals(expected)); + Assert.assertEquals(expected.toString(), actual.toString()); } @Test @@ -137,9 +139,23 @@ public void worksWithConfig() { List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, - listOfMatcherAndSplits, "user", 1, 1, configurations, new HashSet<>(), false); + listOfMatcherAndSplits, "user", 1, 1, configurations, new HashSet<>(), false, new PrerequisitesMatcher(null)); + + Assert.assertEquals(actual.parsedConditions(), expected.parsedConditions()); + Assert.assertEquals(actual.feature(), expected.feature()); + Assert.assertEquals(actual.changeNumber(), expected.changeNumber()); + Assert.assertEquals(actual.defaultTreatment(), expected.defaultTreatment()); + Assert.assertEquals(actual.killed(), expected.killed()); + Assert.assertEquals(actual.impressionsDisabled(), expected.impressionsDisabled()); + Assert.assertEquals(null, actual.flagSets()); + Assert.assertEquals(actual.algo(), expected.algo()); + Assert.assertEquals(actual.seed(), expected.seed()); + Assert.assertEquals(actual.trafficAllocation(), expected.trafficAllocation()); + Assert.assertEquals(actual.trafficAllocationSeed(), expected.trafficAllocationSeed()); + Assert.assertEquals(actual.getSegmentsNames(), expected.getSegmentsNames()); + Assert.assertEquals(actual.getRuleBasedSegmentsNames(), expected.getRuleBasedSegmentsNames()); + Assert.assertEquals(actual.prerequisitesMatcher().toString(), expected.prerequisitesMatcher().toString()); - Assert.assertEquals(actual, expected); Assert.assertEquals(actual.configurations().get("on"), configurations.get("on")); } @@ -173,12 +189,12 @@ public void worksForTwoConditions() { ParsedSplit actual = parser.parse(split); ParsedCondition parsedCondition1 = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new UserDefinedSegmentMatcher(EMPLOYEES)), fullyRollout); - ParsedCondition parsedCondition2 = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new UserDefinedSegmentMatcher(EMPLOYEES)), turnOff); + ParsedCondition parsedCondition2 = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new UserDefinedSegmentMatcher(SALES_PEOPLE)), turnOff); List listOfParsedConditions = Lists.newArrayList(parsedCondition1, parsedCondition2); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfParsedConditions, "user", 1, 1, new HashSet<>(), false); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfParsedConditions, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); - Assert.assertEquals(actual, expected); + compareParsed(actual, expected); } @Test @@ -245,9 +261,9 @@ public void worksWithAttributes() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>(), false); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); - Assert.assertEquals(actual, expected); + compareParsed(actual, expected); } @Test @@ -278,9 +294,9 @@ public void lessThanOrEqualTo() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>(), false); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); - Assert.assertEquals(actual, expected); + compareParsed(actual, expected); } @Test @@ -310,9 +326,9 @@ public void equalTo() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>(), false); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); - Assert.assertEquals(actual, expected); + compareParsed(actual, expected); } @Test @@ -341,9 +357,9 @@ public void equalToNegativeNumber() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>(), false); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); - Assert.assertEquals(actual, expected); + compareParsed(actual, expected); } @Test @@ -377,9 +393,9 @@ public void between() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>(), false); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); - Assert.assertEquals(actual, expected); + compareParsed(actual, expected); } @Test @@ -693,9 +709,27 @@ public void setMatcherTest(Condition c, io.split.engine.matchers.Matcher m) { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("splitName", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, new HashSet<>(), false); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("splitName", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); + + compareParsed(actual, expected); + } - Assert.assertEquals(actual, expected); + private void compareParsed(ParsedSplit actual, ParsedSplit expected) { + Assert.assertEquals(actual.getRuleBasedSegmentsNames(), expected.getRuleBasedSegmentsNames()); + Assert.assertEquals(actual.seed(), expected.seed()); + Assert.assertEquals(actual.algo(), expected.algo()); + Assert.assertEquals(actual.trafficAllocationSeed(), expected.trafficAllocationSeed()); + Assert.assertEquals(actual.flagSets(), expected.flagSets()); + Assert.assertEquals(actual.parsedConditions(), expected.parsedConditions()); + Assert.assertEquals(actual.trafficAllocation(), expected.trafficAllocation()); + Assert.assertEquals(actual.getSegmentsNames(), expected.getSegmentsNames()); + Assert.assertEquals(actual.impressionsDisabled(), expected.impressionsDisabled()); + Assert.assertEquals(actual.killed(), expected.killed()); + Assert.assertEquals(actual.defaultTreatment(), expected.defaultTreatment()); + Assert.assertEquals(actual.changeNumber(), expected.changeNumber()); + Assert.assertEquals(actual.feature(), expected.feature()); + Assert.assertEquals(actual.configurations(), expected.configurations()); + Assert.assertEquals(actual.prerequisitesMatcher().toString(), expected.prerequisitesMatcher().toString()); } private Split makeSplit(String name, int seed, List conditions, long changeNumber) { @@ -715,6 +749,7 @@ private Split makeSplit(String name, int seed, List conditions, long split.changeNumber = changeNumber; split.algo = 1; split.configurations = configurations; + split.prerequisites = new ArrayList<>(); return split; } diff --git a/client/src/test/java/io/split/engine/matchers/PrerequisitesMatcherTest.java b/client/src/test/java/io/split/engine/matchers/PrerequisitesMatcherTest.java new file mode 100644 index 00000000..4fe92d04 --- /dev/null +++ b/client/src/test/java/io/split/engine/matchers/PrerequisitesMatcherTest.java @@ -0,0 +1,55 @@ +package io.split.engine.matchers; + +import io.split.client.dtos.Prerequisites; +import io.split.client.utils.Json; +import io.split.engine.evaluator.EvaluationContext; +import io.split.engine.evaluator.Evaluator; +import io.split.engine.evaluator.EvaluatorImp; +import io.split.storages.RuleBasedSegmentCache; +import io.split.storages.SegmentCache; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.List; + +/** + * Tests for Prerequisites matcher + */ +public class PrerequisitesMatcherTest { + + @Test + public void works() { + Evaluator evaluator = Mockito.mock(Evaluator.class); + EvaluationContext evaluationContext = new EvaluationContext(evaluator, Mockito.mock(SegmentCache.class), Mockito.mock(RuleBasedSegmentCache.class)); + List prerequisites = Arrays.asList(Json.fromJson("{\"n\": \"split1\", \"ts\": [\"on\"]}", Prerequisites.class), Json.fromJson("{\"n\": \"split2\", \"ts\": [\"off\"]}", Prerequisites.class)); + PrerequisitesMatcher matcher = new PrerequisitesMatcher(prerequisites); + Assert.assertEquals("prerequisites: split1 [on], split2 [off]", matcher.toString()); + PrerequisitesMatcher matcher2 = new PrerequisitesMatcher(prerequisites); + Assert.assertTrue(matcher.equals(matcher2)); + Assert.assertTrue(matcher.hashCode() != 0); + + Mockito.when(evaluator.evaluateFeature("user", "user", "split1", null)).thenReturn(new EvaluatorImp.TreatmentLabelAndChangeNumber("on", "")); + Mockito.when(evaluator.evaluateFeature("user", "user", "split2", null)).thenReturn(new EvaluatorImp.TreatmentLabelAndChangeNumber("off", "")); + Assert.assertTrue(matcher.match("user", "user", null, evaluationContext)); + + Mockito.when(evaluator.evaluateFeature("user", "user", "split2", null)).thenReturn(new EvaluatorImp.TreatmentLabelAndChangeNumber("on", "")); + Assert.assertFalse(matcher.match("user", "user", null, evaluationContext)); + } + + @Test + public void invalidParams() { + Evaluator evaluator = Mockito.mock(Evaluator.class); + EvaluationContext evaluationContext = new EvaluationContext(evaluator, Mockito.mock(SegmentCache.class), Mockito.mock(RuleBasedSegmentCache.class)); + + List prerequisites = Arrays.asList(Json.fromJson("{\"n\": \"split1\", \"ts\": [\"on\"]}", Prerequisites.class), Json.fromJson("{\"n\": \"split2\", \"ts\": [\"off\"]}", Prerequisites.class)); + PrerequisitesMatcher matcher = new PrerequisitesMatcher(prerequisites); + Mockito.when(evaluator.evaluateFeature("user", "user", "split1", null)).thenReturn(new EvaluatorImp.TreatmentLabelAndChangeNumber("on", "")); + Assert.assertFalse(matcher.match(null, null, null, evaluationContext)); + Assert.assertFalse(matcher.match(123, null, null, evaluationContext)); + + matcher = new PrerequisitesMatcher(null); + Assert.assertFalse(matcher.match(123, null, null, evaluationContext)); + } +} \ No newline at end of file diff --git a/client/src/test/java/io/split/storages/memory/InMemoryCacheTest.java b/client/src/test/java/io/split/storages/memory/InMemoryCacheTest.java index d2079f1b..5589d71d 100644 --- a/client/src/test/java/io/split/storages/memory/InMemoryCacheTest.java +++ b/client/src/test/java/io/split/storages/memory/InMemoryCacheTest.java @@ -139,10 +139,10 @@ public void getMany() { @Test public void trafficTypesExist() { - ParsedSplit split = ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, null, true); - ParsedSplit split2 = ParsedSplit.createParsedSplitForTests("splitName_2", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, null, true); - ParsedSplit split3 = ParsedSplit.createParsedSplitForTests("splitName_3", 0, false, "default_treatment", new ArrayList<>(), "tt_2", 123, 2, null, true); - ParsedSplit split4 = ParsedSplit.createParsedSplitForTests("splitName_4", 0, false, "default_treatment", new ArrayList<>(), "tt_3", 123, 2, null, true); + ParsedSplit split = ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, null, true, null); + ParsedSplit split2 = ParsedSplit.createParsedSplitForTests("splitName_2", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, null, true, null); + ParsedSplit split3 = ParsedSplit.createParsedSplitForTests("splitName_3", 0, false, "default_treatment", new ArrayList<>(), "tt_2", 123, 2, null, true, null); + ParsedSplit split4 = ParsedSplit.createParsedSplitForTests("splitName_4", 0, false, "default_treatment", new ArrayList<>(), "tt_3", 123, 2, null, true, null); _cache.putMany(Stream.of(split, split2, split3, split4).collect(Collectors.toList())); assertTrue(_cache.trafficTypeExists("tt_2")); @@ -163,10 +163,10 @@ public void testSegmentNames() { ParsedCondition parsedCondition1 = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new UserDefinedSegmentMatcher(EMPLOYEES)), fullyRollout); ParsedCondition parsedCondition2 = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new UserDefinedSegmentMatcher(EMPLOYEES+"2")), turnOff); - ParsedSplit split = ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", Stream.of(parsedCondition1).collect(Collectors.toList()), "tt", 123, 2, null, true); - ParsedSplit split2 = ParsedSplit.createParsedSplitForTests("splitName_2", 0, false, "default_treatment", Stream.of(parsedCondition2).collect(Collectors.toList()), "tt", 123, 2, null, true); - ParsedSplit split3 = ParsedSplit.createParsedSplitForTests("splitName_3", 0, false, "default_treatment", Stream.of(parsedCondition1).collect(Collectors.toList()), "tt_2", 123, 2, null, true); - ParsedSplit split4 = ParsedSplit.createParsedSplitForTests("splitName_4", 0, false, "default_treatment", Stream.of(parsedCondition2).collect(Collectors.toList()), "tt_3", 123, 2, null, true); + ParsedSplit split = ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", Stream.of(parsedCondition1).collect(Collectors.toList()), "tt", 123, 2, null, true, null); + ParsedSplit split2 = ParsedSplit.createParsedSplitForTests("splitName_2", 0, false, "default_treatment", Stream.of(parsedCondition2).collect(Collectors.toList()), "tt", 123, 2, null, true, null); + ParsedSplit split3 = ParsedSplit.createParsedSplitForTests("splitName_3", 0, false, "default_treatment", Stream.of(parsedCondition1).collect(Collectors.toList()), "tt_2", 123, 2, null, true, null); + ParsedSplit split4 = ParsedSplit.createParsedSplitForTests("splitName_4", 0, false, "default_treatment", Stream.of(parsedCondition2).collect(Collectors.toList()), "tt_3", 123, 2, null, true, null); _cache.putMany(Stream.of(split, split2, split3, split4).collect(Collectors.toList())); @@ -178,19 +178,19 @@ public void testSegmentNames() { } private ParsedSplit getParsedSplitWithFlagSetsSameStorage(String splitName) { - return ParsedSplit.createParsedSplitForTests(splitName, 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(Arrays.asList("set1", "set2")), true); + return ParsedSplit.createParsedSplitForTests(splitName, 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(Arrays.asList("set1", "set2")), true, null); } private ParsedSplit getParsedSplitWithFlagSetsNotSameStorage(String splitName) { - return ParsedSplit.createParsedSplitForTests(splitName, 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(Arrays.asList("set3")), true); + return ParsedSplit.createParsedSplitForTests(splitName, 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(Arrays.asList("set3")), true, null); } private ParsedSplit getParsedSplitFlagSetsNull(String splitName) { - return ParsedSplit.createParsedSplitForTests(splitName, 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, null, true); + return ParsedSplit.createParsedSplitForTests(splitName, 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, null, true, null); } private ParsedSplit getParsedSplitFlagSetsEmpty(String splitName) { - return ParsedSplit.createParsedSplitForTests(splitName, 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(), true); + return ParsedSplit.createParsedSplitForTests(splitName, 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(), true, null); } @Test @@ -204,7 +204,7 @@ public void testPutMany() { @Test public void testIncreaseTrafficType() { - ParsedSplit split = ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(), true); + ParsedSplit split = ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(), true, null); _cache.putMany(Stream.of(split).collect(Collectors.toList())); _cache.increaseTrafficType("tt_2"); assertTrue(_cache.trafficTypeExists("tt_2")); @@ -212,7 +212,7 @@ public void testIncreaseTrafficType() { @Test public void testDecreaseTrafficType() { - ParsedSplit split = ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(), true); + ParsedSplit split = ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(), true, null); _cache.putMany(Stream.of(split).collect(Collectors.toList())); _cache.decreaseTrafficType("tt"); assertFalse(_cache.trafficTypeExists("tt_2")); @@ -220,10 +220,10 @@ public void testDecreaseTrafficType() { @Test public void testGetNamesByFlagSets() { - ParsedSplit split = ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(Arrays.asList("set1", "set2", "set3")), true); - ParsedSplit split2 = ParsedSplit.createParsedSplitForTests("splitName_2", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(Arrays.asList("set1")), true); - ParsedSplit split3 = ParsedSplit.createParsedSplitForTests("splitName_3", 0, false, "default_treatment", new ArrayList<>(), "tt_2", 123, 2, new HashSet<>(Arrays.asList("set4")), true); - ParsedSplit split4 = ParsedSplit.createParsedSplitForTests("splitName_4", 0, false, "default_treatment", new ArrayList<>(), "tt_3", 123, 2, new HashSet<>(Arrays.asList("set2")), true); + ParsedSplit split = ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(Arrays.asList("set1", "set2", "set3")), true, null); + ParsedSplit split2 = ParsedSplit.createParsedSplitForTests("splitName_2", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2, new HashSet<>(Arrays.asList("set1")), true, null); + ParsedSplit split3 = ParsedSplit.createParsedSplitForTests("splitName_3", 0, false, "default_treatment", new ArrayList<>(), "tt_2", 123, 2, new HashSet<>(Arrays.asList("set4")), true, null); + ParsedSplit split4 = ParsedSplit.createParsedSplitForTests("splitName_4", 0, false, "default_treatment", new ArrayList<>(), "tt_3", 123, 2, new HashSet<>(Arrays.asList("set2")), true, null); _cache.putMany(Stream.of(split, split2, split3, split4).collect(Collectors.toList())); Map> namesByFlagSets = _cache.getNamesByFlagSets(new ArrayList<>(Arrays.asList("set1", "set2", "set3"))); diff --git a/client/src/test/java/io/split/storages/pluggable/adapters/UserCustomSplitAdapterConsumerTest.java b/client/src/test/java/io/split/storages/pluggable/adapters/UserCustomSplitAdapterConsumerTest.java index 26114711..d12badc0 100644 --- a/client/src/test/java/io/split/storages/pluggable/adapters/UserCustomSplitAdapterConsumerTest.java +++ b/client/src/test/java/io/split/storages/pluggable/adapters/UserCustomSplitAdapterConsumerTest.java @@ -76,9 +76,23 @@ public void testGetSplit() { SplitParser splitParser = new SplitParser(); Split split = getSplit(SPLIT_NAME); Mockito.when(_userStorageWrapper.get(PrefixAdapter.buildSplitKey(SPLIT_NAME))).thenReturn(getSplitAsJson(split)); - ParsedSplit result = _userCustomSplitAdapterConsumer.get(SPLIT_NAME); + ParsedSplit actual = _userCustomSplitAdapterConsumer.get(SPLIT_NAME); ParsedSplit expected = splitParser.parse(split); - Assert.assertEquals(expected, result); + Assert.assertEquals(actual.getRuleBasedSegmentsNames(), expected.getRuleBasedSegmentsNames()); + Assert.assertEquals(actual.seed(), expected.seed()); + Assert.assertEquals(actual.algo(), expected.algo()); + Assert.assertEquals(actual.trafficAllocationSeed(), expected.trafficAllocationSeed()); + Assert.assertEquals(actual.flagSets(), expected.flagSets()); + Assert.assertEquals(actual.parsedConditions(), expected.parsedConditions()); + Assert.assertEquals(actual.trafficAllocation(), expected.trafficAllocation()); + Assert.assertEquals(actual.getSegmentsNames(), expected.getSegmentsNames()); + Assert.assertEquals(actual.impressionsDisabled(), expected.impressionsDisabled()); + Assert.assertEquals(actual.killed(), expected.killed()); + Assert.assertEquals(actual.defaultTreatment(), expected.defaultTreatment()); + Assert.assertEquals(actual.changeNumber(), expected.changeNumber()); + Assert.assertEquals(actual.feature(), expected.feature()); + Assert.assertEquals(actual.configurations(), expected.configurations()); + Assert.assertEquals(actual.prerequisitesMatcher().toString(), expected.prerequisitesMatcher().toString()); } @Test diff --git a/client/src/test/resources/splits_prereq.json b/client/src/test/resources/splits_prereq.json new file mode 100644 index 00000000..5efa7fed --- /dev/null +++ b/client/src/test/resources/splits_prereq.json @@ -0,0 +1,293 @@ +{"ff": { + "d": [ + { + "trafficTypeName": "user", + "name": "test_prereq", + "prerequisites": [ + { "n": "feature_segment", "ts": ["off", "def_test"] }, + { "n": "rbs_flag", "ts": ["on"] } + ], + "trafficAllocation": 100, + "trafficAllocationSeed": 1582960494, + "seed": 1842944006, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "def_treatment", + "changeNumber": 1582741588594, + "algo": 2, + "configurations": {}, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ], + "label": "default rule" + } + ] + }, + { + "name":"feature_segment", + "trafficTypeId":"u", + "trafficTypeName":"User", + "trafficAllocation": 100, + "trafficAllocationSeed": 1582960494, + "seed":-1177551240, + "status":"ACTIVE", + "killed":false, + "defaultTreatment":"def_test", + "changeNumber": 1582741588594, + "algo": 2, + "configurations": {}, + "conditions":[ + { + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "matcherType":"IN_SEGMENT", + "negate":false, + "userDefinedSegmentMatcherData":{ + "segmentName":"segment-test" + }, + "whitelistMatcherData":null + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":100 + }, + { + "treatment":"off", + "size":0 + } + ], + "label": "default label" + } + ] + }, + { + "changeNumber": 10, + "trafficTypeName": "user", + "name": "rbs_flag", + "trafficAllocation": 100, + "trafficAllocationSeed": 1828377380, + "seed": -286617921, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "algo": 2, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "IN_RULE_BASED_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "sample_rule_based_segment" + } + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ], + "label": "in rule based segment sample_rule_based_segment" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "ALL_KEYS", + "negate": false + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ], + "label": "default rule" + } + ], + "configurations": {}, + "sets": [], + "impressionsDisabled": false + }, + { + "trafficTypeName": "user", + "name": "prereq_chain", + "prerequisites": [ + { "n": "test_prereq", "ts": ["on"] } + ], + "trafficAllocation": 100, + "trafficAllocationSeed": -2092979940, + "seed": 105482719, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "on_default", + "changeNumber": 1585948850109, + "algo": 2, + "configurations": {}, + "conditions": [ + { + "conditionType": "WHITELIST", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": null, + "matcherType": "WHITELIST", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": { + "whitelist": [ + "bilal@split.io" + ] + }, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on_whitelist", + "size": 100 + } + ], + "label": "whitelisted" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "V1", + "size": 0 + } + ], + "label": "default rule" + } + ] + } + ], + "s": -1, + "t": 1585948850109 +}, "rbs":{"d": [ + { + "changeNumber": 5, + "name": "sample_rule_based_segment", + "status": "ACTIVE", + "trafficTypeName": "user", + "excluded":{ + "keys":["mauro@split.io","gaston@split.io"], + "segments":[] + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "email" + }, + "matcherType": "ENDS_WITH", + "negate": false, + "whitelistMatcherData": { + "whitelist": [ + "@split.io" + ] + } + } + ] + } + } + ] + }], "s": -1, "t": 1585948850109} +}