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.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.junit.Test;
import org.threadly.concurrent.future.ListenableFuture;
import org.threadly.test.concurrent.AsyncVerifier;
import org.threadly.test.concurrent.TestRunnable;
@SuppressWarnings("javadoc")
public abstract class SubmitterSchedulerInterfaceTest extends SubmitterExecutorInterfaceTest {
protected abstract SubmitterSchedulerFactory getSubmitterSchedulerFactory();
protected abstract boolean isSingleThreaded();
@Override
protected SubmitterExecutorFactory getSubmitterExecutorFactory() {
return getSubmitterSchedulerFactory();
}
@Test
@Override
public void executeInOrderTest() throws InterruptedException, TimeoutException {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler executor = factory.makeSubmitterScheduler(1, false);
final AsyncVerifier av = new AsyncVerifier();
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) {
av.assertTrue(fLastRun.ranOnce());
}
av.signalComplete();
}
};
executor.schedule(DoNothingRunnable.instance(), 5);
executor.execute(lastRun);
}
av.waitForTest(10 * 1000, testQty);
} finally {
factory.shutdown();
}
}
@Test
public void scheduleInOrderTest() throws InterruptedException, TimeoutException {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(1, false);
final AsyncVerifier av = new AsyncVerifier();
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) {
av.assertTrue(fLastRun.ranOnce());
}
av.signalComplete();
}
};
// TODO - can we adjust the DELAY_TIME so that we are more likely to have collisions?
scheduler.schedule(lastRun, DELAY_TIME);
}
av.waitForTest(10 * 1000, testQty);
} finally {
factory.shutdown();
}
}
@Test
public void scheduleTest() {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(TEST_QTY, true);
List<TestRunnable> runnables = new ArrayList<>(TEST_QTY);
for (int i = 0; i < TEST_QTY; i++) {
TestRunnable tr = new TestRunnable();
scheduler.schedule(tr, DELAY_TIME);
runnables.add(tr);
}
// verify execution and execution times
Iterator<TestRunnable> it = runnables.iterator();
while (it.hasNext()) {
TestRunnable tr = it.next();
long executionDelay = tr.getDelayTillFirstRun();
assertTrue(executionDelay >= DELAY_TIME);
// should be very timely with a core pool size that matches runnable count
assertTrue(executionDelay <= (DELAY_TIME + (SLOW_MACHINE ? 2000 : 1000)));
assertEquals(1, tr.getRunCount());
}
} finally {
factory.shutdown();
}
}
@Test
public void scheduleNoDelayTest() {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(TEST_QTY, true);
TestRunnable tr = new TestRunnable();
scheduler.schedule(tr, 0);
tr.blockTillStarted();
assertEquals(1, tr.getRunCount());
} finally {
factory.shutdown();
}
}
@Test
public void submitScheduledRunnableNoDelayTest() throws InterruptedException, ExecutionException {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(TEST_QTY, true);
TestRunnable tr = new TestRunnable();
ListenableFuture<?> f = scheduler.submitScheduled(tr, 0);
assertNotNull(f);
tr.blockTillFinished();
assertEquals(1, tr.getRunCount());
assertNull(f.get());
} finally {
factory.shutdown();
}
}
@Test
public void submitScheduledCallableNoDelayTest() throws InterruptedException, ExecutionException {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(TEST_QTY, true);
TestCallable tc = new TestCallable(0);
ListenableFuture<?> f = scheduler.submitScheduled(tc, 0);
assertNotNull(f);
assertTrue(tc.getReturnedResult() == f.get());
} finally {
factory.shutdown();
}
}
@Test
public void scheduleFail() {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(1, false);
try {
scheduler.schedule(null, 1000);
fail("Exception should have been thrown");
} catch (IllegalArgumentException e) {
// expected
}
try {
scheduler.schedule(DoNothingRunnable.instance(), -1);
fail("Exception should have been thrown");
} catch (IllegalArgumentException e) {
// expected
}
} finally {
factory.shutdown();
}
}
@Test
public void scheduleWithFixedDelayTest() {
recurringExecutionTest(false, true);
}
@Test
public void scheduleWithFixedDelayInitialDelayTest() {
recurringExecutionTest(true, true);
}
@Test
public void scheduleAtFixedRateTest() {
recurringExecutionTest(false, false);
}
@Test
public void scheduleAtFixedRateInitialDelayTest() {
recurringExecutionTest(true, false);
}
protected void recurringExecutionTest(boolean initialDelay, boolean fixedDelay) {
final int initialDelayInMillis = initialDelay ? DELAY_TIME : 0;
final int expectedMinimumDelay = DELAY_TIME * (CYCLE_COUNT - (initialDelay ? 0 : 1));
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(TEST_QTY, true);
List<TestRunnable> runnables = new ArrayList<>(TEST_QTY);
for (int i = 0; i < TEST_QTY; i++) {
TestRunnable tr = new TestRunnable(fixedDelay ? 0 : DELAY_TIME);
if (fixedDelay) {
scheduler.scheduleWithFixedDelay(tr, initialDelayInMillis, DELAY_TIME);
} else {
scheduler.scheduleAtFixedRate(tr, initialDelayInMillis, DELAY_TIME);
}
runnables.add(tr);
}
// verify execution and execution times
Iterator<TestRunnable> it = runnables.iterator();
while (it.hasNext()) {
TestRunnable tr = it.next();
tr.blockTillFinished((TEST_QTY * (expectedMinimumDelay + 5000)), CYCLE_COUNT);
if (initialDelay) {
long executionDelay = tr.getDelayTillFirstRun();
assertTrue(executionDelay >= DELAY_TIME);
if (! isSingleThreaded()) {
// should be very timely with a core pool size that matches runnable count
assertTrue(executionDelay <= DELAY_TIME + (SLOW_MACHINE ? 10_000 : 2_000));
}
}
long executionDelay = tr.getDelayTillRun(CYCLE_COUNT);
assertTrue(executionDelay >= expectedMinimumDelay);
// should be very timely with a core pool size that matches runnable count
if (! isSingleThreaded()) {
assertTrue(executionDelay <= expectedMinimumDelay + (SLOW_MACHINE ? 10_000 : 2_000));
}
}
} finally {
factory.shutdown();
}
}
@Test
public void recurringExecutionFail() {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(1, false);
try {
scheduler.scheduleWithFixedDelay(null, 1000, 1000);
fail("Exception should have been thrown");
} catch (IllegalArgumentException e) {
// expected
}
try {
scheduler.scheduleWithFixedDelay(DoNothingRunnable.instance(), -1, 1000);
fail("Exception should have been thrown");
} catch (IllegalArgumentException e) {
// expected
}
try {
scheduler.scheduleWithFixedDelay(DoNothingRunnable.instance(), 1000, -1);
fail("Exception should have been thrown");
} catch (IllegalArgumentException e) {
// expected
}
} finally {
factory.shutdown();
}
}
@Test
public void submitScheduledRunnableTest() throws InterruptedException, ExecutionException, TimeoutException {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(TEST_QTY, true);
List<TestRunnable> runnables = new ArrayList<>(TEST_QTY);
List<Future<?>> futures = new ArrayList<>(TEST_QTY);
for (int i = 0; i < TEST_QTY; i++) {
TestRunnable tr = new TestRunnable();
Future<?> future = scheduler.submitScheduled(tr, DELAY_TIME);
assertNotNull(future);
runnables.add(tr);
futures.add(future);
}
// verify execution and execution times
Iterator<TestRunnable> it = runnables.iterator();
while (it.hasNext()) {
TestRunnable tr = it.next();
long executionDelay = tr.getDelayTillFirstRun();
assertTrue(executionDelay >= DELAY_TIME);
// should be very timely with a core pool size that matches runnable count
assertTrue(executionDelay <= (DELAY_TIME + 2000));
assertEquals(1, tr.getRunCount());
}
Iterator<Future<?>> futureIt = futures.iterator();
while (futureIt.hasNext()) {
Future<?> future = futureIt.next();
// future should basically be done already, but we set a bit of a timeout in case of slow systems
assertNull(future.get(10, TimeUnit.SECONDS));
assertTrue(future.isDone());
}
} finally {
factory.shutdown();
}
}
@Test
public void submitScheduledRunnableWithResultTest() throws InterruptedException, ExecutionException, TimeoutException {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(TEST_QTY, true);
List<TestRunnable> runnables = new ArrayList<>(TEST_QTY);
List<Future<TestRunnable>> futures = new ArrayList<>(TEST_QTY);
for (int i = 0; i < TEST_QTY; i++) {
TestRunnable tr = new TestRunnable();
Future<TestRunnable> future = scheduler.submitScheduled(tr, tr, DELAY_TIME);
assertNotNull(future);
runnables.add(tr);
futures.add(future);
}
// verify execution and execution times
Iterator<TestRunnable> it = runnables.iterator();
while (it.hasNext()) {
TestRunnable tr = it.next();
long executionDelay = tr.getDelayTillFirstRun();
assertTrue(executionDelay >= DELAY_TIME);
// should be very timely with a core pool size that matches runnable count
assertTrue(executionDelay <= (DELAY_TIME + (SLOW_MACHINE ? 2000 : 1000)));
assertEquals(1, tr.getRunCount());
}
it = runnables.iterator();
Iterator<Future<TestRunnable>> futureIt = futures.iterator();
while (futureIt.hasNext()) {
Future<?> future = futureIt.next();
assertTrue(future.get(10, TimeUnit.SECONDS) == it.next());
assertTrue(future.isDone());
}
} finally {
factory.shutdown();
}
}
@Test
public void submitScheduledCallableTest() throws InterruptedException, ExecutionException, TimeoutException {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(TEST_QTY, true);
List<TestCallable> callables = new ArrayList<>(TEST_QTY);
List<Future<Object>> futures = new ArrayList<>(TEST_QTY);
for (int i = 0; i < TEST_QTY; i++) {
TestCallable tc = new TestCallable(0);
Future<Object> future = scheduler.submitScheduled(tc, DELAY_TIME);
assertNotNull(future);
callables.add(tc);
futures.add(future);
}
// verify execution and execution times
Iterator<TestCallable> it = callables.iterator();
Iterator<Future<Object>> futureIt = futures.iterator();
while (futureIt.hasNext()) {
Future<Object> future = futureIt.next();
TestCallable tc = it.next();
assertTrue(tc.getReturnedResult() == future.get(10, TimeUnit.SECONDS));
assertTrue(future.isDone());
long executionDelay = tc.getDelayTillFirstRun();
assertTrue(executionDelay >= DELAY_TIME);
// should be very timely with a core pool size that matches runnable count
assertTrue(executionDelay <= (DELAY_TIME + (SLOW_MACHINE ? 2000 : 1000)));
}
} finally {
factory.shutdown();
}
}
@Test
public void submitScheduledRunnableFail() {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(1, false);
try {
scheduler.submitScheduled((Runnable)null, 1000);
fail("Exception should have been thrown");
} catch (IllegalArgumentException e) {
// expected
}
try {
scheduler.submitScheduled((Runnable)null, null, 1000);
fail("Exception should have been thrown");
} catch (IllegalArgumentException e) {
// expected
}
try {
scheduler.submitScheduled(DoNothingRunnable.instance(), -1);
fail("Exception should have been thrown");
} catch (IllegalArgumentException e) {
// expected
}
try {
scheduler.submitScheduled(DoNothingRunnable.instance(), null, -1);
fail("Exception should have been thrown");
} catch (IllegalArgumentException e) {
// expected
}
} finally {
factory.shutdown();
}
}
@Test
public void submitScheduledCallableFail() {
SubmitterSchedulerFactory factory = getSubmitterSchedulerFactory();
try {
SubmitterScheduler scheduler = factory.makeSubmitterScheduler(1, false);
try {
scheduler.submitScheduled((Callable<Void>)null, 1000);
fail("Exception should have been thrown");
} catch (IllegalArgumentException e) {
// expected
}
try {
scheduler.submitScheduled(new TestCallable(0), -1);
fail("Exception should have been thrown");
} catch (IllegalArgumentException e) {
// expected
}
} finally {
factory.shutdown();
}
}
public interface SubmitterSchedulerFactory extends SubmitterExecutorFactory {
public SubmitterScheduler makeSubmitterScheduler(int poolSize, boolean prestartIfAvailable);
}
}