/* * Copyright 2014-2020 Real Logic Limited. * * 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 * * https://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.archive; import io.aeron.Counter; import io.aeron.Image; import io.aeron.Subscription; import io.aeron.archive.client.AeronArchive; import io.aeron.logbuffer.BlockHandler; import io.aeron.logbuffer.FrameDescriptor; import io.aeron.logbuffer.LogBufferDescriptor; import io.aeron.protocol.DataHeaderFlyweight; import org.agrona.CloseHelper; import org.agrona.IoUtil; import org.agrona.concurrent.EpochClock; import org.agrona.concurrent.UnsafeBuffer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.File; import java.nio.channels.FileChannel; import static io.aeron.Aeron.NULL_VALUE; import static io.aeron.archive.Archive.segmentFileName; import static io.aeron.archive.client.AeronArchive.NULL_POSITION; import static java.nio.file.StandardOpenOption.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; public class RecordingSessionTest { private static final int RECORDED_BLOCK_LENGTH = 100; private static final long RECORDING_ID = 12345; private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH; private static final int SEGMENT_LENGTH = TERM_BUFFER_LENGTH; private static final String CHANNEL = "channel"; private static final String SOURCE_IDENTITY = "sourceIdentity"; private static final int STREAM_ID = 54321; private static final int SESSION_ID = 12345; private static final int TERM_OFFSET = 1024; private static final int MTU_LENGTH = 1024; private static final long START_POSITION = TERM_OFFSET; private static final int INITIAL_TERM_ID = 0; private static final FileChannel ARCHIVE_CHANNEL = null; private static final ControlSession CONTROL_SESSION = null; private final RecordingEventsProxy recordingEventsProxy = mock(RecordingEventsProxy.class); private final Counter mockPosition = mock(Counter.class); private final Image image = mockImage(mockSubscription()); private final File archiveDir = ArchiveTests.makeTestDirectory(); private FileChannel mockLogBufferChannel; private UnsafeBuffer mockLogBufferMapped; private File termFile; private final EpochClock epochClock = mock(EpochClock.class); private Archive.Context context; private long positionLong; @BeforeEach public void before() throws Exception { when(mockPosition.getWeak()).then((invocation) -> positionLong); when(mockPosition.get()).then((invocation) -> positionLong); doAnswer( (invocation) -> { positionLong = invocation.getArgument(0); return null; }) .when(mockPosition).setOrdered(anyLong()); termFile = File.createTempFile("test.rec", "sourceIdentity"); mockLogBufferChannel = FileChannel.open(termFile.toPath(), CREATE, READ, WRITE); mockLogBufferMapped = new UnsafeBuffer( mockLogBufferChannel.map(FileChannel.MapMode.READ_WRITE, 0, TERM_BUFFER_LENGTH)); final DataHeaderFlyweight headerFlyweight = new DataHeaderFlyweight(); headerFlyweight.wrap(mockLogBufferMapped, TERM_OFFSET, DataHeaderFlyweight.HEADER_LENGTH); headerFlyweight .termOffset(TERM_OFFSET) .sessionId(SESSION_ID) .streamId(STREAM_ID) .headerType(DataHeaderFlyweight.HDR_TYPE_DATA) .frameLength(RECORDED_BLOCK_LENGTH); context = new Archive.Context() .segmentFileLength(SEGMENT_LENGTH) .archiveDir(archiveDir) .epochClock(epochClock); } @AfterEach public void after() { IoUtil.unmap(mockLogBufferMapped.byteBuffer()); CloseHelper.close(mockLogBufferChannel); IoUtil.delete(archiveDir, false); IoUtil.delete(termFile, false); } @Test public void shouldRecordFragmentsFromImage() { final RecordingSession session = new RecordingSession( NULL_VALUE, RECORDING_ID, START_POSITION, SEGMENT_LENGTH, CHANNEL, recordingEventsProxy, image, mockPosition, ARCHIVE_CHANNEL, context, CONTROL_SESSION, null, null, false); assertEquals(RECORDING_ID, session.sessionId()); session.doWork(); when(image.blockPoll(any(), anyInt())).thenAnswer( (invocation) -> { final BlockHandler handle = invocation.getArgument(0); if (handle == null) { return 0; } handle.onBlock( mockLogBufferMapped, TERM_OFFSET, RECORDED_BLOCK_LENGTH, SESSION_ID, 0); return RECORDED_BLOCK_LENGTH; }); assertNotEquals(0, session.doWork(), "Expect some work"); final File segmentFile = new File(archiveDir, segmentFileName(RECORDING_ID, 0)); assertTrue(segmentFile.exists()); final RecordingSummary recordingSummary = new RecordingSummary(); recordingSummary.recordingId = RECORDING_ID; recordingSummary.startPosition = START_POSITION; recordingSummary.segmentFileLength = context.segmentFileLength(); recordingSummary.initialTermId = INITIAL_TERM_ID; recordingSummary.termBufferLength = TERM_BUFFER_LENGTH; recordingSummary.streamId = STREAM_ID; recordingSummary.sessionId = SESSION_ID; recordingSummary.stopPosition = START_POSITION + RECORDED_BLOCK_LENGTH; try (RecordingReader reader = new RecordingReader( recordingSummary, archiveDir, NULL_POSITION, AeronArchive.NULL_LENGTH)) { final int fragments = reader.poll( (buffer, offset, length, frameType, flags, reservedValue) -> { final int frameOffset = offset - DataHeaderFlyweight.HEADER_LENGTH; assertEquals(TERM_OFFSET, frameOffset); assertEquals(RECORDED_BLOCK_LENGTH, FrameDescriptor.frameLength(buffer, frameOffset)); assertEquals(RECORDED_BLOCK_LENGTH - DataHeaderFlyweight.HEADER_LENGTH, length); }, 1); assertEquals(1, fragments); } when(image.blockPoll(any(), anyInt())).thenReturn(0); assertEquals(0, session.doWork(), "Expect no work"); when(image.isClosed()).thenReturn(true); session.doWork(); session.doWork(); assertTrue(session.isDone()); session.close(); } private static Subscription mockSubscription() { final Subscription subscription = mock(Subscription.class); when(subscription.channel()).thenReturn(CHANNEL); when(subscription.streamId()).thenReturn(STREAM_ID); return subscription; } private static Image mockImage(final Subscription subscription) { final Image image = mock(Image.class); when(image.sessionId()).thenReturn(SESSION_ID); when(image.initialTermId()).thenReturn(INITIAL_TERM_ID); when(image.sourceIdentity()).thenReturn(SOURCE_IDENTITY); when(image.termBufferLength()).thenReturn(TERM_BUFFER_LENGTH); when(image.mtuLength()).thenReturn(MTU_LENGTH); when(image.joinPosition()).thenReturn(START_POSITION); when(image.subscription()).thenReturn(subscription); return image; } }