/* * JBoss, Home of Professional Open Source. * * Copyright 2012 Red Hat, Inc. and/or its affiliates, and individual * contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.xnio; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.xnio.IoFuture.Status; /** * Test for {@link AbstractIoFuture}. * * @author <a href="mailto:flavia.rainone@jboss.com">Flavia Rainone</a> * */ public class AbstractIoFutureTestCase { private TestIoFuture future; @Before public void createFuture() { future = new TestIoFuture(); assertSame(Status.WAITING, future.getStatus()); } @Test public void waitForResult() throws InterruptedException { final Awaiter awaiter = new Awaiter(future); final Thread awaiterThread = new Thread(awaiter); awaiterThread.start(); awaiterThread.join(100); assertTrue(awaiterThread.isAlive()); assertTrue(future.setResult("test")); awaiterThread.join(); assertSame(Status.DONE, future.getStatus()); assertSame(Status.DONE, awaiter.getAwaitStatus()); assertSame(Status.DONE, future.await()); // shouldn't block assertFalse(future.setResult("second reult")); } @Test public void interruptWaiter() throws InterruptedException { final Awaiter awaiter = new Awaiter(future); final Thread awaiterThread = new Thread(awaiter); awaiterThread.start(); // interrupt a couple of times for (int i = 0; i < 3; i++) { awaiterThread.interrupt(); Thread.sleep(100); } assertSame(Status.WAITING, future.getStatus()); assertSame(future, future.cancel()); awaiterThread.join(); assertSame(Status.CANCELLED, awaiter.getAwaitStatus()); } @Test public void waitForResultWithTimeout() throws InterruptedException { final Awaiter awaiter = new Awaiter(future, 1, TimeUnit.DAYS); final Thread awaiterThread = new Thread(awaiter); awaiterThread.start(); awaiterThread.join(100); assertTrue(awaiterThread.isAlive()); assertTrue(future.setResult("test")); awaiterThread.join(); assertSame(Status.DONE, future.getStatus()); assertSame(Status.DONE, awaiter.getAwaitStatus()); assertSame(Status.DONE, future.await(100, TimeUnit.MILLISECONDS)); // shouldn't block assertFalse(future.setResult("second result")); } @Test public void interruptWaiterWithTimeout() throws InterruptedException { assertSame(Status.WAITING, future.await(0, TimeUnit.MICROSECONDS)); assertSame(Status.WAITING, future.await(-5, TimeUnit.MICROSECONDS)); final Awaiter awaiter = new Awaiter(future, 1000, TimeUnit.SECONDS); final Thread awaiterThread = new Thread(awaiter); awaiterThread.start(); // interrupt a couple of times for (int i = 0; i < 3; i++) { awaiterThread.interrupt(); Thread.sleep(100); } assertSame(Status.WAITING, future.getStatus()); assertTrue(awaiterThread.isAlive()); assertSame(future, future.cancel()); awaiterThread.join(); assertSame(Status.CANCELLED, awaiter.getAwaitStatus()); assertSame(Status.CANCELLED, future.getStatus()); } @Test public void waitInterruptiblyForResult() throws InterruptedException { final InterruptiblyAwaiter awaiter = new InterruptiblyAwaiter(future); final Thread awaiterThread = new Thread(awaiter); awaiterThread.start(); awaiterThread.join(100); assertTrue(awaiterThread.isAlive()); assertTrue(future.setResult("test")); awaiterThread.join(); assertSame(Status.DONE, future.getStatus()); assertSame(Status.DONE, awaiter.getAwaitStatus()); assertSame(Status.DONE, future.awaitInterruptibly()); // shouldn't block assertFalse(future.setResult("second reult")); } @Test public void interruptInterruptiblyWaiter() throws InterruptedException { final InterruptiblyAwaiter awaiter = new InterruptiblyAwaiter(future); final Thread awaiterThread = new Thread(awaiter); awaiterThread.start(); // interrupt a couple of times for (int i = 0; i < 3; i++) { awaiterThread.interrupt(); Thread.sleep(100); } assertSame(Status.WAITING, future.getStatus()); assertSame(future, future.cancel()); awaiterThread.join(); assertSame(null, awaiter.getAwaitStatus()); assertNotNull(awaiter.getException()); } @Test public void waitInterruptiblyForResultWithTimeout() throws InterruptedException { final InterruptiblyAwaiter awaiter = new InterruptiblyAwaiter(future, 1, TimeUnit.DAYS); final Thread awaiterThread = new Thread(awaiter); awaiterThread.start(); awaiterThread.join(100); assertTrue(awaiterThread.isAlive()); assertTrue(future.setResult("test")); awaiterThread.join(); assertSame(Status.DONE, future.getStatus()); assertSame(Status.DONE, awaiter.getAwaitStatus()); assertSame(Status.DONE, future.awaitInterruptibly(100, TimeUnit.MILLISECONDS)); // shouldn't block assertFalse(future.setResult("second result")); } @Test public void interruptInterruptiblyWaiterWithTimeout() throws InterruptedException { assertSame(Status.WAITING, future.awaitInterruptibly(0, TimeUnit.MICROSECONDS)); assertSame(Status.WAITING, future.awaitInterruptibly(-5, TimeUnit.MICROSECONDS)); final InterruptiblyAwaiter awaiter = new InterruptiblyAwaiter(future, 1000, TimeUnit.SECONDS); final Thread awaiterThread = new Thread(awaiter); awaiterThread.start(); // interrupt a couple of times for (int i = 0; i < 3; i++) { awaiterThread.interrupt(); Thread.sleep(100); } assertSame(Status.WAITING, future.getStatus()); assertFalse(awaiterThread.isAlive()); assertSame(future, future.cancel()); awaiterThread.join(); assertSame(null, awaiter.getAwaitStatus()); assertNotNull(awaiter.getException()); assertSame(Status.CANCELLED, future.getStatus()); } @Test public void retrieveFutureResult() throws Exception { final ResultRetriever resultRetriever = new ResultRetriever(future); final Thread retrieverThread = new Thread(resultRetriever); retrieverThread.start(); retrieverThread.join(100); assertTrue(retrieverThread.isAlive()); assertTrue(future.setResult("get this")); retrieverThread.join(); assertSame(Status.DONE, future.getStatus()); assertSame("get this", resultRetriever.getResult()); assertSame("get this", future.get()); // shouldn't block // cannot change the result nor set a new result assertFalse(future.setResult("second result")); assertFalse(future.setException(new IOException())); assertFalse(future.setCancelled()); } @Test public void retrieveFutureFailure() throws InterruptedException { final ResultRetriever resultRetriever = new ResultRetriever(future); final Thread retrieverThread = new Thread(resultRetriever); retrieverThread.start(); retrieverThread.join(100); assertTrue(retrieverThread.isAlive()); final IOException exception = new IOException("Test exception"); assertTrue(future.setException(exception)); // can't set any result after setting exception assertFalse(future.setException(exception)); assertFalse(future.setCancelled()); assertFalse(future.setResult("anything")); retrieverThread.join(); assertSame(Status.FAILED, future.getStatus()); assertNull(resultRetriever.getResult()); assertSame(exception, resultRetriever.getIOException()); IOException expected = null; try { future.get(); // shouldn't block } catch (IOException e) { expected = e; } assertNotNull(expected); } @Test public void retrieveFutureCancellationResult() throws Exception { final ResultRetriever resultRetriever = new ResultRetriever(future); final Thread retrieverThread = new Thread(resultRetriever); retrieverThread.start(); retrieverThread.join(100); assertTrue(retrieverThread.isAlive()); future.cancel(); // can't set any result after canceling assertFalse(future.setException(new IOException())); assertFalse(future.setCancelled()); future.cancel(); // useless call, future is already canceled assertFalse(future.setResult("anything")); retrieverThread.join(); assertSame(Status.CANCELLED, future.getStatus()); assertNull(resultRetriever.getResult()); assertNotNull(resultRetriever.getCancellationException()); CancellationException expected = null; try { future.get(); // shouldn't block } catch (CancellationException e) { expected = e; } assertNotNull(expected); } @Test public void interruptResultRetrieval() throws InterruptedException { final ResultRetriever resultRetriever= new ResultRetriever(future); final Thread retrieverThread = new Thread(resultRetriever); retrieverThread.start(); // interrupt a couple of times for (int i = 0; i < 3; i++) { retrieverThread.interrupt(); Thread.sleep(100); } assertSame(Status.WAITING, future.getStatus()); assertSame(future, future.cancel()); retrieverThread.join(); assertSame(null, resultRetriever.getResult()); assertNotNull(resultRetriever.getCancellationException()); } @Test public void retrieveFutureResultInterruptibly() throws Exception { final ResultRetriever resultRetriever = new ResultRetriever(future, true); final Thread retrieverThread = new Thread(resultRetriever); retrieverThread.start(); retrieverThread.join(100); assertTrue(retrieverThread.isAlive()); assertTrue(future.setResult("get this")); retrieverThread.join(); assertSame(Status.DONE, future.getStatus()); assertSame("get this", resultRetriever.getResult()); assertSame("get this", future.get()); // shouldn't block // cannot change the result nor set a new result assertFalse(future.setResult("second result")); assertFalse(future.setException(new IOException())); assertFalse(future.setCancelled()); } @Test public void retrieveFutureFailureInterruptibly() throws InterruptedException { final ResultRetriever resultRetriever = new ResultRetriever(future, true); final Thread retrieverThread = new Thread(resultRetriever); retrieverThread.start(); retrieverThread.join(100); assertTrue(retrieverThread.isAlive()); final IOException exception = new IOException("Test exception"); assertTrue(future.setException(exception)); // can't set any result after setting exception assertFalse(future.setException(exception)); assertFalse(future.setCancelled()); assertFalse(future.setResult("anything")); retrieverThread.join(); assertSame(Status.FAILED, future.getStatus()); assertNull(resultRetriever.getResult()); assertSame(exception, resultRetriever.getIOException()); IOException expected = null; try { future.get(); // shouldn't block } catch (IOException e) { expected = e; } assertNotNull(expected); } @Test public void retrieveFutureCancellationResultInterruptibly() throws Exception { final ResultRetriever resultRetriever = new ResultRetriever(future, true); final Thread retrieverThread = new Thread(resultRetriever); retrieverThread.start(); retrieverThread.join(100); assertTrue(retrieverThread.isAlive()); future.cancel(); // can't set any result after canceling assertFalse(future.setException(new IOException())); assertFalse(future.setCancelled()); future.cancel(); // useless call, future is already canceled assertFalse(future.setResult("anything")); retrieverThread.join(); assertSame(Status.CANCELLED, future.getStatus()); assertNull(resultRetriever.getResult()); assertNotNull(resultRetriever.getCancellationException()); CancellationException expected = null; try { future.get(); // shouldn't block } catch (CancellationException e) { expected = e; } assertNotNull(expected); } @Test public void interruptInterruptiblyResultRetrieval() throws InterruptedException { final ResultRetriever resultRetriever= new ResultRetriever(future, true); final Thread retrieverThread = new Thread(resultRetriever); retrieverThread.start(); // interrupt a couple of times for (int i = 0; i < 3; i++) { retrieverThread.interrupt(); Thread.sleep(100); } assertSame(Status.WAITING, future.getStatus()); retrieverThread.join(); assertSame(null, resultRetriever.getResult()); assertNotNull(resultRetriever.getInterruptedException()); } @Test public void setAndRetrieveException () { final IOException exception = new IOException(); future.setException(exception); assertSame(exception, future.getException()); assertFalse(future.setException(new IOException())); assertFalse(future.setException(new IOException())); assertFalse(future.setException(new IOException())); assertFalse(future.setException(new IOException())); assertSame(exception, future.getException()); // wrongfully try to retrieve non-existent exception IllegalStateException expected = null; final TestIoFuture future2 = new TestIoFuture(); future2.setResult("future2"); try { future2.getException(); } catch (IllegalStateException e) { expected = e; } assertNotNull(expected); expected = null; final TestIoFuture future3 = new TestIoFuture(); future3.cancel(); try { future3.getException(); } catch (IllegalStateException e) { expected = e; } assertNotNull(expected); expected = null; final TestIoFuture future4 = new TestIoFuture(); assertSame(Status.WAITING, future4.getStatus()); try { future4.getException(); } catch (IllegalStateException e) { expected = e; } assertNotNull(expected); } private void checkNotifiers(Runnable stopWaitingTask) { final TestNotifier notifier1 = new TestNotifier(); final TestNotifier notifier2 = new TestNotifier(); final TestNotifier notifier3 = new TestNotifier(); final TestNotifier notifier4 = new TestNotifier(); final TestNotifier notifier5 = new TestNotifier(); notifier2.throwExceptionOnNotify(); final Object attachment1 = new Object(); final Object attachment2 = new Object(); final Object attachment3 = new Object(); final Object attachment4 = new Object(); final Object attachment5 = new Object(); future.addNotifier(notifier1, attachment1); future.addNotifier(notifier2, attachment2); future.addNotifier(notifier3, attachment3); future.addNotifier(notifier4, attachment4); assertFalse(notifier1.isInvoked()); assertFalse(notifier2.isInvoked()); assertFalse(notifier3.isInvoked()); assertFalse(notifier4.isInvoked()); stopWaitingTask.run(); assertTrue(notifier1.isInvoked()); assertSame(future, notifier1.getFuture()); assertSame(attachment1, notifier1.getAttachment()); assertTrue(notifier2.isInvoked()); assertSame(future, notifier2.getFuture()); assertSame(attachment2, notifier2.getAttachment()); assertTrue(notifier3.isInvoked()); assertSame(future, notifier3.getFuture()); assertSame(attachment3, notifier3.getAttachment()); assertTrue(notifier4.isInvoked()); assertSame(future, notifier4.getFuture()); assertSame(attachment4, notifier4.getAttachment()); assertFalse(notifier5.isInvoked()); future.addNotifier(notifier5, attachment5); assertTrue(notifier5.isInvoked()); assertSame(future, notifier5.getFuture()); assertSame(attachment5, notifier5.getAttachment()); } @Test public void checkNotifiersWhenDone() { checkNotifiers(new Runnable() { public void run() { future.setResult("run notifiers"); } }); } @Test public void checkNotifiersWhenCancelled() { checkNotifiers(new Runnable() { public void run() { future.cancel(); } }); } @Test public void checkNotifiersWhenFailed() { checkNotifiers(new Runnable() { public void run() { future.setException(new IOException("Test exceptionÅ›")); } }); } @Test public void invokeCancelHandlers() { final TestCancelHandler cancelHandler1 = new TestCancelHandler(); final TestCancelHandler cancelHandler2 = new TestCancelHandler(); final TestCancelHandler cancelHandler3 = new TestCancelHandler(); final TestCancelHandler cancelHandler4 = new TestCancelHandler(); future.addCancelHandler(cancelHandler1); future.addCancelHandler(cancelHandler2); assertFalse(cancelHandler1.isInvoked()); assertFalse(cancelHandler2.isInvoked()); future.startCancellation(); assertTrue(cancelHandler1.isInvoked()); assertTrue(cancelHandler2.isInvoked()); cancelHandler1.clear(); cancelHandler2.clear(); // should be idempotent future.startCancellation(); assertFalse(cancelHandler1.isInvoked()); assertFalse(cancelHandler2.isInvoked()); future.addCancelHandler(cancelHandler3); assertTrue(cancelHandler3.isInvoked()); cancelHandler3.clear(); assertSame(Status.WAITING, future.getStatus()); future.concludeCancellation(); future.addCancelHandler(cancelHandler4); assertTrue(cancelHandler4.isInvoked()); // no cancel handler should have been invoked again, they must be called only once assertFalse(cancelHandler1.isInvoked()); assertFalse(cancelHandler2.isInvoked()); assertFalse(cancelHandler3.isInvoked()); } private static class TestIoFuture extends AbstractIoFuture<String> { @Override public TestIoFuture cancel() { super.cancel(); setCancelled(); return this; } public void startCancellation() { super.cancel(); } public void concludeCancellation() { setCancelled(); } } private static class Awaiter implements Runnable { private final IoFuture<?> ioFuture; private final long timeout; private final TimeUnit timeoutUnit; private Status status; public Awaiter(IoFuture<?> f) { this(f, -1, null); } public Awaiter(IoFuture<?> f, long t, TimeUnit tu) { ioFuture = f; timeout = t; timeoutUnit = tu; } public void run() { if (timeoutUnit == null) { status = ioFuture.await(); } else { status = ioFuture.await(timeout, timeoutUnit); } } public Status getAwaitStatus() { return status; } } private static class InterruptiblyAwaiter implements Runnable { private final IoFuture<?> ioFuture; private final long timeout; private final TimeUnit timeoutUnit; private InterruptedException exception; private Status status; public InterruptiblyAwaiter(IoFuture<?> f) { this(f, -1, null); } public InterruptiblyAwaiter(IoFuture<?> f, long t, TimeUnit tu) { ioFuture = f; timeout = t; timeoutUnit = tu; } public void run() { try { if (timeoutUnit == null) { status = ioFuture.awaitInterruptibly(); } else { status = ioFuture.awaitInterruptibly(timeout, timeoutUnit); } } catch (InterruptedException e) { exception = e; } } public InterruptedException getException() { return exception; } public Status getAwaitStatus() { return status; } } private static class ResultRetriever implements Runnable { private final IoFuture<String> ioFuture; private final boolean interruptibly; private String result; private CancellationException cancellationException; private IOException exception; private InterruptedException interruptedException; public ResultRetriever(IoFuture<String> f) { this(f, false); } public ResultRetriever(IoFuture<String> f, boolean i) { ioFuture = f; interruptibly = i; } public void run() { try { if (interruptibly) { result = ioFuture.getInterruptibly(); } else { result = ioFuture.get(); } } catch (CancellationException e) { cancellationException = e; } catch (IOException e) { exception = e; } catch (InterruptedException e) { interruptedException = e; } } public String getResult() { return result; } public CancellationException getCancellationException() { return cancellationException; } public InterruptedException getInterruptedException() { return interruptedException; } public IOException getIOException() { return exception; } } private static class TestNotifier implements IoFuture.Notifier<String, Object> { private boolean throwException; private boolean invoked; private IoFuture<? extends String> future; private Object attachment; public void throwExceptionOnNotify() { throwException = true; } @Override public void notify(IoFuture<? extends String> f, Object a) { invoked = true; future = f; attachment = a; if (throwException) { throw new RuntimeException("Test exception"); } } public boolean isInvoked() { return invoked; } public IoFuture<? extends String> getFuture() { return future; } public Object getAttachment() { return attachment; } } private static class TestCancelHandler implements Cancellable { private boolean invoked; @Override public Cancellable cancel() { invoked = true; return this; } public boolean isInvoked() { return invoked; } public void clear() { invoked = false; } } }