package com.github.marschall.memoryfilesystem;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.NonReadableChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class MemoryContentsTest {
private static final byte[] SAMPLE_DATA = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
private static final int INITIAL_BLOCKS = 4;
private final boolean direct;
private MemoryFile contents;
public MemoryContentsTest(boolean direct) {
this.direct = direct;
}
private ByteBuffer allocate(int capacity) {
if (this.direct) {
return ByteBuffer.allocateDirect(capacity);
} else {
return ByteBuffer.allocate(capacity);
}
}
@Parameters(name = "direct allocated: {0}")
public static List<Object[]> parameters() {
return Arrays.asList(new Object[]{true}, new Object[]{false});
}
@Before
public void setUp() {
this.contents = new MemoryFile("", EntryCreationContext.empty(), INITIAL_BLOCKS);
}
private ByteBuffer writeTestData(SeekableByteChannel channel) throws IOException {
ByteBuffer src = this.allocate(SAMPLE_DATA.length);
byte[] data = SAMPLE_DATA;
src.put(data);
src.rewind();
assertEquals(SAMPLE_DATA.length, channel.write(src));
return src;
}
@Test
public void firstBlockEmpty() throws IOException {
ByteBuffer src = this.allocate(SAMPLE_DATA.length);
byte[] data = SAMPLE_DATA;
src.put(data);
src.rewind();
Path path = new MockPath();
SeekableByteChannel channel = this.contents.newChannel(true, true, false, path);
assertEquals(0, channel.size());
assertEquals(0, channel.position());
assertEquals(SAMPLE_DATA.length, channel.write(src));
assertEquals(SAMPLE_DATA.length, channel.size());
assertEquals(SAMPLE_DATA.length, channel.position());
channel.position(0L);
ByteBuffer dst = this.allocate(SAMPLE_DATA.length);
assertEquals(SAMPLE_DATA.length, channel.read(dst));
dst.rewind();
byte[] extracted = new byte[SAMPLE_DATA.length];
dst.get(extracted);
assertArrayEquals(data, extracted);
dst.rewind();
assertEquals(-1, channel.read(dst));
}
@Test
public void positition() throws IOException {
Path path = new MockPath();
SeekableByteChannel channel = this.contents.newChannel(true, true, false, path);
assertEquals(0L, channel.position());
// set the position bigger than the limit
channel.position(5L);
assertEquals(5L, channel.position());
assertEquals(0, channel.size());
// make sure we're at the end
ByteBuffer dst = this.allocate(1);
assertEquals(-1, channel.read(dst));
this.writeTestData(channel);
assertEquals(5L + SAMPLE_DATA.length, channel.position());
assertEquals(5L + SAMPLE_DATA.length, channel.size());
channel.position(channel.position() - SAMPLE_DATA.length);
byte[] readBack = this.readBackSampleData(channel);
assertArrayEquals(SAMPLE_DATA, readBack);
}
@Test
public void readOnly() throws IOException {
Path path = new MockPath();
SeekableByteChannel channel = this.contents.newChannel(true, true, false, path);
ByteBuffer src = this.writeTestData(channel);
channel = this.contents.newChannel(true, false, false, path);
try {
channel.write(src);
fail("channel should not be writable");
} catch (NonWritableChannelException e) {
// should reach here
assertTrue(true);
}
}
@Test
public void writeOnly() throws IOException {
Path path = new MockPath();
SeekableByteChannel channel = this.contents.newChannel(false, true, false, path);
ByteBuffer src = this.writeTestData(channel);
src.rewind();
channel.position(0L);
try {
channel.read(src);
fail("channel should not be readable");
} catch (NonReadableChannelException e) {
// should reach here
assertTrue(true);
}
}
@Test
public void truncate() throws IOException {
Path path = new MockPath();
SeekableByteChannel channel = this.contents.newChannel(true, true, false, path);
ByteBuffer src = this.allocate(1);
for (byte data : SAMPLE_DATA) {
src.rewind();
src.put(data);
src.rewind();
channel.write(src);
}
src.rewind();
src.put((byte) 1);
for (int i = 0; i < MemoryInode.BLOCK_SIZE; i++) {
src.rewind();
channel.write(src);
}
long expectedSize = (long) MemoryInode.BLOCK_SIZE + SAMPLE_DATA.length;
assertEquals(expectedSize, channel.size());
// truncating a bigger value should make no difference
assertSame(channel, channel.truncate(Long.MAX_VALUE));
assertEquals(expectedSize, channel.size());
assertSame(channel, channel.truncate(expectedSize + 1L));
assertEquals(expectedSize, channel.size());
// now really truncate
assertSame(channel, channel.truncate(SAMPLE_DATA.length));
assertEquals(SAMPLE_DATA.length, channel.size());
assertEquals(SAMPLE_DATA.length, channel.position());
channel.position(0);
byte[] readBack = this.readBackSampleData(channel);
assertArrayEquals(SAMPLE_DATA, readBack);
// should be at the end
ByteBuffer dst = this.allocate(1);
assertEquals(-1, channel.read(dst));
}
@Test
public void appendNonTruncatable() throws IOException {
Path path = new MockPath();
SeekableByteChannel channel = this.contents.newAppendingChannel(true, false, path);
ByteBuffer src = this.writeTestData(channel);
channel.write(src);
try {
channel.truncate(5L);
fail("channel should not allow truncation");
} catch (IOException e) {
// should reach here
assertTrue(true);
}
}
@Test
public void appendReadable() throws IOException {
Path path = new MockPath();
SeekableByteChannel channel = this.contents.newAppendingChannel(true, false, path);
assertEquals(0L, channel.position());
ByteBuffer src = this.allocate(1);
for (byte data : SAMPLE_DATA) {
src.rewind();
channel.position(0L);
src.put(data);
src.rewind();
channel.write(src);
assertEquals(data + 1, channel.size());
assertEquals(data + 1, channel.position());
}
channel.position(0);
byte[] readBack = this.readBackSampleData(channel);
assertArrayEquals(SAMPLE_DATA, readBack);
}
private byte[] readBackSampleData(SeekableByteChannel channel) throws IOException {
ByteBuffer dst = this.allocate(SAMPLE_DATA.length);
assertEquals(SAMPLE_DATA.length, channel.read(dst));
dst.rewind();
byte[] readBack = new byte[SAMPLE_DATA.length];
dst.get(readBack);
return readBack;
}
@Test
public void appendNotReadable() throws IOException {
Path path = new MockPath();
SeekableByteChannel channel = this.contents.newAppendingChannel(false, false, path);
ByteBuffer testData = this.writeTestData(channel);
channel.position(0L);
testData.rewind();
try {
channel.read(testData);
fail("channel should not be readable");
} catch (NonReadableChannelException e) {
// should reach here
assertTrue(true);
}
}
}