/*
* Copyright 2014-2017 Real Logic Ltd.
*
* 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 io.aeron;
import io.aeron.logbuffer.*;
import io.aeron.logbuffer.ControlledFragmentHandler.Action;
import io.aeron.protocol.*;
import org.agrona.*;
import org.agrona.concurrent.UnsafeBuffer;
import org.agrona.concurrent.status.*;
import org.junit.*;
import org.mockito.*;
import static io.aeron.logbuffer.LogBufferDescriptor.*;
import static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;
import static java.nio.ByteBuffer.allocateDirect;
import static junit.framework.TestCase.assertTrue;
import static org.agrona.BitUtil.align;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
public class ImageTest
{
private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;
private static final int POSITION_BITS_TO_SHIFT = Integer.numberOfTrailingZeros(TERM_BUFFER_LENGTH);
private static final byte[] DATA = new byte[36];
static
{
for (int i = 0; i < DATA.length; i++)
{
DATA[i] = (byte)i;
}
}
private static final long CORRELATION_ID = 0xC044E1AL;
private static final int SESSION_ID = 0x5E55101D;
private static final int STREAM_ID = 0xC400E;
private static final String SOURCE_IDENTITY = "ipc";
private static final int INITIAL_TERM_ID = 0xEE81D;
private static final int MESSAGE_LENGTH = HEADER_LENGTH + DATA.length;
private static final int ALIGNED_FRAME_LENGTH = align(MESSAGE_LENGTH, FrameDescriptor.FRAME_ALIGNMENT);
private final UnsafeBuffer rcvBuffer = new UnsafeBuffer(allocateDirect(ALIGNED_FRAME_LENGTH));
private final DataHeaderFlyweight dataHeader = new DataHeaderFlyweight();
private final FragmentHandler mockFragmentHandler = mock(FragmentHandler.class);
private final ControlledFragmentHandler mockControlledFragmentHandler = mock(ControlledFragmentHandler.class);
private final Position position = spy(new AtomicLongPosition());
private final LogBuffers logBuffers = mock(LogBuffers.class);
private final ErrorHandler errorHandler = mock(ErrorHandler.class);
private final Subscription subscription = mock(Subscription.class);
private UnsafeBuffer[] termBuffers = new UnsafeBuffer[PARTITION_COUNT];
@Before
public void setUp()
{
dataHeader.wrap(rcvBuffer);
for (int i = 0; i < PARTITION_COUNT; i++)
{
termBuffers[i] = new UnsafeBuffer(allocateDirect(TERM_BUFFER_LENGTH));
}
final UnsafeBuffer logMetaDataBuffer = new UnsafeBuffer(allocateDirect(LOG_META_DATA_LENGTH));
when(logBuffers.termBuffers()).thenReturn(termBuffers);
when(logBuffers.termLength()).thenReturn(TERM_BUFFER_LENGTH);
when(logBuffers.metaDataBuffer()).thenReturn(logMetaDataBuffer);
}
@Test
public void shouldHandleClosedImage()
{
final Image image = createImage();
image.managedResource();
assertTrue(image.isClosed());
assertThat(image.poll(mockFragmentHandler, Integer.MAX_VALUE), is(0));
}
@Test
public void shouldReportCorrectPositionOnReception()
{
final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);
position.setOrdered(initialPosition);
final Image image = createImage();
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));
final int messages = image.poll(mockFragmentHandler, Integer.MAX_VALUE);
assertThat(messages, is(1));
verify(mockFragmentHandler).onFragment(
any(UnsafeBuffer.class),
eq(HEADER_LENGTH),
eq(DATA.length),
any(Header.class));
final InOrder inOrder = Mockito.inOrder(position);
inOrder.verify(position).setOrdered(initialPosition);
inOrder.verify(position).setOrdered(initialPosition + ALIGNED_FRAME_LENGTH);
}
@Test
public void shouldReportCorrectPositionOnReceptionWithNonZeroPositionInInitialTermId()
{
final int initialMessageIndex = 5;
final int initialTermOffset = offsetForFrame(initialMessageIndex);
final long initialPosition = computePosition(
INITIAL_TERM_ID, initialTermOffset, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);
position.setOrdered(initialPosition);
final Image image = createImage();
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(initialMessageIndex));
final int messages = image.poll(mockFragmentHandler, Integer.MAX_VALUE);
assertThat(messages, is(1));
verify(mockFragmentHandler).onFragment(
any(UnsafeBuffer.class),
eq(initialTermOffset + HEADER_LENGTH),
eq(DATA.length),
any(Header.class));
final InOrder inOrder = Mockito.inOrder(position);
inOrder.verify(position).setOrdered(initialPosition);
inOrder.verify(position).setOrdered(initialPosition + ALIGNED_FRAME_LENGTH);
}
@Test
public void shouldReportCorrectPositionOnReceptionWithNonZeroPositionInNonInitialTermId()
{
final int activeTermId = INITIAL_TERM_ID + 1;
final int initialMessageIndex = 5;
final int initialTermOffset = offsetForFrame(initialMessageIndex);
final long initialPosition =
computePosition(activeTermId, initialTermOffset, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);
position.setOrdered(initialPosition);
final Image image = createImage();
insertDataFrame(activeTermId, offsetForFrame(initialMessageIndex));
final int messages = image.poll(mockFragmentHandler, Integer.MAX_VALUE);
assertThat(messages, is(1));
verify(mockFragmentHandler).onFragment(
any(UnsafeBuffer.class),
eq(initialTermOffset + HEADER_LENGTH),
eq(DATA.length),
any(Header.class));
final InOrder inOrder = Mockito.inOrder(position);
inOrder.verify(position).setOrdered(initialPosition);
inOrder.verify(position).setOrdered(initialPosition + ALIGNED_FRAME_LENGTH);
}
@Test
public void shouldPollNoFragmentsToControlledFragmentHandler()
{
final Image image = createImage();
final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);
assertThat(fragmentsRead, is(0));
verify(position, never()).setOrdered(anyLong());
verify(mockControlledFragmentHandler, never()).onFragment(
any(UnsafeBuffer.class), anyInt(), anyInt(), any(Header.class));
}
@Test
public void shouldPollOneFragmentToControlledFragmentHandlerOnContinue()
{
final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);
position.setOrdered(initialPosition);
final Image image = createImage();
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));
when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))
.thenReturn(Action.CONTINUE);
final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);
assertThat(fragmentsRead, is(1));
final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);
inOrder.verify(mockControlledFragmentHandler).onFragment(
any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));
inOrder.verify(position).setOrdered(initialPosition + ALIGNED_FRAME_LENGTH);
}
@Test
public void shouldUpdatePositionOnRethrownExceptionInControlledPoll()
{
final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);
position.setOrdered(initialPosition);
final Image image = createImage();
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));
when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))
.thenThrow(new RuntimeException());
doThrow(new RuntimeException()).when(errorHandler).onError(any());
boolean thrown = false;
try
{
image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);
}
catch (final Exception ignore)
{
thrown = true;
}
assertTrue(thrown);
assertThat(image.position(), is(initialPosition + ALIGNED_FRAME_LENGTH));
verify(mockControlledFragmentHandler).onFragment(
any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));
}
@Test
public void shouldUpdatePositionOnRethrownExceptionInPoll()
{
final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);
position.setOrdered(initialPosition);
final Image image = createImage();
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));
doThrow(new RuntimeException()).when(mockFragmentHandler)
.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class));
doThrow(new RuntimeException()).when(errorHandler).onError(any());
boolean thrown = false;
try
{
image.poll(mockFragmentHandler, Integer.MAX_VALUE);
}
catch (final Exception ignore)
{
thrown = true;
}
assertTrue(thrown);
assertThat(image.position(), is(initialPosition + ALIGNED_FRAME_LENGTH));
verify(mockFragmentHandler).onFragment(
any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));
}
@Test
public void shouldNotPollOneFragmentToControlledFragmentHandlerOnAbort()
{
final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);
position.setOrdered(initialPosition);
final Image image = createImage();
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));
when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))
.thenReturn(Action.ABORT);
final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);
assertThat(fragmentsRead, is(0));
assertThat(image.position(), is(initialPosition));
verify(mockControlledFragmentHandler).onFragment(
any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));
}
@Test
public void shouldPollOneFragmentToControlledFragmentHandlerOnBreak()
{
final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);
position.setOrdered(initialPosition);
final Image image = createImage();
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));
when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))
.thenReturn(Action.BREAK);
final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);
assertThat(fragmentsRead, is(1));
final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);
inOrder.verify(mockControlledFragmentHandler).onFragment(
any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));
inOrder.verify(position).setOrdered(initialPosition + ALIGNED_FRAME_LENGTH);
}
@Test
public void shouldPollFragmentsToControlledFragmentHandlerOnCommit()
{
final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);
position.setOrdered(initialPosition);
final Image image = createImage();
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));
when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))
.thenReturn(Action.COMMIT);
final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);
assertThat(fragmentsRead, is(2));
final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);
inOrder.verify(mockControlledFragmentHandler).onFragment(
any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));
inOrder.verify(position).setOrdered(initialPosition + ALIGNED_FRAME_LENGTH);
inOrder.verify(mockControlledFragmentHandler).onFragment(
any(UnsafeBuffer.class), eq(ALIGNED_FRAME_LENGTH + HEADER_LENGTH), eq(DATA.length), any(Header.class));
inOrder.verify(position).setOrdered(initialPosition + (ALIGNED_FRAME_LENGTH * 2));
}
@Test
public void shouldUpdatePositionToEndOfCommittedFragmentOnCommit()
{
final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);
position.setOrdered(initialPosition);
final Image image = createImage();
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(2));
when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))
.thenReturn(Action.CONTINUE, Action.COMMIT, Action.CONTINUE);
final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);
assertThat(fragmentsRead, is(3));
final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);
// first fragment, continue
inOrder.verify(mockControlledFragmentHandler).onFragment(
any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));
// second fragment, commit
inOrder.verify(mockControlledFragmentHandler).onFragment(
any(UnsafeBuffer.class),
eq(ALIGNED_FRAME_LENGTH + HEADER_LENGTH),
eq(DATA.length),
any(Header.class));
inOrder.verify(position).setOrdered(initialPosition + (ALIGNED_FRAME_LENGTH * 2));
// third fragment, continue, but position is updated because last
inOrder.verify(mockControlledFragmentHandler).onFragment(
any(UnsafeBuffer.class),
eq(2 * ALIGNED_FRAME_LENGTH + HEADER_LENGTH),
eq(DATA.length),
any(Header.class));
inOrder.verify(position).setOrdered(initialPosition + (ALIGNED_FRAME_LENGTH * 3));
}
@Test
public void shouldPollFragmentsToControlledFragmentHandlerOnContinue()
{
final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);
position.setOrdered(initialPosition);
final Image image = createImage();
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));
insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));
when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))
.thenReturn(Action.CONTINUE);
final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);
assertThat(fragmentsRead, is(2));
final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);
inOrder.verify(mockControlledFragmentHandler).onFragment(
any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));
inOrder.verify(mockControlledFragmentHandler).onFragment(
any(UnsafeBuffer.class), eq(ALIGNED_FRAME_LENGTH + HEADER_LENGTH), eq(DATA.length), any(Header.class));
inOrder.verify(position).setOrdered(initialPosition + (ALIGNED_FRAME_LENGTH * 2));
}
private Image createImage()
{
return new Image(subscription, SESSION_ID, position, logBuffers, errorHandler, SOURCE_IDENTITY, CORRELATION_ID);
}
private void insertDataFrame(final int activeTermId, final int termOffset)
{
dataHeader
.termId(INITIAL_TERM_ID)
.streamId(STREAM_ID)
.sessionId(SESSION_ID)
.termOffset(termOffset)
.frameLength(DATA.length + HEADER_LENGTH)
.headerType(HeaderFlyweight.HDR_TYPE_DATA)
.flags(DataHeaderFlyweight.BEGIN_AND_END_FLAGS)
.version(HeaderFlyweight.CURRENT_VERSION);
rcvBuffer.putBytes(dataHeader.dataOffset(), DATA);
final int activeIndex = indexByTerm(INITIAL_TERM_ID, activeTermId);
TermRebuilder.insert(termBuffers[activeIndex], termOffset, rcvBuffer, ALIGNED_FRAME_LENGTH);
}
private static int offsetForFrame(final int index)
{
return index * ALIGNED_FRAME_LENGTH;
}
}