/*
* 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.store;
import static voldemort.TestUtils.getClock;
import static voldemort.TestUtils.randomLetters;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import org.junit.Test;
import voldemort.TestUtils;
import voldemort.utils.ByteArray;
import voldemort.versioning.ObsoleteVersionException;
import voldemort.versioning.VectorClock;
import voldemort.versioning.Version;
import voldemort.versioning.Versioned;
import com.google.common.base.Objects;
public abstract class AbstractStoreTest<K, V, T> extends TestCase {
public abstract Store<K, V, T> getStore() throws Exception;
public abstract List<V> getValues(int numValues);
public abstract List<K> getKeys(int numKeys);
public List<String> getStrings(int numKeys, int size) {
List<String> ts = new ArrayList<String>(numKeys);
for(int i = 0; i < numKeys; i++)
ts.add(randomLetters(size));
return ts;
}
public List<byte[]> getByteValues(int numValues, int size) {
List<byte[]> values = new ArrayList<byte[]>();
for(int i = 0; i < numValues; i++)
values.add(TestUtils.randomBytes(size));
return values;
}
public List<ByteArray> getByteArrayValues(int numValues, int size) {
List<ByteArray> values = new ArrayList<ByteArray>();
for(int i = 0; i < numValues; i++)
values.add(new ByteArray(TestUtils.randomBytes(size)));
return values;
}
public K getKey() {
return getKeys(1).get(0);
}
public V getValue() {
return getValues(1).get(0);
}
public Version getExpectedVersionAfterPut(Version version) {
return version;
}
protected boolean valuesEqual(V t1, V t2) {
return Objects.equal(t1, t2);
}
protected void assertEquals(String message, Versioned<V> v1, Versioned<V> v2) {
String assertTrueMessage = v1 + " != " + v2 + ".";
if(message != null)
assertTrueMessage += message;
assertTrue(assertTrueMessage, valuesEqual(v1.getValue(), v2.getValue()));
assertEquals(message, v1.getVersion(), v2.getVersion());
}
protected void assertEquals(Versioned<V> v1, Versioned<V> v2) {
assertEquals(null, v1, v2);
}
public void assertContains(Collection<Versioned<V>> collection, Versioned<V> value) {
boolean found = false;
for(Versioned<V> t: collection)
if(valuesEqual(t.getValue(), value.getValue()))
found = true;
assertTrue(collection + " does not contain " + value + ".", found);
}
@Test
public void testNullKeys() throws Exception {
Store<K, V, T> store = getStore();
try {
store.put(null, new Versioned<V>(getValue()), null);
fail("Store should not put null keys!");
} catch(IllegalArgumentException e) {
// this is good
}
try {
store.get(null, null);
fail("Store should not get null keys!");
} catch(IllegalArgumentException e) {
// this is good
}
try {
store.getAll(null, null);
fail("Store should not getAll null keys!");
} catch(IllegalArgumentException e) {
// this is good
}
try {
store.getAll(Collections.<K> singleton(null),
Collections.<K, T> singletonMap(null, null));
fail("Store should not getAll null keys!");
} catch(IllegalArgumentException e) {
// this is good
}
try {
store.delete(null, new VectorClock());
fail("Store should not delete null keys!");
} catch(IllegalArgumentException e) {
// this is good
}
}
@Test
public void testPutNullValue() {
// Store<K,V> store = getStore();
// K key = getKey();
// store.put(key, new Versioned<V>(null));
// List<Versioned<V>> found = store.get(key);
// assertEquals("Wrong number of values.", 1, found.size());
// assertEquals("Returned non-null value.", null,
// found.get(0).getValue());
}
@Test
public void testGetAndDeleteNonExistentKey() throws Exception {
K key = getKey();
Store<K, V, T> store = getStore();
List<Versioned<V>> found = store.get(key, null);
assertEquals("Found non-existent key: " + found, 0, found.size());
assertTrue("Delete of non-existent key succeeded.",
!store.delete(key, getClock(1, 1, 2, 2, 3, 3)));
}
private void testObsoletePutFails(String message,
Store<K, V, T> store,
K key,
Versioned<V> versioned) {
VectorClock clock = (VectorClock) versioned.getVersion();
clock = clock.clone();
try {
store.put(key, versioned, null);
fail(message);
} catch(ObsoleteVersionException e) {
// this is good, but check that we didn't fuck with the version
assertEquals(clock, versioned.getVersion());
}
}
@Test
public void testFetchedEqualsPut() throws Exception {
K key = getKey();
Store<K, V, T> store = getStore();
VectorClock clock = getClock(1, 1, 2, 3, 3, 4);
V value = getValue();
assertEquals("Store not empty at start!", 0, store.get(key, null).size());
Versioned<V> versioned = new Versioned<V>(value, clock);
store.put(key, versioned, null);
List<Versioned<V>> found = store.get(key, null);
assertValueEquals(versioned.getValue(), found);
}
@Test
public void testVersionedPut() throws Exception {
K key = getKey();
Store<K, V, T> store = getStore();
VectorClock clock = getClock(1, 1);
VectorClock clockCopy = clock.clone();
V value = getValue();
assertEquals("Store not empty at start!", 0, store.get(key, null).size());
Versioned<V> versioned = new Versioned<V>(value, clock);
// put initial version
store.put(key, versioned, null);
assertContains(store.get(key, null), versioned);
// test that putting obsolete versions fails
testObsoletePutFails("Put of identical version/value succeeded.",
store,
key,
new Versioned<V>(value, clockCopy));
testObsoletePutFails("Put of identical version succeeded.",
store,
key,
new Versioned<V>(getValue(), clockCopy));
testObsoletePutFails("Put of obsolete version succeeded.",
store,
key,
new Versioned<V>(getValue(), getClock(1)));
assertEquals("Should still only be one version in store.", store.get(key, null).size(), 1);
assertContains(store.get(key, null), versioned);
// test that putting a concurrent version succeeds
if(allowConcurrentOperations()) {
store.put(key, new Versioned<V>(getValue(), getClock(1, 2)), null);
assertEquals(2, store.get(key, null).size());
} else {
try {
store.put(key, new Versioned<V>(getValue(), getClock(1, 2)), null);
fail();
} catch(ObsoleteVersionException e) {
// expected
}
}
// test that putting an incremented version succeeds
Versioned<V> newest = new Versioned<V>(getValue(), getClock(1, 1, 2, 2));
store.put(key, newest, null);
assertContains(store.get(key, null), newest);
}
@Test
public void testDelete() throws Exception {
K key = getKey();
Store<K, V, T> store = getStore();
VectorClock c1 = getClock(1, 1);
VectorClock c2 = getClock(1, 2);
V value = getValue();
// can't delete something that isn't there
assertTrue(!store.delete(key, c1));
// put two conflicting versions, then delete one
Versioned<V> v1 = new Versioned<V>(value, c1);
Versioned<V> v2 = new Versioned<V>(value, c2);
store.put(key, v1, null);
store.put(key, v2, null);
assertTrue("Delete failed!", store.delete(key, v1.getVersion()));
List<Versioned<V>> found = store.get(key, null);
// check that there is a single remaining version, namely the
// non-deleted
assertValueEquals(v2.getValue(), found);
assertEquals(v2.getVersion(), found.get(0).getVersion());
// now delete that version too
assertTrue("Delete failed!", store.delete(key, c2));
assertEquals(0, store.get(key, null).size());
}
@Test
public void testGetVersions() throws Exception {
List<K> keys = getKeys(2);
K key = keys.get(0);
V value = getValue();
Store<K, V, T> store = getStore();
store.put(key, Versioned.value(value), null);
List<Versioned<V>> versioneds = store.get(key, null);
List<Version> versions = store.getVersions(key);
assertEquals(1, versioneds.size());
assertTrue(versions.size() > 0);
for(int i = 0; i < versions.size(); i++)
assertEquals(versioneds.get(0).getVersion(), versions.get(i));
assertEquals(0, store.getVersions(keys.get(1)).size());
}
@Test
public void testGetAll() throws Exception {
Store<K, V, T> store = getStore();
int putCount = 10;
List<K> keys = getKeys(putCount);
List<V> values = getValues(putCount);
assertEquals(putCount, values.size());
for(int i = 0; i < putCount; i++)
store.put(keys.get(i), new Versioned<V>(values.get(i)), null);
int countForGet = putCount / 2;
List<K> keysForGet = keys.subList(0, countForGet);
List<V> valuesForGet = values.subList(0, countForGet);
Map<K, List<Versioned<V>>> result = store.getAll(keysForGet, null);
assertGetAllValues(keysForGet, valuesForGet, result);
}
@Test
public void testGetAllWithAbsentKeys() throws Exception {
Store<K, V, T> store = getStore();
Map<K, List<Versioned<V>>> result = store.getAll(getKeys(3), null);
assertEquals(0, result.size());
}
@Test
public void testCloseIsIdempotent() throws Exception {
Store<K, V, T> store = getStore();
store.close();
// second close is okay, should not throw an exception
store.close();
}
protected void assertValueEquals(V expectedValue, List<Versioned<V>> versioneds) {
assertEquals("Value expected to have a single version",1, versioneds.size());
assertTrue("GetAll Get value differs from put value",
valuesEqual(expectedValue, versioneds.get(0).getValue()));
}
protected boolean allowConcurrentOperations() {
return true;
}
protected void assertGetAllValues(List<K> keys,
List<V> values,
Map<K, List<Versioned<V>>> result) {
assertEquals(keys.size(), values.size());
assertEquals(keys.size(), result.size());
for(int i = 0; i < keys.size(); ++i) {
K key = keys.get(i);
V expectedValue = values.get(i);
List<Versioned<V>> versioneds = result.get(key);
assertValueEquals(expectedValue, versioneds);
}
}
}