Skip to content

Commit

Permalink
Merge pull request #560 from Ladicek/circuit-breaker-config-test
Browse files Browse the repository at this point in the history
add TCK tests for configuring @CIRCUITBREAKER attributes
  • Loading branch information
Ladicek authored Jun 19, 2020
2 parents cb36d09 + b501d5d commit 07589e2
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,72 @@
import org.eclipse.microprofile.faulttolerance.CircuitBreaker;

import javax.enterprise.context.Dependent;
import java.time.temporal.ChronoUnit;

/**
* Suite of methods for testing the various parameters of CircuitBreaker
*/
@Dependent
public class CircuitBreakerConfigBean {

/**
* Method throws TestConfigExceptionA which will result in CircuitBreakerOpenException on the third call, unless skipOn is configured
* Method throws TestConfigExceptionA which will NOT result in CircuitBreakerOpenException on the third call,
* unless failOn is configured to TestConfigExceptionA.
*/
@CircuitBreaker(requestVolumeThreshold = 2, failOn = TestConfigExceptionB.class)
public void failOnMethod() {
throw new TestConfigExceptionA();
}

/**
* Method throws TestConfigExceptionA which will result in CircuitBreakerOpenException on the third call,
* unless skipOn is configured to TestConfigExceptionA.
*/
@CircuitBreaker(requestVolumeThreshold = 2)
public void skipOnMethod() {
throw new TestConfigExceptionA();
}

/**
* This method's circuit breaker moves from open to half-open after 10 micros,
* unless delay and delayUnit are configured differently.
*/
@CircuitBreaker(requestVolumeThreshold = 2, delay = 20, delayUnit = ChronoUnit.MICROS)
public void delayMethod(boolean fail) {
if (fail) {
throw new TestConfigExceptionA();
}
}

/**
* Method throws TestConfigExceptionA which will result in CircuitBreakerOpenException on the third call,
* unless requestVolumeThreshold is configured to a greater number.
*/
@CircuitBreaker(requestVolumeThreshold = 2)
public void requestVolumeThresholdMethod() {
throw new TestConfigExceptionA();
}

/**
* This method's circuit breaker moves from closed to open after 10 consecutive failures,
* unless failureRatio is configured differently.
*/
@CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 1.0)
public void failureRatioMethod(boolean fail) {
if (fail) {
throw new TestConfigExceptionA();
}
}

