org.eclipse.microprofile.fault.tolerance.tck.AsynchronousCSTest Maven / Gradle / Ivy
Show all versions of microprofile-fault-tolerance-tck Show documentation
/*
*******************************************************************************
* Copyright (c) 2016-2018 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.microprofile.fault.tolerance.tck.asynchronous.AsyncClassLevelClient;
import org.eclipse.microprofile.fault.tolerance.tck.asynchronous.AsyncClient;
import org.eclipse.microprofile.fault.tolerance.tck.asynchronous.CompletableFutureHelper;
import org.eclipse.microprofile.fault.tolerance.tck.util.Connection;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.Assert;
import org.testng.Assert.ThrowingRunnable;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
import jakarta.inject.Inject;
/**
* Verify the asynchronous invocation with CompletionStage
*
* @author Ondro Mihalyi
*/
public class AsynchronousCSTest extends Arquillian {
private @Inject AsyncClient client;
private @Inject AsyncClassLevelClient clientClass;
private List> waitingFutures = new ArrayList<>();
@Deployment
public static WebArchive deploy() {
JavaArchive testJar = ShrinkWrap
.create(JavaArchive.class, "ftAsynchronous.jar")
.addClasses(AsyncClient.class, AsyncClassLevelClient.class, Connection.class,
CompletableFutureHelper.class)
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
.as(JavaArchive.class);
WebArchive war = ShrinkWrap.create(WebArchive.class, "ftAsynchronous.war").addAsLibrary(testJar);
return war;
}
/**
* Test that the stage returned by calling an asynchronous method is not done if called right after the operation
*/
@Test
public void testAsyncIsNotFinished() {
CompletableFuture waitingFuture = newWaitingFuture();
CompletionStage resultFuture = client.serviceCS(waitingFuture);
Assert.assertFalse(completesQuickly(resultFuture));
complete(waitingFuture);
try {
waitUntilCompleted(resultFuture);
} catch (CompletionException e) {
handleCompletionException(e);
}
}
/**
* Test that the stage returned by calling an asynchronous method is done if called after waiting enough time to end
* the operation
*/
@Test
public void testAsyncIsFinished() {
CompletableFuture waitingFuture = newWaitingFuture();
CompletionStage resultFuture = client.serviceCS(waitingFuture);
complete(waitingFuture);
Assert.assertTrue(completesQuickly(resultFuture));
try {
waitUntilCompleted(resultFuture);
} catch (CompletionException e) {
handleCompletionException(e);
}
}
/**
* Test that the stage returned by calling a method in an asynchronous class is not done if called right after the
* operation
*/
@Test
public void testClassLevelAsyncIsNotFinished() {
CompletableFuture waitingFuture = newWaitingFuture();
CompletionStage resultFuture = clientClass.serviceCS(waitingFuture);
Assert.assertFalse(completesQuickly(resultFuture));
complete(waitingFuture);
try {
waitUntilCompleted(resultFuture);
} catch (CompletionException e) {
handleCompletionException(e);
}
}
/**
* Test that the stage returned by calling a method in an asynchronous class is done if called after waiting enough
* time to end the operation
*/
@Test
public void testClassLevelAsyncIsFinished() {
CompletableFuture waitingFuture = newWaitingFuture();
CompletionStage resultFuture = clientClass.serviceCS(waitingFuture);
complete(waitingFuture);
Assert.assertTrue(completesQuickly(resultFuture));
try {
waitUntilCompleted(resultFuture);
} catch (CompletionException e) {
handleCompletionException(e);
}
}
/**
* Test that the callbacks added to the initial stage are executed after the stage returned by the asynchronous
* method call is completed.
*
* The callbacks added inside method invocation must be called first and then callbacks added to the result of the
* call (on the calling thread) must be executed in the order they were added.
*/
@Test
public void testAsyncCallbacksChained() {
CompletableFuture waitingFuture = newWaitingFuture();
StringBuilder executionRecord = new StringBuilder();
CompletableFuture innerFuture = new CompletableFuture();
CompletableFuture returnedFuture = innerFuture
.thenApply(v -> {
executionRecord.append("1");
return v;
});
CompletionStage resultFuture = client
.serviceCS(waitingFuture, returnedFuture)
.thenApply(v -> {
executionRecord.append("2");
return v;
});
complete(waitingFuture);
resultFuture = resultFuture.thenApply(v -> {
executionRecord.append("3");
return v;
});
Assert.assertFalse(completesQuickly(resultFuture),
"Stage returned by the method isn't completed yet so also the outer stage mustn't be completed");
innerFuture.complete(new EmptyConnection());
Assert.assertTrue(completesQuickly(resultFuture),
"Stage returned by the method is completed so also the outer stage must be completed");
Assert.assertEquals(executionRecord.toString(), "123",
"The execution didn't happen in the expected order");
try {
waitUntilCompleted(resultFuture);
} catch (CompletionException e) {
handleCompletionException(e);
}
}
/**
* Test that the stage returned by calling an asynchronous method is completed exceptionally if the method throws an
* exception
*/
@Test
public void testAsyncCompletesExceptionallyWhenExceptionThrown() {
CompletableFuture waitingFuture = newWaitingFuture();
CompletionStage resultFuture = client.serviceCS(waitingFuture, true);
waitingFuture.completeExceptionally(new SimulatedException("completedExceptionally"));
Assert.assertTrue(completesQuickly(resultFuture));
Assert.assertTrue(isCompletedExceptionally(resultFuture));
Assert.assertFalse(isCancelled(resultFuture));
assertThrowsExecutionExceptionWithCause(SimulatedException.class,
CompletableFutureHelper.toCompletableFuture(resultFuture)::get);
}
/**
* Test that the stage returned by calling an asynchronous method is completed exceptionally if the method returns a
* stage completed exceptionally
*/
@Test
public void testAsyncCompletesExceptionallyWhenCompletedExceptionally() {
CompletableFuture waitingFuture = newWaitingFuture();
CompletionStage resultFuture = client.serviceCS(waitingFuture, false);
waitingFuture.completeExceptionally(new SimulatedException("completedExceptionally"));
Assert.assertTrue(completesQuickly(resultFuture));
Assert.assertTrue(isCompletedExceptionally(resultFuture));
Assert.assertFalse(isCancelled(resultFuture));
assertThrowsExecutionExceptionWithCause(SimulatedException.class,
CompletableFutureHelper.toCompletableFuture(resultFuture)::get);
}
/**
* Ensure that any waiting futures get completed at the end of each test
*
* Important in case tests end early due to an exception or failure.
*/
@AfterMethod
public void completeWaitingFutures() {
waitingFutures.forEach((future) -> {
future.complete(null);
});
waitingFutures.clear();
}
/**
* Use this method to obtain futures for passing to methods on {@link AsyncClient}
*
* Using this factory method ensures they will be completed at the end of the test if your test fails.
*/
private CompletableFuture newWaitingFuture() {
CompletableFuture result = new CompletableFuture<>();
waitingFutures.add(result);
return result;
}
/**
* A helper method to complete a waiting future with a more readable syntax
*/
private void complete(CompletableFuture> future) {
future.complete(null);
}
private void handleCompletionException(CompletionException e) throws AssertionError {
throw new AssertionError("testAsync: unexpected Exception calling service: "
+ e.getCause().getMessage(), e);
}
private static Connection waitUntilCompleted(CompletionStage resultFuture) {
return CompletableFutureHelper.toCompletableFuture(resultFuture).join();
}
/**
* Tests whether the given CompletionStage completes within 500ms
*
* Used to avoid a race condition where a test wants to take some action which should cause the CompletionStage to
* complete, but asynchronous execution means it may not happen immediately.
*/
private static boolean completesQuickly(CompletionStage resultFuture) {
try {
CompletableFutureHelper.toCompletableFuture(resultFuture).get(500, TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
// Did not complete quickly
return false;
} catch (Exception ex) {
// Completed with exception, fall through, we don't care about the result
}
return true;
}
private static boolean isCancelled(CompletionStage resultFuture) {
return CompletableFutureHelper.toCompletableFuture(resultFuture).isCancelled();
}
private static boolean isCompletedExceptionally(CompletionStage resultFuture) {
return CompletableFutureHelper.toCompletableFuture(resultFuture).isCompletedExceptionally();
}
private static void assertThrowsExecutionExceptionWithCause(Class extends Throwable> causeClazz,
ThrowingRunnable runnable) {
try {
runnable.run();
Assert.fail("ExecutionException not thrown");
} catch (ExecutionException ex) {
Assert.assertTrue(causeClazz.isInstance(ex.getCause()), "Cause of ExecutionException was " + ex.getCause());
} catch (Throwable ex) {
Assert.fail("Unexpected exception thrown", ex);
}
}
private static class SimulatedException extends RuntimeException {
public SimulatedException() {
}
public SimulatedException(String message) {
super(message);
}
}
private static class EmptyConnection implements Connection {
@Override
public String getData() {
return null;
}
}
}