package org.threadly.concurrent; import static org.junit.Assert.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.threadly.concurrent.SingleThreadScheduler; import org.threadly.concurrent.future.FutureUtils; import org.threadly.concurrent.future.ListenableFuture; import org.threadly.concurrent.future.SettableListenableFuture; import org.threadly.test.concurrent.TestCondition; import org.threadly.test.concurrent.TestUtils; import org.threadly.test.concurrent.TestableScheduler; @SuppressWarnings("javadoc") public class PollerTest { private static final int POLL_INTERVAL = 10_000; private TestableScheduler scheduler; private Poller poller; @Before public void setup() { scheduler = new TestableScheduler(); poller = new Poller(scheduler, POLL_INTERVAL); } @After public void cleanup() { scheduler = null; poller = null; } @Test public void simplePollTest() { AtomicBoolean pollState = new AtomicBoolean(false); ListenableFuture<?> f = poller.watch(() -> pollState.get()); assertFalse(f.isDone()); assertEquals(1, scheduler.advance(POLL_INTERVAL)); assertFalse(f.isDone()); pollState.set(true); assertEquals(1, scheduler.advance(POLL_INTERVAL)); assertTrue(f.isDone()); assertEquals(0, scheduler.advance(POLL_INTERVAL)); } @Test public void timeoutTest() { poller = new Poller(new SingleThreadScheduler(), POLL_INTERVAL, 10); ListenableFuture<?> f = poller.watch(() -> false); TestUtils.sleep(10); new TestCondition() { @Override public boolean get() { return f.isDone() && f.isCancelled(); } }.blockTillTrue();// will throw if does not become true } @Test public void watchListenableFutureWithExtendedResultTest() { ListenableFuture<DoNothingRunnable> lf = FutureUtils.immediateResultFuture(DoNothingRunnable.instance()); ListenableFuture<Runnable> lfResult = poller.watch(lf); assertTrue(((ListenableFuture<?>)lfResult) == lf); } @Test public void watchAlreadyDoneFutureWithResultTest() throws InterruptedException, ExecutionException { final Object objResult = new Object(); ListenableFuture<Object> lfResult = poller.watch(new AlreadyDoneFuture() { @Override public Object get() throws ExecutionException { return objResult; } @Override public Object get(long timeout, TimeUnit unit) throws ExecutionException { return objResult; } }); assertTrue(lfResult.isDone()); assertTrue(objResult == lfResult.get()); } @Test public void watchAlreadyDoneFutureWithFailureTest() throws InterruptedException { final Throwable rootCause = new Exception(); ListenableFuture<Object> lfResult = poller.watch(new AlreadyDoneFuture() { @Override public Object get() throws ExecutionException { throw new ExecutionException(rootCause); } @Override public Object get(long timeout, TimeUnit unit) throws ExecutionException { throw new ExecutionException(rootCause); } }); assertTrue(lfResult.isDone()); try { lfResult.get(); fail("Exception should have thrown"); } catch (ExecutionException e) { assertTrue(e.getCause() == rootCause); } } @Test public void watchAlreadyCanceledFutureTest() { Future<Object> canceledFuture = new FutureTask<>(() -> { return null; }); assertTrue(canceledFuture.cancel(true)); ListenableFuture<Object> lfResult = poller.watch(canceledFuture); assertTrue(lfResult.isDone()); assertTrue(lfResult.isCancelled()); } @Test public void watchIncompleteFutureWithResultTest() throws InterruptedException, ExecutionException { poller = new Poller(scheduler, POLL_INTERVAL, POLL_INTERVAL * 2); // must have timeout to avoid just casting SLF Object objResult = new Object(); SettableListenableFuture<Object> slf = new SettableListenableFuture<>(); ListenableFuture<Object> lfResult = poller.watch(slf); assertFalse(lfResult.isDone()); slf.setResult(objResult); assertEquals(1, scheduler.advance(POLL_INTERVAL)); assertTrue(lfResult.isDone()); assertTrue(objResult == lfResult.get()); } @Test public void watchIncompleteFutureWithFailureTest() throws InterruptedException { poller = new Poller(scheduler, POLL_INTERVAL, POLL_INTERVAL * 2); // must have timeout to avoid just casting SLF Throwable rootCause = new Exception(); SettableListenableFuture<Object> slf = new SettableListenableFuture<>(); ListenableFuture<Object> lfResult = poller.watch(slf); assertFalse(lfResult.isDone()); slf.setFailure(rootCause); assertEquals(1, scheduler.advance(POLL_INTERVAL)); assertTrue(lfResult.isDone()); try { lfResult.get(); fail("Exception should have thrown"); } catch (ExecutionException e) { assertTrue(e.getCause() == rootCause); } } @Test public void watchIncompleteCanceledFutureTest() { poller = new Poller(scheduler, POLL_INTERVAL, POLL_INTERVAL * 2); // must have timeout to avoid just casting SLF SettableListenableFuture<Object> slf = new SettableListenableFuture<>(); ListenableFuture<Object> lfResult = poller.watch(slf); assertFalse(lfResult.isDone()); slf.cancel(false); assertEquals(1, scheduler.advance(POLL_INTERVAL)); assertTrue(lfResult.isDone()); assertTrue(lfResult.isCancelled()); } private static abstract class AlreadyDoneFuture implements Future<Object> { @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return true; } } }