/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2014-2015 ForgeRock AS. */ package org.opends.server.replication.server.changelog.file; import static org.assertj.core.api.Assertions.*; import static org.opends.server.replication.server.changelog.api.DBCursor.KeyMatchingStrategy.*; import static org.opends.server.replication.server.changelog.api.DBCursor.PositionStrategy.*; import static org.opends.server.replication.server.changelog.file.BlockLogReader.*; import java.io.File; import java.io.FileNotFoundException; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.assertj.core.api.SoftAssertions; import org.forgerock.opendj.ldap.ByteSequenceReader; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.ByteStringBuilder; import org.forgerock.util.Pair; import org.opends.server.DirectoryServerTestCase; import org.opends.server.TestCaseUtils; import org.opends.server.replication.server.changelog.api.ChangelogException; import org.opends.server.replication.server.changelog.api.DBCursor.KeyMatchingStrategy; import org.opends.server.replication.server.changelog.api.DBCursor.PositionStrategy; import org.opends.server.util.StaticUtils; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @SuppressWarnings("javadoc") public class BlockLogReaderWriterTest extends DirectoryServerTestCase { private static final File TEST_DIRECTORY = new File(TestCaseUtils.getUnitTestRootPath(), "changelog-unit"); private static final File TEST_FILE = new File(TEST_DIRECTORY, "file"); private static final RecordParser<Integer, Integer> RECORD_PARSER = new IntRecordParser(); private static final int INT_RECORD_SIZE = 12; @BeforeClass void createTestDirectory() { TEST_DIRECTORY.mkdirs(); } @BeforeMethod void ensureTestFileIsEmpty() throws Exception { StaticUtils.recursiveDelete(TEST_FILE); } @AfterClass void cleanTestDirectory() { StaticUtils.recursiveDelete(TEST_DIRECTORY); } @DataProvider(name = "recordsData") Object[][] recordsData() { return new Object[][] { // raw size taken by each record is: 4 (record size) + 4 (key) + 4 (value) = 12 bytes // size of block, expected size of file after all records are written, records { 12, 12, records(1) }, // zero block marker { 10, 16, records(1) }, // one block marker { 8, 16, records(1) }, // one block marker { 7, 20, records(1) }, // two block markers { 6, 24, records(1) }, // three block markers { 5, 40, records(1) }, // seven block markers { 16, 28, records(1,2) }, // one block marker { 12, 32, records(1,2) }, // two block markers { 10, 36, records(1,2) }, // three block markers }; } /** * Tests that records can be written then read correctly for different block sizes. */ @Test(dataProvider="recordsData") public void testWriteThenRead(int blockSize, int expectedSizeOfFile, List<Record<Integer, Integer>> records) throws Exception { writeRecords(blockSize, records); try (BlockLogReader<Integer, Integer> reader = newReader(blockSize)) { for (int i = 0; i < records.size(); i++) { Record<Integer, Integer> record = reader.readRecord(); assertThat(record).isEqualTo(records.get(i)); } assertThat(reader.readRecord()).isNull(); assertThat(reader.getFilePosition()).isEqualTo(expectedSizeOfFile); } } @DataProvider(name = "recordsForSeek") Object[][] recordsForSeek() { Object[][] data = new Object[][] { // records, key, key matching strategy, position strategy, expectedRecord, should be found ? // no record { records(), 1, EQUAL_TO_KEY, ON_MATCHING_KEY, null, false }, { records(), 1, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, false }, { records(), 1, LESS_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, null, false }, // 1 record { records(1), 0, EQUAL_TO_KEY, ON_MATCHING_KEY, null, false }, { records(1), 1, EQUAL_TO_KEY, ON_MATCHING_KEY, record(1), true }, { records(1), 0, EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, false }, { records(1), 1, EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, true }, { records(1), 0, GREATER_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(1), true }, { records(1), 1, GREATER_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(1), true }, { records(1), 0, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(1), true }, { records(1), 1, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, true }, // 3 records equal matching { records(1,2,3), 0, EQUAL_TO_KEY, ON_MATCHING_KEY, null, false }, { records(1,2,3), 1, EQUAL_TO_KEY, ON_MATCHING_KEY, record(1), true }, { records(1,2,3), 2, EQUAL_TO_KEY, ON_MATCHING_KEY, record(2), true }, { records(1,2,3), 3, EQUAL_TO_KEY, ON_MATCHING_KEY, record(3), true }, { records(1,2,3), 4, EQUAL_TO_KEY, ON_MATCHING_KEY, null, false }, { records(1,2,3), 0, EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, false }, { records(1,2,3), 2, EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(3), true }, { records(1,2,3), 3, EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, true }, // 3 records less than or equal matching { records(1,2,3), 0, LESS_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, null, false }, { records(1,2,3), 1, LESS_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(1), true }, { records(1,2,3), 2, LESS_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(2), true }, { records(1,2,3), 3, LESS_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(3), true }, { records(1,2,3), 4, LESS_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(3), true }, { records(1,2,3), 0, LESS_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(1), true }, { records(1,2,3), 1, LESS_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(2), true }, { records(1,2,3), 2, LESS_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(3), true }, { records(1,2,3), 3, LESS_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, true }, { records(1,2,3), 4, LESS_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, true }, // 3 records greater or equal matching { records(1,2,3), 0, GREATER_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(1), true }, { records(1,2,3), 2, GREATER_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(2), true }, { records(1,2,3), 3, GREATER_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(3), true }, { records(1,2,3), 0, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(1), true }, { records(1,2,3), 1, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(2), true }, { records(1,2,3), 2, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(3), true }, { records(1,2,3), 3, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, true }, { records(1,2,3), 4, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, false }, // 10 records equal matching { records(1,2,3,4,5,6,7,8,9,10), 0, EQUAL_TO_KEY, ON_MATCHING_KEY, null, false }, { records(1,2,3,4,5,6,7,8,9,10), 1, EQUAL_TO_KEY, ON_MATCHING_KEY, record(1), true }, { records(1,2,3,4,5,6,7,8,9,10), 5, EQUAL_TO_KEY, ON_MATCHING_KEY, record(5), true }, { records(1,2,3,4,5,7,8,9,10), 6, EQUAL_TO_KEY, ON_MATCHING_KEY, null, false }, { records(1,2,3,4,5,6,7,8,9,10), 10, EQUAL_TO_KEY, ON_MATCHING_KEY, record(10), true }, { records(1,2,3,4,5,6,7,8,9,10), 11, EQUAL_TO_KEY, ON_MATCHING_KEY, null, false }, { records(1,2,3,4,5,6,7,8,9,10), 0, EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, false }, { records(1,2,3,4,5,6,7,8,9,10), 1, EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(2), true }, { records(1,2,3,4,5,6,7,8,9,10), 5, EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(6), true }, { records(1,2,3,4,5,7,8,9,10), 6, EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, false }, { records(1,2,3,4,5,6,7,8,9,10), 10, EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, true }, { records(1,2,3,4,5,6,7,8,9,10), 11, EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, false }, // 10 records less than or equal matching { records(1,2,3,4,5,6,7,8,9,10), 0, LESS_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, null, false }, { records(1,2,3,4,5,6,7,8,9,10), 1, LESS_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(1), true }, { records(1,2,3,4,5,6,7,8,9,10), 5, LESS_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(5), true }, { records(1,2,3,4,5,7,8,9,10), 6, LESS_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(5), true }, { records(1,2,3,4,5,6,7,8,9,10), 10, LESS_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(10), true }, { records(1,2,3,4,5,6,7,8,9,10), 11, LESS_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(10), true }, { records(1,2,3,4,5,6,7,8,9,10), 0, LESS_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(1), true }, { records(1,2,3,4,5,6,7,8,9,10), 1, LESS_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(2), true }, { records(1,2,3,4,5,6,7,8,9,10), 5, LESS_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(6), true }, { records(1,2,3,4,5,7,8,9,10), 6, LESS_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(7), true }, { records(1,2,3,4,5,6,7,8,9,10), 10, LESS_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, true }, { records(1,2,3,4,5,6,7,8,9,10), 11, LESS_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, true }, // 10 records greater or equal matching { records(1,2,3,4,5,6,7,8,9,10), 0, GREATER_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(1), true }, { records(1,2,3,4,5,6,7,8,9,10), 1, GREATER_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(1), true }, { records(1,2,3,4,5,6,7,8,9,10), 5, GREATER_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(5), true }, { records(1,2,3,4,5,7,8,9,10), 6, GREATER_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(7), true }, { records(1,2,3,4,5,6,7,8,9,10), 10, GREATER_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, record(10), true }, { records(1,2,3,4,5,6,7,8,9,10), 11, GREATER_THAN_OR_EQUAL_TO_KEY, ON_MATCHING_KEY, null, false }, { records(1,2,3,4,5,6,7,8,9,10), 0, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(1), true }, { records(1,2,3,4,5,6,7,8,9,10), 1, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(2), true }, { records(1,2,3,4,5,6,7,8,9,10), 5, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(6), true }, { records(1,2,3,4,5,7,8,9,10), 6, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, record(7), true }, { records(1,2,3,4,5,6,7,8,9,10), 10, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, true }, { records(1,2,3,4,5,6,7,8,9,10), 11, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY, null, false }, }; // For each test case, do a test with various block sizes to ensure algorithm is not broken // on a given size int[] sizes = new int[] { 500, 100, 50, 30, 25, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10 }; Object[][] finalData = new Object[sizes.length * data.length][7]; for (int i = 0; i < data.length; i++) { for (int j = 0; j < sizes.length; j++) { Object[] a = data[i]; // add the block size at beginning of each test case finalData[sizes.length*i+j] = new Object[] { sizes[j], a[0], a[1], a[2], a[3], a[4], a[5]}; } } return finalData; } @Test(dataProvider = "recordsForSeek") public void testSeekToRecord(int blockSize, List<Record<Integer, Integer>> records, int key, KeyMatchingStrategy matchingStrategy, PositionStrategy positionStrategy, Record<Integer, Integer> expectedRecord, boolean shouldBeFound) throws Exception { writeRecords(blockSize, records); try (BlockLogReader<Integer, Integer> reader = newReader(blockSize)) { Pair<Boolean, Record<Integer, Integer>> result = reader.seekToRecord(key, matchingStrategy, positionStrategy); final SoftAssertions softly = new SoftAssertions(); softly.assertThat(result.getFirst()).isEqualTo(shouldBeFound); softly.assertThat(result.getSecond()).isEqualTo(expectedRecord); softly.assertAll(); } } @Test public void testGetClosestMarkerBeforeOrAtPosition() throws Exception { final int blockSize = 10; BlockLogReader<Integer, Integer> reader = newReaderWithNullFile(blockSize); assertThat(reader.getClosestBlockStartBeforeOrAtPosition(0)).isEqualTo(0); assertThat(reader.getClosestBlockStartBeforeOrAtPosition(5)).isEqualTo(0); assertThat(reader.getClosestBlockStartBeforeOrAtPosition(9)).isEqualTo(0); assertThat(reader.getClosestBlockStartBeforeOrAtPosition(10)).isEqualTo(10); assertThat(reader.getClosestBlockStartBeforeOrAtPosition(15)).isEqualTo(10); assertThat(reader.getClosestBlockStartBeforeOrAtPosition(20)).isEqualTo(20); } @Test public void testGetClosestMarkerStrictlyAfterPosition() throws Exception { final int blockSize = 10; BlockLogReader<Integer, Integer> reader = newReaderWithNullFile(blockSize); assertThat(reader.getClosestBlockStartStrictlyAfterPosition(0)).isEqualTo(10); assertThat(reader.getClosestBlockStartStrictlyAfterPosition(5)).isEqualTo(10); assertThat(reader.getClosestBlockStartStrictlyAfterPosition(10)).isEqualTo(20); assertThat(reader.getClosestBlockStartStrictlyAfterPosition(11)).isEqualTo(20); assertThat(reader.getClosestBlockStartStrictlyAfterPosition(15)).isEqualTo(20); assertThat(reader.getClosestBlockStartStrictlyAfterPosition(20)).isEqualTo(30); } @Test public void testSearchClosestMarkerToKey() throws Exception { int blockSize = 20; writeRecords(blockSize, records(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)); try (BlockLogReader<Integer, Integer> reader = newReader(blockSize)) { assertThat(reader.searchClosestBlockStartToKey(0)).isEqualTo(0); assertThat(reader.searchClosestBlockStartToKey(1)).isEqualTo(0); assertThat(reader.searchClosestBlockStartToKey(2)).isEqualTo(20); assertThat(reader.searchClosestBlockStartToKey(3)).isEqualTo(20); assertThat(reader.searchClosestBlockStartToKey(4)).isEqualTo(40); assertThat(reader.searchClosestBlockStartToKey(5)).isEqualTo(60); assertThat(reader.searchClosestBlockStartToKey(6)).isEqualTo(80); assertThat(reader.searchClosestBlockStartToKey(7)).isEqualTo(80); assertThat(reader.searchClosestBlockStartToKey(8)).isEqualTo(100); assertThat(reader.searchClosestBlockStartToKey(9)).isEqualTo(120); assertThat(reader.searchClosestBlockStartToKey(10)).isEqualTo(140); assertThat(reader.searchClosestBlockStartToKey(19)).isEqualTo(260); assertThat(reader.searchClosestBlockStartToKey(20)).isEqualTo(280); // out of reach keys assertThat(reader.searchClosestBlockStartToKey(21)).isEqualTo(280); assertThat(reader.searchClosestBlockStartToKey(22)).isEqualTo(280); } } @Test public void testLengthOfStoredRecord() throws Exception { final int blockSize = 100; BlockLogReader<Integer, Integer> reader = newReaderWithNullFile(blockSize); int recordLength = 10; assertThat(reader.getLengthOfStoredRecord(recordLength, 99)).isEqualTo(recordLength); assertThat(reader.getLengthOfStoredRecord(recordLength, 20)).isEqualTo(recordLength); assertThat(reader.getLengthOfStoredRecord(recordLength, 10)).isEqualTo(recordLength); assertThat(reader.getLengthOfStoredRecord(recordLength, 9)).isEqualTo(recordLength + SIZE_OF_BLOCK_OFFSET); assertThat(reader.getLengthOfStoredRecord(recordLength, 0)).isEqualTo(recordLength + SIZE_OF_BLOCK_OFFSET); recordLength = 150; assertThat(reader.getLengthOfStoredRecord(recordLength, 99)).isEqualTo(recordLength + SIZE_OF_BLOCK_OFFSET); assertThat(reader.getLengthOfStoredRecord(recordLength, 60)).isEqualTo(recordLength + SIZE_OF_BLOCK_OFFSET); assertThat(reader.getLengthOfStoredRecord(recordLength, 54)).isEqualTo(recordLength + SIZE_OF_BLOCK_OFFSET); assertThat(reader.getLengthOfStoredRecord(recordLength, 53)).isEqualTo(recordLength + 2 * SIZE_OF_BLOCK_OFFSET); assertThat(reader.getLengthOfStoredRecord(recordLength, 0)).isEqualTo(recordLength + 2 * SIZE_OF_BLOCK_OFFSET); recordLength = 200; assertThat(reader.getLengthOfStoredRecord(recordLength, 99)).isEqualTo(recordLength + 2 * SIZE_OF_BLOCK_OFFSET); assertThat(reader.getLengthOfStoredRecord(recordLength, 8)).isEqualTo(recordLength + 2 * SIZE_OF_BLOCK_OFFSET); assertThat(reader.getLengthOfStoredRecord(recordLength, 7)).isEqualTo(recordLength + 3 * SIZE_OF_BLOCK_OFFSET); assertThat(reader.getLengthOfStoredRecord(recordLength, 0)).isEqualTo(recordLength + 3 * SIZE_OF_BLOCK_OFFSET); } /** * This test is intended to be run only manually to check the performance between binary search * and sequential access. * Note that sequential run may be extremely long when using high values. */ @Test(enabled=false) public void seekPerformanceComparison() throws Exception { // You may change these values long fileSizeInBytes = 100*1024*1024; int numberOfValuesToSeek = 50000; int blockSize = 256; writeRecordsToReachFileSize(blockSize, fileSizeInBytes); try (BlockLogReader<Integer, Integer> reader = newReader(blockSize)) { List<Integer> keysToSeek = getShuffledKeys(fileSizeInBytes, numberOfValuesToSeek); System.out.println("File size: " + TEST_FILE.length() + " bytes"); System.out.println("\n---- BINARY SEARCH"); long minTime = Long.MAX_VALUE; long maxTime = Long.MIN_VALUE; final long t0 = System.nanoTime(); for (Integer key : keysToSeek) { final long ts = System.nanoTime(); Pair<Boolean, Record<Integer, Integer>> result = reader.seekToRecord(key, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY); final long te = System.nanoTime() - ts; if (te < minTime) { minTime = te; } if (te > maxTime) { maxTime = te; } // show time for seeks that last more than N microseconds (tune as needed) if (te/1000 > 1000) { System.out.println("TIME! key:" + result.getSecond().getKey() + ", time=" + te/1000 + " microseconds"); } assertThat(result.getSecond()).isEqualTo(record(key)); } System.out.println("Time taken: " + ((System.nanoTime() - t0)/1000000) + " milliseconds"); System.out.println("Min time for a search: " + minTime/1000 + " microseconds"); System.out.println("Max time for a search: " + maxTime/1000 + " microseconds"); System.out.println("Max difference for a search: " + (maxTime - minTime)/1000 + " microseconds"); System.out.println("\n---- SEQUENTIAL SEARCH"); minTime = Long.MAX_VALUE; maxTime = Long.MIN_VALUE; final long t1 = System.nanoTime(); for (Integer val : keysToSeek) { long ts = System.nanoTime(); Pair<Boolean, Record<Integer, Integer>> result = reader.positionToKey(0, val, GREATER_THAN_OR_EQUAL_TO_KEY, AFTER_MATCHING_KEY); assertThat(result.getSecond()).isEqualTo(Record.from(val, val)); long te = System.nanoTime() - ts; if (te < minTime) { minTime = te; } if (te > maxTime) { maxTime = te; } } System.out.println("Time taken: " + ((System.nanoTime() - t1)/1000000) + " milliseconds"); System.out.println("Min time for a search: " + minTime/1000 + " microseconds"); System.out.println("Max time for a search: " + maxTime/1000000 + " milliseconds"); System.out.println("Max difference for a search: " + (maxTime - minTime)/1000000 + " milliseconds"); } } /** Write provided records with the provided block size. */ private void writeRecords(int blockSize, List<Record<Integer, Integer>> records) throws ChangelogException { try (BlockLogWriter<Integer, Integer> writer = newWriter(blockSize)) { for (Record<Integer, Integer> record : records) { writer.write(record); } } } /** Write as many records as needed to reach provided file size. Records goes from 1 up to N. */ private void writeRecordsToReachFileSize(int blockSize, long sizeInBytes) throws Exception { final int numberOfValues = (int) sizeInBytes / INT_RECORD_SIZE; final int[] values = new int[numberOfValues]; for (int i = 0; i < numberOfValues; i++) { values[i] = i+1; } writeRecords(blockSize, records(values)); } /** Returns provided number of keys to seek in random order, for a file of provided size. */ private List<Integer> getShuffledKeys(long fileSizeInBytes, int numberOfKeys) { final int numberOfValues = (int) fileSizeInBytes / INT_RECORD_SIZE; final List<Integer> values = new ArrayList<>(numberOfValues); for (int i = 0; i < numberOfValues; i++) { values.add(i+1); } Collections.shuffle(values); return values.subList(0, numberOfKeys); } private BlockLogWriter<Integer, Integer> newWriter(int sizeOfBlock) throws ChangelogException { return BlockLogWriter.newWriterForTests(new LogWriter(TEST_FILE), RECORD_PARSER, sizeOfBlock); } private BlockLogReader<Integer, Integer> newReader(int blockSize) throws FileNotFoundException { return BlockLogReader.newReaderForTests(TEST_FILE, new RandomAccessFile(TEST_FILE, "r"), RECORD_PARSER, blockSize); } private BlockLogReader<Integer, Integer> newReaderWithNullFile(int blockSize) throws FileNotFoundException { return BlockLogReader.newReaderForTests(null, null, RECORD_PARSER, blockSize); } /** Helper to build a list of records. */ private List<Record<Integer, Integer>> records(int...keys) { List<Record<Integer, Integer>> records = new ArrayList<>(); for (int key : keys) { records.add(Record.from(key, key)); } return records; } /** Helper to build a record. */ private Record<Integer, Integer> record(int key) { return Record.from(key, key); } /** * Record parser implementation for records with keys and values as integers to be used in tests. * Using integer allow to know precisely the size of the records (4 bytes for key + 4 bytes for value), * which is useful for some tests. */ private static class IntRecordParser implements RecordParser<Integer, Integer> { @Override public Record<Integer, Integer> decodeRecord(final ByteString data) throws DecodingException { ByteSequenceReader reader = data.asReader(); int key = reader.readInt(); int value = reader.readInt(); return Record.from(key, value); } @Override public ByteString encodeRecord(Record<Integer, Integer> record) { return new ByteStringBuilder().appendInt(record.getKey()).appendInt(record.getValue()).toByteString(); } @Override public Integer decodeKeyFromString(String key) throws ChangelogException { return Integer.valueOf(key); } @Override public String encodeKeyToString(Integer key) { return String.valueOf(key); } @Override public Integer getMaxKey() { return Integer.MAX_VALUE; } } }