package org.threadly.test.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.atomic.AtomicReference;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.threadly.concurrent.DoNothingRunnable;
import org.threadly.concurrent.TestCallable;
import org.threadly.concurrent.TestRuntimeFailureRunnable;
import org.threadly.util.Clock;
import org.threadly.util.ExceptionHandler;
import org.threadly.util.SuppressedStackRuntimeException;
@SuppressWarnings("javadoc")
public class TestableSchedulerTest {
protected TestableScheduler scheduler;
@Before
public void setup() {
scheduler = new TestableScheduler();
}
@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 lastTickTimeTest() {
long now = Clock.lastKnownTimeMillis();
scheduler.tick(now + 1000);
assertEquals(now + 1000, scheduler.getLastTickTime());
}
@Test
public void advanceWithoutHandlerThrowsRuntimeExceptionTest() {
RuntimeException failure = new SuppressedStackRuntimeException();
scheduler.execute(new TestRuntimeFailureRunnable(failure));
try {
scheduler.advance(10);
fail("Exception should have thrown");
} catch (Exception e) {
assertTrue(e == failure);
}
}
@Test
public void advanceHandlesRuntimeExceptionTest() {
RuntimeException failure = new SuppressedStackRuntimeException();
final AtomicReference<Throwable> handledException = new AtomicReference<>(null);
scheduler.execute(new TestRuntimeFailureRunnable(failure));
int runCount = scheduler.advance(10, new ExceptionHandler() {
@Override
public void handleException(Throwable thrown) {
handledException.set(thrown);
}
});
assertEquals(1, runCount);
assertTrue(handledException.get() == failure);
}
@Test
public void tickWithoutHandlerThrowsRuntimeExceptionTest() {
RuntimeException failure = new SuppressedStackRuntimeException();
scheduler.execute(new TestRuntimeFailureRunnable(failure));
try {
scheduler.tick();
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 advanceTest() {
long now = scheduler.getLastTickTime();
scheduler.advance(1000);
assertEquals(now + 1000, scheduler.getLastTickTime());
}
@Test
public void advanceThenTickTest() {
scheduler.execute(DoNothingRunnable.instance());
scheduler.advance(1000 * 10);
TestRunnable tr = new TestRunnable();
scheduler.execute(tr);
assertEquals(1, scheduler.tick());
assertTrue(tr.ranOnce());
}
@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(Clock.lastKnownTimeMillis()));
it = runnables.iterator();
while (it.hasNext()) {
assertEquals(1, it.next().getRunCount());
}
// verify no more run after a second tick
assertEquals(scheduler.tick(), 0);
it = runnables.iterator();
while (it.hasNext()) {
assertEquals(1, it.next().getRunCount());
}
}
@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(Clock.lastKnownTimeMillis()));
it = runnables.iterator();
while (it.hasNext()) {
assertEquals(1, it.next().getRunCount());
}
// verify no more run after a second tick
assertEquals(0, scheduler.tick());
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(Clock.lastKnownTimeMillis()));
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() {
long scheduleDelay = 1000 * 10;
TestRunnable executeRun = new TestRunnable();
TestRunnable scheduleRun = new TestRunnable();
scheduler.schedule(scheduleRun, scheduleDelay);
scheduler.execute(executeRun);
assertEquals(1, scheduler.tick());
assertEquals(1, executeRun.getRunCount()); // should have run
assertEquals(0, scheduleRun.getRunCount()); // should NOT have run yet
assertEquals(1, scheduler.advance(scheduleDelay));
assertEquals(1, executeRun.getRunCount()); // should NOT have run again
assertEquals(1, scheduleRun.getRunCount()); // should have run
assertEquals(scheduler.advance(scheduleDelay), 0); // should not execute anything
assertEquals(1, executeRun.getRunCount()); // should NOT have run again
assertEquals(1, scheduleRun.getRunCount()); // should NOT have run again
}
@Test
public void submitScheduledRunnableTest() {
long scheduleDelay = 1000 * 10;
TestRunnable submitRun = new TestRunnable();
TestRunnable scheduleRun = new TestRunnable();
Future<?> future = scheduler.submit(submitRun);
assertNotNull(future);
future = scheduler.submitScheduled(scheduleRun, scheduleDelay);
assertNotNull(future);
assertEquals(1, scheduler.tick());
assertEquals(1, submitRun.getRunCount()); // should have run
assertEquals(0, scheduleRun.getRunCount()); // should NOT have run yet
assertEquals(1, scheduler.advance(scheduleDelay));
assertEquals(1, submitRun.getRunCount()); // should NOT have run again
assertEquals(1, scheduleRun.getRunCount()); // should have run
assertEquals(0, scheduler.advance(scheduleDelay)); // should not execute anything
assertEquals(1, submitRun.getRunCount()); // should NOT have run again
assertEquals(1, scheduleRun.getRunCount()); // should NOT have run again
}
@Test
public void submitScheduledRunnableFail() {
try {
scheduler.submitScheduled((Runnable)null, 10);
fail("Exception should have thrown");
} catch (IllegalArgumentException e) {
// expected
}
try {
scheduler.submitScheduled(new TestRunnable(), -10);
fail("Exception should have thrown");
} catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void submitScheduledCallableTest() {
long scheduleDelay = 1000 * 10;
TestCallable submitRun = new TestCallable();
TestCallable scheduleRun = new TestCallable();
Future<?> future = scheduler.submit(submitRun);
assertNotNull(future);
future = scheduler.submitScheduled(scheduleRun, scheduleDelay);
assertNotNull(future);
assertEquals(1, scheduler.tick());
assertTrue(submitRun.isDone()); // should have run
assertFalse(scheduleRun.isDone()); // should NOT have run yet
assertEquals(1, scheduler.advance(scheduleDelay));
assertTrue(scheduleRun.isDone()); // should have run
assertEquals(0, scheduler.advance(scheduleDelay)); // should not execute anything
}
@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 scheduleWithFixedDelayTest() {
scheduleRecurringTest(true);
}
private void scheduleRecurringTest(boolean fixedDelay) {
long delay = 1000 * 10;
TestRunnable immediateRun = new TestRunnable();
TestRunnable initialDelay = new TestRunnable();
if (fixedDelay) {
scheduler.scheduleWithFixedDelay(immediateRun, 0, delay);
scheduler.scheduleWithFixedDelay(initialDelay, delay, delay);
} else {
scheduler.scheduleAtFixedRate(immediateRun, 0, delay);
scheduler.scheduleAtFixedRate(initialDelay, delay, delay);
}
assertEquals(1, scheduler.tick());
assertEquals(1, immediateRun.getRunCount()); // should have run
assertEquals(0, initialDelay.getRunCount()); // should NOT have run yet
assertEquals(2, scheduler.advance(delay));
assertEquals(2, immediateRun.getRunCount()); // should have run again
assertEquals(1, initialDelay.getRunCount()); // should have run for the first time
assertEquals(2, scheduler.advance(delay));
assertEquals(3, immediateRun.getRunCount()); // should have run again
assertEquals(2, initialDelay.getRunCount()); // should have run again
assertEquals(0, scheduler.tick(scheduler.getLastTickTime())); // should not execute anything
assertEquals(3, immediateRun.getRunCount()); // should NOT have run again
assertEquals(2, initialDelay.getRunCount()); // should NOT have run again
}
@Test
public void scheduleWithFixedDelayFail() {
try {
scheduler.scheduleWithFixedDelay(null, 10, 10);
fail("Exception should have thrown");
} catch (IllegalArgumentException e) {
// expected
}
try {
scheduler.scheduleWithFixedDelay(new TestRunnable(), -10, 10);
fail("Exception should have thrown");
} catch (IllegalArgumentException e) {
// expected
}
try {
scheduler.scheduleWithFixedDelay(new TestRunnable(), 10, -10);
fail("Exception should have thrown");
} catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void scheduleAtFixedRateTest() {
scheduleRecurringTest(false);
}
@Test
public void scheduleAtFixedRateFail() {
try {
scheduler.scheduleAtFixedRate(null, 10, 10);
fail("Exception should have thrown");
} catch (IllegalArgumentException e) {
// expected
}
try {
scheduler.scheduleAtFixedRate(new TestRunnable(), -10, 10);
fail("Exception should have thrown");
} catch (IllegalArgumentException e) {
// expected
}
try {
scheduler.scheduleAtFixedRate(new TestRunnable(), 10, -10);
fail("Exception should have thrown");
} catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void removeRunnableTest() {
long delay = 1000 * 10;
TestRunnable immediateRun = new TestRunnable();
TestRunnable initialDelay = new TestRunnable();
assertFalse(scheduler.remove(immediateRun));
scheduler.scheduleWithFixedDelay(immediateRun, 0, delay);
assertTrue(scheduler.remove(immediateRun));
scheduler.scheduleWithFixedDelay(immediateRun, 0, delay);
scheduler.scheduleWithFixedDelay(initialDelay, delay, delay);
assertEquals(1, scheduler.tick());
assertEquals(1, immediateRun.getRunCount()); // should have run
assertEquals(0, initialDelay.getRunCount()); // should NOT have run yet
assertTrue(scheduler.remove(immediateRun));
assertEquals(1, scheduler.advance(delay));
assertEquals(1, immediateRun.getRunCount()); // should NOT have run again
assertEquals(1, initialDelay.getRunCount()); // should have run
assertEquals(0, scheduler.tick(scheduler.getLastTickTime())); // should not execute anything
assertEquals(1, immediateRun.getRunCount()); // should NOT have run
assertEquals(1, initialDelay.getRunCount()); // should NOT have run
}
@Test
public void removeCallableTest() {
long delay = 1000 * 10;
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);
assertEquals(0, scheduler.tick());
// neither should run yet
assertFalse(immediateRun.isDone());
assertFalse(delayRun.isDone());
scheduler.submitScheduled(immediateRun, 0);
assertEquals(2, scheduler.advance(delay));
// both should run now
assertTrue(immediateRun.isDone());
assertTrue(delayRun.isDone());
// neither should be in scheduler any more
assertFalse(scheduler.remove(immediateRun));
assertFalse(scheduler.remove(delayRun));
}
@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());
// should be removed for subsequent ticks
assertEquals(0, scheduler.tick());
assertEquals(1, tr.getRunCount());
}
@Test (expected = IllegalArgumentException.class)
public void tickFail() {
scheduler.tick(scheduler.getLastTickTime() - 1);
fail("Exception should have been thrown");
}
@Test
public void hasTaskReadyToRunTest() {
assertFalse(scheduler.hasTaskReadyToRun());
scheduler.schedule(DoNothingRunnable.instance(), 1);
assertFalse(scheduler.hasTaskReadyToRun());
scheduler.execute(DoNothingRunnable.instance());
assertTrue(scheduler.hasTaskReadyToRun());
}
@Test
public void getDelayTillNextTaskTest() {
assertEquals(Long.MAX_VALUE, scheduler.getDelayTillNextTask());
scheduler.schedule(DoNothingRunnable.instance(), 1);
assertEquals(1, scheduler.getDelayTillNextTask());
scheduler.execute(DoNothingRunnable.instance());
assertEquals(0, scheduler.getDelayTillNextTask());
}
@Test
public void clearTasksTest() {
assertTrue(scheduler.clearTasks().isEmpty());
TestRunnable executeTask = new TestRunnable();
TestRunnable scheduleTask = new TestRunnable();
scheduler.execute(executeTask);
scheduler.schedule(scheduleTask, 1);
List<Runnable> result = scheduler.clearTasks();
assertEquals(2, result.size());
assertTrue(result.contains(executeTask));
assertTrue(result.contains(scheduleTask));
}
}