/*
* Copyright 2008-2009 LinkedIn, Inc
*
* Licensed 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 voldemort.versioning;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static voldemort.TestUtils.getClock;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import org.junit.Test;
import voldemort.TestUtils;
import com.google.common.collect.Lists;
/**
* VectorClock tests
*
*
*/
@SuppressWarnings("deprecation")
public class VectorClockTest {
@Test
public void testEqualsAndHashcode() {
VectorClock one = getClock(1, 2);
VectorClock other = getClock(1, 2);
assertEquals(one, other);
assertEquals(one.hashCode(), other.hashCode());
}
@Test
public void testComparisons() {
assertTrue("The empty clock should not happen before itself.",
getClock().compare(getClock()) != Occurred.CONCURRENTLY);
assertTrue("A clock should not happen before an identical clock.",
getClock(1, 1, 2).compare(getClock(1, 1, 2)) != Occurred.CONCURRENTLY);
assertTrue(" A clock should happen before an identical clock with a single additional event.",
getClock(1, 1, 2).compare(getClock(1, 1, 2, 3)) == Occurred.BEFORE);
assertTrue("Clocks with different events should be concurrent.",
getClock(1).compare(getClock(2)) == Occurred.CONCURRENTLY);
assertTrue("Clocks with different events should be concurrent.",
getClock(1, 1, 2).compare(getClock(1, 1, 3)) == Occurred.CONCURRENTLY);
assertTrue("Clocks with different events should be concurrent.",
getClock(1, 2, 3, 3).compare(getClock(1, 1, 2, 3)) == Occurred.CONCURRENTLY);
assertTrue(getClock(2, 2).compare(getClock(1, 2, 2, 3)) == Occurred.BEFORE
&& getClock(1, 2, 2, 3).compare(getClock(2, 2)) == Occurred.AFTER);
}
@Test
public void testMerge() {
// merging two clocks should create a clock contain the element-wise
// maximums
assertEquals("Two empty clocks merge to an empty clock.",
getClock().merge(getClock()),
getClock());
assertEquals("Merge of a clock with itself does nothing",
getClock(1).merge(getClock(1)),
getClock(1));
assertEquals(getClock(1).merge(getClock(2)), getClock(1, 2));
assertEquals(getClock(1).merge(getClock(1, 2)), getClock(1, 2));
assertEquals(getClock(1, 2).merge(getClock(1)), getClock(1, 2));
assertEquals("Two-way merge fails.",
getClock(1, 1, 1, 2, 3, 5).merge(getClock(1, 2, 2, 4)),
getClock(1, 1, 1, 2, 2, 3, 4, 5));
assertEquals(getClock(2, 3, 5).merge(getClock(1, 2, 2, 4, 7)),
getClock(1, 2, 2, 3, 4, 5, 7));
}
/**
* See github issue #25: Incorrect coercion of version to short before
* passing to ClockEntry constructor
*/
@Test
public void testMergeWithLargeVersion() {
VectorClock clock1 = getClock(1);
VectorClock clock2 = new VectorClock(Lists.newArrayList(new ClockEntry((short) 1,
Short.MAX_VALUE + 1)),
System.currentTimeMillis());
VectorClock mergedClock = clock1.merge(clock2);
assertEquals(mergedClock.getMaxVersion(), Short.MAX_VALUE + 1);
}
@Test
public void testSerialization() {
assertEquals("The empty clock serializes incorrectly.",
getClock(),
new VectorClock(getClock().toBytes()));
VectorClock clock = getClock(1, 1, 2, 3, 4, 4, 6);
assertEquals("This clock does not serialize to itself.",
clock,
new VectorClock(clock.toBytes()));
}
@Test
public void testSerializationBackwardCompatibility() {
assertEquals("The empty clock serializes incorrectly.",
getClock(),
new VectorClock(getClock().toBytes()));
VectorClock clock = getClock(1, 1, 2, 3, 4, 4, 6);
// Old Vector Clock would serialize to this:
// 0 5 1 0 1 2 0 2 1 0 3 1 0 4 2 0 6 1 [timestamp]
byte[] knownSerializedHead = { 0, 5, 1, 0, 1, 2, 0, 2, 1, 0, 3, 1, 0, 4, 2, 0, 6, 1 };
byte[] serialized = clock.toBytes();
for(int index = 0; index < knownSerializedHead.length; index++) {
assertEquals("byte at index " + index + " is not equal",
knownSerializedHead[index],
serialized[index]);
}
}
/**
* Pre-condition: timestamp is ignored in determine vector clock equality
*/
@Test
public void testDeserializationBackwardCompatibility() {
assertEquals("The empty clock serializes incorrectly.",
getClock(),
new VectorClock(getClock().toBytes()));
VectorClock clock = getClock(1, 1, 2, 3, 4, 4, 6);
// Old Vector Clock would serialize to this:
// 0 5; 1; 0 1, 2; 0 2, 1; 0 3, 1; 0 4, 2; 0 6, 1; [timestamp=random]
byte[] knownSerialized = { 0, 5, 1, 0, 1, 2, 0, 2, 1, 0, 3, 1, 0, 4, 2, 0, 6, 1, 0, 0, 1,
0x3e, 0x7b, (byte) 0x8c, (byte) 0x9d, 0x19 };
assertEquals("vector clock does not deserialize correctly on given byte array",
clock,
new VectorClock(knownSerialized));
DataInputStream ds = new DataInputStream(new ByteArrayInputStream(knownSerialized));
VectorClock clock2 = VectorClock.createVectorClock(ds);
assertEquals("vector clock does not deserialize correctly on given input stream",
clock,
clock2);
}
@Test
public void testSerializationWraps() {
VectorClock clock = getClock(1, 1, 2, 3, 3, 6);
for(int i = 0; i < 300; i++)
clock.incrementVersion(2, System.currentTimeMillis());
assertEquals("Clock does not serialize to itself.", clock, new VectorClock(clock.toBytes()));
}
@Test
public void testIncrementOrderDoesntMatter() {
// Clocks should have the property that no matter what order the
// increment operations are done in the resulting clocks are equal
int numTests = 10;
int numNodes = 10;
int numValues = 100;
VectorClock[] clocks = new VectorClock[numNodes];
for(int t = 0; t < numTests; t++) {
int[] test = TestUtils.randomInts(numNodes, numValues);
for(int n = 0; n < numNodes; n++)
clocks[n] = getClock(TestUtils.shuffle(test));
// test all are equal
for(int n = 0; n < numNodes - 1; n++)
assertEquals("Clock " + n + " and " + (n + 1) + " are not equal.",
clocks[n],
clocks[n + 1]);
}
}
@Test
public void testIncrementAndSerialize() {
int node = 1;
VectorClock vc = getClock(node);
assertEquals(node, vc.getMaxVersion());
int increments = 3000;
for(int i = 0; i < increments; i++) {
vc.incrementVersion(node, 45);
// serialize
vc = new VectorClock(vc.toBytes());
}
assertEquals(increments + 1, vc.getMaxVersion());
}
/**
* A test for comparing vector clocks that nodes of clock entries are not
* sorted In case people insert clock entries without using increment we
* need to test although it has been deprecated
*/
@Test
public void testNodeClockEntryDeprecate() {
VectorClock vc1 = new VectorClock();
try {
vc1.getEntries().add(new ClockEntry((short) 2, 2));
fail("Did not throw UnsupportedOperationException");
} catch(UnsupportedOperationException e) {
}
}
@Test
public void testVersion0NotAcceptable() {
try {
ClockEntry clockEntry = new ClockEntry();
clockEntry.setVersion(0);
fail("Did not throw IllegalArgumentException");
} catch(IllegalArgumentException e) {}
}
@Test
public void testNodeLess0NotAcceptable() {
try {
ClockEntry clockEntry = new ClockEntry();
clockEntry.setNodeId((short) -1);
fail("Did not throw IllegalArgumentException");
} catch(IllegalArgumentException e) {}
}
}