/**
* This method's circuit breaker moves from half-open to closed after 4 consecutive successes,
* unless successThreshold is configured differently.
*/
@CircuitBreaker(requestVolumeThreshold = 10, successThreshold = 4, delay = 1000)
public void successThresholdMethod(boolean fail) {
if (fail) {
throw new TestConfigExceptionA();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@

package org.eclipse.microprofile.fault.tolerance.tck.config;

import javax.inject.Inject;

import org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
import org.eclipse.microprofile.fault.tolerance.tck.util.TCKConfig;
import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
Expand All @@ -33,40 +31,188 @@
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.Test;

import javax.inject.Inject;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

import static org.awaitility.Awaitility.await;
import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expect;
import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expectCbOpen;
import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expectNoException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan;

/**
* Test configuration of parameters of {@link CircuitBreaker}
*/
public class CircuitBreakerConfigTest extends Arquillian {

@Deployment
public static WebArchive create() {
ConfigAnnotationAsset config = new ConfigAnnotationAsset();
config.set(CircuitBreakerConfigBean.class, "skipOnMethod", CircuitBreaker.class, "skipOn", TestConfigExceptionA.class.getCanonicalName());

ConfigAnnotationAsset config = new ConfigAnnotationAsset()
.set(CircuitBreakerConfigBean.class, "skipOnMethod", CircuitBreaker.class,
"skipOn", TestConfigExceptionA.class.getName())
.set(CircuitBreakerConfigBean.class, "failOnMethod", CircuitBreaker.class,
"failOn", TestConfigExceptionA.class.getName())
.set(CircuitBreakerConfigBean.class, "delayMethod", CircuitBreaker.class,
"delay", TCKConfig.getConfig().getTimeoutInStr(1000))
.set(CircuitBreakerConfigBean.class, "delayMethod", CircuitBreaker.class,
"delayUnit", "MILLIS")
.set(CircuitBreakerConfigBean.class, "requestVolumeThresholdMethod", CircuitBreaker.class,
"requestVolumeThreshold", "4")
.set(CircuitBreakerConfigBean.class, "failureRatioMethod", CircuitBreaker.class,
"failureRatio","0.8")
.set(CircuitBreakerConfigBean.class, "successThresholdMethod", CircuitBreaker.class,
"successThreshold", "2")
// only changing value here to scale the original, not for the purpose of this test
.set(CircuitBreakerConfigBean.class, "successThresholdMethod", CircuitBreaker.class,
"delay", TCKConfig.getConfig().getTimeoutInStr(1000));

JavaArchive jar = ShrinkWrap
.create(JavaArchive.class, "ftCircuitBreakerConfig.jar")
.addPackage(CircuitBreakerConfigTest.class.getPackage())
.addClasses(CircuitBreakerConfigBean.class, TestConfigExceptionA.class, TestConfigExceptionB.class)
.addPackage(Packages.UTILS)
.addAsManifestResource(config, "microprofile-config.properties")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
WebArchive war = ShrinkWrap

return ShrinkWrap
.create(WebArchive.class, "ftCircuitBreakerConfig.war")
.addAsLibraries(jar);

return war;
}

@Inject
private CircuitBreakerConfigBean bean;


@Test
public void testConfigureFailOn() {
// In annotation: requestVolumeThreshold = 2
// skipOn = {}
// failOn = {TestConfigExceptionB.class}
// In config: failOn = {TestConfigExceptionA.class}

expect(TestConfigExceptionA.class, () -> bean.failOnMethod());
expect(TestConfigExceptionA.class, () -> bean.failOnMethod());

// If failOn is not configured to include TestConfigExceptionA, this would throw a TestConfigExceptionA
expectCbOpen(() -> bean.failOnMethod());
}

@Test
public void testConfigureSkipOn() {
Exceptions.expect(TestConfigExceptionA.class, () -> bean.skipOnMethod());
Exceptions.expect(TestConfigExceptionA.class, () -> bean.skipOnMethod());

// In annotation: requestVolumeThreshold = 2
// failOn = {Throwable.class}
// skipOn = {}
// In config: skipOn = {TestConfigExceptionA.class}

expect(TestConfigExceptionA.class, () -> bean.skipOnMethod());
expect(TestConfigExceptionA.class, () -> bean.skipOnMethod());

// If skipOn is not configured to include TestConfigExceptionA, this would throw a CircuitBreakerOpenException
Exceptions.expect(TestConfigExceptionA.class, () -> bean.skipOnMethod());
expect(TestConfigExceptionA.class, () -> bean.skipOnMethod());
}

@Test
public void testConfigureDelay() {
// In annotation: requestVolumeThreshold = 2
// delay = 20
// delayUnit = MICROS
// In config: delay = 1000
// delayUnit = MILLIS

expect(TestConfigExceptionA.class, () -> bean.delayMethod(true));
expect(TestConfigExceptionA.class, () -> bean.delayMethod(true));

expectCbOpen(() -> bean.delayMethod(false));
// CB is now open, wait until it moves to half-open (and time how long that took)

long start = System.nanoTime();
await().atMost(1, TimeUnit.MINUTES).untilAsserted(() -> {
expectNoException(() -> bean.delayMethod(false));
});
long end = System.nanoTime();

long durationInMillis = Duration.ofNanos(end - start).toMillis();
assertThat(durationInMillis, greaterThan(TCKConfig.getConfig().getTimeoutInMillis(800)));
assertThat(durationInMillis, lessThan(TCKConfig.getConfig().getTimeoutInMillis(2000)));
}


@Test
public void testConfigureRequestVolumeThreshold() {
// In annotation: requestVolumeThreshold = 2
// In config: requestVolumeThreshold = 4

expect(TestConfigExceptionA.class, () -> bean.requestVolumeThresholdMethod());
expect(TestConfigExceptionA.class, () -> bean.requestVolumeThresholdMethod());

// If requestVolumeThreshold is not configured to 4, this would throw a CircuitBreakerOpenException
expect(TestConfigExceptionA.class, () -> bean.requestVolumeThresholdMethod());
expect(TestConfigExceptionA.class, () -> bean.requestVolumeThresholdMethod());

// If requestVolumeThreshold is not configured to 4, this would NOT throw a CircuitBreakerOpenException
expectCbOpen(() -> bean.requestVolumeThresholdMethod());
}

@Test
public void testConfigureFailureRatio() {
// In annotation: requestVolumeThreshold = 10
// failureRatio = 1.0
// In config: failureRatio = 0.8

expect(TestConfigExceptionA.class, () -> bean.failureRatioMethod(true));
expect(TestConfigExceptionA.class, () -> bean.failureRatioMethod(true));
expectNoException(() -> bean.failureRatioMethod(false));
expect(TestConfigExceptionA.class, () -> bean.failureRatioMethod(true));
expect(TestConfigExceptionA.class, () -> bean.failureRatioMethod(true));
expect(TestConfigExceptionA.class, () -> bean.failureRatioMethod(true));
expectNoException(() -> bean.failureRatioMethod(false));
expect(TestConfigExceptionA.class, () -> bean.failureRatioMethod(true));
expect(TestConfigExceptionA.class, () -> bean.failureRatioMethod(true));
expect(TestConfigExceptionA.class, () -> bean.failureRatioMethod(true));

// If failureRatio is not configured to 0.8, this would NOT throw a CircuitBreakerOpenException:
// - if failureRatio > 0.8, this would throw TestConfigExceptionA
// - if failureRatio < 0.8, CircuitBreakerOpenException would be thrown sooner
expectCbOpen(() -> bean.failureRatioMethod(false));
}

@Test
public void testConfigureSuccessThreshold() {
// In annotation: delay = 1000
// requestVolumeThreshold = 10
// successThreshold = 4
// In config: successThreshold = 2

for (int i = 0; i < 10; i++) {
expect(TestConfigExceptionA.class, () -> bean.successThresholdMethod(true));
}

// CB is now open, wait until it moves to half-open
expectCbOpen(() -> bean.successThresholdMethod(false));
await().atMost(1, TimeUnit.MINUTES).untilAsserted(() -> {
expectNoException(() -> bean.successThresholdMethod(false));
});

// CB is now half-open and 1st successful invocation already occured
expectNoException(() -> bean.successThresholdMethod(false));

// 2nd successful invocation occured, CB is now closed
for (int i = 0; i < 10; i++) {
// 10 because after moving to close, we start with a new, empty rolling window
expect(TestConfigExceptionA.class, () -> bean.successThresholdMethod(true));
}

// CB is now open, wait until it moves to half-open
expectCbOpen(() -> bean.successThresholdMethod(false));
await().atMost(1, TimeUnit.MINUTES).untilAsserted(() -> {
expectNoException(() -> bean.successThresholdMethod(false));
});

// CB is now half-open and 1st successful invocation already occured
expect(TestConfigExceptionA.class, () -> bean.successThresholdMethod(true));

// 2nd invocation was a failure, CB is back to open
expectCbOpen(() -> bean.successThresholdMethod(false));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,21 @@ public static void expect(Class<? extends Exception> expectedException, Exceptio
}
}
}


/**
* Run an action and ensure that no exception is thrown
*
* @param action the action to run
*/
public static void expectNoException(ExceptionThrowingAction action) {
try {
action.call();
}
catch (Exception e) {
fail("Unexpected exception thrown", e);
}
}

@FunctionalInterface
public static interface ExceptionThrowingAction {
public void call() throws Exception;
Expand Down

0 comments on commit 07589e2

Please sign in to comment.