/*
* Copyright Terracotta, 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 org.ehcache.core;
import org.ehcache.config.CacheConfiguration;
import org.ehcache.core.config.BaseCacheConfiguration;
import org.ehcache.core.config.ResourcePoolsHelper;
import org.ehcache.core.events.CacheEventDispatcher;
import org.ehcache.core.exceptions.StorePassThroughException;
import org.ehcache.core.spi.store.Store;
import org.ehcache.core.spi.store.StoreAccessException;
import org.ehcache.spi.loaderwriter.CacheLoadingException;
import org.ehcache.spi.loaderwriter.CacheWritingException;
import org.ehcache.core.spi.function.BiFunction;
import org.ehcache.core.spi.function.Function;
import org.ehcache.core.spi.function.NullaryFunction;
import org.ehcache.spi.loaderwriter.CacheLoaderWriter;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.slf4j.LoggerFactory;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
/**
* @author vfunshteyn
*/
public class EhcacheLoaderWriterTest {
private EhcacheWithLoaderWriter<Number, String> cache;
private Store<Number, String> store;
@SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
store = mock(Store.class);
CacheLoaderWriter<Number, String> loaderWriter = mock(CacheLoaderWriter.class);
final CacheConfiguration<Number, String> config = new BaseCacheConfiguration<Number, String>(Number.class, String.class, null,
null, null, ResourcePoolsHelper.createHeapOnlyPools());
CacheEventDispatcher<Number, String> notifier = mock(CacheEventDispatcher.class);
cache = new EhcacheWithLoaderWriter<Number, String>(
config, store, loaderWriter, notifier, LoggerFactory.getLogger(EhcacheWithLoaderWriter.class + "-" + "EhcacheLoaderWriterTest"));
cache.init();
}
@Test
public void testGet() throws Exception {
when(store.computeIfAbsent(any(Number.class), anyFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Function<Number, String> function = asFunction(invocation);
function.apply((Number)invocation.getArguments()[0]);
return null;
}
});
cache.get(1);
verify(cache.getCacheLoaderWriter()).load(1);
}
@Test
public void testGetThrowsOnCompute() throws Exception {
when(store.computeIfAbsent(any(Number.class), anyFunction())).thenThrow(new StoreAccessException("boom"));
String expected = "foo";
when((String)cache.getCacheLoaderWriter().load(any(Number.class))).thenReturn(expected);
assertThat(cache.get(1), is(expected));
verify(store).remove(1);
}
@Test(expected=CacheLoadingException.class)
public void testGetThrowsOnLoad() throws Exception {
when(store.computeIfAbsent(any(Number.class), anyFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Function<Number, String> function = asFunction(invocation);
try {
function.apply((Number)invocation.getArguments()[0]);
} catch (StorePassThroughException e) {
throw e.getCause();
}
return null;
}
});
when(cache.getCacheLoaderWriter().load(any(Number.class))).thenThrow(new Exception());
cache.get(1);
}
@Test
public void testPut() throws Exception {
when(store.compute(any(Number.class), anyBiFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, String, String> function = asBiFunction(invocation);
function.apply((Number)invocation.getArguments()[0], null);
return null;
}
});
cache.put(1, "one");
verify(cache.getCacheLoaderWriter()).write(1, "one");
}
@Test
public void testPutThrowsOnCompute() throws Exception {
when(store.compute(any(Number.class), anyBiFunction())).thenThrow(new StoreAccessException("boom"));
cache.put(1, "one");
verify(store).remove(1);
verify(cache.getCacheLoaderWriter()).write(1, "one");
}
@Test(expected=CacheWritingException.class)
public void testPutThrowsOnWrite() throws Exception {
when(store.compute(any(Number.class), anyBiFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, String, String> function = asBiFunction(invocation);
try {
function.apply((Number)invocation.getArguments()[0], null);
} catch (StorePassThroughException e) {
throw e.getCause();
}
return null;
}
});
doThrow(new Exception()).when(cache.getCacheLoaderWriter()).write(any(Number.class), anyString());
cache.put(1, "one");
}
@Test
public void testRemove() throws Exception {
when(store.compute(any(Number.class), anyBiFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, String, String> function = asBiFunction(invocation);
function.apply((Number)invocation.getArguments()[0], null);
return null;
}
});
cache.remove(1);
verify(cache.getCacheLoaderWriter()).delete(1);
}
@Test
public void testRemoveThrowsOnCompute() throws Exception {
when(store.compute(any(Number.class), anyBiFunction())).thenThrow(new StoreAccessException("boom"));
cache.remove(1);
verify(store).remove(1);
verify(cache.getCacheLoaderWriter()).delete(1);
}
@Test(expected=CacheWritingException.class)
public void testRemoveThrowsOnWrite() throws Exception {
when(store.compute(any(Number.class), anyBiFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, String, String> function = asBiFunction(invocation);
try {
function.apply((Number)invocation.getArguments()[0], null);
} catch (StorePassThroughException e) {
throw e.getCause();
}
return null;
}
});
doThrow(new Exception()).when(cache.getCacheLoaderWriter()).delete(any(Number.class));
cache.remove(1);
}
@Test
public void testPutIfAbsent_present() throws Exception {
when(store.computeIfAbsent(any(Number.class), anyFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Function<Number, String> function = asFunction(invocation);
Number key = (Number) invocation.getArguments()[0];
if (!key.equals(1)) {
function.apply(key);
}
return null;
}
});
cache.putIfAbsent(1, "foo");
verifyZeroInteractions(cache.getCacheLoaderWriter());
}
@Test
public void testPutIfAbsent_absent() throws Exception {
when(store.computeIfAbsent(any(Number.class), anyFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Function<Number, String> function = asFunction(invocation);
Number key = (Number) invocation.getArguments()[0];
function.apply(key);
return null;
}
});
cache.putIfAbsent(1, "foo");
verify(cache.getCacheLoaderWriter()).load(1);
verify(cache.getCacheLoaderWriter()).write(1, "foo");
}
@Test
public void testPutIfAbsentThrowsOnCompute() throws Exception {
when(store.computeIfAbsent(any(Number.class), anyFunction())).thenThrow(new StoreAccessException("boom"));
cache.putIfAbsent(1, "one");
verify(cache.getCacheLoaderWriter()).write(1, "one");
verify(store).remove(1);
}
@Test(expected=CacheWritingException.class)
public void testPutIfAbsentThrowsOnWrite() throws Exception {
when(store.computeIfAbsent(any(Number.class), anyFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Function<Number, String> function = asFunction(invocation);
try {
function.apply((Number)invocation.getArguments()[0]);
} catch (StorePassThroughException e) {
throw e.getCause();
}
return null;
}
});
doThrow(new Exception()).when(cache.getCacheLoaderWriter()).write(any(Number.class), anyString());
cache.putIfAbsent(1, "one");
}
@Test
public void testTwoArgRemoveMatch() throws Exception {
final String cachedValue = "cached";
when(store.compute(any(Number.class), anyBiFunction(), anyNullaryFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, String, String> function = asBiFunction(invocation);
function.apply((Number)invocation.getArguments()[0], cachedValue);
return null;
}
});
assertThat(cache.remove(1, cachedValue), is(true));
verify(cache.getCacheLoaderWriter()).delete(1);
}
@Test
public void testTwoArgRemoveKeyNotInCache() throws Exception {
when(store.compute(any(Number.class), anyBiFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, String, String> function = asBiFunction(invocation);
function.apply((Number)invocation.getArguments()[0], null);
return null;
}
});
String toRemove = "foo";
assertThat(cache.remove(1, toRemove), is(false));
verify(cache.getCacheLoaderWriter(), never()).delete(1);
}
@Test
public void testTwoArgRemoveWriteUnsuccessful() throws Exception {
when(store.compute(any(Number.class), anyBiFunction(), anyNullaryFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, String, String> function = asBiFunction(invocation);
function.apply((Number)invocation.getArguments()[0], null);
return null;
}
});
String toRemove = "foo";
assertThat(cache.remove(1, toRemove), is(false));
verify(cache.getCacheLoaderWriter(), never()).delete(1);
}
@Test
public void testTwoArgRemoveThrowsOnCompute() throws Exception {
String toRemove = "foo";
when(store.compute(any(Number.class), anyBiFunction(), anyNullaryFunction())).thenThrow(new StoreAccessException("boom"));
assertThat(cache.remove(1, toRemove), is(false));
verify(cache.getCacheLoaderWriter(), never()).delete(1);
verify(store).remove(1);
}
@Test(expected=CacheWritingException.class)
public void testTwoArgRemoveThrowsOnWrite() throws Exception {
final String expected = "foo";
when(store.compute(any(Number.class), anyBiFunction(), anyNullaryFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, String, String> function = asBiFunction(invocation);
try {
function.apply((Number)invocation.getArguments()[0], expected);
} catch (StorePassThroughException e) {
throw e.getCause();
}
return null;
}
});
doThrow(new Exception()).when(cache.getCacheLoaderWriter()).delete(any(Number.class));
cache.remove(1, expected);
}
@Test
public void testReplace() throws Exception {
final String oldValue = "foo";
final String newValue = "bar";
when(store.compute(any(Number.class), anyBiFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, CharSequence, String> function = asBiFunction(invocation);
function.apply((Number)invocation.getArguments()[0], oldValue);
return null;
}
});
when((String)cache.getCacheLoaderWriter().load(any(Number.class))).thenReturn(oldValue);
assertThat(cache.replace(1, newValue), is(oldValue));
verify(cache.getCacheLoaderWriter()).write(1, newValue);
}
@Test
public void testReplaceThrowsOnCompute() throws Exception {
when(store.compute(any(Number.class), anyBiFunction())).thenThrow(new StoreAccessException("boom"));
String value = "foo";
assertThat(cache.replace(1, value), nullValue());
verify(cache.getCacheLoaderWriter()).load(1);
verify(store).remove(1);
}
@Test(expected=CacheWritingException.class)
public void testReplaceThrowsOnWrite() throws Exception {
final String expected = "old";
when(store.compute(any(Number.class), anyBiFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, String, String> function = asBiFunction(invocation);
try {
function.apply((Number)invocation.getArguments()[0], expected);
} catch (StorePassThroughException e) {
throw e.getCause();
}
return null;
}
});
when((String)cache.getCacheLoaderWriter().load(any(Number.class))).thenReturn(expected);
doThrow(new Exception()).when(cache.getCacheLoaderWriter()).write(any(Number.class), anyString());
cache.replace(1, "bar");
}
@Test
public void testThreeArgReplaceMatch() throws Exception {
final String cachedValue = "cached";
final String newValue = "toReplace";
when(store.compute(any(Number.class), anyBiFunction(), anyNullaryFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, String, String> function = asBiFunction(invocation);
function.apply((Number)invocation.getArguments()[0], cachedValue);
return null;
}
});
assertThat(cache.replace(1, cachedValue, newValue), is(true));
verify(cache.getCacheLoaderWriter()).write(1, newValue);
}
@Test
public void testThreeArgReplaceKeyNotInCache() throws Exception {
final String oldValue = "cached";
final String newValue = "toReplace";
when(store.compute(any(Number.class), anyBiFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, String, String> function = asBiFunction(invocation);
function.apply((Number)invocation.getArguments()[0], null);
return null;
}
});
assertThat(cache.replace(1, oldValue, newValue), is(false));
verify(cache.getCacheLoaderWriter(), never()).write(1, newValue);
}
@Test
public void testThreeArgReplaceThrowsOnCompute() throws Exception {
final String oldValue = "cached";
final String newValue = "toReplace";
when(store.compute(any(Number.class), anyBiFunction(), anyNullaryFunction())).thenThrow(new StoreAccessException("boom"));
assertThat(cache.replace(1, oldValue, newValue), is(false));
verify(cache.getCacheLoaderWriter(), never()).write(1, newValue);
verify(store).remove(1);
}
@Test(expected=CacheWritingException.class)
public void testThreeArgReplaceThrowsOnWrite() throws Exception {
when(store.compute(any(Number.class), anyBiFunction(), anyNullaryFunction())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
BiFunction<Number, String, String> function = asBiFunction(invocation);
final String applied;
try {
applied = function.apply((Number)invocation.getArguments()[0], "old");
} catch (StorePassThroughException e) {
throw e.getCause();
}
@SuppressWarnings("unchecked")
final Store.ValueHolder<Object> mock = mock(Store.ValueHolder.class);
when(mock.value()).thenReturn(applied);
return mock;
}
});
doThrow(new Exception()).when(cache.getCacheLoaderWriter()).write(any(Number.class), anyString());
cache.replace(1, "old", "new");
}
@SuppressWarnings("unchecked")
private static BiFunction<Number, String, String> anyBiFunction() {
return any(BiFunction.class);
}
@SuppressWarnings("unchecked")
private Function<Number, String> anyFunction() {
return any(Function.class);
}
@SuppressWarnings("unchecked")
private NullaryFunction<Boolean> anyNullaryFunction() {
return any(NullaryFunction.class);
}
@SuppressWarnings("unchecked")
private static <A, B, T> BiFunction<A, B, T> asBiFunction(InvocationOnMock in) {
return (BiFunction<A, B, T>)in.getArguments()[1];
}
@SuppressWarnings("unchecked")
private static <A, T> Function<A, T> asFunction(InvocationOnMock in) {
return (Function<A, T>)in.getArguments()[1];
}
}