package com.linkedin.parseq; import com.linkedin.parseq.promise.Promise; import com.linkedin.parseq.promise.Promises; import com.linkedin.parseq.promise.SettablePromise; import org.testng.annotations.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static com.linkedin.parseq.Tasks.par; import static com.linkedin.parseq.Task.value; import static org.testng.Assert.assertFalse; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import static org.testng.AssertJUnit.fail; /** * @author Chris Pettitt */ public class TestParTask extends BaseEngineTest { @Test public void testIterableParWithEmptyList() { try { par(Collections.<Task<?>> emptyList()); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException e) { // Expected case } } @Test public void testIterableParWithSingletonList() throws InterruptedException { final String valueStr = "value"; final Task<String> task = value("value", valueStr); final Task<List<String>> par = par(Collections.singleton(task)); runAndWait("TestParTask.testIterableParWithSingletonList", par); assertEquals(1, par.get().size()); assertEquals(valueStr, par.get().get(0)); assertEquals(valueStr, task.get()); } @Test public void testIterableSeqWithMultipleElements() throws InterruptedException { final int iters = 500; final Task<?>[] tasks = new BaseTask<?>[iters]; final AtomicInteger counter = new AtomicInteger(0); for (int i = 0; i < iters; i++) { tasks[i] = Task.action("task-" + i, () -> { // Note: We intentionally do not use CAS. We guarantee that // the run method of Tasks are never executed in parallel. final int currentCount = counter.get(); counter.set(currentCount + 1); } ); } final ParTask<?> par = par(Arrays.asList(tasks)); runAndWait("TestParTask.testIterableSeqWithMultipleElements", par); assertEquals(500, par.getSuccessful().size()); assertEquals(500, par.getTasks().size()); assertEquals(500, par.get().size()); assertEquals(500, counter.get()); } @SuppressWarnings("deprecation") @Test public void testAsyncTasksInPar() throws InterruptedException { // Tasks cannot have their run methods invoked at the same time, however // asynchronous tasks are allowed to execute concurrently outside of their // run methods. This test verifies that two asynchronous tasks are not // serialized such that one must complete before the other. final SettablePromise<String> promise1 = Promises.settable(); final SettablePromise<Integer> promise2 = Promises.settable(); // Used to ensure that both tasks have been run final CountDownLatch cdl = new CountDownLatch(2); final Task<String> task1 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { cdl.countDown(); return promise1; } }; final Task<Integer> task2 = new BaseTask<Integer>() { @Override public Promise<Integer> run(final Context context) throws Exception { cdl.countDown(); return promise2; } }; final ParTask<Object> par = Tasks.<Object> par(task1, task2); getEngine().run(par); assertTrue("Both tasks did not run within the timeout", cdl.await(100, TimeUnit.MILLISECONDS)); // If execution was serialized then it would have hung after executing // task 1, because task 1's promise was not set. promise1.done("test"); promise2.done(10); // We add a promise listener to the promise returned from a task to finish // tasks asynchronously. Since we can only wait on the start of tasks above // we should wait for the completion of tasks here before checking their // values. assertTrue("Par task did not finish in a reasonable amount of time", par.await(100, TimeUnit.MILLISECONDS)); assertEquals(2, par.getTasks().size()); assertEquals("test", par.getTasks().get(0).get()); assertEquals(10, par.getTasks().get(1).get()); assertEquals(2, par.getSuccessful().size()); assertEquals("test", par.getSuccessful().get(0)); assertEquals(10, par.getSuccessful().get(1)); } @Test public void testAsyncTasksInParWithType() throws InterruptedException { // Tasks cannot have their run methods invoked at the same time, however // asynchronous tasks are allowed to execute concurrently outside of their // run methods. This test verifies that two asynchronous tasks are not // serialized such that one must complete before the other. final SettablePromise<String> promise1 = Promises.settable(); final SettablePromise<String> promise2 = Promises.settable(); final SettablePromise<String> promise3 = Promises.settable(); // Used to ensure that both tasks have been run final CountDownLatch cdl = new CountDownLatch(3); final Task<String> task1 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { cdl.countDown(); return promise1; } }; final Task<String> task2 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { cdl.countDown(); return promise2; } }; final Task<String> task3 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { cdl.countDown(); return promise3; } }; List<Task<String>> taskList = new ArrayList<Task<String>>(); taskList.add(task1); taskList.add(task2); taskList.add(task3); final ParTask<String> par = par(taskList); getEngine().run(par); assertTrue("Both tasks did not run within the timeout", cdl.await(100, TimeUnit.MILLISECONDS)); promise1.done("test1"); promise2.done("test2"); promise3.done(null); assertTrue("Par task did not finish in a reasonable amount of time", par.await(100, TimeUnit.MILLISECONDS)); assertTrue(par.await(5, TimeUnit.SECONDS)); List<String> result = par.get(); assertEquals(3, par.getTasks().size()); assertEquals("Count should only be 3.", 3, result.size()); assertEquals("Missing task1 in result.", "test1", result.get(0)); assertEquals("Missing task2 in result.", "test2", result.get(1)); assertEquals("Missing task3 in result.", null, result.get(2)); assertEquals(par.get(), par.getSuccessful()); } @SuppressWarnings("deprecation") @Test public void testParWithGeneralType() throws InterruptedException { final Integer intVal = 123; final Double dblVal = 456.789; final Task<Integer> intTask = value("intTask", intVal); final Task<Double> dblTask = value("dblTask", dblVal); final ParTask<? extends Number> par = par(intTask, dblTask); runAndWait("TestParTask.testParWithGeneralType", par); assertEquals(2, par.get().size()); assertEquals(intVal, par.get().get(0)); assertEquals(dblVal, par.get().get(1)); } @Test public void testTypedParWithGeneralType() throws InterruptedException { final Integer intVal = 123; final Double dblVal = 456.789; final Task<Integer> intTask = value("intTask", intVal); final Task<Double> dblTask = value("dblTask", dblVal); final List<Task<? extends Number>> numTasks = new ArrayList<Task<? extends Number>>(); numTasks.add(intTask); numTasks.add(dblTask); final ParTask<? extends Number> par = par(numTasks); runAndWait("TestParTask.testTypedParWithGeneralType", par); assertEquals(2, par.get().size()); assertEquals(intVal, par.get().get(0)); assertEquals(dblVal, par.get().get(1)); } @Test public void testFailTaskInPar() throws InterruptedException { final SettablePromise<String> promise1 = Promises.settable(); final SettablePromise<String> promise2 = Promises.settable(); final Task<String> task1 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise1; } }; final Task<String> task2 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise2; } }; List<Task<String>> taskList = new ArrayList<Task<String>>(); taskList.add(task1); taskList.add(task2); final ParTask<String> par = par(taskList); getEngine().run(par); promise1.fail(new Exception()); promise2.fail(new Exception()); assertTrue(par.await(5, TimeUnit.SECONDS)); if (!par.isFailed()) { fail("par should have failed."); } List<String> successful = par.getSuccessful(); List<Task<String>> tasks = par.getTasks(); assertEquals("Should have a size 2 for exceptions", 2, ((MultiException) par.getError()).getCauses().size()); assertEquals(0, successful.size()); assertEquals(2, tasks.size()); assertEquals(true, tasks.get(0).isFailed()); assertEquals(true, tasks.get(1).isFailed()); } @Test public void testFailAndPassTaskInPar() throws InterruptedException { final SettablePromise<String> promise1 = Promises.settable(); final SettablePromise<String> promise2 = Promises.settable(); final SettablePromise<String> promise3 = Promises.settable(); final Task<String> task1 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise1; } }; final Task<String> task2 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise2; } }; final Task<String> task3 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise3; } }; List<Task<String>> taskList = new ArrayList<Task<String>>(); taskList.add(task1); taskList.add(task2); taskList.add(task3); final ParTask<String> par = par(taskList); getEngine().run(par); promise1.done("done1"); promise2.fail(new Exception()); promise3.done("done3"); assertTrue(par.await(5, TimeUnit.SECONDS)); if (!par.isFailed()) { fail("par should have failed."); } List<String> successful = par.getSuccessful(); List<Task<String>> tasks = par.getTasks(); assertEquals("Should have a size 1 for exceptions", 1, ((MultiException) par.getError()).getCauses().size()); assertEquals(2, successful.size()); assertEquals("done1", successful.get(0)); assertEquals("done3", successful.get(1)); assertEquals(3, tasks.size()); assertEquals(false, tasks.get(0).isFailed()); assertEquals(true, tasks.get(1).isFailed()); assertEquals(false, tasks.get(2).isFailed()); assertEquals("done1", tasks.get(0).get()); assertEquals("done3", tasks.get(2).get()); } @Test public void testNoFastFailInPar() throws InterruptedException { final SettablePromise<String> promise1 = Promises.settable(); final SettablePromise<String> promise2 = Promises.settable(); final Task<String> task1 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise1; } }; final Task<String> task2 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise2; } }; List<Task<String>> taskList = new ArrayList<Task<String>>(); taskList.add(task1); taskList.add(task2); final ParTask<String> par = par(taskList); getEngine().run(par); promise2.fail(new Exception()); //This failure does not complete tha par Thread.sleep(100); assertFalse(par.isDone()); promise1.done("done1"); assertTrue(par.await(5, TimeUnit.SECONDS)); if (!par.isFailed()) { fail("par should have failed."); } List<String> successful = par.getSuccessful(); List<Task<String>> tasks = par.getTasks(); assertEquals("Should have a size 1 for exceptions", 1, ((MultiException) par.getError()).getCauses().size()); assertEquals(1, successful.size()); assertEquals("done1", successful.get(0)); assertEquals(2, tasks.size()); assertEquals(false, tasks.get(0).isFailed()); assertEquals(true, tasks.get(1).isFailed()); assertEquals("done1", tasks.get(0).get()); } @Test public void testAllEarlyFinishTaskInPar() throws InterruptedException { final SettablePromise<String> promise1 = Promises.settable(); final SettablePromise<String> promise2 = Promises.settable(); final SettablePromise<String> promise3 = Promises.settable(); final Task<String> task1 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise1; } }; final Task<String> task2 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise2; } }; final Task<String> task3 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise3; } }; List<Task<String>> taskList = new ArrayList<Task<String>>(); taskList.add(task1); taskList.add(task2); taskList.add(task3); final ParTask<String> par = par(taskList); getEngine().run(par); promise1.done("done1"); promise2.fail(new CancellationException(Exceptions.EARLY_FINISH_EXCEPTION)); promise3.fail(new CancellationException(Exceptions.EARLY_FINISH_EXCEPTION)); assertTrue(par.await(5, TimeUnit.SECONDS)); if (!par.isFailed()) { fail("par should have failed."); } List<String> successful = par.getSuccessful(); List<Task<String>> tasks = par.getTasks(); assertTrue("Should be early finish", Exceptions.isEarlyFinish(par.getError())); assertEquals(1, successful.size()); assertEquals("done1", successful.get(0)); assertEquals(3, tasks.size()); assertEquals(false, tasks.get(0).isFailed()); assertEquals(true, tasks.get(1).isFailed()); assertEquals(true, tasks.get(2).isFailed()); } @Test public void testOneEarlyFinishTaskInPar() throws InterruptedException { final SettablePromise<String> promise1 = Promises.settable(); final SettablePromise<String> promise2 = Promises.settable(); final SettablePromise<String> promise3 = Promises.settable(); final Task<String> task1 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise1; } }; final Task<String> task2 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise2; } }; final Task<String> task3 = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise3; } }; List<Task<String>> taskList = new ArrayList<Task<String>>(); taskList.add(task1); taskList.add(task2); taskList.add(task3); final ParTask<String> par = par(taskList); getEngine().run(par); promise1.done("done1"); promise2.fail(new CancellationException(Exceptions.EARLY_FINISH_EXCEPTION)); promise3.fail(new Exception()); assertTrue(par.await(5, TimeUnit.SECONDS)); if (!par.isFailed()) { fail("par should have failed."); } List<String> successful = par.getSuccessful(); List<Task<String>> tasks = par.getTasks(); assertEquals("Should have a size 2 for exceptions", 2, ((MultiException) par.getError()).getCauses().size()); assertEquals(1, successful.size()); assertEquals("done1", successful.get(0)); assertEquals(3, tasks.size()); assertEquals(false, tasks.get(0).isFailed()); assertEquals(true, tasks.get(1).isFailed()); assertEquals(true, tasks.get(2).isFailed()); } @SuppressWarnings("deprecation") @Test public void testPar2() throws InterruptedException { final Task<List<Integer>> par = par(value(1), value(2)); runAndWait("TestParTask.testPar2", par); assertEquals(Arrays.asList(1, 2), par.get()); } @SuppressWarnings("deprecation") @Test public void testPar3() throws InterruptedException { final Task<List<Integer>> par = par(value(1), value(2), value(3)); runAndWait("TestParTask.testPar3", par); assertEquals(Arrays.asList(1, 2, 3), par.get()); } @SuppressWarnings("deprecation") @Test public void testPar4() throws InterruptedException { final Task<List<Integer>> par = par(value(1), value(2), value(3), value(4)); runAndWait("TestParTask.testPar4", par); assertEquals(Arrays.asList(1, 2, 3, 4), par.get()); } @SuppressWarnings("deprecation") @Test public void testPar5() throws InterruptedException { final Task<List<Integer>> par = par(value(1), value(2), value(3), value(4), value(5)); runAndWait("TestParTask.testPar5", par); assertEquals(Arrays.asList(1, 2, 3, 4, 5), par.get()); } @SuppressWarnings("deprecation") @Test public void testPar6() throws InterruptedException { final Task<List<Integer>> par = par(value(1), value(2), value(3), value(4), value(5), value(6)); runAndWait("TestParTask.testPar6", par); assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), par.get()); } @SuppressWarnings("deprecation") @Test public void testPar7() throws InterruptedException { final Task<List<Integer>> par = par(value(1), value(2), value(3), value(4), value(5), value(6), value(7)); runAndWait("TestParTask.testPar7", par); assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7), par.get()); } @SuppressWarnings("deprecation") @Test public void testPar8() throws InterruptedException { final Task<List<Integer>> par = par(value(1), value(2), value(3), value(4), value(5), value(6), value(7), value(8)); runAndWait("TestParTask.testPar8", par); assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8), par.get()); } @SuppressWarnings("deprecation") @Test public void testPar9() throws InterruptedException { final Task<List<Integer>> par = par(value(1), value(2), value(3), value(4), value(5), value(6), value(7), value(8), value(9)); runAndWait("TestParTask.testPar9", par); assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9), par.get()); } @SuppressWarnings("deprecation") @Test public void testPar10() throws InterruptedException { final Task<List<Integer>> par = par(value(1), value(2), value(3), value(4), value(5), value(6), value(7), value(8), value(9), value(10)); runAndWait("TestParTask.testPar10", par); assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), par.get()); } @Test public void testParIsNotSystemHidden() { final ParTask<String> par = par(Collections.singleton(value("value"))); assertFalse(par.getShallowTrace().getSystemHidden()); } }