/*
* Copyright 2002-2017 the original author or authors.
*
* 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 org.springframework.cache;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.UUID;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.hamcrest.core.Is.*;
import static org.junit.Assert.*;
/**
* @author Stephane Nicoll
*/
public abstract class AbstractCacheTests<T extends Cache> {
@Rule
public final ExpectedException thrown = ExpectedException.none();
protected final static String CACHE_NAME = "testCache";
protected abstract T getCache();
protected abstract Object getNativeCache();
@Test
public void testCacheName() throws Exception {
assertEquals(CACHE_NAME, getCache().getName());
}
@Test
public void testNativeCache() throws Exception {
assertSame(getNativeCache(), getCache().getNativeCache());
}
@Test
public void testCachePut() throws Exception {
T cache = getCache();
String key = createRandomKey();
Object value = "george";
assertNull(cache.get(key));
assertNull(cache.get(key, String.class));
assertNull(cache.get(key, Object.class));
cache.put(key, value);
assertEquals(value, cache.get(key).get());
assertEquals(value, cache.get(key, String.class));
assertEquals(value, cache.get(key, Object.class));
assertEquals(value, cache.get(key, (Class<?>) null));
cache.put(key, null);
assertNotNull(cache.get(key));
assertNull(cache.get(key).get());
assertNull(cache.get(key, String.class));
assertNull(cache.get(key, Object.class));
}
@Test
public void testCachePutIfAbsent() throws Exception {
T cache = getCache();
String key = createRandomKey();
Object value = "initialValue";
assertNull(cache.get(key));
assertNull(cache.putIfAbsent(key, value));
assertEquals(value, cache.get(key).get());
assertEquals("initialValue", cache.putIfAbsent(key, "anotherValue").get());
assertEquals(value, cache.get(key).get()); // not changed
}
@Test
public void testCacheRemove() throws Exception {
T cache = getCache();
String key = createRandomKey();
Object value = "george";
assertNull(cache.get(key));
cache.put(key, value);
}
@Test
public void testCacheClear() throws Exception {
T cache = getCache();
assertNull(cache.get("enescu"));
cache.put("enescu", "george");
assertNull(cache.get("vlaicu"));
cache.put("vlaicu", "aurel");
cache.clear();
assertNull(cache.get("vlaicu"));
assertNull(cache.get("enescu"));
}
@Test
public void testCacheGetCallable() {
doTestCacheGetCallable("test");
}
@Test
public void testCacheGetCallableWithNull() {
doTestCacheGetCallable(null);
}
private void doTestCacheGetCallable(Object returnValue) {
T cache = getCache();
String key = createRandomKey();
assertNull(cache.get(key));
Object value = cache.get(key, () -> returnValue );
assertEquals(returnValue, value);
assertEquals(value, cache.get(key).get());
}
@Test
public void testCacheGetCallableNotInvokedWithHit() {
doTestCacheGetCallableNotInvokedWithHit("existing");
}
@Test
public void testCacheGetCallableNotInvokedWithHitNull() {
doTestCacheGetCallableNotInvokedWithHit(null);
}
private void doTestCacheGetCallableNotInvokedWithHit(Object initialValue) {
T cache = getCache();
String key = createRandomKey();
cache.put(key, initialValue);
Object value = cache.get(key, () -> {
throw new IllegalStateException("Should not have been invoked");
});
assertEquals(initialValue, value);
}
@Test
public void testCacheGetCallableFail() {
T cache = getCache();
String key = createRandomKey();
assertNull(cache.get(key));
try {
cache.get(key, () -> {
throw new UnsupportedOperationException("Expected exception");
});
}
catch (Cache.ValueRetrievalException ex) {
assertNotNull(ex.getCause());
assertEquals(UnsupportedOperationException.class, ex.getCause().getClass());
}
}
/**
* Test that a call to get with a Callable concurrently properly synchronize the
* invocations.
*/
@Test
public void testCacheGetSynchronized() throws InterruptedException {
T cache = getCache();
final AtomicInteger counter = new AtomicInteger();
final List<Object> results = new CopyOnWriteArrayList<>();
final CountDownLatch latch = new CountDownLatch(10);
String key = createRandomKey();
Runnable run = () -> {
try {
Integer value = cache.get(key, () -> {
Thread.sleep(50); // make sure the thread will overlap
return counter.incrementAndGet();
});
results.add(value);
}
finally {
latch.countDown();
}
};
for (int i = 0; i < 10; i++) {
new Thread(run).start();
}
latch.await();
assertEquals(10, results.size());
results.forEach(r -> assertThat(r, is(1))); // Only one method got invoked
}
protected String createRandomKey() {
return UUID.randomUUID().toString();
}
}