/*
*
* Copyright 2016 Robert Winkler and Bohdan Storozhuk
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*/
package io.github.resilience4j.ratelimiter;
import static com.jayway.awaitility.Awaitility.await;
import static org.assertj.core.api.BDDAssertions.then;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static io.vavr.API.$;
import static io.vavr.API.Case;
import static io.vavr.API.Match;
import static io.vavr.Predicates.instanceOf;
import static java.util.concurrent.CompletableFuture.supplyAsync;
import org.junit.Before;
import org.junit.Test;
import org.mockito.BDDMockito;
import io.vavr.CheckedFunction0;
import io.vavr.CheckedFunction1;
import io.vavr.CheckedRunnable;
import io.vavr.control.Try;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@SuppressWarnings("unchecked")
public class RateLimiterTest {
private static final int LIMIT = 50;
private static final Duration TIMEOUT = Duration.ofSeconds(5);
private static final Duration REFRESH_PERIOD = Duration.ofNanos(500);
private RateLimiterConfig config;
private RateLimiter limit;
@Before
public void init() {
config = RateLimiterConfig.custom()
.timeoutDuration(TIMEOUT)
.limitRefreshPeriod(REFRESH_PERIOD)
.limitForPeriod(LIMIT)
.build();
limit = mock(RateLimiter.class);
when(limit.getRateLimiterConfig())
.thenReturn(config);
}
@Test
public void decorateCheckedSupplier() throws Throwable {
CheckedFunction0 supplier = mock(CheckedFunction0.class);
CheckedFunction0 decorated = RateLimiter.decorateCheckedSupplier(limit, supplier);
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(false);
Try decoratedSupplierResult = Try.of(decorated);
then(decoratedSupplierResult.isFailure()).isTrue();
then(decoratedSupplierResult.getCause()).isInstanceOf(RequestNotPermitted.class);
verify(supplier, never()).apply();
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(true);
Try secondSupplierResult = Try.of(decorated);
then(secondSupplierResult.isSuccess()).isTrue();
verify(supplier, times(1)).apply();
}
@Test
public void decorateCheckedRunnable() throws Throwable {
CheckedRunnable runnable = mock(CheckedRunnable.class);
CheckedRunnable decorated = RateLimiter.decorateCheckedRunnable(limit, runnable);
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(false);
Try decoratedRunnableResult = Try.run(decorated);
then(decoratedRunnableResult.isFailure()).isTrue();
then(decoratedRunnableResult.getCause()).isInstanceOf(RequestNotPermitted.class);
verify(runnable, never()).run();
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(true);
Try secondRunnableResult = Try.run(decorated);
then(secondRunnableResult.isSuccess()).isTrue();
verify(runnable, times(1)).run();
}
@Test
public void decorateCheckedFunction() throws Throwable {
CheckedFunction1<Integer, String> function = mock(CheckedFunction1.class);
CheckedFunction1<Integer, String> decorated = RateLimiter.decorateCheckedFunction(limit, function);
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(false);
Try<String> decoratedFunctionResult = Try.success(1).mapTry(decorated);
then(decoratedFunctionResult.isFailure()).isTrue();
then(decoratedFunctionResult.getCause()).isInstanceOf(RequestNotPermitted.class);
verify(function, never()).apply(any());
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(true);
Try secondFunctionResult = Try.success(1).mapTry(decorated);
then(secondFunctionResult.isSuccess()).isTrue();
verify(function, times(1)).apply(1);
}
@Test
public void decorateSupplier() throws Exception {
Supplier supplier = mock(Supplier.class);
Supplier decorated = RateLimiter.decorateSupplier(limit, supplier);
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(false);
Try decoratedSupplierResult = Try.success(decorated).map(Supplier::get);
then(decoratedSupplierResult.isFailure()).isTrue();
then(decoratedSupplierResult.getCause()).isInstanceOf(RequestNotPermitted.class);
verify(supplier, never()).get();
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(true);
Try secondSupplierResult = Try.success(decorated).map(Supplier::get);
then(secondSupplierResult.isSuccess()).isTrue();
verify(supplier, times(1)).get();
}
@Test
public void decorateConsumer() throws Exception {
Consumer<Integer> consumer = mock(Consumer.class);
Consumer<Integer> decorated = RateLimiter.decorateConsumer(limit, consumer);
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(false);
Try<Integer> decoratedConsumerResult = Try.success(1).andThen(decorated);
then(decoratedConsumerResult.isFailure()).isTrue();
then(decoratedConsumerResult.getCause()).isInstanceOf(RequestNotPermitted.class);
verify(consumer, never()).accept(any());
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(true);
Try secondConsumerResult = Try.success(1).andThen(decorated);
then(secondConsumerResult.isSuccess()).isTrue();
verify(consumer, times(1)).accept(1);
}
@Test
public void decorateRunnable() throws Exception {
Runnable runnable = mock(Runnable.class);
Runnable decorated = RateLimiter.decorateRunnable(limit, runnable);
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(false);
Try decoratedRunnableResult = Try.success(decorated).andThen(Runnable::run);
then(decoratedRunnableResult.isFailure()).isTrue();
then(decoratedRunnableResult.getCause()).isInstanceOf(RequestNotPermitted.class);
verify(runnable, never()).run();
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(true);
Try secondRunnableResult = Try.success(decorated).andThen(Runnable::run);
then(secondRunnableResult.isSuccess()).isTrue();
verify(runnable, times(1)).run();
}
@Test
public void decorateFunction() throws Exception {
Function<Integer, String> function = mock(Function.class);
Function<Integer, String> decorated = RateLimiter.decorateFunction(limit, function);
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(false);
Try<String> decoratedFunctionResult = Try.success(1).map(decorated);
then(decoratedFunctionResult.isFailure()).isTrue();
then(decoratedFunctionResult.getCause()).isInstanceOf(RequestNotPermitted.class);
verify(function, never()).apply(any());
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(true);
Try secondFunctionResult = Try.success(1).map(decorated);
then(secondFunctionResult.isSuccess()).isTrue();
verify(function, times(1)).apply(1);
}
@Test
public void decorateCompletionStage() throws Exception {
Supplier supplier = mock(Supplier.class);
BDDMockito.given(supplier.get()).willReturn("Resource");
Supplier<CompletionStage<String>> completionStage = () -> supplyAsync(supplier);
Supplier<CompletionStage<String>> decorated = RateLimiter.decorateCompletionStage(limit, completionStage);
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(false);
AtomicReference<Throwable> error = new AtomicReference<>(null);
CompletableFuture<String> notPermittedFuture = decorated.get()
.whenComplete((v, e) -> error.set(e))
.toCompletableFuture();
Try<String> errorResult = Try.of(notPermittedFuture::get);
assertTrue(errorResult.isFailure());
then(errorResult.getCause()).isInstanceOf(ExecutionException.class);
then(notPermittedFuture.isCompletedExceptionally()).isTrue();
then(error.get()).isExactlyInstanceOf(RequestNotPermitted.class);
verify(supplier, never()).get();
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(true);
AtomicReference<Throwable> shouldBeEmpty = new AtomicReference<>(null);
CompletableFuture<String> success = decorated.get()
.whenComplete((v, e) -> error.set(e))
.toCompletableFuture();
Try<String> successResult = Try.of(success::get);
then(successResult.isSuccess()).isTrue();
then(success.isCompletedExceptionally()).isFalse();
then(shouldBeEmpty.get()).isNull();
verify(supplier).get();
}
@Test
public void waitForPermissionWithOne() throws Exception {
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(true);
RateLimiter.waitForPermission(limit);
verify(limit, times(1))
.getPermission(config.getTimeoutDuration());
}
@Test(expected = RequestNotPermitted.class)
public void waitForPermissionWithoutOne() throws Exception {
when(limit.getPermission(config.getTimeoutDuration()))
.thenReturn(false);
RateLimiter.waitForPermission(limit);
verify(limit, times(1))
.getPermission(config.getTimeoutDuration());
}
@Test
public void waitForPermissionWithInterruption() throws Exception {
when(limit.getPermission(config.getTimeoutDuration()))
.then(invocation -> {
LockSupport.parkNanos(5_000_000_000L);
return null;
});
AtomicBoolean wasInterrupted = new AtomicBoolean(true);
Thread thread = new Thread(() -> {
wasInterrupted.set(false);
Throwable cause = Try.run(() -> RateLimiter.waitForPermission(limit))
.getCause();
Boolean interrupted = Match(cause).of(
Case($(instanceOf(IllegalStateException.class)), true)
);
wasInterrupted.set(interrupted);
});
thread.setDaemon(true);
thread.start();
await()
.atMost(5, TimeUnit.SECONDS)
.until(wasInterrupted::get, equalTo(false));
thread.interrupt();
await()
.atMost(5, TimeUnit.SECONDS)
.until(wasInterrupted::get, equalTo(true));
}
@Test
public void construction() throws Exception {
RateLimiter rateLimiter = RateLimiter.of("test", () -> config);
then(rateLimiter).isNotNull();
}
}