public virtual void TestTripCircuit()
        {
            var             properties = GetCommandConfig();
            ICommandMetrics metrics    = GetMetrics(properties);
            var             cb         = new TestCircuitBreaker(properties, metrics);

            metrics.MarkSuccess();
            metrics.MarkSuccess();
            metrics.MarkSuccess();


            // this should still allow requests as everything has been successful
            Thread.Sleep(properties.MetricsHealthSnapshotIntervalInMilliseconds);
            Assert.IsTrue(cb.AllowRequest());
            Assert.IsFalse(cb.IsOpen());

            // fail
            metrics.MarkFailure();
            metrics.MarkFailure();
            metrics.MarkFailure();;
            Thread.Sleep(properties.MetricsHealthSnapshotIntervalInMilliseconds);

            // everything has failed in the test window so we should return false now
            Assert.IsFalse(cb.AllowRequest());
            Assert.IsTrue(cb.IsOpen());;
        }
        public void TestCircuitDoesNotTripOnFailuresBelowThreshold()
        {
            var             properties = GetCommandConfig();
            ICommandMetrics metrics    = GetMetrics(properties);
            var             cb         = new TestCircuitBreaker(properties, metrics);

            // this should start as allowing requests
            Assert.IsTrue(cb.AllowRequest());
            Assert.IsFalse(cb.IsOpen());

            metrics.MarkSuccess();
            metrics.MarkSuccess();
            metrics.MarkSuccess();
            metrics.MarkSuccess();
            metrics.MarkFailure();
            metrics.MarkFailure();

            // this should remain open as the failure threshold is below the percentage limit
            Assert.IsTrue(cb.AllowRequest());
            Assert.IsFalse(cb.IsOpen());
        }
        public void TestTripCircuitOnFailuresAboveThreshold()
        {
            var             properties = GetCommandConfig();
            ICommandMetrics metrics    = GetMetrics(properties);
            var             cb         = new TestCircuitBreaker(properties, metrics);

            // this should start as allowing requests
            Assert.IsTrue(cb.AllowRequest());
            Assert.IsFalse(cb.IsOpen());

            // success with high latency
            metrics.MarkSuccess();
            metrics.MarkFailure();
            metrics.MarkSuccess();
            metrics.MarkFailure();
            metrics.MarkFailure();
            metrics.MarkFailure();
            Thread.Sleep(properties.MetricsHealthSnapshotIntervalInMilliseconds);

            // this should trip the circuit as the error percentage is above the threshold
            Assert.IsFalse(cb.AllowRequest());
            Assert.IsTrue(cb.IsOpen());
        }
        public virtual void testCircuitClosedAfterSuccessAndClearsStatisticalWindow()
        {
            int statisticalWindow = 200;
            int sleepWindow       = 10; // this is set very low so that returning from a retry still ends up having data in the buckets for the statisticalWindow
            var properties        = GetCommandConfig();

            properties.CircuitBreakerSleepWindowInMilliseconds       = sleepWindow;
            properties.MetricsRollingStatisticalWindowInMilliseconds = statisticalWindow;
            ICommandMetrics metrics = GetMetrics(properties);
            var             cb      = new TestCircuitBreaker(properties, metrics);

            // fail
            metrics.MarkFailure();
            metrics.MarkFailure();
            metrics.MarkFailure();
            metrics.MarkFailure();

            // everything has failed in the test window so we should return false now
            Assert.IsFalse(cb.AllowRequest());
            Assert.IsTrue(cb.IsOpen());

            // wait for sleepWindow to pass
            Thread.Sleep(sleepWindow + 50);

            // we should now allow 1 request
            Assert.IsTrue(cb.AllowRequest());
            // but the circuit should still be open
            Assert.IsTrue(cb.IsOpen());
            // and further requests are still blocked
            Assert.IsFalse(cb.AllowRequest());

            // the 'singleTest' succeeds so should cause the circuit to be closed
            metrics.MarkSuccess();
            cb.MarkSuccess();

            // all requests should be open again
            Assert.IsTrue(cb.AllowRequest());
            Assert.IsTrue(cb.AllowRequest());
            Assert.IsTrue(cb.AllowRequest());
            // and the circuit should be closed again
            Assert.IsFalse(cb.IsOpen());
        }
        public void TestGetErrorPercentage()
        {
            var config = GetCommandConfig();

            ICommandMetrics metrics = GetMetrics(config);

            metrics.MarkSuccess(1000);
            Thread.Sleep(config.MetricsHealthSnapshotIntervalInMilliseconds);
            Assert.AreEqual(0, metrics.GetErrorPercentage());

            metrics.MarkFailure();
            Thread.Sleep(config.MetricsHealthSnapshotIntervalInMilliseconds);

            Assert.AreEqual(50, metrics.GetErrorPercentage());

            metrics.MarkSuccess(100);
            metrics.MarkSuccess(100);
            Thread.Sleep(config.MetricsHealthSnapshotIntervalInMilliseconds);
            Assert.AreEqual(25, metrics.GetErrorPercentage());

            metrics.MarkTimeout(5000);
            metrics.MarkTimeout(5000);
            Thread.Sleep(config.MetricsHealthSnapshotIntervalInMilliseconds);
            Assert.AreEqual(50, metrics.GetErrorPercentage());

            metrics.MarkSuccess(100);
            metrics.MarkSuccess(100);
            metrics.MarkSuccess(100);

            // latent
            Thread.Sleep(config.MetricsHealthSnapshotIntervalInMilliseconds);
            metrics.MarkSuccess(5000);
            Thread.Sleep(config.MetricsHealthSnapshotIntervalInMilliseconds);

            // 6 success + 1 latent success + 1 failure + 2 timeout = 10 total
            // latent success not considered error
            // error percentage = 1 failure + 2 timeout / 10
            Assert.AreEqual(30, metrics.GetErrorPercentage());
        }
        public void TestMultipleTimeWindowRetriesBeforeClosingCircuit()
        {
            int sleepWindow = 200;
            var properties  = GetCommandConfig();

            properties.CircuitBreakerSleepWindowInMilliseconds = sleepWindow;
            ICommandMetrics metrics = GetMetrics(properties);
            var             cb      = new TestCircuitBreaker(properties, metrics);

            // fail
            metrics.MarkFailure();
            metrics.MarkFailure();
            metrics.MarkFailure();
            metrics.MarkFailure();

            // everything has failed in the test window so we should return false now
            Assert.IsFalse(cb.AllowRequest());
            Assert.IsTrue(cb.IsOpen());

            // wait for sleepWindow to pass
            Thread.Sleep(sleepWindow + 50);

            // we should now allow 1 request
            Assert.IsTrue(cb.AllowRequest());
            // but the circuit should still be open
            Assert.IsTrue(cb.IsOpen());
            // and further requests are still blocked
            Assert.IsFalse(cb.AllowRequest());

            // the 'singleTest' fails so it should go back to sleep and not allow any requests again until another 'singleTest' after the sleep
            metrics.MarkFailure();

            Assert.IsFalse(cb.AllowRequest());
            Assert.IsFalse(cb.AllowRequest());
            Assert.IsFalse(cb.AllowRequest());

            // wait for sleepWindow to pass
            Thread.Sleep(sleepWindow + 50);

            // we should now allow 1 request
            Assert.IsTrue(cb.AllowRequest());
            // but the circuit should still be open
            Assert.IsTrue(cb.IsOpen());
            // and further requests are still blocked
            Assert.IsFalse(cb.AllowRequest());

            // the 'singleTest' fails again so it should go back to sleep and not allow any requests again until another 'singleTest' after the sleep
            metrics.MarkFailure();


            Assert.IsFalse(cb.AllowRequest());
            Assert.IsFalse(cb.AllowRequest());
            Assert.IsFalse(cb.AllowRequest());

            // wait for sleepWindow to pass
            Thread.Sleep(sleepWindow + 50);


            // we should now allow 1 request
            Assert.IsTrue(cb.AllowRequest());
            // but the circuit should still be open
            Assert.IsTrue(cb.IsOpen());
            // and further requests are still blocked
            Assert.IsFalse(cb.AllowRequest());

            // now it finally succeeds
            metrics.MarkSuccess();
            cb.MarkSuccess();

            // all requests should be open again
            Assert.IsTrue(cb.AllowRequest());
            Assert.IsTrue(cb.AllowRequest());
            Assert.IsTrue(cb.AllowRequest());
            // and the circuit should be closed again
            Assert.IsFalse(cb.IsOpen());
        }