// Copyright © 2011-2013, Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0
package fi.jumi.core.ipc.buffer;
import fi.jumi.core.util.TestableRandom;
import org.junit.*;
import org.junit.rules.ExpectedException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
public class IpcBufferTest {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Rule
public final TestableRandom random = new TestableRandom();
private IpcBuffer buffer;
@Test
public void buffer_will_increase_capacity_automatically_when_writing_beyond_it() {
final int initialCapacity = 10;
final int toBeWritten = initialCapacity + 5;
IpcBuffer buffer = new IpcBuffer(new AllocatedByteBufferSequence(initialCapacity));
for (int i = 0; i < toBeWritten; i++) {
buffer.writeByte(random.nextByte());
}
random.resetSeed();
buffer.position(0);
for (int i = 0; i < toBeWritten; i++) {
assertThat("byte at index " + i, buffer.readByte(), is(random.nextByte()));
}
}
/**
* Opening memory-mapped files is relatively slow.
*/
@Test
public void tries_to_minimize_the_number_of_times_that_a_backing_buffer_is_looked_up() {
AllocatedByteBufferSequence sequence = spy(new AllocatedByteBufferSequence(10));
IpcBuffer buffer = new IpcBuffer(sequence);
buffer.setByte(5, (byte) 0);
buffer.setByte(15, (byte) 0);
buffer.setByte(5, (byte) 0);
buffer.setByte(15, (byte) 0);
verify(sequence, times(1)).get(0);
verify(sequence, times(1)).get(1);
}
@Test
public void position_can_be_changed() {
buffer = new IpcBuffer(new FixedByteBufferSequence(10));
assertThat(buffer.position(), is(0));
buffer.position(5);
assertThat(buffer.position(), is(5));
}
@Test
public void cannot_make_position_negative() {
buffer = new IpcBuffer(new AllocatedByteBufferSequence(10));
thrown.expect(IllegalArgumentException.class);
buffer.position(-1);
}
@Test
public void cannot_read_from_negative_positions() {
buffer = new IpcBuffer(new AllocatedByteBufferSequence(10));
thrown.expect(IndexOutOfBoundsException.class);
buffer.getByte(-1);
}
@Test
public void cannot_write_to_negative_positions() {
buffer = new IpcBuffer(new AllocatedByteBufferSequence(10));
thrown.expect(IndexOutOfBoundsException.class);
buffer.setByte(-1, (byte) 0);
}
@Test
public void test_traversing_forward_and_backward() {
final int segmentCapacity = 2;
final int end = segmentCapacity * 5;
IpcBuffer buffer = new IpcBuffer(new AllocatedByteBufferSequence(segmentCapacity));
// forward
for (int i = 0; i < end; i++) {
buffer.setByte(i, (byte) i);
}
// backward
for (int i = end - 1; i >= 0; i--) {
assertThat(buffer.getByte(i), is((byte) i));
}
}
// absolute get/set
@Test
public void absolute_byte() {
testAbsolute(Byte.SIZE,
(index) -> buffer.setByte(index, random.nextByte()),
(index) -> assertThat(buffer.getByte(index), is(random.nextByte()))
);
}
@Test
public void absolute_short() {
testAbsolute(Short.SIZE,
(index) -> buffer.setShort(index, random.nextShort()),
(index) -> assertThat(buffer.getShort(index), is(random.nextShort()))
);
}
@Test
public void absolute_char() {
testAbsolute(Character.SIZE,
(index) -> buffer.setChar(index, random.nextChar()),
(index) -> assertThat(buffer.getChar(index), is(random.nextChar()))
);
}
@Test
public void absolute_int() {
testAbsolute(Integer.SIZE,
(index) -> buffer.setInt(index, random.nextInt()),
(index) -> assertThat(buffer.getInt(index), is(random.nextInt()))
);
}
@Test
public void absolute_long() {
testAbsolute(Long.SIZE,
(index) -> buffer.setLong(index, random.nextLong()),
(index) -> assertThat(buffer.getLong(index), is(random.nextLong()))
);
}
// relative read/write
@Test
public void relative_byte() {
testRelative(Byte.SIZE,
() -> buffer.writeByte(random.nextByte()),
() -> assertThat(buffer.readByte(), is(random.nextByte()))
);
}
@Test
public void relative_short() {
testRelative(Short.SIZE,
() -> buffer.writeShort(random.nextShort()),
() -> assertThat(buffer.readShort(), is(random.nextShort()))
);
}
@Test
public void relative_char() {
testRelative(Character.SIZE,
() -> buffer.writeChar(random.nextChar()),
() -> assertThat(buffer.readChar(), is(random.nextChar()))
);
}
@Test
public void relative_int() {
testRelative(Integer.SIZE,
() -> buffer.writeInt(random.nextInt()),
() -> assertThat(buffer.readInt(), is(random.nextInt()))
);
}
@Test
public void relative_long() {
testRelative(Long.SIZE,
() -> buffer.writeLong(random.nextLong()),
() -> assertThat(buffer.readLong(), is(random.nextLong()))
);
}
// randomized testing
private void testAbsolute(int sizeInBits, AbsoluteWriter writer, AbsoluteReader reader) {
final int sizeInBytes = sizeInBits / Byte.SIZE;
int index;
for (int howMuchGoesToNextBuffer = 0; howMuchGoesToNextBuffer < sizeInBytes; howMuchGoesToNextBuffer++) {
int firstSegment = sizeInBytes - howMuchGoesToNextBuffer;
FixedByteBufferSequence buffers = new FixedByteBufferSequence(firstSegment, sizeInBytes * 2);
buffer = new IpcBuffer(buffers);
random.info("scenario: " + sizeInBytes + " byte values, first segment is " + firstSegment + " bytes");
random.resetSeed();
index = 0;
assertReturnedItself(writer.run(index));
index += sizeInBytes;
assertReturnedItself(writer.run(index));
random.resetSeed();
index = 0;
reader.run(index);
index += sizeInBytes;
reader.run(index);
random.info("scenario: concatenated buffers, results should be the same");
buffer = new IpcBuffer(new FixedByteBufferSequence(buffers.combinedBuffer()));
random.resetSeed();
index = 0;
reader.run(index);
index += sizeInBytes;
reader.run(index);
}
}
private interface AbsoluteWriter {
IpcBuffer run(int index);
}
private interface AbsoluteReader {
void run(int index);
}
private void testRelative(int sizeInBits, RelativeWriter writer, RelativeReader checker) {
final int sizeInBytes = sizeInBits / Byte.SIZE;
for (int howMuchGoesToNextBuffer = 0; howMuchGoesToNextBuffer < sizeInBytes; howMuchGoesToNextBuffer++) {
int firstSegment = sizeInBytes - howMuchGoesToNextBuffer;
FixedByteBufferSequence buffers = new FixedByteBufferSequence(firstSegment, sizeInBytes * 2);
buffer = new IpcBuffer(buffers);
random.info("scenario: " + sizeInBytes + " byte values, first segment is " + firstSegment + " bytes");
random.resetSeed();
buffer.position(0);
assertReturnedItself(writer.run());
assertReturnedItself(writer.run());
random.resetSeed();
buffer.position(0);
checker.run();
checker.run();
random.info("scenario: concatenated buffers, results should be the same");
buffer = new IpcBuffer(new FixedByteBufferSequence(buffers.combinedBuffer()));
random.resetSeed();
buffer.position(0);
checker.run();
checker.run();
}
}
private interface RelativeWriter {
IpcBuffer run();
}
private interface RelativeReader {
void run();
}
private void assertReturnedItself(IpcBuffer run) {
assertThat("write operations should return `this`", run, is(buffer));
}
}