/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.github.geophile.erdo.map.diskmap;
import com.github.geophile.erdo.TestFactory;
import com.github.geophile.erdo.TestKey;
import com.github.geophile.erdo.TestRecord;
import com.github.geophile.erdo.AbstractKey;
import com.github.geophile.erdo.RecordFactory;
import com.github.geophile.erdo.TransactionCallback;
import com.github.geophile.erdo.transaction.Transaction;
import com.github.geophile.erdo.transaction.TransactionManager;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static junit.framework.Assert.*;
public class DiskPageTest
{
@BeforeClass
public static void beforeClass()
{
pageSize = FACTORY.configuration().diskPageSizeBytes();
FACTORY.recordFactory(ERDO_ID, RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
Transaction.initialize(FACTORY);
}
@Before
public void before()
{
TestKey.testErdoId(ERDO_ID);
}
@After
public void after()
{
FACTORY.reset();
}
@Test
public void testFixedSizedRecords() throws Exception
{
for (int size = 1; size < pageSize / 2; size++) {
testSize(size);
}
}
@Test
public void testSearch() throws Exception
{
for (int nRecords = 0; nRecords < 100; nRecords++) {
testSearch(nRecords);
}
}
private void testSize(int size) throws Exception
{
ByteBuffer erdoIdBuffer = newDiskPageSectionBuffer();
ByteBuffer timestampBuffer = newDiskPageSectionBuffer();
ByteBuffer keyBuffer = newDiskPageSectionBuffer();
ByteBuffer recordBuffer = newDiskPageSectionBuffer();
DiskPage page = new DiskPage(FACTORY,
new PageId(0, 0),
0,
0,
erdoIdBuffer,
timestampBuffer,
keyBuffer,
recordBuffer);
int i = 0;
boolean appended;
byte[] bytes = new byte[size];
TransactionManager transactionManager = FACTORY.transactionManager();
do {
startTransaction();
Arrays.fill(bytes, (byte) (i % 256));
TestRecord record = TestRecord.createRecord(i, bytes);
AbstractKey key = record.key();
Transaction transaction = transactionManager.currentTransaction();
key.transaction(transaction);
assert key.transaction() == transaction;
transactionManager.commitTransaction(TransactionCallback.DO_NOTHING, null);
appended = page.append(record);
i++;
} while (appended);
page.close();
/* TODO: Disabled until disk page space optimization is done.
assertTrue(String.format("page.filledSize: %s, size: %s", page.filledSize(), size),
pageSize - page.filledSize() < keyAndRecordSize(size));
*/
page = readable(erdoIdBuffer, timestampBuffer, keyBuffer, recordBuffer);
startTransaction();
DiskPageCursor cursor = new DiskPageCursor(page);
TestRecord record;
i = 0;
while ((record = (TestRecord) cursor.next()) != null) {
Arrays.fill(bytes, (byte) (i % 256));
assertEquals(i, record.key().key());
compare(bytes, record.bytesValue());
i++;
}
transactionManager.commitTransaction(TransactionCallback.DO_NOTHING, null);
assertEquals(i, page.nRecords());
}
private DiskPage readable(ByteBuffer erdoIdBuffer,
ByteBuffer timestampBuffer,
ByteBuffer keyBuffer,
ByteBuffer recordBuffer)
{
ByteBuffer pageBuffer = ByteBuffer.allocate(FACTORY.configuration().diskPageSizeBytes());
erdoIdBuffer.flip();
pageBuffer.put(erdoIdBuffer);
timestampBuffer.flip();
pageBuffer.put(timestampBuffer);
keyBuffer.flip();
pageBuffer.put(keyBuffer);
recordBuffer.flip();
pageBuffer.put(recordBuffer);
return new DiskPage(FACTORY, new PageId(0, 0), 0, 0, erdoIdBuffer, timestampBuffer, keyBuffer, recordBuffer);
}
private void startTransaction()
{
FACTORY.transactionManager().currentTransaction(); // ensures transaction started
}
private void testSearch(int nRecords) throws Exception
{
byte[] value = new byte[]{0};
// Keys stored are 0, 2, 4, ..., 2 * (nRecords - 1)
DiskPage page =
new DiskPage(FACTORY,
new PageId(0, 0),
0,
0,
newDiskPageSectionBuffer(),
newDiskPageSectionBuffer(),
newDiskPageSectionBuffer(),
newDiskPageSectionBuffer());
TransactionManager transactionManager = FACTORY.transactionManager();
for (int i = 0; i < nRecords; i++) {
startTransaction();
int key = i * 2;
TestRecord record = TestRecord.createRecord(key, value);
Transaction transaction = transactionManager.currentTransaction();
record.key().transaction(transaction);
assert record.key().transaction() == transaction;
transactionManager.commitTransaction(TransactionCallback.DO_NOTHING, null);
boolean appended = page.append(record);
assertTrue(appended);
}
assertNotNull(page);
try {
page.close();
assertTrue(nRecords > 0);
} catch (AssertionError e) {
assertEquals(0, nRecords);
}
for (int key = -1; key <= 2 * nRecords - 1; key++) {
int position = page.recordNumber(new TestKey(key));
if (nRecords == 0) {
assertEquals(-1, position);
} else if (key < 0) {
assertEquals(-1, position);
} else if (key > 2 * (nRecords - 1)) {
assertEquals(-nRecords - 1, position);
} else if (key % 2 == 0) {
assertEquals(key / 2, position);
} else {
assertEquals(-(key / 2 + 1) - 1, position);
}
}
}
private int keyAndRecordSize(int valueSize)
{
// TestKey:
// 4: erdoId
// 2: timestamp (assuming 2-byte timestamp deltas on page)
// 4: int key value
// TestRecord:
// 1: type tag
// 4: size
// valueSize: value
// 4: index entries in key and record DiskPageSections
return 19 + valueSize;
}
private void compare(byte[] expected, byte[] actual)
{
assertEquals(expected.length, actual.length);
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], actual[i]);
}
}
private static void clearDirectory(File dir, String name)
{
dir.mkdirs();
File directory = new File(dir, name);
if (directory.exists()) {
deleteRecursively(directory);
}
}
private static void deleteRecursively(File file)
{
if (file.isFile()) {
file.delete();
} else {
for (File child : file.listFiles()) {
deleteRecursively(child);
}
}
}
private ByteBuffer newDiskPageSectionBuffer()
{
return ByteBuffer.allocate(pageSize);
}
private static final TestFactory FACTORY = new TestFactory();
private static final int ERDO_ID = 1;
private static int pageSize;
}