/*
* Copyright 2009-2010 Brian S O'Neill
*
* 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.cojen.dirmi.io;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.util.Random;
import org.junit.*;
import static org.junit.Assert.*;
import org.cojen.dirmi.ClosedException;
import org.cojen.dirmi.util.ThreadPool;
import static org.cojen.dirmi.AbstractTestSuite.sleep;
/**
*
*
* @author Brian S O'Neill
*/
public class TestPacketInputStream {
public static void main(String[] args) {
org.junit.runner.JUnitCore.main(TestPacketInputStream.class.getName());
}
private static volatile ThreadPool threadPool;
private static volatile IOExecutor executor;
@BeforeClass
public static void before() {
threadPool = new ThreadPool(1000, true);
executor = new IOExecutor(threadPool);
}
@AfterClass
public static void after() {
ThreadPool pool = threadPool;
threadPool = null;
if (pool != null) {
pool.shutdown();
}
}
@Test
public void empty() throws Exception {
ByteArrayInputStream bin = new ByteArrayInputStream(new byte[] {0, 0});
Reader pin = new Reader(bin);
assertEquals(-1, pin.read());
Reader recycled = pin.recycle();
assertFalse(pin == recycled);
assertTrue(recycled != null);
assertNull(pin.recycle());
try {
pin.read();
fail();
} catch (ClosedException e) {
}
assertEquals(-1, recycled.read());
recycled = recycled.recycle();
assertTrue(recycled != null);
}
@Test
public void one() throws Exception {
ByteArrayInputStream bin = new ByteArrayInputStream(new byte[] {1, 10, 0});
Reader pin = new Reader(bin);
assertEquals(10, pin.read());
assertEquals(-1, pin.read());
PacketInputStream recycled = pin.recycle();
assertFalse(pin == recycled);
assertTrue(recycled != null);
}
@Test
public void oneAsync() throws Exception {
ByteArrayInputStream bin = new ByteArrayInputStream(new byte[] {1, 10, 0});
Reader pin = new Reader(bin);
assertEquals(10, pin.read());
PacketInputStream recycled = pin.recycleAndWait();
assertFalse(pin == recycled);
assertTrue(recycled != null);
assertTrue(pin.didUseExecutor());
}
@Test
public void two() throws Exception {
ByteArrayInputStream bin = new ByteArrayInputStream(new byte[] {2, 34, 78, 0});
Reader pin = new Reader(bin);
assertEquals(34, pin.read());
assertEquals(78, pin.read());
assertEquals(-1, pin.read());
PacketInputStream recycled = pin.recycle();
assertFalse(pin == recycled);
assertTrue(recycled != null);
}
@Test
public void twoAsync() throws Exception {
twoAsync(2);
}
@Test
public void twoAsyncAbort() throws Exception {
twoAsync(1);
}
private void twoAsync(int limit) throws Exception {
ByteArrayInputStream bin = new ByteArrayInputStream(new byte[] {2, 34, 78, 0});
Reader pin = new Reader(bin);
if (--limit >= 0) {
assertEquals(34, pin.read());
if (--limit >= 0) {
assertEquals(78, pin.read());
}
}
PacketInputStream recycled = pin.recycleAndWait();
assertFalse(pin == recycled);
assertTrue(recycled != null);
assertTrue(pin.didUseExecutor());
}
@Test
public void many() throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
PacketOutputStream pout = new TestPacketOutputStream.Writer(bout);
byte[] data = new byte[200];
new Random().nextBytes(data);
pout.write(data);
pout.close();
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
Reader pin = new Reader(bin);
for (int i=0; i<200; i++) {
assertEquals(data[i] & 0xff, pin.read());
}
}
@Test
public void manyAsyncAbort1() throws Exception {
manyAsyncAbort(200);
}
@Test
public void manyAsyncAbort2() throws Exception {
manyAsyncAbort(100);
}
@Test
public void manyAsyncAbort3() throws Exception {
manyAsyncAbort(0);
}
private void manyAsyncAbort(int limit) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
PacketOutputStream pout = new TestPacketOutputStream.Writer(bout);
byte[] data = new byte[200];
new Random().nextBytes(data);
pout.write(data);
pout.close();
// Manually apply empty packet header, since PacketOutputStream did not
// have a recycler.
byte[] data2 = bout.toByteArray();
byte[] data3 = new byte[data2.length + 1];
System.arraycopy(data2, 0, data3, 0, data2.length);
data3[data3.length - 1] = 0;
ByteArrayInputStream bin = new ByteArrayInputStream(data3);
Reader pin = new Reader(bin);
for (int i=0; i<limit; i++) {
assertEquals(data[i] & 0xff, pin.read());
}
PacketInputStream recycled = pin.recycleAndWait();
assertFalse(pin == recycled);
assertTrue(recycled != null);
assertTrue(pin.didUseExecutor());
}
@Test
public void recyclingChaos() throws Throwable {
long seed = System.nanoTime();
try {
recyclingChaos(seed);
} catch (Throwable e) {
System.out.println("Seed: " + seed);
throw e;
}
}
private void recyclingChaos(long seed) throws Exception {
final PipedInputStream masterInput = new PipedInputStream();
final PipedOutputStream masterOutput = new PipedOutputStream(masterInput);
final int maxTotal = 1000000;
Reader pin = new Reader(masterInput);
TestPacketOutputStream.Writer pout = new TestPacketOutputStream.Writer(masterOutput);
Random inSizeRandom = new Random(seed - 1);
for (int i=0; i<100; i++) {
seed++;
final long fseed = seed;
final TestPacketOutputStream.Writer fpout = pout;
class WriterThread extends Thread {
public void run() {
Random outRandom = new Random(fseed);
Random sizeRandom = new Random(fseed + 1);
try {
int total = outRandom.nextInt(maxTotal);
while (total > 0) {
if (sizeRandom.nextInt(100) == 0) {
fpout.write(outRandom.nextInt());
total--;
} else {
int size = sizeRandom.nextInt(1000) + 1;
if (size > total) {
size = total;
}
byte[] data = new byte[size];
for (int j=0; j<size; j++) {
data[j] = (byte) outRandom.nextInt();
}
fpout.write(data);
total -= size;
}
}
fpout.close();
} catch (ClosedException e) {
// Expected at times.
} catch (IOException e) {
e.printStackTrace();
}
}
};
WriterThread writerThread = new WriterThread();
writerThread.start();
final Random inRandom = new Random(seed);
final int expectedTotal = inRandom.nextInt(maxTotal);
int abortAt = Integer.MAX_VALUE;
if (inSizeRandom.nextBoolean()) {
if (inSizeRandom.nextBoolean()) {
abortAt = expectedTotal;
} else {
abortAt = expectedTotal - inSizeRandom.nextInt(maxTotal);
if (abortAt < 0) {
abortAt = 0;
}
}
}
int total = 0;
while (total < abortAt) {
if (inSizeRandom.nextInt(100) == 0) {
int b = pin.read();
if (b < 0) {
break;
}
assertEquals(inRandom.nextInt() & 0xff, b);
total++;
} else {
int size = inSizeRandom.nextInt(1000) + 1;
if (abortAt == expectedTotal && total + size > expectedTotal) {
size = expectedTotal - total;
}
byte[] data = new byte[size];
int amt = pin.read(data);
if (amt <= 0) {
break;
}
for (int j=0; j<amt; j++) {
assertEquals(inRandom.nextInt() & 0xff, data[j] & 0xff);
}
total += amt;
}
}
pin.recycle();
if (abortAt == Integer.MAX_VALUE) {
assertEquals(expectedTotal, total);
} else {
fpout.close();
}
pin = pin.waitForRecycled();
writerThread.join();
pout = fpout.getRecycled();
}
}
private static class Reader extends PacketInputStream<Reader> {
private volatile boolean mUsedExecutor;
private Reader mRecycled;
Reader(InputStream in) {
super(in);
}
Reader(InputStream in, int size) {
super(in, size);
}
private Reader() {
}
@Override
protected IOExecutor executor() {
mUsedExecutor = true;
return executor;
}
@Override
protected Reader newInstance() {
return new Reader(this);
}
@Override
protected synchronized void recycled(Reader newInstance) {
mRecycled = newInstance;
notifyAll();
}
Reader recycle() throws IOException {
synchronized (this) {
mRecycled = null;
}
close();
synchronized (this) {
return mRecycled;
}
}
Reader recycleAndWait() throws IOException, InterruptedException {
recycle();
return waitForRecycled();
}
Reader waitForRecycled() throws InterruptedException {
long end = System.nanoTime() + 10 * 1000000000L;
synchronized (this) {
do {
if (mRecycled != null) {
break;
}
wait(10000);
if (mRecycled != null) {
break;
}
} while (System.nanoTime() < end);
return mRecycled;
}
}
boolean didUseExecutor() {
return mUsedExecutor;
}
}
}