/**
* Licensed to the zk1931 under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the
* License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.zk1931.jzab;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tests for different log implementations.
*/
@RunWith(Parameterized.class)
public class LogTest extends TestBase {
private static final Logger LOG = LoggerFactory.getLogger(LogTest.class);
Class<Log> logClass;
String fileName;
@Parameterized.Parameters
public static Collection<Object[]> instancesToTest() throws Exception {
return Arrays.asList(
new Object[][] {
{SimpleLog.class, "transaction.log"},
{RollingLog.class, ""}
});
}
public LogTest(Class<Log> logClass, String fileName) {
LOG.debug("Testting impelementation of {}", logClass);
this.logClass = logClass;
this.fileName = fileName;
}
Log getLog() throws Exception {
File f = new File(getDirectory(), this.fileName);
if (logClass == (Class<?>)RollingLog.class) {
// For testing purpose, set the rolling size to be very small.
return logClass.getConstructor(File.class, Long.TYPE)
.newInstance(f, 128);
} else {
return logClass.getConstructor(File.class).newInstance(f);
}
}
void corruptFile(int position) throws Exception {
File file = new File(getDirectory(), "transaction.log");
RandomAccessFile ra = new RandomAccessFile(file, "rw");
ra.seek(position);
ra.write(0xff);
ra.close();
}
/**
* Gets transaction id of all the transactions after zxid in log.
*/
List<Zxid> getZxids(Log log, Zxid startZxid) throws IOException {
List<Zxid> zxids = new ArrayList<Zxid>();
try (Log.LogIterator iter = log.getIterator(startZxid)) {
while (iter.hasNext()) {
Zxid zxid = iter.next().getZxid();
zxids.add(zxid);
}
}
return zxids;
}
@Test
public void testAppend() throws Exception {
Log log = getLog();
// Appends 100 transactions.
appendTxns(log, new Zxid(0, 0), 100);
List<Zxid> zxids = getZxids(log, Zxid.ZXID_NOT_EXIST);
// Check all the transactions are in log.
Assert.assertEquals(100, zxids.size());
// Check if the transactions are correct.
Assert.assertEquals(new Zxid(0, 0), zxids.get(0));
Assert.assertEquals(new Zxid(0, zxids.size() - 1),
zxids.get(zxids.size() - 1));
}
@Test
public void testGetLatestZxid() throws Exception {
Log log = getLog();
// Appends 100 transactions.
appendTxns(log, new Zxid(0, 0), 100);
// Make sure latest zxid is <0, 99>.
Assert.assertEquals(new Zxid(0, 99), log.getLatestZxid());
}
@Test
public void testTruncateFile() throws Exception {
Log log = getLog();
// Appends 100 transactions.
appendTxns(log, new Zxid(0, 0), 100);
// Make sure latest zxid is <0, 99>.
Assert.assertEquals(new Zxid(0, 99), log.getLatestZxid());
int truncIdx = new Random().nextInt(100);
log.truncate(new Zxid(0, truncIdx));
// Make sure latest zxid is <0, truncIdx>.
Assert.assertEquals(new Zxid(0, truncIdx), log.getLatestZxid());
}
@Test
public void testTruncateAll() throws Exception {
Log log = getLog();
// Appends 100 transactions.
appendTxns(log, new Zxid(0, 0), 100);
log.truncate(new Zxid(0, -1));
appendTxns(log, new Zxid(0, 0), 100);
}
@Test
public void testTruncateAndAppend() throws Exception {
Log log = getLog();
// Appends 100 transactions.
appendTxns(log, new Zxid(0, 0), 100);
// Make sure latest zxid is <0, 99>.
Assert.assertEquals(new Zxid(0, 99), log.getLatestZxid());
// Truncates file after <0, 77>
log.truncate(new Zxid(0, 77));
// Make sure latest zxid is <0, 77>.
Assert.assertEquals(new Zxid(0, 77), log.getLatestZxid());
// Appends 22 more transactions.
appendTxns(log, new Zxid(0, 78), 22);
List<Zxid> zxids = getZxids(log, Zxid.ZXID_NOT_EXIST);
// Make sure latest zxid is <0, 99>
Assert.assertEquals(new Zxid(0, 99), log.getLatestZxid());
// Check all the transactions are in log.
Assert.assertEquals(100, zxids.size());
}
/**
* Appending a transaction with a zxid smaller than the previous zxid should
* result in a RuntimeException.
*/
@Test(expected=RuntimeException.class)
public void testAppendSmallerZxid() throws Exception {
Log log = getLog();
log.append(new Transaction(new Zxid(0, 1),
ByteBuffer.wrap("txn".getBytes())));
log.append(new Transaction(new Zxid(0, 0),
ByteBuffer.wrap("txn".getBytes())));
}
/**
* Appending a transaction with the zxid equal to previous zxid should
* result in a RuntimeException.
*/
@Test(expected=RuntimeException.class)
public void testAppendSameZxid() throws Exception {
Log log = getLog();
log.append(new Transaction(new Zxid(0, 1),
ByteBuffer.wrap("txn".getBytes())));
log.append(new Transaction(new Zxid(0, 1),
ByteBuffer.wrap("txn".getBytes())));
}
/**
* Tests whether the getIterator method works as our expectation.
*/
@Test
public void testIterator() throws Exception {
Log log = getLog();
// Appends 100 transactions.
appendTxns(log, new Zxid(0, 0), 100);
Random rand = new Random();
Zxid begZxid = new Zxid(0, rand.nextInt(100));
Log.LogIterator iter = log.getIterator(begZxid);
// Make sure it starts from the correct zxid.
Assert.assertEquals(begZxid, iter.next().getZxid());
// Starts from negative zxid.
iter = log.getIterator(new Zxid(0, -1));
Assert.assertEquals(new Zxid(0, 0), iter.next().getZxid());
}
@Test
public void testRecoverFromExistingLog() throws Exception {
Log log = getLog();
// Appends 100 transactions.
appendTxns(log, new Zxid(0, 0), 100);
log.close();
// Reopen the log.
log = getLog();
List<Zxid> zxids = getZxids(log, Zxid.ZXID_NOT_EXIST);
// Check all the transactions are still in log.
Assert.assertEquals(100, zxids.size());
// Check all the transactions are in correct order.
for (int i = 0; i < zxids.size(); ++i) {
Zxid zxid = new Zxid(0, i);
Assert.assertEquals(zxid, zxids.get(i));
}
}
@Test(expected=RuntimeException.class)
public void testCorruptChecksum() throws Exception {
if (logClass == (Class<?>)SimpleLog.class) {
Log log = getLog();
appendTxns(log, new Zxid(0, 0), 1);
// Corrupts checksum.
corruptFile(0);
// Iterating the log will trigger the checksum error.
log.getIterator(log.getLatestZxid());
log.close();
} else {
throw new RuntimeException("Simulated exception for RollingLog.");
}
}
@Test(expected=RuntimeException.class)
public void testCorruptLength() throws Exception {
if (logClass == (Class<?>)SimpleLog.class) {
Log log = getLog();
appendTxns(log, new Zxid(0, 0), 1);
// Corrupts length.
corruptFile(4);
// Iterating the log will trigger the checksum error.
log.getIterator(log.getLatestZxid());
log.close();
} else {
throw new RuntimeException("Simulated exception for RollingLog.");
}
}
@Test(expected=RuntimeException.class)
public void testCorruptZxid() throws Exception {
if (logClass == (Class<?>)SimpleLog.class) {
Log log = getLog();
appendTxns(log, new Zxid(0, 0), 1);
// Corrupts zxid.
corruptFile(8);
// Iterating the log will trigger the checksum error.
log.getIterator(log.getLatestZxid());
log.close();
} else {
throw new RuntimeException("Simulated exception for RollingLog.");
}
}
@Test(expected=RuntimeException.class)
public void testCorruptType() throws Exception {
if (logClass == (Class<?>)SimpleLog.class) {
Log log = getLog();
appendTxns(log, new Zxid(0, 0), 1);
// Corrupts type.
corruptFile(16);
// Iterating the log will trigger the checksum error.
log.getIterator(log.getLatestZxid());
log.close();
} else {
throw new RuntimeException("Simulated exception for RollingLog.");
}
}
@Test(expected=RuntimeException.class)
public void testCorruptTxn() throws Exception {
if (logClass == (Class<?>)SimpleLog.class) {
Log log = getLog();
appendTxns(log, new Zxid(0, 0), 1);
// Corrupts Transaction.
corruptFile(20);
// Iterating the log will trigger the checksum error.
log.getIterator(log.getLatestZxid());
log.close();
} else {
throw new RuntimeException("Simulated exception for RollingLog.");
}
}
@Test
public void testDivergingPoint() throws Exception {
/*
* case 1:
* the log : <0, 0>, <0, 1>, <1, 1>
* zxid : <0, 2>
* returns (<0, 1>, iter points to <1, 1>)
*/
Log log = getLog();
appendTxns(log, new Zxid(0, 0), 2);
appendTxns(log, new Zxid(1, 1), 1);
Log.DivergingTuple dp = log.firstDivergingPoint(new Zxid(0, 2));
Assert.assertEquals(new Zxid(0, 1), dp.getDivergingZxid());
Assert.assertEquals(new Zxid(1, 1), dp.getIterator().next().getZxid());
/*
* case 2:
* the log : <0, 0>, <0, 1>, <1, 1>
* zxid : <0, 1>
* returns (<0, 1>, iter points to <1, 1>)
*/
dp = log.firstDivergingPoint(new Zxid(0, 1));
Assert.assertEquals(new Zxid(0, 1), dp.getDivergingZxid());
Assert.assertEquals(new Zxid(1, 1), dp.getIterator().next().getZxid());
/*
* case 3:
* the log : <0, 0>, <0, 1>, <1, 1>
* zxid : <1, 2>
* returns (<1, 1>, iter points to end of the log)
*/
dp = log.firstDivergingPoint(new Zxid(1, 2));
Assert.assertEquals(new Zxid(1, 1), dp.getDivergingZxid());
Assert.assertFalse(dp.getIterator().hasNext());
/*
* case 4:
* the log : <0, 2>
* zxid : <0, 1>
* returns (<0, -1>, iter points <0, 2>)
*/
log.truncate(Zxid.ZXID_NOT_EXIST);
appendTxns(log, new Zxid(0, 2), 1);
dp = log.firstDivergingPoint(new Zxid(0, 1));
Assert.assertEquals(Zxid.ZXID_NOT_EXIST, dp.getDivergingZxid());
Assert.assertEquals(new Zxid(0, 2), dp.getIterator().next().getZxid());
/*
* case 5:
* the log : <0, 2>
* zxid : <0, -1>
* returns (<0, -1>, iter points <0, 2>)
*/
dp = log.firstDivergingPoint(Zxid.ZXID_NOT_EXIST);
Assert.assertEquals(Zxid.ZXID_NOT_EXIST, dp.getDivergingZxid());
Assert.assertEquals(new Zxid(0, 2), dp.getIterator().next().getZxid());
/*
* case 6:
* the log : empty
* zxid : <0, 1>
* returns (<0, -1>, iter points to end of the log)
*/
log.truncate(Zxid.ZXID_NOT_EXIST);
dp = log.firstDivergingPoint(new Zxid(0, 1));
Assert.assertEquals(Zxid.ZXID_NOT_EXIST, dp.getDivergingZxid());
Assert.assertFalse(dp.getIterator().hasNext());
}
@Test
public void testSyncEmptyLog() throws Exception {
Log log = getLog();
log.sync();
log.close();
}
}