/*
* Copyright 2014 WANdisco
*
* WANdisco 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 c5db.log;
import c5db.interfaces.log.SequentialEntryCodec;
import com.google.common.collect.Lists;
import org.jmock.Expectations;
import org.jmock.Sequence;
import org.jmock.integration.junit4.JUnitRuleMockery;
import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.List;
import static c5db.log.LogPersistenceService.BytePersistence;
import static c5db.log.LogPersistenceService.PersistenceNavigator;
import static c5db.log.LogTestUtil.anOLogEntry;
import static c5db.log.LogTestUtil.makeEntry;
import static c5db.log.LogTestUtil.nConsecutiveEntries;
import static c5db.log.LogTestUtil.someConsecutiveEntries;
import static c5db.log.ReplicatorLogGenericTestUtil.seqNum;
import static c5db.log.ReplicatorLogGenericTestUtil.term;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
@SuppressWarnings("unchecked")
public class EncodedSequentialLogTest {
@Rule
public JUnitRuleMockery context = new JUnitRuleMockery();
private final BytePersistence persistence = context.mock(BytePersistence.class);
private final SequentialEntryCodec<OLogEntry> codec = context.mock(SequentialEntryCodec.class);
private final PersistenceNavigator navigator = context.mock(PersistenceNavigator.class);
private final SequentialLog<OLogEntry> log = new EncodedSequentialLog<>(persistence, codec, navigator);
@Test
public void writesToTheSuppliedPersistenceObjectUsingTheSuppliedCodec() throws Exception {
OLogEntry entry = makeEntry(seqNum(1), term(2), "data");
context.checking(new Expectations() {{
ignoring(navigator);
allowing(persistence).size();
oneOf(codec).encode(with(equalTo(entry)));
atLeast(1).of(persistence).append(with(any(ByteBuffer[].class)));
}});
log.append(Lists.newArrayList(entry));
}
@Test
public void notifiesTheNavigatorOnceForEveryEntryWritten() throws Exception {
context.checking(new Expectations() {{
ignoring(codec);
ignoring(persistence);
exactly(5).of(navigator).notifyLogging(with(any(Long.class)), with(any(Long.class)));
}});
log.append(nConsecutiveEntries(5));
}
@Test
public void notifiesTheNavigatorWhenTruncating() throws Exception {
long truncationSeqNum = 33;
context.checking(new Expectations() {{
ignoring(codec);
ignoring(persistence);
allowing(navigator).getAddressOfEntry(with(any(Long.class)));
oneOf(navigator).notifyTruncation(truncationSeqNum);
}});
log.truncate(truncationSeqNum);
}
@Test
public void readsEntriesFromTheSuppliedPersistenceObjectUsingTheSuppliedCodec() throws Exception {
long startSeqNum = 77;
long endSeqNum = 83;
context.checking(new Expectations() {{
codecWillReturnEntrySequence(codec, someConsecutiveEntries(startSeqNum, endSeqNum));
allowing(persistence).getReader();
allowing(navigator).getStreamAtSeqNum(startSeqNum);
will(returnValue(aMockInputStream()));
}});
log.subSequence(startSeqNum, endSeqNum);
}
@Test
public void processesTruncationRequestsByDelegatingThemToTheSuppliedPersistence() throws Exception {
long entryAddress = 100;
context.checking(new Expectations() {{
allowing(navigator).notifyTruncation(with(any(Long.class)));
oneOf(navigator).getAddressOfEntry(seqNum(7));
will(returnValue(entryAddress));
oneOf(persistence).truncate(entryAddress);
}});
log.truncate(seqNum(7));
}
@Test
public void returnsNullWhenRequestedToGetTheLastEntryInAnEmptyLog() throws Exception {
context.checking(new Expectations() {{
oneOf(persistence).isEmpty();
will(returnValue(true));
}});
assertThat(log.getLastEntry(), is(nullValue()));
}
@Test
public void delegatesToItsNavigatorToReturnTheLastEntryInANonEmptyLog() throws Exception {
final OLogEntry lastEntry = anOLogEntry();
context.checking(new Expectations() {{
oneOf(persistence).isEmpty();
will(returnValue(false));
oneOf(navigator).getStreamAtLastEntry();
will(returnValue(aMockInputStream()));
oneOf(codec).decode(with(any(InputStream.class)));
will(returnValue(lastEntry));
}});
assertThat(log.getLastEntry(), equalTo(lastEntry));
}
private InputStream aMockInputStream() {
return new InputStream() {
@Override
public int read() throws IOException {
return 0;
}
};
}
private void codecWillReturnEntrySequence(SequentialEntryCodec<OLogEntry> codec, List<OLogEntry> entries) throws Exception {
Sequence seq = context.sequence("Codec#decode method call sequence");
context.checking(new Expectations() {{
for (OLogEntry e : entries) {
oneOf(codec).decode(with(any(InputStream.class)));
will(returnValue(e));
inSequence(seq);
}
}});
}
}