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);
 }
Exemple #3
0
        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);
        }
Exemple #5
0
            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();
            }
Exemple #6
0
        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);
                });
            }
        }
Exemple #9
0
        public void VariationDetailReturnsDefaultForUnknownFlag()
        {
            var expected = new EvaluationDetail <string>("default", null,
                                                         EvaluationReason.ErrorReason(EvaluationErrorKind.FLAG_NOT_FOUND));

            Assert.Equal(expected, client.StringVariationDetail("key", null, "default"));
        }
Exemple #10
0
 /// <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));
 }
Exemple #11
0
        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));
            }
        }
Exemple #14
0
 /// <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);
        }
Exemple #16
0
        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));
 }
Exemple #18
0
        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"));
        }
Exemple #21
0
 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));
            }
        }
Exemple #25
0
        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));
        }
Exemple #26
0
        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);