/* * 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 org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertNull; import static org.testng.AssertJUnit.assertTrue; import static org.testng.AssertJUnit.fail; import java.util.Collection; import java.util.Collections; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import org.testng.annotations.Test; import com.linkedin.parseq.internal.TaskLogger; import com.linkedin.parseq.promise.Promise; import com.linkedin.parseq.promise.PromiseException; import com.linkedin.parseq.promise.PromiseUnresolvedException; import com.linkedin.parseq.promise.Promises; import com.linkedin.parseq.promise.SettablePromise; import com.linkedin.parseq.trace.ShallowTraceBuilder; import com.linkedin.parseq.trace.TraceBuilder; /** * @author Chris Pettitt (cpettitt@linkedin.com) */ public class TestTaskStates { private static Task<?> NO_PARENT = null; private static Collection<Task<?>> NO_PREDECESSORS = Collections.emptySet(); @Test public void testInit() { final Task<?> task = TestUtil.noop(); assertInitOrScheduled(task); assertEquals(0, task.getPriority()); } @Test public void testSetPriorityAfterInit() { final int newPriority = 5; final Task<?> task = TestUtil.noop(); assertTrue(task.setPriority(newPriority)); assertEquals(newPriority, task.getPriority()); assertInitOrScheduled(task); } @Test public void testCancelAfterInit() { final Task<?> task = TestUtil.noop(); final Exception reason = new Exception(); assertTrue(task.cancel(reason)); assertCancelled(task, reason); } @Test public void testCancelAfterCancel() { final Task<?> task = TestUtil.noop(); final Exception reason1 = new Exception(); final Exception reason2 = new Exception(); assertTrue(task.cancel(reason1)); assertFalse(task.cancel(reason2)); assertCancelled(task, reason1); } @Test public void testRun() throws InterruptedException { final String result = "result"; final Task<String> task = Task.callable("task", () -> result); runTask(task); assertTrue(task.await(5, TimeUnit.SECONDS)); assertDone(task, result); } @Test public void testRunAfterRun() throws InterruptedException { final AtomicInteger runCount = new AtomicInteger(); final SettablePromise<Void> promise = Promises.settable(); final Task<Void> task = new BaseTask<Void>() { @Override protected Promise<? extends Void> run(final Context context) throws Exception { runCount.getAndIncrement(); return promise; } }; runTask(task); // Run it again runTask(task); // We'll give each task some time to hit run Thread.sleep(50); assertEquals(1, runCount.get()); promise.done(null); // Again give the other task some time to enter run Thread.sleep(50); assertEquals(1, runCount.get()); } @Test public void testRunWithSyncError() throws InterruptedException { final Exception exception = new Exception(); final Task<String> task = Task.callable("task", () -> { throw exception; } ); runTask(task); assertTrue(task.await(5, TimeUnit.SECONDS)); assertFailed(task, exception); } @Test public void testRunWithAsyncError() throws InterruptedException { final Exception exception = new Exception(); final Task<String> task = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { final SettablePromise<String> promise = Promises.settable(); promise.fail(exception); return promise; } }; runTask(task); assertTrue(task.await(5, TimeUnit.SECONDS)); assertFailed(task, exception); } @Test public void testSetPriorityAfterRun() throws InterruptedException { final String result = "result"; final Task<String> task = Task.callable("task", () -> result); runTask(task); assertTrue(task.await(5, TimeUnit.SECONDS)); assertFalse(task.setPriority(5)); assertEquals(0, task.getPriority()); assertDone(task, result); } @Test public void testCancelAfterRun() throws InterruptedException { final String result = "result"; // Used to wait for the task to start running final CountDownLatch startLatch = new CountDownLatch(1); // Used to finish the sync task final CountDownLatch finishLatch = new CountDownLatch(1); final Task<String> task = Task.callable("task", () -> { startLatch.countDown(); finishLatch.await(); return result; } ); final Thread thread = new Thread(new Runnable() { @Override public void run() { runTask(task); } }); thread.setDaemon(true); thread.start(); startLatch.await(); assertFalse(task.cancel(new Exception())); assertRunOrPending(task); finishLatch.countDown(); assertTrue(task.await(5, TimeUnit.SECONDS)); assertDone(task, result); } @Test public void testCancelAfterPending() throws InterruptedException { final SettablePromise<String> promise = Promises.settable(); final Task<String> task = new BaseTask<String>() { @Override public Promise<String> run(final Context context) throws Exception { return promise; } }; final Thread thread = new Thread(new Runnable() { @Override public void run() { runTask(task); } }); thread.setDaemon(true); thread.start(); thread.join(); final Exception reason = new Exception(); assertTrue(task.cancel(reason)); assertTrue(task.await(5, TimeUnit.SECONDS)); assertCancelled(task, reason); } @Test public void testCancelAfterDone() throws InterruptedException { final String result = "result"; final Task<String> task = Task.callable("task", () -> result); runTask(task); assertTrue(task.await(5, TimeUnit.SECONDS)); assertDone(task, result); assertFalse(task.cancel(new Exception())); assertDone(task, result); } private void runTask(final Task<?> task) { task.contextRun(new NullContext(), NO_PARENT, NO_PREDECESSORS); } private void assertInitOrScheduled(final Task<?> task) { assertFalse(task.isDone()); assertFalse(task.isFailed()); assertNull(task.getShallowTrace().getStartNanos()); assertNull(task.getShallowTrace().getEndNanos()); try { task.get(); fail("Should have thrown PromiseUnresolvedException"); } catch (PromiseUnresolvedException e) { // Expected case } try { task.getError(); fail("Should have thrown PromiseUnresolvedException"); } catch (PromiseUnresolvedException e) { // Expected case } } private void assertRunOrPending(final Task<?> task) { assertFalse(task.isDone()); assertFalse(task.isFailed()); assertTrue(task.getShallowTrace().getStartNanos() > 0); assertNotNull(task.getShallowTrace().getEndNanos()); try { task.get(); fail("Should have thrown PromiseUnresolvedException"); } catch (PromiseUnresolvedException e) { // Expected case } try { task.getError(); fail("Should have thrown PromiseUnresolvedException"); } catch (PromiseUnresolvedException e) { // Expected case } } private <T> void assertDone(final Task<T> task, final T expectedValue) { assertTrue(task.isDone()); assertFalse(task.isFailed()); assertEquals(expectedValue, task.get()); assertNull(task.getError()); assertTrue(task.getShallowTrace().getStartNanos() > 0); assertTrue(task.getShallowTrace().getStartNanos() <= task.getShallowTrace().getEndNanos()); } private void assertFailed(final Task<?> task, Exception exception) { assertTrue(task.isDone()); assertTrue(task.isFailed()); assertEquals(exception, task.getError()); assertTrue(task.getShallowTrace().getStartNanos() > 0); assertTrue(task.getShallowTrace().getStartNanos() <= task.getShallowTrace().getEndNanos()); try { task.get(); fail("Should have thwon PromiseException"); } catch (PromiseException e) { assertEquals(exception, e.getCause()); } } private void assertCancelled(final Task<?> task, Exception exception) { assertTrue(task.isDone()); assertTrue(task.isFailed()); assertTrue(Exceptions.isCancellation(task.getError())); assertEquals(exception, task.getError().getCause()); assertTrue(task.getShallowTrace().getStartNanos() > 0); assertTrue(task.getShallowTrace().getStartNanos() <= task.getShallowTrace().getEndNanos()); try { task.get(); fail("Should have thwon PromiseException"); } catch (PromiseException e) { assertEquals(exception, e.getCause().getCause()); } } private static class NullContext implements Context { @Override public Cancellable createTimer(final long time, final TimeUnit unit, final Task<?> task) { throw new UnsupportedOperationException(); } @Override public void run(final Task<?>... tasks) { throw new UnsupportedOperationException(); } @Override public After after(final Promise<?>... promises) { throw new UnsupportedOperationException(); } @Override public Object getEngineProperty(String key) { throw new UnsupportedOperationException(); } @Override public TraceBuilder getTraceBuilder() { return new TraceBuilder(1024, "test", 0L); } @Override public ShallowTraceBuilder getShallowTraceBuilder() { return new ShallowTraceBuilder(0L); } @Override public Long getPlanId() { return 0L; } @Override public Long getTaskId() { return 0L; } @Override public TaskLogger getTaskLogger() { return new NullTaskLog(); } @Override public void runSideEffect(Task<?>... tasks) { throw new UnsupportedOperationException(); } @Override public String getPlanClass() { return "test"; } } private static class NullTaskLog extends TaskLogger { public NullTaskLog() { super(0L, 0L, null, null, null); } @Override public void logTaskStart(final Task<?> task) { } @Override public <T> void logTaskEnd(Task<T> task, Function<T, String> traceValueProvider) { } } }