/*
* JBoss, Home of Professional Open Source.
* Copyright 2013 Red Hat, Inc., 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.nio.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.xnio.Buffers;
import org.xnio.ChannelPipe;
import org.xnio.IoUtils;
import org.xnio.OptionMap;
import org.xnio.Xnio;
import org.xnio.XnioWorker;
import org.xnio.channels.StreamSinkChannel;
import org.xnio.channels.StreamSourceChannel;
/**
* Tests a pair of connected source/sink channels.
*
* @author <a href="mailto:frainone@redhat.com">Flavia Rainone</a>
*
* @param <S> the sink channel type
* @param <T> the source channel type
*/
public abstract class AbstractStreamSinkSourceChannelTest<S extends StreamSinkChannel, T extends StreamSourceChannel> {
protected static XnioWorker worker;
protected static Xnio xnio;
/** Sink channel targeted by this test. It must be connected to {@code sourceChanel}. */
protected S sinkChannel = null;
/** Source channel targeted by this test. It must be connected to {@code sinkChanel}. */
protected T sourceChannel = null;
/**
* Creates the pair of sink/source channels that should be tested.
*
* @param xnioWorker the worker
* @param optionMap contains channels options
* @param sinkChannelListener handles sink channel creation
* @param sourceChannelListener handles source channel creation
*/
protected abstract void initChannels(XnioWorker xnioWorker, OptionMap optionMap, TestChannelListener<S> sinkChannelListener, TestChannelListener<T> sourceChannelListener) throws IOException;
@BeforeClass
public static void createWorker() throws IOException {
xnio = Xnio.getInstance("nio", AbstractStreamSinkSourceChannelTest.class.getClassLoader());
worker = xnio.createWorker(OptionMap.EMPTY);
}
@AfterClass
public static void shutdownWorker() throws IOException {
worker.shutdownNow();
}
/**
* Creates the channels and sets their values to the fields {@link sinkChannel} and {@link sourceChannel}.
* <p>This method must be invoked by the subclasses in order to enable automatic channel closing after the test.
*
* @param worker the worker
* @param optionMap contains channels options
*/
protected void initChannels(XnioWorker worker, OptionMap optionMap) throws IOException {
if (sinkChannel != null) {
closeChannels();
}
final TestChannelListener<S> sinkChannelListener = new TestChannelListener<S>();
final TestChannelListener<T> sourceChannelListener = new TestChannelListener<T>();
initChannels(worker, optionMap, sinkChannelListener, sourceChannelListener);
assertTrue("Subclass must invoke the channel listeners to setup sink and source channels", sinkChannelListener.isInvokedYet());
assertTrue("Subclass must invoke the channel listeners to setup sink and source channels", sourceChannelListener.isInvokedYet());
sinkChannel = sinkChannelListener.getChannel();
assertNotNull(sinkChannel);
assertTrue(sinkChannel.isOpen());
sourceChannel = sourceChannelListener.getChannel();
assertNotNull(sourceChannel);
assertTrue(sourceChannel.isOpen());
}
/**
* Creates the channels and sets their values to the fields {@link sinkChannel} and {@link sourceChannel}.
* <p>This method must be invoked by the subclasses in order to enable automatic channel closing after the test.
*/
protected void initChannels() throws IOException {
initChannels(worker, OptionMap.EMPTY);
}
@After
public void closeChannels() throws IOException {
if (sinkChannel != null) {
sinkChannel.close();
assertFalse(sinkChannel.isOpen());
sourceChannel.close();
assertFalse(sourceChannel.isOpen());
}
}
@Test
public void writeToSinkAndReadFromSource() throws IOException{
initChannels();
final ByteBuffer writeBuffer = ByteBuffer.allocate(35);
final ByteBuffer readBuffer = ByteBuffer.allocate(35);
writeBuffer.put("write to sink and read from source".getBytes()).flip();
assertEquals(0, sourceChannel.read(readBuffer));
assertEquals(34, sinkChannel.write(writeBuffer));
assertEquals(34, sourceChannel.read(readBuffer));
assertEquals(0, sinkChannel.write(writeBuffer));
assertEquals(0, sourceChannel.read(readBuffer));
readBuffer.flip();
assertEquals("write to sink and read from source", Buffers.getModifiedUtf8(readBuffer));
sinkChannel.close();
assertEquals(0, sourceChannel.read(readBuffer));
writeBuffer.flip();
ClosedChannelException expected = null;
try {
sinkChannel.write(writeBuffer);
} catch (ClosedChannelException e) {
expected = e;
}
assertNotNull(expected);
}
@Test
public void writeToSinkAndReadFromSourceWithMultipleBuffers() throws IOException{
initChannels();
final ByteBuffer[] writeBuffers = new ByteBuffer[] {ByteBuffer.allocate(4), ByteBuffer.allocate(9),
ByteBuffer.allocate(13), Buffers.EMPTY_BYTE_BUFFER};
final ByteBuffer[] readBuffers = new ByteBuffer[] {ByteBuffer.allocate(1), ByteBuffer.allocate(5),
ByteBuffer.allocate(10), ByteBuffer.allocate(15), ByteBuffer.allocate(20)};
writeBuffers[0].put(">= 2".getBytes()).flip();
writeBuffers[1].put("several".getBytes()).flip();
writeBuffers[2].put("+ than 1".getBytes()).flip();
assertEquals(0, sourceChannel.read(readBuffers));
assertEquals(0, sinkChannel.write(writeBuffers, 3, 1));
assertEquals(8, sinkChannel.write(writeBuffers, 2, 2));
assertEquals(0, sinkChannel.write(writeBuffers, 2, 2));
assertEquals(1, sourceChannel.read(readBuffers, 0, 1));
assertEquals('+', readBuffers[0].get(0));
assertEquals(7, sourceChannel.read(readBuffers));
assertEquals(0, sourceChannel.read(readBuffers, 1, 2));
assertEquals(11, sinkChannel.write(writeBuffers));
assertEquals(11, sourceChannel.read(readBuffers, 3, 2));
readBuffers[1].flip();
readBuffers[2].flip();
readBuffers[3].flip();
readBuffers[4].flip();
assertEquals(" than", Buffers.getModifiedUtf8(readBuffers[1]));
assertEquals(" 1", Buffers.getModifiedUtf8(readBuffers[2]));
assertEquals(">= 2several", Buffers.getModifiedUtf8(readBuffers[3]));
assertEquals(0, readBuffers[4].remaining());
sinkChannel.close();
assertEquals(0, sourceChannel.read(readBuffers));
writeBuffers[0].flip();
ClosedChannelException expected = null;
try {
sinkChannel.write(writeBuffers);
} catch (ClosedChannelException e) {
expected = e;
}
assertNotNull(expected);
}
@Test
public void transferTo() throws IOException {
initChannels();
final ByteBuffer buffer = ByteBuffer.allocate(20);
final ByteBuffer transferedMessage1 = ByteBuffer.allocate(6);
final ByteBuffer transferedMessage2 = ByteBuffer.allocate(16);
buffer.put("transfered message".getBytes()).flip();
final File file = File.createTempFile("test", ".txt");
file.deleteOnExit();
final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
final FileChannel fileChannel = randomAccessFile.getChannel();
try {
assertEquals (0, sourceChannel.transferTo(0, 6, fileChannel));
sinkChannel.write(buffer);
assertEquals (8, sourceChannel.transferTo(0, 8, fileChannel));
assertEquals (10, sourceChannel.transferTo(8, 11, fileChannel));
fileChannel.position(0);
assertEquals(6, fileChannel.read(transferedMessage1));
assertEquals(12, fileChannel.read(transferedMessage2));
transferedMessage1.flip();
transferedMessage2.flip();
assertEquals("transf", Buffers.getModifiedUtf8(transferedMessage1));
assertEquals("ered message", Buffers.getModifiedUtf8(transferedMessage2));
assertEquals (0, sourceChannel.transferTo(0, 6, fileChannel));
sourceChannel.close();
long transferred = 0;
ClosedChannelException expected = null;
try {
transferred = sourceChannel.transferTo(0, 6, fileChannel);
} catch (ClosedChannelException e) {
expected = e;
}
assertTrue(expected != null || transferred == 0);
} finally {
fileChannel.close();
randomAccessFile.close();
}
}
@Test
public void transferFrom() throws IOException {
initChannels();
final ByteBuffer buffer = ByteBuffer.allocate(20);
final ByteBuffer transferedMessage1 = ByteBuffer.allocate(6);
final ByteBuffer transferedMessage2 = ByteBuffer.allocate(16);
buffer.put("transferred message".getBytes()).flip();
final File file = File.createTempFile("test", ".txt");
file.deleteOnExit();
final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
final FileChannel fileChannel = randomAccessFile.getChannel();
try {
assertEquals (0, sinkChannel.transferFrom(fileChannel, 0, 6));
fileChannel.write(buffer);
assertEquals (8, sinkChannel.transferFrom(fileChannel, 0, 8));
assertEquals (11, sinkChannel.transferFrom(fileChannel, 8, 11));
assertEquals(6, sourceChannel.read(transferedMessage1));
assertEquals(13, sourceChannel.read(transferedMessage2));
transferedMessage1.flip();
transferedMessage2.flip();
assertEquals("transf", Buffers.getModifiedUtf8(transferedMessage1));
assertEquals("erred message", Buffers.getModifiedUtf8(transferedMessage2));
assertEquals (0, sinkChannel.transferFrom(fileChannel, 19, 6));
sinkChannel.close();
IOException expected = null;
try {
sinkChannel.transferFrom(fileChannel, 0, 6);
} catch (IOException e) {
expected = e;
}
assertNotNull(expected);
} finally {
fileChannel.close();
randomAccessFile.close();
}
}
@Test
public void transferThroughBuffer() throws IOException {
initChannels();
final ChannelPipe<StreamSourceChannel, StreamSinkChannel> channelPipe = worker.createHalfDuplexPipe();
final StreamSourceChannel leftChannel = channelPipe.getLeftSide();
final StreamSinkChannel rightChannel = channelPipe.getRightSide();
try {
final ByteBuffer readBuffer = ByteBuffer.allocate(50);
final ByteBuffer writeBuffer = ByteBuffer.allocate(50);
final ByteBuffer throughBuffer = ByteBuffer.allocate(10);
// Step 1: write to sink channel and transfer from source channel to left channel; read from right channel
writeBuffer.put("looooooooooooooooooooooooooong daaaaaaaaaaaata".getBytes()).flip();
assertEquals(46, sinkChannel.write(writeBuffer));
assertEquals(46, sourceChannel.transferTo(50, throughBuffer, rightChannel));
readBuffer.clear();
assertEquals(46, leftChannel.read(readBuffer));
readBuffer.flip();
assertEquals("looooooooooooooooooooooooooong daaaaaaaaaaaata", Buffers.getModifiedUtf8(readBuffer));
// Step 2: write to sink channel, transfer from source channel to left channel, then transfer from left channel to sink again
// finally, read from source channel
throughBuffer.clear();
writeBuffer.flip();
assertEquals(46, sinkChannel.write(writeBuffer));
assertEquals(46, sourceChannel.transferTo(50, throughBuffer, rightChannel));
assertEquals(46, sinkChannel.transferFrom(leftChannel, 50, throughBuffer));
readBuffer.clear();
assertEquals(46, sourceChannel.read(readBuffer));
readBuffer.flip();
assertEquals("looooooooooooooooooooooooooong daaaaaaaaaaaata", Buffers.getModifiedUtf8(readBuffer));
} finally {
IoUtils.safeClose(leftChannel);
IoUtils.safeClose(rightChannel);
}
}
@Test
@Ignore("Does not follow thread model")
public void suspendResumeReadsAndWrites() throws IOException, InterruptedException {
initChannels();
assertFalse(sourceChannel.isReadResumed());
assertFalse(sinkChannel.isWriteResumed());
final TestChannelListener<StreamSourceChannel> readListener = new TestChannelListener<StreamSourceChannel>();
final TestChannelListener<StreamSinkChannel> writeListener = new TestChannelListener<StreamSinkChannel>();
sourceChannel.getReadSetter().set(readListener);
assertFalse(readListener.isInvokedYet());
sinkChannel.getWriteSetter().set(writeListener);
assertFalse(writeListener.isInvokedYet());
sinkChannel.awaitWritable();
sourceChannel.resumeReads();
int count = 0;
while(!sourceChannel.isReadResumed()) {
Thread.sleep(50);
if (++ count == 10) {
fail("Read is not resumed");
}
}
assertTrue(sourceChannel.isReadResumed());
assertFalse(sinkChannel.isWriteResumed());
sinkChannel.resumeWrites();
count = 0;
assertTrue(writeListener.isInvoked());
assertTrue(sinkChannel.isWriteResumed());
writeListener.clear();
assertTrue(sourceChannel.isReadResumed());
sourceChannel.suspendReads();
assertFalse(sourceChannel.isReadResumed());
assertTrue(sinkChannel.isWriteResumed());
sinkChannel.suspendWrites();
assertFalse(sourceChannel.isReadResumed());
assertFalse(sinkChannel.isWriteResumed());
sinkChannel.wakeupWrites();
assertTrue(writeListener.isInvoked());
assertFalse(readListener.isInvokedYet());
assertSame(sinkChannel, writeListener.getChannel());
sourceChannel.wakeupReads();
assertTrue(readListener.isInvoked());
assertSame(sourceChannel, readListener.getChannel());
sourceChannel.shutdownReads();
sinkChannel.shutdownWrites();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
assertFalse(sourceChannel.isOpen());
assertFalse(sinkChannel.isOpen());
}
@SuppressWarnings("deprecation")
@Test
public void awaitReadableAndWritable() throws IOException, InterruptedException {
initChannels();
final TestChannelListener<StreamSourceChannel> readListener = new TestChannelListener<StreamSourceChannel>();
final TestChannelListener<StreamSinkChannel> writeListener = new TestChannelListener<StreamSinkChannel>();
sourceChannel.getReadSetter().set(readListener);
sinkChannel.getWriteSetter().set(writeListener);
sinkChannel.awaitWritable();
sinkChannel.awaitWritable(1, TimeUnit.HOURS);
final ReadableAwaiter<T> readableAwaiter1 = new ReadableAwaiter<T>(sourceChannel);
final ReadableAwaiter<T> readableAwaiter2 = new ReadableAwaiter<T>(sourceChannel, 10, TimeUnit.MICROSECONDS);
final ReadableAwaiter<T> readableAwaiter3 = new ReadableAwaiter<T>(sourceChannel, 10, TimeUnit.MINUTES);
final WritableAwaiter<S> writableAwaiter1 = new WritableAwaiter<S>(sinkChannel);
final WritableAwaiter<S> writableAwaiter2 = new WritableAwaiter<S>(sinkChannel, 5, TimeUnit.NANOSECONDS);
final WritableAwaiter<S> writableAwaiter3 = new WritableAwaiter<S>(sinkChannel, 2222222, TimeUnit.SECONDS);
final Thread readableAwaiterThread1 = new Thread(readableAwaiter1);
final Thread readableAwaiterThread2 = new Thread(readableAwaiter2);
final Thread readableAwaiterThread3 = new Thread(readableAwaiter3);
final Thread writableAwaiterThread1 = new Thread(writableAwaiter1);
final Thread writableAwaiterThread2 = new Thread(writableAwaiter2);
final Thread writableAwaiterThread3 = new Thread(writableAwaiter3);
readableAwaiterThread1.start();
readableAwaiterThread2.start();
readableAwaiterThread3.start();
readableAwaiterThread1.join(50);
readableAwaiterThread2.join();
readableAwaiterThread3.join(50);
assertTrue(readableAwaiterThread1.isAlive());
assertTrue(readableAwaiterThread3.isAlive());
sinkChannel.shutdownWrites();
writableAwaiterThread1.start();
writableAwaiterThread2.start();
writableAwaiterThread3.start();
writableAwaiterThread1.join();
writableAwaiterThread2.join();
writableAwaiterThread3.join();
final ByteBuffer buffer = ByteBuffer.allocate(5);
buffer.put("12345".getBytes()).flip();
ClosedChannelException expected = null;
try {
sinkChannel.write(buffer);
} catch (ClosedChannelException e) {
expected = e;
}
assertNotNull(expected);
readableAwaiterThread1.join();
readableAwaiterThread3.join();
sinkChannel.resumeWrites();
writableAwaiterThread1.join();
writableAwaiterThread3.join();
assertNotNull(sinkChannel.getWriteThread());
assertNotNull(sinkChannel.getIoThread());
}
protected static class ReadableAwaiter<T extends StreamSourceChannel> implements Runnable {
private final T channel;
private final long timeout;
private final TimeUnit timeoutUnit;
public ReadableAwaiter(T c) {
this(c, -1, null);
}
public ReadableAwaiter(T c, long t, TimeUnit tu) {
channel = c;
timeout = t;
timeoutUnit = tu;
}
public void run() {
try {
if (timeout == -1) {
channel.awaitReadable();
} else {
channel.awaitReadable(timeout, timeoutUnit);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
protected static class WritableAwaiter<S extends StreamSinkChannel> implements Runnable {
private final S channel;
private final long timeout;
private final TimeUnit timeoutUnit;
public WritableAwaiter(S c) {
this(c, -1, null);
}
public WritableAwaiter(S c, long t, TimeUnit tu) {
channel = c;
timeout = t;
timeoutUnit = tu;
}
public void run() {
try {
if (timeout == -1) {
channel.awaitWritable();
} else {
channel.awaitWritable(timeout, timeoutUnit);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}