/* HeliDB -- A simple database for Java, http://www.helidb.org
* Copyright (C) 2008, 2009 Karl Gustafsson
*
* This file is a part of HeliDB.
*
* HeliDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeliDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.helidb.txn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import org.entityfs.Directory;
import org.entityfs.fs.FSRWFileSystemBuilder;
import org.entityfs.util.IteratorDeleter;
import org.entityfs.util.cap.entity.ECFileResolvableUtil;
import org.helidb.AbstractDatabaseOnConstantRecordSizeBackendTest;
import org.helidb.Database;
import org.helidb.test.support.FileSupport;
import org.helidb.test.support.concurrent.ConcurrentTestRunner;
import org.helidb.test.support.concurrent.TestStep;
import org.helidb.txn.NoTransactionException;
import org.helidb.txn.ReadOnlyTransactionException;
import org.helidb.txn.Transaction;
import org.helidb.txn.TransactionCollaborator;
import org.helidb.txn.TransactionalDatabase;
import org.helidb.txn.UnableToCommitException;
import org.junit.Test;
public abstract class AbstractTransactionalDatabaseOnConstantRecordSizeBackendTest extends AbstractDatabaseOnConstantRecordSizeBackendTest
{
private final Map<Integer, Directory> m_dbDirs = new HashMap<Integer, Directory>();
protected abstract TransactionalDatabase<Integer, Long> createDatabaseWoTxnInDirectory(Directory dir);
protected TransactionalDatabase<Integer, Long> createDatabaseWoTxn()
{
File tmpf = FileSupport.createTempDirectory();
// A locking file system
Directory tmpDir = new FSRWFileSystemBuilder().setRoot(tmpf).disableAccessControls().create().getRootDirectory();
// tmpDir.getFileSystem().getLogAdapter().setLevel(Level.ALL);
TransactionalDatabase<Integer, Long> res = createDatabaseWoTxnInDirectory(tmpDir);
m_dbDirs.put(System.identityHashCode(res), tmpDir);
return res;
}
@Override
protected TransactionalDatabase<Integer, Long> createDatabase()
{
assert m_dbDirs.size() > 0 || Transaction.getCurrentTransactionOrNull() == null;
TransactionalDatabase<Integer, Long> res = createDatabaseWoTxn();
Transaction.getOrStartTransaction(false);
return res;
}
protected abstract TransactionalDatabase<Character, Character> createCharacterDatabaseWoTxnInDirectory(Directory dir);
protected TransactionalDatabase<Character, Character> createCharacterDatabaseWoTxn()
{
File tmpf = FileSupport.createTempDirectory();
// A locking file system
Directory tmpDir = new FSRWFileSystemBuilder().setRoot(tmpf).disableAccessControls().create().getRootDirectory();
// tmpDir.getFileSystem().getLogAdapter().setLevel(Level.ALL);
TransactionalDatabase<Character, Character> res = createCharacterDatabaseWoTxnInDirectory(tmpDir);
m_dbDirs.put(System.identityHashCode(res), tmpDir);
return res;
}
@Override
protected TransactionalDatabase<Character, Character> createCharacterDatabase()
{
assert m_dbDirs.size() > 0 || Transaction.getCurrentTransactionOrNull() == null;
TransactionalDatabase<Character, Character> res = createCharacterDatabaseWoTxn();
Transaction.getOrStartTransaction(false);
return res;
}
@Override
protected void tearDownDatabase(Database<?, ?> db)
{
Transaction txn = Transaction.getCurrentTransactionOrNull();
// The transaction may already be rolled back if we're working with
// several databases.
if (txn != null)
{
if (!txn.isFinished())
{
txn.rollback();
}
}
db.close();
Directory d = m_dbDirs.remove(System.identityHashCode(db));
if (d != null)
{
new IteratorDeleter(d).delete();
assertTrue(ECFileResolvableUtil.getFileObject(d).delete());
}
}
/**
* Load some data into the database.
*/
protected void populateDb(Database<Integer, Long> db)
{
boolean successful = false;
Transaction txn = Transaction.startTransaction(false);
try
{
db.fasterInsert(Integer.valueOf(1), Long.valueOf(2));
db.fasterInsert(Integer.valueOf(123), Long.valueOf(234));
assertEquals(Long.valueOf(2), db.get(Integer.valueOf(1)));
assertEquals(Long.valueOf(234), db.get(Integer.valueOf(123)));
txn.commit();
successful = true;
}
catch (RuntimeException e)
{
e.printStackTrace();
}
finally
{
if (!successful && !txn.isFinished())
{
txn.rollback();
}
}
assertNull(Transaction.getCurrentTransactionOrNull());
}
@Test
public void testCommit()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
populateDb(db);
Transaction txn = Transaction.startTransaction(true);
try
{
assertEquals(Long.valueOf(2), db.get(Integer.valueOf(1)));
assertEquals(Long.valueOf(234), db.get(Integer.valueOf(123)));
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testRollback()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.fasterInsert(Integer.valueOf(1), Long.valueOf(2));
db.fasterInsert(Integer.valueOf(123), Long.valueOf(234));
assertEquals(Long.valueOf(2), db.get(Integer.valueOf(1)));
assertEquals(Long.valueOf(234), db.get(Integer.valueOf(123)));
}
finally
{
txn.rollback();
}
txn = Transaction.startTransaction(true);
try
{
assertNull(db.get(Integer.valueOf(1)));
assertNull(db.get(Integer.valueOf(123)));
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testReadWoTransaction()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
populateDb(db);
// Existing record
try
{
db.get(Integer.valueOf(1));
fail();
}
catch (NoTransactionException e)
{
// ok
}
// Nonexistent record
try
{
db.get(Integer.valueOf(2));
fail();
}
catch (NoTransactionException e)
{
// ok
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testWriteWoTransaction()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
populateDb(db);
// Existing record
try
{
db.fasterInsert(Integer.valueOf(1), Long.valueOf(222));
fail();
}
catch (NoTransactionException e)
{
// ok
}
// Nonexistent record
try
{
db.fasterInsert(Integer.valueOf(2), Long.valueOf(23));
fail();
}
catch (NoTransactionException e)
{
// ok
}
Transaction txn = Transaction.startTransaction(true);
try
{
assertEquals(Long.valueOf(2), db.get(Integer.valueOf(1)));
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testWriteToRoTransaction()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
populateDb(db);
assertNull(Transaction.getCurrentTransactionOrNull());
Transaction txn = Transaction.startTransaction(true);
try
{
try
{
db.fasterInsert(Integer.valueOf(3), Long.valueOf(333));
fail();
}
catch (ReadOnlyTransactionException e)
{
// ok
}
assertNull(db.get(Integer.valueOf(3)));
}
finally
{
txn.commit();
}
txn = Transaction.startTransaction(true);
try
{
assertNull(db.get(Integer.valueOf(3)));
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testCommitOnClosedDb()
{
boolean destroyed = false;
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
populateDb(db);
boolean committed = false;
Transaction txn = Transaction.startTransaction(false);
try
{
db.fasterInsert(Integer.valueOf(3), Long.valueOf(333L));
tearDownDatabase(db);
destroyed = true;
committed = true;
try
{
txn.commit();
}
catch (IllegalStateException e)
{
// ok
}
}
finally
{
if (!committed)
{
txn.rollback();
}
}
}
finally
{
if (!destroyed)
{
tearDownDatabase(db);
}
}
}
@Test
public void testRollbackOnClosedDb()
{
boolean destroyed = false;
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
populateDb(db);
boolean committed = false;
Transaction txn = Transaction.startTransaction(false);
try
{
db.fasterInsert(Integer.valueOf(3), Long.valueOf(333L));
tearDownDatabase(db);
destroyed = true;
committed = true;
try
{
txn.rollback();
}
catch (IllegalStateException e)
{
// ok
}
}
finally
{
if (!committed)
{
txn.rollback();
}
}
}
finally
{
if (!destroyed)
{
tearDownDatabase(db);
}
}
}
@Test
public void testInsertCommit()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.insert(1, 10L);
db.insert(2, 20L);
txn.commit();
txn = Transaction.startTransaction(true);
assertEquals(2, db.size());
assertEquals(20L, db.get(2).longValue());
assertEquals(10L, db.get(1).longValue());
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testInsertRollback()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.insert(1, 10L);
db.insert(2, 20L);
txn.commit();
txn = Transaction.startTransaction(false);
db.insert(3, 30L);
db.insert(4, 40L);
txn.rollback();
txn = Transaction.startTransaction(true);
assertEquals(2, db.size());
assertEquals(20L, db.get(2).longValue());
assertEquals(10L, db.get(1).longValue());
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testUpdateCommit()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.insert(1, 10L);
db.insert(2, 20L);
txn.commit();
txn = Transaction.startTransaction(false);
db.update(1, 100L);
db.update(2, 200L);
txn.rollback();
txn = Transaction.startTransaction(true);
assertEquals(2, db.size());
assertEquals(20L, db.get(2).longValue());
assertEquals(10L, db.get(1).longValue());
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testUpdateRollback()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.insert(1, 10L);
db.insert(2, 20L);
txn.commit();
txn = Transaction.startTransaction(false);
db.update(1, 100L);
db.update(2, 200L);
txn.rollback();
txn = Transaction.startTransaction(true);
assertEquals(2, db.size());
assertEquals(20L, db.get(2).longValue());
assertEquals(10L, db.get(1).longValue());
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testClearCommit()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.insert(1, 10L);
db.insert(2, 20L);
txn.commit();
txn = Transaction.startTransaction(false);
db.clear();
txn.commit();
txn = Transaction.startTransaction(true);
assertEquals(0, db.size());
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testClearRollback()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.insert(1, 10L);
db.insert(2, 20L);
txn.commit();
txn = Transaction.startTransaction(false);
db.clear();
db.insert(1, 100L);
txn.rollback();
txn = Transaction.startTransaction(true);
assertEquals(2, db.size());
assertEquals(20L, db.get(2).longValue());
assertEquals(10L, db.get(1).longValue());
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testCompactCommit()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.insert(1, 10L);
db.insert(2, 20L);
txn.commit();
txn = Transaction.startTransaction(false);
db.delete(1);
db.insert(3, 30L);
txn.commit();
txn = Transaction.startTransaction(false);
db.compact();
txn.commit();
txn = Transaction.startTransaction(true);
assertEquals(2, db.size());
assertEquals(30L, db.get(3).longValue());
assertEquals(20L, db.get(2).longValue());
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testCompactRollback()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.insert(1, 10L);
db.insert(2, 20L);
txn.commit();
txn = Transaction.startTransaction(false);
db.delete(1);
db.insert(3, 30L);
txn.commit();
txn = Transaction.startTransaction(false);
db.compact();
txn.rollback();
txn = Transaction.startTransaction(true);
assertEquals(2, db.size());
assertEquals(30L, db.get(3).longValue());
assertEquals(20L, db.get(2).longValue());
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testDeleteCommit()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.insert(1, 10L);
db.insert(2, 20L);
db.insert(3, 30L);
txn.commit();
txn = Transaction.startTransaction(false);
db.delete(2);
txn.commit();
txn = Transaction.startTransaction(true);
assertEquals(2, db.size());
assertEquals(30L, db.get(3).longValue());
assertEquals(10L, db.get(1).longValue());
assertNull(db.get(2));
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testDeleteRollback()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.insert(1, 10L);
db.insert(2, 20L);
db.insert(3, 30L);
txn.commit();
txn = Transaction.startTransaction(false);
db.delete(2);
txn.rollback();
txn = Transaction.startTransaction(true);
assertEquals(3, db.size());
assertEquals(30L, db.get(3).longValue());
assertEquals(10L, db.get(1).longValue());
assertEquals(20L, db.get(2).longValue());
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testCommitRightValue()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.insert(1, 10L);
db.insert(2, 20L);
txn.commit();
txn = Transaction.startTransaction(false);
db.update(1, 1000L);
db.update(1, 10000L);
txn.commit();
txn = Transaction.startTransaction(true);
assertEquals(2, db.size());
assertEquals(20L, db.get(2).longValue());
assertEquals(10000L, db.get(1).longValue());
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testRollbackToRightValue()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
Transaction txn = Transaction.startTransaction(false);
try
{
db.insert(1, 10L);
db.insert(2, 20L);
txn.commit();
txn = Transaction.startTransaction(false);
db.update(1, 100L);
db.update(2, 200L);
txn.commit();
txn = Transaction.startTransaction(false);
db.update(1, 1000L);
db.update(1, 10000L);
txn.rollback();
txn = Transaction.startTransaction(true);
assertEquals(2, db.size());
assertEquals(200L, db.get(2).longValue());
assertEquals(100L, db.get(1).longValue());
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testAutoRollback()
{
TransactionalDatabase<Integer, Long> db = createDatabaseWoTxn();
try
{
populateDb(db);
Transaction txn = Transaction.startTransaction(false);
try
{
db.fasterInsert(Integer.valueOf(12), Long.valueOf(345823L));
db.close();
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testAutoRollbackWithSeveralActiveTransactions()
{
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
populateDb(db);
Map<String, Object> m = new HashMap<String, Object>();
m.put("db", db);
m.put("latch1", new CountDownLatch(3));
m.put("flag1", new AtomicBoolean(false));
TestStep readingTestStep = new TestStep() {
public void runStep(Map<String, Object> mp) throws Exception
{
CountDownLatch l1 = (CountDownLatch) mp.get("latch1");
try
{
TransactionalDatabase<?, ?> tdb = (TransactionalDatabase<?, ?>) mp.get("db");
Transaction txn = Transaction.startTransaction(true);
try
{
tdb.joinTransaction(true);
l1.countDown();
l1.await();
l1 = null;
Thread.sleep(1000);
// Now the database is closed.
assertFalse(((AtomicBoolean) mp.get("flag1")).get());
}
finally
{
txn.rollback();
}
}
finally
{
if (l1 != null)
{
l1.countDown();
}
}
}
};
new ConcurrentTestRunner(new TestStep[][] {
// Thread that will close the database
new TestStep[] { new TestStep() {
public void runStep(Map<String, Object> mp) throws Exception
{
CountDownLatch l1 = (CountDownLatch) mp.get("latch1");
try
{
TransactionalDatabase<?, ?> tdb = (TransactionalDatabase<?, ?>) mp.get("db");
// The other threads start their transactions
l1.countDown();
l1.await();
l1 = null;
tdb.close();
((AtomicBoolean) mp.get("flag1")).set(true);
}
finally
{
if (l1 != null)
{
l1.countDown();
}
}
}
} },
// Threads with reading transactions
new TestStep[] { readingTestStep }, new TestStep[] { readingTestStep } }, m).run();
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testConcurrentWritingTransactions()
{
// Pessimistic locking
Database<Integer, Long> db = createDatabaseWoTxn();
try
{
populateDb(db);
Map<String, Object> m = new HashMap<String, Object>();
m.put("db", db);
m.put("latch1", new CountDownLatch(2));
m.put("flag1", new AtomicBoolean(false));
new ConcurrentTestRunner(new TestStep[][] { new TestStep[] { new TestStep() {
public void runStep(Map<String, Object> mp) throws Exception
{
CountDownLatch l1 = (CountDownLatch) mp.get("latch1");
try
{
TransactionalDatabase<?, ?> tdb = (TransactionalDatabase<?, ?>) mp.get("db");
// Start a transaction and count down the latch
Transaction txn = Transaction.startTransaction(false);
try
{
// Let the database join the transaction
tdb.joinTransaction(false);
l1.countDown();
l1.await();
l1 = null;
// Sleep awhile to give the other thread plenty of
// time to set the flag
Thread.sleep(1000);
// Still not set
assertFalse(((AtomicBoolean) mp.get("flag1")).get());
}
finally
{
txn.rollback();
}
}
finally
{
if (l1 != null)
{
l1.countDown();
}
}
}
} }, new TestStep[] { new TestStep() {
public void runStep(Map<String, Object> mp) throws Exception
{
CountDownLatch l1 = (CountDownLatch) mp.get("latch1");
try
{
TransactionalDatabase<?, ?> tdb = (TransactionalDatabase<?, ?>) mp.get("db");
Transaction txn = Transaction.startTransaction(false);
try
{
l1.countDown();
l1.await();
l1 = null;
// Try to join the transaction. This should not
// be possible until the other thread leaves it
tdb.joinTransaction(false);
((AtomicBoolean) mp.get("flag1")).set(true);
}
finally
{
txn.rollback();
}
}
finally
{
if (l1 != null)
{
l1.countDown();
}
}
}
} } }, m).run();
}
finally
{
tearDownDatabase(db);
}
}
@Test
public void testConcurrentReadingAndWritingTransactionsOverTwoDatabases()
{
Database<Integer, Long> db1 = createDatabaseWoTxn();
try
{
Database<Integer, Long> db2 = createDatabaseWoTxn();
try
{
populateDb(db1);
Map<String, Object> m = new HashMap<String, Object>();
m.put("db1", db1);
m.put("db2", db2);
m.put("latch1", new CountDownLatch(2));
m.put("flag1", new AtomicBoolean(false));
new ConcurrentTestRunner(new TestStep[][] { new TestStep[] { new TestStep() {
public void runStep(Map<String, Object> mp) throws Exception
{
CountDownLatch l1 = (CountDownLatch) mp.get("latch1");
try
{
TransactionalDatabase<?, ?> tdb1 = (TransactionalDatabase<?, ?>) mp.get("db1");
TransactionalDatabase<?, ?> tdb2 = (TransactionalDatabase<?, ?>) mp.get("db2");
// Start a transaction and count down the latch
Transaction txn = Transaction.startTransaction(false);
try
{
// Let the databases join the transaction
tdb1.joinTransaction(false);
tdb2.joinTransaction(false);
l1.countDown();
l1.await();
l1 = null;
// Sleep awhile to give the other thread plenty
// of time to
// set the flag
Thread.sleep(1000);
// Still not set
assertFalse(((AtomicBoolean) mp.get("flag1")).get());
}
finally
{
txn.rollback();
}
}
finally
{
if (l1 != null)
{
l1.countDown();
}
}
}
} }, new TestStep[] { new TestStep() {
public void runStep(Map<String, Object> mp) throws Exception
{
CountDownLatch l1 = (CountDownLatch) mp.get("latch1");
try
{
TransactionalDatabase<?, ?> tdb1 = (TransactionalDatabase<?, ?>) mp.get("db1");
TransactionalDatabase<?, ?> tdb2 = (TransactionalDatabase<?, ?>) mp.get("db2");
Transaction txn = Transaction.startTransaction(false);
try
{
l1.countDown();
l1.await();
l1 = null;
// Try to join the transaction. This should not
// be possible until the other thread leaves it
tdb1.joinTransaction(false);
tdb2.joinTransaction(false);
((AtomicBoolean) mp.get("flag1")).set(true);
}
finally
{
txn.rollback();
}
}
finally
{
if (l1 != null)
{
l1.countDown();
}
}
}
} } }, m).run();
}
finally
{
tearDownDatabase(db2);
}
}
finally
{
tearDownDatabase(db1);
}
}
protected abstract void injectCannotCommit(TransactionCollaborator<?, ?, ?> collab);
@Test
public void testThatNothingIsChangedIfOneDatabaseCannotCommit()
{
TransactionalDatabase<Integer, Long> db1 = createDatabaseWoTxn();
try
{
populateDb(db1);
TransactionalDatabase<Integer, Long> db2 = createDatabaseWoTxn();
try
{
populateDb(db2);
Transaction txn = Transaction.startTransaction(false);
try
{
db1.update(Integer.valueOf(1), Long.valueOf(1235L));
db2.update(Integer.valueOf(1), Long.valueOf(2432398L));
db2.fasterInsert(Integer.valueOf(438), Long.valueOf(248238L));
injectCannotCommit(txn.getCollaborator(db2));
try
{
txn.commit();
fail();
}
catch (UnableToCommitException e)
{
assertTrue(e.getMessage().contains("inject"));
}
assertTrue(txn.isFinished());
txn = null;
}
finally
{
if (txn != null)
{
txn.rollback();
}
}
// Verify that nothing was updated in the databases
txn = Transaction.startTransaction(true);
try
{
assertEquals(Long.valueOf(2), db1.get(Integer.valueOf(1)));
assertEquals(Long.valueOf(2), db2.get(Integer.valueOf(1)));
assertNull(db2.get(Integer.valueOf(438)));
}
finally
{
txn.rollback();
}
}
finally
{
tearDownDatabase(db2);
}
}
finally
{
tearDownDatabase(db1);
}
}
protected abstract TransactionalDatabase<Integer, Long> createNewDatabaseUsingSameFiles(TransactionalDatabase<Integer, Long> otherDb);
@Test
public void testStateMaintenanceOverDbCrash()
{
TransactionalDatabase<Integer, Long> db1 = createDatabaseWoTxn();
try
{
populateDb(db1);
Transaction txn = Transaction.startTransaction(false);
try
{
db1.delete(1);
db1.update(123, 10000L);
db1.insert(2, 2L);
TransactionalDatabase<Integer, Long> db2 = createNewDatabaseUsingSameFiles(db1);
try
{
// This should contain the old values.
assertEquals(234L, db2.get(123).longValue());
assertEquals(2L, db2.get(1).longValue());
}
finally
{
tearDownDatabase(db2);
}
}
finally
{
if (!txn.isFinished())
{
txn.rollback();
}
}
}
finally
{
tearDownDatabase(db1);
}
}
@Override
public void testEqualsAndHashCode()
{
// reimplemented
}
}