package com.linkedin.parseq; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.testng.annotations.Test; import com.linkedin.parseq.function.Failure; import com.linkedin.parseq.function.Function1; import com.linkedin.parseq.function.Success; import com.linkedin.parseq.function.Try; /** * @author Jaroslaw Odzga (jodzga@linkedin.com) */ public abstract class AbstractTaskTest extends BaseEngineTest { public void testMap(int expectedNumberOfTasks) { Task<Integer> task = getSuccessTask().map(String::length); runAndWait("AbstractTaskTest.testMap", task); assertTrue(task.isDone()); assertEquals((int) task.get(), TASK_VALUE.length()); assertEquals(countTasks(task.getTrace()), expectedNumberOfTasks); } public void testFlatMapFuncReturnNulll() { Task<String> task = getSuccessTask().flatMap(str -> null); runAndWaitException("AbstractTaskTest.testFlatMapFuncReturnNulll", task, RuntimeException.class); assertTrue(task.getError().getMessage().contains("returned null")); } public void testFlattenTaskReturnNulll() { Function1<String, Task<String>> func = s -> null; Task<String> task = Task.flatten(getSuccessTask().map(func)); runAndWaitException("AbstractTaskTest.testFlattenTaskReturnNulll", task, RuntimeException.class); assertTrue(task.getError().getMessage().contains("returned null")); } public void testRecoverWithFuncReturnNulll() { Task<String> task = getFailureTask().recoverWith(e -> null); runAndWaitException("AbstractTaskTest.testRecoverWithFuncReturnNulll", task, RuntimeException.class); assertTrue(task.getError().getMessage().contains("returned null")); } public void testWithSideEffectFuncReturnNulll() { Task<String> task = getSuccessTask().withSideEffect(str -> null); runAndWaitException("AbstractTaskTest.testWithSideEffectFuncReturnNulll", task, RuntimeException.class); assertTrue(task.getError().getMessage().contains("returned null")); } public void testFlatMap(int expectedNumberOfTasks) { Task<String> task = getSuccessTask().flatMap(str -> Task.callable("strlenstr", () -> String.valueOf(str.length()))); runAndWait("AbstractTaskTest.testFlatMap", task); assertEquals(task.get(), String.valueOf(TASK_VALUE.length())); assertEquals(countTasks(task.getTrace()), expectedNumberOfTasks); } public void testAndThenConsumer(int expectedNumberOfTasks) { final AtomicReference<String> variable = new AtomicReference<String>(); Task<String> task = getSuccessTask().andThen(variable::set); runAndWait("AbstractTaskTest.testAndThenConsumer", task); assertEquals(task.get(), TASK_VALUE); assertEquals(variable.get(), TASK_VALUE); assertEquals(countTasks(task.getTrace()), expectedNumberOfTasks); } public void testAndThenTask(int expectedNumberOfTasks) { Task<Integer> task = getSuccessTask().andThen(Task.callable("life", () -> 42)); runAndWait("AbstractTaskTest.testAndThenTask", task); assertEquals((int) task.get(), 42); assertEquals(countTasks(task.getTrace()), expectedNumberOfTasks); } public void testRecover(int expectedNumberOfTasks) { Task<Integer> success = getSuccessTask().map("strlen", String::length).recover(e -> -1); runAndWait("AbstractTaskTest.testRecoverSuccess", success); assertEquals((int) success.get(), TASK_VALUE.length()); assertEquals(countTasks(success.getTrace()), expectedNumberOfTasks); Task<Integer> failure = getFailureTask().map("strlen", String::length).recover(e -> -1); runAndWait("AbstractTaskTest.testRecoverFailure", failure); assertEquals((int) failure.get(), -1); assertEquals(countTasks(failure.getTrace()), expectedNumberOfTasks); } public void testCancelledRecover(int expectedNumberOfTasks) { Task<Integer> cancelled = getCancelledTask().map("strlen", String::length).recover(e -> -1); try { runAndWait("AbstractTaskTest.testCancelledRecover", cancelled); fail("should have failed"); } catch (Exception ex) { assertTrue(cancelled.isFailed()); assertTrue(Exceptions.isCancellation(cancelled.getError())); } assertEquals(countTasks(cancelled.getTrace()), expectedNumberOfTasks); } public void testNoRecover(int expectedNumberOfTasks) { Task<Integer> task = getFailureTask().map("strlen", String::length); try { runAndWait("AbstractTaskTest.testNoRecover", task); fail("should have failed"); } catch (Exception ex) { assertEquals(ex.getCause().getMessage(), TASK_ERROR_MESSAGE); } assertEquals(countTasks(task.getTrace()), expectedNumberOfTasks); } public void testToTry(int expectedNumberOfTasks) { Task<Try<Integer>> success = getSuccessTask().map("strlen", String::length).toTry(); runAndWait("AbstractTaskTest.testToTrySuccess", success); assertFalse(success.get().isFailed()); assertEquals((int) success.get().get(), TASK_VALUE.length()); assertEquals(countTasks(success.getTrace()), expectedNumberOfTasks); Task<Try<Integer>> failure = getFailureTask().map("strlen", String::length).toTry(); runAndWait("AbstractTaskTest.testToTryFailure", failure); assertTrue(failure.get().isFailed()); assertEquals(failure.get().getError().getMessage(), TASK_ERROR_MESSAGE); assertEquals(countTasks(failure.getTrace()), expectedNumberOfTasks); } @Test public void testToTryCancelled() throws InterruptedException { Task<String> cancelMain = delayedValue("value", 6000, TimeUnit.MILLISECONDS); Task<Try<String>> task = cancelMain.toTry(); run(task); assertTrue(cancelMain.cancel(new Exception("canceled"))); task.await(); assertTrue(task.isDone()); assertTrue(task.isFailed()); assertTrue(Exceptions.isCancellation(task.getError())); logTracingResults("AbstractTaskTest.testToTryCancelled", task); } public void testRecoverWithSuccess(int expectedNumberOfTasks) { Task<String> success = getSuccessTask().recoverWith(e -> Task.callable("recover failure", () -> { throw new RuntimeException("recover failed!"); } )); runAndWait("AbstractTaskTest.testRecoverWithSuccess", success); assertEquals(success.get(), TASK_VALUE); assertEquals(countTasks(success.getTrace()), expectedNumberOfTasks); } public void testRecoverWithFailure(int expectedNumberOfTasks) { Task<String> failure = getFailureTask().recoverWith(e -> Task.callable("recover failure", () -> { throw new RuntimeException("recover failed!"); } )); try { runAndWait("AbstractTaskTest.testRecoverWithFailure", failure); fail("should have failed"); } catch (Exception ex) { // fail with throwable from recovery function assertEquals(ex.getCause().getMessage(), "recover failed!"); } assertEquals(countTasks(failure.getTrace()), expectedNumberOfTasks); } public void testRecoverWithCancelled(int expectedNumberOfTasks) { Task<String> cancelled = getCancelledTask().recoverWith(e -> Task.callable("recover success", () -> "recovered")); try { runAndWait("AbstractTaskTest.testRecoverWithCancelled", cancelled); fail("should have failed"); } catch (Exception ex) { assertTrue(cancelled.isFailed()); assertTrue(Exceptions.isCancellation(cancelled.getError())); } assertEquals(countTasks(cancelled.getTrace()), expectedNumberOfTasks); } public void testRecoverWithRecoverd(int expectedNumberOfTasks) { Task<String> recovered = getFailureTask().recoverWith(e -> Task.callable("recover success", () -> "recovered")); runAndWait("AbstractTaskTest.testRecoverWithRecoverd", recovered); assertEquals(recovered.get(), "recovered"); assertEquals(countTasks(recovered.getTrace()), expectedNumberOfTasks); } @Test public void testWithTimeoutSuccess() { Task<Integer> success = getSuccessTask().andThen(delayedValue(0, 30, TimeUnit.MILLISECONDS)).withTimeout(100, TimeUnit.MILLISECONDS); runAndWait("AbstractTaskTest.testWithTimeoutSuccess", success); assertEquals((int) success.get(), 0); assertEquals(countTasks(success.getTrace()), 5); } @Test public void testWithTimeoutTwiceSuccess() { Task<Integer> success = getSuccessTask().andThen(delayedValue(0, 30, TimeUnit.MILLISECONDS)) .withTimeout(100, TimeUnit.MILLISECONDS).withTimeout(5000, TimeUnit.MILLISECONDS); runAndWait("AbstractTaskTest.testWithTimeoutTwiceSuccess", success); assertEquals((int) success.get(), 0); assertEquals(countTasks(success.getTrace()), 7); } @Test public void testWithTimeoutFailure() { Task<Integer> failure = getSuccessTask().andThen(delayedValue(0, 110, TimeUnit.MILLISECONDS)).withTimeout(100, TimeUnit.MILLISECONDS); try { runAndWait("AbstractTaskTest.testWithTimeoutFailure", failure); fail("should have failed!"); } catch (Exception ex) { assertEquals(ex.getCause().getClass(), Exceptions.TIMEOUT_EXCEPTION.getClass()); } assertEquals(countTasks(failure.getTrace()), 5); } @Test public void testWithTimeoutTwiceFailure() { Task<Integer> failure = getSuccessTask().andThen(delayedValue(0, 2000, TimeUnit.MILLISECONDS)) .withTimeout(5000, TimeUnit.MILLISECONDS).withTimeout(100, TimeUnit.MILLISECONDS); try { runAndWait("AbstractTaskTest.testWithTimeoutTwiceFailure", failure); fail("should have failed!"); } catch (Exception ex) { assertEquals(ex.getCause().getClass(), Exceptions.TIMEOUT_EXCEPTION.getClass()); } assertEquals(countTasks(failure.getTrace()), 7); } @Test public void testWithSideEffectPartial() { Task<String> fastMain = getSuccessTask(); Task<String> slowSideEffect = delayedValue("slooow", 5100, TimeUnit.MILLISECONDS); Task<String> partial = fastMain.withSideEffect(s -> slowSideEffect); // ensure the whole task can finish before individual side effect task finishes runAndWait("AbstractTaskTest.testWithSideEffectPartial", partial); assertTrue(fastMain.isDone()); assertTrue(partial.isDone()); assertFalse(slowSideEffect.isDone()); } public void testWithSideEffectFullCompletion(int expectedNumberOfTasks) throws Exception { Task<String> fastMain = getSuccessTask(); Task<String> slowSideEffect = delayedValue("slow", 50, TimeUnit.MILLISECONDS); Task<String> full = fastMain.withSideEffect(s -> slowSideEffect); // ensure the side effect task will be run runAndWait("AbstractTaskTest.testWithSideEffectFullCompletion", full); slowSideEffect.await(); assertTrue(full.isDone()); assertTrue(fastMain.isDone()); assertTrue(slowSideEffect.isDone()); assertEquals(countTasks(full.getTrace()), expectedNumberOfTasks); } @Test public void testWithSideEffectCancel() throws Exception { Task<String> cancelMain = delayedValue("value", 6000, TimeUnit.MILLISECONDS); Task<String> fastSideEffect = getSuccessTask(); Task<String> cancel = cancelMain.withSideEffect(s -> fastSideEffect); // test cancel, side effect task should not be run // add 10 ms delay so that we can reliably cancel it before it's run by the engine Task<String> mainTaks = delayedValue("value", 10, TimeUnit.MILLISECONDS).andThen(cancel); run(mainTaks); assertTrue(cancelMain.cancel(new Exception("canceled"))); cancel.await(); fastSideEffect.await(10, TimeUnit.MILLISECONDS); assertTrue(cancel.isDone()); assertFalse(fastSideEffect.isDone()); logTracingResults("AbstractTaskTest.testWithSideEffectCancel", mainTaks); } public void testWithSideEffectFailure(int expectedNumberOfTasks) throws Exception { Task<String> failureMain = getFailureTask(); Task<String> fastSideEffect = getSuccessTask(); Task<String> failure = failureMain.withSideEffect(s -> fastSideEffect); // test failure, side effect task should not be run try { runAndWait("AbstractTaskTest.testWithSideEffectFailure", failure); fail("should have failed"); } catch (Exception ex) { assertTrue(failure.isFailed()); fastSideEffect.await(10, TimeUnit.MILLISECONDS); assertFalse(fastSideEffect.isDone()); } assertEquals(countTasks(failure.getTrace()), expectedNumberOfTasks); } public void testOnFailure(int expectedNumberOfTasks) { final AtomicReference<Throwable> variable = new AtomicReference<Throwable>(); Task<String> success = getSuccessTask().onFailure(variable::set); runAndWait("AbstractTaskTest.testOnFailureSuccess", success); assertNull(variable.get()); assertEquals(countTasks(success.getTrace()), expectedNumberOfTasks); Task<String> failure = getFailureTask().onFailure(variable::set); try { runAndWait("AbstractTaskTest.testRecoverFailure", failure); fail("should have failed"); } catch (Exception ex) { assertTrue(failure.isFailed()); } assertEquals(variable.get().getMessage(), TASK_ERROR_MESSAGE); assertEquals(countTasks(failure.getTrace()), expectedNumberOfTasks); } public void testOnFailureWhenCancelled(int expectedNumberOfTasks) { final AtomicReference<Throwable> variable = new AtomicReference<Throwable>(); Task<String> cancelled = getCancelledTask().onFailure(variable::set); try { runAndWait("AbstractTaskTest.testOnFailureWhenCancelled", cancelled); fail("should have failed"); } catch (Exception ex) { assertTrue(cancelled.isFailed()); assertTrue(Exceptions.isCancellation(cancelled.getError())); } assertNull(variable.get()); assertEquals(countTasks(cancelled.getTrace()), expectedNumberOfTasks); } @Test public void testTransformSuccessToSuccess() { Task<String> success = getSuccessTask(); Task<String> transformed = success.transform(tryT -> Success.of(tryT.get() + "transformed")); runAndWait("AbstractTaskTest.testTransformSuccessToSuccess", transformed); assertEquals(transformed.get(), success.get() + "transformed"); } @Test public void testTransformSuccessToFailure() { Task<String> success = getSuccessTask(); final Exception failureReason = new Exception(); Task<String> transformed = success.transform(tryT -> Failure.of(failureReason)); try { runAndWait("AbstractTaskTest.testTransformSuccessToSuccess", transformed); fail("should have failed"); } catch (Exception ex) { assertTrue(transformed.isFailed()); } assertSame(transformed.getError(), failureReason); } @Test public void testTransformFailureToSuccess() { Task<String> failure = getFailureTask(); Task<String> transformed = failure.transform(tryT -> Success.of(tryT.getError().toString() + "transformed")); runAndWait("AbstractTaskTest.testTransformFailureToSuccess", transformed); assertEquals(transformed.get(), failure.getError().toString() + "transformed"); } @Test public void testTransformFailureToFailure() { Task<String> failure = getFailureTask(); final Exception failureReason = new Exception(); Task<String> transformed = failure.transform(tryT -> Failure.of(failureReason)); try { runAndWait("AbstractTaskTest.testTransformFailureToFailure", transformed); fail("should have failed"); } catch (Exception ex) { assertTrue(transformed.isFailed()); } assertSame(transformed.getError(), failureReason); } @Test public void testFlatten() { Task<Task<String>> nested = Task.callable(() -> getSuccessTask()); Task<String> flat = Task.flatten(nested); runAndWait("AbstractTaskTest.testFlatten", flat); assertEquals(flat.get(), TASK_VALUE); } @Test public void testFailureInNestedFlatMap() { final Exception failureReason = new Exception(); Task<String> failing = getSuccessTask() .flatMap(Task::value) .flatMap(Task::value) .flatMap(i -> Task.failure(failureReason)); Task<String> nested = failing .flatMap(Task::value) .flatMap(Task::value); try { runAndWait("AbstractTaskTest.testFailureInNestedFlatMap", nested); fail("should have failed"); } catch (Exception ex) { assertTrue(nested.isFailed()); } assertSame(nested.getError(), failureReason); } @Test public void testFlattenFailure() { Task<Task<String>> nested = Task.callable(() -> getFailureTask()); Task<String> flat = Task.flatten(nested); try { runAndWait("AbstractTaskTest.testFlattenFailure", flat); fail("should have failed"); } catch (Exception ex) { assertTrue(flat.isFailed()); } assertEquals(flat.getError().getMessage(), TASK_ERROR_MESSAGE); } @Test public void testTaskNameTruncation() { Task<?> t = Task.value(times("x", 4096), "hello"); assertEquals(t.getName(), times("x", 1024)); } private String times(String s, int times) { StringBuilder sb = new StringBuilder(); for (int i=0; i < times; i++) { sb.append(s); } return sb.toString(); } protected static final String TASK_VALUE = "value"; protected static final String TASK_ERROR_MESSAGE = "error"; abstract Task<String> getSuccessTask(); abstract Task<String> getFailureTask(); abstract Task<String> getCancelledTask(); }