package org.threadly.concurrent; import static org.junit.Assert.*; import static org.threadly.TestConstants.*; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.threadly.concurrent.AbstractPriorityScheduler.OneTimeTaskWrapper; import org.threadly.concurrent.future.ListenableFuture; import org.threadly.test.concurrent.AsyncVerifier; import org.threadly.test.concurrent.TestRunnable; import org.threadly.util.Clock; import org.threadly.util.ExceptionHandler; import org.threadly.util.SuppressedStackRuntimeException; @SuppressWarnings("javadoc") public class NoThreadSchedulerTest { protected NoThreadScheduler scheduler; @Before public void setup() { scheduler = new NoThreadScheduler(); } @After public void cleanup() { scheduler = null; } private static List<TestRunnable> getRunnableList() { List<TestRunnable> result = new ArrayList<>(TEST_QTY); for (int i = 0; i < TEST_QTY; i++) { result.add(new TestRunnable()); } return result; } private static List<TestCallable> getCallableList() { List<TestCallable> result = new ArrayList<>(TEST_QTY); for (int i = 0; i < TEST_QTY; i++) { result.add(new TestCallable()); } return result; } @Test public void isShutdownTest() { assertFalse(scheduler.isShutdown()); } @Test public void tickWithoutHandlerThrowsRuntimeExceptionTest() { RuntimeException failure = new SuppressedStackRuntimeException(); scheduler.execute(new TestRuntimeFailureRunnable(failure)); try { scheduler.tick(null); fail("Exception should have thrown"); } catch (Exception e) { assertTrue(e == failure); } } @Test public void tickHandlesRuntimeExceptionTest() { RuntimeException failure = new SuppressedStackRuntimeException(); final AtomicReference<Throwable> handledException = new AtomicReference<>(null); scheduler.execute(new TestRuntimeFailureRunnable(failure)); int runCount = scheduler.tick(new ExceptionHandler() { @Override public void handleException(Throwable thrown) { handledException.set(thrown); } }); assertEquals(1, runCount); assertTrue(handledException.get() == failure); } @Test public void executeTest() { List<TestRunnable> runnables = getRunnableList(); Iterator<TestRunnable> it = runnables.iterator(); while (it.hasNext()) { scheduler.execute(it.next()); } // all should run now assertEquals(TEST_QTY, scheduler.tick(null)); it = runnables.iterator(); while (it.hasNext()) { assertEquals(1, it.next().getRunCount()); } // verify no more run after a second tick assertEquals(scheduler.tick(null), 0); it = runnables.iterator(); while (it.hasNext()) { assertEquals(1, it.next().getRunCount()); } } @Test public void executeInOrderTest() { TestRunnable lastRun = null; long startTime = System.currentTimeMillis(); int testQty = 0; while (testQty < TEST_QTY || System.currentTimeMillis() - startTime < 100) { testQty++; final TestRunnable fLastRun = lastRun; lastRun = new TestRunnable() { @Override public void handleRunStart() { if (fLastRun != null && !fLastRun.ranOnce()) { fail("last run did not complete"); } } }; scheduler.schedule(DoNothingRunnable.instance(), 5); scheduler.execute(lastRun); } // should throw exception if any internal failures occurred scheduler.tick(null); } @Test public void scheduleInOrderTest() { TestRunnable lastRun = null; int testQty = 0; while (testQty < TEST_QTY) { testQty++; final TestRunnable fLastRun = lastRun; lastRun = new TestRunnable() { @Override public void handleRunStart() { if (fLastRun != null && !fLastRun.ranOnce()) { fail("last run did not complete"); } } }; scheduler.schedule(lastRun, DELAY_TIME); } // should throw exception if any internal failures occurred scheduler.tick(null); } @Test public void submitRunnableTest() { List<TestRunnable> runnables = getRunnableList(); List<Future<?>> futures = new ArrayList<>(runnables.size()); Iterator<TestRunnable> it = runnables.iterator(); while (it.hasNext()) { Future<?> future = scheduler.submit(it.next()); assertNotNull(future); futures.add(future); } // all should run now assertEquals(TEST_QTY, scheduler.tick(null)); it = runnables.iterator(); while (it.hasNext()) { assertEquals(1, it.next().getRunCount()); } // verify no more run after a second tick assertEquals(0, scheduler.tick(null)); it = runnables.iterator(); while (it.hasNext()) { assertEquals(1, it.next().getRunCount()); } Iterator<Future<?>> futureIt = futures.iterator(); while (futureIt.hasNext()) { assertTrue(futureIt.next().isDone()); } } @Test public void submitCallableTest() throws InterruptedException, ExecutionException { List<TestCallable> callables = getCallableList(); List<Future<Object>> futures = new ArrayList<>(callables.size()); Iterator<TestCallable> it = callables.iterator(); while (it.hasNext()) { Future<Object> future = scheduler.submit(it.next()); assertNotNull(future); futures.add(future); } // all should run now assertEquals(TEST_QTY, scheduler.tick(null)); it = callables.iterator(); while (it.hasNext()) { assertTrue(it.next().isDone()); } it = callables.iterator(); Iterator<Future<Object>> futureIt = futures.iterator(); while (futureIt.hasNext()) { Future<Object> future = futureIt.next(); TestCallable tc = it.next(); assertTrue(future.isDone()); assertTrue(tc.getReturnedResult() == future.get()); } } @Test public void scheduleRunnableTest() { TestRunnable tr = new TestRunnable(); long scheduleTime = Clock.accurateForwardProgressingMillis(); scheduler.schedule(tr, DELAY_TIME); int runCount = 0; while (runCount == 0) { runCount = scheduler.tick(null); } long runTime = Clock.accurateForwardProgressingMillis(); assertEquals(1, runCount); assertTrue(tr.ranOnce()); assertTrue((runTime - scheduleTime) >= DELAY_TIME); } @Test public void submitScheduledRunnableTest() { TestRunnable tr = new TestRunnable(); ListenableFuture<?> future = scheduler.submitScheduled(tr, DELAY_TIME); int runCount = 0; while (runCount == 0) { runCount = scheduler.tick(null); } assertEquals(1, runCount); assertTrue(tr.getDelayTillFirstRun() >= DELAY_TIME); assertTrue(future.isDone()); } @Test public void submitScheduledRunnableFail() { try { scheduler.submitScheduled((Runnable)null, 10); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } try { scheduler.submitScheduled(DoNothingRunnable.instance(), -10); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } } @Test public void submitScheduledCallableTest() throws InterruptedException, ExecutionException { TestCallable tc = new TestCallable(); ListenableFuture<?> future = scheduler.submitScheduled(tc, DELAY_TIME); int runCount = 0; while (runCount == 0) { runCount = scheduler.tick(null); } assertEquals(1, runCount); assertTrue(tc.getDelayTillFirstRun() >= DELAY_TIME); assertTrue(future.isDone()); assertTrue(future.get() == tc.getReturnedResult()); } @Test public void submitScheduledCallableFail() { try { scheduler.submitScheduled((Callable<?>)null, 10); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } try { scheduler.submitScheduled(new TestCallable(), -10); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } } @Test public void scheduleWithFixedZeroDelayTest() throws InterruptedException, TimeoutException { final AsyncVerifier av = new AsyncVerifier(); final TestRunnable[] testRunnables = new TestRunnable[TEST_QTY]; for (int i = 0; i < TEST_QTY; i++) { testRunnables[i] = new TestRunnable(); } Runnable workRunnable = new Runnable() { private int runIndex = -1; @Override public void run() { if (runIndex >= 0) { // verify they ran ahead of us av.assertTrue(testRunnables[runIndex].ranOnce()); } if (++runIndex < testRunnables.length) { scheduler.execute(testRunnables[runIndex]); } else { // we are done // remove task so .tick can unblock scheduler.remove(this); av.signalComplete(); } } }; scheduler.scheduleWithFixedDelay(workRunnable, 0, 0); scheduler.tick(null); av.waitForTest(); } @Test public void scheduleWithFixedDelayFail() { try { scheduler.scheduleWithFixedDelay(null, 10, 10); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } try { scheduler.scheduleWithFixedDelay(DoNothingRunnable.instance(), -10, 10); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } try { scheduler.scheduleWithFixedDelay(DoNothingRunnable.instance(), 10, -10); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } } @Test public void scheduleAtFixedRateFail() { try { scheduler.scheduleAtFixedRate(null, 10, 10); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } try { scheduler.scheduleAtFixedRate(DoNothingRunnable.instance(), -10, 10); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } try { scheduler.scheduleAtFixedRate(DoNothingRunnable.instance(), 10, -10); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } } @Test public void removeRunnableTest() { TestRunnable tr = new TestRunnable(); assertFalse(scheduler.remove(tr)); scheduler.execute(tr); assertTrue(scheduler.remove(tr)); assertFalse(scheduler.remove(tr)); scheduler.submit(tr); assertTrue(scheduler.remove(tr)); assertFalse(scheduler.remove(tr)); scheduler.submit(tr, new Object()); assertTrue(scheduler.remove(tr)); assertFalse(scheduler.remove(tr)); scheduler.schedule(tr, DELAY_TIME); assertTrue(scheduler.remove(tr)); assertFalse(scheduler.remove(tr)); scheduler.submitScheduled(tr, DELAY_TIME); assertTrue(scheduler.remove(tr)); assertFalse(scheduler.remove(tr)); scheduler.submitScheduled(tr, new Object(), DELAY_TIME); assertTrue(scheduler.remove(tr)); assertFalse(scheduler.remove(tr)); scheduler.scheduleWithFixedDelay(tr, 0, DELAY_TIME); assertTrue(scheduler.remove(tr)); assertFalse(scheduler.remove(tr)); } @Test public void removeRecurringRunnableTest() throws InterruptedException { TestRunnable immediateRun = new TestRunnable(); TestRunnable initialDelay = new TestRunnable(); assertFalse(scheduler.remove(immediateRun)); scheduler.scheduleWithFixedDelay(immediateRun, 0, DELAY_TIME); assertTrue(scheduler.remove(immediateRun)); scheduler.scheduleWithFixedDelay(immediateRun, 0, DELAY_TIME); scheduler.scheduleWithFixedDelay(initialDelay, DELAY_TIME, DELAY_TIME); assertEquals(1, scheduler.tick(null)); assertEquals(1, immediateRun.getRunCount()); // should have run assertEquals(0, initialDelay.getRunCount()); // should NOT have run yet assertTrue(scheduler.remove(immediateRun)); assertEquals(1, scheduler.blockingTick(null)); assertEquals(1, immediateRun.getRunCount()); // should NOT have run again assertEquals(1, initialDelay.getRunCount()); // should have run } @Test public void removeCallableTest() throws InterruptedException { TestCallable immediateRun = new TestCallable(); TestCallable delayRun = new TestCallable(); assertFalse(scheduler.remove(immediateRun)); scheduler.submitScheduled(immediateRun, 0); assertTrue(scheduler.remove(immediateRun)); assertFalse(scheduler.remove(immediateRun)); scheduler.submitScheduled(delayRun, DELAY_TIME); assertEquals(1, scheduler.blockingTick(null)); assertFalse(immediateRun.isDone()); assertTrue(delayRun.isDone()); } @Test public void removeWhileRunningTest() { TestRunnable tr = new TestRunnable() { @Override public void handleRunStart() { assertTrue(scheduler.remove(this)); } }; scheduler.scheduleWithFixedDelay(tr, 0, 0); assertEquals(1, scheduler.tick(null)); // should be removed for subsequent ticks assertEquals(0, scheduler.tick(null)); assertEquals(1, tr.getRunCount()); } @Test public void blockTillAvailableExecuteTest() throws InterruptedException, TimeoutException { final AsyncVerifier av = new AsyncVerifier(); TestRunnable tickRunnable = new TestRunnable() { @Override public void handleRunStart() { try { int runCount = scheduler.blockingTick(null); // should block av.assertEquals(1, runCount); av.signalComplete(); } catch (InterruptedException e) { av.fail(e); } } }; new Thread(tickRunnable).start(); // should be blocked waiting for task now tickRunnable.blockTillStarted(); TestRunnable testTask = new TestRunnable(); scheduler.execute(testTask); testTask.blockTillFinished(); // should run without issue av.waitForTest(); // our parent thread should finish quickly } @Test public void blockTillAvailableScheduleTest() throws InterruptedException, TimeoutException { final AsyncVerifier av = new AsyncVerifier(); final TestRunnable testTask = new TestRunnable(); TestRunnable tickRunnable = new TestRunnable() { @Override public void handleRunStart() { try { long startTime = Clock.accurateForwardProgressingMillis(); scheduler.schedule(testTask, DELAY_TIME); int runCount = scheduler.blockingTick(null); // should block long finishTime = Clock.accurateForwardProgressingMillis(); av.assertEquals(1, runCount); av.assertTrue(finishTime - startTime >= DELAY_TIME); av.assertTrue(testTask.ranOnce()); av.signalComplete(); } catch (InterruptedException e) { av.fail(e); } } }; new Thread(tickRunnable).start(); av.waitForTest(); } @Test public void hasTaskReadyToRunTest() { assertFalse(scheduler.hasTaskReadyToRun()); // schedule in the future scheduler.schedule(DoNothingRunnable.instance(), 1000 * 15); // still should have nothing ready to run assertFalse(scheduler.hasTaskReadyToRun()); scheduler.execute(DoNothingRunnable.instance()); // should now have tasks ready to run assertTrue(scheduler.hasTaskReadyToRun()); scheduler.tick(null); // should no longer have anything to run assertFalse(scheduler.hasTaskReadyToRun()); scheduler.queueManager.highPriorityQueueSet .addScheduled(new OneTimeTaskWrapper(DoNothingRunnable.instance(), scheduler.queueManager.highPriorityQueueSet.scheduleQueue, Clock.lastKnownForwardProgressingMillis())); // now should be true with scheduled task which is ready to run assertTrue(scheduler.hasTaskReadyToRun()); } @Test public void hasTaskReadyToRunRunningTaskTest() { scheduler.scheduleWithFixedDelay(new Runnable() { @Override public void run() { assertFalse(scheduler.hasTaskReadyToRun()); scheduler.execute(DoNothingRunnable.instance()); assertTrue(scheduler.hasTaskReadyToRun()); scheduler.remove(this); } }, 0, 1000); scheduler.tick(null); } @Test public void getDelayTillNextTaskTest() { assertEquals(Long.MAX_VALUE, scheduler.getDelayTillNextTask()); // schedule in the future scheduler.schedule(DoNothingRunnable.instance(), 1000 * 15); // still should have nothing ready to run assertTrue(scheduler.getDelayTillNextTask() > 0); scheduler.execute(DoNothingRunnable.instance()); // should now have tasks ready to run assertTrue(scheduler.getDelayTillNextTask() <= 0); scheduler.tick(null); // should no longer have anything to run assertTrue(scheduler.getDelayTillNextTask() > 0); scheduler.queueManager.highPriorityQueueSet .addScheduled(new OneTimeTaskWrapper(DoNothingRunnable.instance(), scheduler.queueManager.highPriorityQueueSet.scheduleQueue, Clock.lastKnownForwardProgressingMillis())); // now should be true with scheduled task which is ready to run assertTrue(scheduler.getDelayTillNextTask() <= 0); } @Test public void getDelayTillNextTaskRunningTaskTest() { scheduler.execute(new Runnable() { @Override public void run() { assertEquals(Long.MAX_VALUE, scheduler.getDelayTillNextTask()); scheduler.execute(DoNothingRunnable.instance()); assertTrue(scheduler.getDelayTillNextTask() <= 0); scheduler.remove(this); } }); scheduler.tick(null); } @Test public void clearTasksTest() { scheduler.schedule(DoNothingRunnable.instance(), 1000 * 15); scheduler.execute(DoNothingRunnable.instance()); scheduler.clearTasks(); assertEquals(0, scheduler.queueManager.highPriorityQueueSet.queueSize()); assertEquals(0, scheduler.queueManager.lowPriorityQueueSet.queueSize()); } }