/**
* Licensed to the Apache Software Foundation (ASF) 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 org.waveprotocol.box.server.waveserver;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.ArrayList;
import junit.framework.TestCase;
import org.waveprotocol.wave.federation.Proto.ProtocolAppliedWaveletDelta;
import org.waveprotocol.wave.model.id.IdURIEncoderDecoder;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta;
import org.waveprotocol.wave.model.operation.wave.WaveletDelta;
import org.waveprotocol.wave.model.testing.DeltaTestUtil;
import org.waveprotocol.wave.model.version.HashedVersion;
import org.waveprotocol.wave.model.version.HashedVersionFactory;
import org.waveprotocol.wave.model.version.HashedVersionFactoryImpl;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.data.ReadableWaveletData;
import org.waveprotocol.wave.util.escapers.jvm.JavaUrlCodec;
import org.waveprotocol.wave.federation.Proto;
import org.waveprotocol.box.common.ListReceiver;
import org.waveprotocol.box.common.Receiver;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Tests for {@link WaveletState} implementations.
*
* @author anorth@google.com (Alex North)
* @author akaplanov@gmail.com (Andrew Kaplanov)
*/
public abstract class WaveletStateTestBase extends TestCase {
private static final WaveletName NAME = WaveletName.of(WaveId.of("example.com", "waveid"),
WaveletId.of("example.com", "waveletid"));
private static final ParticipantId AUTHOR = ParticipantId.ofUnsafe("author@example.com");
private static final DeltaTestUtil UTIL = new DeltaTestUtil(AUTHOR);
private static final long TS = 1234567890L;
private static final long TS2 = TS + 1;
private static final long TS3 = TS2 + 1;
private static final IdURIEncoderDecoder URI_CODEC =
new IdURIEncoderDecoder(new JavaUrlCodec());
private static final HashedVersionFactory HASH_FACTORY = new HashedVersionFactoryImpl(URI_CODEC);
private static final HashedVersion V0 = HASH_FACTORY.createVersionZero(NAME);
/**
* Creates a new, empty wavelet state.
*/
protected abstract WaveletState createEmptyState(WaveletName name) throws Exception;
/**
* Waits for all pending persistence operations to be completed. All
* persistence listener callbacks must be completed before this method
* returns.
*/
protected abstract void awaitPersistence() throws Exception;
private WaveletDeltaRecord d1;
private WaveletDeltaRecord d2;
private WaveletDeltaRecord d3;
private WaveletState target;
@Override
public void setUp() throws Exception {
d1 = makeDelta(V0, TS, 2);
d2 = makeDelta(d1.getResultingVersion(), TS2, 2);
d3 = makeDelta(d2.getResultingVersion(), TS3, 1);
target = createEmptyState(NAME);
}
public void testReportsWaveletName() {
assertEquals(NAME, target.getWaveletName());
}
public void testEmptyStateIsEmpty() {
assertNull(target.getSnapshot());
assertEquals(V0, target.getCurrentVersion());
assertEquals(V0, target.getHashedVersion(0));
assertNull(target.getTransformedDelta(V0));
assertNull(target.getAppliedDelta(V0));
}
public void testSnapshotMetadataReflectsDeltas() throws Exception {
HashedVersion v2 = d1.getResultingVersion();
appendDeltas(d1);
assertEquals(v2, target.getCurrentVersion());
ReadableWaveletData snapshot = target.getSnapshot();
assertEquals(AUTHOR, snapshot.getCreator());
assertEquals(v2, snapshot.getHashedVersion());
assertEquals(TS, snapshot.getCreationTime());
assertEquals(TS, snapshot.getLastModifiedTime());
assertEquals(2, snapshot.getVersion());
HashedVersion v4 = d2.getResultingVersion();
appendDeltas(d2);
assertEquals(v4, target.getCurrentVersion());
snapshot = target.getSnapshot();
assertEquals(v4, snapshot.getHashedVersion());
assertEquals(4, snapshot.getVersion());
// Last-modified-time doesn't change due to unworthiness.
}
public void testHashedVersionAccessibleOnDeltaBoundaries() throws Exception {
appendDeltas(d1, d2, d3);
assertEquals(V0, target.getHashedVersion(0));
assertEquals(d1.getResultingVersion(), target.getHashedVersion(2));
assertEquals(d2.getResultingVersion(), target.getHashedVersion(4));
assertEquals(d3.getResultingVersion(), target.getHashedVersion(5));
assertNull(target.getHashedVersion(1));
assertNull(target.getHashedVersion(3));
assertNull(target.getHashedVersion(6));
}
public void testDeltasAccessibleByBeginVersion() throws Exception {
appendDeltas(d1, d2, d3);
assertEquals(d1.getTransformedDelta(), target.getTransformedDelta(V0));
assertEquals(d1.getAppliedDelta(), target.getAppliedDelta(V0));
assertEquals(d2.getTransformedDelta(), target.getTransformedDelta(d1.getResultingVersion()));
assertEquals(d2.getAppliedDelta(), target.getAppliedDelta(d1.getResultingVersion()));
assertEquals(d3.getTransformedDelta(), target.getTransformedDelta(d2.getResultingVersion()));
assertEquals(d3.getAppliedDelta(), target.getAppliedDelta(d2.getResultingVersion()));
// Wrong hashes return null.
assertNull(target.getTransformedDelta(HashedVersion.unsigned(0)));
assertNull(target.getAppliedDelta(HashedVersion.unsigned(0)));
}
public void testDeltasAccesssibleByEndVersion() throws Exception {
appendDeltas(d1, d2, d3);
for (WaveletDeltaRecord d : Arrays.asList(d1, d2, d3)) {
assertEquals(d.getTransformedDelta(),
target.getTransformedDeltaByEndVersion(d.getResultingVersion()));
assertEquals(d.getAppliedDelta(),
target.getAppliedDeltaByEndVersion(d.getResultingVersion()));
}
// Wrong hashes return null.
assertNull(target.getTransformedDeltaByEndVersion(
HashedVersion.unsigned(d1.getResultingVersion().getVersion())));
assertNull(target.getAppliedDeltaByEndVersion(
HashedVersion.unsigned(d1.getResultingVersion().getVersion())));
}
public void testDeltaHistoryRequiresCorrectHash() throws Exception {
appendDeltas(d1);
target.persist(d1.getResultingVersion());
Receiver<TransformedWaveletDelta> transformedDeltasReceiver =
new ListReceiver<TransformedWaveletDelta>();
Receiver<ByteStringMessage<Proto.ProtocolAppliedWaveletDelta>> appliedDeltasReceiver =
new ListReceiver<ByteStringMessage<Proto.ProtocolAppliedWaveletDelta>>();
// Wrong start hash.
checkGetTransformedDeltasThrowsException(HashedVersion.unsigned(0), d1.getResultingVersion(), transformedDeltasReceiver,
IllegalArgumentException.class);
checkGetAppliedDeltasThrowsException(HashedVersion.unsigned(0), d1.getResultingVersion(), appliedDeltasReceiver,
IllegalArgumentException.class);
// Wrong end hash.
checkGetTransformedDeltasThrowsException(V0, HashedVersion.unsigned(d1.getResultingVersion().getVersion()),
transformedDeltasReceiver, IllegalArgumentException.class);
checkGetAppliedDeltasThrowsException(V0, HashedVersion.unsigned(d1.getResultingVersion().getVersion()),
appliedDeltasReceiver, IllegalArgumentException.class);
}
@SuppressWarnings("rawtypes")
private void checkGetTransformedDeltasThrowsException(HashedVersion startVersion, HashedVersion endVersion,
Receiver<TransformedWaveletDelta> receiver, Class exceptionClass) {
try {
target.getTransformedDeltaHistory(startVersion, endVersion, receiver);
fail("Expected exception not thrown.");
} catch (Exception ex) {
assertEquals(IllegalArgumentException.class, exceptionClass);
}
}
@SuppressWarnings("rawtypes")
private void checkGetAppliedDeltasThrowsException(HashedVersion startVersion, HashedVersion endVersion,
Receiver<ByteStringMessage<Proto.ProtocolAppliedWaveletDelta>> receiver, Class exceptionClass) {
try {
target.getAppliedDeltaHistory(startVersion, endVersion, receiver);
fail("Expected exception not thrown.");
} catch (Exception ex) {
assertEquals(IllegalArgumentException.class, exceptionClass);
}
}
public void testSingleDeltaHistoryAccessible() throws Exception {
appendDeltas(d1);
target.persist(d1.getResultingVersion());
ListReceiver<TransformedWaveletDelta> transformedDeltasReceiver =
new ListReceiver<TransformedWaveletDelta>();
target.getTransformedDeltaHistory(V0, d1.getResultingVersion(), transformedDeltasReceiver);
assertEquals(1, transformedDeltasReceiver.size());
assertEquals(d1.getTransformedDelta(), transformedDeltasReceiver.get(0));
ListReceiver<ByteStringMessage<Proto.ProtocolAppliedWaveletDelta>> appliedDeltasReceiver =
new ListReceiver<ByteStringMessage<Proto.ProtocolAppliedWaveletDelta>>();
target.getAppliedDeltaHistory(V0, d1.getResultingVersion(), appliedDeltasReceiver);
assertEquals(1, appliedDeltasReceiver.size());
assertEquals(d1.getAppliedDelta(), Iterables.getOnlyElement(appliedDeltasReceiver));
}
public void testDeltaHistoryQueriesCorrectHistory() throws Exception {
appendDeltas(d1, d2, d3);
target.persist(d3.getResultingVersion());
checkHistoryForDeltas(d1);
checkHistoryForDeltas(d1, d2);
checkHistoryForDeltas(d2, d3);
checkHistoryForDeltas(d1, d2, d3);
}
public void testDeltaHistoryInterruptQueriesCorrectHistory() throws Exception {
appendDeltas(d1, d2, d3);
target.persist(d3.getResultingVersion());
checkHistoryForDeltasWithInterrupt(0, d1);
checkHistoryForDeltasWithInterrupt(1, d1, d2);
checkHistoryForDeltasWithInterrupt(2, d1, d2, d3);
}
/**
* Checks that a request for the deltas spanning a contiguous sequence of
* delta facets produces correct results.
*/
private void checkHistoryForDeltas(WaveletDeltaRecord... deltas) {
HashedVersion beginVersion = deltas[0].getAppliedAtVersion();
HashedVersion endVersion = deltas[deltas.length - 1].getTransformedDelta().getResultingVersion();
{
List<TransformedWaveletDelta> expected = Lists.newArrayListWithExpectedSize(deltas.length);
for (WaveletDeltaRecord d : deltas) {
expected.add(d.getTransformedDelta());
}
ListReceiver<TransformedWaveletDelta> transformedDeltasReceiver =
new ListReceiver<TransformedWaveletDelta>();
target.getTransformedDeltaHistory(beginVersion, endVersion, transformedDeltasReceiver);
assertTrue(Iterables.elementsEqual(expected, transformedDeltasReceiver));
}
{
List<ByteStringMessage<ProtocolAppliedWaveletDelta>> expected =
Lists.newArrayListWithExpectedSize(deltas.length);
for (WaveletDeltaRecord d : deltas) {
expected.add(d.getAppliedDelta());
}
ListReceiver<ByteStringMessage<Proto.ProtocolAppliedWaveletDelta>> appliedDeltasReceiver =
new ListReceiver<ByteStringMessage<Proto.ProtocolAppliedWaveletDelta>>();
target.getAppliedDeltaHistory(beginVersion, endVersion, appliedDeltasReceiver);
assertTrue(Iterables.elementsEqual(expected, appliedDeltasReceiver));
}
}
private void checkHistoryForDeltasWithInterrupt(final int interruptIndex, WaveletDeltaRecord... deltas) {
HashedVersion beginVersion = deltas[0].getAppliedAtVersion();
HashedVersion endVersion = deltas[interruptIndex].getTransformedDelta().getResultingVersion();
{
List<TransformedWaveletDelta> expected = Lists.newArrayListWithExpectedSize(interruptIndex+1);
for (int i=0; i <= interruptIndex; i++) {
expected.add(deltas[i].getTransformedDelta());
}
final AtomicInteger index = new AtomicInteger(0);
final List<TransformedWaveletDelta> transformedDeltas = new ArrayList<TransformedWaveletDelta>();
target.getTransformedDeltaHistory(beginVersion, endVersion, new Receiver<TransformedWaveletDelta>() {
@Override
public boolean put(TransformedWaveletDelta delta) {
transformedDeltas.add(delta);
return index.getAndIncrement() < interruptIndex;
}
});
assertTrue(Iterables.elementsEqual(expected, transformedDeltas));
}
{
List<ByteStringMessage<ProtocolAppliedWaveletDelta>> expected =
Lists.newArrayListWithExpectedSize(interruptIndex+1);
for (int i=0; i <= interruptIndex; i++) {
expected.add(deltas[i].getAppliedDelta());
}
final AtomicInteger index = new AtomicInteger(0);
final List<ByteStringMessage<Proto.ProtocolAppliedWaveletDelta>> appliedDeltas =
new ArrayList<ByteStringMessage<Proto.ProtocolAppliedWaveletDelta>>();
target.getAppliedDeltaHistory(beginVersion, endVersion,
new Receiver<ByteStringMessage<ProtocolAppliedWaveletDelta>>() {
@Override
public boolean put(ByteStringMessage<ProtocolAppliedWaveletDelta> delta) {
appliedDeltas.add(delta);
return index.getAndIncrement() < interruptIndex;
}
});
assertTrue(Iterables.elementsEqual(expected, appliedDeltas));
}
}
public void checkSingleDeltaPersistFutureDone() throws Exception {
appendDeltas(d1);
Future<Void> future = target.persist(d1.getResultingVersion());
awaitPersistence();
assertTrue(future.isDone());
assertEquals(null, future.get());
assertEquals(d1.getResultingVersion(), target.getLastPersistedVersion());
}
public void checkManyDeltasPersistFutureDone() throws Exception {
appendDeltas(d1, d2, d3);
Future<Void> future = target.persist(d3.getResultingVersion());
awaitPersistence();
assertTrue(future.isDone());
assertEquals(null, future.get());
assertEquals(d3.getResultingVersion(), target.getLastPersistedVersion());
}
public void testCanPersistOnlySomeDeltas() throws Exception {
appendDeltas(d1, d2, d3);
Future<Void> future = target.persist(d2.getResultingVersion());
awaitPersistence();
assertTrue(future.isDone());
assertEquals(null, future.get());
assertEquals(d2.getResultingVersion(), target.getLastPersistedVersion());
future = target.persist(d3.getResultingVersion());
awaitPersistence();
assertTrue(future.isDone());
assertEquals(null, future.get());
assertEquals(d3.getResultingVersion(), target.getLastPersistedVersion());
}
/**
* Applies a delta to the target.
*/
private void appendDeltas(WaveletDeltaRecord... deltas) throws InvalidProtocolBufferException,
OperationException {
for (WaveletDeltaRecord delta : deltas) {
target.appendDelta(delta);
}
}
/**
* Creates a delta of no-ops and builds the corresponding applied and
* transformed delta objects.
*/
private static WaveletDeltaRecord makeDelta(HashedVersion appliedAtVersion, long timestamp,
int numOps) throws InvalidProtocolBufferException {
// Use no-op delta so the ops can actually apply.
WaveletDelta delta = UTIL.makeNoOpDelta(appliedAtVersion, timestamp, numOps);
ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta =
WaveServerTestUtil.buildAppliedDelta(delta, timestamp);
TransformedWaveletDelta transformedDelta =
AppliedDeltaUtil.buildTransformedDelta(appliedDelta, delta);
return new WaveletDeltaRecord(appliedAtVersion, appliedDelta, transformedDelta);
}
}