public void FeatureEventCanHaveReason() { _config.InlineUsersInEvents = true; var mockSender = MakeMockSender(); var captured = EventCapture.From(mockSender); using (var ep = MakeProcessor(_config, mockSender)) { var reasons = new EvaluationReason[] { _irrelevantReason, EvaluationReason.FallthroughReason, EvaluationReason.TargetMatchReason, EvaluationReason.RuleMatchReason(1, "id"), EvaluationReason.PrerequisiteFailedReason("key"), EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType) }; foreach (var reason in reasons) { captured.Events.Clear(); var eval = BasicEval; eval.Reason = reason; RecordEval(ep, BasicFlagWithTracking, eval); FlushAndWait(ep); Assert.Collection(captured.Events, item => CheckFeatureEvent(item, BasicFlagWithTracking, eval, _userJson), item => CheckSummaryEvent(item)); } } }
public bool IsExperiment(EvaluationReason reason) { // EventFactory passes the reason parameter to this method because the server-side SDK needs to // look at the reason; but in this client-side SDK, we don't look at that parameter, because // LD has already done the relevant calculation for us and sent us the result in trackReason. return(_featureFlag.trackReason); }
private bool EvaluateThreadMaybeAbort(EvaluationReason reason) { int workItemsCount; lock (_workItems) { workItemsCount = _workItems.Count; } lock (_Threads) { foreach (System.Threading.Thread SThread in _Threads) { if (SThread.ThreadState != ThreadState.Running) { _startEvaluator.CurrentlyQueuedWorkItems = workItemsCount; _startEvaluator.CurrentlyRunningThreadCount = _Threads.Count; switch (_startEvaluator.EvaluateThreadStartStop(reason)) { case EvaluationResult.FinishCurrentThreadWhenWorkItemCompleted: return(true); case EvaluationResult.StartNewThread: AddNewThread(); break; } } } } return(false); }
private static void AssertVariationIndexFromRollout( int expectedVariation, Rollout rollout, User user, string flagKey, string salt ) { var flag1 = new FeatureFlagBuilder(flagKey) .On(true) .GeneratedVariations(3) .FallthroughRollout(rollout) .Salt(salt) .Build(); var result1 = BasicEvaluator.Evaluate(flag1, user, EventFactory.Default); Assert.Equal(EvaluationReason.FallthroughReason, result1.Result.Reason); Assert.Equal(expectedVariation, result1.Result.VariationIndex); // Make sure we consistently apply the rollout regardless of whether it's in a rule or a fallthrough var flag2 = new FeatureFlagBuilder(flagKey) .On(true) .GeneratedVariations(3) .Rules(new RuleBuilder().Id("id") .Rollout(rollout) .Clauses(new ClauseBuilder().Attribute(UserAttribute.Key).Op(Operator.In).Values(LdValue.Of(user.Key)).Build()) .Build()) .Salt(salt) .Build(); var result2 = BasicEvaluator.Evaluate(flag2, user, EventFactory.Default); Assert.Equal(EvaluationReason.RuleMatchReason(0, "id"), result2.Result.Reason); Assert.Equal(expectedVariation, result2.Result.VariationIndex); }
public static void WriteJsonValue(EvaluationReason value, IValueWriter writer) { var obj = writer.Object(); obj.Name("kind").String(EvaluationReasonKindConverter.ToIdentifier(value.Kind)); switch (value.Kind) { case EvaluationReasonKind.RuleMatch: obj.Name("ruleIndex").Int(value.RuleIndex ?? 0); obj.Name("ruleId").String(value.RuleId); break; case EvaluationReasonKind.PrerequisiteFailed: obj.Name("prerequisiteKey").String(value.PrerequisiteKey); break; case EvaluationReasonKind.Error: obj.Name("errorKind").String(EvaluationErrorKindConverter.ToIdentifier(value.ErrorKind.Value)); break; } if (value.InExperiment) { obj.Name("inExperiment").Bool(true); // omit property if false } if (value.BigSegmentsStatus.HasValue) { obj.Name("bigSegmentsStatus").String( BigSegmentsStatusConverter.ToIdentifier(value.BigSegmentsStatus.Value)); } obj.End(); }
public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotInitialized() { var config = BasicConfig() .DataSource(new MockDataSourceThatNeverInitializes().AsSingletonFactory()) .Events(_factory); using (LdClient client = TestUtil.CreateClient(config.Build(), user)) { EvaluationDetail <string> result = client.StringVariationDetail("flag", "b"); var expectedReason = EvaluationReason.ErrorReason(EvaluationErrorKind.ClientNotReady); Assert.Equal("b", result.Value); Assert.Equal(expectedReason, result.Reason); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { EvaluationEvent fe = Assert.IsType <EvaluationEvent>(e); Assert.Equal("flag", fe.FlagKey); Assert.Equal("b", fe.Value.AsString); Assert.Null(fe.Variation); Assert.Null(fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.False(fe.TrackEvents); Assert.Null(fe.DebugEventsUntilDate); Assert.Equal(expectedReason, fe.Reason); Assert.NotEqual(0, fe.Timestamp.Value); }); } }
public void ExceptionWhenGettingOneFlagIsHandledCorrectly() { // If the data store's Get method throws an error, the expected behavior is that we log // a message and return the default value. The exception should not propagate to the caller. var ex = new Exception("fake-error"); var flagKey = "flag-key"; var mockStore = new Mock <IDataStore>(); mockStore.Setup(s => s.Get(DataModel.Features, flagKey)).Throws(ex); var configWithCustomStore = Configuration.Builder("sdk-key") .DataStore(new SpecificDataStoreFactory(mockStore.Object)) .DataSource(Components.ExternalUpdatesOnly) .Logging(testLogging) .Build(); using (var clientWithCustomStore = new LdClient(configWithCustomStore)) { var defaultValue = "default-value"; var result = clientWithCustomStore.StringVariationDetail("flag-key", user, defaultValue); Assert.Equal(defaultValue, result.Value); Assert.Null(result.VariationIndex); Assert.Equal(EvaluationReason.ErrorReason(EvaluationErrorKind.Exception), result.Reason); Assert.True(logCapture.HasMessageWithRegex(Logging.LogLevel.Error, ex.Message)); } }
public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotInitialized() { var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "") .UpdateProcessorFactory(MockUpdateProcessorThatNeverInitializes.Factory()) .EventProcessor(eventProcessor); config.EventProcessor(eventProcessor); using (LdClient client = TestUtil.CreateClient(config.Build(), user)) { EvaluationDetail <string> result = client.StringVariationDetail("flag", "b"); var expectedReason = EvaluationReason.ErrorReason(EvaluationErrorKind.CLIENT_NOT_READY); Assert.Equal("b", result.Value); Assert.Equal(expectedReason, result.Reason); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { FeatureRequestEvent fe = Assert.IsType <FeatureRequestEvent>(e); Assert.Equal("flag", fe.Key); Assert.Equal("b", fe.Value.AsString); Assert.Null(fe.Variation); Assert.Null(fe.Version); Assert.Equal("b", fe.Default.AsString); Assert.False(fe.TrackEvents); Assert.Null(fe.DebugEventsUntilDate); Assert.Equal(expectedReason, fe.Reason); }); } }
public void VariationDetailReturnsDefaultForUnknownFlag() { var expected = new EvaluationDetail <string>("default", null, EvaluationReason.ErrorReason(EvaluationErrorKind.FLAG_NOT_FOUND)); Assert.Equal(expected, client.StringVariationDetail("key", null, "default")); }
/// <summary> /// Creates a feature request event for an evaluation that returned the default value /// when the flag did not exist or the feature store was unavailable. /// </summary> /// <param name="key">the flag key that was requested</param> /// <param name="user">the user passed to the Variation method</param> /// <param name="defaultVal">the default value passed to the Variation method</param> /// <param name="errorKind">the type of error</param> /// <returns>an event</returns> internal FeatureRequestEvent NewUnknownFeatureRequestEvent(string key, User user, LdValue defaultVal, EvaluationErrorKind errorKind) { return(new FeatureRequestEvent(GetTimestamp(), key, user, null, defaultVal, defaultVal, null, null, false, null, false, IncludeReasons ? EvaluationReason.ErrorReason(errorKind) : null)); }
public void FlagReturnsOffVariationAndEventIfPrerequisiteIsNotMet() { var f0 = new FeatureFlagBuilder("feature0") .On(true) .Prerequisites(new Prerequisite("feature1", 1)) .OffVariation(1) .FallthroughVariation(0) .Variations(fallthroughValue, offValue, onValue) .Version(1) .Build(); var f1 = new FeatureFlagBuilder("feature1") .On(true) .FallthroughVariation(0) .Variations(LdValue.Of("nogo"), LdValue.Of("go")) .Version(2) .Build(); var evaluator = BasicEvaluator.WithStoredFlags(f1); var result = evaluator.Evaluate(f0, baseUser, EventFactory.Default); var expected = new EvaluationDetail <LdValue>(offValue, 1, EvaluationReason.PrerequisiteFailedReason("feature1")); Assert.Equal(expected, result.Result); Assert.Equal(1, result.PrerequisiteEvents.Count); EvaluationEvent e = result.PrerequisiteEvents[0]; Assert.Equal(f1.Key, e.FlagKey); Assert.Equal(LdValue.Of("nogo"), e.Value); Assert.Equal(f1.Version, e.FlagVersion); Assert.Equal(f0.Key, e.PrerequisiteOf); }
public void TestReasonSerializationDeserialization(EvaluationReason reason, string jsonString, string expectedShortString) { AssertJsonEqual(jsonString, JsonConvert.SerializeObject(reason)); Assert.Equal(reason, JsonConvert.DeserializeObject <EvaluationReason>(jsonString)); Assert.Equal(expectedShortString, reason.ToString()); }
public void FeatureEventCanHaveReason() { _config.InlineUsersInEvents = true; _ep = MakeProcessor(_config); IFlagEventProperties flag = new FlagEventPropertiesBuilder("flagkey").Version(11).TrackEvents(true).Build(); var reasons = new EvaluationReason[] { EvaluationReason.OffReason, EvaluationReason.FallthroughReason, EvaluationReason.TargetMatchReason, EvaluationReason.RuleMatchReason(1, "id"), EvaluationReason.PrerequisiteFailedReason("key"), EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE) }; foreach (var reason in reasons) { FeatureRequestEvent fe = EventFactory.DefaultWithReasons.NewFeatureRequestEvent(flag, _user, new EvaluationDetail <LdValue>(LdValue.Of("value"), 1, reason), LdValue.Null); _ep.SendEvent(fe); JArray output = FlushAndGetEvents(OkResponse()); Assert.Collection(output, item => CheckFeatureEvent(item, fe, flag, false, _userJson, reason), item => CheckSummaryEvent(item)); } }
/// <summary> /// Creates a feature request event for an evaluation that returned the default value /// (i.e. an error), even though the flag existed. /// </summary> /// <param name="flag">abstraction of the basic flag properties we need</param> /// <param name="user">the user passed to the Variation method</param> /// <param name="defaultVal">the default value passed to the Variation method</param> /// <param name="errorKind">the type of error</param> /// <returns>an event</returns> internal FeatureRequestEvent NewDefaultFeatureRequestEvent(IFlagEventProperties flag, User user, LdValue defaultVal, EvaluationErrorKind errorKind) { return(new FeatureRequestEvent(GetTimestamp(), flag.Key, user, null, defaultVal, defaultVal, flag.EventVersion, null, flag.TrackEvents, flag.DebugEventsUntilDate, false, IncludeReasons ? EvaluationReason.ErrorReason(errorKind) : null)); }
public void FlagReturnsOffVariationAndEventIfPrerequisiteIsNotMet() { var f0 = new FeatureFlagBuilder("feature0") .On(true) .Prerequisites(new Prerequisite("feature1", 1)) .OffVariation(1) .FallthroughVariation(0) .Variations(new JValue("fall"), new JValue("off"), new JValue("on")) .Version(1) .Build(); var f1 = new FeatureFlagBuilder("feature1") .On(true) .FallthroughVariation(0) .Variations(new JValue("nogo"), new JValue("go")) .Version(2) .Build(); featureStore.Upsert(VersionedDataKind.Features, f1); var result = f0.Evaluate(baseUser, featureStore, EventFactory.Default); var expected = new EvaluationDetail <LdValue>(LdValue.Of("off"), 1, EvaluationReason.PrerequisiteFailedReason("feature1")); Assert.Equal(expected, result.Result); Assert.Equal(1, result.PrerequisiteEvents.Count); FeatureRequestEvent e = result.PrerequisiteEvents[0]; Assert.Equal(f1.Key, e.Key); Assert.Equal(LdValue.Of("nogo"), e.LdValue); Assert.Equal(f1.Version, e.Version); Assert.Equal(f0.Key, e.PrereqOf); }
public void UnknownFeatureEventHasReasonWhenUsingFactoryWithReason() { var err = EvaluationErrorKind.FLAG_NOT_FOUND; var e = EventFactory.DefaultWithReasons.NewUnknownFeatureRequestEvent("flag-key", user, defaultVal, err); Assert.Equal(EvaluationReason.ErrorReason(err), e.Reason); }
private EvaluationDetail <LdValue> GetOffValue(EvaluationReason reason) { if (_flag.OffVariation is null) // off variation unspecified - return default value { return(new EvaluationDetail <LdValue>(LdValue.Null, null, reason)); } return(GetVariation(_flag.OffVariation.Value, reason)); }
public void DefaultFeatureEventHasReasonWhenUsingFactoryWithReason() { var flag = new FlagEventPropertiesBuilder("flag-key").Version(100).Build(); var err = EvaluationErrorKind.EXCEPTION; var e = EventFactory.DefaultWithReasons.NewDefaultFeatureRequestEvent(flag, user, defaultVal, err); Assert.Equal(EvaluationReason.ErrorReason(err), e.Reason); }
public void VariationDetailReturnsDefaultForWrongType() { testData.UsePreconfiguredFlag(new FeatureFlagBuilder("key").OffWithValue(LdValue.Of("wrong")).Build()); var expected = new EvaluationDetail <int>(1, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); Assert.Equal(expected, client.IntVariationDetail("key", user, 1)); }
public void VariationDetailReturnsDefaultForUserWithNullKey() { testData.UsePreconfiguredFlag(new FeatureFlagBuilder("key").OffWithValue(LdValue.Of("b")).Build()); var expected = new EvaluationDetail <string>("default", null, EvaluationReason.ErrorReason(EvaluationErrorKind.UserNotSpecified)); Assert.Equal(expected, client.StringVariationDetail("key", User.WithKey(null), "default")); }
public void DefaultValueAndReasonIsReturnedIfValueTypeIsDifferent() { _testData.Update(_testData.Flag(flagKey).Variation(LdValue.Of("string value"))); using (var client = MakeClient()) { var expected = new EvaluationDetail <int>(3, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); Assert.Equal(expected, client.IntVariationDetail(flagKey, 3)); } }
private void WriteReason(EvaluationReason reason) { if (reason is null) { return; } _jsonWriter.WritePropertyName("reason"); EvaluationReasonConverter.Instance.WriteJson(_jsonWriter, reason, _jsonSerializer); }
private EvaluationDetail <LdValue> GetVariation(int variation, EvaluationReason reason) { if (variation < 0 || variation >= _flag.Variations.Count()) { _parent._logger.Error("Data inconsistency in feature flag \"{0}\": invalid variation index", _flag.Key); return(ErrorResult(EvaluationErrorKind.MalformedFlag)); } return(new EvaluationDetail <LdValue>(_flag.Variations.ElementAt(variation), variation, reason)); }
public void DefaultValueAndReasonIsReturnedIfValueTypeIsDifferent() { string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of("string value")); using (var client = ClientWithFlagsJson(flagsJson)) { var expected = new EvaluationDetail <int>(3, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); Assert.Equal(expected, client.IntVariationDetail("flag-key", 3)); } }
public void VariationDetailReturnsDefaultForWrongType() { featureStore.Upsert(VersionedDataKind.Features, new FeatureFlagBuilder("key").OffWithValue(new JValue("wrong")).Build()); var expected = new EvaluationDetail <int>(1, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); Assert.Equal(expected, client.IntVariationDetail("key", user, 1)); }
public void VariationDetailReturnsDefaultForUserWithNullKey() { featureStore.Upsert(VersionedDataKind.Features, new FeatureFlagBuilder("key").OffWithValue(new JValue("b")).Build()); var expected = new EvaluationDetail <string>("default", null, EvaluationReason.ErrorReason(EvaluationErrorKind.USER_NOT_SPECIFIED)); Assert.Equal(expected, client.StringVariationDetail("key", User.WithKey(null), "default")); }
public void TestEqualityAndHashCode() { // For parameterless (singleton) reasons, object.Equals and object.HashCode() already do what // we want. Test our implementations for the parameterized reasons. VerifyEqualityAndHashCode(() => EvaluationReason.RuleMatchReason(0, "rule1"), () => EvaluationReason.RuleMatchReason(1, "rule2")); VerifyEqualityAndHashCode(() => EvaluationReason.PrerequisiteFailedReason("a"), () => EvaluationReason.PrerequisiteFailedReason("b")); VerifyEqualityAndHashCode(() => EvaluationReason.ErrorReason(EvaluationErrorKind.FLAG_NOT_FOUND), () => EvaluationReason.ErrorReason(EvaluationErrorKind.EXCEPTION)); }
public FeatureFlag(LdValue value, int version, int?flagVersion, bool trackEvents, bool trackReason, int?variation, long?debugEventsUntilDate, EvaluationReason reason) { this.value = value; this.version = version; this.flagVersion = flagVersion; this.trackEvents = trackEvents; this.trackReason = trackReason; this.variation = variation; this.debugEventsUntilDate = debugEventsUntilDate; this.reason = reason; }
public void EnumVariationDetailReturnsDefaultValueForInvalidFlagValue() { var client = new MockStringVariationClient(); client.SetupStringVariationDetail("key", "Blue", new EvaluationDetail <string>("not-a-color", 1, EvaluationReason.FallthroughReason)); var result = client.EnumVariationDetail("key", MyEnum.Blue); var expected = new EvaluationDetail <MyEnum>(MyEnum.Blue, 1, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); Assert.Equal(expected, result); }
public void EnumVariationDetailReturnsDefaultValueForInvalidFlagValue() { var clientMock = new Mock <ILdClient>(); clientMock.Setup(c => c.StringVariationDetail("key", "Blue")) .Returns(new EvaluationDetail <string>("not-a-color", 1, EvaluationReason.FallthroughReason)); var client = clientMock.Object; var result = client.EnumVariationDetail("key", MyEnum.Blue); var expected = new EvaluationDetail <MyEnum>(MyEnum.Blue, 1, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); Assert.Equal(expected, result); }
private bool EvaluateThreadMaybeAbort(EvaluationReason reason) { int workItemsCount; lock (_workItems) { workItemsCount = _workItems.Count; } lock (_Threads) { foreach (System.Threading.Thread SThread in _Threads) { if (SThread.ThreadState != ThreadState.Running) { _startEvaluator.CurrentlyQueuedWorkItems = workItemsCount; _startEvaluator.CurrentlyRunningThreadCount = _Threads.Count; switch (_startEvaluator.EvaluateThreadStartStop(reason)) { case EvaluationResult.FinishCurrentThreadWhenWorkItemCompleted: return true; case EvaluationResult.StartNewThread: AddNewThread(); break; } } } } return false; }
public override EvaluationResult EvaluateThreadStartStop(EvaluationReason reason) { //Run another SThread if we have a big queue if (CurrentlyRunningThreadCount == 0 || CurrentlyRunningThreadCount < CurrentlyQueuedWorkItems / MaxDesiredQueued) { return EvaluationResult.StartNewThread; } if (CurrentlyRunningThreadCount > 1 && CurrentlyRunningThreadCount > CurrentlyQueuedWorkItems / MaxDesiredQueued) return EvaluationResult.FinishCurrentThreadWhenWorkItemCompleted; return EvaluationResult.NoOperation; }
/// <summary> /// Override this method to determine whether you wish to start a new SThread, stop the current SThread, or just leave /// the current number as is. Refer to the <see cref="EvaluationReason"/> parameter and the /// <see cref="CurrentlyRunningThreadCount"/> and <see cref="CurrentlyQueuedWorkItems"/> properties to /// work out what you wish to do. /// </summary> /// <param name="reason"></param> /// <returns></returns> public abstract EvaluationResult EvaluateThreadStartStop(EvaluationReason reason);