public void EvaluateFeature_WhenSplitWithoutConditions_ReturnsDefaultTreatment() { // Arrange. var splitName = "always_on"; var key = new Key("test", "test"); var parsedSplit = new ParsedSplit { algo = AlgorithmEnum.Murmur, changeNumber = 123123, name = splitName, defaultTreatment = "off", trafficTypeName = "tt", conditions = new List <ConditionWithLogic>() }; _splitCache .Setup(mock => mock.GetSplit(splitName)) .Returns(parsedSplit); // Act. var result = _evaluator.EvaluateFeature(key, splitName); // Assert. Assert.AreEqual(parsedSplit.defaultTreatment, result.Treatment); Assert.AreEqual(parsedSplit.changeNumber, result.ChangeNumber); Assert.AreEqual(Labels.DefaultRule, result.Label); }
private TreatmentResult EvaluateTreatment(Key key, ParsedSplit parsedSplit, string featureName, Stopwatch clock = null, Dictionary <string, object> attributes = null) { try { if (clock == null) { clock = new Stopwatch(); clock.Start(); } if (parsedSplit == null) { _log.Warn($"GetTreatment: you passed {featureName} that does not exist in this environment, please double check what Splits exist in the web console."); return(new TreatmentResult(Labels.SplitNotFound, Control, elapsedMilliseconds: clock.ElapsedMilliseconds)); } var treatmentResult = GetTreatmentResult(key, parsedSplit, attributes); if (parsedSplit.configurations != null && parsedSplit.configurations.ContainsKey(treatmentResult.Treatment)) { treatmentResult.Config = parsedSplit.configurations[treatmentResult.Treatment]; } treatmentResult.ElapsedMilliseconds = clock.ElapsedMilliseconds; return(treatmentResult); } catch (Exception e) { _log.Error($"Exception caught getting treatment for feature: {featureName}", e); return(new TreatmentResult(Labels.Exception, Control, elapsedMilliseconds: clock.ElapsedMilliseconds)); } }
public ParsedSplit Parse(Split split) { try { StatusEnum result; var isValidStatus = Enum.TryParse(split.status, out result); if (!isValidStatus || result != StatusEnum.ACTIVE) { return(null); } ParsedSplit parsedSplit = new ParsedSplit() { name = split.name, killed = split.killed, defaultTreatment = split.defaultTreatment, seed = split.seed, conditions = new List <ConditionWithLogic>(), changeNumber = split.changeNumber, trafficTypeName = split.trafficTypeName, algo = split.algo == 0 || split.algo == null ? AlgorithmEnum.LegacyHash : (AlgorithmEnum)split.algo, trafficAllocation = split.trafficAllocation, trafficAllocationSeed = split.trafficAllocationSeed.HasValue ? split.trafficAllocationSeed.Value : 0 }; parsedSplit = ParseConditions(split, parsedSplit); return(parsedSplit); } catch (Exception e) { Log.Error("Exception caught parsing split", e); return(null); } }
public void AddDuplicateSplitTest() { //Arrange var splitCache = new InMemorySplitCache(new ConcurrentDictionary <string, ParsedSplit>()); var splitName = "test1"; //Act var parsedSplit1 = new ParsedSplit() { name = splitName }; splitCache.AddSplit(splitName, parsedSplit1); var parsedSplit2 = new ParsedSplit() { name = splitName }; splitCache.AddSplit(splitName, parsedSplit2); var result = splitCache.GetAllSplits(); //Assert Assert.IsNotNull(result); Assert.AreEqual(1, result.Count); Assert.AreEqual(result[0], parsedSplit1); Assert.AreNotEqual(result[0], parsedSplit2); }
public void ExecuteGetSuccessfulWithResultsFromJSONFileIncludingTrafficAllocation() { //Arrange var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary <string, Segment>()); var splitParser = new InMemorySplitParser(new JSONFileSegmentFetcher("segment_payed.json", segmentCache), segmentCache); var splitChangeFetcher = new JSONFileSplitChangeFetcher("splits_staging_4.json"); var splitChangesResult = splitChangeFetcher.Fetch(-1); var splitCache = new InMemorySplitCache(new ConcurrentDictionary <string, ParsedSplit>()); var gates = new InMemoryReadinessGatesCache(); var selfRefreshingSplitFetcher = new SelfRefreshingSplitFetcher(splitChangeFetcher, splitParser, gates, 30, splitCache); selfRefreshingSplitFetcher.Start(); gates.IsSDKReady(1000); //Act ParsedSplit result = (ParsedSplit)splitCache.GetSplit("Traffic_Allocation_UI"); //Assert Assert.IsNotNull(result); Assert.IsTrue(result.name == "Traffic_Allocation_UI"); Assert.IsTrue(result.trafficAllocation == 100); Assert.IsTrue(result.trafficAllocationSeed == 0); Assert.IsTrue(result.conditions.Count > 0); Assert.IsNotNull(result.conditions.Find(x => x.conditionType == ConditionType.ROLLOUT)); }
public void EvaluateFeature_WithWhitelistCondition_EqualToBooleanMatcher_ReturnsOff() { // Arrange. var splitName = "always_on"; var key = new Key("true", "true"); var parsedSplit = new ParsedSplit { algo = AlgorithmEnum.Murmur, changeNumber = 123123, name = splitName, defaultTreatment = "off", trafficTypeName = "tt", trafficAllocationSeed = 18, trafficAllocation = 20, conditions = new List <ConditionWithLogic> { new ConditionWithLogic { label = "label", conditionType = ConditionType.WHITELIST, partitions = new List <PartitionDefinition> { new PartitionDefinition { treatment = "on", size = 100 } }, matcher = new CombiningMatcher { combiner = CombinerEnum.AND, delegates = new List <AttributeMatcher> { new AttributeMatcher { matcher = new EqualToBooleanMatcher(false) } } } } } }; _splitCache .Setup(mock => mock.GetSplit(splitName)) .Returns(parsedSplit); // Act. var result = _evaluator.EvaluateFeature(key, splitName); // Assert. Assert.AreEqual("off", result.Treatment); Assert.AreEqual(Labels.DefaultRule, result.Label); Assert.AreEqual(parsedSplit.changeNumber, result.ChangeNumber); }
public void EvaluateFeature_WithRolloutCondition_BucketIsBiggerTrafficAllocation_ReturnsDefailtTreatment() { // Arrange. var splitName = "always_on"; var key = new Key("test", "test"); var parsedSplit = new ParsedSplit { algo = AlgorithmEnum.Murmur, changeNumber = 123123, name = splitName, defaultTreatment = "off", trafficTypeName = "tt", trafficAllocationSeed = 12, trafficAllocation = 10, conditions = new List <ConditionWithLogic> { new ConditionWithLogic { conditionType = ConditionType.ROLLOUT, partitions = new List <PartitionDefinition> { new PartitionDefinition { treatment = "on", size = 100 } }, matcher = new CombiningMatcher { combiner = CombinerEnum.AND, delegates = new List <AttributeMatcher> { new AttributeMatcher() } } } } }; _splitCache .Setup(mock => mock.GetSplit(splitName)) .Returns(parsedSplit); _splitter .Setup(mock => mock.GetBucket(key.bucketingKey, parsedSplit.trafficAllocationSeed, AlgorithmEnum.Murmur)) .Returns(18); // Act. var result = _evaluator.EvaluateFeature(key, splitName); // Assert. Assert.AreEqual(parsedSplit.defaultTreatment, result.Treatment); Assert.AreEqual(parsedSplit.changeNumber, result.ChangeNumber); Assert.AreEqual(Labels.TrafficAllocationFailed, result.Label); }
/// <summary> /// Creates a ParsedSplit instance that always returns /// treatment specified in input file. It is implemented this way /// for simplification. When a split is killed, the client /// returns default treatment for that feature. /// </summary> /// <param name="name"></param> /// <param name="treatment"></param> /// <returns></returns> private ParsedSplit CreateParsedSplit(string name, string treatment) { var split = new ParsedSplit() { name = name, seed = 0, killed = true, defaultTreatment = treatment, conditions = null }; return(split); }
private CombiningMatcher ParseMatcherGroup(ParsedSplit parsedSplit, MatcherGroupDefinition matcherGroupDefinition) { if (matcherGroupDefinition.matchers == null || matcherGroupDefinition.matchers.Count() == 0) { throw new Exception("Missing or empty matchers"); } return(new CombiningMatcher() { delegates = matcherGroupDefinition.matchers.Select(x => ParseMatcher(parsedSplit, x)).ToList(), combiner = ParseCombiner(matcherGroupDefinition.combiner) }); }
protected ParsedSplit CreateParsedSplit(string name, string treatment, List <ConditionWithLogic> codnitions = null) { var split = new ParsedSplit() { name = name, seed = 0, defaultTreatment = treatment, conditions = codnitions, algo = AlgorithmEnum.Murmur, trafficAllocation = 100 }; return(split); }
private ParsedSplit ParseConditions(Split split, ParsedSplit parsedSplit) { foreach (var condition in split.conditions) { ConditionType result; var isValidCondition = Enum.TryParse(condition.conditionType, out result); parsedSplit.conditions.Add(new ConditionWithLogic() { conditionType = isValidCondition ? result : ConditionType.WHITELIST, partitions = condition.partitions, matcher = ParseMatcherGroup(parsedSplit, condition.matcherGroup), label = condition.label }); } return(parsedSplit); }
public void EvaluateFeature_WhenSplitNameDoesntExist_ReturnsControl() { // Arrange. var splitName = "always_on"; var key = new Key("test", "test"); ParsedSplit parsedSplit = null; _splitCache .Setup(mock => mock.GetSplit(splitName)) .Returns(parsedSplit); // Act. var result = _evaluator.EvaluateFeature(key, splitName); // Assert. Assert.AreEqual("control", result.Treatment); Assert.AreEqual(Labels.SplitNotFound, result.Label); _log.Verify(mock => mock.Warn($"GetTreatment: you passed {splitName} that does not exist in this environment, please double check what Splits exist in the web console."), Times.Once); }
protected override string GetTreatmentForFeature(Key key, string feature, Dictionary <string, object> attributes = null, bool logMetricsAndImpressions = true) { long start = CurrentTimeHelper.CurrentTimeMillis(); var clock = new Stopwatch(); clock.Start(); try { var split = splitCache.GetSplit(feature); if (split == null) { if (logMetricsAndImpressions) { //if split definition was not found, impression label = "rules not found" RecordStats(key, feature, null, LabelSplitNotFound, start, Control, SdkGetTreatment, clock); } Log.Warn(String.Format("Unknown or invalid feature: {0}", feature)); return(Control); } ParsedSplit parsedSplit = splitParser.Parse((Split)split); var treatment = GetTreatment(key, parsedSplit, attributes, start, clock, this, logMetricsAndImpressions); return(treatment); } catch (Exception e) { if (logMetricsAndImpressions) { //if there was an exception, impression label = "exception" RecordStats(key, feature, null, LabelException, start, Control, SdkGetTreatment, clock); } Log.Error(String.Format("Exception caught getting treatment for feature: {0}", feature), e); return(Control); } }
protected TreatmentResult GetTreatment(Key key, ParsedSplit split, Dictionary <string, object> attributes, ISplitClient splitClient) { if (!split.killed) { bool inRollout = false; // use the first matching condition foreach (var condition in split.conditions) { if (!inRollout && condition.conditionType == ConditionType.ROLLOUT) { if (split.trafficAllocation < 100) { // bucket ranges from 1-100. var bucket = split.algo == AlgorithmEnum.LegacyHash ? splitter.LegacyBucket(key.bucketingKey, split.trafficAllocationSeed) : splitter.Bucket(key.bucketingKey, split.trafficAllocationSeed); if (bucket > split.trafficAllocation) { return(new TreatmentResult(LabelTrafficAllocationFailed, split.defaultTreatment, split.changeNumber)); } } inRollout = true; } var combiningMatcher = condition.matcher; if (combiningMatcher.Match(key, attributes, splitClient)) { var treatment = splitter.GetTreatment(key.bucketingKey, split.seed, condition.partitions, split.algo); return(new TreatmentResult(condition.label, treatment, split.changeNumber)); } } return(new TreatmentResult(LabelDefaultRule, split.defaultTreatment, split.changeNumber)); } else { return(new TreatmentResult(LabelKilled, split.defaultTreatment, split.changeNumber)); } }
private void DecreaseTrafficTypeCount(ParsedSplit split) { if (split == null || string.IsNullOrEmpty(split.trafficTypeName)) { return; } if (_trafficTypes.TryGetValue(split.trafficTypeName, out int quantity)) { if (quantity <= 1) { _trafficTypes.TryRemove(split.trafficTypeName, out int value); return; } var newQuantity = quantity - 1; _trafficTypes.TryUpdate(split.trafficTypeName, newQuantity, quantity); } }
private TreatmentResult GetTreatmentResult(Key key, ParsedSplit split, Dictionary <string, object> attributes = null) { if (split.killed) { return(new TreatmentResult(Labels.Killed, split.defaultTreatment, split.changeNumber)); } var inRollout = false; // use the first matching condition foreach (var condition in split.conditions) { if (!inRollout && condition.conditionType == ConditionType.ROLLOUT) { if (split.trafficAllocation < 100) { // bucket ranges from 1-100. var bucket = _splitter.GetBucket(key.bucketingKey, split.trafficAllocationSeed, split.algo); if (bucket > split.trafficAllocation) { return(new TreatmentResult(Labels.TrafficAllocationFailed, split.defaultTreatment, split.changeNumber)); } } inRollout = true; } var combiningMatcher = condition.matcher; if (combiningMatcher.Match(key, attributes, this)) { var treatment = _splitter.GetTreatment(key.bucketingKey, split.seed, condition.partitions, split.algo); return(new TreatmentResult(condition.label, treatment, split.changeNumber)); } } return(new TreatmentResult(Labels.DefaultRule, split.defaultTreatment, split.changeNumber)); }
public void ExecuteGetSuccessfulWithResults() { //Arrange var baseUrl = "https://sdk-aws-staging.split.io/api/"; //var baseUrl = "http://localhost:3000/api/"; var headers = new Dictionary <string, string> { { "SplitSDKMachineIP", "1.0.0.0" }, { "SplitSDKMachineName", "localhost" }, { "SplitSDKVersion", "1" } }; var telemetryStorage = new InMemoryTelemetryStorage(); var sdkApiClient = new SplitSdkApiClient("///PUT API KEY HERE///", headers, baseUrl, 10000, 10000, telemetryStorage); var apiSplitChangeFetcher = new ApiSplitChangeFetcher(sdkApiClient); var sdkSegmentApiClient = new SegmentSdkApiClient("///PUT API KEY HERE///", headers, baseUrl, 10000, 10000, telemetryStorage); var apiSegmentChangeFetcher = new ApiSegmentChangeFetcher(sdkSegmentApiClient); var gates = new InMemoryReadinessGatesCache(); var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary <string, Segment>()); var segmentTaskQueue = new SegmentTaskQueue(); var wrapperAdapter = new WrapperAdapter(); var selfRefreshingSegmentFetcher = new SelfRefreshingSegmentFetcher(apiSegmentChangeFetcher, gates, 30, segmentCache, 4, segmentTaskQueue, new TasksManager(wrapperAdapter), wrapperAdapter); var splitParser = new InMemorySplitParser(selfRefreshingSegmentFetcher, segmentCache); var splitCache = new InMemorySplitCache(new ConcurrentDictionary <string, ParsedSplit>()); var selfRefreshingSplitFetcher = new SelfRefreshingSplitFetcher(apiSplitChangeFetcher, splitParser, gates, 30, new TasksManager(wrapperAdapter), splitCache); selfRefreshingSplitFetcher.Start(); //Act gates.WaitUntilReady(1000); selfRefreshingSplitFetcher.Stop(); ParsedSplit result = (ParsedSplit)splitCache.GetSplit("Pato_Test_1"); ParsedSplit result2 = (ParsedSplit)splitCache.GetSplit("Manu_Test_1"); //Assert Assert.IsNotNull(result); Assert.IsTrue(result.name == "Pato_Test_1"); Assert.IsTrue(result.conditions.Count > 0); }
public void ExecuteGetSuccessfulWithResults() { //Arrange var baseUrl = "https://sdk-aws-staging.split.io/api/"; //var baseUrl = "http://localhost:3000/api/"; var httpHeader = new HTTPHeader() { authorizationApiKey = "///PUT API KEY HERE///", splitSDKMachineIP = "1.0.0.0", splitSDKMachineName = "localhost", splitSDKVersion = "net-0.0.0", splitSDKSpecVersion = "1.2", encoding = "gzip" }; var sdkApiClient = new SplitSdkApiClient(httpHeader, baseUrl, 10000, 10000); var apiSplitChangeFetcher = new ApiSplitChangeFetcher(sdkApiClient); var sdkSegmentApiClient = new SegmentSdkApiClient(httpHeader, baseUrl, 10000, 10000); var apiSegmentChangeFetcher = new ApiSegmentChangeFetcher(sdkSegmentApiClient); var gates = new InMemoryReadinessGatesCache(); var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary <string, Segment>()); var selfRefreshingSegmentFetcher = new SelfRefreshingSegmentFetcher(apiSegmentChangeFetcher, gates, 30, segmentCache, 4); var splitParser = new InMemorySplitParser(selfRefreshingSegmentFetcher, segmentCache); var splitCache = new InMemorySplitCache(new ConcurrentDictionary <string, ParsedSplit>()); var selfRefreshingSplitFetcher = new SelfRefreshingSplitFetcher(apiSplitChangeFetcher, splitParser, gates, 30, splitCache); selfRefreshingSplitFetcher.Start(); //Act gates.IsSDKReady(1000); selfRefreshingSplitFetcher.Stop(); ParsedSplit result = (ParsedSplit)splitCache.GetSplit("Pato_Test_1"); ParsedSplit result2 = (ParsedSplit)splitCache.GetSplit("Manu_Test_1"); //Assert Assert.IsNotNull(result); Assert.IsTrue(result.name == "Pato_Test_1"); Assert.IsTrue(result.conditions.Count > 0); }
public void ExecuteGetSuccessfulWithResultsFromJSONFile() { //Arrange var segmentCache = new InMemorySegmentCache(new ConcurrentDictionary <string, Segment>()); var splitParser = new InMemorySplitParser(new JSONFileSegmentFetcher("segment_payed.json", segmentCache), segmentCache); var splitChangeFetcher = new JSONFileSplitChangeFetcher("splits_staging.json"); var splitChangesResult = splitChangeFetcher.Fetch(-1); var splitCache = new InMemorySplitCache(new ConcurrentDictionary <string, ParsedSplit>()); var gates = new InMemoryReadinessGatesCache(); var selfRefreshingSplitFetcher = new SelfRefreshingSplitFetcher(splitChangeFetcher, splitParser, gates, 30, splitCache); selfRefreshingSplitFetcher.Start(); gates.IsSDKReady(1000); //Act ParsedSplit result = (ParsedSplit)splitCache.GetSplit("Pato_Test_1"); //Assert Assert.IsNotNull(result); Assert.IsTrue(result.name == "Pato_Test_1"); Assert.IsTrue(result.conditions.Count > 0); }
public void AddOrUpdate_WhenUpdateTraffictType_ReturnsTrue() { // Arrange var splitCache = new InMemorySplitCache(new ConcurrentDictionary <string, ParsedSplit>()); var splitName = "split_1"; var splitName2 = "split_2"; var split = new ParsedSplit { name = splitName, trafficTypeName = "traffic_type_1" }; var split2 = new ParsedSplit { name = splitName, trafficTypeName = "traffic_type_2" }; var split3 = new ParsedSplit { name = splitName, trafficTypeName = "traffic_type_3" }; var split4 = new ParsedSplit { name = splitName2, trafficTypeName = "traffic_type_4" }; splitCache.AddOrUpdate(splitName, split); splitCache.AddOrUpdate(splitName, split2); splitCache.AddOrUpdate(splitName, split3); splitCache.AddOrUpdate(splitName2, split4); // Act var result1 = splitCache.TrafficTypeExists("traffic_type_1"); var result2 = splitCache.TrafficTypeExists("traffic_type_2"); var result3 = splitCache.TrafficTypeExists("traffic_type_3"); // Assert Assert.IsFalse(result1); Assert.IsFalse(result2); Assert.IsTrue(result3); }
protected abstract IMatcher GetInSegmentMatcher(MatcherDefinition matcherDefinition, ParsedSplit parsedSplit);
private AttributeMatcher ParseMatcher(ParsedSplit parsedSplit, MatcherDefinition matcherDefinition) { if (matcherDefinition.matcherType == null) { throw new Exception("Missing matcher type value"); } var matcherType = matcherDefinition.matcherType; IMatcher matcher = null; try { MatcherTypeEnum result; var isValidMatcherType = Enum.TryParse(matcherType, out result); if (isValidMatcherType) { switch (result) { case MatcherTypeEnum.ALL_KEYS: matcher = GetAllKeysMatcher(); break; case MatcherTypeEnum.BETWEEN: matcher = GetBetweenMatcher(matcherDefinition); break; case MatcherTypeEnum.EQUAL_TO: matcher = GetEqualToMatcher(matcherDefinition); break; case MatcherTypeEnum.GREATER_THAN_OR_EQUAL_TO: matcher = GetGreaterThanOrEqualToMatcher(matcherDefinition); break; case MatcherTypeEnum.IN_SEGMENT: matcher = GetInSegmentMatcher(matcherDefinition, parsedSplit); break; case MatcherTypeEnum.LESS_THAN_OR_EQUAL_TO: matcher = GetLessThanOrEqualToMatcher(matcherDefinition); break; case MatcherTypeEnum.WHITELIST: matcher = GetWhitelistMatcher(matcherDefinition); break; case MatcherTypeEnum.EQUAL_TO_SET: matcher = GetEqualToSetMatcher(matcherDefinition); break; case MatcherTypeEnum.CONTAINS_ANY_OF_SET: matcher = GetContainsAnyOfSetMatcher(matcherDefinition); break; case MatcherTypeEnum.CONTAINS_ALL_OF_SET: matcher = GetContainsAllOfSetMatcher(matcherDefinition); break; case MatcherTypeEnum.PART_OF_SET: matcher = GetPartOfSetMatcher(matcherDefinition); break; case MatcherTypeEnum.STARTS_WITH: matcher = GetStartsWithMatcher(matcherDefinition); break; case MatcherTypeEnum.ENDS_WITH: matcher = GetEndsWithMatcher(matcherDefinition); break; case MatcherTypeEnum.CONTAINS_STRING: matcher = GetContainsStringMatcher(matcherDefinition); break; case MatcherTypeEnum.IN_SPLIT_TREATMENT: matcher = GetDependencyMatcher(matcherDefinition); break; case MatcherTypeEnum.EQUAL_TO_BOOLEAN: matcher = GetEqualToBooleanMatcher(matcherDefinition); break; case MatcherTypeEnum.MATCHES_STRING: matcher = GetMatchesStringMatcher(matcherDefinition); break; } } } catch (Exception e) { Log.Error("Error parsing matcher", e); } if (matcher == null) { throw new Exception(string.Format("Unable to create matcher for matcher type: {0}", matcherType)); } AttributeMatcher attributeMatcher = new AttributeMatcher() { matcher = matcher, negate = matcherDefinition.negate }; if (matcherDefinition.keySelector != null && matcherDefinition.keySelector.attribute != null) { attributeMatcher.attribute = matcherDefinition.keySelector.attribute; } return(attributeMatcher); }
public void EvaluateFeatures_WhenSplitNameDoesntExist_ReturnsControl() { // Arrange. var splitNames = new List <string> { "always_on", "always_off" }; var key = new Key("*****@*****.**", "test"); var parsedSplitOn = new ParsedSplit { algo = AlgorithmEnum.Murmur, changeNumber = 123123, name = splitNames.First(s => s.Equals("always_on")), defaultTreatment = "off", trafficTypeName = "tt", trafficAllocationSeed = 18, trafficAllocation = 20, seed = 123123133, conditions = new List <ConditionWithLogic> { new ConditionWithLogic { label = "labelEndsWithMatcher", conditionType = ConditionType.ROLLOUT, partitions = new List <PartitionDefinition> { new PartitionDefinition { treatment = "on", size = 100 } }, matcher = new CombiningMatcher { combiner = CombinerEnum.AND, delegates = new List <AttributeMatcher> { new AttributeMatcher { matcher = new EndsWithMatcher(new List <string> { "@split.io" }) } } } } } }; var parsedSplitOff = new ParsedSplit { algo = AlgorithmEnum.Murmur, changeNumber = 123123, name = splitNames.First(s => s.Equals("always_off")), defaultTreatment = "off", trafficTypeName = "tt", seed = 5647567, conditions = new List <ConditionWithLogic> { new ConditionWithLogic { label = "labelWhiteList", conditionType = ConditionType.WHITELIST, partitions = new List <PartitionDefinition> { new PartitionDefinition { treatment = "off", size = 100 } }, matcher = new CombiningMatcher { combiner = CombinerEnum.AND, delegates = new List <AttributeMatcher> { new AttributeMatcher { matcher = new EndsWithMatcher(new List <string> { "@split.io" }) } } } }, new ConditionWithLogic { label = "labelRollout", conditionType = ConditionType.ROLLOUT, partitions = new List <PartitionDefinition> { new PartitionDefinition { treatment = "off", size = 100 } }, matcher = new CombiningMatcher { combiner = CombinerEnum.AND, delegates = new List <AttributeMatcher> { new AttributeMatcher { matcher = new EqualToMatcher(DataTypeEnum.NUMBER, 123) } } } } } }; _splitter .Setup(mock => mock.GetBucket(key.bucketingKey, parsedSplitOn.trafficAllocationSeed, AlgorithmEnum.Murmur)) .Returns(18); _splitCache .Setup(mock => mock.FetchMany(It.IsAny <List <string> >())) .Returns(new List <ParsedSplit> { parsedSplitOff, parsedSplitOn }); _splitter .Setup(mock => mock.GetTreatment(key.bucketingKey, parsedSplitOn.seed, It.IsAny <List <PartitionDefinition> >(), parsedSplitOn.algo)) .Returns("on"); _splitter .Setup(mock => mock.GetTreatment(key.bucketingKey, parsedSplitOff.seed, It.IsAny <List <PartitionDefinition> >(), parsedSplitOff.algo)) .Returns("off"); // Act. var result = _evaluator.EvaluateFeatures(key, splitNames); // Assert. var resultOn = result.TreatmentResults.FirstOrDefault(tr => tr.Key.Equals("always_on")); Assert.AreEqual("on", resultOn.Value.Treatment); Assert.AreEqual(parsedSplitOn.changeNumber, resultOn.Value.ChangeNumber); Assert.AreEqual("labelEndsWithMatcher", resultOn.Value.Label); var resultOff = result.TreatmentResults.FirstOrDefault(tr => tr.Key.Equals("always_off")); Assert.AreEqual("off", resultOff.Value.Treatment); Assert.AreEqual(parsedSplitOn.changeNumber, resultOff.Value.ChangeNumber); Assert.AreEqual("labelWhiteList", resultOff.Value.Label); }
protected override IMatcher GetInSegmentMatcher(MatcherDefinition matcherDefinition, ParsedSplit parsedSplit) { var matcherData = matcherDefinition.userDefinedSegmentMatcherData; segmentFetcher.InitializeSegment(matcherData.segmentName); return(new UserDefinedSegmentMatcher(matcherData.segmentName, segmentsCache)); }
public void EvaluateFeature_WithTwoConditions_EndsWithMatch_ReturnsOn() { // Arrange. var splitName = "always_on"; var key = new Key("*****@*****.**", "true"); var parsedSplit = new ParsedSplit { algo = AlgorithmEnum.Murmur, changeNumber = 123123, name = splitName, defaultTreatment = "off", trafficTypeName = "tt", trafficAllocationSeed = 18, trafficAllocation = 20, conditions = new List <ConditionWithLogic> { new ConditionWithLogic { label = "label", conditionType = ConditionType.WHITELIST, partitions = new List <PartitionDefinition> { new PartitionDefinition { treatment = "on", size = 100 } }, matcher = new CombiningMatcher { combiner = CombinerEnum.AND, delegates = new List <AttributeMatcher> { new AttributeMatcher { matcher = new EqualToBooleanMatcher(false) } } } }, new ConditionWithLogic { label = "labelEndsWith", conditionType = ConditionType.WHITELIST, partitions = new List <PartitionDefinition> { new PartitionDefinition { treatment = "on", size = 100 } }, matcher = new CombiningMatcher { combiner = CombinerEnum.AND, delegates = new List <AttributeMatcher> { new AttributeMatcher { matcher = new EndsWithMatcher(new List <string> { "@split.io" }) } } } }, } }; _splitCache .Setup(mock => mock.GetSplit(splitName)) .Returns(parsedSplit); _splitter .Setup(mock => mock.GetTreatment(key.bucketingKey, parsedSplit.seed, It.IsAny <List <PartitionDefinition> >(), parsedSplit.algo)) .Returns("on"); // Act. var result = _evaluator.EvaluateFeature(key, splitName); // Assert. Assert.AreEqual("on", result.Treatment); Assert.AreEqual("labelEndsWith", result.Label); Assert.AreEqual(parsedSplit.changeNumber, result.ChangeNumber); }
protected string GetTreatment(Key key, ParsedSplit split, Dictionary <string, object> attributes, long start, Stopwatch clock, ISplitClient splitClient, bool logMetricsAndImpressions) { if (!split.killed) { bool inRollout = false; // use the first matching condition foreach (ConditionWithLogic condition in split.conditions) { if (!inRollout && condition.conditionType == ConditionType.ROLLOUT) { if (split.trafficAllocation < 100) { // bucket ranges from 1-100. int bucket = split.algo == AlgorithmEnum.LegacyHash ? splitter.LegacyBucket(key.bucketingKey, split.trafficAllocationSeed) : splitter.Bucket(key.bucketingKey, split.trafficAllocationSeed); if (bucket >= split.trafficAllocation) { if (logMetricsAndImpressions) { // If not in traffic allocation, abort and return // default treatment RecordStats(key, split.name, split.changeNumber, LabelTrafficAllocationFailed, start, split.defaultTreatment, SdkGetTreatment, clock); } return(split.defaultTreatment); } } inRollout = true; } var combiningMatcher = condition.matcher; if (combiningMatcher.Match(key, attributes, splitClient)) { var treatment = splitter.GetTreatment(key.bucketingKey, split.seed, condition.partitions, split.algo); if (logMetricsAndImpressions) { //If condition matched, impression label = condition.label RecordStats(key, split.name, split.changeNumber, condition.label, start, treatment, SdkGetTreatment, clock); } return(treatment); } } if (logMetricsAndImpressions) { //If no condition matched, impression label = "no rule matched" RecordStats(key, split.name, split.changeNumber, LabelNoConditionMatched, start, split.defaultTreatment, SdkGetTreatment, clock); } return(split.defaultTreatment); } else { if (logMetricsAndImpressions) { //If split was killed, impression label = "killed" RecordStats(key, split.name, split.changeNumber, LabelKilled, start, split.defaultTreatment, SdkGetTreatment, clock); } return(split.defaultTreatment); } }