package com.limegroup.gnutella.io;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.Random;
import junit.framework.Test;
import com.limegroup.gnutella.util.BaseTestCase;
import com.limegroup.gnutella.util.ManagedThread;
import com.limegroup.gnutella.util.PrivilegedAccessor;
/**
* Tests that NIOSocket delegates events correctly.
*/
public final class NIOSocketTest extends BaseTestCase {
private static final int PORT = 9999;
public NIOSocketTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(NIOSocketTest.class);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
public void testDelayedGetInputStream() throws Exception {
ServerSocket server = new ServerSocket(PORT, 0);
try {
server.setReuseAddress(true);
InetSocketAddress addr = new InetSocketAddress("127.0.0.1", PORT);
NIOSocket socket = new NIOSocket();
socket.setSoTimeout(1000);
socket.connect(addr);
server.setSoTimeout(1000);
Socket accepted = server.accept();
byte[] rnd = new byte[100];
new Random().nextBytes(rnd);
accepted.getOutputStream().write(rnd); // this'll go immediately into the buffer
ICROAdapter icro = new ICROAdapter();
ByteBuffer read = icro.getReadBuffer();
assertEquals(0, read.position());
socket.setReadObserver(icro);
Thread.sleep(500); // let NIODispatcher to its thang.
assertEquals(100, read.position()); // data was transferred to the reader.
for(int i = 0; i < 100; i++)
assertEquals(rnd[i], read.get(i));
InputStream stream = socket.getInputStream();
byte[] readData = new byte[100];
assertEquals(100, stream.read(readData));
assertEquals(rnd, readData);
assertEquals(0, read.position()); // moved to the InputStream
new Random().nextBytes(rnd);
accepted.getOutputStream().write(rnd); // write some more, make sure it goes to stream
Thread.sleep(500);
assertEquals(0, read.position());
assertEquals(100, stream.read(readData));
assertEquals(rnd, readData);
socket.close();
} finally {
server.close();
}
}
// tests to make sure that calling setReadObserver
// will gobble any data that wasn't gobbled in blocking mode.
public void testSetReadObserver() throws Exception {
byte[] data = new byte[127];
for(byte i = 0; i < data.length; i++)
data[i] = i;
Listener listener = new Listener(PORT);
NIOSocket socket = new NIOSocket("127.0.0.1", PORT);
Stream stream = listener.getStream();
stream.write(data);
InputStream in = socket.getInputStream();
byte[] readIn = new byte[57];
in.read(readIn);
for(int i = 0; i < readIn.length; i++)
assertEquals(i, readIn[i]);
Thread.sleep(1000);
// Make sure that the NIOInputStream did gobble up the data.
// This is checking the internal implementation in NIOSocket, so
// should the implementation ever change, the test may need to be updated.
// (This portion is only checking that the blocking mode did read it up.)
NIOInputStream nioInput = (NIOInputStream)PrivilegedAccessor.getValue(socket, "reader");
ByteBuffer buffered = (ByteBuffer)PrivilegedAccessor.getValue(nioInput, "buffer");
assertEquals(buffered.toString(), data.length - readIn.length, buffered.position());
// End internal implementation test.
ReadTester reader = new ReadTester();
socket.setReadObserver(reader);
Thread.sleep(1000); // let the NIO thread pump since setReadObserver is invokedLater
ByteBuffer remaining = reader.getRead();
assertEquals(remaining.toString(), data.length - readIn.length, remaining.remaining());
for(int i = readIn.length; i < data.length; i++)
assertEquals(i, remaining.get());
assertInstanceof(SocketInterestReadAdapter.class, reader.getReadChannel());
assertSame(socket.getChannel(), ((SocketInterestReadAdapter)reader.getReadChannel()).getChannel());
}
public void testSetReadObserverGoesThroughChains() throws Exception {
NIOSocket socket = new NIOSocket();
Object channel = socket.getChannel();
RCROAdapter entry = new RCROAdapter();
socket.setReadObserver(entry);
Thread.sleep(1000);
assertInstanceof(SocketInterestReadAdapter.class, entry.getReadChannel());
assertSame(channel, ((SocketInterestReadAdapter)entry.getReadChannel()).getChannel());
RCRAdapter chain1 = new RCRAdapter();
entry.setReadChannel(chain1);
socket.setReadObserver(entry);
Thread.sleep(1000);
assertInstanceof(SocketInterestReadAdapter.class, chain1.getReadChannel());
assertSame(channel, ((SocketInterestReadAdapter)chain1.getReadChannel()).getChannel());
assertSame(chain1, entry.getReadChannel());
RCRAdapter chain2 = new RCRAdapter();
chain1.setReadChannel(chain2);
socket.setReadObserver(entry);
Thread.sleep(1000);
assertInstanceof(SocketInterestReadAdapter.class, chain2.getReadChannel());
assertSame(channel, ((SocketInterestReadAdapter)chain2.getReadChannel()).getChannel());
assertSame(chain2, chain1.getReadChannel());
assertSame(chain1, entry.getReadChannel());
}
public void testBlockingConnect() throws Exception {
NIOSocket socket = new NIOSocket();
socket.connect(new InetSocketAddress("www.google.com", 80));
assertTrue(socket.isConnected());
socket.close();
Thread.sleep(500);
assertFalse(socket.isConnected());
}
public void testBlockingConnectFailing() throws Exception {
NIOSocket socket = new NIOSocket();
// Measure time for testNonBlockingConnectFailing()
//long start = System.currentTimeMillis();
try {
socket.connect(new InetSocketAddress("www.google.com", 9999));
fail("shouldn't have connected");
} catch(ConnectException iox) {
assertFalse(socket.isConnected());
}
//long end = System.currentTimeMillis();
//System.out.println("Time: " + (end-start));
}
public void testBlockingConnectTimesOut() throws Exception {
NIOSocket socket = new NIOSocket();
try {
socket.connect(new InetSocketAddress("www.google.com", 9999), 1000);
fail("shouldn't have connected");
} catch(SocketTimeoutException iox) {
assertEquals("operation timed out (1000)", iox.getMessage());
}
}
public void testNonBlockingConnect() throws Exception {
NIOSocket socket = new NIOSocket();
StubConnectObserver observer = new StubConnectObserver();
socket.connect(new InetSocketAddress("www.google.com", 80), 5000, observer);
observer.waitForResponse(5500);
assertTrue(socket.isConnected());
assertSame(socket, observer.getSocket());
assertFalse(observer.isShutdown());
assertNull(observer.getIoException());
socket.close();
Thread.sleep(500);
assertFalse(observer.isShutdown()); // doesn't get both connect & shutdown
assertFalse(socket.isConnected());
}
public void testNonBlockingConnectFailing() throws Exception {
NIOSocket socket = new NIOSocket();
StubConnectObserver observer = new StubConnectObserver();
// IMPORTANT: The waitForResponse() timeout must be
// set to about the same time that testBlockingConnectFailing()
// needs to pass the test which depends on the operatin system
// and the remote host.
socket.connect(new InetSocketAddress("www.google.com", 9999), 90000, observer);
observer.waitForResponse(80000);
assertTrue(observer.isShutdown());
assertNull(observer.getSocket());
assertNull(observer.getIoException()); // NIOSocket swallows the IOX.
assertFalse(socket.isConnected());
}
public void testNonBlockingConnectTimesOut() throws Exception {
NIOSocket socket = new NIOSocket();
StubConnectObserver observer = new StubConnectObserver();
socket.connect(new InetSocketAddress("www.google.com", 9999), 1000, observer);
observer.waitForResponse(1500);
assertTrue(observer.isShutdown());
assertNull(observer.getSocket());
assertNull(observer.getIoException()); // NIOSocket swallows the IOX.
assertFalse(socket.isConnected());
}
public void testSoTimeoutUsedForNonBlockingRead() throws Exception {
ServerSocket server = new ServerSocket(PORT, 0);
try {
server.setReuseAddress(true);
InetSocketAddress addr = new InetSocketAddress("127.0.0.1", PORT);
NIOSocket socket = new NIOSocket();
socket.setSoTimeout(1000);
socket.connect(addr);
server.setSoTimeout(1000);
Socket accepted = server.accept();
accepted.getOutputStream().write(new byte[100]); // this'll go immediately into the buffer
socket.getInputStream().read(new byte[100]);
Thread.sleep(2000);
assertTrue(!socket.isClosed()); // didn't close 'cause we're using stream reading
accepted.getOutputStream().write(new byte[1]); // give it some data just to make sure it has
socket.setReadObserver(new ReadTester());
Thread.sleep(2000);
assertTrue(socket.isClosed()); // closed because we switched to NB reading w/ timeout set
} finally {
server.close();
}
}
private static class Listener {
private ServerSocket server;
private Socket accepted;
Listener(int port) throws Exception {
server = new ServerSocket();
server.setReuseAddress(true);
server.bind(new InetSocketAddress(port));
server.setSoTimeout(30 * 1000);
new ManagedThread(new Runnable() {
public void run() {
try {
accepted = server.accept();
} catch(IOException iox) {
fail(iox);
}
try {
server.close();
} catch(IOException ignored) {}
}
}).start();
}
Stream getStream() {
return new Stream(accepted);
}
}
private static class Stream {
private Socket socket;
Stream(Socket socket) {
this.socket = socket;
}
void write(final byte[] data) {
new ManagedThread(new Runnable() {
public void run() {
try {
socket.getOutputStream().write(data);
socket.getOutputStream().flush();
} catch(IOException iox) {
fail(iox);
}
}
}).start();
}
}
private static class WriteTester implements ChannelWriter {
private ByteBuffer buffer;
private InterestWriteChannel channel;
public WriteTester(ByteBuffer buffer, InterestWriteChannel channel) {
this.buffer = buffer;
this.channel = channel;
if(channel != null)
channel.interest(this, true);
}
public WriteTester(byte[] data, InterestWriteChannel channel) {
this(ByteBuffer.wrap(data), channel);
}
public WriteTester(byte[] data, int off, int len, InterestWriteChannel channel) {
this(ByteBuffer.wrap(data, off, len), channel);
}
public WriteTester() {
this(ByteBuffer.allocate(0), null);
}
public synchronized InterestWriteChannel getWriteChannel() { return channel; }
public synchronized void setWriteChannel(InterestWriteChannel channel) {
this.channel = channel;
channel.interest(this, true);
}
public boolean handleWrite() throws IOException {
while(buffer.hasRemaining() && channel.write(buffer) > 0);
return buffer.hasRemaining();
}
public void shutdown() {}
public void handleIOException(IOException iox) {
throw (RuntimeException)new UnsupportedOperationException("not implemented").initCause(iox);
}
}
private static class ReadTester implements ChannelReadObserver {
private InterestReadChannel source;
private ByteBuffer readData = ByteBuffer.allocate(128 * 1024);
// ChannelReader methods.
public InterestReadChannel getReadChannel() { return source; }
public void setReadChannel(InterestReadChannel channel) { source = channel; }
// IOErrorObserver methods.
public void handleIOException(IOException x) { fail(x); }
// ReadObserver methods.
public void handleRead() throws IOException {
source.read(readData);
assertEquals(0, source.read(readData)); // must have finish on first read.
}
// Shutdownable methods.
public void shutdown() {}
public ByteBuffer getRead() { return (ByteBuffer)readData.flip(); }
}
private static class ICROAdapter implements ChannelReadObserver, InterestReadChannel {
private InterestReadChannel source;
private ByteBuffer buffer = ByteBuffer.allocate(1024);
public ByteBuffer getReadBuffer() {
return buffer;
}
public InterestReadChannel getReadChannel() {
return source;
}
public void setReadChannel(InterestReadChannel channel) {
source = channel;
}
public int read(ByteBuffer b) {
return BufferUtils.transfer(buffer, b);
}
public void close() throws IOException {
source.close();
}
public boolean isOpen() {
return source.isOpen();
}
public void interest(boolean status) {
source.interest(status);
}
public void handleRead() throws IOException {
while (buffer.hasRemaining() && source.read(buffer) != 0);
}
public void handleIOException(IOException iox) {
}
public void shutdown() {
}
}
private static class RCRAdapter implements ChannelReader, InterestReadChannel {
protected InterestReadChannel source;
public InterestReadChannel getReadChannel() { return source; }
public void setReadChannel(InterestReadChannel channel) { source = channel; }
public int read(ByteBuffer b) throws IOException { return source.read(b); }
public void close() throws IOException { source.close(); }
public boolean isOpen() { return source.isOpen(); }
public void interest(boolean status) { source.interest(status); }
}
private static class RCROAdapter extends RCRAdapter implements ChannelReadObserver {
public void handleRead() throws IOException { source.read(ByteBuffer.allocate(1)); }
public void handleIOException(IOException iox) {}
public void shutdown() {}
}
}