/*
* 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 com.ok2c.lightnio.impl;
import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.Assert;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.ok2c.lightnio.IOEventDispatch;
import com.ok2c.lightnio.IOReactorException;
import com.ok2c.lightnio.IOReactorExceptionHandler;
import com.ok2c.lightnio.IOReactorStatus;
import com.ok2c.lightnio.IOSession;
import com.ok2c.lightnio.ListenerEndpoint;
import com.ok2c.lightnio.ListeningIOReactor;
import com.ok2c.lightnio.SessionInputBuffer;
import com.ok2c.lightnio.SessionRequest;
import com.ok2c.lightnio.testprotocol.NoOpSimpleProtocolHandler;
import com.ok2c.lightnio.testprotocol.OoopsieRuntimeException;
import com.ok2c.lightnio.testprotocol.SimpleClient;
import com.ok2c.lightnio.testprotocol.SimpleClientProtocolHandler;
import com.ok2c.lightnio.testprotocol.SimpleIOEventDispatch;
import com.ok2c.lightnio.testprotocol.SimpleTestJob;
import com.ok2c.lightnio.testprotocol.SimpleServer;
import com.ok2c.lightnio.testprotocol.SimpleServerProtocolHandler;
import com.ok2c.lightnio.testprotocol.SimpleTestState;
import com.ok2c.lightnio.testprotocol.SimpleTestStatus;
/**
* Unit tests for {@link DefaultConnectingIOReactor} and {@link DefaultListeningIOReactor}.
*/
public class TestIOReactors {
private SimpleClient testclient;
private SimpleServer testserver;
@Before
public void setUp() throws Exception {
IOReactorConfig config = new IOReactorConfig();
config.setWorkerCount(2);
this.testclient = new SimpleClient(config);
this.testserver = new SimpleServer(config);
}
@After
public void tearDown() throws Exception {
try {
this.testclient.shutdown(1000);
} catch (IOException ex) {
ex.printStackTrace();
}
List<ExceptionEvent> clogs = this.testclient.getAuditLog();
if (clogs != null) {
for (ExceptionEvent clog: clogs) {
Throwable cause = clog.getCause();
if (!(cause instanceof OoopsieRuntimeException)) {
cause.printStackTrace();
}
}
}
try {
this.testserver.shutdown(1000);
} catch (IOException ex) {
ex.printStackTrace();
}
List<ExceptionEvent> slogs = this.testserver.getAuditLog();
if (slogs != null) {
for (ExceptionEvent slog: slogs) {
Throwable cause = slog.getCause();
if (!(cause instanceof OoopsieRuntimeException)) {
cause.printStackTrace();
}
}
}
}
@Test
public void testBasicIO() throws Exception {
this.testserver.start(new SimpleServerProtocolHandler());
this.testclient.start(new SimpleClientProtocolHandler());
ListenerEndpoint listenerEndpoint = this.testserver.getListenerEndpoint();
listenerEndpoint.waitFor();
InetSocketAddress address = (InetSocketAddress) listenerEndpoint.getAddress();
InetSocketAddress target = new InetSocketAddress("localhost", address.getPort());
SimpleTestJob[] testjobs = new SimpleTestJob[50];
for (int i = 0; i < testjobs.length; i++) {
testjobs[i] = new SimpleTestJob(1000);
}
for (int i = 0; i < testjobs.length; i++) {
SimpleTestJob testjob = testjobs[i];
SessionRequest sessionRequest = this.testclient.openConnection(target, testjob);
sessionRequest.waitFor();
if (sessionRequest.getException() != null) {
throw sessionRequest.getException();
}
Assert.assertNotNull(sessionRequest.getSession());
}
for (int i = 0; i < testjobs.length; i++) {
SimpleTestJob testjob = testjobs[i];
testjob.waitFor();
Exception ex = testjob.getException();
if (ex != null) {
throw ex;
}
SimpleTestState state = testjob.getTestState();
Assert.assertNotNull(state);
Assert.assertEquals(SimpleTestStatus.RESPONSE_RECEIVED, state.getStatus());
String pattern = testjob.getPattern();
int count = testjob.getCount();
SessionInputBuffer inbuffer = state.getInBuffer();
for (int n = 0; n < count; n++) {
String line = inbuffer.readLine(true);
Assert.assertEquals(pattern, line);
}
Assert.assertFalse(inbuffer.hasData());
}
}
@Test
public void testGracefulShutdown() throws Exception {
// Open connections and do nothing
final int connNo = 10;
final AtomicInteger openServerConns = new AtomicInteger(0);
final AtomicInteger closedServerConns = new AtomicInteger(0);
final AtomicInteger openClientConns = new AtomicInteger(0);
final AtomicInteger closedClientConns = new AtomicInteger(0);
this.testserver.start(new NoOpSimpleProtocolHandler() {
@Override
public void connected(IOSession session, SimpleTestState state) throws IOException {
openServerConns.incrementAndGet();
}
public void disconnected(IOSession session, SimpleTestState state) throws IOException {
closedServerConns.incrementAndGet();
}
});
this.testclient.start(new NoOpSimpleProtocolHandler() {
@Override
public void connected(IOSession session, SimpleTestState state) throws IOException {
openClientConns.incrementAndGet();
session.setEventMask(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
@Override
public void outputReady(IOSession session, SimpleTestState state) throws IOException {
byte[] tmp = new byte[] {'1', '2', '3', '4', '5'};
ByteBuffer src = ByteBuffer.wrap(tmp);
session.channel().write(src);
}
@Override
public void disconnected(IOSession session, SimpleTestState state) throws IOException {
closedClientConns.incrementAndGet();
}
});
ListenerEndpoint listenerEndpoint = this.testserver.getListenerEndpoint();
listenerEndpoint.waitFor();
InetSocketAddress address = (InetSocketAddress) listenerEndpoint.getAddress();
InetSocketAddress target = new InetSocketAddress("localhost", address.getPort());
for (int i = 0; i < connNo; i++) {
SessionRequest sessionRequest = this.testclient.openConnection(target, null);
sessionRequest.waitFor();
if (sessionRequest.getException() != null) {
throw sessionRequest.getException();
}
Assert.assertNotNull(sessionRequest.getSession());
}
// Make sure all connections go down
this.testclient.shutdown(1000);
this.testserver.shutdown(1000);
Assert.assertEquals(openServerConns.get(), closedServerConns.get());
Assert.assertEquals(openClientConns.get(), closedClientConns.get());
}
@Test
public void testRuntimeException() throws Exception {
this.testserver.start(new NoOpSimpleProtocolHandler() {
public void connected(IOSession session, SimpleTestState state) throws IOException {
session.setEventMask(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
public void outputReady(IOSession session, SimpleTestState state) throws IOException {
session.setEventMask(SelectionKey.OP_READ);
throw new OoopsieRuntimeException();
}
});
this.testclient.start(new NoOpSimpleProtocolHandler());
ListenerEndpoint listenerEndpoint = this.testserver.getListenerEndpoint();
listenerEndpoint.waitFor();
InetSocketAddress address = (InetSocketAddress) listenerEndpoint.getAddress();
InetSocketAddress target = new InetSocketAddress("localhost", address.getPort());
SessionRequest sessionRequest = this.testclient.openConnection(target, null);
sessionRequest.waitFor();
if (sessionRequest.getException() != null) {
throw sessionRequest.getException();
}
Assert.assertNotNull(sessionRequest.getSession());
this.testserver.join(20000);
Exception ex = this.testserver.getException();
Assert.assertNotNull(ex);
Assert.assertTrue(ex instanceof IOReactorException);
Assert.assertNotNull(ex.getCause());
Assert.assertTrue(ex.getCause() instanceof OoopsieRuntimeException);
List<ExceptionEvent> auditlog = this.testserver.getAuditLog();
Assert.assertNotNull(auditlog);
Assert.assertEquals(1, auditlog.size());
// I/O reactor shut down itself
Assert.assertEquals(IOReactorStatus.SHUT_DOWN, this.testserver.getStatus());
}
@Test
public void testUnhandledRuntimeException() throws Exception {
final AtomicInteger requestConns = new AtomicInteger(0);
this.testserver.setExceptionHandler(new IOReactorExceptionHandler() {
public boolean handle(final IOException ex) {
return false;
}
public boolean handle(final RuntimeException ex) {
requestConns.incrementAndGet();
return false;
}
});
this.testserver.start(new NoOpSimpleProtocolHandler() {
public void connected(IOSession session, SimpleTestState state) throws IOException {
session.setEventMask(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
public void outputReady(IOSession session, SimpleTestState state) throws IOException {
session.setEventMask(SelectionKey.OP_READ);
throw new OoopsieRuntimeException();
}
});
this.testclient.start(new NoOpSimpleProtocolHandler());
ListenerEndpoint listenerEndpoint = this.testserver.getListenerEndpoint();
listenerEndpoint.waitFor();
InetSocketAddress address = (InetSocketAddress) listenerEndpoint.getAddress();
InetSocketAddress target = new InetSocketAddress("localhost", address.getPort());
SessionRequest sessionRequest = this.testclient.openConnection(target, null);
sessionRequest.waitFor();
if (sessionRequest.getException() != null) {
throw sessionRequest.getException();
}
Assert.assertNotNull(sessionRequest.getSession());
this.testserver.join(20000);
Assert.assertEquals(1, requestConns.get());
Exception ex = this.testserver.getException();
Assert.assertNotNull(ex);
Assert.assertTrue(ex instanceof IOReactorException);
Assert.assertNotNull(ex.getCause());
Assert.assertTrue(ex.getCause() instanceof OoopsieRuntimeException);
List<ExceptionEvent> auditlog = this.testserver.getAuditLog();
Assert.assertNotNull(auditlog);
Assert.assertEquals(1, auditlog.size());
// I/O reactor shut down itself
Assert.assertEquals(IOReactorStatus.SHUT_DOWN, this.testserver.getStatus());
}
@Test
public void testHandledRuntimeException() throws Exception {
final CountDownLatch requestConns = new CountDownLatch(1);
this.testserver.setExceptionHandler(new IOReactorExceptionHandler() {
public boolean handle(final IOException ex) {
return false;
}
public boolean handle(final RuntimeException ex) {
requestConns.countDown();
return true;
}
});
this.testserver.start(new NoOpSimpleProtocolHandler() {
public void connected(IOSession session, SimpleTestState state) throws IOException {
session.setEventMask(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
public void outputReady(IOSession session, SimpleTestState state) throws IOException {
session.setEventMask(SelectionKey.OP_READ);
throw new OoopsieRuntimeException();
}
});
this.testclient.start(new NoOpSimpleProtocolHandler());
ListenerEndpoint listenerEndpoint = this.testserver.getListenerEndpoint();
listenerEndpoint.waitFor();
InetSocketAddress address = (InetSocketAddress) listenerEndpoint.getAddress();
InetSocketAddress target = new InetSocketAddress("localhost", address.getPort());
SessionRequest sessionRequest = this.testclient.openConnection(target, null);
sessionRequest.waitFor();
if (sessionRequest.getException() != null) {
throw sessionRequest.getException();
}
Assert.assertNotNull(sessionRequest.getSession());
requestConns.await();
Assert.assertEquals(IOReactorStatus.ACTIVE, this.testserver.getStatus());
Assert.assertNull(this.testserver.getException());
}
@Test
public void testEndpointUpAndDown() throws Exception {
final IOEventDispatch eventDispatch = new SimpleIOEventDispatch(
"server",
new NoOpSimpleProtocolHandler());
final ListeningIOReactor ioreactor = new DefaultListeningIOReactor(
new IOReactorConfig());
Thread t = new Thread(new Runnable() {
public void run() {
try {
ioreactor.execute(eventDispatch);
} catch (IOException ex) {
}
}
});
t.start();
Set<ListenerEndpoint> endpoints = ioreactor.getEndpoints();
Assert.assertNotNull(endpoints);
Assert.assertEquals(0, endpoints.size());
ListenerEndpoint endpoint9998 = ioreactor.listen(new InetSocketAddress(9998));
endpoint9998.waitFor();
ListenerEndpoint endpoint9999 = ioreactor.listen(new InetSocketAddress(9999));
endpoint9999.waitFor();
endpoints = ioreactor.getEndpoints();
Assert.assertNotNull(endpoints);
Assert.assertEquals(2, endpoints.size());
endpoint9998.close();
endpoints = ioreactor.getEndpoints();
Assert.assertNotNull(endpoints);
Assert.assertEquals(1, endpoints.size());
ListenerEndpoint endpoint = endpoints.iterator().next();
Assert.assertEquals(9999, ((InetSocketAddress) endpoint.getAddress()).getPort());
ioreactor.shutdown(1000);
t.join(1000);
Assert.assertEquals(IOReactorStatus.SHUT_DOWN, ioreactor.getStatus());
}
@Test
public void testEndpointAlreadyBoundFatal() throws Exception {
final IOEventDispatch eventDispatch = new SimpleIOEventDispatch(
"server",
new NoOpSimpleProtocolHandler());
final ListeningIOReactor ioreactor = new DefaultListeningIOReactor(
new IOReactorConfig());
final CountDownLatch latch = new CountDownLatch(1);
Thread t = new Thread(new Runnable() {
public void run() {
try {
ioreactor.execute(eventDispatch);
Assert.fail("IOException should have been thrown");
} catch (IOException ex) {
latch.countDown();
}
}
});
t.start();
ListenerEndpoint endpoint1 = ioreactor.listen(new InetSocketAddress(9999));
endpoint1.waitFor();
ListenerEndpoint endpoint2 = ioreactor.listen(new InetSocketAddress(9999));
endpoint2.waitFor();
Assert.assertNotNull(endpoint2.getException());
// I/O reactor is now expected to be shutting down
latch.await(2000, TimeUnit.MILLISECONDS);
Assert.assertTrue(ioreactor.getStatus().compareTo(IOReactorStatus.SHUTTING_DOWN) >= 0);
Set<ListenerEndpoint> endpoints = ioreactor.getEndpoints();
Assert.assertNotNull(endpoints);
Assert.assertEquals(0, endpoints.size());
ioreactor.shutdown(1000);
t.join(1000);
Assert.assertTrue(ioreactor.getStatus().compareTo(IOReactorStatus.SHUTTING_DOWN) >= 0);
}
@Test
public void testEndpointAlreadyBoundNonFatal() throws Exception {
final IOEventDispatch eventDispatch = new SimpleIOEventDispatch(
"server",
new NoOpSimpleProtocolHandler());
final DefaultListeningIOReactor ioreactor = new DefaultListeningIOReactor(
new IOReactorConfig());
ioreactor.setExceptionHandler(new IOReactorExceptionHandler() {
public boolean handle(final IOException ex) {
return (ex instanceof BindException);
}
public boolean handle(final RuntimeException ex) {
return false;
}
});
Thread t = new Thread(new Runnable() {
public void run() {
try {
ioreactor.execute(eventDispatch);
} catch (IOException ex) {
}
}
});
t.start();
ListenerEndpoint endpoint1 = ioreactor.listen(new InetSocketAddress(9999));
endpoint1.waitFor();
ListenerEndpoint endpoint2 = ioreactor.listen(new InetSocketAddress(9999));
endpoint2.waitFor();
Assert.assertNotNull(endpoint2.getException());
// Sleep a little to make sure the I/O reactor is not shutting down
Thread.sleep(500);
Assert.assertEquals(IOReactorStatus.ACTIVE, ioreactor.getStatus());
ioreactor.shutdown(1000);
t.join(1000);
Assert.assertEquals(IOReactorStatus.SHUT_DOWN, ioreactor.getStatus());
}
}