/* * 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.clustered.client.internal.store; import org.ehcache.clustered.client.TestTimeSource; import org.ehcache.clustered.client.config.ClusteredResourcePool; import org.ehcache.clustered.client.config.builders.ClusteredResourcePoolBuilder; import org.ehcache.clustered.client.internal.ClusterTierManagerClientEntityFactory; import org.ehcache.clustered.client.internal.UnitTestConnectionService; import org.ehcache.clustered.client.internal.store.operations.ChainResolver; import org.ehcache.clustered.client.internal.store.operations.Result; import org.ehcache.clustered.client.internal.store.operations.codecs.OperationsCodec; import org.ehcache.clustered.common.ServerSideConfiguration; import org.ehcache.clustered.common.internal.ServerStoreConfiguration; import org.ehcache.clustered.common.internal.messages.ServerStoreMessageFactory; import org.ehcache.clustered.common.internal.store.Chain; import org.ehcache.config.units.MemoryUnit; import org.ehcache.core.Ehcache; import org.ehcache.core.spi.function.Function; import org.ehcache.core.spi.store.Store; import org.ehcache.core.spi.store.StoreAccessException; import org.ehcache.core.spi.store.StoreAccessTimeoutException; import org.ehcache.core.spi.time.TimeSource; import org.ehcache.core.statistics.StoreOperationOutcomes; import org.ehcache.expiry.Expirations; import org.ehcache.impl.store.HashUtils; import org.ehcache.impl.serialization.LongSerializer; import org.ehcache.impl.serialization.StringSerializer; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.terracotta.connection.Connection; import java.net.URI; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.UUID; import java.util.concurrent.TimeoutException; import static org.ehcache.clustered.client.internal.store.ClusteredStore.DEFAULT_CHAIN_COMPACTION_THRESHOLD; import static org.ehcache.clustered.client.internal.store.ClusteredStore.CHAIN_COMPACTION_THRESHOLD_PROP; import static org.ehcache.clustered.util.StatisticsTestUtils.validateStat; import static org.ehcache.clustered.util.StatisticsTestUtils.validateStats; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; 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.when; public class ClusteredStoreTest { private static final String CACHE_IDENTIFIER = "testCache"; private static final URI CLUSTER_URI = URI.create("terracotta://localhost:9510"); private ClusteredStore<Long, String> store; @Before public void setup() throws Exception { UnitTestConnectionService.add( CLUSTER_URI, new UnitTestConnectionService.PassthroughServerBuilder().resource("defaultResource", 8, MemoryUnit.MB).build() ); Connection connection = new UnitTestConnectionService().connect(CLUSTER_URI, new Properties()); ClusterTierManagerClientEntityFactory entityFactory = new ClusterTierManagerClientEntityFactory(connection); ServerSideConfiguration serverConfig = new ServerSideConfiguration("defaultResource", Collections.emptyMap()); entityFactory.create("TestCacheManager", serverConfig); ClusteredResourcePool resourcePool = ClusteredResourcePoolBuilder.clusteredDedicated(4, MemoryUnit.MB); ServerStoreConfiguration serverStoreConfiguration = new ServerStoreConfiguration(resourcePool.getPoolAllocation(), Long.class.getName(), String.class.getName(), LongSerializer.class.getName(), StringSerializer.class.getName(), null); ClusterTierClientEntity clientEntity = entityFactory.fetchOrCreateClusteredStoreEntity(UUID.randomUUID(), "TestCacheManager", CACHE_IDENTIFIER, serverStoreConfiguration, true); clientEntity.validate(serverStoreConfiguration); ServerStoreMessageFactory factory = new ServerStoreMessageFactory(clientEntity.getClientId()); ServerStoreProxy serverStoreProxy = new CommonServerStoreProxy(CACHE_IDENTIFIER, factory, clientEntity); TestTimeSource testTimeSource = new TestTimeSource(); OperationsCodec<Long, String> codec = new OperationsCodec<Long, String>(new LongSerializer(), new StringSerializer()); ChainResolver<Long, String> resolver = new ChainResolver<Long, String>(codec, Expirations.noExpiration()); store = new ClusteredStore<>(codec, resolver, serverStoreProxy, testTimeSource); } @After public void tearDown() throws Exception { UnitTestConnectionService.remove("terracotta://localhost:9510/my-application?auto-create"); } @Test public void testPut() throws Exception { assertThat(store.put(1L, "one"), is(Store.PutStatus.PUT)); validateStats(store, EnumSet.of(StoreOperationOutcomes.PutOutcome.PUT)); assertThat(store.put(1L, "another one"), is(Store.PutStatus.UPDATE)); assertThat(store.put(1L, "yet another one"), is(Store.PutStatus.UPDATE)); validateStat(store, StoreOperationOutcomes.PutOutcome.REPLACED, 2); validateStat(store, StoreOperationOutcomes.PutOutcome.PUT, 1); } @Test(expected = StoreAccessTimeoutException.class) @SuppressWarnings("unchecked") public void testPutTimeout() throws Exception { ServerStoreProxy proxy = mock(ServerStoreProxy.class); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenThrow(TimeoutException.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, null, proxy, timeSource); store.put(1L, "one"); } @Test public void testGet() throws Exception { assertThat(store.get(1L), nullValue()); validateStats(store, EnumSet.of(StoreOperationOutcomes.GetOutcome.MISS)); store.put(1L, "one"); assertThat(store.get(1L).value(), is("one")); validateStats(store, EnumSet.of(StoreOperationOutcomes.GetOutcome.MISS, StoreOperationOutcomes.GetOutcome.HIT)); } @Test(expected = StoreAccessException.class) public void testGetThrowsOnlySAE() throws Exception { @SuppressWarnings("unchecked") OperationsCodec<Long, String> codec = mock(OperationsCodec.class); @SuppressWarnings("unchecked") ChainResolver<Long, String> chainResolver = mock(ChainResolver.class); ServerStoreProxy serverStoreProxy = mock(ServerStoreProxy.class); when(serverStoreProxy.get(anyLong())).thenThrow(new RuntimeException()); TestTimeSource testTimeSource = mock(TestTimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, chainResolver, serverStoreProxy, testTimeSource); store.get(1L); } @Test @SuppressWarnings("unchecked") public void testGetTimeout() throws Exception { ServerStoreProxy proxy = mock(ServerStoreProxy.class); long longKey = HashUtils.intHashToLong(new Long(1L).hashCode()); when(proxy.get(longKey)).thenThrow(TimeoutException.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(null, null, proxy, null); assertThat(store.get(1L), nullValue()); validateStats(store, EnumSet.of(StoreOperationOutcomes.GetOutcome.TIMEOUT)); } @Test public void testGetThatCompactsInvokesReplace() throws Exception { TestTimeSource timeSource = new TestTimeSource(); timeSource.advanceTime(134556L); long now = timeSource.getTimeMillis(); @SuppressWarnings("unchecked") OperationsCodec<Long, String> operationsCodec = new OperationsCodec<Long, String>(new LongSerializer(), new StringSerializer()); @SuppressWarnings("unchecked") ChainResolver<Long, String> chainResolver = mock(ChainResolver.class); @SuppressWarnings("unchecked") ResolvedChain<Long, String> resolvedChain = mock(ResolvedChain.class); when(resolvedChain.isCompacted()).thenReturn(true); when(chainResolver.resolve(any(Chain.class), eq(42L), eq(now))).thenReturn(resolvedChain); ServerStoreProxy serverStoreProxy = mock(ServerStoreProxy.class); Chain chain = mock(Chain.class); when(chain.isEmpty()).thenReturn(false); long longKey = HashUtils.intHashToLong(new Long(42L).hashCode()); when(serverStoreProxy.get(longKey)).thenReturn(chain); ClusteredStore<Long, String> clusteredStore = new ClusteredStore<Long, String>(operationsCodec, chainResolver, serverStoreProxy, timeSource); clusteredStore.get(42L); verify(serverStoreProxy).replaceAtHead(eq(longKey), eq(chain), any(Chain.class)); } @Test public void testGetThatDoesNotCompactsInvokesReplace() throws Exception { TestTimeSource timeSource = new TestTimeSource(); timeSource.advanceTime(134556L); long now = timeSource.getTimeMillis(); OperationsCodec<Long, String> operationsCodec = new OperationsCodec<Long, String>(new LongSerializer(), new StringSerializer()); @SuppressWarnings("unchecked") ChainResolver<Long, String> chainResolver = mock(ChainResolver.class); @SuppressWarnings("unchecked") ResolvedChain<Long, String> resolvedChain = mock(ResolvedChain.class); when(resolvedChain.isCompacted()).thenReturn(false); when(chainResolver.resolve(any(Chain.class), eq(42L), eq(now))).thenReturn(resolvedChain); ServerStoreProxy serverStoreProxy = mock(ServerStoreProxy.class); Chain chain = mock(Chain.class); when(chain.isEmpty()).thenReturn(false); long longKey = HashUtils.intHashToLong(new Long(42L).hashCode()); when(serverStoreProxy.get(longKey)).thenReturn(chain); ClusteredStore<Long, String> clusteredStore = new ClusteredStore<Long, String>(operationsCodec, chainResolver, serverStoreProxy, timeSource); clusteredStore.get(42L); verify(serverStoreProxy, never()).replaceAtHead(eq(longKey), eq(chain), any(Chain.class)); } @Test public void testContainsKey() throws Exception { assertThat(store.containsKey(1L), is(false)); store.put(1L, "one"); assertThat(store.containsKey(1L), is(true)); validateStat(store, StoreOperationOutcomes.GetOutcome.HIT, 0); validateStat(store, StoreOperationOutcomes.GetOutcome.MISS, 0); } @Test(expected = StoreAccessException.class) public void testContainsKeyThrowsOnlySAE() throws Exception { @SuppressWarnings("unchecked") OperationsCodec<Long, String> codec = mock(OperationsCodec.class); @SuppressWarnings("unchecked") ChainResolver<Long, String> chainResolver = mock(ChainResolver.class); ServerStoreProxy serverStoreProxy = mock(ServerStoreProxy.class); when(serverStoreProxy.get(anyLong())).thenThrow(new RuntimeException()); TestTimeSource testTimeSource = mock(TestTimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, chainResolver, serverStoreProxy, testTimeSource); store.containsKey(1L); } @Test public void testRemove() throws Exception { assertThat(store.remove(1L), is(false)); validateStats(store, EnumSet.of(StoreOperationOutcomes.RemoveOutcome.MISS)); store.put(1L, "one"); assertThat(store.remove(1L), is(true)); assertThat(store.containsKey(1L), is(false)); validateStats(store, EnumSet.of(StoreOperationOutcomes.RemoveOutcome.MISS, StoreOperationOutcomes.RemoveOutcome.REMOVED)); } @Test(expected = StoreAccessException.class) public void testRemoveThrowsOnlySAE() throws Exception { @SuppressWarnings("unchecked") OperationsCodec<Long, String> codec = mock(OperationsCodec.class); @SuppressWarnings("unchecked") ChainResolver<Long, String> chainResolver = mock(ChainResolver.class); ServerStoreProxy serverStoreProxy = mock(ServerStoreProxy.class); when(serverStoreProxy.get(anyLong())).thenThrow(new RuntimeException()); TestTimeSource testTimeSource = mock(TestTimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, chainResolver, serverStoreProxy, testTimeSource); store.remove(1L); } @Test(expected = StoreAccessTimeoutException.class) @SuppressWarnings("unchecked") public void testRemoveTimeout() throws Exception { ServerStoreProxy proxy = mock(ServerStoreProxy.class); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenThrow(TimeoutException.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, null, proxy, timeSource); store.remove(1L); } @Test public void testClear() throws Exception { assertThat(store.containsKey(1L), is(false)); store.clear(); assertThat(store.containsKey(1L), is(false)); store.put(1L, "one"); store.put(2L, "two"); store.put(3L, "three"); assertThat(store.containsKey(1L), is(true)); store.clear(); assertThat(store.containsKey(1L), is(false)); assertThat(store.containsKey(2L), is(false)); assertThat(store.containsKey(3L), is(false)); } @Test(expected = StoreAccessException.class) public void testClearThrowsOnlySAE() throws Exception { @SuppressWarnings("unchecked") OperationsCodec<Long, String> codec = mock(OperationsCodec.class); @SuppressWarnings("unchecked") ChainResolver<Long, String> chainResolver = mock(ChainResolver.class); ServerStoreProxy serverStoreProxy = mock(ServerStoreProxy.class); doThrow(new RuntimeException()).when(serverStoreProxy).clear(); TestTimeSource testTimeSource = mock(TestTimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, chainResolver, serverStoreProxy, testTimeSource); store.clear(); } @Test(expected = StoreAccessTimeoutException.class) public void testClearTimeout() throws Exception { ServerStoreProxy proxy = mock(ServerStoreProxy.class); @SuppressWarnings("unchecked") OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); doThrow(TimeoutException.class).when(proxy).clear(); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, null, proxy, timeSource); store.clear(); } @Test public void testPutIfAbsent() throws Exception { assertThat(store.putIfAbsent(1L, "one"), nullValue()); validateStats(store, EnumSet.of(StoreOperationOutcomes.PutIfAbsentOutcome.PUT)); assertThat(store.putIfAbsent(1L, "another one").value(), is("one")); validateStats(store, EnumSet.of(StoreOperationOutcomes.PutIfAbsentOutcome.PUT, StoreOperationOutcomes.PutIfAbsentOutcome.HIT)); } @Test(expected = StoreAccessException.class) public void testPutIfAbsentThrowsOnlySAE() throws Exception { @SuppressWarnings("unchecked") OperationsCodec<Long, String> codec = mock(OperationsCodec.class); @SuppressWarnings("unchecked") ChainResolver<Long, String> chainResolver = mock(ChainResolver.class); ServerStoreProxy serverStoreProxy = mock(ServerStoreProxy.class); when(serverStoreProxy.get(anyLong())).thenThrow(new RuntimeException()); TestTimeSource testTimeSource = mock(TestTimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, chainResolver, serverStoreProxy, testTimeSource); store.putIfAbsent(1L, "one"); } @Test(expected = StoreAccessTimeoutException.class) @SuppressWarnings("unchecked") public void testPutIfAbsentTimeout() throws Exception { ServerStoreProxy proxy = mock(ServerStoreProxy.class); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenThrow(TimeoutException.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, null, proxy, timeSource); store.putIfAbsent(1L, "one"); } @Test public void testConditionalRemove() throws Exception { assertThat(store.remove(1L, "one"), is(Store.RemoveStatus.KEY_MISSING)); validateStats(store, EnumSet.of(StoreOperationOutcomes.ConditionalRemoveOutcome.MISS)); store.put(1L, "one"); assertThat(store.remove(1L, "one"), is(Store.RemoveStatus.REMOVED)); validateStats(store, EnumSet.of(StoreOperationOutcomes.ConditionalRemoveOutcome.MISS, StoreOperationOutcomes.ConditionalRemoveOutcome.REMOVED)); store.put(1L, "another one"); assertThat(store.remove(1L, "one"), is(Store.RemoveStatus.KEY_PRESENT)); validateStat(store, StoreOperationOutcomes.ConditionalRemoveOutcome.MISS, 2); } @Test(expected = StoreAccessException.class) public void testConditionalRemoveThrowsOnlySAE() throws Exception { @SuppressWarnings("unchecked") OperationsCodec<Long, String> codec = mock(OperationsCodec.class); @SuppressWarnings("unchecked") ChainResolver<Long, String> chainResolver = mock(ChainResolver.class); ServerStoreProxy serverStoreProxy = mock(ServerStoreProxy.class); when(serverStoreProxy.get(anyLong())).thenThrow(new RuntimeException()); TestTimeSource testTimeSource = mock(TestTimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, chainResolver, serverStoreProxy, testTimeSource); store.remove(1L, "one"); } @Test(expected = StoreAccessTimeoutException.class) @SuppressWarnings("unchecked") public void testConditionalRemoveTimeout() throws Exception { ServerStoreProxy proxy = mock(ServerStoreProxy.class); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenThrow(TimeoutException.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, null, proxy, timeSource); store.remove(1L, "one"); } @Test public void testReplace() throws Exception { assertThat(store.replace(1L, "one"), nullValue()); validateStats(store, EnumSet.of(StoreOperationOutcomes.ReplaceOutcome.MISS)); store.put(1L, "one"); assertThat(store.replace(1L, "another one").value(), is("one")); validateStats(store, EnumSet.of(StoreOperationOutcomes.ReplaceOutcome.MISS, StoreOperationOutcomes.ReplaceOutcome.REPLACED)); } @Test(expected = StoreAccessException.class) public void testReplaceThrowsOnlySAE() throws Exception { @SuppressWarnings("unchecked") OperationsCodec<Long, String> codec = mock(OperationsCodec.class); @SuppressWarnings("unchecked") ChainResolver<Long, String> chainResolver = mock(ChainResolver.class); ServerStoreProxy serverStoreProxy = mock(ServerStoreProxy.class); when(serverStoreProxy.get(anyLong())).thenThrow(new RuntimeException()); TestTimeSource testTimeSource = mock(TestTimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, chainResolver, serverStoreProxy, testTimeSource); store.replace(1L, "one"); } @Test(expected = StoreAccessTimeoutException.class) @SuppressWarnings("unchecked") public void testReplaceTimeout() throws Exception { ServerStoreProxy proxy = mock(ServerStoreProxy.class); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenThrow(TimeoutException.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, null, proxy, timeSource); store.replace(1L, "one"); } @Test public void testConditionalReplace() throws Exception { assertThat(store.replace(1L, "one" , "another one"), is(Store.ReplaceStatus.MISS_NOT_PRESENT)); validateStats(store, EnumSet.of(StoreOperationOutcomes.ConditionalReplaceOutcome.MISS)); store.put(1L, "some other one"); assertThat(store.replace(1L, "one" , "another one"), is(Store.ReplaceStatus.MISS_PRESENT)); validateStat(store, StoreOperationOutcomes.ConditionalReplaceOutcome.MISS, 2); validateStat(store, StoreOperationOutcomes.ConditionalReplaceOutcome.REPLACED, 0); assertThat(store.replace(1L, "some other one" , "another one"), is(Store.ReplaceStatus.HIT)); validateStat(store, StoreOperationOutcomes.ConditionalReplaceOutcome.REPLACED, 1); validateStat(store, StoreOperationOutcomes.ConditionalReplaceOutcome.MISS, 2); } @Test(expected = StoreAccessException.class) public void testConditionalReplaceThrowsOnlySAE() throws Exception { @SuppressWarnings("unchecked") OperationsCodec<Long, String> codec = mock(OperationsCodec.class); @SuppressWarnings("unchecked") ChainResolver<Long, String> chainResolver = mock(ChainResolver.class); ServerStoreProxy serverStoreProxy = mock(ServerStoreProxy.class); when(serverStoreProxy.get(anyLong())).thenThrow(new RuntimeException()); TestTimeSource testTimeSource = mock(TestTimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, chainResolver, serverStoreProxy, testTimeSource); store.replace(1L, "one", "another one"); } @Test(expected = StoreAccessTimeoutException.class) @SuppressWarnings("unchecked") public void testConditionalReplaceTimeout() throws Exception { ServerStoreProxy proxy = mock(ServerStoreProxy.class); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenThrow(TimeoutException.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, null, proxy, timeSource); store.replace(1L, "one", "another one"); } @Test public void testBulkComputePutAll() throws Exception { store.put(1L, "another one"); Map<Long, String> map = new HashMap<Long, String>(); map.put(1L, "one"); map.put(2L, "two"); Ehcache.PutAllFunction<Long, String> putAllFunction = new Ehcache.PutAllFunction<Long, String>(null, map, null); Map<Long, Store.ValueHolder<String>> valueHolderMap = store.bulkCompute(new HashSet<Long>(Arrays.asList(1L, 2L)), putAllFunction); assertThat(valueHolderMap.get(1L).value(), is(map.get(1L))); assertThat(store.get(1L).value(), is(map.get(1L))); assertThat(valueHolderMap.get(2L).value(), is(map.get(2L))); assertThat(store.get(2L).value(), is(map.get(2L))); assertThat(putAllFunction.getActualPutCount().get(), is(2)); validateStats(store, EnumSet.of(StoreOperationOutcomes.PutOutcome.PUT)); //outcome of the initial store put } @Test public void testBulkComputeRemoveAll() throws Exception { store.put(1L, "one"); store.put(2L, "two"); store.put(3L, "three"); Ehcache.RemoveAllFunction<Long, String> removeAllFunction = new Ehcache.RemoveAllFunction<Long, String>(); Map<Long, Store.ValueHolder<String>> valueHolderMap = store.bulkCompute(new HashSet<Long>(Arrays.asList(1L, 2L, 4L)), removeAllFunction); assertThat(valueHolderMap.get(1L), nullValue()); assertThat(store.get(1L), nullValue()); assertThat(valueHolderMap.get(2L), nullValue()); assertThat(store.get(2L), nullValue()); assertThat(valueHolderMap.get(4L), nullValue()); assertThat(store.get(4L), nullValue()); validateStats(store, EnumSet.noneOf(StoreOperationOutcomes.RemoveOutcome.class)); } @Test(expected = UnsupportedOperationException.class) public void testBulkComputeThrowsForGenericFunction() throws Exception { @SuppressWarnings("unchecked") Function<Iterable<? extends Map.Entry<? extends Long, ? extends String>>, Iterable<? extends Map.Entry<? extends Long, ? extends String>>> remappingFunction = mock(Function.class); store.bulkCompute(new HashSet<Long>(Arrays.asList(1L, 2L)), remappingFunction); } @Test public void testBulkComputeIfAbsentGetAll() throws Exception { store.put(1L, "one"); store.put(2L, "two"); Ehcache.GetAllFunction<Long, String> getAllAllFunction = new Ehcache.GetAllFunction<Long, String>(); Map<Long, Store.ValueHolder<String>> valueHolderMap = store.bulkComputeIfAbsent(new HashSet<Long>(Arrays.asList(1L, 2L)), getAllAllFunction); assertThat(valueHolderMap.get(1L).value(), is("one")); assertThat(store.get(1L).value(), is("one")); assertThat(valueHolderMap.get(2L).value(), is("two")); assertThat(store.get(2L).value(), is("two")); } @Test(expected = UnsupportedOperationException.class) public void testBulkComputeIfAbsentThrowsForGenericFunction() throws Exception { @SuppressWarnings("unchecked") Function<Iterable<? extends Long>, Iterable<? extends Map.Entry<? extends Long, ? extends String>>> mappingFunction = mock(Function.class); store.bulkComputeIfAbsent(new HashSet<Long>(Arrays.asList(1L, 2L)), mappingFunction); } @Test @SuppressWarnings("unchecked") public void testPutReplacesChainOnlyOnCompressionThreshold() throws Exception { ResolvedChain resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(mock(Result.class)); when(resolvedChain.getCompactedChain()).thenReturn(mock(Chain.class)); ServerStoreProxy proxy = mock(ServerStoreProxy.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenReturn(mock(Chain.class)); ChainResolver resolver = mock(ChainResolver.class); when(resolver.resolve(any(Chain.class), anyInt(), anyLong())).thenReturn(resolvedChain); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, resolver, proxy, timeSource); when(resolvedChain.getCompactionCount()).thenReturn(DEFAULT_CHAIN_COMPACTION_THRESHOLD - 1); // less than the default threshold store.put(1L, "one"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); when(resolvedChain.getCompactionCount()).thenReturn(DEFAULT_CHAIN_COMPACTION_THRESHOLD); // equal to the default threshold store.put(1L, "one"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); when(resolvedChain.getCompactionCount()).thenReturn(DEFAULT_CHAIN_COMPACTION_THRESHOLD + 1); // greater than the default threshold store.put(1L, "one"); verify(proxy).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); } @Test @SuppressWarnings("unchecked") public void testPutIfAbsentReplacesChainOnlyOnCompressionThreshold() throws Exception { Result result = mock(Result.class); when(result.getValue()).thenReturn("one"); ResolvedChain resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(result); when(resolvedChain.getCompactedChain()).thenReturn(mock(Chain.class)); ServerStoreProxy proxy = mock(ServerStoreProxy.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenReturn(mock(Chain.class)); ChainResolver resolver = mock(ChainResolver.class); when(resolver.resolve(any(Chain.class), anyInt(), anyLong())).thenReturn(resolvedChain); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, resolver, proxy, timeSource); when(resolvedChain.getCompactionCount()).thenReturn(DEFAULT_CHAIN_COMPACTION_THRESHOLD - 1); // less than the default threshold store.putIfAbsent(1L, "one"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); when(resolvedChain.getCompactionCount()).thenReturn(DEFAULT_CHAIN_COMPACTION_THRESHOLD); // equal to the default threshold store.putIfAbsent(1L, "one"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); when(resolvedChain.getCompactionCount()).thenReturn(DEFAULT_CHAIN_COMPACTION_THRESHOLD + 1); // greater than the default threshold store.putIfAbsent(1L, "one"); verify(proxy).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); } @Test @SuppressWarnings("unchecked") public void testReplaceReplacesChainOnlyOnCompressionThreshold() throws Exception { Result result = mock(Result.class); when(result.getValue()).thenReturn("one"); ResolvedChain resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(result); when(resolvedChain.getCompactedChain()).thenReturn(mock(Chain.class)); ServerStoreProxy proxy = mock(ServerStoreProxy.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenReturn(mock(Chain.class)); ChainResolver resolver = mock(ChainResolver.class); when(resolver.resolve(any(Chain.class), anyInt(), anyLong())).thenReturn(resolvedChain); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, resolver, proxy, timeSource); when(resolvedChain.getCompactionCount()).thenReturn(DEFAULT_CHAIN_COMPACTION_THRESHOLD - 1); // less than the default threshold store.replace(1L, "one"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); when(resolvedChain.getCompactionCount()).thenReturn(DEFAULT_CHAIN_COMPACTION_THRESHOLD); // equal to the default threshold store.replace(1L, "one"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); when(resolvedChain.getCompactionCount()).thenReturn(DEFAULT_CHAIN_COMPACTION_THRESHOLD + 1); // greater than the default threshold store.replace(1L, "one"); verify(proxy).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); } @Test @SuppressWarnings("unchecked") public void testConditionalReplaceReplacesChainOnlyOnCompressionThreshold() throws Exception { ResolvedChain resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(mock(Result.class)); when(resolvedChain.getCompactedChain()).thenReturn(mock(Chain.class)); ServerStoreProxy proxy = mock(ServerStoreProxy.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenReturn(mock(Chain.class)); ChainResolver resolver = mock(ChainResolver.class); when(resolver.resolve(any(Chain.class), anyInt(), anyLong())).thenReturn(resolvedChain); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, resolver, proxy, timeSource); when(resolvedChain.getCompactionCount()).thenReturn(DEFAULT_CHAIN_COMPACTION_THRESHOLD - 1); // less than the default threshold store.replace(1L, "one", "anotherOne"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); when(resolvedChain.getCompactionCount()).thenReturn(DEFAULT_CHAIN_COMPACTION_THRESHOLD); // equal to the default threshold store.replace(1L, "one", "anotherOne"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); when(resolvedChain.getCompactionCount()).thenReturn(DEFAULT_CHAIN_COMPACTION_THRESHOLD + 1); // greater than the default threshold store.replace(1L, "one", "anotherOne"); verify(proxy).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); } @Test @SuppressWarnings("unchecked") public void testCustomCompressionThreshold() throws Exception { int customThreshold = 4; try { System.setProperty(CHAIN_COMPACTION_THRESHOLD_PROP, String.valueOf(customThreshold)); ResolvedChain resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(mock(Result.class)); when(resolvedChain.getCompactedChain()).thenReturn(mock(Chain.class)); ServerStoreProxy proxy = mock(ServerStoreProxy.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenReturn(mock(Chain.class)); ChainResolver resolver = mock(ChainResolver.class); when(resolver.resolve(any(Chain.class), anyInt(), anyLong())).thenReturn(resolvedChain); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, resolver, proxy, timeSource); when(resolvedChain.getCompactionCount()).thenReturn(customThreshold - 1); // less than the custom threshold store.put(1L, "one"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); when(resolvedChain.getCompactionCount()).thenReturn(customThreshold); // equal to the custom threshold store.put(1L, "one"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); when(resolvedChain.getCompactionCount()).thenReturn(customThreshold + 1); // greater than the custom threshold store.put(1L, "one"); verify(proxy).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); } finally { System.clearProperty(CHAIN_COMPACTION_THRESHOLD_PROP); } } @Test @SuppressWarnings("unchecked") public void testRemoveReplacesChainOnHits() throws Exception { ResolvedChain resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(mock(Result.class)); //simulate a key hit on chain resolution when(resolvedChain.getCompactionCount()).thenReturn(1); ServerStoreProxy proxy = mock(ServerStoreProxy.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenReturn(mock(Chain.class)); ChainResolver resolver = mock(ChainResolver.class); when(resolver.resolve(any(Chain.class), anyInt(), anyLong())).thenReturn(resolvedChain); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, resolver, proxy, timeSource); store.remove(1L); verify(proxy).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); } @Test @SuppressWarnings("unchecked") public void testRemoveDoesNotReplaceChainOnMisses() throws Exception { ResolvedChain resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(null); //simulate a key miss on chain resolution ChainResolver resolver = mock(ChainResolver.class); when(resolver.resolve(any(Chain.class), anyInt(), anyLong())).thenReturn(resolvedChain); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); ServerStoreProxy proxy = mock(ServerStoreProxy.class); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, resolver, proxy, timeSource); store.remove(1L); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); } @Test @SuppressWarnings("unchecked") public void testConditionalRemoveReplacesChainOnHits() throws Exception { Result result = mock(Result.class); when(result.getValue()).thenReturn("foo"); ResolvedChain resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(result); //simulate a key hit on chain resolution when(resolvedChain.getCompactionCount()).thenReturn(1); ServerStoreProxy proxy = mock(ServerStoreProxy.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenReturn(mock(Chain.class)); ChainResolver resolver = mock(ChainResolver.class); when(resolver.resolve(any(Chain.class), anyInt(), anyLong())).thenReturn(resolvedChain); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, resolver, proxy, timeSource); store.remove(1L, "foo"); verify(proxy).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); } @Test @SuppressWarnings("unchecked") public void testConditionalRemoveDoesNotReplaceChainOnKeyMiss() throws Exception { ResolvedChain resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(null); //simulate a key miss on chain resolution ChainResolver resolver = mock(ChainResolver.class); when(resolver.resolve(any(Chain.class), anyInt(), anyLong())).thenReturn(resolvedChain); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); ServerStoreProxy proxy = mock(ServerStoreProxy.class); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, resolver, proxy, timeSource); store.remove(1L, "foo"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); } @Test @SuppressWarnings("unchecked") public void testConditionalRemoveDoesNotReplaceChainOnKeyHitValueMiss() throws Exception { Result result = mock(Result.class); ResolvedChain resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(result); //simulate a key kit when(result.getValue()).thenReturn("bar"); //but a value miss ChainResolver resolver = mock(ChainResolver.class); when(resolver.resolve(any(Chain.class), anyInt(), anyLong())).thenReturn(resolvedChain); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); ServerStoreProxy proxy = mock(ServerStoreProxy.class); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<Long, String>(codec, resolver, proxy, timeSource); store.remove(1L, "foo"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); } }