package freenet.support.io;
import java.io.IOException;
import java.util.Random;
import freenet.support.api.RandomAccessBuffer;
import junit.framework.TestCase;
/** Base class for testing RandomAccessBuffer's. */
public abstract class RandomAccessBufferTestBase extends TestCase {
/** Size list for small tests i.e. stuff that definitely fits in RAM */
protected final int[] sizeList;
/** Size list for big tests i.e. stuff that might not fit in RAM */
private final long[] fullSizeList;
protected RandomAccessBufferTestBase(int[] allSmallTests) {
sizeList = allSmallTests;
fullSizeList = new long[sizeList.length];
for(int i=0;i<sizeList.length;i++) fullSizeList[i] = sizeList[i];
}
protected RandomAccessBufferTestBase(int[] smallTests, long[] bigTests) {
sizeList = smallTests;
fullSizeList = bigTests;
}
/** Construct an instance of a given size.
* @throws IOException */
protected abstract RandomAccessBuffer construct(long size) throws IOException;
private void innerTestSize(long sz) throws IOException {
RandomAccessBuffer raf = construct(sz);
assertEquals(raf.size(), sz);
raf.close();
raf.free();
}
/** Test that we can create and free a RandomAccessBuffer of various sizes, and it returns the correct
* size. */
public void testSize() throws IOException {
for(long size : fullSizeList)
innerTestSize(size);
}
private static final int BUFFER_SIZE = 65536;
public void testFormula() throws IOException {
Random r = new Random(2126);
Formula modulo256 = new Formula() {
@Override
public byte getByte(long offset) {
return (byte)offset;
}
};
Formula modulo57 = new Formula() {
@Override
public byte getByte(long offset) {
return (byte)(offset % 57);
}
};
for(long size : fullSizeList) {
innerTestFormula(size, r, modulo256);
innerTestFormula(size, r, modulo57);
}
}
protected interface Formula {
byte getByte(long offset);
}
/** Write using a given formula in random small writes, then check using random small reads. */
protected void innerTestFormula(long sz, Random r, Formula f) throws IOException {
RandomAccessBuffer raf = construct(sz);
assertEquals(raf.size(), sz);
int x = 0;
// Write (and check as go)
while(x < sz) {
int maxRead = (int)Math.min(BUFFER_SIZE, sz - x);
int toRead = maxRead == 1 ? 1 : r.nextInt(maxRead-1)+1;
byte[] buf = new byte[toRead];
for(int i=0;i<buf.length;i++) buf[i] = f.getByte(i+x);
raf.pwrite(x, buf, 0, toRead);
for(int i=0;i<buf.length;i++) buf[i] = (byte)~buf[i];
raf.pread(x, buf, 0, toRead);
for(int i=0;i<buf.length;i++) assertEquals(buf[i], f.getByte(i+x));
x += toRead;
}
// Read
while(x < sz) {
int maxRead = (int)Math.min(BUFFER_SIZE, sz - x);
int toRead = r.nextInt(maxRead-1)+1;
byte[] buf = new byte[toRead];
raf.pread(x, buf, 0, toRead);
for(int i=0;i<buf.length;i++) assertEquals(buf[i], f.getByte(i+x));
x += toRead;
}
x = 0;
raf.close();
raf.free();
}
/** Test that we can't write or read after the size limit */
public void testWriteOverLimit() throws IOException {
Random r = new Random(21092506);
innerTestWriteOverLimit(0L, 1);
innerTestWriteOverLimit(1, 1);
innerTestWriteOverLimit(1, 1024);
for(int i=0;i<10;i++) {
innerTestWriteOverLimit(1024*1024+1, r.nextInt(1024));
}
innerTestWriteOverLimit(1024*1024+1, 1024*1024);
innerTestWriteOverLimit(1024*1024+1, 1024*1024+2);
for(long size : fullSizeList) {
innerTestWriteOverLimit(size, 1024);
}
for(int size : sizeList) {
innerTestWriteOverLimit(size, size);
innerTestWriteOverLimit(size, size+1);
}
}
private void innerTestWriteOverLimit(long sz, int choppedBytes) throws IOException {
RandomAccessBuffer raf = construct(sz);
assertEquals(raf.size(), sz);
long startAt = sz - choppedBytes;
byte[] buf = new byte[choppedBytes];
if(sz != 0 && choppedBytes < sz) {
if(startAt >= 0)
readWriteMustSucceed(raf, startAt, buf, 0, buf.length); // Read, write up to the end work.
else
try {
readWriteMustSucceed(raf, startAt, buf, 0, buf.length); // Read, write up to the end work.
fail("Should fail to read at negative index");
} catch (IllegalArgumentException e) {
// Ok.
}
}
if(startAt+1 >= 0)
readWriteMustFail(raf, startAt+1, buf, 0, buf.length); // Read, write over the end fail.
else
try {
readWriteMustSucceed(raf, startAt+1, buf, 0, buf.length); // Read, write up to the end work.
fail("Should fail to read at negative index");
} catch (IllegalArgumentException e) {
// Ok.
}
readWriteMustFail(raf, sz, buf, 0, buf.length); // Read, write at the end fail.
readWriteMustFail(raf, sz+1, buf, 0, buf.length); // One byte into end
readWriteMustFail(raf, sz+1025, buf, 0, buf.length); // 1KB in
readWriteMustFail(raf, sz+buf.length, buf, 0, buf.length);
raf.close();
raf.free();
}
private void readWriteMustSucceed(RandomAccessBuffer raf, long startAt, byte[] buf, int offset, int length) throws IOException {
raf.pread(startAt, buf, 0, buf.length); // Should work
raf.pwrite(startAt, buf, 0, buf.length); // Should work
}
private void readWriteMustFail(RandomAccessBuffer raf, long startAt, byte[] buf, int offset, int length) throws IOException {
if(length == 0) return; // NOP.
try {
raf.pread(startAt, buf, 0, buf.length); // Should work
fail("Must throw!");
} catch (IOException e) {
// Ok.
}
try {
raf.pwrite(startAt, buf, 0, buf.length); // Should work
fail("Must throw!");
} catch (IOException e) {
// Ok.
}
}
public void testClose() throws IOException {
// Try to cover any thresholds for e.g. moving to disk.
// Implementations should add their own tests according to known thresholds (white box).
for(long size : fullSizeList)
innerTestClose(size);
}
/** Test that after closing a RandomAccessBuffer we cannot read from it or write to it */
protected void innerTestClose(long sz) throws IOException {
RandomAccessBuffer raf = construct(sz);
raf.close();
byte[] buf = new byte[(int)Math.min(1024, sz)];
readWriteMustFail(raf, 0L, buf, 0, buf.length);
raf.free();
}
public void testArray() throws IOException {
Random r = new Random(21162506);
for(int size : sizeList)
innerTestArray(size, r, false);
}
/** Create an array, fill it with random numbers, write it sequentially to the
* RandomAccessBuffer, then read randomly and compare. */
protected void innerTestArray(int len, Random r, boolean readOnly) throws IOException {
if(len == 0) return;
byte[] buf = new byte[len];
r.nextBytes(buf);
RandomAccessBuffer raf = construct(len);
raf.pwrite(0L, buf, 0, buf.length);
for(int i=0;i<100;i++) {
int end = len == 1 ? 1 : r.nextInt(len)+1;
int start = r.nextInt(end);
checkArraySectionEqualsReadData(buf, raf, start, end, readOnly);
}
checkArraySectionEqualsReadData(buf, raf, 0, len, readOnly);
if(len > 1)
checkArraySectionEqualsReadData(buf, raf, 1, len-1, readOnly);
raf.close();
raf.free();
}
/** Check that the array section equals the read data, then write it and repeat the check. */
public static void checkArraySectionEqualsReadData(byte[] buf, RandomAccessBuffer raf, int start, int end, boolean readOnly) throws IOException {
int len = end - start;
if(len == 0) return;
byte[] tmp = new byte[len];
raf.pread(start, tmp, 0, len);
for(int i=0;i<len;i++)
assertEquals(tmp[i], buf[start+i]);
if(!readOnly) {
raf.pwrite(start, buf, start, len);
}
for(int i=0;i<len;i++) tmp[i] = 0;
raf.pread(start, tmp, 0, len);
for(int i=0;i<len;i++)
assertEquals(tmp[i], buf[start+i]);
}
}