/* * 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.ResourcePool; import org.ehcache.config.ResourcePools; import org.ehcache.config.ResourceType; import org.ehcache.config.SizedResourcePool; import org.ehcache.core.internal.service.ServiceLocator; import org.ehcache.core.spi.service.DiskResourceService; import org.ehcache.core.spi.function.BiFunction; import org.ehcache.core.spi.function.Function; import org.ehcache.core.spi.function.NullaryFunction; import org.ehcache.core.spi.store.Store; import org.ehcache.core.spi.store.Store.RemoveStatus; import org.ehcache.core.spi.store.Store.ReplaceStatus; import org.ehcache.core.spi.store.StoreAccessException; import org.ehcache.core.spi.store.tiering.AuthoritativeTier; import org.ehcache.core.spi.store.tiering.CachingTier; import org.ehcache.impl.internal.store.heap.OnHeapStore; import org.ehcache.impl.internal.store.offheap.OffHeapStore; import org.ehcache.spi.service.Service; import org.ehcache.spi.service.ServiceConfiguration; import org.ehcache.spi.service.ServiceProvider; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import static org.ehcache.core.internal.service.ServiceLocator.dependencySet; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.Is.is; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * Tests for {@link TieredStore}. */ public class TieredStoreTest { @Mock private CachingTier<Number, CharSequence> numberCachingTier; @Mock private AuthoritativeTier<Number, CharSequence> numberAuthoritativeTier; @Mock private CachingTier<String, String> stringCachingTier; @Mock private AuthoritativeTier<String, String> stringAuthoritativeTier; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); } @Test @SuppressWarnings("unchecked") public void testGetHitsCachingTier() throws Exception { when(numberCachingTier.getOrComputeIfAbsent(eq(1), any(Function.class))).thenReturn(newValueHolder("one")); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.get(1).value(), Matchers.<CharSequence>equalTo("one")); verify(numberAuthoritativeTier, times(0)).getAndFault(any(Number.class)); } @Test @SuppressWarnings("unchecked") public void testGetHitsAuthoritativeTier() throws Exception { Store.ValueHolder<CharSequence> valueHolder = newValueHolder("one"); when(numberAuthoritativeTier.getAndFault(eq(1))).thenReturn(valueHolder); when(numberCachingTier.getOrComputeIfAbsent(any(Number.class), any(Function.class))).then(new Answer<Store.ValueHolder<CharSequence>>() { @Override public Store.ValueHolder<CharSequence> answer(InvocationOnMock invocation) throws Throwable { Number key = (Number) invocation.getArguments()[0]; Function<Number, Store.ValueHolder<CharSequence>> function = (Function<Number, Store.ValueHolder<CharSequence>>) invocation.getArguments()[1]; return function.apply(key); } }); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.get(1).value(), Matchers.<CharSequence>equalTo("one")); verify(numberCachingTier, times(1)).getOrComputeIfAbsent(eq(1), any(Function.class)); verify(numberAuthoritativeTier, times(1)).getAndFault(any(Number.class)); } @Test @SuppressWarnings("unchecked") public void testGetMisses() throws Exception { when(numberAuthoritativeTier.getAndFault(eq(1))).thenReturn(null); when(numberCachingTier.getOrComputeIfAbsent(any(Number.class), any(Function.class))).then(new Answer<Store.ValueHolder<CharSequence>>() { @Override public Store.ValueHolder<CharSequence> answer(InvocationOnMock invocation) throws Throwable { Number key = (Number) invocation.getArguments()[0]; Function<Number, Store.ValueHolder<CharSequence>> function = (Function<Number, Store.ValueHolder<CharSequence>>) invocation.getArguments()[1]; return function.apply(key); } }); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.get(1), is(nullValue())); verify(numberCachingTier, times(1)).getOrComputeIfAbsent(eq(1), any(Function.class)); verify(numberAuthoritativeTier, times(1)).getAndFault(any(Number.class)); } @Test public void testPut() throws Exception { TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); tieredStore.put(1, "one"); verify(numberCachingTier, times(1)).invalidate(eq(1)); verify(numberAuthoritativeTier, times(1)).put(eq(1), eq("one")); } @Test public void testPutIfAbsent_whenAbsent() throws Exception { TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.putIfAbsent(1, "one"), is(nullValue())); verify(numberCachingTier, times(1)).invalidate(eq(1)); verify(numberAuthoritativeTier, times(1)).putIfAbsent(eq(1), eq("one")); } @Test public void testPutIfAbsent_whenPresent() throws Exception { when(numberAuthoritativeTier.putIfAbsent(1, "one")).thenReturn(newValueHolder("un")); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.putIfAbsent(1, "one").value(), Matchers.<CharSequence>equalTo("un")); verify(numberCachingTier, times(1)).invalidate(1); verify(numberAuthoritativeTier, times(1)).putIfAbsent(1, "one"); } @Test public void testRemove() throws Exception { TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); tieredStore.remove(1); verify(numberCachingTier, times(1)).invalidate(eq(1)); verify(numberAuthoritativeTier, times(1)).remove(eq(1)); } @Test public void testRemove2Args_removes() throws Exception { when(numberAuthoritativeTier.remove(eq(1), eq("one"))).thenReturn(RemoveStatus.REMOVED); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.remove(1, "one"), is(RemoveStatus.REMOVED)); verify(numberCachingTier, times(1)).invalidate(eq(1)); verify(numberAuthoritativeTier, times(1)).remove(eq(1), eq("one")); } @Test public void testRemove2Args_doesNotRemove() throws Exception { when(numberAuthoritativeTier.remove(eq(1), eq("one"))).thenReturn(RemoveStatus.KEY_MISSING); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.remove(1, "one"), is(RemoveStatus.KEY_MISSING)); verify(numberCachingTier).invalidate(any(Number.class)); verify(numberAuthoritativeTier, times(1)).remove(eq(1), eq("one")); } @Test public void testReplace2Args_replaces() throws Exception { when(numberAuthoritativeTier.replace(eq(1), eq("one"))).thenReturn(newValueHolder("un")); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.replace(1, "one").value(), Matchers.<CharSequence>equalTo("un")); verify(numberCachingTier, times(1)).invalidate(eq(1)); verify(numberAuthoritativeTier, times(1)).replace(eq(1), eq("one")); } @Test public void testReplace2Args_doesNotReplace() throws Exception { when(numberAuthoritativeTier.replace(eq(1), eq("one"))).thenReturn(null); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.replace(1, "one"), is(nullValue())); verify(numberCachingTier).invalidate(any(Number.class)); verify(numberAuthoritativeTier, times(1)).replace(eq(1), eq("one")); } @Test public void testReplace3Args_replaces() throws Exception { when(numberAuthoritativeTier.replace(eq(1), eq("un"), eq("one"))).thenReturn(ReplaceStatus.HIT); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.replace(1, "un", "one"), is(ReplaceStatus.HIT)); verify(numberCachingTier, times(1)).invalidate(eq(1)); verify(numberAuthoritativeTier, times(1)).replace(eq(1), eq("un"), eq("one")); } @Test public void testReplace3Args_doesNotReplace() throws Exception { when(numberAuthoritativeTier.replace(eq(1), eq("un"), eq("one"))).thenReturn(ReplaceStatus.MISS_NOT_PRESENT); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.replace(1, "un", "one"), is(ReplaceStatus.MISS_NOT_PRESENT)); verify(numberCachingTier).invalidate(any(Number.class)); verify(numberAuthoritativeTier, times(1)).replace(eq(1), eq("un"), eq("one")); } @Test public void testClear() throws Exception { TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); tieredStore.clear(); verify(numberCachingTier, times(1)).clear(); verify(numberAuthoritativeTier, times(1)).clear(); } @Test @SuppressWarnings("unchecked") public void testCompute2Args() throws Exception { when(numberAuthoritativeTier.compute(any(Number.class), any(BiFunction.class))).then(new Answer<Store.ValueHolder<CharSequence>>() { @Override public Store.ValueHolder<CharSequence> answer(InvocationOnMock invocation) throws Throwable { Number key = (Number) invocation.getArguments()[0]; BiFunction<Number, CharSequence, CharSequence> function = (BiFunction<Number, CharSequence, CharSequence>) invocation.getArguments()[1]; return newValueHolder(function.apply(key, null)); } }); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.compute(1, new BiFunction<Number, CharSequence, CharSequence>() { @Override public CharSequence apply(Number number, CharSequence charSequence) { return "one"; } }).value(), Matchers.<CharSequence>equalTo("one")); verify(numberCachingTier, times(1)).invalidate(any(Number.class)); verify(numberAuthoritativeTier, times(1)).compute(eq(1), any(BiFunction.class)); } @Test @SuppressWarnings("unchecked") public void testCompute3Args() throws Exception { when(numberAuthoritativeTier.compute(any(Number.class), any(BiFunction.class), any(NullaryFunction.class))).then(new Answer<Store.ValueHolder<CharSequence>>() { @Override public Store.ValueHolder<CharSequence> answer(InvocationOnMock invocation) throws Throwable { Number key = (Number) invocation.getArguments()[0]; BiFunction<Number, CharSequence, CharSequence> function = (BiFunction<Number, CharSequence, CharSequence>) invocation.getArguments()[1]; return newValueHolder(function.apply(key, null)); } }); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.compute(1, new BiFunction<Number, CharSequence, CharSequence>() { @Override public CharSequence apply(Number number, CharSequence charSequence) { return "one"; } }, new NullaryFunction<Boolean>() { @Override public Boolean apply() { return true; } }).value(), Matchers.<CharSequence>equalTo("one")); verify(numberCachingTier, times(1)).invalidate(any(Number.class)); verify(numberAuthoritativeTier, times(1)).compute(eq(1), any(BiFunction.class), any(NullaryFunction.class)); } @Test @SuppressWarnings("unchecked") public void testComputeIfAbsent_computes() throws Exception { when(numberCachingTier.getOrComputeIfAbsent(any(Number.class), any(Function.class))).thenAnswer(new Answer<Store.ValueHolder<CharSequence>>() { @Override public Store.ValueHolder<CharSequence> answer(InvocationOnMock invocation) throws Throwable { Number key = (Number) invocation.getArguments()[0]; Function<Number, Store.ValueHolder<CharSequence>> function = (Function<Number, Store.ValueHolder<CharSequence>>) invocation.getArguments()[1]; return function.apply(key); } }); when(numberAuthoritativeTier.computeIfAbsentAndFault(any(Number.class), any(Function.class))).thenAnswer(new Answer<Store.ValueHolder<CharSequence>>() { @Override public Store.ValueHolder<CharSequence> answer(InvocationOnMock invocation) throws Throwable { Number key = (Number) invocation.getArguments()[0]; Function<Number, CharSequence> function = (Function<Number, CharSequence>) invocation.getArguments()[1]; return newValueHolder(function.apply(key)); } }); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.computeIfAbsent(1, new Function<Number, CharSequence>() { @Override public CharSequence apply(Number number) { return "one"; } }).value(), Matchers.<CharSequence>equalTo("one")); verify(numberCachingTier, times(1)).getOrComputeIfAbsent(eq(1), any(Function.class)); verify(numberAuthoritativeTier, times(1)).computeIfAbsentAndFault(eq(1), any(Function.class)); } @Test @SuppressWarnings("unchecked") public void testComputeIfAbsent_doesNotCompute() throws Exception { final Store.ValueHolder<CharSequence> valueHolder = newValueHolder("one"); when(numberCachingTier.getOrComputeIfAbsent(any(Number.class), any(Function.class))).thenAnswer(new Answer<Store.ValueHolder<CharSequence>>() { @Override public Store.ValueHolder<CharSequence> answer(InvocationOnMock invocation) throws Throwable { return valueHolder; } }); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); assertThat(tieredStore.computeIfAbsent(1, new Function<Number, CharSequence>() { @Override public CharSequence apply(Number number) { return "one"; } }).value(), Matchers.<CharSequence>equalTo("one")); verify(numberCachingTier, times(1)).getOrComputeIfAbsent(eq(1), any(Function.class)); verify(numberAuthoritativeTier, times(0)).computeIfAbsentAndFault(eq(1), any(Function.class)); } @Test @SuppressWarnings("unchecked") public void testBulkCompute2Args() throws Exception { when(numberAuthoritativeTier.bulkCompute(any(Set.class), any(Function.class))).thenAnswer(new Answer<Map<Number, Store.ValueHolder<CharSequence>>>() { @Override public Map<Number, Store.ValueHolder<CharSequence>> answer(InvocationOnMock invocation) throws Throwable { Set<Number> keys = (Set) invocation.getArguments()[0]; Function<Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>> function = (Function<Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>>) invocation.getArguments()[1]; List<Map.Entry<? extends Number, ? extends CharSequence>> functionArg = new ArrayList<Map.Entry<? extends Number, ? extends CharSequence>>(); for (Number key : keys) { functionArg.add(newMapEntry(key, null)); } Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> functionResult = function.apply(functionArg); Map<Number, Store.ValueHolder<CharSequence>> result = new HashMap<Number, Store.ValueHolder<CharSequence>>(); for (Map.Entry<? extends Number, ? extends CharSequence> entry : functionResult) { result.put(entry.getKey(), newValueHolder(entry.getValue())); } return result; } }); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); Map<Number, Store.ValueHolder<CharSequence>> result = tieredStore.bulkCompute(new HashSet<Number>(Arrays.asList(1, 2, 3)), new Function<Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>>() { @Override public Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> apply(Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> entries) { return new ArrayList<Map.Entry<? extends Number, ? extends CharSequence>>(Arrays.asList(newMapEntry(1, "one"), newMapEntry(2, "two"), newMapEntry(3, "three"))); } }); assertThat(result.size(), is(3)); assertThat(result.get(1).value(), Matchers.<CharSequence>equalTo("one")); assertThat(result.get(2).value(), Matchers.<CharSequence>equalTo("two")); assertThat(result.get(3).value(), Matchers.<CharSequence>equalTo("three")); verify(numberCachingTier, times(1)).invalidate(1); verify(numberCachingTier, times(1)).invalidate(2); verify(numberCachingTier, times(1)).invalidate(3); verify(numberAuthoritativeTier, times(1)).bulkCompute(any(Set.class), any(Function.class)); } @Test @SuppressWarnings("unchecked") public void testBulkCompute3Args() throws Exception { when( numberAuthoritativeTier.bulkCompute(any(Set.class), any(Function.class), any(NullaryFunction.class))).thenAnswer(new Answer<Map<Number, Store.ValueHolder<CharSequence>>>() { @Override public Map<Number, Store.ValueHolder<CharSequence>> answer(InvocationOnMock invocation) throws Throwable { Set<Number> keys = (Set) invocation.getArguments()[0]; Function<Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>> function = (Function<Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>>) invocation.getArguments()[1]; List<Map.Entry<? extends Number, ? extends CharSequence>> functionArg = new ArrayList<Map.Entry<? extends Number, ? extends CharSequence>>(); for (Number key : keys) { functionArg.add(newMapEntry(key, null)); } Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> functionResult = function.apply(functionArg); Map<Number, Store.ValueHolder<CharSequence>> result = new HashMap<Number, Store.ValueHolder<CharSequence>>(); for (Map.Entry<? extends Number, ? extends CharSequence> entry : functionResult) { result.put(entry.getKey(), newValueHolder(entry.getValue())); } return result; } }); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); Map<Number, Store.ValueHolder<CharSequence>> result = tieredStore.bulkCompute(new HashSet<Number>(Arrays.asList(1, 2, 3)), new Function<Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>>() { @Override public Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> apply(Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> entries) { return new ArrayList<Map.Entry<? extends Number, ? extends CharSequence>>(Arrays.asList(newMapEntry(1, "one"), newMapEntry(2, "two"), newMapEntry(3, "three"))); } }, new NullaryFunction<Boolean>() { @Override public Boolean apply() { return true; } }); assertThat(result.size(), is(3)); assertThat(result.get(1).value(), Matchers.<CharSequence>equalTo("one")); assertThat(result.get(2).value(), Matchers.<CharSequence>equalTo("two")); assertThat(result.get(3).value(), Matchers.<CharSequence>equalTo("three")); verify(numberCachingTier, times(1)).invalidate(1); verify(numberCachingTier, times(1)).invalidate(2); verify(numberCachingTier, times(1)).invalidate(3); verify(numberAuthoritativeTier, times(1)).bulkCompute(any(Set.class), any(Function.class), any(NullaryFunction.class)); } @Test @SuppressWarnings("unchecked") public void testBulkComputeIfAbsent() throws Exception { when(numberAuthoritativeTier.bulkComputeIfAbsent(any(Set.class), any(Function.class))).thenAnswer(new Answer<Map<Number, Store.ValueHolder<CharSequence>>>() { @Override public Map<Number, Store.ValueHolder<CharSequence>> answer(InvocationOnMock invocation) throws Throwable { Set<Number> keys = (Set) invocation.getArguments()[0]; Function<Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>> function = (Function<Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>>) invocation.getArguments()[1]; List<Map.Entry<? extends Number, ? extends CharSequence>> functionArg = new ArrayList<Map.Entry<? extends Number, ? extends CharSequence>>(); for (Number key : keys) { functionArg.add(newMapEntry(key, null)); } Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> functionResult = function.apply(functionArg); Map<Number, Store.ValueHolder<CharSequence>> result = new HashMap<Number, Store.ValueHolder<CharSequence>>(); for (Map.Entry<? extends Number, ? extends CharSequence> entry : functionResult) { result.put(entry.getKey(), newValueHolder(entry.getValue())); } return result; } }); TieredStore<Number, CharSequence> tieredStore = new TieredStore<Number, CharSequence>(numberCachingTier, numberAuthoritativeTier); Map<Number, Store.ValueHolder<CharSequence>> result = tieredStore.bulkComputeIfAbsent(new HashSet<Number>(Arrays.asList(1, 2, 3)), new Function<Iterable<? extends Number>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>>() { @Override public Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> apply(Iterable<? extends Number> numbers) { return Arrays.asList(newMapEntry(1, "one"), newMapEntry(2, "two"), newMapEntry(3, "three")); } }); assertThat(result.size(), is(3)); assertThat(result.get(1).value(), Matchers.<CharSequence>equalTo("one")); assertThat(result.get(2).value(), Matchers.<CharSequence>equalTo("two")); assertThat(result.get(3).value(), Matchers.<CharSequence>equalTo("three")); verify(numberCachingTier, times(1)).invalidate(1); verify(numberCachingTier, times(1)).invalidate(2); verify(numberCachingTier, times(1)).invalidate(3); verify(numberAuthoritativeTier, times(1)).bulkComputeIfAbsent(any(Set.class), any(Function.class)); } @Test public void CachingTierDoesNotSeeAnyOperationDuringClear() throws StoreAccessException, BrokenBarrierException, InterruptedException { final TieredStore<String, String> tieredStore = new TieredStore<String, String>(stringCachingTier, stringAuthoritativeTier); final CyclicBarrier barrier = new CyclicBarrier(2); doAnswer(new Answer<Void>() { @Override public Void answer(final InvocationOnMock invocation) throws Throwable { barrier.await(); barrier.await(); return null; } }).when(stringAuthoritativeTier).clear(); Thread t = new Thread(new Runnable() { @Override public void run() { try { tieredStore.clear(); } catch (Exception e) { throw new RuntimeException(e); } } }); t.start(); barrier.await(); tieredStore.get("foo"); barrier.await(); t.join(); verify(stringCachingTier, never()).getOrComputeIfAbsent( org.mockito.Matchers.<String>any(), org.mockito.Matchers.<Function<String, Store.ValueHolder<String>>>anyObject()); } @Test @SuppressWarnings("unchecked") public void testReleaseStoreFlushes() throws Exception { TieredStore.Provider tieredStoreProvider = new TieredStore.Provider(); ResourcePools resourcePools = mock(ResourcePools.class); when(resourcePools.getResourceTypeSet()) .thenReturn(new HashSet<ResourceType<?>>(Arrays.asList(ResourceType.Core.HEAP, ResourceType.Core.OFFHEAP))); SizedResourcePool heapPool = mock(SizedResourcePool.class); when(heapPool.getType()).thenReturn((ResourceType)ResourceType.Core.HEAP); when(resourcePools.getPoolForResource(ResourceType.Core.HEAP)).thenReturn(heapPool); OnHeapStore.Provider onHeapStoreProvider = mock(OnHeapStore.Provider.class); Set<ResourceType<?>> singleton = Collections.<ResourceType<?>>singleton( ResourceType.Core.HEAP); when(onHeapStoreProvider.rankCachingTier(eq(singleton), any(Collection.class))).thenReturn(1); when(onHeapStoreProvider.createCachingTier(any(Store.Configuration.class), org.mockito.Matchers.<ServiceConfiguration<?>[]>anyVararg())) .thenReturn(stringCachingTier); SizedResourcePool offHeapPool = mock(SizedResourcePool.class); when(heapPool.getType()).thenReturn((ResourceType)ResourceType.Core.OFFHEAP); when(resourcePools.getPoolForResource(ResourceType.Core.OFFHEAP)).thenReturn(offHeapPool); OffHeapStore.Provider offHeapStoreProvider = mock(OffHeapStore.Provider.class); when(offHeapStoreProvider.rankAuthority(eq(ResourceType.Core.OFFHEAP), any(Collection.class))).thenReturn(1); when(offHeapStoreProvider.createAuthoritativeTier(any(Store.Configuration.class), org.mockito.Matchers.<ServiceConfiguration<?>[]>anyVararg())) .thenReturn(stringAuthoritativeTier); Store.Configuration<String, String> configuration = mock(Store.Configuration.class); when(configuration.getResourcePools()).thenReturn(resourcePools); Set<AuthoritativeTier.Provider> authorities = new HashSet<AuthoritativeTier.Provider>(); authorities.add(offHeapStoreProvider); Set<CachingTier.Provider> cachingTiers = new HashSet<CachingTier.Provider>(); cachingTiers.add(onHeapStoreProvider); ServiceProvider<Service> serviceProvider = mock(ServiceProvider.class); when(serviceProvider.getService(OnHeapStore.Provider.class)).thenReturn(onHeapStoreProvider); when(serviceProvider.getService(OffHeapStore.Provider.class)).thenReturn(offHeapStoreProvider); when(serviceProvider.getServicesOfType(AuthoritativeTier.Provider.class)).thenReturn(authorities); when(serviceProvider.getServicesOfType(CachingTier.Provider.class)).thenReturn(cachingTiers); tieredStoreProvider.start(serviceProvider); final Store<String, String> tieredStore = tieredStoreProvider.createStore(configuration); tieredStoreProvider.initStore(tieredStore); tieredStoreProvider.releaseStore(tieredStore); verify(onHeapStoreProvider, times(1)).releaseCachingTier(any(CachingTier.class)); } @Test public void testRank() throws Exception { TieredStore.Provider provider = new TieredStore.Provider(); ServiceLocator serviceLocator = dependencySet().with(provider).with(mock(DiskResourceService.class)).build(); serviceLocator.startAllServices(); assertRank(provider, 0, ResourceType.Core.DISK); assertRank(provider, 0, ResourceType.Core.HEAP); assertRank(provider, 0, ResourceType.Core.OFFHEAP); assertRank(provider, 2, ResourceType.Core.OFFHEAP, ResourceType.Core.HEAP); assertRank(provider, 2, ResourceType.Core.DISK, ResourceType.Core.HEAP); assertRank(provider, 0, ResourceType.Core.DISK, ResourceType.Core.OFFHEAP); assertRank(provider, 3, ResourceType.Core.DISK, ResourceType.Core.OFFHEAP, ResourceType.Core.HEAP); final ResourceType<ResourcePool> unmatchedResourceType = new ResourceType<ResourcePool>() { @Override public Class<ResourcePool> getResourcePoolClass() { return ResourcePool.class; } @Override public boolean isPersistable() { return true; } @Override public boolean requiresSerialization() { return true; } @Override public int getTierHeight() { return 10; } }; assertRank(provider, 0, unmatchedResourceType); assertRank(provider, 0, ResourceType.Core.DISK, ResourceType.Core.OFFHEAP, ResourceType.Core.HEAP, unmatchedResourceType); } private void assertRank(final Store.Provider provider, final int expectedRank, final ResourceType<?>... resources) { Assert.assertThat(provider.rank( new HashSet<ResourceType<?>>(Arrays.asList(resources)), Collections.<ServiceConfiguration<?>>emptyList()), Matchers.is(expectedRank)); } public Map.Entry<? extends Number, ? extends CharSequence> newMapEntry(Number key, CharSequence value) { return new AbstractMap.SimpleEntry<Number, CharSequence>(key, value); } public Store.ValueHolder<CharSequence> newValueHolder(final CharSequence v) { return new Store.ValueHolder<CharSequence>() { @Override public CharSequence value() { return v; } @Override public long creationTime(TimeUnit unit) { return 0; } @Override public long expirationTime(TimeUnit unit) { return 0; } @Override public boolean isExpired(long expirationTime, TimeUnit unit) { return false; } @Override public long lastAccessTime(TimeUnit unit) { return 0; } @Override public float hitRate(long now, TimeUnit unit) { return 0; } @Override public long hits() { throw new UnsupportedOperationException("Implement me!"); } @Override public long getId() { throw new UnsupportedOperationException("Implement me!"); } }; } }