/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2005
* Sleepycat Software. All rights reserved.
*
* $Id: LNFileReaderTest.java,v 1.77 2005/08/11 17:27:30 cwl Exp $
*/
package com.sleepycat.je.log;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.TransactionConfig;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.tree.LN;
import com.sleepycat.je.tree.MapLN;
import com.sleepycat.je.txn.LockType;
import com.sleepycat.je.txn.Txn;
import com.sleepycat.je.util.TestUtils;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.Tracer;
/**
* Test the LNFileReader
*/
public class LNFileReaderTest extends TestCase {
static private final boolean DEBUG = false;
private File envHome;
private Environment env;
private EnvironmentImpl envImpl;
private Database db;
private List checkList;
public LNFileReaderTest() {
super();
envHome = new File(System.getProperty(TestUtils.DEST_DIR));
}
public void setUp()
throws IOException, DatabaseException {
/*
* Note that we use the official Environment class to make the
* environment, so that everything is set up, but we then go a backdoor
* route to get to the underlying EnvironmentImpl class so that we
* don't require that the Environment.getDbEnvironment method be
* unnecessarily public.
*/
TestUtils.removeLogFiles("Setup", envHome, false);
EnvironmentConfig envConfig = TestUtils.initEnvConfig();
DbInternal.disableParameterValidation(envConfig);
envConfig.setConfigParam(EnvironmentParams.NODE_MAX.getName(), "6");
envConfig.setConfigParam
(EnvironmentParams.LOG_FILE_MAX.getName(), "1024");
envConfig.setAllowCreate(true);
env = new Environment(envHome, envConfig);
envImpl = DbInternal.envGetEnvironmentImpl(env);
}
public void tearDown()
throws IOException, DatabaseException {
envImpl = null;
env.close();
TestUtils.removeFiles("TearDown", envHome, FileManager.JE_SUFFIX);
}
/**
* Test no log file
*/
public void testNoFile()
throws IOException, DatabaseException {
/* Make a log file with a valid header, but no data. */
LNFileReader reader =
new LNFileReader(envImpl,
1000, // read buffer size
DbLsn.NULL_LSN, // start lsn
true, // redo
DbLsn.NULL_LSN, // end of file lsn
DbLsn.NULL_LSN, // finish lsn
null); // single file
reader.addTargetType(LogEntryType.LOG_LN_TRANSACTIONAL);
reader.addTargetType(LogEntryType.LOG_DEL_DUPLN_TRANSACTIONAL);
assertFalse("Empty file should not have entries",
reader.readNextEntry());
}
/**
* Run with an empty file.
*/
public void testEmpty()
throws IOException, DatabaseException {
/* Make a log file with a valid header, but no data. */
FileManager fileManager = envImpl.getFileManager();
FileManagerTestUtils.createLogFile(fileManager, envImpl, 1000);
fileManager.clear();
LNFileReader reader =
new LNFileReader(envImpl,
1000, // read buffer size
DbLsn.NULL_LSN, // start lsn
true, // redo
DbLsn.NULL_LSN, // end of file lsn
DbLsn.NULL_LSN, // finish lsn
null); // single file
reader.addTargetType(LogEntryType.LOG_LN_TRANSACTIONAL);
reader.addTargetType(LogEntryType.LOG_DEL_DUPLN_TRANSACTIONAL);
assertFalse("Empty file should not have entries",
reader.readNextEntry());
}
/**
* Run with defaults, read whole log for redo, going forwards.
*/
public void testBasicRedo()
throws Throwable {
try {
DbConfigManager cm = envImpl.getConfigManager();
doTest(50,
cm.getInt(EnvironmentParams.LOG_ITERATOR_READ_SIZE),
0,
false,
true);
} catch (Throwable t) {
t.printStackTrace();
throw t;
}
}
/**
* Run with defaults, read whole log for undo, going backwards.
*/
public void testBasicUndo()
throws Throwable {
try {
DbConfigManager cm = envImpl.getConfigManager();
doTest(50,
cm.getInt(EnvironmentParams.LOG_ITERATOR_READ_SIZE),
0,
false,
false);
} catch (Throwable t) {
t.printStackTrace();
throw t;
}
}
/**
* Run with very small read buffer for redo, and track LNs.
*/
public void testSmallBuffersRedo()
throws IOException, DatabaseException {
doTest(50, 10, 0, true, true);
}
/**
* Run with very small read buffer for undo and track LNs.
*/
public void testSmallBuffersUndo()
throws IOException, DatabaseException {
doTest(50, 10, 0, true, false);
}
/**
* Run with medium buffers for redo.
*/
public void testMedBuffersRedo()
throws IOException, DatabaseException {
doTest(50, 100, 0, false, true);
}
/**
* Run with medium buffers for undo.
*/
public void testMedBuffersUndo()
throws IOException, DatabaseException {
doTest(50, 100, 0, false, false);
}
/**
* Start in the middle of the file for redo.
*/
public void testMiddleStartRedo()
throws IOException, DatabaseException {
doTest(50, 100, 20, true, true);
}
/**
* Start in the middle of the file for undo.
*/
public void testMiddleStartUndo()
throws IOException, DatabaseException {
doTest(50, 100, 20, true, false);
}
/**
* Create a log file, create the reader, read the log file
* @param numIters each iteration makes 3 log entries (debug record, ln
* and mapLN
* @param bufferSize to pass to reader
* @param checkIndex where in the test data to start
* @param trackLNs true if we're tracking LNS, false if we're tracking
* mapLNs
*/
private void doTest(int numIters,
int bufferSize,
int checkIndex,
boolean trackLNs,
boolean redo)
throws IOException, DatabaseException {
checkList = new ArrayList();
/* Fill up a fake log file. */
long endOfFileLsn = createLogFile(numIters, trackLNs, redo);
if (DEBUG) {
System.out.println("eofLsn = " + endOfFileLsn);
}
/* Decide where to start. */
long startLsn = DbLsn.NULL_LSN;
long finishLsn = DbLsn.NULL_LSN;
if (redo) {
startLsn = ((CheckInfo) checkList.get(checkIndex)).lsn;
} else {
/* Going backwards. Start at last check entry. */
int lastEntryIdx = checkList.size() - 1;
startLsn = ((CheckInfo) checkList.get(lastEntryIdx)).lsn;
finishLsn = ((CheckInfo) checkList.get(checkIndex)).lsn;
}
LNFileReader reader =
new LNFileReader(envImpl, bufferSize, startLsn, redo, endOfFileLsn,
finishLsn, null);
if (trackLNs) {
reader.addTargetType(LogEntryType.LOG_LN_TRANSACTIONAL);
reader.addTargetType(LogEntryType.LOG_DEL_DUPLN_TRANSACTIONAL);
} else {
reader.addTargetType(LogEntryType.LOG_MAPLN_TRANSACTIONAL);
}
if (!redo) {
reader.addTargetType(LogEntryType.LOG_TXN_COMMIT);
}
/* read. */
checkLogFile(reader, checkIndex, redo);
}
/**
* Write a logfile of entries, put the entries that we expect to
* read into a list for later verification.
* @return end of file LSN.
*/
private long createLogFile(int numIters, boolean trackLNs, boolean redo)
throws IOException, DatabaseException {
/*
* Create a log file full of LNs, DeletedDupLNs, MapLNs and Debug
* Records
*/
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(true);
db = env.openDatabase(null, "foo", dbConfig);
LogManager logManager = envImpl.getLogManager();
long lsn;
Txn userTxn = new Txn(envImpl, new TransactionConfig());
long txnId = userTxn.getId();
for (int i = 0; i < numIters; i++) {
/* Add a debug record just to be filler. */
Tracer rec = new Tracer("Hello there, rec " + (i+1));
logManager.log(rec);
/* Make a transactional LN, we expect it to be there. */
byte [] data = new byte[i+1];
Arrays.fill(data, (byte)(i+1));
LN ln = new LN(data);
byte [] key = new byte[i+1];
Arrays.fill(key, (byte)(i+10));
/*
* Log an LN. If we're tracking LNs add it to the verification
* list.
*/
userTxn.lock
(ln.getNodeId(), LockType.WRITE,
DbInternal.dbGetDatabaseImpl(db));
lsn = ln.log(envImpl,
DbInternal.dbGetDatabaseImpl(db).getId(),
key,
DbLsn.NULL_LSN,
userTxn);
if (trackLNs) {
checkList.add(new CheckInfo(lsn, ln, key,
ln.getData(), txnId));
}
/* Log a deleted duplicate LN. */
LN deleteLN = new LN(data);
byte[] dupKey = new byte[i+1];
Arrays.fill(dupKey, (byte)(i+2));
userTxn.lock
(deleteLN.getNodeId(), LockType.WRITE,
DbInternal.dbGetDatabaseImpl(db));
lsn = deleteLN.delete(DbInternal.dbGetDatabaseImpl(db),
key,
dupKey,
DbLsn.NULL_LSN,
userTxn);
if (trackLNs) {
checkList.add(new CheckInfo(lsn, deleteLN,
dupKey, key, txnId));
}
/*
* Make a non-transactional ln. Shouldn't get picked up by reader.
*/
LN nonTxnalLN = new LN(data);
nonTxnalLN.log(envImpl,
DbInternal.dbGetDatabaseImpl(db).getId(),
key, DbLsn.NULL_LSN, null);
/* Add a MapLN. */
MapLN mapLN = new MapLN(DbInternal.dbGetDatabaseImpl(db));
userTxn.lock
(mapLN.getNodeId(), LockType.WRITE,
DbInternal.dbGetDatabaseImpl(db));
lsn = mapLN.log(envImpl,
DbInternal.dbGetDatabaseImpl(db).getId(),
key, DbLsn.NULL_LSN, userTxn);
if (!trackLNs) {
checkList.add(new CheckInfo(lsn, mapLN, key,
mapLN.getData(),
txnId));
}
}
long commitLsn = userTxn.commit(Txn.TXN_SYNC);
/* We only expect checkpoint entries to be read in redo passes. */
if (!redo) {
checkList.add(new CheckInfo(commitLsn, null, null, null, txnId));
}
/* Make a marker log entry to pose as the end of file. */
Tracer rec = new Tracer("Pretend this is off the file");
long lastLsn = logManager.log(rec);
db.close();
logManager.flush();
envImpl.getFileManager().clear();
return lastLsn;
}
private void checkLogFile(LNFileReader reader,
int checkIndex,
boolean redo)
throws IOException, DatabaseException {
LN lnFromLog;
byte[] keyFromLog;
/* Read all the LNs. */
int i;
if (redo) {
/* start where indicated. */
i = checkIndex;
} else {
/* start at the end. */
i = checkList.size() - 1;
}
while (reader.readNextEntry()) {
CheckInfo expected = (CheckInfo) checkList.get(i);
/* Check LSN. */
assertEquals("LSN " + i + " should match",
expected.lsn,
reader.getLastLsn());
if (reader.isLN()) {
/* Check the LN. */
lnFromLog = reader.getLN();
LN expectedLN = expected.ln;
assertEquals("Should be the same type of object",
expectedLN.getClass(),
lnFromLog.getClass());
if (DEBUG) {
if (!expectedLN.toString().equals(lnFromLog.toString())) {
System.out.println("expected = " +
expectedLN.toString()+
"lnFromLog = " +
lnFromLog.toString());
}
}
assertEquals("LN " + i + " should match",
expectedLN.toString(),
lnFromLog.toString());
/* Check the key. */
keyFromLog = reader.getKey();
byte[] expectedKey = expected.key;
if (DEBUG) {
if (!Arrays.equals(expectedKey, keyFromLog)) {
System.out.println("expectedKey=" + expectedKey +
" logKey=" + keyFromLog);
}
}
assertTrue("Key " + i + " should match",
Arrays.equals(expectedKey, keyFromLog));
/* Check the dup key. */
byte[] dupKeyFromLog = reader.getDupTreeKey();
byte[] expectedDupKey = expected.dupKey;
assertTrue(Arrays.equals(expectedDupKey, dupKeyFromLog));
assertEquals(expected.txnId,
reader.getTxnId().longValue());
} else {
/* Should be a txn commit record. */
assertEquals(expected.txnId,
reader.getTxnCommitId());
}
if (redo) {
i++;
} else {
i--;
}
}
int expectedCount = checkList.size() - checkIndex;
assertEquals(expectedCount, reader.getNumRead());
}
private class CheckInfo {
long lsn;
LN ln;
byte[] key;
byte[] dupKey;
long txnId;
CheckInfo(long lsn, LN ln, byte[] key, byte[] dupKey, long txnId) {
this.lsn = lsn;
this.ln = ln;
this.key = key;
this.dupKey = dupKey;
this.txnId = txnId;
}
}
}