/* * 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.logbuffer; import io.aeron.ReservedValueSupplier; import org.junit.Before; import org.junit.Test; import org.mockito.InOrder; import org.agrona.concurrent.UnsafeBuffer; import static io.aeron.logbuffer.LogBufferDescriptor.TERM_TAIL_COUNTERS_OFFSET; import static io.aeron.logbuffer.LogBufferDescriptor.rawTailVolatile; import static io.aeron.logbuffer.TermAppender.pack; import static io.aeron.protocol.DataHeaderFlyweight.RESERVED_VALUE_OFFSET; import static io.aeron.protocol.DataHeaderFlyweight.createDefaultHeader; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.ByteOrder.LITTLE_ENDIAN; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.*; import static io.aeron.logbuffer.FrameDescriptor.*; import static io.aeron.logbuffer.TermAppender.TRIPPED; import static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH; import static org.agrona.BitUtil.*; public class TermAppenderTest { private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH; private static final int META_DATA_BUFFER_LENGTH = LogBufferDescriptor.LOG_META_DATA_LENGTH; private static final int MAX_FRAME_LENGTH = 1024; private static final int MAX_PAYLOAD_LENGTH = MAX_FRAME_LENGTH - HEADER_LENGTH; private static final int PARTITION_INDEX = 0; private static final int TERM_TAIL_COUNTER_OFFSET = TERM_TAIL_COUNTERS_OFFSET + (PARTITION_INDEX * SIZE_OF_LONG); private static final int TERM_ID = 7; private static final long RV = 7777L; private static final ReservedValueSupplier RVS = (termBuffer, termOffset, frameLength) -> RV; private static final UnsafeBuffer DEFAULT_HEADER = new UnsafeBuffer(allocateDirect(HEADER_LENGTH)); private final UnsafeBuffer termBuffer = spy(new UnsafeBuffer(allocateDirect(TERM_BUFFER_LENGTH))); private final UnsafeBuffer logMetaDataBuffer = new UnsafeBuffer(allocateDirect(META_DATA_BUFFER_LENGTH)); private final HeaderWriter headerWriter = spy(new HeaderWriter(createDefaultHeader(0, 0, TERM_ID))); private TermAppender termAppender; @Before public void setUp() { termAppender = new TermAppender(termBuffer, logMetaDataBuffer, PARTITION_INDEX); } @Test public void shouldPackResult() { final int termId = 7; final int termOffset = -1; final long result = pack(termId, termOffset); assertThat(TermAppender.termId(result), is(termId)); assertThat(TermAppender.termOffset(result), is(termOffset)); } @Test public void shouldAppendFrameToEmptyLog() { final int headerLength = DEFAULT_HEADER.capacity(); final UnsafeBuffer buffer = new UnsafeBuffer(new byte[128]); final int msgLength = 20; final int frameLength = msgLength + headerLength; final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT); final int tail = 0; logMetaDataBuffer.putLong(TERM_TAIL_COUNTER_OFFSET, pack(TERM_ID, tail)); assertThat(termAppender.appendUnfragmentedMessage( headerWriter, buffer, 0, msgLength, RVS), is((long)alignedFrameLength)); assertThat(rawTailVolatile(logMetaDataBuffer, PARTITION_INDEX), is(pack(TERM_ID, tail + alignedFrameLength))); final InOrder inOrder = inOrder(termBuffer, headerWriter); inOrder.verify(headerWriter, times(1)).write(termBuffer, tail, frameLength, TERM_ID); inOrder.verify(termBuffer, times(1)).putBytes(headerLength, buffer, 0, msgLength); inOrder.verify(termBuffer, times(1)).putLong(tail + RESERVED_VALUE_OFFSET, RV, LITTLE_ENDIAN); inOrder.verify(termBuffer, times(1)).putIntOrdered(tail, frameLength); } @Test public void shouldAppendFrameTwiceToLog() { final int headerLength = DEFAULT_HEADER.capacity(); final UnsafeBuffer buffer = new UnsafeBuffer(new byte[128]); final int msgLength = 20; final int frameLength = msgLength + headerLength; final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT); int tail = 0; logMetaDataBuffer.putLong(TERM_TAIL_COUNTER_OFFSET, pack(TERM_ID, tail)); assertThat(termAppender.appendUnfragmentedMessage( headerWriter, buffer, 0, msgLength, RVS), is((long)alignedFrameLength)); assertThat(termAppender.appendUnfragmentedMessage( headerWriter, buffer, 0, msgLength, RVS), is((long)alignedFrameLength * 2)); assertThat(rawTailVolatile(logMetaDataBuffer, PARTITION_INDEX), is(pack(TERM_ID, tail + (alignedFrameLength * 2)))); final InOrder inOrder = inOrder(termBuffer, headerWriter); inOrder.verify(headerWriter, times(1)).write(termBuffer, tail, frameLength, TERM_ID); inOrder.verify(termBuffer, times(1)).putBytes(headerLength, buffer, 0, msgLength); inOrder.verify(termBuffer, times(1)).putLong(tail + RESERVED_VALUE_OFFSET, RV, LITTLE_ENDIAN); inOrder.verify(termBuffer, times(1)).putIntOrdered(tail, frameLength); tail = alignedFrameLength; inOrder.verify(headerWriter, times(1)).write(termBuffer, tail, frameLength, TERM_ID); inOrder.verify(termBuffer, times(1)).putBytes(tail + headerLength, buffer, 0, msgLength); inOrder.verify(termBuffer, times(1)).putLong(tail + RESERVED_VALUE_OFFSET, RV, LITTLE_ENDIAN); inOrder.verify(termBuffer, times(1)).putIntOrdered(tail, frameLength); } @Test public void shouldPadLogAndTripWhenAppendingWithInsufficientRemainingCapacity() { final int msgLength = 120; final int headerLength = DEFAULT_HEADER.capacity(); final int requiredFrameSize = align(headerLength + msgLength, FRAME_ALIGNMENT); final int tailValue = TERM_BUFFER_LENGTH - align(msgLength, FRAME_ALIGNMENT); final UnsafeBuffer buffer = new UnsafeBuffer(new byte[128]); final int frameLength = TERM_BUFFER_LENGTH - tailValue; logMetaDataBuffer.putLong(TERM_TAIL_COUNTER_OFFSET, pack(TERM_ID, tailValue)); final long expectResult = pack(TERM_ID, TRIPPED); assertThat(termAppender.appendUnfragmentedMessage(headerWriter, buffer, 0, msgLength, RVS), is(expectResult)); assertThat(rawTailVolatile(logMetaDataBuffer, PARTITION_INDEX), is(pack(TERM_ID, tailValue + requiredFrameSize))); final InOrder inOrder = inOrder(termBuffer, headerWriter); inOrder.verify(headerWriter, times(1)).write(termBuffer, tailValue, frameLength, TERM_ID); inOrder.verify(termBuffer, times(1)).putShort(typeOffset(tailValue), (short)PADDING_FRAME_TYPE, LITTLE_ENDIAN); inOrder.verify(termBuffer, times(1)).putIntOrdered(tailValue, frameLength); } @Test public void shouldFragmentMessageOverTwoFrames() { final int msgLength = MAX_PAYLOAD_LENGTH + 1; final int headerLength = DEFAULT_HEADER.capacity(); final int frameLength = headerLength + 1; final int requiredCapacity = align(headerLength + 1, FRAME_ALIGNMENT) + MAX_FRAME_LENGTH; final UnsafeBuffer buffer = new UnsafeBuffer(new byte[msgLength]); int tail = 0; logMetaDataBuffer.putLong(TERM_TAIL_COUNTER_OFFSET, pack(TERM_ID, tail)); assertThat(termAppender.appendFragmentedMessage( headerWriter, buffer, 0, msgLength, MAX_PAYLOAD_LENGTH, RVS), is((long)requiredCapacity)); assertThat(rawTailVolatile(logMetaDataBuffer, PARTITION_INDEX), is(pack(TERM_ID, tail + requiredCapacity))); final InOrder inOrder = inOrder(termBuffer, headerWriter); inOrder.verify(headerWriter, times(1)).write(termBuffer, tail, MAX_FRAME_LENGTH, TERM_ID); inOrder.verify(termBuffer, times(1)).putBytes(tail + headerLength, buffer, 0, MAX_PAYLOAD_LENGTH); inOrder.verify(termBuffer, times(1)).putByte(flagsOffset(tail), BEGIN_FRAG_FLAG); inOrder.verify(termBuffer, times(1)).putLong(tail + RESERVED_VALUE_OFFSET, RV, LITTLE_ENDIAN); inOrder.verify(termBuffer, times(1)).putIntOrdered(tail, MAX_FRAME_LENGTH); tail = MAX_FRAME_LENGTH; inOrder.verify(headerWriter, times(1)).write(termBuffer, tail, frameLength, TERM_ID); inOrder.verify(termBuffer, times(1)).putBytes(tail + headerLength, buffer, MAX_PAYLOAD_LENGTH, 1); inOrder.verify(termBuffer, times(1)).putByte(flagsOffset(tail), END_FRAG_FLAG); inOrder.verify(termBuffer, times(1)).putLong(tail + RESERVED_VALUE_OFFSET, RV, LITTLE_ENDIAN); inOrder.verify(termBuffer, times(1)).putIntOrdered(tail, frameLength); } @Test public void shouldClaimRegionForZeroCopyEncoding() { final int headerLength = DEFAULT_HEADER.capacity(); final int msgLength = 20; final int frameLength = msgLength + headerLength; final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT); final int tail = 0; final BufferClaim bufferClaim = new BufferClaim(); logMetaDataBuffer.putLong(TERM_TAIL_COUNTER_OFFSET, pack(TERM_ID, tail)); assertThat(termAppender.claim(headerWriter, msgLength, bufferClaim), is((long)alignedFrameLength)); assertThat(bufferClaim.offset(), is(tail + headerLength)); assertThat(bufferClaim.length(), is(msgLength)); assertThat(rawTailVolatile(logMetaDataBuffer, PARTITION_INDEX), is(pack(TERM_ID, tail + alignedFrameLength))); // Map flyweight or encode to buffer directly then call commit() when done bufferClaim.commit(); final InOrder inOrder = inOrder(headerWriter); inOrder.verify(headerWriter, times(1)).write(termBuffer, tail, frameLength, TERM_ID); } }