/* * Copyright 2012 LinkedIn, Inc * * 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 com.linkedin.parseq; import static com.linkedin.parseq.Task.value; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertTrue; import static org.testng.AssertJUnit.fail; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import org.testng.annotations.Test; import com.linkedin.parseq.promise.Promise; import com.linkedin.parseq.promise.PromiseListener; import com.linkedin.parseq.promise.Promises; import com.linkedin.parseq.promise.SettablePromise; /** * @author Chris Pettitt (cpettitt@linkedin.com) * @author Chi Chan (ckchan@linkedin.com) */ public class TestTasks extends BaseEngineTest { @Test public void testTaskThatThrows() throws InterruptedException { final Exception error = new Exception(); final Task<Object> task = new BaseTask<Object>() { @Override protected Promise<Object> run(final Context context) throws Exception { throw error; } }; try { runAndWait("TestTasks.testTaskThatThrows", task); fail("task should finish with Exception"); } catch (Throwable t) { assertEquals(error, task.getError()); } assertTrue(task.isFailed()); } @Test public void testAwait() throws InterruptedException { final String value = "value"; final Task<String> task = value("value", value); final AtomicReference<Boolean> resultRef = new AtomicReference<Boolean>(false); task.addListener(new PromiseListener<String>() { @Override public void onResolved(Promise<String> stringPromise) { try { Thread.sleep(100); } catch (InterruptedException e) { //ignore } finally { resultRef.set(true); } } }); runAndWait("TestTasks.testAwait", task); assertEquals(Boolean.TRUE, resultRef.get()); } @Test public void testSideEffectPartialCompletion() throws InterruptedException { // ensure that the whole can finish before the individual side effect task finishes. Task<String> fastTask = new BaseTask<String>() { @Override protected Promise<? extends String> run(Context context) throws Exception { return Promises.value("fast"); } }; // this task will not complete. Task<String> settableTask = new BaseTask<String>() { @Override protected Promise<? extends String> run(Context context) throws Exception { return Promises.settable(); } }; Task<String> withSideEffect = fastTask.withSideEffect(x -> settableTask); runAndWait("TestTasks.testSideEffectPartialCompletion", withSideEffect); assertTrue(withSideEffect.isDone()); assertTrue(fastTask.isDone()); assertFalse(settableTask.isDone()); } @Test public void testSideEffectFullCompletion() throws InterruptedException { // ensure that the individual side effect task will be run Task<String> taskOne = new BaseTask<String>() { @Override protected Promise<? extends String> run(Context context) throws Exception { return Promises.value("one"); } }; Task<String> taskTwo = new BaseTask<String>() { @Override protected Promise<? extends String> run(Context context) throws Exception { return Promises.value("two"); } }; Task<String> withSideEffect = taskOne.withSideEffect(x -> taskTwo); runAndWait("TestTasks.testSideEffectFullCompletion", withSideEffect); taskTwo.await(); assertTrue(withSideEffect.isDone()); assertTrue(taskTwo.isDone()); } @Test public void testSideEffectCancelled() throws InterruptedException { // this task will not complete. Task<String> settableTask = new BaseTask<String>() { @Override protected Promise<? extends String> run(Context context) throws Exception { return Promises.settable(); } }; Task<String> fastTask = new BaseTask<String>() { @Override protected Promise<? extends String> run(Context context) throws Exception { return Promises.value("fast"); } }; Task<String> withSideEffect = settableTask.withSideEffect(x -> fastTask); // add 10 ms delay so that we can cancel settableTask reliably getEngine().run(delayedValue("value", 10, TimeUnit.MILLISECONDS).andThen(withSideEffect)); assertTrue(settableTask.cancel(new Exception("task cancelled"))); withSideEffect.await(); fastTask.await(10, TimeUnit.MILLISECONDS); assertTrue(withSideEffect.isDone()); assertFalse(fastTask.isDone()); } @Test public void testTimeoutTaskWithTimeout() throws InterruptedException { // This task will not complete on its own, which allows us to test the timeout final Task<String> task = new BaseTask<String>("task") { @Override protected Promise<? extends String> run(final Context context) throws Exception { return Promises.settable(); } }; final Task<String> timeoutTask = task.withTimeout(200, TimeUnit.MILLISECONDS); try { runAndWait("TestTasks.testTimeoutTaskWithTimeout", timeoutTask); fail("task should finish with Error"); } catch (Throwable t) { assertTrue(timeoutTask.getError() instanceof TimeoutException); } assertTrue(timeoutTask.isFailed()); assertTrue(task.await(5, TimeUnit.SECONDS)); // The original task should also be failed - this time with an early finish // exception. assertTrue(task.isFailed()); assertTrue(Exceptions.isEarlyFinish(task.getError())); } @Test public void testTimeoutTaskWithoutTimeout() throws InterruptedException { final String value = "value"; final Task<String> task = Task.callable("task", () -> value); final Task<String> timeoutTask = task.withTimeout(200, TimeUnit.MILLISECONDS); runAndWait("TestTasks.testTimeoutTaskWithoutTimeout", timeoutTask); assertEquals(value, task.get()); // The wrapping task should get the same value as the wrapped task assertEquals(value, timeoutTask.get()); } @Test public void testTimeoutTaskWithError() throws InterruptedException { final Exception error = new Exception(); final Task<String> task = Task.callable("task", () -> { throw error; } ); final Task<String> timeoutTask = task.withTimeout(2000, TimeUnit.MILLISECONDS); getEngine().run(timeoutTask); // We should complete with an error almost immediately assertTrue(timeoutTask.await(100, TimeUnit.MILLISECONDS)); assertTrue(timeoutTask.isFailed()); assertEquals(error, timeoutTask.getError()); } /** * Test scenario in which there are many TimeoutWithErrorTasks scheduled for execution * e.g. by using Tasks.par(). */ @Test public void testManyTimeoutTaskWithoutTimeoutOnAQueue() throws InterruptedException, IOException { final String value = "value"; final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); List<Task<String>> tasks = new ArrayList<Task<String>>(); for (int i = 0; i < 50; i++) { // Task which simulates doing something for 0.5ms and setting response // asynchronously after 5ms. Task<String> t = new BaseTask<String>("test") { @Override protected Promise<? extends String> run(Context context) throws Throwable { final SettablePromise<String> result = Promises.settable(); Thread.sleep(0, 500000); scheduler.schedule(new Runnable() { @Override public void run() { result.done(value); } }, 5, TimeUnit.MILLISECONDS); return result; } }; tasks.add(t.withTimeout(50, TimeUnit.MILLISECONDS)); } // final task runs all the tasks in parallel final Task<?> timeoutTask = Tasks.par(tasks); runAndWait("TestTasks.testManyTimeoutTaskWithoutTimeoutOnAQueue", timeoutTask); scheduler.shutdown(); //tasks should not time out assertEquals(false, timeoutTask.isFailed()); } @Test public void testSetPriorityBelowMinValue() { try { TestUtil.noop().setPriority(Priority.MIN_PRIORITY - 1); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException e) { // Expected case } } @Test public void testSetPriorityAboveMaxValue() { try { TestUtil.noop().setPriority(Priority.MAX_PRIORITY + 1); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException e) { // Expected case } } @SuppressWarnings("deprecation") @Test public void testThrowableCallableNoError() throws InterruptedException { final Integer magic = 0x5f3759df; final ThrowableCallable<Integer> callable = new ThrowableCallable<Integer>() { @Override public Integer call() throws Throwable { return magic; } }; final Task<Integer> task = Tasks.callable("magic", callable); getEngine().run(task); task.await(100, TimeUnit.MILLISECONDS); assertTrue(task.isDone()); assertFalse(task.isFailed()); assertEquals(magic, task.get()); assertEquals("magic", task.getName()); } @SuppressWarnings("deprecation") @Test public void testThrowableCallableWithError() throws InterruptedException { final Throwable throwable = new Throwable(); final ThrowableCallable<Integer> callable = new ThrowableCallable<Integer>() { @Override public Integer call() throws Throwable { throw throwable; } }; final Task<Integer> task = Tasks.callable("error", callable); getEngine().run(task); task.await(100, TimeUnit.MILLISECONDS); assertTrue(task.isDone()); assertTrue(task.isFailed()); assertEquals("Throwable should not be wrapped", throwable, task.getError()); assertEquals("error", task.getName()); } }