/*
* 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;
import com.github.geophile.erdo.apiimpl.DisklessTestDatabase;
import com.github.geophile.erdo.util.FileUtil;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.*;
public class OrderedMapTest
{
@BeforeClass
public static void beforeClass()
{
FACTORY = new TestFactory();
}
@After
public void after()
{
FACTORY.reset();
}
@Test
public void testPut()
throws IOException,
InterruptedException,
DeadlockException,
TransactionRolledBackException
{
Database db = new DisklessTestDatabase(FACTORY);
OrderedMap map = db.createMap(MAP_NAME, RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
for (int i = 0; i < N; i++) {
AbstractRecord replaced = map.put(TestRecord.createRecord(i, "first"));
assertNull(replaced);
}
for (int i = 0; i < N; i++) {
AbstractRecord replaced = map.put(TestRecord.createRecord(i, "second"));
assertEquals(i, ((TestKey) replaced.key()).key());
assertEquals("first", ((TestRecord) replaced).stringValue());
}
Cursor cursor = map.first();
TestRecord record;
int expected = 0;
while ((record = (TestRecord) cursor.next()) != null) {
assertEquals(expected++, record.key().key());
assertEquals("second", record.stringValue());
}
assertEquals(N, expected);
db.close();
}
@Test
public void testEnsurePresent()
throws IOException,
InterruptedException,
DeadlockException,
TransactionRolledBackException
{
Database db = new DisklessTestDatabase(FACTORY);
OrderedMap map = db.createMap(MAP_NAME, RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
for (int i = 0; i < N; i++) {
map.ensurePresent(TestRecord.createRecord(i, "first"));
}
for (int i = 0; i < N; i++) {
map.ensurePresent(TestRecord.createRecord(i, "second"));
}
Cursor cursor = map.first();
TestRecord record;
int expected = 0;
while ((record = (TestRecord) cursor.next()) != null) {
assertEquals(expected++, ((TestKey) record.key()).key());
assertEquals("second", record.stringValue());
}
assertEquals(N, expected);
db.close();
}
@Test
public void testDelete()
throws IOException,
InterruptedException,
DeadlockException,
TransactionRolledBackException
{
Database db = new DisklessTestDatabase(FACTORY);
OrderedMap map = db.createMap(MAP_NAME, RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
for (int i = 0; i < N; i++) {
AbstractRecord replaced = map.put(TestRecord.createRecord(i, "first"));
assertNull(replaced);
}
for (int i = 0; i < N; i++) {
// Delete present key
{
AbstractRecord replaced = map.delete(new TestKey(i));
assertNotNull(replaced);
assertEquals(i, ((TestKey) replaced.key()).key());
assertEquals("first", ((TestRecord) replaced).stringValue());
}
// Delete missing key (a version of bug #4)
{
AbstractRecord replaced = map.delete(new TestKey(-i));
assertNull(replaced);
}
}
assertNull(map.first().next());
db.close();
}
@Test
public void testEnsureDeleted()
throws IOException,
InterruptedException,
DeadlockException,
TransactionRolledBackException
{
Database db = new DisklessTestDatabase(FACTORY);
OrderedMap map = db.createMap(MAP_NAME, RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
for (int i = 0; i < N; i++) {
map.ensurePresent(TestRecord.createRecord(i, "first"));
}
for (int i = 0; i < N; i++) {
map.ensureAbsent(new TestKey(i));
}
assertNull(map.first().next());
db.close();
}
@Test
public void testScan()
throws IOException,
InterruptedException,
DeadlockException,
TransactionRolledBackException
{
// Based on SealedMapTest.testScan
Database db = new DisklessTestDatabase(FACTORY);
OrderedMap map = db.createMap(MAP_NAME, RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
Cursor cursor;
int expectedKey;
int expectedLastKey;
boolean expectedEmpty;
int gap = 10;
AbstractRecord record;
// Load
for (int i = 0; i < N; i++) {
map.put(TestRecord.createRecord(i * gap, null));
}
// Full cursor
cursor = map.first();
expectedKey = 0;
while ((record = cursor.next()) != null) {
assertEquals(expectedKey, key(record));
expectedKey += gap;
}
assertEquals(N * gap, expectedKey);
// Try scans starting at, before, and after each key and ending at, before and after each key.
for (int i = 0; i < N; i++) {
int startBase = gap * i;
int endBase = gap * (N - 1 - i);
for (int start = startBase - 1; start <= startBase + 1; start++) {
for (int end = endBase - 1; end <= endBase + 1; end++) {
if (start <= end) {
TestKey endKey = new TestKey(end);
cursor = map.cursor(new TestKey(start));
expectedKey = start <= startBase ? startBase : startBase + gap;
expectedLastKey = end >= endBase ? endBase : endBase - gap;
expectedEmpty = start > end || start <= end && (end >= startBase || start <= endBase);
boolean empty = true;
while ((record = cursor.next()) != null && record.key().compareTo(endKey) <= 0) {
assertEquals(expectedKey, key(record));
expectedKey += gap;
empty = false;
}
if (empty) {
assertTrue(expectedEmpty);
} else {
assertEquals(expectedLastKey + gap, expectedKey);
}
}
}
}
}
db.close();
}
// Problem uncovered while working on geophile-erdo. Bug #1.
@Test
public void testBackwardFromBeginning()
throws IOException, InterruptedException, DeadlockException, TransactionRolledBackException
{
Database db = new DisklessTestDatabase(FACTORY);
OrderedMap map = db.createMap(MAP_NAME, RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
for (int i = 0; i < 10; i++) {
map.ensurePresent(TestRecord.createRecord(i, null));
}
// map is represented by an empty forest and a PrivateMap containing updates. These will be combined
// by a MergeCursor intent on going forward.
Cursor cursor = map.cursor(new TestKey(-1));
TestRecord record = (TestRecord) cursor.previous();
assertNull(record);
}
@Test
public void testForwardFromEnd()
throws IOException, InterruptedException, DeadlockException, TransactionRolledBackException
{
Database db = new DisklessTestDatabase(FACTORY);
OrderedMap map = db.createMap(MAP_NAME, RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
for (int i = 0; i < 10; i++) {
map.ensurePresent(TestRecord.createRecord(i, null));
}
// map is represented by an empty forest and a PrivateMap containing updates. These will be combined
// by a MergeCursor intent on going forward.
Cursor cursor = map.cursor(new TestKey(99));
TestRecord record = (TestRecord) cursor.next();
assertNull(record);
}
// End of tests for bug #1.
// Bug #3
@Test
public void testPutAfterDelete()
throws IOException, InterruptedException, DeadlockException, TransactionRolledBackException
{
Database db = new DisklessTestDatabase(FACTORY);
OrderedMap map = db.createMap(MAP_NAME, RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
TestRecord record = TestRecord.createRecord(100, null);
TestRecord replaced = (TestRecord) map.put(record);
assertNull(replaced);
TestRecord removed = (TestRecord) map.delete(record.key());
assertTrue(!removed.deleted());
// Comparing TestKey objects doesn't work. record.key() has no erdoId.
assertEquals(record.key().key(), removed.key().key());
replaced = (TestRecord) map.put(record);
assertNull(replaced);
}
// Check for another version of bug #3
@Test
public void testIterationOverMapWithDeletedRecords()
throws IOException, InterruptedException, DeadlockException, TransactionRolledBackException
{
final int N = 10;
Database db = new DisklessTestDatabase(FACTORY);
OrderedMap map = db.createMap(MAP_NAME, RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
TestRecord[] records = new TestRecord[N];
for (int i = 0; i < N; i++) {
records[i] = TestRecord.createRecord(i, null);
map.ensurePresent(records[i]);
}
int firstExpected = 0;
TestKey key0 = new TestKey(0);
for (int i = 0; i < N; i++) {
// Check contents before deletion
Cursor cursor = map.cursor(key0);
TestRecord record;
int expected = firstExpected;
while ((record = (TestRecord) cursor.next()) != null) {
assertEquals(expected, record.key().key());
expected++;
}
assertEquals(N, expected);
// delete key i
TestRecord replaced = (TestRecord) map.delete(new TestKey(i));
assertEquals(i, replaced.key().key());
firstExpected++;
// Check contents after deletion
cursor = map.cursor(key0);
expected = firstExpected;
while ((record = (TestRecord) cursor.next()) != null) {
assertEquals(expected, record.key().key());
expected++;
}
assertEquals(N, expected);
}
}
// Bug #4
@Test
public void testMoreDeletion()
throws IOException,
InterruptedException,
DeadlockException,
TransactionRolledBackException
{
final File DB_DIRECTORY = new File(FileUtil.tempDirectory(), "erdo");
FileUtil.deleteDirectory(DB_DIRECTORY);
Database db = Database.createDatabase(DB_DIRECTORY);
OrderedMap map = db.createMap(MAP_NAME, RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
for (int i = 0; i < N; i++) {
AbstractRecord replaced = map.put(TestRecord.createRecord(i, String.format("v%s", i)));
assertNull(replaced);
}
// Delete odd keys
for (int i = 1; i < N; i += 2) {
TestRecord replaced = (TestRecord) map.delete(new TestKey(i));
assertNotNull(replaced);
assertEquals(i, replaced.key().key());
}
db.commitTransaction();
db.flush();
// Check that the expected even keys remain
assertEquals(0, N % 2);
int evenCount = 0;
for (int i = 0; i < N; i += 2) {
TestRecord record = (TestRecord) map.find(new TestKey(i));
assertEquals(i, record.key().key());
evenCount++;
}
assertEquals(N/2, evenCount);
// Delete them again, even though they aren't there
for (int i = 1; i < N; i += 2) {
TestRecord replaced = (TestRecord) map.delete(new TestKey(i));
assertNull(replaced);
}
db.close();
}
@Test
public void testEvenMoreDeletion()
throws IOException, InterruptedException, DeadlockException, TransactionRolledBackException
{
final File DB_DIRECTORY = new File(FileUtil.tempDirectory(), "erdo");
FileUtil.deleteDirectory(DB_DIRECTORY);
// Make background consolidation happen more often
Configuration configuration = Configuration.defaultConfiguration();
configuration.consolidationThreads(1);
configuration.consolidationMinSizeBytes(100);
configuration.consolidationMinMapsToConsolidate(2);
Database db = Database.createDatabase(DB_DIRECTORY, configuration);
OrderedMap map = db.createMap(MAP_NAME, RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
int txn = 0;
{
// Load some records
int id = 0;
for (int t = 0; t < 100; t++) {
String transactionName = String.format("t%s", txn++);
for (int i = 0; i < 100; i++) {
AbstractRecord replaced = map.put(TestRecord.createRecord(id++, transactionName));
assertNull(replaced);
}
db.commitTransaction();
}
}
{
// Delete them all
int id = 0;
for (int t = 0; t < 100; t++) {
txn++;
for (int i = 0; i < 100; i++) {
TestRecord deleted = (TestRecord) map.delete(new TestKey(id));
assertNotNull(deleted);
assertEquals(id, deleted.key().key());
id++;
}
db.commitTransaction();
}
}
{
// Check empty
Cursor cursor = map.first();
while (cursor.next() != null) {
fail();
}
}
db.close();
}
// End testing of bug #4
private int key(AbstractRecord record)
{
return ((TestKey) record.key()).key();
}
private void dump(String label, OrderedMap map) throws IOException, InterruptedException
{
System.out.println(label);
Cursor cursor = map.first();
AbstractRecord record;
while ((record = cursor.next()) != null) {
System.out.println(String.format(" %s, deleted: %s", record, record.deleted()));
}
}
private static TestFactory FACTORY;
private static final String MAP_NAME = "map";
private static final int N = 10;
}