/*
* 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.impl.internal.store.tiering;
import org.ehcache.config.ResourceType;
import org.ehcache.core.spi.function.BiFunction;
import org.ehcache.core.spi.function.Function;
import org.ehcache.core.spi.store.Store;
import org.ehcache.core.spi.store.tiering.CachingTier;
import org.ehcache.core.spi.store.tiering.HigherCachingTier;
import org.ehcache.core.spi.store.tiering.LowerCachingTier;
import org.ehcache.impl.internal.util.UnmatchedResourceType;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.EMPTY_LIST;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Ludovic Orban
*/
public class CompoundCachingTierTest {
@Test
@SuppressWarnings("unchecked")
public void testGetOrComputeIfAbsentComputesWhenBothTiersEmpty() throws Exception {
HigherCachingTier<String, String> higherTier = mock(HigherCachingTier.class);
LowerCachingTier<String, String> lowerTier = mock(LowerCachingTier.class);
final Store.ValueHolder<String> valueHolder = mock(Store.ValueHolder.class);
final ArgumentCaptor<Function> functionArg = ArgumentCaptor.forClass(Function.class);
final ArgumentCaptor<String> keyArg = ArgumentCaptor.forClass(String.class);
when(higherTier.getOrComputeIfAbsent(keyArg.capture(), functionArg.capture())).then(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return functionArg.getValue().apply(keyArg.getValue());
}
});
when(lowerTier.getAndRemove(anyString())).thenReturn(null);
CompoundCachingTier<String, String> compoundCachingTier = new CompoundCachingTier<String, String>(higherTier, lowerTier);
final AtomicBoolean computed = new AtomicBoolean(false);
assertThat(compoundCachingTier.getOrComputeIfAbsent("1", new Function<String, Store.ValueHolder<String>>() {
@Override
public Store.ValueHolder<String> apply(String s) {
computed.set(true);
return valueHolder;
}
}), is(valueHolder));
assertThat(computed.get(), is(true));
}
@Test
@SuppressWarnings("unchecked")
public void testGetOrComputeIfAbsentDoesNotComputesWhenHigherTierContainsValue() throws Exception {
HigherCachingTier<String, String> higherTier = mock(HigherCachingTier.class);
LowerCachingTier<String, String> lowerTier = mock(LowerCachingTier.class);
final Store.ValueHolder<String> valueHolder = mock(Store.ValueHolder.class);
when(higherTier.getOrComputeIfAbsent(anyString(), any(Function.class))).thenReturn(valueHolder);
when(lowerTier.getAndRemove(anyString())).thenThrow(AssertionError.class);
CompoundCachingTier<String, String> compoundCachingTier = new CompoundCachingTier<String, String>(higherTier, lowerTier);
final AtomicBoolean computed = new AtomicBoolean(false);
assertThat(compoundCachingTier.getOrComputeIfAbsent("1", new Function<String, Store.ValueHolder<String>>() {
@Override
public Store.ValueHolder<String> apply(String s) {
computed.set(true);
return valueHolder;
}
}), is(valueHolder));
assertThat(computed.get(), is(false));
}
@Test
@SuppressWarnings("unchecked")
public void testGetOrComputeIfAbsentDoesNotComputesWhenLowerTierContainsValue() throws Exception {
HigherCachingTier<String, String> higherTier = mock(HigherCachingTier.class);
LowerCachingTier<String, String> lowerTier = mock(LowerCachingTier.class);
final Store.ValueHolder<String> valueHolder = mock(Store.ValueHolder.class);
final ArgumentCaptor<Function> functionArg = ArgumentCaptor.forClass(Function.class);
final ArgumentCaptor<String> keyArg = ArgumentCaptor.forClass(String.class);
when(higherTier.getOrComputeIfAbsent(keyArg.capture(), functionArg.capture())).then(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return functionArg.getValue().apply(keyArg.getValue());
}
});
when(lowerTier.getAndRemove(anyString())).thenReturn(valueHolder);
CompoundCachingTier<String, String> compoundCachingTier = new CompoundCachingTier<String, String>(higherTier, lowerTier);
final AtomicBoolean computed = new AtomicBoolean(false);
assertThat(compoundCachingTier.getOrComputeIfAbsent("1", new Function<String, Store.ValueHolder<String>>() {
@Override
public Store.ValueHolder<String> apply(String s) {
computed.set(true);
return valueHolder;
}
}), is(valueHolder));
assertThat(computed.get(), is(false));
}
@Test
@SuppressWarnings("unchecked")
public void testGetOrComputeIfAbsentComputesWhenLowerTierExpires() throws Exception {
HigherCachingTier<String, String> higherTier = mock(HigherCachingTier.class);
final LowerCachingTier<String, String> lowerTier = mock(LowerCachingTier.class);
final Store.ValueHolder<String> originalValueHolder = mock(Store.ValueHolder.class);
final Store.ValueHolder<String> newValueHolder = mock(Store.ValueHolder.class);
final ArgumentCaptor<Function> functionArg = ArgumentCaptor.forClass(Function.class);
final ArgumentCaptor<String> keyArg = ArgumentCaptor.forClass(String.class);
when(higherTier.getOrComputeIfAbsent(keyArg.capture(), functionArg.capture())).then(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return functionArg.getValue().apply(keyArg.getValue());
}
});
final ArgumentCaptor<CachingTier.InvalidationListener> invalidationListenerArg = ArgumentCaptor.forClass(CachingTier.InvalidationListener.class);
doNothing().when(lowerTier).setInvalidationListener(invalidationListenerArg.capture());
when(lowerTier.getAndRemove(anyString())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
invalidationListenerArg.getValue().onInvalidation(invocation.getArguments()[0], originalValueHolder);
return null;
}
});
final AtomicReference<Store.ValueHolder<String>> invalidated = new AtomicReference<Store.ValueHolder<String>>();
CompoundCachingTier<String, String> compoundCachingTier = new CompoundCachingTier<String, String>(higherTier, lowerTier);
compoundCachingTier.setInvalidationListener(new CachingTier.InvalidationListener<String, String>() {
@Override
public void onInvalidation(String key, Store.ValueHolder<String> valueHolder) {
invalidated.set(valueHolder);
}
});
final AtomicBoolean computed = new AtomicBoolean(false);
assertThat(compoundCachingTier.getOrComputeIfAbsent("1", new Function<String, Store.ValueHolder<String>>() {
@Override
public Store.ValueHolder<String> apply(String s) {
computed.set(true);
return newValueHolder;
}
}), is(newValueHolder));
assertThat(computed.get(), is(true));
assertThat(invalidated.get(), is(originalValueHolder));
}
@Test
@SuppressWarnings("unchecked")
public void testInvalidateNoArg() throws Exception {
HigherCachingTier<String, String> higherTier = mock(HigherCachingTier.class);
LowerCachingTier<String, String> lowerTier = mock(LowerCachingTier.class);
CompoundCachingTier<String, String> compoundCachingTier = new CompoundCachingTier<String, String>(higherTier, lowerTier);
compoundCachingTier.clear();
verify(higherTier, times(1)).clear();
verify(lowerTier, times(1)).clear();
}
@Test
@SuppressWarnings("unchecked")
public void testInvalidateWhenNoValueDoesNotFireListener() throws Exception {
HigherCachingTier<String, String> higherTier = mock(HigherCachingTier.class);
LowerCachingTier<String, String> lowerTier = mock(LowerCachingTier.class);
final AtomicReference<Store.ValueHolder<String>> invalidated = new AtomicReference<Store.ValueHolder<String>>();
CompoundCachingTier<String, String> compoundCachingTier = new CompoundCachingTier<String, String>(higherTier, lowerTier);
compoundCachingTier.setInvalidationListener(new CachingTier.InvalidationListener<String, String>() {
@Override
public void onInvalidation(String key, Store.ValueHolder<String> valueHolder) {
invalidated.set(valueHolder);
}
});
compoundCachingTier.invalidate("1");
assertThat(invalidated.get(), is(nullValue()));
}
@Test
@SuppressWarnings("unchecked")
public void testInvalidateWhenValueInLowerTierFiresListener() throws Exception {
HigherCachingTier<String, String> higherTier = mock(HigherCachingTier.class);
LowerCachingTier<String, String> lowerTier = mock(LowerCachingTier.class);
final Store.ValueHolder<String> valueHolder = mock(Store.ValueHolder.class);
final AtomicReference<Store.ValueHolder<String>> higherTierValueHolder = new AtomicReference<Store.ValueHolder<String>>();
final AtomicReference<Store.ValueHolder<String>> lowerTierValueHolder = new AtomicReference<Store.ValueHolder<String>>(valueHolder);
final ArgumentCaptor<CachingTier.InvalidationListener> higherTierInvalidationListenerArg = ArgumentCaptor.forClass(CachingTier.InvalidationListener.class);
doNothing().when(higherTier).setInvalidationListener(higherTierInvalidationListenerArg.capture());
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
higherTierInvalidationListenerArg.getValue().onInvalidation(invocation.getArguments()[0], higherTierValueHolder.getAndSet(null));
((Function) invocation.getArguments()[1]).apply(higherTierValueHolder.get());
return null;
}
}).when(higherTier).silentInvalidate(anyString(), any(Function.class));
final ArgumentCaptor<Function> functionArg = ArgumentCaptor.forClass(Function.class);
final ArgumentCaptor<String> keyArg = ArgumentCaptor.forClass(String.class);
when(lowerTier.installMapping(keyArg.capture(), functionArg.capture())).then(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return lowerTierValueHolder.get();
}
});
final ArgumentCaptor<CachingTier.InvalidationListener> lowerTierInvalidationListenerArg = ArgumentCaptor.forClass(CachingTier.InvalidationListener.class);
doNothing().when(lowerTier).setInvalidationListener(lowerTierInvalidationListenerArg.capture());
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
lowerTierInvalidationListenerArg.getValue().onInvalidation(invocation.getArguments()[0], lowerTierValueHolder.getAndSet(null));
return null;
}
}).when(lowerTier).invalidate(anyString());
final AtomicReference<Store.ValueHolder<String>> invalidated = new AtomicReference<Store.ValueHolder<String>>();
CompoundCachingTier<String, String> compoundCachingTier = new CompoundCachingTier<String, String>(higherTier, lowerTier);
compoundCachingTier.setInvalidationListener(new CachingTier.InvalidationListener<String, String>() {
@Override
public void onInvalidation(String key, Store.ValueHolder<String> valueHolder) {
invalidated.set(valueHolder);
}
});
compoundCachingTier.invalidate("1");
assertThat(invalidated.get(), is(valueHolder));
assertThat(higherTierValueHolder.get(), is(nullValue()));
assertThat(lowerTierValueHolder.get(), is(nullValue()));
}
@Test
@SuppressWarnings("unchecked")
public void testInvalidateWhenValueInHigherTierFiresListener() throws Exception {
HigherCachingTier<String, String> higherTier = mock(HigherCachingTier.class);
LowerCachingTier<String, String> lowerTier = mock(LowerCachingTier.class);
final Store.ValueHolder<String> valueHolder = mock(Store.ValueHolder.class);
final AtomicReference<Store.ValueHolder<String>> higherTierValueHolder = new AtomicReference<Store.ValueHolder<String>>(valueHolder);
final AtomicReference<Store.ValueHolder<String>> lowerTierValueHolder = new AtomicReference<Store.ValueHolder<String>>();
final ArgumentCaptor<CachingTier.InvalidationListener> higherTierInvalidationListenerArg = ArgumentCaptor.forClass(CachingTier.InvalidationListener.class);
doNothing().when(higherTier).setInvalidationListener(higherTierInvalidationListenerArg.capture());
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
higherTierInvalidationListenerArg.getValue().onInvalidation(invocation.getArguments()[0], higherTierValueHolder.getAndSet(null));
((Function) invocation.getArguments()[1]).apply(higherTierValueHolder.get());
return null;
}
}).when(higherTier).silentInvalidate(anyString(), any(Function.class));
final ArgumentCaptor<Function> functionArg = ArgumentCaptor.forClass(Function.class);
final ArgumentCaptor<String> keyArg = ArgumentCaptor.forClass(String.class);
when(lowerTier.installMapping(keyArg.capture(), functionArg.capture())).then(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object apply = functionArg.getValue().apply(keyArg.getValue());
lowerTierValueHolder.set((Store.ValueHolder<String>) apply);
return apply;
}
});
final ArgumentCaptor<CachingTier.InvalidationListener> lowerTierInvalidationListenerArg = ArgumentCaptor.forClass(CachingTier.InvalidationListener.class);
doNothing().when(lowerTier).setInvalidationListener(lowerTierInvalidationListenerArg.capture());
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
lowerTierInvalidationListenerArg.getValue().onInvalidation(invocation.getArguments()[0], lowerTierValueHolder.getAndSet(null));
return null;
}
}).when(lowerTier).invalidate(anyString());
final AtomicReference<Store.ValueHolder<String>> invalidated = new AtomicReference<Store.ValueHolder<String>>();
CompoundCachingTier<String, String> compoundCachingTier = new CompoundCachingTier<String, String>(higherTier, lowerTier);
compoundCachingTier.setInvalidationListener(new CachingTier.InvalidationListener<String, String>() {
@Override
public void onInvalidation(String key, Store.ValueHolder<String> valueHolder) {
invalidated.set(valueHolder);
}
});
compoundCachingTier.invalidate("1");
assertThat(invalidated.get(), is(valueHolder));
assertThat(higherTierValueHolder.get(), is(nullValue()));
assertThat(lowerTierValueHolder.get(), is(nullValue()));
}
@Test
@SuppressWarnings("unchecked")
public void testInvalidateAllCoversBothTiers() throws Exception {
HigherCachingTier<String, String> higherTier = mock(HigherCachingTier.class);
LowerCachingTier<String, String> lowerTier = mock(LowerCachingTier.class);
CompoundCachingTier<String, String> compoundCachingTier = new CompoundCachingTier<String, String>(higherTier, lowerTier);
compoundCachingTier.invalidateAll();
verify(higherTier).silentInvalidateAll(any(BiFunction.class));
verify(lowerTier).invalidateAll();
}
@Test
@SuppressWarnings("unchecked")
public void testRankCachingTier() throws Exception {
CompoundCachingTier.Provider provider = new CompoundCachingTier.Provider();
HashSet<ResourceType<?>> resourceTypes = new HashSet<ResourceType<?>>();
resourceTypes.addAll(EnumSet.of(ResourceType.Core.HEAP, ResourceType.Core.OFFHEAP));
assertThat(provider.rankCachingTier(resourceTypes, EMPTY_LIST), is(2));
resourceTypes.clear();
resourceTypes.add(new UnmatchedResourceType());
assertThat(provider.rankCachingTier(resourceTypes, EMPTY_LIST), is(0));
}
}