package org.cojen.dirmi; import org.junit.*; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.TimeUnit; import static org.junit.Assert.*; /** * Tests for the SessionAcceptor class. * * Created by kstemen on 11/21/16. */ public class TestSessionAcceptor { private static Environment env; private SessionListener listener; private SessionAcceptor acceptor; private RemoteFaceServer remoteServer; @BeforeClass public static void createEnv() { env = new Environment(1000, null, new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { synchronized (System.out) { System.out.println("Exception in thread \"" + t.getName() + "\" " + e); if (e instanceof java.io.InvalidClassException) { // Reduce output when testing broken serializable objects. return; } e.printStackTrace(System.out); } } }); } @AfterClass public static void closeEnv() throws Exception { if (env != null) { env.close(); env = null; } } @Before public void setUp() throws Exception { remoteServer = new RemoteFaceServer(); acceptor = env.newSessionAcceptor(new InetSocketAddress("127.0.0.1", 0)); listener = new SessionListener() { @Override public void established(Session session) throws IOException { acceptor.accept(this); try { session.send(remoteServer); } catch (IOException e) { try { session.close(); } catch (IOException e2) { // Ignore. } } } @Override public void establishFailed(IOException cause) throws IOException { acceptor.accept(this); } @Override public void acceptFailed(IOException cause) { } }; acceptor.accept(listener); } @After public void tearDown() throws Exception { if (acceptor != null) { acceptor.close(); acceptor = null; } } private void ping(long connectTimeoutMillis) throws IOException { Session localSession = env.newSessionConnector ((InetSocketAddress) acceptor.getLocalAddress()).connect( connectTimeoutMillis, TimeUnit.MILLISECONDS); RemoteFace localServer = (RemoteFace) localSession.receive(); String reply = localServer.echoObject("test"); assertEquals("test", reply); localSession.close(); } private void connectAndClose(long connectTimeoutMillis) throws IOException { Session localSession = env.newSessionConnector ((InetSocketAddress) acceptor.getLocalAddress()).connect( connectTimeoutMillis, TimeUnit.MILLISECONDS); RemoteFace localServer = (RemoteFace) localSession.receive(); localSession.close(); } @Test /** * Verify when a client times out while trying to connect to the acceptor, that doesn't * break the acceptor. Specifically the acceptor must be able to accept new connections * afterwards. */ public void connectTimeout() throws IOException { // A large connect timeout. The timeout is high enough that the call should easily // finish without timing out. For this test to cover the right paths, this timeout // must be lower than BasicChannelBroker.PING_DELAY_MILLIS. final int SUCCESS_TIMEOUT_MS = 2_400; // Call ping() once without timing it, to warm up the caches ping(SUCCESS_TIMEOUT_MS); // Now get an estimate of how long each call takes long start = System.currentTimeMillis(); for(int i = 0; i < 3; i++) { connectAndClose(SUCCESS_TIMEOUT_MS); } long end = System.currentTimeMillis(); long avgTime = (end - start) / 3; // Call ping again, but use a connect timeout. p is a percentage of avgTime. int timeouts = 0; for (int p = 0; p < 100; p += 10) { try { ping(avgTime * p / 100); } catch (RemoteTimeoutException e) { timeouts++; // This test purposely uses a timeout that is too low, so this exception is // expected. Now that the client has timed out, verify the server is still // healthy, and can process a new session without waiting for the old session // to fail a ping. ping(SUCCESS_TIMEOUT_MS); } } assertNotSame("None of the calls timed out", 0, timeouts); } }