package com.equalexperts.logging.impl; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import java.util.concurrent.*; import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; public class AsyncExecutorTest { private final ThreadFactory factory = mock(ThreadFactory.class); private final AsyncExecutor asyncExecutor = new AsyncExecutor(factory); private Thread createdThread; @Before public void setup() { when(factory.newThread(any())).then((InvocationOnMock invocation) -> { Runnable r = (Runnable) invocation.getArguments()[0]; createdThread = new Thread(r); return createdThread; }); } @Test public void execute_shouldCreateAndStartAThreadWithTheProvidedRunnableAndReturnAFuture() throws Exception { CountDownLatch latch = new CountDownLatch(1); Future<?> result = asyncExecutor.execute(latch::countDown); latch.await(5, TimeUnit.SECONDS); //give the new thread time to start to running verify(factory).newThread(any()); //The provided runnable will be wrapped, not passed directly assertEquals(0, latch.getCount()); //but the runnable will be executed assertNotNull(result); assertNotNull(createdThread); assertNotEquals(Thread.State.NEW, createdThread.getState()); } /* The following tests are for method calls on the Future instance return by the execute method */ @Test public void get_shouldJoinTheCreatedThread_givenAFutureReturnedByExecute() throws Exception { long startTime = System.nanoTime(); Future<?> result = asyncExecutor.execute(suppressCheckedExceptions(() -> Thread.sleep(250L))); result.get(); long endTime = System.nanoTime(); assertThat(endTime - startTime, isGreaterThan(100 * 1000000L)); } @Test public void get_shouldSucceed_whenTheThreadThrowsAnException_givenAFutureReturnedByExecute() throws Exception { Future<?> result = asyncExecutor.execute(() -> {throw new RuntimeException("blah"); }); result.get(100, TimeUnit.MILLISECONDS); } @Test public void getWithATimeout_shouldSucceed_whenTheThreadEndsQuickly_givenAFutureReturnedByExecute() throws Exception { Future<?> result = asyncExecutor.execute(suppressCheckedExceptions(() -> Thread.sleep(50L))); result.get(100, TimeUnit.MILLISECONDS); } @Test public void getWithATimeout_shouldThrowATimeoutException_whenTheThreadDoesNotEndQuickly_givenAFutureReturnedByExecute() throws Exception { Future<?> result = asyncExecutor.execute(suppressCheckedExceptions(() -> Thread.sleep(250L))); try { result.get(50, TimeUnit.MILLISECONDS); fail("Expected an exception"); } catch (TimeoutException ignore) {} } @Test public void cancel_shouldThrowAnUnsupportedOperationException_givenAFutureReturnedByExecute() throws Exception { Future<?> result = asyncExecutor.execute(() -> {}); try { result.cancel(true); fail("Expected an exception"); } catch (UnsupportedOperationException ignore) {} } @Test public void isCancelled_shouldReturnFalse_givenAFutureReturnedByExecute() throws Exception { Future<?> result = asyncExecutor.execute(() -> {}); assertFalse(result.isCancelled()); } @Test public void isDone_shouldReturnTrue_whenTheThreadIsAlive_givenAFutureReturnedByExecute() throws Exception { CountDownLatch latch = new CountDownLatch(1); Future<?> result = asyncExecutor.execute(suppressCheckedExceptions(latch::await)); try { assertFalse(result.isDone()); } finally { latch.countDown(); //don't want a thread waiting forever... } } @Test public void isDone_shouldReturnFalse_whenTheThreadIsFinished_givenAFutureReturnedByExecute() throws Exception { Future<?> result = asyncExecutor.execute(() -> {}); Thread.sleep(50L); //give other thread a chance to finish... assertTrue(result.isDone()); } private static Runnable suppressCheckedExceptions(RunnableThatThrows r) { return () -> { try { r.run(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } }; } private static Matcher<Long> isGreaterThan(final long other) { return new BaseMatcher<Long>() { @Override public boolean matches(Object item) { if (!(item instanceof Long)) { return false; } long itemValue = (Long) item; return itemValue > other; } @Override public void describeTo(Description description) { description.appendText("greater than").appendValue(other); } }; } @FunctionalInterface private static interface RunnableThatThrows { void run() throws Exception; } }