/******************************************************************************* * Copyright (c) 2016 Bruno Medeiros and other Contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package melnorme.utilbox.concurrency; import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.junit.After; import org.junit.Test; import melnorme.utilbox.concurrency.ExecutorTaskAgent_Test.Tests_ExecutorTaskAgent; import melnorme.utilbox.core.fntypes.Result; import melnorme.utilbox.core.fntypes.SupplierExt; import melnorme.utilbox.tests.CommonTest; public abstract class Futures_Tests extends CommonTest { /** A runnable that is never meant to be run. */ public static class InvalidRunnable extends LatchRunnable2 { public InvalidRunnable() { super(); } @Override protected void doRun() { System.exit(1); } } public static class InterruptibleEndlessRunnable extends LatchRunnable2 { protected volatile boolean interrupted = false; public InterruptibleEndlessRunnable() { super(); } @Override protected void doRun() { try { new CountDownLatch(1).await(); } catch(InterruptedException e) { interrupted = true; } } } /* ----------------- ----------------- */ public static abstract class AbstractFutureTest<FUTURE extends IRunnableFuture2<?>> extends CommonTest { protected Tests_ExecutorTaskAgent executor; protected void init_Executor() { teardown(); executor = new Tests_ExecutorTaskAgent(getClass().getSimpleName()); future = null; } @After public void teardown() { if(executor != null) { executor.shutdownNow(); executor = null; } } @Test public void test() throws Exception { test$(); } public void test$() throws Exception { for(int i = 0; i < 10; i++) { test_result(); testCancellation(); } } protected FUTURE future; protected FUTURE getFuture() { return future; } protected FUTURE initFuture_fromRunnable(Runnable runnable) { FUTURE future = initFuture(() -> { runnable.run(); return null; }); assertTrue(future.isTerminated() == false); return future; } protected abstract FUTURE initFuture(SupplierExt<Object> callable); public void test_result() throws Exception { // Test await result FUTURE future = submitToExecutor(() -> "result"); checkResult(future, "result"); assertTrue(future.tryCancel() == false); assertTrue(future.isCompletedSuccessfully()); checkResult(future, "result"); // Test RuntimeException handling test_result_forRuntimeException(); } protected void test_result_forRuntimeException() { verifyThrows(() -> { submitAndAwaitResult(() -> { throw new RuntimeException("xxx2"); }); }, RuntimeException.class, "xxx2"); } protected void checkResult(FUTURE future, Object result) throws OperationCancellation, InterruptedException, TimeoutException { assertEquals(result, future.awaitResult()); Thread.currentThread().interrupt(); assertEquals(result, future.getResult_forSuccessfulyCompleted()); Thread.currentThread().interrupt(); assertEquals(result, future.getResult_forTerminated()); Thread.currentThread().interrupt(); assertEquals(result, future.awaitResult()); Thread.currentThread().interrupt(); assertEquals(result, future.awaitResult(1000, TimeUnit.DAYS)); assertTrue(Thread.interrupted()); assertTrue(Thread.currentThread().isInterrupted() == false); } protected FUTURE submitToExecutor(SupplierExt<Object> callable) { FUTURE future = initFuture(callable); submitToExecutor(future, ForkJoinPool.commonPool()); return future; } protected void submitToExecutor(FUTURE future, ExecutorService executor) { assertTrue(future.canExecute()); executor.execute(future); } protected <EXC extends Exception> FUTURE submitAndAwaitResult(SupplierExt<Object> callable) throws OperationCancellation, InterruptedException, EXC { FUTURE future = submitToExecutor(callable); future.awaitResult(); return future; } protected void doSubmitFuture(FUTURE future) { submitToExecutor(future, executor); } /* ----------------- ----------------- */ protected void testCancellation() { test_cancellation_before_running(); test_cancellation_of_running_task(); test_cancellation_of_waiting_task(); } protected void test_cancellation_before_running() { init_Executor(); FUTURE future = initFuture_fromRunnable(new InvalidRunnable()); cancelFuture(future); assertTrue(future.isCancelled()); runFuture(future); } protected void runFuture(FUTURE future) { future.run(); } protected void test_cancellation_of_running_task() { init_Executor(); LatchRunnable2 latchRunnable = new InterruptibleEndlessRunnable(); submitRunnableFuture(latchRunnable); latchRunnable.awaitEntry(); cancelFuture(); testExpectInterruptionOfRunningTask(latchRunnable); } protected void test_cancellation_of_waiting_task() { init_Executor(); executor.submit(new InterruptibleEndlessRunnable()); submitRunnableFuture(new InterruptibleEndlessRunnable()); cancelFuture(); } protected void submitRunnableFuture(LatchRunnable2 latchRunnable) { future = initFuture_fromRunnable(latchRunnable); doSubmitFuture(future); } protected void cancelFuture() { cancelFuture(getFuture()); } public void cancelFuture(Future2<?> future2) { future2.tryCancel(); verifyThrows(() -> future2.awaitResult2(), OperationCancellation.class); } protected void testExpectInterruptionOfRunningTask(LatchRunnable2 latchRunnable) { latchRunnable.awaitExit(); } } /* ----------------- ----------------- */ public static class MonitorFuture_Test extends AbstractFutureTest<MonitorRunnableFuture<Object>> { @Override protected MonitorRunnableFuture<Object> initFuture(SupplierExt<Object> callable) { return new MonitorRunnableFuture<Object>() { @Override protected Object internalInvoke() { return callable.call(); } }; } } public static class RunnableFuture2_Test extends AbstractFutureTest<IRunnableFuture2<?>> { @Override protected IRunnableFuture2<Object> initFuture(SupplierExt<Object> callable) { return IRunnableFuture2.toFuture(callable); } @Override public void test_result() throws Exception { super.test_result(); IRunnableFuture2<Result<Object, RuntimeException>> future = IRunnableFuture2.toResultFuture(() -> { throw new RuntimeException("xxx2"); }); submitToExecutor(future, ForkJoinPool.commonPool()); Result<Object, RuntimeException> result = future.awaitResult(); verifyThrows(() -> { result.get(); }, RuntimeException.class, "xxx2"); } } public static class Future2Adapter_Test extends AbstractFutureTest<RunnableFutureAdapter<Object>> { @Override protected RunnableFutureAdapter<Object> initFuture(SupplierExt<Object> callable) { return new RunnableFutureAdapter<>(new FutureTask<>(callable)); } @Override protected void checkResult(RunnableFutureAdapter<Object> future, Object result) throws OperationCancellation, InterruptedException, TimeoutException { super.checkResult(future, new Result<>(result)); } @Override protected void test_result_forRuntimeException() { verifyThrows(() -> { submitAndAwaitResult(() -> { throw new RuntimeException("xxx2"); }); }, null); } @Test public void test_errors() throws Exception { test_errors$(); } public void test_errors$() throws Exception { CompletableFuture<String> completableFuture = new CompletableFuture<>(); FutureAdapter<String> future2Adapter = new FutureAdapter<>(completableFuture); assertTrue(future2Adapter.isCompletedSuccessfully() == false); completableFuture.completeExceptionally(new RuntimeException("XXX")); assertTrue(future2Adapter.isTerminated() == true); assertTrue(future2Adapter.isCompletedSuccessfully() == true); Result<String, Throwable> awaitResult = future2Adapter.awaitResult(); verifyThrows(awaitResult::get, RuntimeException.class, "XXX"); } } }